]> git.r.bdr.sh - rbdr/mobius/blame - hotline/transaction.go
Fix panic when commenting file without existing info fork
[rbdr/mobius] / hotline / transaction.go
CommitLineData
6988a057
JH
1package hotline
2
3import (
4 "encoding/binary"
5 "errors"
6 "fmt"
7 "github.com/jhalter/mobius/concat"
8 "math/rand"
6988a057
JH
9)
10
11const (
12 tranError = 0
13 tranGetMsgs = 101
14 tranNewMsg = 102
15 tranOldPostNews = 103
16 tranServerMsg = 104
17 tranChatSend = 105
18 tranChatMsg = 106
19 tranLogin = 107
20 tranSendInstantMsg = 108
21 tranShowAgreement = 109
22 tranDisconnectUser = 110
23 // tranDisconnectMsg = 111 TODO: implement friendly disconnect
24 tranInviteNewChat = 112
25 tranInviteToChat = 113
26 tranRejectChatInvite = 114
27 tranJoinChat = 115
28 tranLeaveChat = 116
29 tranNotifyChatChangeUser = 117
30 tranNotifyChatDeleteUser = 118
31 tranNotifyChatSubject = 119
32 tranSetChatSubject = 120
33 tranAgreed = 121
9067f234 34 tranServerBanner = 122
6988a057
JH
35 tranGetFileNameList = 200
36 tranDownloadFile = 202
37 tranUploadFile = 203
38 tranNewFolder = 205
39 tranDeleteFile = 204
40 tranGetFileInfo = 206
41 tranSetFileInfo = 207
42 tranMoveFile = 208
7cd900d6 43 tranMakeFileAlias = 209
decc2fbf 44 tranDownloadFldr = 210
6988a057 45 // tranDownloadInfo = 211 TODO: implement file transfer queue
9067f234 46 tranDownloadBanner = 212
d2810ae9
JH
47 tranUploadFldr = 213
48 tranGetUserNameList = 300
49 tranNotifyChangeUser = 301
50 tranNotifyDeleteUser = 302
51 tranGetClientInfoText = 303
52 tranSetClientUserInfo = 304
53 tranListUsers = 348
54 tranUpdateUser = 349
6988a057
JH
55 tranNewUser = 350
56 tranDeleteUser = 351
57 tranGetUser = 352
58 tranSetUser = 353
59 tranUserAccess = 354
60 tranUserBroadcast = 355
61 tranGetNewsCatNameList = 370
62 tranGetNewsArtNameList = 371
63 tranDelNewsItem = 380
64 tranNewNewsFldr = 381
65 tranNewNewsCat = 382
66 tranGetNewsArtData = 400
67 tranPostNewsArt = 410
68 tranDelNewsArt = 411
69 tranKeepAlive = 500
70)
71
72type Transaction struct {
73 clientID *[]byte
74
75 Flags byte // Reserved (should be 0)
76 IsReply byte // Request (0) or reply (1)
77 Type []byte // Requested operation (user defined)
78 ID []byte // Unique transaction ID (must be != 0)
79 ErrorCode []byte // Used in the reply (user defined, 0 = no error)
80 TotalSize []byte // Total data size for the transaction (all parts)
81 DataSize []byte // Size of data in this transaction part. This allows splitting large transactions into smaller parts.
82 ParamCount []byte // Number of the parameters for this transaction
83 Fields []Field
84}
85
86func NewTransaction(t int, clientID *[]byte, fields ...Field) *Transaction {
87 typeSlice := make([]byte, 2)
88 binary.BigEndian.PutUint16(typeSlice, uint16(t))
89
90 idSlice := make([]byte, 4)
91 binary.BigEndian.PutUint32(idSlice, rand.Uint32())
92
93 return &Transaction{
94 clientID: clientID,
95 Flags: 0x00,
96 IsReply: 0x00,
97 Type: typeSlice,
98 ID: idSlice,
99 ErrorCode: []byte{0, 0, 0, 0},
100 Fields: fields,
101 }
102}
103
104// ReadTransaction parses a byte slice into a struct. The input slice may be shorter or longer
105// that the transaction size depending on what was read from the network connection.
106func ReadTransaction(buf []byte) (*Transaction, int, error) {
107 totalSize := binary.BigEndian.Uint32(buf[12:16])
108
109 // the buf may include extra bytes that are not part of the transaction
110 // tranLen represents the length of bytes that are part of the transaction
111 tranLen := int(20 + totalSize)
112
113 if tranLen > len(buf) {
114 return nil, 0, errors.New("buflen too small for tranLen")
115 }
116 fields, err := ReadFields(buf[20:22], buf[22:tranLen])
117 if err != nil {
118 return nil, 0, err
119 }
120
121 return &Transaction{
122 Flags: buf[0],
123 IsReply: buf[1],
124 Type: buf[2:4],
125 ID: buf[4:8],
126 ErrorCode: buf[8:12],
127 TotalSize: buf[12:16],
128 DataSize: buf[16:20],
129 ParamCount: buf[20:22],
130 Fields: fields,
131 }, tranLen, nil
132}
133
6988a057
JH
134func readTransactions(buf []byte) ([]Transaction, int, error) {
135 var transactions []Transaction
136
137 bufLen := len(buf)
138
139 var bytesRead = 0
140 for bytesRead < bufLen {
141 t, tReadLen, err := ReadTransaction(buf[bytesRead:])
142 if err != nil {
143 return transactions, bytesRead, err
144 }
145 bytesRead += tReadLen
146
147 transactions = append(transactions, *t)
148 }
149
150 return transactions, bytesRead, nil
151}
152
153const minFieldLen = 4
154
155func ReadFields(paramCount []byte, buf []byte) ([]Field, error) {
156 paramCountInt := int(binary.BigEndian.Uint16(paramCount))
157 if paramCountInt > 0 && len(buf) < minFieldLen {
158 return []Field{}, fmt.Errorf("invalid field length %v", len(buf))
159 }
160
161 // A Field consists of:
162 // ID: 2 bytes
163 // Size: 2 bytes
164 // Data: FieldSize number of bytes
165 var fields []Field
166 for i := 0; i < paramCountInt; i++ {
167 if len(buf) < minFieldLen {
168 return []Field{}, fmt.Errorf("invalid field length %v", len(buf))
169 }
170 fieldID := buf[0:2]
171 fieldSize := buf[2:4]
172 fieldSizeInt := int(binary.BigEndian.Uint16(buf[2:4]))
173 expectedLen := minFieldLen + fieldSizeInt
174 if len(buf) < expectedLen {
175 return []Field{}, fmt.Errorf("field length too short")
176 }
177
178 fields = append(fields, Field{
179 ID: fieldID,
180 FieldSize: fieldSize,
181 Data: buf[4 : 4+fieldSizeInt],
182 })
183
184 buf = buf[fieldSizeInt+4:]
185 }
186
187 if len(buf) != 0 {
188 return []Field{}, fmt.Errorf("extra field bytes")
189 }
190
191 return fields, nil
192}
193
0a92e50b 194func (t *Transaction) MarshalBinary() (data []byte, err error) {
6988a057
JH
195 payloadSize := t.Size()
196
197 fieldCount := make([]byte, 2)
198 binary.BigEndian.PutUint16(fieldCount, uint16(len(t.Fields)))
199
200 var fieldPayload []byte
201 for _, field := range t.Fields {
202 fieldPayload = append(fieldPayload, field.Payload()...)
203 }
204
205 return concat.Slices(
206 []byte{t.Flags, t.IsReply},
207 t.Type,
208 t.ID,
209 t.ErrorCode,
210 payloadSize,
211 payloadSize, // this is the dataSize field, but seeming the same as totalSize
212 fieldCount,
213 fieldPayload,
72dd37f1 214 ), err
6988a057
JH
215}
216
217// Size returns the total size of the transaction payload
0a92e50b 218func (t *Transaction) Size() []byte {
6988a057
JH
219 bs := make([]byte, 4)
220
221 fieldSize := 0
222 for _, field := range t.Fields {
223 fieldSize += len(field.Data) + 4
224 }
225
226 binary.BigEndian.PutUint32(bs, uint32(fieldSize+2))
227
228 return bs
229}
230
0a92e50b 231func (t *Transaction) GetField(id int) Field {
6988a057
JH
232 for _, field := range t.Fields {
233 if id == int(binary.BigEndian.Uint16(field.ID)) {
234 return field
235 }
236 }
237
238 return Field{}
239}