]> git.r.bdr.sh - rbdr/mobius/blame - hotline/transaction.go
patch: v0.12.3
[rbdr/mobius] / hotline / transaction.go
CommitLineData
6988a057
JH
1package hotline
2
3import (
9174dbe8 4 "bytes"
6988a057
JH
5 "encoding/binary"
6 "errors"
7 "fmt"
8 "github.com/jhalter/mobius/concat"
9 "math/rand"
6988a057
JH
10)
11
12const (
d005ef04
JH
13 TranError = 0
14 TranGetMsgs = 101
15 TranNewMsg = 102
16 TranOldPostNews = 103
17 TranServerMsg = 104
18 TranChatSend = 105
19 TranChatMsg = 106
20 TranLogin = 107
21 TranSendInstantMsg = 108
22 TranShowAgreement = 109
23 TranDisconnectUser = 110
24 TranDisconnectMsg = 111 // TODO: implement server initiated friendly disconnect
25 TranInviteNewChat = 112
26 TranInviteToChat = 113
27 TranRejectChatInvite = 114
28 TranJoinChat = 115
29 TranLeaveChat = 116
30 TranNotifyChatChangeUser = 117
31 TranNotifyChatDeleteUser = 118
32 TranNotifyChatSubject = 119
33 TranSetChatSubject = 120
34 TranAgreed = 121
35 TranServerBanner = 122
36 TranGetFileNameList = 200
37 TranDownloadFile = 202
38 TranUploadFile = 203
39 TranNewFolder = 205
40 TranDeleteFile = 204
41 TranGetFileInfo = 206
42 TranSetFileInfo = 207
43 TranMoveFile = 208
44 TranMakeFileAlias = 209
45 TranDownloadFldr = 210
46 TranDownloadInfo = 211 // TODO: implement file transfer queue
47 TranDownloadBanner = 212
48 TranUploadFldr = 213
49 TranGetUserNameList = 300
50 TranNotifyChangeUser = 301
51 TranNotifyDeleteUser = 302
52 TranGetClientInfoText = 303
53 TranSetClientUserInfo = 304
54 TranListUsers = 348
55 TranUpdateUser = 349
56 TranNewUser = 350
57 TranDeleteUser = 351
58 TranGetUser = 352
59 TranSetUser = 353
60 TranUserAccess = 354
61 TranUserBroadcast = 355
62 TranGetNewsCatNameList = 370
63 TranGetNewsArtNameList = 371
64 TranDelNewsItem = 380
65 TranNewNewsFldr = 381
66 TranNewNewsCat = 382
67 TranGetNewsArtData = 400
68 TranPostNewsArt = 410
69 TranDelNewsArt = 411
70 TranKeepAlive = 500
6988a057
JH
71)
72
73type Transaction struct {
74 clientID *[]byte
75
76 Flags byte // Reserved (should be 0)
77 IsReply byte // Request (0) or reply (1)
78 Type []byte // Requested operation (user defined)
79 ID []byte // Unique transaction ID (must be != 0)
80 ErrorCode []byte // Used in the reply (user defined, 0 = no error)
81 TotalSize []byte // Total data size for the transaction (all parts)
82 DataSize []byte // Size of data in this transaction part. This allows splitting large transactions into smaller parts.
83 ParamCount []byte // Number of the parameters for this transaction
84 Fields []Field
85}
86
87func NewTransaction(t int, clientID *[]byte, fields ...Field) *Transaction {
88 typeSlice := make([]byte, 2)
89 binary.BigEndian.PutUint16(typeSlice, uint16(t))
90
91 idSlice := make([]byte, 4)
92 binary.BigEndian.PutUint32(idSlice, rand.Uint32())
93
94 return &Transaction{
95 clientID: clientID,
96 Flags: 0x00,
97 IsReply: 0x00,
98 Type: typeSlice,
99 ID: idSlice,
100 ErrorCode: []byte{0, 0, 0, 0},
101 Fields: fields,
102 }
103}
104
854a92fc
JH
105// Write implements io.Writer interface for Transaction
106func (t *Transaction) Write(p []byte) (n int, err error) {
107 totalSize := binary.BigEndian.Uint32(p[12:16])
6988a057
JH
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
854a92fc
JH
113 if tranLen > len(p) {
114 return n, errors.New("buflen too small for tranLen")
6988a057 115 }
854a92fc 116 fields, err := ReadFields(p[20:22], p[22:tranLen])
6988a057 117 if err != nil {
854a92fc 118 return n, err
6988a057
JH
119 }
120
854a92fc
JH
121 t.Flags = p[0]
122 t.IsReply = p[1]
123 t.Type = p[2:4]
124 t.ID = p[4:8]
125 t.ErrorCode = p[8:12]
126 t.TotalSize = p[12:16]
127 t.DataSize = p[16:20]
128 t.ParamCount = p[20:22]
129 t.Fields = fields
130
131 return len(p), err
6988a057
JH
132}
133
3178ae58 134const tranHeaderLen = 20 // fixed length of transaction fields before the variable length fields
6988a057 135
3178ae58
JH
136// transactionScanner implements bufio.SplitFunc for parsing incoming byte slices into complete tokens
137func transactionScanner(data []byte, _ bool) (advance int, token []byte, err error) {
138 // The bytes that contain the size of a transaction are from 12:16, so we need at least 16 bytes
139 if len(data) < 16 {
140 return 0, nil, nil
141 }
6988a057 142
3178ae58 143 totalSize := binary.BigEndian.Uint32(data[12:16])
6988a057 144
3178ae58
JH
145 // tranLen represents the length of bytes that are part of the transaction
146 tranLen := int(tranHeaderLen + totalSize)
147 if tranLen > len(data) {
148 return 0, nil, nil
6988a057
JH
149 }
150
3178ae58 151 return tranLen, data[0:tranLen], nil
6988a057
JH
152}
153
154const minFieldLen = 4
155
156func ReadFields(paramCount []byte, buf []byte) ([]Field, error) {
157 paramCountInt := int(binary.BigEndian.Uint16(paramCount))
158 if paramCountInt > 0 && len(buf) < minFieldLen {
159 return []Field{}, fmt.Errorf("invalid field length %v", len(buf))
160 }
161
162 // A Field consists of:
163 // ID: 2 bytes
164 // Size: 2 bytes
165 // Data: FieldSize number of bytes
166 var fields []Field
167 for i := 0; i < paramCountInt; i++ {
168 if len(buf) < minFieldLen {
169 return []Field{}, fmt.Errorf("invalid field length %v", len(buf))
170 }
171 fieldID := buf[0:2]
172 fieldSize := buf[2:4]
173 fieldSizeInt := int(binary.BigEndian.Uint16(buf[2:4]))
174 expectedLen := minFieldLen + fieldSizeInt
175 if len(buf) < expectedLen {
176 return []Field{}, fmt.Errorf("field length too short")
177 }
178
179 fields = append(fields, Field{
180 ID: fieldID,
181 FieldSize: fieldSize,
182 Data: buf[4 : 4+fieldSizeInt],
183 })
184
185 buf = buf[fieldSizeInt+4:]
186 }
187
188 if len(buf) != 0 {
189 return []Field{}, fmt.Errorf("extra field bytes")
190 }
191
192 return fields, nil
193}
194
0a92e50b 195func (t *Transaction) MarshalBinary() (data []byte, err error) {
6988a057
JH
196 payloadSize := t.Size()
197
198 fieldCount := make([]byte, 2)
199 binary.BigEndian.PutUint16(fieldCount, uint16(len(t.Fields)))
200
201 var fieldPayload []byte
202 for _, field := range t.Fields {
203 fieldPayload = append(fieldPayload, field.Payload()...)
204 }
205
206 return concat.Slices(
207 []byte{t.Flags, t.IsReply},
208 t.Type,
209 t.ID,
210 t.ErrorCode,
211 payloadSize,
212 payloadSize, // this is the dataSize field, but seeming the same as totalSize
213 fieldCount,
214 fieldPayload,
72dd37f1 215 ), err
6988a057
JH
216}
217
218// Size returns the total size of the transaction payload
0a92e50b 219func (t *Transaction) Size() []byte {
6988a057
JH
220 bs := make([]byte, 4)
221
222 fieldSize := 0
223 for _, field := range t.Fields {
224 fieldSize += len(field.Data) + 4
225 }
226
227 binary.BigEndian.PutUint32(bs, uint32(fieldSize+2))
228
229 return bs
230}
231
0a92e50b 232func (t *Transaction) GetField(id int) Field {
6988a057
JH
233 for _, field := range t.Fields {
234 if id == int(binary.BigEndian.Uint16(field.ID)) {
235 return field
236 }
237 }
238
239 return Field{}
240}
9174dbe8
JH
241
242func (t *Transaction) IsError() bool {
043c00da 243 return bytes.Equal(t.ErrorCode, []byte{0, 0, 0, 1})
9174dbe8 244}