-
Notifications
You must be signed in to change notification settings - Fork 0
/
protocol_message.go
224 lines (210 loc) · 5.34 KB
/
protocol_message.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
package arbor
import (
"encoding/json"
"fmt"
)
const (
// WelcomeType should be used as the `Type` field of a WELCOME ProtocolMessage
WelcomeType = 0
// QueryType should be used as the `Type` field of a QUERY ProtocolMessage
QueryType = 1
// NewMessageType should be used as the `Type` field of a NEW_MESSAGE ProtocolMessage
NewMessageType = 2
// NewType is the `Type` of a NEW Message
NewType = 2
// MetaType is the `Type` of a META Message
MetaType = 3
)
// Message is a protocol-layer message in Arbor
type Message ProtocolMessage
// ProtocolMessage represents a message in the Arbor chat protocol. This may or
// may not contain a chat message sent between users.
type ProtocolMessage struct {
// Root is only used in WELCOME messages and identifies the root of this server's message tree
Root string
// Recent is only used in WELCOME messages and provides a list of recently-sent message ids
Recent []string
// The type of the message, should be one of the constants defined in this
// package.
Type uint8
// Major is only used in WELCOME messages and identifies the major version number of the protocol version in use
Major uint8
// Minor is only used in WELCOME messages and identifies the minor version number of the protocol version in use
Minor uint8
// Message is the actual chat message content, if any. This is currently only
// used in NEW_MESSAGE messages
*ChatMessage
// Meta is the `Meta` field in META type arbor messages.
Meta map[string]string
}
// Equals returns true if other is equivalent to the message (has the same data or is the same message)
func (m *ProtocolMessage) Equals(other *ProtocolMessage) bool {
if (m == nil) != (other == nil) {
// one is nil and the other is not
return false
}
if m == other {
// either both nil or pointers to the same address
return true
}
if m.Type != other.Type || m.Root != other.Root || m.Major != other.Major || m.Minor != other.Minor {
return false
}
if !m.ChatMessage.Equals(other.ChatMessage) {
return false
}
if !sameSlice(m.Recent, other.Recent) {
return false
}
if !sameMap(m.Meta, other.Meta) {
return false
}
return true
}
// Source: https://stackoverflow.com/a/15312097
func sameSlice(a, b []string) bool {
if (a == nil) != (b == nil) {
return false
}
if len(a) != len(b) {
return false
}
for i, v := range a {
if v != b[i] {
return false
}
}
return true
}
func sameMap(a, b map[string]string) bool {
if (a == nil) != (b == nil) {
return false
}
if len(a) != len(b) {
return false
}
for key, val := range a {
if val != b[key] {
return false
}
}
return true
}
// MarshalJSON transforms a ProtocolMessage into JSON
func (m *ProtocolMessage) MarshalJSON() ([]byte, error) {
switch m.Type {
case WelcomeType:
return json.Marshal(struct {
Root string
Recent []string
Type uint8
Major uint8
Minor uint8
}{Type: m.Type, Root: m.Root, Recent: m.Recent, Major: m.Major, Minor: m.Minor})
case QueryType:
return json.Marshal(struct {
UUID string
Type uint8
}{UUID: m.UUID, Type: m.Type})
case NewMessageType:
return json.Marshal(struct {
*ChatMessage
Type uint8
}{ChatMessage: m.ChatMessage, Type: m.Type})
case MetaType:
return json.Marshal(struct {
Meta map[string]string
Type uint8
}{Type: m.Type, Meta: m.Meta})
default:
return nil, fmt.Errorf("Unknown message type, could not marshal")
}
}
// String returns a JSON representation of the message as a string.
func (m *ProtocolMessage) String() string {
data, _ := json.Marshal(m) // nolint: gosec
dataString := string(data)
return dataString
}
// IsValid returns whether the message has the minimum correct fields for its message
// type.
func (m *ProtocolMessage) IsValid() bool {
switch m.Type {
case WelcomeType:
return m.IsValidWelcome()
case QueryType:
return m.IsValidQuery()
case NewMessageType:
return m.IsValidNew()
case MetaType:
return m.IsValidMeta()
default:
return false
}
}
// IsValidWelcome checks that the message is a valid Welcome message.
func (m *ProtocolMessage) IsValidWelcome() bool {
switch {
case m.Type != WelcomeType:
fallthrough
case m.Major == 0 && m.Minor == 0:
fallthrough
case m.Recent == nil:
fallthrough
case m.Meta != nil && len(m.Meta) != 0:
fallthrough
case m.Root == "":
return false
}
return true
}
// IsValidNew checks that the message is a valid New message.
func (m *ProtocolMessage) IsValidNew() bool {
switch {
case m.Type != NewMessageType:
fallthrough
case m.ChatMessage == nil:
fallthrough
case m.Username == "":
fallthrough
case m.Content == "":
fallthrough
case m.Meta != nil && len(m.Meta) != 0:
fallthrough
case m.Timestamp == 0:
return false
}
return true
}
// IsValidQuery checks that the message is a valid Query message.
func (m *ProtocolMessage) IsValidQuery() bool {
switch {
case m.Type != QueryType:
fallthrough
case m.ChatMessage == nil:
fallthrough
case m.Meta != nil && len(m.Meta) != 0:
fallthrough
case m.UUID == "":
return false
}
return true
}
// IsValidMeta returns whether the message is valid as a META-type protocol message.
func (m *ProtocolMessage) IsValidMeta() bool {
switch {
case m.Type != MetaType:
fallthrough
case m.Meta == nil:
fallthrough
case m.ChatMessage != nil:
fallthrough
case m.Major != 0 || m.Minor != 0:
fallthrough
case m.Root != "":
fallthrough
case m.Recent != nil:
return false
}
return true
}