From 62e3ee780bd825dd25d92540612158fd84807146 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Krzy=C5=BCanowski?= Date: Mon, 29 Apr 2024 13:46:26 +0200 Subject: [PATCH] Finished implementation for B, C, D stages of initiation --- README.md | 1 + client/client.go | 144 +++++++++++++++++++++++++++++++++++++++-------- common/common.go | 19 ++++++- server/server.go | 133 +++++++++++++++++++++++++++++-------------- 4 files changed, 229 insertions(+), 68 deletions(-) diff --git a/README.md b/README.md index 27211a9..80ba639 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # 🔀 Archat *It is not working application yet, work in progress!* + Simple P2P server (and client - for testing purposes only, client for end user will be written in NodeJS using Electron, soon). ## Starting diff --git a/client/client.go b/client/client.go index fa1f934..a022c5b 100644 --- a/client/client.go +++ b/client/client.go @@ -7,8 +7,10 @@ import ( "golang.org/x/sync/errgroup" "net/url" "os" + "slices" "strconv" "strings" + "sync" "time" "github.com/charmbracelet/log" @@ -20,9 +22,11 @@ type Context struct { conn *websocket.Conn // Assumption: size of 1 is enough, because first response read will be response for the last request // no need to buffer - resFromServer chan cm.RFrame - reqFromServer chan cm.RFrame - rToServer chan cm.RFrame + resFromServer chan cm.RFrame + reqFromServer chan cm.RFrame + rToServer chan cm.RFrame + initiations []*cm.Initiation + initiationsLock sync.RWMutex } func NewClientContext(conn *websocket.Conn) *Context { @@ -37,6 +41,7 @@ func NewClientContext(conn *websocket.Conn) *Context { func (cliCtx *Context) serverHandler(syncCtx context.Context) error { defer logger.Debug("server handler last line...") +handleNext: for { select { case <-syncCtx.Done(): @@ -44,25 +49,83 @@ func (cliCtx *Context) serverHandler(syncCtx context.Context) error { case reqFrame := <-cliCtx.reqFromServer: logger.Debug("got request from server", "id", reqFrame.ID) + var res cm.Response + var err error + if reqFrame.ID == cm.EchoReqID { - echoReq, err := cm.RequestFromFrame[cm.EchoRequest](reqFrame) - if err != nil { - return err - } - - resFrame, err := cm.ResponseFrameFrom(cm.EchoResponse(echoReq)) - if err != nil { - return err - } - - cliCtx.rToServer <- resFrame + res, err = cliCtx.handleEcho(reqFrame) + } else if reqFrame.ID == cm.StartChatBReqID { + res, err = cliCtx.handleStartChatB(reqFrame) + } else if reqFrame.ID == cm.StartChatDReqID { + res, err = cliCtx.handleStartChatD(reqFrame) } else { logger.Warn("can't handle it!") } + + if err != nil { + logger.Errorf("could not handle request ID=%d", reqFrame.ID) + return err + } + + if res == nil { + logger.Debugf("request without response ID=%d", reqFrame.ID) + continue handleNext + } + + resFrame, err := cm.ResponseFrameFrom(res) + + if err != nil { + logger.Errorf("could not create frame from response") + return err + } + + cliCtx.rToServer <- resFrame } } } +func (cliCtx *Context) handleEcho(reqFrame cm.RFrame) (res cm.Response, err error) { + echoReq, err := cm.RequestFromFrame[cm.EchoRequest](reqFrame) + if err != nil { + return nil, err + } + + return cm.EchoResponse(echoReq), nil +} + +func (cliCtx *Context) handleStartChatB(reqFrame cm.RFrame) (res cm.Response, err error) { + startChatBReq, err := cm.RequestFromFrame[cm.StartChatBRequest](reqFrame) + if err != nil { + return nil, err + } + + logger.Infof("got start chat, %s wants to contact. use startchatc command to "+ + "decide if you want to accept the chat", startChatBReq.Nickname) + + cliCtx.initiationsLock.Lock() + cliCtx.initiations = append(cliCtx.initiations, &cm.Initiation{ + AbANick: startChatBReq.Nickname, + AbBNick: "", + Stage: cm.InitiationStageB, + }) + cliCtx.initiationsLock.Unlock() + + return nil, nil +} + +func (cliCtx *Context) handleStartChatD(reqFrame cm.RFrame) (res cm.Response, err error) { + startChatDReq, err := cm.RequestFromFrame[cm.StartChatDRequest](reqFrame) + if err != nil { + return nil, err + } + + logger.Infof("servers wants to be punched, got start chat d request for %s with code %s", + startChatDReq.Nickname, startChatDReq.PunchCode) + logger.Warn("handleStartChatD not implemented yet") + + return nil, nil +} + func (cliCtx *Context) serverWriter(syncCtx context.Context) error { defer logger.Debug("server writer last line...") @@ -133,7 +196,7 @@ func init() { } func sendAuth(ctx *Context, nick, pass string) { - logger.Info("Trying to authenticate as krzmaciek...") + logger.Info("trying to authenticate as krzmaciek...") err := ctx.sendRequest(cm.AuthRequest{Nickname: nick, Password: pass}) if err != nil { @@ -141,7 +204,7 @@ func sendAuth(ctx *Context, nick, pass string) { return } - logger.Debug("Request sent, waiting for response...") + logger.Debug("request sent, waiting for response...") arf := ctx.getResponseFrame() ar, err := cm.ResponseFromFrame[cm.AuthResponse](arf) @@ -154,7 +217,7 @@ func sendAuth(ctx *Context, nick, pass string) { } func sendEcho(ctx *Context, echoByte byte) { - logger.Info("Testing echo...", "echoByte", echoByte) + logger.Info("testing echo...", "echoByte", echoByte) err := ctx.sendRequest(cm.EchoRequest{EchoByte: echoByte}) if err != nil { @@ -162,7 +225,7 @@ func sendEcho(ctx *Context, echoByte byte) { return } - logger.Debug("Request sent, waiting for response...") + logger.Debug("request sent, waiting for response...") ereqf := ctx.getResponseFrame() ereq, err := cm.ResponseFromFrame[cm.EchoResponse](ereqf) @@ -171,11 +234,11 @@ func sendEcho(ctx *Context, echoByte byte) { return } - logger.Info("Got response", "echoByte", ereq.EchoByte) + logger.Info("got response", "echoByte", ereq.EchoByte) } func sendListPeers(ctx *Context) { - logger.Info("Trying to get list of peers...") + logger.Info("trying to get list of peers...") err := ctx.sendRequest(cm.ListPeersRequest{}) if err != nil { @@ -183,7 +246,7 @@ func sendListPeers(ctx *Context) { return } - logger.Debug("Request sent, waiting for response...") + logger.Debug("request sent, waiting for response...") lpreqf := ctx.getResponseFrame() lpreq, err := cm.ResponseFromFrame[cm.ListPeersResponse](lpreqf) @@ -196,7 +259,7 @@ func sendListPeers(ctx *Context) { } func sendStartChatA(ctx *Context, nick string) { - logger.Info("Doing chat start A...") + logger.Info("doing chat start A...") err := ctx.sendRequest(cm.StartChatARequest{Nickname: nick}) if err != nil { @@ -204,7 +267,27 @@ func sendStartChatA(ctx *Context, nick string) { return } - logger.Debug("Request sent, no wait for response") + logger.Debug("request sent, no wait for response") +} + +func sendStartChatC(ctx *Context, nick string) { + idx := slices.IndexFunc(ctx.initiations, func(i *cm.Initiation) bool { + return i.AbANick == nick + }) + + if idx == -1 { + logger.Warn("user of that nick did not initiate connection, ignoring") + return + } + + err := ctx.sendRequest(cm.StartChatCRequest{Nickname: nick}) + + if err != nil { + logger.Error(err) + return + } + + logger.Debug("request sent, no wait for response") } func RunClient() { @@ -297,6 +380,21 @@ func RunClient() { } sendStartChatA(cliCtx, cmdArgs[0]) + } else if cmdName == "initations" { + logger.Info("displaying all initations...") + + cliCtx.initiationsLock.RLock() + for _, i := range cliCtx.initiations { + logger.Debugf("from %s, stage: %d", i.AbANick, i.Stage) + } + cliCtx.initiationsLock.RUnlock() + } else if cmdName == "startchatc" { + if len(cmdArgs) != 1 { + logger.Errorf("startchatc command requires 1 argument, but %d was provided", len(cmdArgs)) + continue + } + + sendStartChatC(cliCtx, cmdArgs[0]) } } }() diff --git a/common/common.go b/common/common.go index 1fee048..c69a47f 100644 --- a/common/common.go +++ b/common/common.go @@ -173,10 +173,25 @@ func (StartChatCRequest) ID() int { } type StartChatDRequest struct { - Nickname string `json:"nickname"` - Accept bool `json:"accept"` + Nickname string `json:"nickname"` + PunchCode string `json:"punchCode"` } func (StartChatDRequest) ID() int { return StartChatDReqID } + +type Initiation struct { + AbANick string + AbBNick string + Stage int + AbAPunchCode string + AbBPunchCode string +} + +const ( + InitiationStageA = 1 + InitiationStageB = 2 + InitiationStageC = 3 + InitiationStageD = 4 +) diff --git a/server/server.go b/server/server.go index 5b0aeea..2585bdf 100644 --- a/server/server.go +++ b/server/server.go @@ -2,6 +2,7 @@ package server import ( "context" + "crypto/rand" "encoding/json" "errors" "net/http" @@ -30,26 +31,21 @@ func NewPeer(conn *websocket.Conn) *Peer { return &Peer{-1, conn, false, nil} } +func (p *Peer) NicknameOrEmpty() string { + if p.hasAccount { + return p.account.nickname + } else { + return "" + } +} + type Account struct { nickname string passHash []byte } -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} +func NewInitiation(abA string, abB string) *common.Initiation { + return &common.Initiation{AbANick: abA, AbBNick: abB, Stage: common.InitiationStageA} } type Context struct { @@ -59,7 +55,7 @@ type Context struct { peersListLock sync.RWMutex accounts map[string]*Account accountsLock sync.RWMutex - initiations []*Initiation + initiations []*common.Initiation initiationsLock sync.RWMutex handlerContexts []*HandlerContext handlerContextsLock sync.RWMutex @@ -69,7 +65,7 @@ func NewContext() *Context { return &Context{ peersList: make([]*Peer, 0), accounts: make(map[string]*Account), - initiations: make([]*Initiation, 0), + initiations: make([]*common.Initiation, 0), } } @@ -134,6 +130,11 @@ handleNext: res, err = hdlCtx.handleEcho(&reqFrame) } else if reqFrame.ID == common.StartChatAReqID { res, err = hdlCtx.handleChatStartA(&reqFrame) + } else if reqFrame.ID == common.StartChatCReqID { + res, err = hdlCtx.handleChatStartC(&reqFrame) + } else { + logger.Warnf("can't handle request of ID=%d", reqFrame.ID) + continue } if err != nil { @@ -271,10 +272,10 @@ func (ctx *Context) removePeer(peer *Peer) { return p.id == peer.id }) - ctx.initiations = slices.DeleteFunc[[]*Initiation, *Initiation]( + ctx.initiations = slices.DeleteFunc[[]*common.Initiation, *common.Initiation]( ctx.initiations, - func(i *Initiation) bool { - return peer.hasAccount && (peer.account.nickname == i.abANick || peer.account.nickname == i.abBNick) + func(i *common.Initiation) bool { + return peer.hasAccount && (peer.account.nickname == i.AbANick || peer.account.nickname == i.AbBNick) }) // TODO: Inform the other side about peer leaving @@ -317,21 +318,13 @@ func (hdlCtx *HandlerContext) handleListPeers(reqFrame *common.RFrame) (res comm listPeersRes := common.ListPeersResponse{PeersInfo: make([]common.PeerInfo, 0)} for _, peer := range peersFreeze { - 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, - Nickname: nickname, + Nickname: peer.NicknameOrEmpty(), }, ) } @@ -418,47 +411,101 @@ func (hdlCtx *HandlerContext) handleChatStartA(reqFrame *common.RFrame) (res com receiverPeerCtx.rToClient <- chatStartBReqF hdlCtx.initiationsLock.Lock() - // + idx := slices.IndexFunc(hdlCtx.initiations, func(i *common.Initiation) bool { + return i.AbANick == hdlCtx.peer.account.nickname && i.AbBNick == startChatAReq.Nickname + }) + hdlCtx.initiations[idx].Stage = common.InitiationStageB 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) + hdlCtx.initiationsLock.Lock() + startChatCReq, err := common.RequestFromFrame[common.StartChatCRequest](*reqFrame) if err != nil { return nil, err } - receiverPeerCtx, err := hdlCtx.getCtxByNick(startChatAReq.Nickname) + logger.Debugf("got chat start c for %s", startChatCReq.Nickname) + + receiverPeerCtx, err := hdlCtx.getCtxByNick(startChatCReq.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() + idx := slices.IndexFunc(hdlCtx.initiations, func(i *common.Initiation) bool { + return i.AbBNick == hdlCtx.peer.account.nickname && i.AbANick == startChatCReq.Nickname + }) - chatStartB := common.StartChatBRequest{ - Nickname: hdlCtx.peer.account.nickname, + if idx == -1 { + logger.Debug("initation not found, won't handle") + return nil, nil } - chatStartBReqF, err := common.RequestFrameFrom(chatStartB) + if hdlCtx.initiations[idx].Stage != common.InitiationStageB { + logger.Debug("initation found, but is not in stage B, won't handle") + return nil, nil + } + + hdlCtx.initiations[idx].Stage = common.InitiationStageC + + aCode, err := generatePunchCode() + if err != nil { + logger.Error("failed generating punch code for a") + return nil, nil + } + + bCode, err := generatePunchCode() + if err != nil { + logger.Error("failed generating punch code for b") + return nil, nil + } + + hdlCtx.initiations[idx].AbAPunchCode = aCode + hdlCtx.initiations[idx].AbBPunchCode = bCode + + dReqToA := common.StartChatDRequest{Nickname: hdlCtx.peer.account.nickname, PunchCode: aCode} + dReqToB := common.StartChatDRequest{Nickname: startChatCReq.Nickname, PunchCode: bCode} + + err = hdlCtx.sendRequest(dReqToB) if err != nil { - logger.Debug("chat start B req frame creation failed") - return nil, err + logger.Errorf("could not send chatstartd to B=%s", hdlCtx.peer.account.nickname) + return nil, nil } - receiverPeerCtx.rToClient <- chatStartBReqF + err = receiverPeerCtx.sendRequest(dReqToA) + if err != nil { + logger.Errorf("could not send chatstartd to A=%s", startChatCReq.Nickname) + return nil, nil + } + + hdlCtx.initiations[idx].Stage = common.InitiationStageD + + hdlCtx.initiationsLock.Unlock() return nil, nil } +func generatePunchCode() (string, error) { + codeBytes := make([]byte, 8) + _, err := rand.Read(codeBytes) + + if err != nil { + return "", err + } + + for idx, cb := range codeBytes { + codeBytes[idx] = 65 + (cb % 26) + } + + return string(codeBytes), nil +} + func (ctx *Context) printDebugInfo() { ctx.peersListLock.RLock() logger.Debug("================================ server state") @@ -477,7 +524,7 @@ func (ctx *Context) printDebugInfo() { logger.Debug("displaying all initiations:") for _, i := range ctx.initiations { - logger.Debugf("from %s to %s, stage: %d", i.abBNick, i.abBNick, i.stage) + logger.Debugf("from %s to %s, stage: %d", i.AbANick, i.AbBNick, i.Stage) } ctx.peersListLock.RUnlock()