Finished basic connection initation

This commit is contained in:
Maciej Krzyżanowski 2024-05-08 23:54:34 +02:00
parent 67eda0d069
commit b23e8c00a6
3 changed files with 193 additions and 28 deletions

View File

@ -3,8 +3,9 @@ package client
import ( import (
"bufio" "bufio"
"context" "context"
"encoding/json"
"errors" "errors"
"golang.org/x/sync/errgroup" "net"
"net/url" "net/url"
"os" "os"
"slices" "slices"
@ -13,11 +14,18 @@ import (
"sync" "sync"
"time" "time"
"golang.org/x/sync/errgroup"
"github.com/charmbracelet/log" "github.com/charmbracelet/log"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"krzyzanowski.dev/archat/common"
cm "krzyzanowski.dev/archat/common" cm "krzyzanowski.dev/archat/common"
) )
type InitiationInfo struct {
otherSideNick string
}
type Context struct { type Context struct {
conn *websocket.Conn conn *websocket.Conn
// Assumption: size of 1 is enough, because first response read will be response for the last request // 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 resFromServer chan cm.RFrame
reqFromServer chan cm.RFrame reqFromServer chan cm.RFrame
rToServer chan cm.RFrame rToServer chan cm.RFrame
initiations []*cm.Initiation initiations []InitiationInfo
initiationsLock sync.RWMutex initiationsLock sync.RWMutex
} }
@ -58,6 +66,8 @@ handleNext:
res, err = cliCtx.handleStartChatB(reqFrame) res, err = cliCtx.handleStartChatB(reqFrame)
} else if reqFrame.ID == cm.StartChatDReqID { } else if reqFrame.ID == cm.StartChatDReqID {
res, err = cliCtx.handleStartChatD(reqFrame) res, err = cliCtx.handleStartChatD(reqFrame)
} else if reqFrame.ID == cm.StartChatFinishReqID {
res, err = cliCtx.handleChatStartFinish(reqFrame)
} else { } else {
logger.Warn("can't handle it!") 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) "decide if you want to accept the chat", startChatBReq.Nickname)
cliCtx.initiationsLock.Lock() cliCtx.initiationsLock.Lock()
cliCtx.initiations = append(cliCtx.initiations, &cm.Initiation{ cliCtx.initiations = append(cliCtx.initiations, InitiationInfo{
AbANick: startChatBReq.Nickname, otherSideNick: startChatBReq.Nickname,
AbBNick: "",
Stage: cm.InitiationStageB,
}) })
cliCtx.initiationsLock.Unlock() cliCtx.initiationsLock.Unlock()
@ -126,16 +134,45 @@ func (cliCtx *Context) handleStartChatD(reqFrame cm.RFrame) (res cm.Response, er
startChatDReq.Nickname, startChatDReq.PunchCode) startChatDReq.Nickname, startChatDReq.PunchCode)
logger.Warn("handleStartChatD not implemented yet") logger.Warn("handleStartChatD not implemented yet")
idx := slices.IndexFunc(cliCtx.initiations, func(i *cm.Initiation) bool { idx := slices.IndexFunc(cliCtx.initiations, func(i InitiationInfo) bool {
return i.AbBNick == startChatDReq.Nickname return i.otherSideNick == startChatDReq.Nickname
}) })
if idx == -1 { 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 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 return nil, nil
} }
@ -288,8 +325,8 @@ func sendStartChatC(ctx *Context, nick string) {
ctx.initiationsLock.Lock() ctx.initiationsLock.Lock()
defer ctx.initiationsLock.Unlock() defer ctx.initiationsLock.Unlock()
idx := slices.IndexFunc(ctx.initiations, func(i *cm.Initiation) bool { idx := slices.IndexFunc(ctx.initiations, func(i InitiationInfo) bool {
return i.AbANick == nick return i.otherSideNick == nick
}) })
if idx == -1 { if idx == -1 {
@ -304,8 +341,6 @@ func sendStartChatC(ctx *Context, nick string) {
return return
} }
ctx.initiations[idx].Stage = cm.InitiationStageC
logger.Debug("request sent, no wait for response") logger.Debug("request sent, no wait for response")
} }
@ -399,12 +434,18 @@ func RunClient() {
} }
sendStartChatA(cliCtx, cmdArgs[0]) sendStartChatA(cliCtx, cmdArgs[0])
cliCtx.initiationsLock.Lock()
cliCtx.initiations = append(cliCtx.initiations, InitiationInfo{
otherSideNick: cmdArgs[0],
})
cliCtx.initiationsLock.Unlock()
} else if cmdName == "initations" { } else if cmdName == "initations" {
logger.Info("displaying all initations...") logger.Info("displaying all initations...")
cliCtx.initiationsLock.RLock() cliCtx.initiationsLock.RLock()
for _, i := range cliCtx.initiations { for _, i := range cliCtx.initiations {
logger.Debugf("from %s, stage: %d", i.AbANick, i.Stage) logger.Debugf("with %s", i.otherSideNick)
} }
cliCtx.initiationsLock.RUnlock() cliCtx.initiationsLock.RUnlock()
} else if cmdName == "startchatc" { } else if cmdName == "startchatc" {

View File

@ -17,6 +17,7 @@ const (
StartChatBReqID = 5 StartChatBReqID = 5
StartChatCReqID = 6 StartChatCReqID = 6
StartChatDReqID = 7 StartChatDReqID = 7
StartChatFinishReqID = 8
) )
// Requests & responses subtypes // Requests & responses subtypes
@ -181,12 +182,23 @@ func (StartChatDRequest) ID() int {
return StartChatDReqID return StartChatDReqID
} }
type StartChatFinishRequest struct {
OtherSideNickname string `json:"otherSideNickname"`
OtherSideAddress string `json:"otherSideAddress"`
}
func (StartChatFinishRequest) ID() int {
return StartChatFinishReqID
}
type Initiation struct { type Initiation struct {
AbANick string AbANick string
AbBNick string AbBNick string
Stage int Stage int
AbAPunchCode string AbAPunchCode string
AbBPunchCode string AbBPunchCode string
AbAAddress string
AbBAddress string
} }
const ( const (
@ -195,3 +207,11 @@ const (
InitiationStageC = 3 InitiationStageC = 3
InitiationStageD = 4 InitiationStageD = 4
) )
type PunchRequest struct {
PunchCode string `json:"punchCode"`
}
type PunchResponse struct {
IPAddr int32 `json:"ipAddr"`
}

View File

@ -5,6 +5,7 @@ import (
"crypto/rand" "crypto/rand"
"encoding/json" "encoding/json"
"errors" "errors"
"net"
"net/http" "net/http"
"os" "os"
"slices" "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() { func RunServer() {
srvCtx := NewContext() srvCtx := NewContext()
@ -625,10 +709,30 @@ func RunServer() {
}() }()
http.HandleFunc("/wsapi", srvCtx.wsapiHandler) http.HandleFunc("/wsapi", srvCtx.wsapiHandler)
logger.Info("Starting server...") logger.Info("Starting websocket server...")
go func() {
err := http.ListenAndServe(":8080", nil) err := http.ListenAndServe(":8080", nil)
if err != nil { if err != nil {
logger.Error(err) logger.Error(err)
} }
}()
logger.Info("Starting punching server...")
listener, err := net.ListenPacket("udp", ":8081")
if err != nil {
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)
}
} }