diff options
| author | Kyren223 <ulmliad223@gmail.com> | 2024-10-20 20:12:12 +0300 |
|---|---|---|
| committer | Kyren223 <ulmliad223@gmail.com> | 2024-10-20 20:12:12 +0300 |
| commit | 6f83f199f8311bf7782da59bc2e07df38073e65a (patch) | |
| tree | 8cfbe0a551bea927cf1c7f23bf5753f8ddf0ce46 /internal/server/server.go | |
| parent | 1997bc8a150b92783060cc7129e1ee72a761182b (diff) | |
refactor: finished refactoring server-side code
Diffstat (limited to 'internal/server/server.go')
| -rw-r--r-- | internal/server/server.go | 246 |
1 files changed, 137 insertions, 109 deletions
diff --git a/internal/server/server.go b/internal/server/server.go index 933ad29..4ac8969 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -2,6 +2,7 @@ package server import ( "context" + "crypto/ed25519" "crypto/rand" "crypto/tls" _ "embed" @@ -10,6 +11,7 @@ import ( "io" "log" "net" + "os" "strconv" "strings" "sync" @@ -48,44 +50,64 @@ func init() { } message := packet.ErrorMessage{Error: packet.PacketUnsupportedEncoding.Error()} - encoder, err := packet.NewMsgPackEncoder(&message) - assert.NoError(err, "constant packets should not error") - unsupportedEncodingErrorPacket = packet.NewPacket(encoder) + unsupportedEncodingErrorPacket = packet.NewPacket(packet.NewMsgPackEncoder(&message)) message = packet.ErrorMessage{Error: packet.PacketUnsupportedType.Error()} - encoder, err = packet.NewMsgPackEncoder(&message) - assert.NoError(err, "constant packets should not error") - unsupportedTypeErrorPacket = packet.NewPacket(encoder) + unsupportedTypeErrorPacket = packet.NewPacket(packet.NewMsgPackEncoder(&message)) } type server struct { - node *snowflake.Node - port uint16 + Node *snowflake.Node + Port uint16 + sessions map[snowflake.ID]*Session + sessMu sync.RWMutex } func NewServer(port uint16) server { - assert.Assert(nodeId <= snowflake.NodeMax, "maximum amount of servers reached %v", snowflake.NodeMax) + assert.Assert(nodeId <= snowflake.NodeMax, "maximum amount of servers reached") node := snowflake.NewNode(nodeId) nodeId++ return server{ - node: node, - port: port, + Node: node, + Port: port, + sessions: map[snowflake.ID]*Session{}, } } +func (s *server) AddSession(session *Session) { + s.sessMu.Lock() + defer s.sessMu.Unlock() + s.sessions[session.ID] = session +} + +func (s *server) RemoveSession(id snowflake.ID) { + s.sessMu.Lock() + defer s.sessMu.Unlock() + delete(s.sessions, id) +} + +func (s *server) Session(id snowflake.ID) (*Session, bool) { + s.sessMu.RLock() + defer s.sessMu.RUnlock() + session, ok := s.sessions[id] + return session, ok +} + func (s *server) ListenAndServe(ctx context.Context) { - listener, err := tls.Listen("tcp4", ":"+strconv.Itoa(int(s.port)), tlsConfig) + listener, err := tls.Listen("tcp4", ":"+strconv.Itoa(int(s.Port)), tlsConfig) if err != nil { log.Fatalf("error starting server: %s", err) } + + assert.AddFlush(listener) defer listener.Close() go func() { <-ctx.Done() listener.Close() }() - log.Printf("started listening on port %v...\n", s.port) + log.Printf("started listening on port %v...\n", s.Port) var wg sync.WaitGroup for { conn, err := listener.Accept() @@ -97,46 +119,61 @@ func (s *server) ListenAndServe(ctx context.Context) { } wg.Add(1) go func() { - handleConnection(ctx, conn) + handleConnection(ctx, conn, s) wg.Done() }() } - log.Printf("stopped listening on port %v\n", s.port) + log.Printf("stopped listening on port %v\n", s.Port) log.Println("waiting for all active connections to close...") wg.Wait() log.Println("server shutdown complete") } -func handleConnection(ctx context.Context, conn net.Conn) { +func handleConnection(ctx context.Context, conn net.Conn, server *server) { addr, ok := conn.RemoteAddr().(*net.TCPAddr) assert.Assert(ok, "getting tcp address should never fail as we are using tcp connections") - writeQueue := make(chan packet.Packet, 10) - session := newSession(addr, writeQueue) - nonce := session.Challenge() + log.Println(addr, "accepted") + nonce := [32]byte{} + _, err := rand.Read(nonce[:]) + assert.NoError(err, "random should always produce a value") + pubKey, err := handleAuth(conn, nonce[:]) + if err != nil { + log.Println(addr, err) + conn.Close() + log.Println(addr, "disconnected") + return + } + + // TODO: replace this with DB query for id + id := server.Node.Generate() + session := newSession(server, addr, id, pubKey) + server.AddSession(session) ctx = newContext(ctx, session) framer := packet.NewFramer(ctx) - log.Println(addr, "accepted") - defer func() { conn.Close() + close(framer.Out) + server.RemoveSession(session.ID) + close(session.WriteQueue) log.Println(addr, "disconnected") }() go func() { - var mu sync.Mutex for { - packet, ok := <-writeQueue + packet, ok := <-session.WriteQueue if !ok { break } - mu.Lock() - packet.Into(conn) - mu.Unlock() + if _, err := packet.Into(conn); err != nil { + log.Println(addr, err) + break + } } + session.WriteQueue = nil }() go func() { @@ -146,91 +183,97 @@ func handleConnection(ctx context.Context, conn net.Conn) { break } response := processPacket(ctx, request) - writeQueue <- response + if session.WriteQueue == nil { + break + } + session.WriteQueue <- response } }() buffer := make([]byte, 512) for { + conn.SetReadDeadline(time.Now().Add(time.Second)) n, err := conn.Read(buffer) - if err != nil { + deadlineExceeded := errors.Is(err, os.ErrDeadlineExceeded) + if err != nil && !deadlineExceeded { if !errors.Is(err, io.EOF) { log.Println(addr, "read error:", err) } break } + if ctx.Err() != nil { + log.Println(addr, ctx.Err()) + break + } + err = framer.Push(ctx, buffer[:n]) if err != nil { - // Wrap err and send to client then break + if ctx.Err() != nil { + log.Println(addr, ctx.Err()) + } else { + // TODO: Wrap err and send to client then break + } + break } } } -func _handleConnection(ctx context.Context, conn net.Conn) { - addr, ok := conn.RemoteAddr().(*net.TCPAddr) - assert.Assert(ok, "getting tcp address should be valid") +func handleAuth(conn net.Conn, nonce []byte) (ed25519.PublicKey, error) { + conn.SetDeadline(time.Now().Add(time.Second * 5)) - log.Println(addr, "accepted") - defer log.Println(addr, "disconnected") - defer conn.Close() + challengePacket := make([]byte, len(nonce)+1) + challengePacket[0] = packet.VERSION + copy(challengePacket[1:], nonce) - // ctx = newContext(ctx, addr) - // // TODO: consider adding timeout/deadline to ctx? + _, err := conn.Write(challengePacket) + if err != nil { + return nil, fmt.Errorf("error writing challenge: %w", err) + } - out, outErr := packet.RunFramer(ctx, conn) -outer: - for { - select { - case packet, ok := <-out: - if !ok { - break outer - } - log.Printf("client %v: request packet: %v\n", conn.RemoteAddr().String(), packet) - responsePacket, err := handlePacket(packet) - log.Printf("client %v: response packet: %v\n", conn.RemoteAddr().String(), responsePacket) - if err != nil { - log.Printf("client %v: error processing request: %v\n", conn.RemoteAddr().String(), err) - break outer - } - _, err = responsePacket.Into(conn) - if err != nil { - log.Printf("client %v: error writing packet: %v\n", conn.RemoteAddr().String(), err) - break outer - } + challengeResponsePacket := make([]byte, ed25519.PublicKeySize+ed25519.SignatureSize+1) + bytesRead := 0 + for bytesRead < len(challengeResponsePacket) { + n, err := conn.Read(challengeResponsePacket[bytesRead:]) + if err != nil { + return nil, fmt.Errorf("error reading challenge response: %w", err) + } + bytesRead += n + } - case err := <-outErr: - if err == packet.PacketUnsupportedEncoding { - _, err := unsupportedEncodingErrorPacket.Into(conn) - log.Printf("client %v: error writing unsupported encoding packet: %v\n", conn.RemoteAddr().String(), err) - } else if err == packet.PacketUnsupportedType { - _, err := unsupportedTypeErrorPacket.Into(conn) - log.Printf("client %v: error writing unsupported type packet: %v\n", conn.RemoteAddr().String(), err) - } else if err != nil { - log.Printf("client %v: internal error: %v\n", conn.RemoteAddr().String(), err) - } - break outer + if challengeResponsePacket[0] != packet.VERSION { + return nil, fmt.Errorf("incompatible version: %v", challengeResponsePacket[0]) + } - case <-ctx.Done(): - log.Printf("client %v: %v\n", conn.RemoteAddr().String(), ctx.Err()) - break outer - } + pubKey := ed25519.PublicKey(challengeResponsePacket[1 : 1+ed25519.PublicKeySize]) + signature := ed25519.PrivateKey(challengeResponsePacket[1+ed25519.PublicKeySize:]) + + if ok := ed25519.Verify(pubKey, nonce, signature); !ok { + return nil, errors.New("signature verification failed") } + + conn.SetDeadline(time.Time{}) + return pubKey, nil } type Session struct { + Server *server Addr *net.TCPAddr - WriteQueue <-chan packet.Packet + WriteQueue chan packet.Packet + ID snowflake.ID + PubKey ed25519.PublicKey mu sync.Mutex - challenge []byte + challenge []byte issuedTime time.Time } -func newSession(addr *net.TCPAddr, writeQueue <-chan packet.Packet) *Session { +func newSession(server *server, addr *net.TCPAddr, id snowflake.ID, pubKey ed25519.PublicKey) *Session { session := &Session{ + Server: server, Addr: addr, - WriteQueue: writeQueue, + PubKey: pubKey, + WriteQueue: make(chan packet.Packet, 10), challenge: make([]byte, 32), // Recommended nonce size } session.Challenge() // Make sure an initial nonce is generated @@ -263,58 +306,43 @@ func FromContext(ctx context.Context) (*Session, bool) { // TODO: Move everything below this to somewhere else -func handlePacket(pkt packet.Packet) (packet.Packet, error) { +func processPacket(ctx context.Context, pkt packet.Packet) packet.Packet { + session, ok := FromContext(ctx) + assert.Assert(ok, "context in process packet should always have a session") + var response packet.TypedMessage switch pkt.Type() { - case packet.PacketEko: - var request packet.EkoMessage - if err := pkt.DecodePayload(&request); err != nil { - return packet.Packet{}, fmt.Errorf("decode error: %v", err) - } - - response = &packet.EkoMessage{Message: "Eko \"" + request.Message + "\""} case packet.PacketSendMessage: - var request packet.SendMessageMessage + var request packet.SendMessage if err := pkt.DecodePayload(&request); err != nil { - return packet.Packet{}, fmt.Errorf("decode error: %v", err) + log.Println("decode error:", err) + response = &packet.ErrorMessage{Error: "malformed payload"} + break } content := strings.TrimSpace(request.Content) if content == "" { - response = &packet.ErrorMessage{Error: "content must not be blank"} + response = &packet.ErrorMessage{Error: "message content must not be blank"} break } + node := session.Server.Node message := data.Message{ Id: node.Generate(), - SenderId: node.Generate(), - FrequencyId: node.Generate(), - NetworkId: node.Generate(), + SenderId: session.ID, + FrequencyId: node.Generate(), // TODO: replace with actual ID + NetworkId: node.Generate(), // TODO: replace with actual ID Contents: content, } messages = append(messages, message) - response = &packet.EkoMessage{Message: "Eko OK"} - case packet.PacketGetMessages: - var request packet.GetMessagesMessage - if err := pkt.DecodePayload(&request); err != nil { - return packet.Packet{}, fmt.Errorf("decode error: %v", err) - } - - response = &packet.MessagesMessage{Messages: messages} + response = packet.NewOkMessage() default: - return packet.Packet{}, errors.New("TODO: not implemented yet") + response = &packet.ErrorMessage{Error: "use of unsupported packet type"} } - assert.NotNil(response, "response must always be set") - encoder, err := packet.NewMsgPackEncoder(response) - if err != nil { - return packet.Packet{}, fmt.Errorf("encode error: %v", err) - } - return packet.NewPacket(encoder), nil + assert.NotNil(response, "response must always be assigned to") + return packet.NewPacket(packet.NewMsgPackEncoder(response)) } -var ( - node = snowflake.NewNode(1) - messages []data.Message -) +var messages []data.Message |
