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 (
"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" {

View File

@ -17,6 +17,7 @@ const (
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"`
}

View File

@ -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...")
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("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)
}
}