diff --git a/pkg/msg/msg.go b/pkg/msg/msg.go index 369e0b9..ecab61a 100644 --- a/pkg/msg/msg.go +++ b/pkg/msg/msg.go @@ -2,6 +2,7 @@ package msg import ( "encoding/json" + "errors" ) const ( @@ -11,8 +12,11 @@ const ( // Response type code Response = 2 - // Event type code - Event = 3 + // EventPublic is public event type code + EventPublic = 3 + + // EventPrivate is private event type code + EventPrivate = 4 ) // Msg represent websocket messages, it could be either a request, a response or an event @@ -34,17 +38,26 @@ func NewResponse(req *Msg, method string, args []interface{}) *Msg { } // Encode msg into json -func (m *Msg) Encode() []byte { - s, err := json.Marshal([]interface{}{ - m.Type, - m.ReqID, - m.Method, - m.Args, - }) - if err != nil { - return []byte{} +func (m *Msg) Encode() ([]byte, error) { + switch m.Type { + case Response, Request: + return json.Marshal([]interface{}{ + m.Type, + m.ReqID, + m.Method, + m.Args, + }) + + case EventPrivate, EventPublic: + return json.Marshal([]interface{}{ + m.Type, + m.Method, + m.Args, + }) + + default: + return nil, errors.New("invalid type") } - return s } // Convss2is converts a string slice to interface slice more details: https://golang.org/doc/faq#convert_slice_of_interface) diff --git a/pkg/msg/msg_test.go b/pkg/msg/msg_test.go index 1c3a62f..41749d0 100644 --- a/pkg/msg/msg_test.go +++ b/pkg/msg/msg_test.go @@ -14,5 +14,8 @@ func TestEncoding(t *testing.T) { Args: []interface{}{"hello", "there"}, } - assert.Equal(t, `[1,42,"test",["hello","there"]]`, string(msg.Encode())) + enc, err := msg.Encode() + + assert.NoError(t, err) + assert.Equal(t, `[1,42,"test",["hello","there"]]`, string(enc)) } diff --git a/pkg/msg/parser.go b/pkg/msg/parser.go index 5ef14f3..aad93bc 100644 --- a/pkg/msg/parser.go +++ b/pkg/msg/parser.go @@ -73,37 +73,61 @@ func ParseSliceOfStrings(t interface{}) ([]string, error) { func Parse(msg []byte) (*Msg, error) { req := Msg{} - var v []interface{} if err := json.Unmarshal(msg, &v); err != nil { return nil, fmt.Errorf("Could not parse message: %w", err) } - if len(v) != 4 { - return nil, errors.New("message must contains 4 elements") + if len(v) < 3 { + return nil, errors.New("message is too small") } t, err := ParseUint8(v[0]) if err != nil { return nil, fmt.Errorf("failed to parse type: %w", err) } - if t != Request && t != Response && t != Event { - return nil, errors.New("message type must be 1, 2 or 3") - } - reqID, err := ParseUint64(v[1]) - if err != nil { - return nil, fmt.Errorf("failed to parse request ID: %w", err) - } + var reqID uint64 + var method string + var args []interface{} - method, err := ParseString(v[2]) - if err != nil { - return nil, fmt.Errorf("failed to parse method: %w", err) - } + switch t { + case Request, Response: + if len(v) != 4 { + return nil, errors.New("message must contain 4 elements") + } - args, err := ParseSlice(v[3]) - if err != nil { - return nil, fmt.Errorf("failed to parse arguments: %w", err) + reqID, err = ParseUint64(v[1]) + if err != nil { + return nil, fmt.Errorf("failed to parse request ID: %w", err) + } + + method, err = ParseString(v[2]) + if err != nil { + return nil, fmt.Errorf("failed to parse method: %w", err) + } + + args, err = ParseSlice(v[3]) + if err != nil { + return nil, fmt.Errorf("failed to parse arguments: %w", err) + } + + case EventPrivate, EventPublic: + if len(v) != 3 { + return nil, errors.New("message must contain 3 elements") + } + + method, err = ParseString(v[1]) + if err != nil { + return nil, fmt.Errorf("failed to parse method: %w", err) + } + + args, err = ParseSlice(v[2]) + if err != nil { + return nil, fmt.Errorf("failed to parse arguments: %w", err) + } + default: + return nil, errors.New("message type must be 1, 2, 3 or 4") } req.Type = t diff --git a/pkg/msg/parser_test.go b/pkg/msg/parser_test.go index fb0bae6..98396ad 100644 --- a/pkg/msg/parser_test.go +++ b/pkg/msg/parser_test.go @@ -27,12 +27,12 @@ func TestParserSuccess(t *testing.T) { Args: []interface{}{}, }, msg) - msg, err = Parse([]byte(`[3,42,"temperature",[28.7]]`)) + msg, err = Parse([]byte(`[3,"temperature",[28.7]]`)) assert.NoError(t, err) assert.Equal(t, &Msg{ - Type: Event, - ReqID: 42, + Type: EventPublic, + ReqID: 0, Method: "temperature", Args: []interface{}{28.7}, }, msg) @@ -40,7 +40,7 @@ func TestParserSuccess(t *testing.T) { func TestParserErrorsMessageLength(t *testing.T) { msg, err := Parse([]byte(`[1,42,"ping"]`)) - assert.EqualError(t, err, "message must contains 4 elements") + assert.EqualError(t, err, "message must contain 4 elements") assert.Nil(t, msg) } @@ -51,8 +51,12 @@ func TestParserErrorsBadJSON(t *testing.T) { } func TestParserErrorsType(t *testing.T) { - msg, err := Parse([]byte(`[4,42,"ping",[]]`)) - assert.EqualError(t, err, "message type must be 1, 2 or 3") + msg, err := Parse([]byte(`[5,42,"ping",[]]`)) + assert.EqualError(t, err, "message type must be 1, 2, 3 or 4") + assert.Nil(t, msg) + + msg, err = Parse([]byte(`[5,"ping",[]]`)) + assert.EqualError(t, err, "message type must be 1, 2, 3 or 4") assert.Nil(t, msg) msg, err = Parse([]byte(`[1.1,42,"pong",[]]`)) diff --git a/pkg/routing/client.go b/pkg/routing/client.go index d79f655..b04c724 100644 --- a/pkg/routing/client.go +++ b/pkg/routing/client.go @@ -194,7 +194,12 @@ func (c *Client) read() { req, err := msg.Parse(message) if err != nil { log.Error().Msgf("fail to parse message: %s", err.Error()) - c.send <- msg.NewResponse(&msg.Msg{ReqID: 0}, "error", []interface{}{err.Error()}).Encode() + + resp, err := msg.NewResponse(&msg.Msg{ReqID: 0}, "error", []interface{}{err.Error()}).Encode() + if err == nil { + c.send <- resp + } + continue } diff --git a/pkg/routing/hub.go b/pkg/routing/hub.go index a086a1b..53c8f93 100644 --- a/pkg/routing/hub.go +++ b/pkg/routing/hub.go @@ -95,7 +95,10 @@ func (h *Hub) ListenWebsocketEvents() { select { case req := <-h.Requests: resp := h.handleRequest(&req) - req.client.Send(string(resp.Encode())) + encoded, err := resp.Encode() + if err == nil { + req.client.Send(string(encoded)) + } case client := <-h.Unregister: log.Info().Msgf("Unregistering client %s", client.GetUID())