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