summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/packet/packet.go2
-rw-r--r--internal/packet/protocol.txt (renamed from internal/server/protocol.txt)0
-rw-r--r--internal/server/api/api.go40
-rw-r--r--internal/server/certs/server.crt (renamed from internal/server/server.crt)0
-rw-r--r--internal/server/certs/server.key28
-rw-r--r--internal/server/certs/sslgen.cnf24
-rw-r--r--internal/server/server.go148
-rw-r--r--internal/server/session/session.go85
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
+}