From 54ddec67c477a6fd73b0f623258c0849ba695b88 Mon Sep 17 00:00:00 2001 From: bt Date: Sat, 14 Mar 2026 23:11:15 +0100 Subject: Basic server implementation --- README.md | 14 +- build_spec.sh | 22 -- cmd/client/main.go | 87 ++++-- cmd/daemon/main.go | 58 ++-- cmd/mock/main.go | 41 --- cmd/test/main.go | 2 +- core/data.go | 30 +- core/data_test.go | 3 +- core/network.go | 23 +- core/payload.go | 95 +++++- go.mod | 2 +- hooks/pre-commit | 8 + prompt/prompt.go | 29 ++ run.sh | 6 - server/server.go | 188 ++++++++++++ solec.ksy | 55 ++-- solec.lua | 118 +++++--- solec.svg | 839 ++++++++++++++++++++++++++++------------------------ tools/build_spec.sh | 22 ++ tools/run.sh | 8 + 20 files changed, 1035 insertions(+), 615 deletions(-) delete mode 100755 build_spec.sh delete mode 100644 cmd/mock/main.go create mode 100755 hooks/pre-commit create mode 100644 prompt/prompt.go delete mode 100755 run.sh create mode 100644 server/server.go create mode 100755 tools/build_spec.sh create mode 100755 tools/run.sh diff --git a/README.md b/README.md index ee8e555..c24b68b 100644 --- a/README.md +++ b/README.md @@ -8,18 +8,28 @@ You'll need Go toolchain. Get it [here](https://go.dev/dl/) ``` git clone https://git.sr.ht/~rctt/solec -cd solecd +cd solec go run cmd/daemon/main.go ``` +## Development + +Use following to setup Git hooks: +`git config --local core.hooksPath hooks` + +* `tools/run.sh` - build and start server and few clients +* `tools/build-spec.sh` - build Kaitai spec + ## Wireshark plugin To build wireshark plugin you'll need Kaitai Struct Compiler. KSC currently doesn't support Wireshark plugin generation but [there is a discussion]("https://github.com/kaitai-io/kaitai_struct/issues/50#issuecomment-1485044090"). -After building foked KSC use `./build_spec.sh` to build the plugin and then put +After building foked KSC use `./tools/build_spec.sh` to build the plugin and then put it into Wireshark plugins directory. +There is a problem with dissecting specific payloads. + ## TODO - Protocol diff --git a/build_spec.sh b/build_spec.sh deleted file mode 100755 index dfebbd2..0000000 --- a/build_spec.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/sh - -if [[ "$OSTYPE" == "darwin"* ]]; then - SED=gsed -else - SED=sed -fi - -kaitai-struct-compiler -t graphviz solec.ksy -dot -Tsvg solec.dot > solec.svg -rm solec.dot - -~/bin/kaitai-struct-compiler-ws/bin/kaitai-struct-compiler -t wireshark --read-pos solec.ksy - -${SED} -i -e '5s/.*/package.path = "kaitai_struct_lua_runtime\/?.lua" .. package.path/' solec.lua -${SED} -i -e 's/, self.type_payload//' solec.lua - -cat <> solec.lua -local tcp_port = DissectorTable.get("tcp.port") -tcp_port:add(9999, proto) -EOT - diff --git a/cmd/client/main.go b/cmd/client/main.go index 6e5d996..2612267 100644 --- a/cmd/client/main.go +++ b/cmd/client/main.go @@ -4,58 +4,87 @@ import ( "flag" "log" "net" + "strings" "time" - "git.sr.ht/~rctt/solecd/core" + "git.sr.ht/~rctt/solec/core" + "git.sr.ht/~rctt/solec/prompt" ) -var serverAddr string +var ( + serverAddr string + user string + conn net.Conn +) func main() { + prompt.Commands["*"] = broadcast + flag.StringVar(&serverAddr, "a", "localhost:9999", "Server address:port") + flag.StringVar(&user, "u", "user", "username") flag.Parse() - var d net.Dialer + var ( + d net.Dialer + err error + ) - conn, err := d.Dial("tcp", serverAddr) + log.Println("connecting to " + serverAddr + " as " + user) + + conn, err = d.Dial("tcp", serverAddr) if err != nil { log.Fatal("cannot dial: ", err) } defer conn.Close() - ping(conn) + hs := core.Handshake{0, 1} + if err := core.Send(conn, hs); err != nil { + panic(err) + } + + auth := core.Auth{user, "pass"} + if err := core.Send(conn, auth); err != nil { + panic(err) + } + + go prompt.Read() + + read(conn) } -func ping(conn net.Conn) { +func read(conn net.Conn) { for { - log.Print("ping") - data, err := core.Encode(core.Pong{Timestamp: time.Now()}) + payload, err := core.Read(conn) if err != nil { panic(err) } - if _, err := conn.Write(data); err != nil { - panic(err) - } - test := core.Test{ - Num1: 1, - Time1: time.Now(), - Str1: "test string", - Num2: 2, - Bin1: []byte{0x01, 0x02, 0x03}, - Num3: 3, - Str2: "こんにちは", - Num4: 4, - } + handlePayload(conn, payload) + } +} - data, err = core.Encode(test) - if err != nil { - panic(err) - } - if _, err = conn.Write(data); err != nil { - panic(err) - } +func handlePayload(conn net.Conn, payload any) { + switch v := payload.(type) { + case core.Message: + handleMessage(conn, v) + } +} + +func handleMessage(conn net.Conn, msg core.Message) { + log.Println("received message", msg.Source, "->", msg.Target, msg.Content) +} + +func broadcast(args []string) { + msg := core.Message{ + Source: user, + Target: "*", + Timestamp: time.Now(), + Content: strings.Join(args[0:], " "), + } + + log.Println(msg.Content) - time.Sleep(time.Second) + if err := core.Send(conn, msg); err != nil { + panic(err) } } diff --git a/cmd/daemon/main.go b/cmd/daemon/main.go index b55cc69..56b7c16 100644 --- a/cmd/daemon/main.go +++ b/cmd/daemon/main.go @@ -1,39 +1,53 @@ package main import ( - "flag" + "bufio" + "fmt" "log" - "net" + "maps" + "os" + "slices" + "strings" - "git.sr.ht/~rctt/solecd/core" + "git.sr.ht/~rctt/solec/server" ) -var listenAddr string +var ( + cmds = map[string]func(args []string){ + "ping": sendPing, + } -func main() { - flag.StringVar(&listenAddr, "a", "localhost:9999", "Listening address:port") - flag.Parse() + serv = server.NewServer("localhost:9999") +) - log.Print("starting solec daemon mock") - log.Fatal(listen()) -} +func main() { + fmt.Println("SOLEC MOCK SERVER") + fmt.Println("Commands:", slices.Sorted(maps.Keys(cmds))) -func listen() error { - ln, err := net.Listen("tcp", listenAddr) - if err != nil { - return err + if err := serv.Start(); err != nil { + panic(err) } + readCmds() +} - log.Print("server is listening on: ", listenAddr) +func readCmds() { + sc := bufio.NewScanner(os.Stdin) + for sc.Scan() { + args := strings.Split(sc.Text(), " ") - for { - conn, err := ln.Accept() - if err != nil { - log.Print("cannot accept connection: ", err) + cmd, ok := cmds[args[0]] + if !ok { + fmt.Println("unknown command") + continue } - log.Print("received connection from: ", conn.RemoteAddr()) - - go core.ReadConnection(conn) + cmd(args[1:]) + } + if err := sc.Err(); err != nil { + log.Println(err) } } + +func sendPing(args []string) { + +} diff --git a/cmd/mock/main.go b/cmd/mock/main.go deleted file mode 100644 index c4c726c..0000000 --- a/cmd/mock/main.go +++ /dev/null @@ -1,41 +0,0 @@ -package main - -import ( - "bufio" - "fmt" - "log" - "maps" - "os" - "slices" - "strings" -) - -var cmds = map[string]func(args []string){ - "ping": sendPing, -} - -func main() { - fmt.Println("SOLEC MOCK SERVER") - fmt.Println("Commands:", slices.Sorted(maps.Keys(cmds))) - - scanner := bufio.NewScanner(os.Stdin) - for scanner.Scan() { - args := strings.Split(scanner.Text(), " ") - - cmd, ok := cmds[args[0]] - if !ok { - fmt.Println("unknown command") - continue - } - - cmd(args[1:]) - } - - if err := scanner.Err(); err != nil { - log.Println(err) - } -} - -func sendPing(args []string) { - -} diff --git a/cmd/test/main.go b/cmd/test/main.go index 35eddad..05768ea 100644 --- a/cmd/test/main.go +++ b/cmd/test/main.go @@ -5,7 +5,7 @@ import ( "fmt" "time" - "git.sr.ht/~rctt/solecd/core" + "git.sr.ht/~rctt/solec/core" ) func main() { diff --git a/core/data.go b/core/data.go index 16775db..b25b853 100644 --- a/core/data.go +++ b/core/data.go @@ -16,13 +16,21 @@ type PayloadType uint8 const ( PayloadUnknown PayloadType = 0x00 - PayloadHandshake = 0x01 - PayloadPing = 0x02 - PayloadPong = 0x03 - PayloadMessage = 0x04 + PayloadSuccess = 0x01 + PayloadError = 0x02 + PayloadHandshake = 0x03 + PayloadAuth = 0x04 + PayloadMessage = 0x05 PayloadTest = 0xFF ) +type ErrorType uint8 + +const ( + ErrorUnknown ErrorType = 0x00 + ErrorAuthFailed = 0x01 +) + type Frame struct { Length uint16 Type PayloadType @@ -41,12 +49,13 @@ func Encode(w Wrapper) ([]byte, error) { for _, p := range payload { switch v := p.(type) { case string: - err := binary.Write(buf, binary.BigEndian, uint16(len(v))) + strBytes := []byte(v) + err := binary.Write(buf, binary.BigEndian, uint16(len(strBytes))) if err != nil { return []byte{}, fmt.Errorf("cannot encode string length: %v", err) } - _, err = buf.WriteString(v) + _, err = buf.Write(strBytes) if err != nil { return []byte{}, fmt.Errorf("cannot encode string: %v", err) } @@ -100,11 +109,16 @@ func Decode(buf io.Reader) (any, error) { } switch pType { + case PayloadSuccess: + return Success{}, nil + case PayloadError: + return DecodeError(buf) case PayloadHandshake: return DecodeHandshake(buf) - case PayloadPing: - case PayloadPong: + case PayloadAuth: + return DecodeAuth(buf) case PayloadMessage: + return DecodeMessage(buf) case PayloadTest: return DecodeTest(buf) default: diff --git a/core/data_test.go b/core/data_test.go index 1275ef4..af986cc 100644 --- a/core/data_test.go +++ b/core/data_test.go @@ -1,6 +1,7 @@ package core import ( + "bytes" "testing" "time" @@ -24,7 +25,7 @@ func TestEncoder(t *testing.T) { t.Error(err) } - out, err := Decode(data) + out, err := Decode(bytes.NewBuffer(data)) if err != nil { t.Error(err) } diff --git a/core/network.go b/core/network.go index c182e7f..932cdca 100644 --- a/core/network.go +++ b/core/network.go @@ -1,24 +1,19 @@ package core import ( - "fmt" "net" ) -func ReadConnection(conn net.Conn) { - for { - payload, err := Decode(conn) - if err != nil { - panic(err) - } - - if err := handle(payload); err != nil { - panic(err) - } +func Send(conn net.Conn, payload Wrapper) error { + data, err := Encode(payload) + if err != nil { + return err } + + _, err = conn.Write(data) + return err } -func handle(payload any) error { - fmt.Println(payload) - return nil +func Read(conn net.Conn) (any, error) { + return Decode(conn) } diff --git a/core/payload.go b/core/payload.go index 0711040..b8e8b9f 100644 --- a/core/payload.go +++ b/core/payload.go @@ -5,6 +5,35 @@ import ( "time" ) +type Success struct{} + +func (s Success) Wrap() (PayloadType, []any) { + return PayloadSuccess, []any{} +} + +type Error struct { + ErrorType ErrorType +} + +func (e Error) Wrap() (PayloadType, []any) { + return PayloadError, []any{e.ErrorType} +} + +func DecodeError(buf io.Reader) (Error, error) { + var ( + errorData uint8 + e Error + ) + + err := decodeNumeric(buf, &errorData) + if err != nil { + return e, err + } + + e.ErrorType = ErrorType(errorData) + return e, nil +} + type Handshake struct { Major, Minor uint8 } @@ -31,30 +60,72 @@ func DecodeHandshake(buf io.Reader) (Handshake, error) { return h, nil } -type Ping struct{} +type Auth struct { + Name string + Pass string +} -func (p Ping) Wrap() (PayloadType, []any) { - return PayloadPing, []any{} +func (a Auth) Wrap() (PayloadType, []any) { + return PayloadAuth, []any{ + a.Name, a.Pass, + } } -type Pong struct { +func DecodeAuth(buf io.Reader) (Auth, error) { + var a Auth + err := decodeString(buf, &a.Name) + if err != nil { + return a, err + } + + err = decodeString(buf, &a.Pass) + if err != nil { + return a, err + } + + return a, nil +} + +type Message struct { + Source string + Target string Timestamp time.Time + Content string } -func (p Pong) Wrap() (PayloadType, []any) { - return PayloadPong, []any{ - p.Timestamp, +func (m Message) Wrap() (PayloadType, []any) { + return PayloadMessage, []any{ + m.Source, + m.Target, + m.Timestamp, + m.Content, } } -func DecodePong(buf io.Reader) (Pong, error) { - var p Pong - err := decodeTime(buf, &p.Timestamp) +func DecodeMessage(buf io.Reader) (Message, error) { + var m Message + + err := decodeString(buf, &m.Source) + if err != nil { + return m, err + } + + err = decodeString(buf, &m.Target) + if err != nil { + return m, err + } + + err = decodeTime(buf, &m.Timestamp) + if err != nil { + return m, err + } + + err = decodeString(buf, &m.Content) if err != nil { - return p, err + return m, err } - return p, nil + return m, nil } type Test struct { diff --git a/go.mod b/go.mod index a4a5a0f..a5887e3 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module git.sr.ht/~rctt/solecd +module git.sr.ht/~rctt/solec go 1.25.0 diff --git a/hooks/pre-commit b/hooks/pre-commit new file mode 100755 index 0000000..422b855 --- /dev/null +++ b/hooks/pre-commit @@ -0,0 +1,8 @@ +#!/bin/sh + +if [ $(git rev-parse --abbrev-ref HEAD) != "main" ]; then + exit 0 +fi + +echo "Running tests before commit to main" +go test ./... \ No newline at end of file diff --git a/prompt/prompt.go b/prompt/prompt.go new file mode 100644 index 0000000..63c2a60 --- /dev/null +++ b/prompt/prompt.go @@ -0,0 +1,29 @@ +package prompt + +import ( + "bufio" + "fmt" + "log" + "os" + "strings" +) + +var Commands = make(map[string]func(args []string)) + +func Read() { + sc := bufio.NewScanner(os.Stdin) + for sc.Scan() { + args := strings.Split(sc.Text(), " ") + + cmd, ok := Commands[args[0]] + if !ok { + fmt.Println("unknown command") + continue + } + + cmd(args[1:]) + } + if err := sc.Err(); err != nil { + log.Println(err) + } +} diff --git a/run.sh b/run.sh deleted file mode 100755 index a33fad9..0000000 --- a/run.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh - -tmux \ - new-session "go run cmd/daemon/main.go; read" \; \ - split-window "sleep 0.5; go run cmd/client/main.go; read" \; \ - select-layout even-horizontal; \ No newline at end of file diff --git a/server/server.go b/server/server.go new file mode 100644 index 0000000..51e71b1 --- /dev/null +++ b/server/server.go @@ -0,0 +1,188 @@ +package server + +import ( + "errors" + "fmt" + "log" + "net" + "sync" + + "git.sr.ht/~rctt/solec/core" +) + +type Server struct { + listenAddr string + users map[string]User + mu sync.Mutex +} + +type User struct { + Name string + Conns map[net.Conn]struct{} +} + +func NewServer(listenAddr string) *Server { + return &Server{ + listenAddr: listenAddr, + users: make(map[string]User), + } +} + +func (s *Server) Start() error { + ln, err := net.Listen("tcp", s.listenAddr) + if err != nil { + return err + } + + for { + conn, err := ln.Accept() + if err != nil { + log.Print("cannot accept connection: ", err) + } + + log.Print("received connection from: ", conn.RemoteAddr()) + + go s.handleConn(conn) + } +} + +func (s *Server) handleConn(conn net.Conn) { + if err := s.performHandshake(conn); err != nil { + log.Println("handshake error", err) + return + } + + user, err := s.performAuth(conn) + if err != nil { + log.Println("auth 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("cannot read incomming data", err) + } +} + +func (s *Server) performHandshake(conn net.Conn) error { + serverHs := core.Handshake{0, 1} + + if err := core.Send(conn, serverHs); err != nil { + return err + } + + clientPayload, err := core.Decode(conn) + if err != nil { + return err + } + + clientHs, ok := clientPayload.(core.Handshake) + if !ok { + return errors.New("received payload of invalid type") + } + + 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{}, errors.New("received payload of invalid type") + } + + 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 + } + + log.Println("next connection from user:", user.Name) + user.Conns[conn] = struct{}{} + return user, nil +} + +func newUser(conn net.Conn, auth core.Auth) User { + u := User{ + Name: auth.Name, + Conns: make(map[net.Conn]struct{}), + } + + u.Conns[conn] = struct{}{} + return u +} + +func (s *Server) readInput(conn net.Conn, user User) error { + for { + payload, err := core.Decode(conn) + if err != nil { + return fmt.Errorf("decoder error: %w", err) + } + if err := s.handlePayload(conn, user, payload); err != nil { + return fmt.Errorf("payload handler error: %w", err) + } + } +} + +func (s *Server) handlePayload(conn net.Conn, user User, payload any) error { + switch v := payload.(type) { + case core.Message: + return s.handleMessage(conn, user, v) + + default: + return errors.New("invalid payload") + } +} + +func (s *Server) handleMessage(conn net.Conn, user User, msg core.Message) error { + log.Println("message:", user.Name, "->", msg.Target, msg.Content) + + if msg.Target == "*" { + return s.broadcastMessage(conn, user, msg) + } + + return nil +} + +func (s *Server) broadcastMessage(conn net.Conn, user User, msg core.Message) error { + for _, u := range s.users { + if u.Name == user.Name { + continue + } + + for c := range u.Conns { + if err := core.Send(c, msg); err != nil { + log.Println("cannot send", err) + } + } + } + + // Forward message for other connections from the same user + for c := range s.users[user.Name].Conns { + if err := core.Send(c, msg); err != nil { + log.Println("cannot send", err) + } + } + + return nil +} diff --git a/solec.ksy b/solec.ksy index e7c24e1..dcfcc11 100644 --- a/solec.ksy +++ b/solec.ksy @@ -3,15 +3,13 @@ meta: file-extension: hex endian: be -doc: | - SOLEC protocol uses big endian encoding. Frames uses type-length-value format. - +doc: SOLEC protocol doc-ref: https://git.sr.ht/~rctt/solec/blob/main/solec.svg seq: - id: type_payload type: u1 - enum: type + enum: payload_type - id: len_payload type: u2 - id: payload @@ -19,20 +17,25 @@ seq: type: switch-on: type_payload cases: - 'type::handshake': handshake - 'type::ping': ping - 'type::pong': pong - 'type::message': message - 'type::test': test + 'payload_type::success': success + 'payload_type::error': error + 'payload_type::handshake': handshake + 'payload_type::auth': auth + 'payload_type::message': message + 'payload_type::test': test enums: - type: - 0x01: handshake - 0x02: ping - 0x03: pong - 0x04: message + payload_type: + 0x01: success + 0x02: error + 0x03: handshake + 0x04: auth + 0x05: message 0xFF: test + error_type: + 0x01: auth_failed + types: string: doc: UTF-8 encoded string. @@ -52,6 +55,16 @@ types: - id: payload size: len_payload + success: + doc: Send from server if operation succeded. + + error: + doc: Senf from server if operation failed. + seq: + - id: error_code + type: u1 + enum: error_type + handshake: doc: | Handshake is sent by the client during connection initialization. @@ -63,16 +76,12 @@ types: - id: proto_ver_minor type: u1 - ping: - doc: Ping does not contain any payload. - - pong: + auth: seq: - - id: timestamp - doc: | - Timestamp is set just before sending the pong message. It can be used - to meassure packet's travel time between server and client. - type: u8 + - id: name + type: string + - id: pass + type: string message: doc: | diff --git a/solec.lua b/solec.lua index a06a429..85efaad 100644 --- a/solec.lua +++ b/solec.lua @@ -32,14 +32,14 @@ table.insert(proto.fields, Solec_String_len_payload) local str_decode = require("string_decode") local Solec_String_payload = ProtoField.new('payload', 'Solec.String.payload', ftypes.STRINGZ) table.insert(proto.fields, Solec_String_payload) +local Solec_Error_error_code = ProtoField.new('error_code', 'Solec.Error.error_code', ftypes.BYTES) +table.insert(proto.fields, Solec_Error_error_code) local Solec_Handshake_proto_ver_major = ProtoField.new('proto_ver_major', 'Solec.Handshake.proto_ver_major', ftypes.UINT8) table.insert(proto.fields, Solec_Handshake_proto_ver_major) local Solec_Handshake_proto_ver_minor = ProtoField.new('proto_ver_minor', 'Solec.Handshake.proto_ver_minor', ftypes.UINT8) table.insert(proto.fields, Solec_Handshake_proto_ver_minor) local Solec_Message_timestamp = ProtoField.new('timestamp', 'Solec.Message.timestamp', ftypes.UINT32) table.insert(proto.fields, Solec_Message_timestamp) -local Solec_Pong_timestamp = ProtoField.new('timestamp', 'Solec.Pong.timestamp', ftypes.UINT32) -table.insert(proto.fields, Solec_Pong_timestamp) function proto.dissector(tvb, pinfo, root) pinfo.cols.protocol = 'Solec' @@ -49,18 +49,23 @@ function proto.dissector(tvb, pinfo, root) end -- --- SOLEC protocol uses big endian encoding. Frames uses type-length-value format. +-- SOLEC protocol. -- See also: Source (https://git.sr.ht/~rctt/solec/blob/main/solec.svg) Solec = class.class(KaitaiStruct) -Solec.Type = enum.Enum { - handshake = 1, - ping = 2, - pong = 3, - message = 4, +Solec.PayloadType = enum.Enum { + success = 1, + error = 2, + handshake = 3, + auth = 4, + message = 5, test = 255, } +Solec.ErrorType = enum.Enum { + auth_failed = 1, +} + function Solec:_init(io, tree, parent, root) KaitaiStruct._init(self, io) self._parent = parent @@ -71,43 +76,49 @@ end function Solec:_read() local _offset = self._io:pos() - self.type_payload = Solec.Type(self._io:read_u1()) + self.type_payload = Solec.PayloadType(self._io:read_u1()) self._tree:add(Solec_type_payload, self._io._io.tvb(_offset, self._io:pos() - _offset)) local _offset = self._io:pos() self.len_payload = self._io:read_u2be() self._tree:add(Solec_len_payload, self._io._io.tvb(_offset, self._io:pos() - _offset), self.len_payload) local _offset = self._io:pos() local _on = self.type_payload - if _on == Solec.Type.message then + if _on == Solec.PayloadType.auth then self._raw_payload = self._io:read_bytes(self.len_payload) local _tvb = self._io._io.tvb(_offset, self._io:pos() - _offset) local _io = KaitaiStream(TVBStream(_tvb)) local _tree = self._tree:add(_tvb, 'payload') - self.payload = Solec.Message(_io, _tree, self, self._root) - elseif _on == Solec.Type.ping then + self.payload = Solec.Auth(_io, _tree, self, self._root) + elseif _on == Solec.PayloadType.handshake then self._raw_payload = self._io:read_bytes(self.len_payload) local _tvb = self._io._io.tvb(_offset, self._io:pos() - _offset) local _io = KaitaiStream(TVBStream(_tvb)) local _tree = self._tree:add(_tvb, 'payload') - self.payload = Solec.Ping(_io, _tree, self, self._root) - elseif _on == Solec.Type.pong then + self.payload = Solec.Handshake(_io, _tree, self, self._root) + elseif _on == Solec.PayloadType.test then self._raw_payload = self._io:read_bytes(self.len_payload) local _tvb = self._io._io.tvb(_offset, self._io:pos() - _offset) local _io = KaitaiStream(TVBStream(_tvb)) local _tree = self._tree:add(_tvb, 'payload') - self.payload = Solec.Pong(_io, _tree, self, self._root) - elseif _on == Solec.Type.test then + self.payload = Solec.Test(_io, _tree, self, self._root) + elseif _on == Solec.PayloadType.error then self._raw_payload = self._io:read_bytes(self.len_payload) local _tvb = self._io._io.tvb(_offset, self._io:pos() - _offset) local _io = KaitaiStream(TVBStream(_tvb)) local _tree = self._tree:add(_tvb, 'payload') - self.payload = Solec.Test(_io, _tree, self, self._root) - elseif _on == Solec.Type.handshake then + self.payload = Solec.Error(_io, _tree, self, self._root) + elseif _on == Solec.PayloadType.success then self._raw_payload = self._io:read_bytes(self.len_payload) local _tvb = self._io._io.tvb(_offset, self._io:pos() - _offset) local _io = KaitaiStream(TVBStream(_tvb)) local _tree = self._tree:add(_tvb, 'payload') - self.payload = Solec.Handshake(_io, _tree, self, self._root) + self.payload = Solec.Success(_io, _tree, self, self._root) + elseif _on == Solec.PayloadType.message then + self._raw_payload = self._io:read_bytes(self.len_payload) + local _tvb = self._io._io.tvb(_offset, self._io:pos() - _offset) + local _io = KaitaiStream(TVBStream(_tvb)) + local _tree = self._tree:add(_tvb, 'payload') + self.payload = Solec.Message(_io, _tree, self, self._root) else self.payload = self._io:read_bytes(self.len_payload) end @@ -157,6 +168,22 @@ function Solec.Test:_read() end +-- +-- Send from server if operation succeded. +Solec.Success = class.class(KaitaiStruct) + +function Solec.Success:_init(io, tree, parent, root) + KaitaiStruct._init(self, io) + self._parent = parent + self._root = root or self + self._tree = tree + self:_read() +end + +function Solec.Success:_read() +end + + -- -- Binary data of unspecifed type. Solec.Binary = class.class(KaitaiStruct) @@ -179,6 +206,28 @@ function Solec.Binary:_read() end +Solec.Auth = class.class(KaitaiStruct) + +function Solec.Auth:_init(io, tree, parent, root) + KaitaiStruct._init(self, io) + self._parent = parent + self._root = root or self + self._tree = tree + self:_read() +end + +function Solec.Auth:_read() + local _offset = self._io:pos() + local _tree = self._tree:add(self._io._io.tvb(_offset, 0), 'name') + self.name = Solec.String(self._io, _tree, self, self._root) + _tree:set_len(self._io:pos() - _offset) + local _offset = self._io:pos() + local _tree = self._tree:add(self._io._io.tvb(_offset, 0), 'pass') + self.pass = Solec.String(self._io, _tree, self, self._root) + _tree:set_len(self._io:pos() - _offset) +end + + -- -- UTF-8 encoded string. Solec.String = class.class(KaitaiStruct) @@ -202,10 +251,10 @@ end -- --- Ping does not contain any payload. -Solec.Ping = class.class(KaitaiStruct) +-- Senf from server if operation failed. +Solec.Error = class.class(KaitaiStruct) -function Solec.Ping:_init(io, tree, parent, root) +function Solec.Error:_init(io, tree, parent, root) KaitaiStruct._init(self, io) self._parent = parent self._root = root or self @@ -213,7 +262,10 @@ function Solec.Ping:_init(io, tree, parent, root) self:_read() end -function Solec.Ping:_read() +function Solec.Error:_read() + local _offset = self._io:pos() + self.error_code = Solec.ErrorType(self._io:read_u1()) + self._tree:add(Solec_Error_error_code, self._io._io.tvb(_offset, self._io:pos() - _offset), self.error_code) end @@ -274,25 +326,5 @@ end -- -- Set just before sending the message. -Solec.Pong = class.class(KaitaiStruct) - -function Solec.Pong:_init(io, tree, parent, root) - KaitaiStruct._init(self, io) - self._parent = parent - self._root = root or self - self._tree = tree - self:_read() -end - -function Solec.Pong:_read() - local _offset = self._io:pos() - self.timestamp = self._io:read_u8be() - self._tree:add(Solec_Pong_timestamp, self._io._io.tvb(_offset, self._io:pos() - _offset), self.timestamp) -end - --- --- Timestamp is set just before sending the pong message. It can be used --- to meassure packet's travel time between server and client. - local tcp_port = DissectorTable.get("tcp.port") tcp_port:add(9999, proto) diff --git a/solec.svg b/solec.svg index 63e66fe..40365b4 100644 --- a/solec.svg +++ b/solec.svg @@ -4,472 +4,531 @@ - - - + + + cluster__solec - -Solec + +Solec -cluster__binary - -Solec::Binary +cluster__auth + +Solec::Auth -cluster__handshake - -Solec::Handshake +cluster__binary + +Solec::Binary -cluster__message - -Solec::Message +cluster__error + +Solec::Error -cluster__ping - -Solec::Ping +cluster__handshake + +Solec::Handshake -cluster__pong - -Solec::Pong +cluster__message + +Solec::Message cluster__string - -Solec::String + +Solec::String +cluster__success + +Solec::Success + + cluster__test - -Solec::Test + +Solec::Test solec__seq - - -pos - - -size - - -type - - -id - -0 - -1 - -u1→Type - -type_payload - -1 - -2 - -u2be - -len_payload - -3 - -... - -switch (type_payload) - -payload + + +pos + + +size + + +type + + +id + +0 + +1 + +u1→PayloadType + +type_payload + +1 + +2 + +u2be + +len_payload + +3 + +... + +switch (type_payload) + +payload - + solec__seq:type_payload_type->solec__seq:payload_type - - + + solec__seq_payload_switch - - -case - - -type - -:type_handshake - -Handshake - -:type_message - -Message - -:type_ping - -Ping - -:type_pong - -Pong - -:type_test - -Test + + +case + + +type + +:payload_type_auth + +Auth + +:payload_type_error + +Error + +:payload_type_handshake + +Handshake + +:payload_type_message + +Message + +:payload_type_success + +Success + +:payload_type_test + +Test solec__seq:payload_type->solec__seq_payload_switch - - + + - - -handshake__seq - - -pos - - -size - - -type - - -id - -0 - -1 - -u1 - -proto_ver_major - -1 - -1 - -u1 - -proto_ver_minor - - + + +auth__seq + + +pos + + +size + + +type + + +id + +0 + +... + +String + +name + +... + +... + +String + +pass + + -solec__seq_payload_switch:case0->handshake__seq - - +solec__seq_payload_switch:case0->auth__seq + + - + -message__seq - - -pos - - -size - - -type - - -id - -0 - -... - -String - -source - -... - -... - -String - -target - -... - -8 - -u8be - -timestamp - -... - -... - -String - -content - - +error__seq + + +pos + + +size + + +type + + +id + +0 + +1 + +u1→ErrorType + +error_code + + -solec__seq_payload_switch:case1->message__seq - - +solec__seq_payload_switch:case1->error__seq + + - + -ping__seq - - -pos - - -size - - -type - - -id - - +handshake__seq + + +pos + + +size + + +type + + +id + +0 + +1 + +u1 + +proto_ver_major + +1 + +1 + +u1 + +proto_ver_minor + + -solec__seq_payload_switch:case2->ping__seq - - +solec__seq_payload_switch:case2->handshake__seq + + - + -pong__seq - - -pos - - -size - - -type - - -id - -0 - -8 - -u8be - -timestamp - - +message__seq + + +pos + + +size + + +type + + +id + +0 + +... + +String + +source + +... + +... + +String + +target + +... + +8 + +u8be + +timestamp + +... + +... + +String + +content + + -solec__seq_payload_switch:case3->pong__seq - - +solec__seq_payload_switch:case3->message__seq + + - + +success__seq + + +pos + + +size + + +type + + +id + + + +solec__seq_payload_switch:case4->success__seq + + + + + test__seq - - -pos - - -size - - -type - - -id - -0 - -1 - -u1 - -num1 - -1 - -8 - -u8be - -time1 - -9 - -... - -String - -str1 - -... - -2 - -u2be - -num2 - -... - -... - -Binary - -bin1 - -... - -4 - -u4be - -num3 - -... - -... - -String - -str2 - -... - -8 - -u8be - -num4 + + +pos + + +size + + +type + + +id + +0 + +1 + +u1 + +num1 + +1 + +8 + +u8be + +time1 + +9 + +... + +String + +str1 + +... + +2 + +u2be + +num2 + +... + +... + +Binary + +bin1 + +... + +4 + +u4be + +num3 + +... + +... + +String + +str2 + +... + +8 + +u8be + +num4 - -solec__seq_payload_switch:case4->test__seq - - + +solec__seq_payload_switch:case5->test__seq + + + + + +string__seq + + +pos + + +size + + +type + + +id + +0 + +2 + +u2be + +len_payload + +2 + +len_payload + +str(UTF-8) + +payload + + + +auth__seq:name_type->string__seq + + + + + +auth__seq:pass_type->string__seq + + - + binary__seq - - -pos - - -size - - -type - - -id - -0 - -2 - -u2be - -len_payload - -2 - -len_payload - - -payload + + +pos + + +size + + +type + + +id + +0 + +2 + +u2be + +len_payload + +2 + +len_payload + + +payload - + binary__seq:len_payload_type->binary__seq:payload_size - - - - - -string__seq - - -pos - - -size - - -type - - -id - -0 - -2 - -u2be - -len_payload - -2 - -len_payload - -str(UTF-8) - -payload + + - + message__seq:source_type->string__seq - - + + - + message__seq:target_type->string__seq - - + + - + message__seq:content_type->string__seq - - + + - + string__seq:len_payload_type->string__seq:payload_size - - + + - + test__seq:bin1_type->binary__seq - - + + - + test__seq:str1_type->string__seq - - + + - + test__seq:str2_type->string__seq - - + + diff --git a/tools/build_spec.sh b/tools/build_spec.sh new file mode 100755 index 0000000..dfebbd2 --- /dev/null +++ b/tools/build_spec.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +if [[ "$OSTYPE" == "darwin"* ]]; then + SED=gsed +else + SED=sed +fi + +kaitai-struct-compiler -t graphviz solec.ksy +dot -Tsvg solec.dot > solec.svg +rm solec.dot + +~/bin/kaitai-struct-compiler-ws/bin/kaitai-struct-compiler -t wireshark --read-pos solec.ksy + +${SED} -i -e '5s/.*/package.path = "kaitai_struct_lua_runtime\/?.lua" .. package.path/' solec.lua +${SED} -i -e 's/, self.type_payload//' solec.lua + +cat <> solec.lua +local tcp_port = DissectorTable.get("tcp.port") +tcp_port:add(9999, proto) +EOT + diff --git a/tools/run.sh b/tools/run.sh new file mode 100755 index 0000000..dbe0bf0 --- /dev/null +++ b/tools/run.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +tmux \ + new-session "go run cmd/daemon/main.go; read" \; \ + split-window "sleep 0.5; go run cmd/client/main.go -u user1; read" \; \ + split-window "sleep 0.5; go run cmd/client/main.go -u user2; read" \; \ + split-window "sleep 0.5; go run cmd/client/main.go -u user3; read" \; \ + select-layout tiled; \ No newline at end of file -- cgit v1.2.3