summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbt <bt@rctt.net>2026-04-10 19:31:31 +0200
committerbt <bt@rctt.net>2026-04-18 22:33:20 +0200
commite9aebac1a2a4732763c2f7e4428a23983d4eb6a3 (patch)
treeb66ea36939ea75360ed6c554cf352348d19786bb
parentf66e28aa88a5f4176934001fa9e4967ddccde4a9 (diff)
downloadsolec-e9aebac1a2a4732763c2f7e4428a23983d4eb6a3.tar.gz
solec-e9aebac1a2a4732763c2f7e4428a23983d4eb6a3.zip
[common] Exchange messages between servers
-rw-r--r--client/client.go9
-rw-r--r--cmd/client/main.go1
-rw-r--r--core/data.go33
-rw-r--r--core/errors.go13
-rw-r--r--core/payload.go34
-rw-r--r--docs/rfc.md69
-rw-r--r--docs/rfc.txt114
-rw-r--r--server/message.go37
-rw-r--r--server/remote.go112
-rw-r--r--server/server.go139
-rw-r--r--server/user.go68
11 files changed, 432 insertions, 197 deletions
diff --git a/client/client.go b/client/client.go
index 7fb9cc9..0fe967f 100644
--- a/client/client.go
+++ b/client/client.go
@@ -1,6 +1,8 @@
package client
import (
+ "errors"
+ "io"
"net"
"go.rctt.net/solec/core"
@@ -37,12 +39,12 @@ func (c *Client) Connect() error {
}
defer c.conn.Close()
- hs := core.Handshake{0, 1}
+ hs := core.Handshake{0, 1, core.ConnTypeUser}
if err := core.Send(c.conn, hs); err != nil {
return err
}
- auth := core.Auth{c.uname, c.pass}
+ auth := core.UserAuth{c.uname, c.pass}
if err := core.Send(c.conn, auth); err != nil {
return err
}
@@ -66,6 +68,9 @@ func (c *Client) read() {
payload, err := core.Read(c.conn)
if err != nil {
c.h.HandleError(err)
+ if errors.Is(err, io.EOF) {
+ break
+ }
}
c.handlePayload(payload)
diff --git a/cmd/client/main.go b/cmd/client/main.go
index d3bffd7..635e7ca 100644
--- a/cmd/client/main.go
+++ b/cmd/client/main.go
@@ -38,7 +38,6 @@ func main() {
log.Println("connecting to " + serverAddr + " as " + user)
c = client.NewClient(&Handler{}, serverAddr, user, "valid")
-
go prompt.Read()
if err := c.Connect(); err != nil {
diff --git a/core/data.go b/core/data.go
index a96b56f..dd2777f 100644
--- a/core/data.go
+++ b/core/data.go
@@ -16,13 +16,14 @@ type Wrapper interface {
type PayloadType uint8
const (
- PayloadUnknown PayloadType = 0x00
- PayloadSuccess = 0x01
- PayloadError = 0x02
- PayloadHandshake = 0x03
- PayloadAuth = 0x04
- PayloadMessage = 0x05
- PayloadTest = 0xFF
+ PayloadUnknown PayloadType = 0x00
+ PayloadSuccess = 0x01
+ PayloadError = 0x02
+ PayloadHandshake = 0x03
+ PayloadUserAuth = 0x04
+ PayloadMessage = 0x05
+ PayloadServerAuth = 0x06
+ PayloadTest = 0xFF
)
type ErrorType uint8
@@ -33,6 +34,14 @@ const (
ErrorNotFound = 0x02
)
+type ConnType uint8
+
+const (
+ ConnTypeUnknown ConnType = 0x00
+ ConnTypeUser = 0x01
+ ConnTypeServer = 0x02
+)
+
type Frame struct {
Length uint16
Type PayloadType
@@ -99,7 +108,7 @@ func Decode(buf io.Reader) (any, error) {
err := binary.Read(buf, binary.BigEndian, &pTypeByte)
if err != nil {
- return nil, fmt.Errorf("cannot read payload type: %v", err)
+ return nil, fmt.Errorf("cannot read payload type: %w", err)
}
pType := PayloadType(pTypeByte)
@@ -107,7 +116,7 @@ func Decode(buf io.Reader) (any, error) {
var pLen uint16
err = binary.Read(buf, binary.BigEndian, &pLen)
if err != nil {
- return nil, fmt.Errorf("cannot read payload length: %v", err)
+ return nil, fmt.Errorf("cannot read payload length: %w", err)
}
switch pType {
@@ -117,8 +126,10 @@ func Decode(buf io.Reader) (any, error) {
return DecodeError(buf)
case PayloadHandshake:
return DecodeHandshake(buf)
- case PayloadAuth:
- return DecodeAuth(buf)
+ case PayloadUserAuth:
+ return DecodeUserAuth(buf)
+ case PayloadServerAuth:
+ return DecodeServerAuth(buf)
case PayloadMessage:
return DecodeMessage(buf)
case PayloadTest:
diff --git a/core/errors.go b/core/errors.go
index a50fbf7..ed6702d 100644
--- a/core/errors.go
+++ b/core/errors.go
@@ -3,10 +3,11 @@ package core
import "errors"
var (
- ErrUnexpectedPayloadType = errors.New("unexpected payload type")
- ErrAuthInvalidUser = errors.New("invalid user")
- ErrAuthInvalidPassword = errors.New("invalid password")
- ErrInvalidAddress = errors.New("invalid address")
- ErrNotSupported = errors.New("not supported")
- ErrDisconnected = errors.New("disconnected")
+ ErrUnexpectedPayloadType = errors.New("unexpected payload type")
+ ErrAuthInvalidUser = errors.New("invalid user")
+ ErrAuthInvalidPassword = errors.New("invalid password")
+ ErrAuthReverseLookupFailed = errors.New("declared name does not match with DNS name")
+ ErrInvalidAddress = errors.New("invalid address")
+ ErrNotSupported = errors.New("not supported")
+ ErrDisconnected = errors.New("disconnected")
)
diff --git a/core/payload.go b/core/payload.go
index b8e8b9f..e943647 100644
--- a/core/payload.go
+++ b/core/payload.go
@@ -36,11 +36,12 @@ func DecodeError(buf io.Reader) (Error, error) {
type Handshake struct {
Major, Minor uint8
+ ConnType ConnType
}
func (h Handshake) Wrap() (PayloadType, []any) {
return PayloadHandshake, []any{
- h.Major, h.Minor,
+ h.Major, h.Minor, h.ConnType,
}
}
@@ -57,22 +58,27 @@ func DecodeHandshake(buf io.Reader) (Handshake, error) {
return h, err
}
+ err = decodeNumeric(buf, &h.ConnType)
+ if err != nil {
+ return h, err
+ }
+
return h, nil
}
-type Auth struct {
+type UserAuth struct {
Name string
Pass string
}
-func (a Auth) Wrap() (PayloadType, []any) {
- return PayloadAuth, []any{
+func (a UserAuth) Wrap() (PayloadType, []any) {
+ return PayloadUserAuth, []any{
a.Name, a.Pass,
}
}
-func DecodeAuth(buf io.Reader) (Auth, error) {
- var a Auth
+func DecodeUserAuth(buf io.Reader) (UserAuth, error) {
+ var a UserAuth
err := decodeString(buf, &a.Name)
if err != nil {
return a, err
@@ -86,6 +92,22 @@ func DecodeAuth(buf io.Reader) (Auth, error) {
return a, nil
}
+type ServerAuth struct {
+ Name string
+}
+
+func (a ServerAuth) Wrap() (PayloadType, []any) {
+ return PayloadServerAuth, []any{
+ a.Name,
+ }
+}
+
+func DecodeServerAuth(buf io.Reader) (ServerAuth, error) {
+ var a ServerAuth
+ err := decodeString(buf, &a.Name)
+ return a, err
+}
+
type Message struct {
Source string
Target string
diff --git a/docs/rfc.md b/docs/rfc.md
index 8c5818e..8296cfa 100644
--- a/docs/rfc.md
+++ b/docs/rfc.md
@@ -153,15 +153,16 @@ Payload type attributes describes following characteristics:
* C - Client: can be send only by a client
* E - Empty: signals an event but does not carry any data
-| Type | Name | Attributes |
-|------|-----------|------------|
-| 0x00 | | R |
-| 0x01 | Success | SCE |
-| 0x02 | Error | S |
-| 0x03 | Handshake | SC |
-| 0x04 | Auth | C |
-| 0x05 | Message | SC |
-| 0xFF | Test | R |
+| Type | Name | Attributes |
+|------|------------|------------|
+| 0x00 | | R |
+| 0x01 | Success | SCE |
+| 0x02 | Error | S |
+| 0x03 | Handshake | SC |
+| 0x04 | UserAuth | C |
+| 0x05 | Message | SC |
+| 0x06 | ServerAuth | S |
+| 0xFF | Test | R |
### Success
@@ -177,8 +178,9 @@ Payload is always empty for this type.
| Type | Description |
|------|-----------------------------------------------------------|
-| 0x01 | Auth failed. Invalid username or password. |
+| 0x01 | Client auth failed. Invalid username or password. |
| 0x02 | Not found. User or channel cannot access user or channel. |
+| 0x03 | Server auth failed. Unknown name. |
### Handshake
@@ -200,13 +202,19 @@ different auth method will be used.
| 0x01 | User -> Server |
| 0x02 | Server -> Server |
-### Auth
+### UserAuth
| Type | Name |
|--------|----------|
| string | username |
| string | password |
+### ServerAuth
+
+| Type | Name |
+|--------|----------|
+| string | name |
+
### Message
| Type | Name |
@@ -238,7 +246,7 @@ Some operations require multiple rounds of communication.
In this case payloads are send in a sequence. Payload that is not part of this
specific operation (for example incoming message) cannot interrupt this process.
-### Connection initialisation
+### Client-Server connection initialisation
~~~ ascii-art
+--------+ +--------+
@@ -254,7 +262,7 @@ specific operation (for example incoming message) cannot interrupt this process.
| +- If [ver_major] does not match server
| | protocol version close the connection
| |
- | Send [Auth] |
+ | Send [UserAuth] |
+--------------------------->|
| |
| Send [Error 0x01] |
@@ -263,4 +271,37 @@ specific operation (for example incoming message) cannot interrupt this process.
| |
| Send [Sucesss] |
|<---------------------------+
-~~~ \ No newline at end of file
+~~~
+
+### Server-Server connection initialisation
+
+~~~ ascii-art
++--------+ +--------+
+| Server | | Server |
++----+---+ +----+---+
+ | |
+ | Initialise TCP connection |
+ +--------------------------->|
+ | |
+ | Send [Handshake] |
+ +--------------------------->|
+ | |
+ | +- If [ver_major] does not match server
+ | | protocol version close the connection
+ | |
+ | Send [ServerAuth] |
+ +--------------------------->|
+ | |
+ | Send [Error 0x03] |
+ |<---------------------------+- If [name] is not present in known public
+ | | keys list.
+ | |
+ | Send [Sucesss] |
+ |<---------------------------+
+~~~
+
+## Server to server operation
+
+Exchanging messages between SOLEC servers is a core concept behind the project.
+Sending message to user residing on a different server require estabilishing a
+connection between both servers.
diff --git a/docs/rfc.txt b/docs/rfc.txt
index f9e8bca..ffd2f67 100644
--- a/docs/rfc.txt
+++ b/docs/rfc.txt
@@ -1,7 +1,7 @@
SOLEC Working Group bt, Ed.
Internet-Draft RCTT.net
-Intended status: Experimental 14 April 2026
-Expires: 16 October 2026
+Intended status: Experimental 18 April 2026
+Expires: 20 October 2026
System of Lightweight Electronic Communication
@@ -29,11 +29,14 @@ Table of Contents
2.4.1. Success
2.4.2. Error
2.4.3. Handshake
- 2.4.4. Auth
- 2.4.5. Message
- 2.4.6. Test
+ 2.4.4. UserAuth
+ 2.4.5. ServerAuth
+ 2.4.6. Message
+ 2.4.7. Test
2.5. Sequential operations
- 2.5.1. Connection initialisation
+ 2.5.1. Client-Server connection initialisation
+ 2.5.2. Server-Server connection initialisation
+ 2.6. Server
1. Introduction
@@ -153,25 +156,27 @@ Table of Contents
* C - Client: can be send only by a client
* E - Empty: signals an event but does not carry any data
- +======+===========+============+
- | Type | Name | Attributes |
- +======+===========+============+
- | 0x00 | | R |
- +------+-----------+------------+
- | 0x01 | Success | SCE |
- +------+-----------+------------+
- | 0x02 | Error | S |
- +------+-----------+------------+
- | 0x03 | Handshake | SC |
- +------+-----------+------------+
- | 0x04 | Auth | C |
- +------+-----------+------------+
- | 0x05 | Message | SC |
- +------+-----------+------------+
- | 0xFF | Test | R |
- +------+-----------+------------+
-
- Table 1
+ +======+============+============+
+ | Type | Name | Attributes |
+ +======+============+============+
+ | 0x00 | | R |
+ +------+------------+------------+
+ | 0x01 | Success | SCE |
+ +------+------------+------------+
+ | 0x02 | Error | S |
+ +------+------------+------------+
+ | 0x03 | Handshake | SC |
+ +------+------------+------------+
+ | 0x04 | UserAuth | C |
+ +------+------------+------------+
+ | 0x05 | Message | SC |
+ +------+------------+------------+
+ | 0x06 | ServerAuth | S |
+ +------+------------+------------+
+ | 0xFF | Test | R |
+ +------+------------+------------+
+
+ Table 1
2.4.1. Success
@@ -192,10 +197,12 @@ Table of Contents
+======+============================================================+
| Type | Description |
+======+============================================================+
- | 0x01 | Auth failed. Invalid username or password. |
+ | 0x01 | Client auth failed. Invalid username or password. |
+------+------------------------------------------------------------+
- | 0x02 | Not found. User or channel cannot access |
- | | user or channel. |
+ | 0x02 | Not found. User or channel cannot access user or |
+ | | channel. |
+ +------+------------------------------------------------------------+
+ | 0x03 | Server auth failed. Unknown name. |
+------+------------------------------------------------------------+
Table 3
@@ -231,7 +238,7 @@ Table of Contents
Table 5
-2.4.4. Auth
+2.4.4. UserAuth
+========+==========+
| Type | Name |
@@ -243,7 +250,17 @@ Table of Contents
Table 6
-2.4.5. Message
+2.4.5. ServerAuth
+
+ +========+======+
+ | Type | Name |
+ +========+======+
+ | string | name |
+ +--------+------+
+
+ Table 7
+
+2.4.6. Message
+===========+=================+
| Type | Name |
@@ -257,9 +274,9 @@ Table of Contents
| string | message_content |
+-----------+-----------------+
- Table 7
+ Table 8
-2.4.6. Test
+2.4.7. Test
Test payload is used for encoder and decoders testing. Clients and
servers should ignore this kind of payload.
@@ -284,7 +301,7 @@ Table of Contents
| uint64 | num4 |
+-----------+-------+
- Table 8
+ Table 9
2.5. Sequential operations
@@ -293,7 +310,7 @@ Table of Contents
this specific operation (for example incoming message) cannot
interrupt this process.
-2.5.1. Connection initialisation
+2.5.1. Client-Server connection initialisation
+--------+ +--------+
| Client | | Server |
@@ -308,7 +325,7 @@ Table of Contents
| +- If [ver_major] does not match server
| | protocol version close the connection
| |
- | Send [Auth] |
+ | Send [UserAuth] |
+--------------------------->|
| |
| Send [Error 0x01] |
@@ -317,3 +334,30 @@ Table of Contents
| |
| Send [Sucesss] |
|<---------------------------+
+
+2.5.2. Server-Server connection initialisation
+
+ +--------+ +--------+
+ | Server | | Server |
+ +----+---+ +----+---+
+ | |
+ | Initialise TCP connection |
+ +--------------------------->|
+ | |
+ | Send [Handshake] |
+ +--------------------------->|
+ | |
+ | +- If [ver_major] does not match server
+ | | protocol version close the connection
+ | |
+ | Send [ServerAuth] |
+ +--------------------------->|
+ | |
+ | Send [Error 0x03] |
+ |<---------------------------+- If [name] is not present in known public
+ | | keys list.
+ | |
+ | Send [Sucesss] |
+ |<---------------------------+
+
+2.6. Server
diff --git a/server/message.go b/server/message.go
index c1384f7..2487abd 100644
--- a/server/message.go
+++ b/server/message.go
@@ -1,6 +1,8 @@
package server
import (
+ "errors"
+ "fmt"
"log"
"time"
@@ -27,3 +29,38 @@ func (s *Server) SendBroadcast(msg string) {
}
}
}
+
+func (s *Server) handleMessage(msg core.Message) error {
+ log.Println("message:", msg.Source, "->", msg.Target, msg.Content)
+
+ channel, host, err := core.ReadAddr(msg.Target)
+ if err != nil {
+ return err
+ }
+
+ if host == s.name {
+ return s.handleLocalMessage(channel, msg)
+ }
+
+ return s.handleOutboundMessage(channel, host, msg)
+}
+
+func (s *Server) handleLocalMessage(channel string, msg core.Message) error {
+ s.usersMu.RLock()
+ user, ok := s.users[channel]
+ if !ok {
+ return errors.New("target not found")
+ }
+ s.usersMu.RUnlock()
+
+ return user.Send(msg)
+}
+
+func (s *Server) handleOutboundMessage(channel, host string, msg core.Message) error {
+ remote, err := s.getRemote(host)
+ if err != nil {
+ return fmt.Errorf("cannot access remote server: %w", err)
+ }
+
+ return core.Send(remote.Conn, msg)
+}
diff --git a/server/remote.go b/server/remote.go
new file mode 100644
index 0000000..2449511
--- /dev/null
+++ b/server/remote.go
@@ -0,0 +1,112 @@
+package server
+
+import (
+ "log"
+ "net"
+
+ "go.rctt.net/solec/core"
+)
+
+type RemoteServer struct {
+ Name string
+ Conn net.Conn
+}
+
+func NewRemoteServer(name string, conn net.Conn) RemoteServer {
+ return RemoteServer{name, conn}
+}
+
+func (s *Server) handleServerConn(conn net.Conn) {
+ defer conn.Close()
+
+ name, err := s.performServerAuth(conn)
+ if err != nil {
+ log.Println("server auth error:", err)
+ return
+ }
+
+ s.serversMu.RLock()
+ if _, ok := s.servers[name]; ok {
+ log.Println("server already connected")
+ return
+ }
+ s.serversMu.RUnlock()
+
+ rs := NewRemoteServer(name, conn)
+ s.serversMu.Lock()
+ s.servers[name] = rs
+ s.serversMu.Unlock()
+ log.Println("connection from server:", name)
+
+ defer func() {
+ s.serversMu.Lock()
+ log.Println("server disconnected: ", rs.Name)
+ delete(s.servers, rs.Name)
+ s.serversMu.Unlock()
+ }()
+
+ if err := s.readInput(conn); err != nil {
+ log.Println(err)
+ }
+}
+
+func (s *Server) performServerAuth(conn net.Conn) (string, error) {
+ payload, err := core.Decode(conn)
+ if err != nil {
+ return "", err
+ }
+ auth, ok := payload.(core.ServerAuth)
+ if !ok {
+ return "", core.ErrUnexpectedPayloadType
+ }
+
+ if err := core.Send(conn, core.Success{}); err != nil {
+ return "", err
+ }
+
+ return auth.Name, nil
+}
+
+func (s *Server) getRemote(name string) (RemoteServer, error) {
+ s.serversMu.RLock()
+ remote, ok := s.servers[name]
+ s.serversMu.RUnlock()
+
+ if ok {
+ return remote, nil
+ }
+
+ conn, err := s.initRemoteConn(name)
+ if err != nil {
+ return RemoteServer{}, err
+ }
+
+ rs := NewRemoteServer(name, conn)
+ s.serversMu.Lock()
+ s.servers[name] = rs
+ s.serversMu.Unlock()
+ log.Println("connected to server:", name)
+
+ return rs, nil
+}
+
+func (s *Server) initRemoteConn(name string) (net.Conn, error) {
+ conn, err := net.Dial("tcp", name+":9999")
+ if err != nil {
+ return conn, err
+ }
+
+ hs := core.Handshake{0, 1, core.ConnTypeServer}
+ if err := core.Send(conn, hs); err != nil {
+ conn.Close()
+ return conn, err
+ }
+
+ auth := core.ServerAuth{Name: s.name}
+ if err := core.Send(conn, auth); err != nil {
+ conn.Close()
+ return conn, err
+ }
+
+ return conn, nil
+}
diff --git a/server/server.go b/server/server.go
index 712f654..7573968 100644
--- a/server/server.go
+++ b/server/server.go
@@ -13,7 +13,9 @@ type Server struct {
listenAddr string
name string
users map[string]User
- mu sync.Mutex
+ servers map[string]RemoteServer
+ usersMu sync.RWMutex
+ serversMu sync.RWMutex
}
func NewServer(listenAddr string, name string) *Server {
@@ -21,6 +23,7 @@ func NewServer(listenAddr string, name string) *Server {
listenAddr: listenAddr,
name: name,
users: make(map[string]User),
+ servers: make(map[string]RemoteServer),
}
}
@@ -45,158 +48,64 @@ func (s *Server) Start() error {
func (s *Server) handleConn(conn net.Conn) {
defer conn.Close()
- if err := s.performHandshake(conn); err != nil {
- log.Println("handshake error:", err)
- return
- }
-
- user, err := s.performAuth(conn)
+ cType, err := s.performHandshake(conn)
if err != nil {
- log.Println("auth error:", err)
+ log.Println("handshake error:", err)
return
}
- defer func() {
- s.mu.Lock()
- log.Println("client disconnected: ", user.Name)
- delete(s.users[user.Name].Conns, conn)
- if len(s.users[user.Name].Conns) == 0 {
- log.Println("all connections closed for user:", user.Name)
- delete(s.users, user.Name)
- }
- s.mu.Unlock()
- }()
-
- if err := s.readInput(conn, user); err != nil {
- log.Println(err)
+ switch cType {
+ case core.ConnTypeUnknown:
+ log.Println("invalid connection type")
+ case core.ConnTypeUser:
+ s.handleUserConn(conn)
+ case core.ConnTypeServer:
+ s.handleServerConn(conn)
}
}
-func (s *Server) performHandshake(conn net.Conn) error {
- serverHs := core.Handshake{0, 1}
+func (s *Server) performHandshake(conn net.Conn) (core.ConnType, error) {
+ serverHs := core.Handshake{0, 2, core.ConnTypeServer}
if err := core.Send(conn, serverHs); err != nil {
- return err
+ return core.ConnTypeUnknown, err
}
clientPayload, err := core.Decode(conn)
if err != nil {
- return err
+ return core.ConnTypeUnknown, err
}
clientHs, ok := clientPayload.(core.Handshake)
if !ok {
- return core.ErrUnexpectedPayloadType
+ return core.ConnTypeUnknown, core.ErrUnexpectedPayloadType
}
if serverHs.Major != clientHs.Major {
- return errors.New("server and client are using different protocol version")
- }
-
- return nil
-}
-
-func (s *Server) performAuth(conn net.Conn) (User, error) {
- clientPayload, err := core.Decode(conn)
- if err != nil {
- return User{}, err
- }
-
- clientAuth, ok := clientPayload.(core.Auth)
- if !ok {
- return User{}, core.ErrUnexpectedPayloadType
- }
-
- // For testing ---
- if clientAuth.Pass != "valid" {
- if err := core.Send(conn, core.Error{core.ErrorAuthFailed}); err != nil {
- log.Println("cannot send auth error:", err)
- }
-
- return User{}, core.ErrAuthInvalidPassword
- }
- // ---
-
- if err := core.Send(conn, core.Success{}); err != nil {
- return User{}, err
- }
-
- user, ok := s.users[clientAuth.Name]
- if !ok {
- log.Println("initial connection from user:", clientAuth.Name)
- user = NewUser(conn, clientAuth)
- s.users[clientAuth.Name] = user
- return user, nil
+ return clientHs.ConnType, errors.New("server and client are using different protocol version")
}
- log.Println("next connection from user:", user.Name)
- user.Conns[conn] = struct{}{}
- return user, nil
+ return clientHs.ConnType, nil
}
-func (s *Server) readInput(conn net.Conn, user User) error {
+func (s *Server) readInput(conn net.Conn) error {
for {
payload, err := core.Decode(conn)
if err != nil {
return err
}
- if err := s.handlePayload(conn, user, payload); err != nil {
+ if err := s.handlePayload(payload); err != nil {
log.Print("handler error: ", err)
}
}
}
-func (s *Server) handlePayload(conn net.Conn, user User, payload any) error {
+func (s *Server) handlePayload(payload any) error {
switch v := payload.(type) {
case core.Message:
- return s.handleMessage(conn, user, v)
+ return s.handleMessage(v)
default:
return core.ErrUnexpectedPayloadType
}
}
-
-func (s *Server) handleMessage(conn net.Conn, user User, msg core.Message) error {
- log.Println("message:", user.Name, "->", msg.Target, msg.Content)
-
- channel, host, err := core.ReadAddr(msg.Target)
- if err != nil {
- return err
- }
-
- if host == s.name {
- return s.handleLocalMessage(channel, msg)
- }
-
- return s.handleOutboundMessage(channel, host, msg)
-}
-
-func (s *Server) handleLocalMessage(channel string, msg core.Message) error {
- user, ok := s.users[channel]
- if !ok {
- return errors.New("target not found")
- }
-
- return user.Send(msg)
-}
-
-func (s *Server) handleOutboundMessage(channel, host string, msg core.Message) error {
- conn, err := net.Dial("tcp", host+":9999")
- if err != nil {
- return err
- }
- defer conn.Close()
-
- hs := core.Handshake{0, 1}
- if err := core.Send(conn, hs); err != nil {
- return err
- }
-
- // TODO, servers should not use this type of auth
- auth := core.Auth{"server", "valid"}
- if err := core.Send(conn, auth); err != nil {
- return err
- }
-
- return core.Send(conn, msg)
-}
diff --git a/server/user.go b/server/user.go
index f69d126..5d2731c 100644
--- a/server/user.go
+++ b/server/user.go
@@ -1,6 +1,7 @@
package server
import (
+ "log"
"net"
"go.rctt.net/solec/core"
@@ -11,9 +12,9 @@ type User struct {
Conns map[net.Conn]struct{}
}
-func NewUser(conn net.Conn, auth core.Auth) User {
+func NewUser(conn net.Conn, name string) User {
u := User{
- Name: auth.Name,
+ Name: name,
Conns: make(map[net.Conn]struct{}),
}
@@ -31,12 +32,65 @@ func (u *User) Send(payload core.Wrapper) error {
return nil
}
-func (u *User) Auth(pass string) error {
- // TODO: Implement auth
+func (s *Server) handleUserConn(conn net.Conn) {
+ name, err := s.performUserAuth(conn)
+ if err != nil {
+ log.Println("user auth error:", err)
+ return
+ }
- if pass != "valid" {
- return core.ErrAuthInvalidPassword
+ s.usersMu.Lock()
+ user, ok := s.users[name]
+ if ok {
+ log.Println("next connection from user:", user.Name)
+ user.Conns[conn] = struct{}{}
+ } else {
+ log.Println("initial connection from user:", name)
+ user = NewUser(conn, name)
+ s.users[name] = user
}
+ s.usersMu.Unlock()
- return nil
+ defer func() {
+ s.usersMu.Lock()
+ log.Println("client disconnected: ", user.Name)
+ delete(s.users[user.Name].Conns, conn)
+ if len(s.users[user.Name].Conns) == 0 {
+ log.Println("all connections closed for user:", user.Name)
+ delete(s.users, user.Name)
+ }
+ s.usersMu.Unlock()
+ }()
+
+ if err := s.readInput(conn); err != nil {
+ log.Println(err)
+ }
+}
+
+func (s *Server) performUserAuth(conn net.Conn) (string, error) {
+ clientPayload, err := core.Decode(conn)
+ if err != nil {
+ return "", err
+ }
+
+ clientAuth, ok := clientPayload.(core.UserAuth)
+ if !ok {
+ return "", core.ErrUnexpectedPayloadType
+ }
+
+ // For testing ---
+ if clientAuth.Pass != "valid" {
+ if err := core.Send(conn, core.Error{core.ErrorAuthFailed}); err != nil {
+ log.Println("cannot send auth error:", err)
+ }
+
+ return "", core.ErrAuthInvalidPassword
+ }
+ // ---
+
+ if err := core.Send(conn, core.Success{}); err != nil {
+ return "", err
+ }
+
+ return clientAuth.Name, nil
}