archat-server/server/server.go

588 lines
14 KiB
Go
Raw Normal View History

package server
import (
2024-04-29 00:02:51 +00:00
"context"
"encoding/json"
2024-04-29 00:02:51 +00:00
"errors"
2024-03-24 13:09:36 +00:00
"net/http"
2024-03-24 16:10:02 +00:00
"os"
2024-04-29 00:02:51 +00:00
"slices"
2024-03-24 13:09:36 +00:00
"strings"
"sync"
"time"
2024-04-29 00:02:51 +00:00
"golang.org/x/sync/errgroup"
2024-03-24 16:10:02 +00:00
"github.com/charmbracelet/log"
2024-03-24 13:09:36 +00:00
"github.com/gorilla/websocket"
"golang.org/x/crypto/bcrypt"
2024-04-29 00:02:51 +00:00
"krzyzanowski.dev/archat/common"
)
2024-04-29 00:02:51 +00:00
type Peer struct {
id int
conn *websocket.Conn
hasAccount bool
account *Account
}
func NewPeer(conn *websocket.Conn) *Peer {
return &Peer{-1, conn, false, nil}
}
type Account struct {
nickname string
passHash []byte
}
2024-04-29 00:02:51 +00:00
const (
InitiationStageA = 1
InitiationStageB = 2
InitiationStageC = 3
InitiationStageD = 4
)
type Initiation struct {
abANick string
abBNick string
stage int
}
func NewInitiation(abA string, abB string) *Initiation {
return &Initiation{abA, abB, InitiationStageA}
}
type Context struct {
2024-04-29 00:02:51 +00:00
idCounter int
idCounterLock sync.RWMutex
peersList []*Peer
peersListLock sync.RWMutex
accounts map[string]*Account
accountsLock sync.RWMutex
initiations []*Initiation
initiationsLock sync.RWMutex
handlerContexts []*HandlerContext
handlerContextsLock sync.RWMutex
}
func NewContext() *Context {
return &Context{
peersList: make([]*Peer, 0),
accounts: make(map[string]*Account),
initiations: make([]*Initiation, 0),
}
}
// Remember to lock before calling
func (ctx *Context) getPeerByNick(nick string) (*Peer, error) {
for _, peer := range ctx.peersList {
if peer.hasAccount && peer.account.nickname == nick {
return peer, nil
}
}
return nil, errors.New("peer not found")
}
// Remember to lock before calling
func (ctx *Context) getCtxByNick(nick string) (*HandlerContext, error) {
idx := slices.IndexFunc[[]*HandlerContext, *HandlerContext](
ctx.handlerContexts,
func(handlerContext *HandlerContext) bool {
return handlerContext.peer.hasAccount && handlerContext.peer.account.nickname == nick
})
if idx != -1 {
return ctx.handlerContexts[idx], nil
}
return nil, errors.New("not found")
}
type HandlerContext struct {
2024-03-24 13:09:36 +00:00
peer *Peer
*Context
resFromClient chan common.RFrame
reqFromClient chan common.RFrame
rToClient chan common.RFrame
}
func NewHandlerContext(peer *Peer, srvCtx *Context) *HandlerContext {
return &HandlerContext{
peer,
srvCtx,
make(chan common.RFrame),
make(chan common.RFrame),
make(chan common.RFrame),
}
}
2024-04-29 00:02:51 +00:00
func (hdlCtx *HandlerContext) clientHandler(syncCtx context.Context) error {
handleNext:
for {
2024-04-29 00:02:51 +00:00
select {
case <-syncCtx.Done():
return nil
case reqFrame := <-hdlCtx.reqFromClient:
var res common.Response
var err error
if reqFrame.ID == common.AuthReqID {
res, err = hdlCtx.handleAuth(&reqFrame)
} else if reqFrame.ID == common.ListPeersReqID {
res, err = hdlCtx.handleListPeers(&reqFrame)
} else if reqFrame.ID == common.EchoReqID {
res, err = hdlCtx.handleEcho(&reqFrame)
} else if reqFrame.ID == common.StartChatAReqID {
res, err = hdlCtx.handleChatStartA(&reqFrame)
}
2024-04-29 00:02:51 +00:00
if err != nil {
logger.Errorf("could not handle request ID=%d", reqFrame.ID)
return err
}
2024-04-29 00:02:51 +00:00
if res == nil {
logger.Debugf("request without response ID=%d", reqFrame.ID)
continue handleNext
}
2024-04-29 00:02:51 +00:00
resFrame, err := common.ResponseFrameFrom(res)
2024-04-29 00:02:51 +00:00
if err != nil {
logger.Errorf("could not create frame from response")
return err
}
hdlCtx.rToClient <- resFrame
}
}
}
2024-04-29 00:02:51 +00:00
func (hdlCtx *HandlerContext) clientWriter(syncCtx context.Context) error {
for {
2024-04-29 00:02:51 +00:00
select {
case <-syncCtx.Done():
return nil
case rFrame := <-hdlCtx.rToClient:
resJsonBytes, err := json.Marshal(rFrame)
2024-04-29 00:02:51 +00:00
if err != nil {
logger.Errorf("error marshalling frame to json")
return err
}
2024-04-29 00:02:51 +00:00
logger.Debugf("sending %s", string(resJsonBytes))
err = hdlCtx.peer.conn.WriteMessage(websocket.TextMessage, resJsonBytes)
2024-04-29 00:02:51 +00:00
if err != nil {
logger.Errorf("error writing rframe")
return err
}
}
}
}
2024-04-29 00:02:51 +00:00
func (hdlCtx *HandlerContext) clientReader(syncCtx context.Context) error {
for {
2024-04-29 00:02:51 +00:00
select {
case <-syncCtx.Done():
return nil
default:
messType, messBytes, err := hdlCtx.peer.conn.ReadMessage()
if err != nil {
return err
}
2024-04-29 00:02:51 +00:00
if messType != 1 {
err := hdlCtx.peer.conn.WriteMessage(websocket.CloseUnsupportedData, []byte("Only JSON text is supported"))
if err != nil {
logger.Debugf("[Server] error sending unsupported data close message")
return err
}
}
2024-04-29 00:02:51 +00:00
logger.Debugf("got message text: %s", strings.Trim(string(messBytes), "\n"))
var rFrame common.RFrame
err = json.Unmarshal(messBytes, &rFrame)
if err != nil {
return err
}
2024-04-29 00:02:51 +00:00
logger.Debugf("unmarshalled request frame (ID=%d)", rFrame.ID)
2024-04-29 00:02:51 +00:00
if rFrame.IsResponse() {
logger.Debug("it is response frame", "id", rFrame.ID)
hdlCtx.resFromClient <- rFrame
} else {
logger.Debug("it is request frame", "id", rFrame.ID)
hdlCtx.reqFromClient <- rFrame
}
}
}
}
func (hdlCtx *HandlerContext) sendRequest(req common.Request) error {
rf, err := common.RequestFrameFrom(req)
if err != nil {
return err
}
hdlCtx.rToClient <- rf
return nil
}
func (hdlCtx *HandlerContext) getResponseFrame() common.RFrame {
return <-hdlCtx.resFromClient
}
2024-03-24 16:10:02 +00:00
var logger = log.NewWithOptions(os.Stdout, log.Options{
ReportTimestamp: true,
TimeFormat: time.TimeOnly,
Prefix: "⚙️ Server",
})
func init() {
if common.IsProd {
logger.SetLevel(log.InfoLevel)
} else {
logger.SetLevel(log.DebugLevel)
}
}
2024-04-29 00:02:51 +00:00
type Matcher[T any] func(*T) bool
2024-03-24 13:09:36 +00:00
2024-04-29 00:02:51 +00:00
func (ctx *Context) removePeer(peer *Peer) {
ctx.handlerContextsLock.Lock()
ctx.peersListLock.Lock()
ctx.initiationsLock.Lock()
2024-04-29 00:02:51 +00:00
ctx.handlerContexts = slices.DeleteFunc[[]*HandlerContext, *HandlerContext](
ctx.handlerContexts,
func(h *HandlerContext) bool {
return h.peer.id == peer.id
})
2024-04-29 00:02:51 +00:00
ctx.peersList = slices.DeleteFunc[[]*Peer, *Peer](
ctx.peersList,
func(p *Peer) bool {
return p.id == peer.id
})
2024-04-29 00:02:51 +00:00
ctx.initiations = slices.DeleteFunc[[]*Initiation, *Initiation](
ctx.initiations,
func(i *Initiation) bool {
return peer.hasAccount && (peer.account.nickname == i.abANick || peer.account.nickname == i.abBNick)
})
2024-04-29 00:02:51 +00:00
// TODO: Inform the other side about peer leaving
ctx.handlerContextsLock.Unlock()
ctx.peersListLock.Unlock()
ctx.initiationsLock.Unlock()
}
2024-03-24 13:09:36 +00:00
func handleDisconnection(handlerCtx *HandlerContext) {
handlerCtx.removePeer(handlerCtx.peer)
2024-03-24 16:10:02 +00:00
logger.Infof("%s disconnected", handlerCtx.peer.conn.RemoteAddr())
}
func (hdlCtx *HandlerContext) handleEcho(reqFrame *common.RFrame) (res common.Response, err error) {
2024-03-24 13:09:36 +00:00
echoReq, err := common.RequestFromFrame[common.EchoRequest](*reqFrame)
if err != nil {
2024-03-24 16:10:02 +00:00
logger.Error("could not read request from frame")
return nil, err
}
echoRes := common.EchoResponse(echoReq)
2024-03-24 13:09:36 +00:00
return echoRes, nil
}
func (hdlCtx *HandlerContext) handleListPeers(reqFrame *common.RFrame) (res common.Response, err error) {
2024-03-24 13:09:36 +00:00
// Currently list peers request is empty, so we can ignore it - we won't use it
_, err = common.RequestFromFrame[common.ListPeersRequest](*reqFrame)
if err != nil {
2024-03-24 16:10:02 +00:00
logger.Error("could not read request from frame")
return nil, err
}
2024-03-24 13:09:36 +00:00
hdlCtx.peersListLock.RLock()
peersFreeze := make([]*Peer, len(hdlCtx.peersList))
copy(peersFreeze, hdlCtx.peersList)
hdlCtx.peersListLock.RUnlock()
listPeersRes := common.ListPeersResponse{PeersInfo: make([]common.PeerInfo, 0)}
for _, peer := range peersFreeze {
2024-04-29 00:02:51 +00:00
var nickname string
if peer.hasAccount {
nickname = peer.account.nickname
} else {
nickname = ""
}
listPeersRes.PeersInfo = append(
listPeersRes.PeersInfo,
common.PeerInfo{
ID: peer.id,
Addr: peer.conn.RemoteAddr().String(),
HasNickname: peer.hasAccount,
2024-04-29 00:02:51 +00:00
Nickname: nickname,
},
)
}
2024-03-24 13:09:36 +00:00
return listPeersRes, nil
}
func (hdlCtx *HandlerContext) handleAuth(reqFrame *common.RFrame) (res common.Response, err error) {
2024-03-24 13:09:36 +00:00
authReq, err := common.RequestFromFrame[common.AuthRequest](*reqFrame)
if err != nil {
2024-03-24 16:10:02 +00:00
logger.Error("could not read request from frame")
return nil, err
}
// Check if account already exists
2024-03-24 13:09:36 +00:00
hdlCtx.accountsLock.RLock()
account, ok := hdlCtx.accounts[authReq.Nickname]
hdlCtx.accountsLock.RUnlock()
var authRes *common.AuthResponse
if ok {
// Check if password matches
if bcrypt.CompareHashAndPassword(account.passHash, []byte(authReq.Password)) == nil {
2024-03-24 13:09:36 +00:00
authRes = &common.AuthResponse{IsSuccess: true}
hdlCtx.peersListLock.Lock()
hdlCtx.peer.hasAccount = true
hdlCtx.peer.account = account
hdlCtx.peersListLock.Unlock()
} else {
2024-03-24 13:09:36 +00:00
authRes = &common.AuthResponse{IsSuccess: false}
}
} else {
2024-03-24 13:09:36 +00:00
authRes = &common.AuthResponse{IsSuccess: true}
passHash, err := bcrypt.GenerateFromPassword([]byte(authReq.Password), bcrypt.DefaultCost)
if err != nil {
2024-03-24 13:09:36 +00:00
authRes = &common.AuthResponse{IsSuccess: false}
} else {
newAcc := Account{authReq.Nickname, passHash}
2024-03-24 13:09:36 +00:00
hdlCtx.accountsLock.Lock()
hdlCtx.accounts[newAcc.nickname] = &newAcc
hdlCtx.accountsLock.Unlock()
hdlCtx.peersListLock.Lock()
hdlCtx.peer.hasAccount = true
hdlCtx.peer.account = &newAcc
hdlCtx.peersListLock.Unlock()
}
}
2024-03-24 13:09:36 +00:00
return authRes, nil
}
2024-04-29 00:02:51 +00:00
func (hdlCtx *HandlerContext) handleChatStartA(reqFrame *common.RFrame) (res common.Response, err error) {
startChatAReq, err := common.RequestFromFrame[common.StartChatARequest](*reqFrame)
if err != nil {
return nil, err
}
receiverPeerCtx, err := hdlCtx.getCtxByNick(startChatAReq.Nickname)
if err != nil {
logger.Debug("receiver peer not found")
return nil, nil
}
// initation started
hdlCtx.initiationsLock.Lock()
hdlCtx.initiations = append(hdlCtx.initiations, NewInitiation(hdlCtx.peer.account.nickname, startChatAReq.Nickname))
hdlCtx.initiationsLock.Unlock()
chatStartB := common.StartChatBRequest{
Nickname: hdlCtx.peer.account.nickname,
}
chatStartBReqF, err := common.RequestFrameFrom(chatStartB)
if err != nil {
logger.Debug("chat start B req frame creation failed")
return nil, err
}
receiverPeerCtx.rToClient <- chatStartBReqF
hdlCtx.initiationsLock.Lock()
//
hdlCtx.initiationsLock.Unlock()
return nil, nil
}
func (hdlCtx *HandlerContext) handleChatStartC(reqFrame *common.RFrame) (res common.Response, err error) {
startChatAReq, err := common.RequestFromFrame[common.StartChatARequest](*reqFrame)
if err != nil {
return nil, err
}
receiverPeerCtx, err := hdlCtx.getCtxByNick(startChatAReq.Nickname)
if err != nil {
logger.Debug("receiver peer not found")
return nil, nil
}
// initation started
hdlCtx.initiationsLock.Lock()
hdlCtx.initiations = append(hdlCtx.initiations, NewInitiation(hdlCtx.peer.account.nickname, startChatAReq.Nickname))
hdlCtx.initiationsLock.Unlock()
chatStartB := common.StartChatBRequest{
Nickname: hdlCtx.peer.account.nickname,
}
chatStartBReqF, err := common.RequestFrameFrom(chatStartB)
if err != nil {
logger.Debug("chat start B req frame creation failed")
return nil, err
}
receiverPeerCtx.rToClient <- chatStartBReqF
return nil, nil
}
func (ctx *Context) printDebugInfo() {
ctx.peersListLock.RLock()
logger.Debug("================================ server state")
2024-03-24 16:10:02 +00:00
logger.Debug("displaying all connections:")
2024-04-29 00:02:51 +00:00
for _, p := range ctx.peersList {
nick := "-"
if p.hasAccount {
nick = p.account.nickname
}
logger.Debugf("ID#%d, Addr:%s, Auth:%t, Nick:%s", p.id, p.conn.RemoteAddr(), p.hasAccount, nick)
}
2024-04-29 00:02:51 +00:00
logger.Debug("displaying all initiations:")
for _, i := range ctx.initiations {
logger.Debugf("from %s to %s, stage: %d", i.abBNick, i.abBNick, i.stage)
}
ctx.peersListLock.RUnlock()
}
2024-04-29 00:02:51 +00:00
func (ctx *Context) addPeer(peer *Peer) {
ctx.idCounterLock.Lock()
ctx.idCounter++
peer.id = ctx.idCounter
ctx.idCounterLock.Unlock()
ctx.peersListLock.Lock()
ctx.peersList = append(ctx.peersList, peer)
ctx.peersListLock.Unlock()
2024-03-24 13:09:36 +00:00
}
func testEcho(hdlCtx *HandlerContext) {
logger.Debug("sending echo request...")
_ = hdlCtx.sendRequest(common.EchoRequest{EchoByte: 123})
logger.Debug("sent")
echoResF := hdlCtx.getResponseFrame()
logger.Debug("got response")
echoRes, err := common.ResponseFromFrame[common.EchoResponse](echoResF)
if err != nil {
logger.Error(err)
return
}
logger.Debug("test echo done", "byteSent", 123, "byteReceived", echoRes.EchoByte)
}
2024-04-29 00:02:51 +00:00
func (ctx *Context) wsapiHandler(w http.ResponseWriter, r *http.Request) {
2024-03-24 13:09:36 +00:00
upgrader := websocket.Upgrader{}
conn, err := upgrader.Upgrade(w, r, nil)
2024-03-24 13:09:36 +00:00
if err != nil {
2024-03-24 16:10:02 +00:00
logger.Errorf("upgrade failed")
2024-03-24 13:09:36 +00:00
return
}
peer := NewPeer(conn)
2024-04-29 00:02:51 +00:00
ctx.addPeer(peer)
handlerCtx := NewHandlerContext(peer, ctx)
ctx.handlerContextsLock.Lock()
ctx.handlerContexts = append(ctx.handlerContexts, handlerCtx)
ctx.handlerContextsLock.Unlock()
2024-03-24 13:09:36 +00:00
defer handleDisconnection(handlerCtx)
defer func(conn *websocket.Conn) {
err := conn.Close()
if err != nil {
logger.Error(err)
}
}(conn)
logger.Infof("%s connected", conn.RemoteAddr())
2024-04-29 00:02:51 +00:00
errGroup, syncCtx := errgroup.WithContext(context.Background())
errGroup.Go(func() error {
return handlerCtx.clientHandler(syncCtx)
})
errGroup.Go(func() error {
return handlerCtx.clientWriter(syncCtx)
})
errGroup.Go(func() error {
return handlerCtx.clientReader(syncCtx)
})
errGroup.Go(func() error {
<-syncCtx.Done()
time.Sleep(time.Second * 3)
close(handlerCtx.rToClient)
close(handlerCtx.resFromClient)
close(handlerCtx.reqFromClient)
return conn.Close()
})
testEcho(handlerCtx)
err = errGroup.Wait()
if err != nil {
logger.Error(err)
return
}
}
2024-03-24 13:09:36 +00:00
func RunServer() {
2024-04-29 00:02:51 +00:00
srvCtx := NewContext()
2024-03-24 13:09:36 +00:00
go func() {
for {
2024-04-29 00:02:51 +00:00
srvCtx.printDebugInfo()
2024-03-24 13:09:36 +00:00
time.Sleep(time.Second * 5)
}
}()
http.HandleFunc("/wsapi", srvCtx.wsapiHandler)
2024-03-24 16:10:02 +00:00
logger.Info("Starting server...")
err := http.ListenAndServe(":8080", nil)
if err != nil {
logger.Error(err)
}
2024-03-24 13:09:36 +00:00
}