Finished implementation for B, C, D stages of initiation

This commit is contained in:
Maciej Krzyżanowski 2024-04-29 13:46:26 +02:00
parent d5b891e4f5
commit 62e3ee780b
4 changed files with 229 additions and 68 deletions

View File

@ -1,5 +1,6 @@
# 🔀 Archat # 🔀 Archat
*It is not working application yet, work in progress!* *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). Simple P2P server (and client - for testing purposes only, client for end user will be written in NodeJS using Electron, soon).
## Starting ## Starting

View File

@ -7,8 +7,10 @@ import (
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
"net/url" "net/url"
"os" "os"
"slices"
"strconv" "strconv"
"strings" "strings"
"sync"
"time" "time"
"github.com/charmbracelet/log" "github.com/charmbracelet/log"
@ -20,9 +22,11 @@ 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
// no need to buffer // no need to buffer
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
initiationsLock sync.RWMutex
} }
func NewClientContext(conn *websocket.Conn) *Context { func NewClientContext(conn *websocket.Conn) *Context {
@ -37,6 +41,7 @@ func NewClientContext(conn *websocket.Conn) *Context {
func (cliCtx *Context) serverHandler(syncCtx context.Context) error { func (cliCtx *Context) serverHandler(syncCtx context.Context) error {
defer logger.Debug("server handler last line...") defer logger.Debug("server handler last line...")
handleNext:
for { for {
select { select {
case <-syncCtx.Done(): case <-syncCtx.Done():
@ -44,25 +49,83 @@ func (cliCtx *Context) serverHandler(syncCtx context.Context) error {
case reqFrame := <-cliCtx.reqFromServer: case reqFrame := <-cliCtx.reqFromServer:
logger.Debug("got request from server", "id", reqFrame.ID) logger.Debug("got request from server", "id", reqFrame.ID)
var res cm.Response
var err error
if reqFrame.ID == cm.EchoReqID { if reqFrame.ID == cm.EchoReqID {
echoReq, err := cm.RequestFromFrame[cm.EchoRequest](reqFrame) res, err = cliCtx.handleEcho(reqFrame)
if err != nil { } else if reqFrame.ID == cm.StartChatBReqID {
return err res, err = cliCtx.handleStartChatB(reqFrame)
} } else if reqFrame.ID == cm.StartChatDReqID {
res, err = cliCtx.handleStartChatD(reqFrame)
resFrame, err := cm.ResponseFrameFrom(cm.EchoResponse(echoReq))
if err != nil {
return err
}
cliCtx.rToServer <- resFrame
} else { } else {
logger.Warn("can't handle it!") 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 { func (cliCtx *Context) serverWriter(syncCtx context.Context) error {
defer logger.Debug("server writer last line...") defer logger.Debug("server writer last line...")
@ -133,7 +196,7 @@ func init() {
} }
func sendAuth(ctx *Context, nick, pass string) { 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}) err := ctx.sendRequest(cm.AuthRequest{Nickname: nick, Password: pass})
if err != nil { if err != nil {
@ -141,7 +204,7 @@ func sendAuth(ctx *Context, nick, pass string) {
return return
} }
logger.Debug("Request sent, waiting for response...") logger.Debug("request sent, waiting for response...")
arf := ctx.getResponseFrame() arf := ctx.getResponseFrame()
ar, err := cm.ResponseFromFrame[cm.AuthResponse](arf) ar, err := cm.ResponseFromFrame[cm.AuthResponse](arf)
@ -154,7 +217,7 @@ func sendAuth(ctx *Context, nick, pass string) {
} }
func sendEcho(ctx *Context, echoByte byte) { 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}) err := ctx.sendRequest(cm.EchoRequest{EchoByte: echoByte})
if err != nil { if err != nil {
@ -162,7 +225,7 @@ func sendEcho(ctx *Context, echoByte byte) {
return return
} }
logger.Debug("Request sent, waiting for response...") logger.Debug("request sent, waiting for response...")
ereqf := ctx.getResponseFrame() ereqf := ctx.getResponseFrame()
ereq, err := cm.ResponseFromFrame[cm.EchoResponse](ereqf) ereq, err := cm.ResponseFromFrame[cm.EchoResponse](ereqf)
@ -171,11 +234,11 @@ func sendEcho(ctx *Context, echoByte byte) {
return return
} }
logger.Info("Got response", "echoByte", ereq.EchoByte) logger.Info("got response", "echoByte", ereq.EchoByte)
} }
func sendListPeers(ctx *Context) { 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{}) err := ctx.sendRequest(cm.ListPeersRequest{})
if err != nil { if err != nil {
@ -183,7 +246,7 @@ func sendListPeers(ctx *Context) {
return return
} }
logger.Debug("Request sent, waiting for response...") logger.Debug("request sent, waiting for response...")
lpreqf := ctx.getResponseFrame() lpreqf := ctx.getResponseFrame()
lpreq, err := cm.ResponseFromFrame[cm.ListPeersResponse](lpreqf) lpreq, err := cm.ResponseFromFrame[cm.ListPeersResponse](lpreqf)
@ -196,7 +259,7 @@ func sendListPeers(ctx *Context) {
} }
func sendStartChatA(ctx *Context, nick string) { 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}) err := ctx.sendRequest(cm.StartChatARequest{Nickname: nick})
if err != nil { if err != nil {
@ -204,7 +267,27 @@ func sendStartChatA(ctx *Context, nick string) {
return 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() { func RunClient() {
@ -297,6 +380,21 @@ func RunClient() {
} }
sendStartChatA(cliCtx, cmdArgs[0]) 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])
} }
} }
}() }()

View File

@ -173,10 +173,25 @@ func (StartChatCRequest) ID() int {
} }
type StartChatDRequest struct { type StartChatDRequest struct {
Nickname string `json:"nickname"` Nickname string `json:"nickname"`
Accept bool `json:"accept"` PunchCode string `json:"punchCode"`
} }
func (StartChatDRequest) ID() int { func (StartChatDRequest) ID() int {
return StartChatDReqID return StartChatDReqID
} }
type Initiation struct {
AbANick string
AbBNick string
Stage int
AbAPunchCode string
AbBPunchCode string
}
const (
InitiationStageA = 1
InitiationStageB = 2
InitiationStageC = 3
InitiationStageD = 4
)

View File

@ -2,6 +2,7 @@ package server
import ( import (
"context" "context"
"crypto/rand"
"encoding/json" "encoding/json"
"errors" "errors"
"net/http" "net/http"
@ -30,26 +31,21 @@ func NewPeer(conn *websocket.Conn) *Peer {
return &Peer{-1, conn, false, nil} return &Peer{-1, conn, false, nil}
} }
func (p *Peer) NicknameOrEmpty() string {
if p.hasAccount {
return p.account.nickname
} else {
return ""
}
}
type Account struct { type Account struct {
nickname string nickname string
passHash []byte passHash []byte
} }
const ( func NewInitiation(abA string, abB string) *common.Initiation {
InitiationStageA = 1 return &common.Initiation{AbANick: abA, AbBNick: abB, Stage: common.InitiationStageA}
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 { type Context struct {
@ -59,7 +55,7 @@ type Context struct {
peersListLock sync.RWMutex peersListLock sync.RWMutex
accounts map[string]*Account accounts map[string]*Account
accountsLock sync.RWMutex accountsLock sync.RWMutex
initiations []*Initiation initiations []*common.Initiation
initiationsLock sync.RWMutex initiationsLock sync.RWMutex
handlerContexts []*HandlerContext handlerContexts []*HandlerContext
handlerContextsLock sync.RWMutex handlerContextsLock sync.RWMutex
@ -69,7 +65,7 @@ func NewContext() *Context {
return &Context{ return &Context{
peersList: make([]*Peer, 0), peersList: make([]*Peer, 0),
accounts: make(map[string]*Account), accounts: make(map[string]*Account),
initiations: make([]*Initiation, 0), initiations: make([]*common.Initiation, 0),
} }
} }
@ -134,6 +130,11 @@ handleNext:
res, err = hdlCtx.handleEcho(&reqFrame) res, err = hdlCtx.handleEcho(&reqFrame)
} else if reqFrame.ID == common.StartChatAReqID { } else if reqFrame.ID == common.StartChatAReqID {
res, err = hdlCtx.handleChatStartA(&reqFrame) 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 { if err != nil {
@ -271,10 +272,10 @@ func (ctx *Context) removePeer(peer *Peer) {
return p.id == peer.id return p.id == peer.id
}) })
ctx.initiations = slices.DeleteFunc[[]*Initiation, *Initiation]( ctx.initiations = slices.DeleteFunc[[]*common.Initiation, *common.Initiation](
ctx.initiations, ctx.initiations,
func(i *Initiation) bool { func(i *common.Initiation) bool {
return peer.hasAccount && (peer.account.nickname == i.abANick || peer.account.nickname == i.abBNick) return peer.hasAccount && (peer.account.nickname == i.AbANick || peer.account.nickname == i.AbBNick)
}) })
// TODO: Inform the other side about peer leaving // 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)} listPeersRes := common.ListPeersResponse{PeersInfo: make([]common.PeerInfo, 0)}
for _, peer := range peersFreeze { for _, peer := range peersFreeze {
var nickname string
if peer.hasAccount {
nickname = peer.account.nickname
} else {
nickname = ""
}
listPeersRes.PeersInfo = append( listPeersRes.PeersInfo = append(
listPeersRes.PeersInfo, listPeersRes.PeersInfo,
common.PeerInfo{ common.PeerInfo{
ID: peer.id, ID: peer.id,
Addr: peer.conn.RemoteAddr().String(), Addr: peer.conn.RemoteAddr().String(),
HasNickname: peer.hasAccount, HasNickname: peer.hasAccount,
Nickname: nickname, Nickname: peer.NicknameOrEmpty(),
}, },
) )
} }
@ -418,47 +411,101 @@ func (hdlCtx *HandlerContext) handleChatStartA(reqFrame *common.RFrame) (res com
receiverPeerCtx.rToClient <- chatStartBReqF receiverPeerCtx.rToClient <- chatStartBReqF
hdlCtx.initiationsLock.Lock() 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() hdlCtx.initiationsLock.Unlock()
return nil, nil return nil, nil
} }
func (hdlCtx *HandlerContext) handleChatStartC(reqFrame *common.RFrame) (res common.Response, err error) { 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 { if err != nil {
return nil, err 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 { if err != nil {
logger.Debug("receiver peer not found") logger.Debug("receiver peer not found")
return nil, nil return nil, nil
} }
// initation started idx := slices.IndexFunc(hdlCtx.initiations, func(i *common.Initiation) bool {
hdlCtx.initiationsLock.Lock() return i.AbBNick == hdlCtx.peer.account.nickname && i.AbANick == startChatCReq.Nickname
hdlCtx.initiations = append(hdlCtx.initiations, NewInitiation(hdlCtx.peer.account.nickname, startChatAReq.Nickname)) })
hdlCtx.initiationsLock.Unlock()
chatStartB := common.StartChatBRequest{ if idx == -1 {
Nickname: hdlCtx.peer.account.nickname, 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 { if err != nil {
logger.Debug("chat start B req frame creation failed") logger.Errorf("could not send chatstartd to B=%s", hdlCtx.peer.account.nickname)
return nil, err 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 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() { func (ctx *Context) printDebugInfo() {
ctx.peersListLock.RLock() ctx.peersListLock.RLock()
logger.Debug("================================ server state") logger.Debug("================================ server state")
@ -477,7 +524,7 @@ func (ctx *Context) printDebugInfo() {
logger.Debug("displaying all initiations:") logger.Debug("displaying all initiations:")
for _, i := range ctx.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() ctx.peersListLock.RUnlock()