diff options
| author | Kyren223 <ulmliad223@gmail.com> | 2024-10-24 18:13:39 +0300 |
|---|---|---|
| committer | Kyren223 <ulmliad223@gmail.com> | 2024-10-24 18:13:39 +0300 |
| commit | a7f743f035d0f0d14400027e4781cad76503085a (patch) | |
| tree | 31c021ef76e7ca0428c5bca6b2aa2b23697f4f4c /internal | |
| parent | 688668f6d34d4d962247bd5df60c3fca61185927 (diff) | |
refactor: server split into server, session and api
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/packet/packet.go | 2 | ||||
| -rw-r--r-- | internal/packet/protocol.txt (renamed from internal/server/protocol.txt) | 0 | ||||
| -rw-r--r-- | internal/server/api/api.go | 40 | ||||
| -rw-r--r-- | internal/server/certs/server.crt (renamed from internal/server/server.crt) | 0 | ||||
| -rw-r--r-- | internal/server/certs/server.key | 28 | ||||
| -rw-r--r-- | internal/server/certs/sslgen.cnf | 24 | ||||
| -rw-r--r-- | internal/server/server.go | 148 | ||||
| -rw-r--r-- | internal/server/session/session.go | 85 |
8 files changed, 209 insertions, 118 deletions
diff --git a/internal/packet/packet.go b/internal/packet/packet.go index f38ad14..9365d93 100644 --- a/internal/packet/packet.go +++ b/internal/packet/packet.go @@ -239,7 +239,7 @@ func (f *PacketFramer) parse() (*Packet, error) { } if f.buffer[VERSION_OFFSET] != VERSION { - return nil, fmt.Errorf("%w version=%v", PacketUnsupportedVersion, f.buffer[VERSION_OFFSET]) + return nil, PacketUnsupportedVersion } encoding := Encoding(f.buffer[ENCODING_OFFSET] >> 6) diff --git a/internal/server/protocol.txt b/internal/packet/protocol.txt index 9a0070d..9a0070d 100644 --- a/internal/server/protocol.txt +++ b/internal/packet/protocol.txt diff --git a/internal/server/api/api.go b/internal/server/api/api.go new file mode 100644 index 0000000..4596cdb --- /dev/null +++ b/internal/server/api/api.go @@ -0,0 +1,40 @@ +package api + +import ( + "context" + "strings" + + "github.com/kyren223/eko/internal/data" + "github.com/kyren223/eko/internal/packet" + "github.com/kyren223/eko/internal/server/session" + "github.com/kyren223/eko/pkg/assert" +) + +func SendMessage(ctx context.Context, request *packet.SendMessage) packet.Payload { + sess, ok := session.FromContext(ctx) + assert.Assert(ok, "context in process packet should always have a session") + + content := strings.TrimSpace(request.Content) + if content == "" { + return &packet.ErrorMessage{Error: "message content must not be blank"} + } + + node := sess.Manager().Node() + message := data.Message{ + Id: node.Generate(), + SenderId: sess.ID(), + FrequencyId: node.Generate(), // TODO: replace with actual ID + NetworkId: node.Generate(), // TODO: replace with actual ID + Contents: content, + } + + messages = append(messages, message) + + payload := &packet.Messages{Messages: messages} + pkt := packet.NewPacket(packet.NewMsgPackEncoder(payload)) + sess.WriteQueue <- pkt + + return packet.NewOkMessage() +} + +var messages []data.Message diff --git a/internal/server/server.crt b/internal/server/certs/server.crt index caf2384..caf2384 100644 --- a/internal/server/server.crt +++ b/internal/server/certs/server.crt diff --git a/internal/server/certs/server.key b/internal/server/certs/server.key new file mode 100644 index 0000000..6f85667 --- /dev/null +++ b/internal/server/certs/server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCu4Xfs05Kq5//B +ISxDtLjCih4H6MnM2Fk5QPbjxQAzrI3c10S7kf2N6ytDwROLuecXI2GUn5syZlR7 +r9axBTret+GDqBNlMCXfVdjNKfnA+9elyBAzT31QlHu+xaGaGGh9YfEBeTK8dngx ++SWbevdBdOTt/c2M22+/kZQoVpbdZAXJixGdRiiXl852y+pSzIdtHXmF/e6HpqmD +Kj/QxWxXh5Lm3mdtMkLylvXDlmnvTPiIRPXQGvH5uVnX4g6E3QB32yGsMa9TgeGK +n15V5Xq/cAB3x1ngktCoGQk3YIRdYesLWJcMkGXFLr/nRze0j+81aPKTg2+zVKox +QDBpTYH9AgMBAAECggEAAqxqVgg5mEitFZAPw+ADgq0Nq3pI4RhRON/zRkuKZr/V +eJHthSteSg9vA5cNv1eYUeq8QNzo+8eQgazDZl5mZkw/Jteb2eXdoyeGH0QFtohD +GJyeRS9XnGYNhD7NayePXvqJCC6icqFVa6vCG7l9n5ZAmU2fGSJ4d8aRq/iZT6Lq +DMJ8g0vd2NXeQZXiLto6TGzLiIfBOmCSshSX9UxZNnXuzp2SYtAAAKHjft74rXfi +6ptLP5wKiHTazDJXOTezWa/FUn1leDgZUFrFw8RFRFkj209txcVbleHHtaLLtB4m +lkyvWzFpAcB9V8PAJDikc227yUY1gLKCo/XPEVMI8QKBgQDUCktxTUlUwF0Htblw +ebaSrtOo31ObzeDLUZLltiCNAH4LfML/Friqp/3b3RCk7ttMJ0x40W2ryyVcfFJD +ByLIzJn56oKMDp3+oK4EvhBdCLgUWxe7OPLE1en44RP3dtTgrwL8NF4Swn0XSMqN +JXoxZSh6ZX9t3CsbBvUNsLrmaQKBgQDTIv391c5Jh7wyVm+RZbFSRhIFdZwM77zA +xTRoKNLJG55SxCFVypwi+fW+XCTxUseu9hYWm/loFt6WCj9G8NspVn603JxRFDJ5 +ZfKLxCEzHv37r4Ikfkz9Ur3zILDUamh5n9k0xaehijG5UN0E6oMb2aijkGpfR9C7 +lMeDUpIUdQKBgQCc33jtPEaygxbQq2ukgQfIAbvqRj9vF6PmCFXryRDQALs/+G+z +lbXN3Dd+QFEeFPD9qF11TUwcO1FK2Vdq646ajHSmZw9qWy4cwfe83njwu5CkzrgB +9aH2amSNksfY33ujNxe4S24F5wnw/hOM1VtPzA1kcE8Ukjz54+3M0SGVcQKBgQCX +Tr5iyHQ7MbDnChaORihbY6fKjcw1YSENrMWv14Gnacl+Wtvg1YAreKJptzMqVj6P +3f9aWJDVzX3aXKGYP0dwSnInPuovGKBLa4n0K6v3LMCrO82yRWj7Ke7Ca60NrOwO +LUXFkdhd5eFYguDBgyZOMR+vh/ETMy4dH7QTIntZeQKBgEPalCWl85u9MuU/bEWQ +qY5XO4+ocPRzJQKpIrM6rsIPWcnEXuNWa/cRaBEsUjqk+2pR54v8aX98kAYqGlbs +SaWXn/UN2Ml3lkusXReJoILrEWT5bpwwDCwDi47avMLJj5CGd8fhDHSRDpZJa1Ox +Sb7kfjRNjO9LjUcNpqIXxtlE +-----END PRIVATE KEY----- diff --git a/internal/server/certs/sslgen.cnf b/internal/server/certs/sslgen.cnf new file mode 100644 index 0000000..888d519 --- /dev/null +++ b/internal/server/certs/sslgen.cnf @@ -0,0 +1,24 @@ +# https://stackoverflow.com/questions/46100377/how-to-create-an-openssl-self-signed-certificate-using-san +[CA_default] +copy_extensions = copy + +[req] +default_bits = 2048 +prompt = no +default_md = sha256 +distinguished_name = req_distinguished_name +x509_extensions = v3_ca + +[req_distinguished_name] +emailAddress = kyren223@proton.me +CN = kyren223.dev + +[v3_ca] +basicConstraints = CA:FALSE +keyUsage = digitalSignature, keyEncipherment +subjectAltName = @alternate_names + +[alternate_names] +DNS.1 = localhost +DNS.2 = *.localhost +DNS.3 = eko.localhost diff --git a/internal/server/server.go b/internal/server/server.go index ff28ad7..ab178eb 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -13,28 +13,25 @@ import ( "net" "os" "strconv" - "strings" "sync" "time" - "github.com/kyren223/eko/internal/data" "github.com/kyren223/eko/internal/packet" + "github.com/kyren223/eko/internal/server/api" + "github.com/kyren223/eko/internal/server/session" "github.com/kyren223/eko/pkg/assert" "github.com/kyren223/eko/pkg/snowflake" ) -//go:embed server.crt +//go:embed certs/server.crt var certPEM []byte -//go:embed server.key +//go:embed certs/server.key var keyPEM []byte var ( nodeId int64 = 0 tlsConfig *tls.Config - - unsupportedEncodingErrorPacket packet.Packet - unsupportedTypeErrorPacket packet.Packet ) var ErrClosedNilListener error = errors.New("server: close on nil listener") @@ -48,18 +45,12 @@ func init() { tlsConfig = &tls.Config{ Certificates: []tls.Certificate{cert}, } - - message := packet.ErrorMessage{Error: packet.PacketUnsupportedEncoding.Error()} - unsupportedEncodingErrorPacket = packet.NewPacket(packet.NewMsgPackEncoder(&message)) - - message = packet.ErrorMessage{Error: packet.PacketUnsupportedType.Error()} - unsupportedTypeErrorPacket = packet.NewPacket(packet.NewMsgPackEncoder(&message)) } type server struct { - Node *snowflake.Node + node *snowflake.Node Port uint16 - sessions map[snowflake.ID]*Session + sessions map[snowflake.ID]*session.Session sessMu sync.RWMutex } @@ -69,16 +60,16 @@ func NewServer(port uint16) server { nodeId++ return server{ - Node: node, + node: node, Port: port, - sessions: map[snowflake.ID]*Session{}, + sessions: map[snowflake.ID]*session.Session{}, } } -func (s *server) AddSession(session *Session) { +func (s *server) AddSession(session *session.Session) { s.sessMu.Lock() defer s.sessMu.Unlock() - s.sessions[session.ID] = session + s.sessions[session.ID()] = session } func (s *server) RemoveSession(id snowflake.ID) { @@ -87,13 +78,17 @@ func (s *server) RemoveSession(id snowflake.ID) { delete(s.sessions, id) } -func (s *server) Session(id snowflake.ID) (*Session, bool) { +func (s *server) Session(id snowflake.ID) (*session.Session, bool) { s.sessMu.RLock() defer s.sessMu.RUnlock() session, ok := s.sessions[id] return session, ok } +func (s *server) Node() *snowflake.Node { + return s.node +} + func (s *server) ListenAndServe(ctx context.Context) { listener, err := tls.Listen("tcp4", ":"+strconv.Itoa(int(s.Port)), tlsConfig) if err != nil { @@ -148,23 +143,24 @@ func handleConnection(ctx context.Context, conn net.Conn, server *server) { } // 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) + id := server.Node().Generate() + sess := session.NewSession(server, addr, id, pubKey) + server.AddSession(sess) + ctx = session.NewContext(ctx, sess) framer := packet.NewFramer(ctx) defer func() { conn.Close() close(framer.Out) - server.RemoveSession(session.ID) - close(session.WriteQueue) + server.RemoveSession(sess.ID()) + close(sess.WriteQueue) + sess.WriteQueue = nil log.Println(addr, "disconnected") }() go func() { for { - packet, ok := <-session.WriteQueue + packet, ok := <-sess.WriteQueue if !ok { break } @@ -176,7 +172,6 @@ func handleConnection(ctx context.Context, conn net.Conn, server *server) { break } } - session.WriteQueue = nil }() go func() { @@ -186,10 +181,10 @@ func handleConnection(ctx context.Context, conn net.Conn, server *server) { break } response := processPacket(ctx, request) - if session.WriteQueue == nil { + if sess.WriteQueue == nil { break } - session.WriteQueue <- response + sess.WriteQueue <- response } }() @@ -216,7 +211,9 @@ func handleConnection(ctx context.Context, conn net.Conn, server *server) { if ctx.Err() != nil { log.Println(addr, ctx.Err()) } else { - // TODO: Wrap err and send to client then break + payload := packet.ErrorMessage{Error: err.Error()} + pkt := packet.NewPacket(packet.NewMsgPackEncoder(&payload)) + sess.WriteQueue <- pkt } break } @@ -265,58 +262,8 @@ func handleAuth(conn net.Conn, nonce []byte) (ed25519.PublicKey, error) { return pubKey, nil } -type Session struct { - Server *server - Addr *net.TCPAddr - WriteQueue chan packet.Packet - ID snowflake.ID - PubKey ed25519.PublicKey - - mu sync.Mutex - challenge []byte - issuedTime time.Time -} - -func newSession(server *server, addr *net.TCPAddr, id snowflake.ID, pubKey ed25519.PublicKey) *Session { - session := &Session{ - Server: server, - Addr: addr, - PubKey: pubKey, - WriteQueue: make(chan packet.Packet, 10), - challenge: make([]byte, 32), // Recommended nonce size - } - session.Challenge() // Make sure an initial nonce is generated - return session -} - -func (s *Session) Challenge() []byte { - s.mu.Lock() - defer s.mu.Unlock() - if time.Since(s.issuedTime) > time.Minute { - s.issuedTime = time.Now() - _, err := rand.Read(s.challenge) - assert.NoError(err, "random should always produce a value") - } - return s.challenge -} - -type key struct{} - -var sessKey key - -func newContext(ctx context.Context, sess *Session) context.Context { - return context.WithValue(ctx, sessKey, sess) -} - -func FromContext(ctx context.Context) (*Session, bool) { - sess, ok := ctx.Value(sessKey).(*Session) - return sess, ok -} - -// TODO: Move everything below this to somewhere else - func processPacket(ctx context.Context, pkt packet.Packet) packet.Packet { - session, ok := FromContext(ctx) + session, ok := session.FromContext(ctx) assert.Assert(ok, "context in process packet should always have a session") var response packet.Payload @@ -334,47 +281,14 @@ func processPacket(ctx context.Context, pkt packet.Packet) packet.Packet { } func processRequest(ctx context.Context, request packet.Payload) packet.Payload { - session, ok := FromContext(ctx) + session, ok := session.FromContext(ctx) assert.Assert(ok, "context in process packet should always have a session") - log.Println(session.Addr, "processing", request.Type(), "request:", request) switch request := request.(type) { case *packet.SendMessage: - content := strings.TrimSpace(request.Content) - if content == "" { - return &packet.ErrorMessage{Error: "message content must not be blank"} - } - - node := session.Server.Node - message := data.Message{ - Id: node.Generate(), - SenderId: session.ID, - FrequencyId: node.Generate(), // TODO: replace with actual ID - NetworkId: node.Generate(), // TODO: replace with actual ID - Contents: content, - } - sendMessage(ctx, message) - - return packet.NewOkMessage() - + return api.SendMessage(ctx, request) default: return &packet.ErrorMessage{Error: "use of unsupported packet type"} } } - -var messages []data.Message - -func sendMessage(ctx context.Context, msg data.Message) { - session, ok := FromContext(ctx) - assert.Assert(ok, "context in process packet should always have a session") - - messages = append(messages, msg) - - payload := &packet.Messages{ - Messages: messages, - } - - pkt := packet.NewPacket(packet.NewMsgPackEncoder(payload)) - session.WriteQueue <- pkt -} diff --git a/internal/server/session/session.go b/internal/server/session/session.go new file mode 100644 index 0000000..ff56780 --- /dev/null +++ b/internal/server/session/session.go @@ -0,0 +1,85 @@ +package session + +import ( + "context" + "crypto/ed25519" + "crypto/rand" + "net" + "sync" + "time" + + "github.com/kyren223/eko/internal/packet" + "github.com/kyren223/eko/pkg/assert" + "github.com/kyren223/eko/pkg/snowflake" +) + +type SessionManager interface { + AddSession(session *Session) + RemoveSession(id snowflake.ID) + Session(id snowflake.ID) (session *Session, ok bool) + + Node() *snowflake.Node +} + +type Session struct { + // Channel to directly write packets to the cient. + // Can be nil in cases where the connection is not available. + WriteQueue chan packet.Packet + PubKey ed25519.PublicKey + + manager SessionManager + addr *net.TCPAddr + id snowflake.ID + + mu sync.Mutex + challenge []byte + issuedTime time.Time +} + +func NewSession(manager SessionManager, addr *net.TCPAddr, id snowflake.ID, pubKey ed25519.PublicKey) *Session { + session := &Session{ + manager: manager, + addr: addr, + PubKey: pubKey, + WriteQueue: make(chan packet.Packet, 10), + challenge: make([]byte, 32), // Recommended nonce size + } + session.Challenge() // Make sure an initial nonce is generated + return session +} + +func (s *Session) Addr() *net.TCPAddr { + return s.addr +} + +func (s *Session) ID() snowflake.ID { + return s.id +} + +func (s *Session) Manager() SessionManager { + return s.manager +} + +func (s *Session) Challenge() []byte { + s.mu.Lock() + defer s.mu.Unlock() + if time.Since(s.issuedTime) > time.Minute { + s.issuedTime = time.Now() + _, err := rand.Read(s.challenge) + assert.NoError(err, "random should always produce a value") + } + return s.challenge +} + +type key struct{} + +var sessKey key + +func NewContext(ctx context.Context, sess *Session) context.Context { + return context.WithValue(ctx, sessKey, sess) +} + +func FromContext(ctx context.Context) (*Session, bool) { + sess, ok := ctx.Value(sessKey).(*Session) + return sess, ok +} |
