From b23e8c00a6a3585b4c0c6555dd65e819a1ca7508 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Krzy=C5=BCanowski?= Date: Wed, 8 May 2024 23:54:34 +0200 Subject: [PATCH] Finished basic connection initation --- client/client.go | 71 +++++++++++++++++++++++------- common/common.go | 40 ++++++++++++----- server/server.go | 110 +++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 193 insertions(+), 28 deletions(-) diff --git a/client/client.go b/client/client.go index e6e1e6f..6c0daac 100644 --- a/client/client.go +++ b/client/client.go @@ -3,8 +3,9 @@ package client import ( "bufio" "context" + "encoding/json" "errors" - "golang.org/x/sync/errgroup" + "net" "net/url" "os" "slices" @@ -13,11 +14,18 @@ import ( "sync" "time" + "golang.org/x/sync/errgroup" + "github.com/charmbracelet/log" "github.com/gorilla/websocket" + "krzyzanowski.dev/archat/common" cm "krzyzanowski.dev/archat/common" ) +type InitiationInfo struct { + otherSideNick string +} + type Context struct { conn *websocket.Conn // Assumption: size of 1 is enough, because first response read will be response for the last request @@ -25,7 +33,7 @@ type Context struct { resFromServer chan cm.RFrame reqFromServer chan cm.RFrame rToServer chan cm.RFrame - initiations []*cm.Initiation + initiations []InitiationInfo initiationsLock sync.RWMutex } @@ -58,6 +66,8 @@ handleNext: res, err = cliCtx.handleStartChatB(reqFrame) } else if reqFrame.ID == cm.StartChatDReqID { res, err = cliCtx.handleStartChatD(reqFrame) + } else if reqFrame.ID == cm.StartChatFinishReqID { + res, err = cliCtx.handleChatStartFinish(reqFrame) } else { logger.Warn("can't handle it!") } @@ -103,10 +113,8 @@ func (cliCtx *Context) handleStartChatB(reqFrame cm.RFrame) (res cm.Response, er "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.initiations = append(cliCtx.initiations, InitiationInfo{ + otherSideNick: startChatBReq.Nickname, }) cliCtx.initiationsLock.Unlock() @@ -126,16 +134,45 @@ func (cliCtx *Context) handleStartChatD(reqFrame cm.RFrame) (res cm.Response, er startChatDReq.Nickname, startChatDReq.PunchCode) logger.Warn("handleStartChatD not implemented yet") - idx := slices.IndexFunc(cliCtx.initiations, func(i *cm.Initiation) bool { - return i.AbBNick == startChatDReq.Nickname + idx := slices.IndexFunc(cliCtx.initiations, func(i InitiationInfo) bool { + return i.otherSideNick == startChatDReq.Nickname }) if idx == -1 { - logger.Error("there is no initation related to chatstartd's nickname, ignoring") + logger.Error("there is no initation related to chatstartd's nickname, ignoring", + "nickname", startChatDReq.Nickname) return nil, nil } - cliCtx.initiations[idx].Stage = cm.InitiationStageD + conn, err := net.Dial("udp", ":8081") + if err != nil { + logger.Error("error udp dialing for punch", err) + return nil, nil + } + + enc := json.NewEncoder(conn) + err = enc.Encode(cm.PunchRequest{PunchCode: startChatDReq.PunchCode}) + + if err != nil { + logger.Error("error sending punch request data", "err", err) + return nil, nil + } + + logger.Debug("punch request sent!") + + return nil, nil +} + +func (ctx *Context) handleChatStartFinish(reqFrame common.RFrame) (res common.Response, err error) { + startChatFinishReq, err := common.RequestFromFrame[common.StartChatFinishRequest](reqFrame) + + if err != nil { + return nil, err + } + + logger.Info("got chat finish info!", + "nick", startChatFinishReq.OtherSideNickname, + "addr", startChatFinishReq.OtherSideAddress) return nil, nil } @@ -288,8 +325,8 @@ func sendStartChatC(ctx *Context, nick string) { ctx.initiationsLock.Lock() defer ctx.initiationsLock.Unlock() - idx := slices.IndexFunc(ctx.initiations, func(i *cm.Initiation) bool { - return i.AbANick == nick + idx := slices.IndexFunc(ctx.initiations, func(i InitiationInfo) bool { + return i.otherSideNick == nick }) if idx == -1 { @@ -304,8 +341,6 @@ func sendStartChatC(ctx *Context, nick string) { return } - ctx.initiations[idx].Stage = cm.InitiationStageC - logger.Debug("request sent, no wait for response") } @@ -399,12 +434,18 @@ func RunClient() { } sendStartChatA(cliCtx, cmdArgs[0]) + + cliCtx.initiationsLock.Lock() + cliCtx.initiations = append(cliCtx.initiations, InitiationInfo{ + otherSideNick: cmdArgs[0], + }) + cliCtx.initiationsLock.Unlock() } 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) + logger.Debugf("with %s", i.otherSideNick) } cliCtx.initiationsLock.RUnlock() } else if cmdName == "startchatc" { diff --git a/common/common.go b/common/common.go index c69a47f..116ae5e 100644 --- a/common/common.go +++ b/common/common.go @@ -7,16 +7,17 @@ import ( // Constants const ( - EchoReqID = 1 - EchoResID = 128 + EchoReqID - ListPeersReqID = 2 - ListPeersResID = 128 + ListPeersReqID - AuthReqID = 3 - AuthResID = 128 + AuthReqID - StartChatAReqID = 4 - StartChatBReqID = 5 - StartChatCReqID = 6 - StartChatDReqID = 7 + EchoReqID = 1 + EchoResID = 128 + EchoReqID + ListPeersReqID = 2 + ListPeersResID = 128 + ListPeersReqID + AuthReqID = 3 + AuthResID = 128 + AuthReqID + StartChatAReqID = 4 + StartChatBReqID = 5 + StartChatCReqID = 6 + StartChatDReqID = 7 + StartChatFinishReqID = 8 ) // Requests & responses subtypes @@ -181,12 +182,23 @@ func (StartChatDRequest) ID() int { return StartChatDReqID } +type StartChatFinishRequest struct { + OtherSideNickname string `json:"otherSideNickname"` + OtherSideAddress string `json:"otherSideAddress"` +} + +func (StartChatFinishRequest) ID() int { + return StartChatFinishReqID +} + type Initiation struct { AbANick string AbBNick string Stage int AbAPunchCode string AbBPunchCode string + AbAAddress string + AbBAddress string } const ( @@ -195,3 +207,11 @@ const ( InitiationStageC = 3 InitiationStageD = 4 ) + +type PunchRequest struct { + PunchCode string `json:"punchCode"` +} + +type PunchResponse struct { + IPAddr int32 `json:"ipAddr"` +} diff --git a/server/server.go b/server/server.go index 2585bdf..411c629 100644 --- a/server/server.go +++ b/server/server.go @@ -5,6 +5,7 @@ import ( "crypto/rand" "encoding/json" "errors" + "net" "net/http" "os" "slices" @@ -614,6 +615,89 @@ func (ctx *Context) wsapiHandler(w http.ResponseWriter, r *http.Request) { } } +func (srvCtx *Context) handleUDP(data []byte, addr net.Addr) { + var punchReq common.PunchRequest + err := json.Unmarshal(data, &punchReq) + + if err != nil { + logger.Error("error unmarshalling punch request", "err", err) + return + } + + logger.Debugf("got punch request %+v", punchReq) + + srvCtx.initiationsLock.Lock() + defer srvCtx.initiationsLock.Unlock() + + idx := slices.IndexFunc(srvCtx.initiations, func(i *common.Initiation) bool { + return i.AbAPunchCode == punchReq.PunchCode || + i.AbBPunchCode == punchReq.PunchCode + }) + + if idx == -1 { + logger.Debugf("haven't found initiation for the request") + return + } + + matchedInitation := srvCtx.initiations[idx] + logger.Debugf("matched initiation %+v", matchedInitation) + + if matchedInitation.AbAPunchCode == punchReq.PunchCode { + matchedInitation.AbAAddress = addr.String() + } else { + matchedInitation.AbBAddress = addr.String() + } + + if matchedInitation.AbAAddress == "" || matchedInitation.AbBAddress == "" { + // does not have two addresses can't do anything yet + return + } + + logger.Debugf("finished completing initiation %+v", matchedInitation) + logger.Debug("now sending peers their addresses") + + srvCtx.peersListLock.Lock() + defer srvCtx.peersListLock.Unlock() + + abA, err := srvCtx.getCtxByNick(matchedInitation.AbANick) + + if err != nil { + logger.Debug("could not finish punching, abA not found", + "err", err) + return + } + + abB, err := srvCtx.getCtxByNick(matchedInitation.AbBNick) + + if err != nil { + logger.Debug("could not finish punching, abB not found", + "err", err) + return + } + + err = abA.sendRequest(common.StartChatFinishRequest{ + OtherSideNickname: matchedInitation.AbBNick, + OtherSideAddress: matchedInitation.AbBAddress, + }) + + if err != nil { + logger.Debug("could not send start chat finish request to abA, aborting", + "err", err) + return + } + + err = abB.sendRequest(common.StartChatFinishRequest{ + OtherSideNickname: matchedInitation.AbANick, + OtherSideAddress: matchedInitation.AbAAddress, + }) + + if err != nil { + logger.Debug("could not send start chat finish request to abB, aborting", + "err", err) + return + } +} + func RunServer() { srvCtx := NewContext() @@ -625,10 +709,30 @@ func RunServer() { }() http.HandleFunc("/wsapi", srvCtx.wsapiHandler) - logger.Info("Starting server...") - err := http.ListenAndServe(":8080", nil) + logger.Info("Starting websocket server...") + go func() { + err := http.ListenAndServe(":8080", nil) + if err != nil { + logger.Error(err) + } + }() + logger.Info("Starting punching server...") + listener, err := net.ListenPacket("udp", ":8081") if err != nil { - logger.Error(err) + logger.Error("could not create listener for punching server", err) + } + + for { + data := make([]byte, 65536) + n, punchAddr, err := listener.ReadFrom(data) + if err != nil { + logger.Error("error reading from punching server", err) + continue + } + + data = data[:n] + logger.Debugf("got message: %+v", data) + srvCtx.handleUDP(data, punchAddr) } }