]> git.r.bdr.sh - rbdr/mobius/blame - hotline/transaction.go
Extensive refactor and clean up
[rbdr/mobius] / hotline / transaction.go
CommitLineData
6988a057
JH
1package hotline
2
3import (
95159e55 4 "bufio"
9174dbe8 5 "bytes"
6988a057
JH
6 "encoding/binary"
7 "errors"
8 "fmt"
95159e55 9 "io"
6988a057 10 "math/rand"
9c44621e 11 "slices"
6988a057
JH
12)
13
d9bc63a1
JH
14type TranType [2]byte
15
a2ef262a 16var (
d9bc63a1
JH
17 TranError = TranType{0x00, 0x00} // 0
18 TranGetMsgs = TranType{0x00, 0x65} // 101
19 TranNewMsg = TranType{0x00, 0x66} // 102
20 TranOldPostNews = TranType{0x00, 0x67} // 103
21 TranServerMsg = TranType{0x00, 0x68} // 104
22 TranChatSend = TranType{0x00, 0x69} // 105
23 TranChatMsg = TranType{0x00, 0x6A} // 106
24 TranLogin = TranType{0x00, 0x6B} // 107
25 TranSendInstantMsg = TranType{0x00, 0x6C} // 108
26 TranShowAgreement = TranType{0x00, 0x6D} // 109
27 TranDisconnectUser = TranType{0x00, 0x6E} // 110
28 TranDisconnectMsg = TranType{0x00, 0x6F} // 111
29 TranInviteNewChat = TranType{0x00, 0x70} // 112
30 TranInviteToChat = TranType{0x00, 0x71} // 113
31 TranRejectChatInvite = TranType{0x00, 0x72} // 114
32 TranJoinChat = TranType{0x00, 0x73} // 115
33 TranLeaveChat = TranType{0x00, 0x74} // 116
34 TranNotifyChatChangeUser = TranType{0x00, 0x75} // 117
35 TranNotifyChatDeleteUser = TranType{0x00, 0x76} // 118
36 TranNotifyChatSubject = TranType{0x00, 0x77} // 119
37 TranSetChatSubject = TranType{0x00, 0x78} // 120
38 TranAgreed = TranType{0x00, 0x79} // 121
39 TranServerBanner = TranType{0x00, 0x7A} // 122
40 TranGetFileNameList = TranType{0x00, 0xC8} // 200
41 TranDownloadFile = TranType{0x00, 0xCA} // 202
42 TranUploadFile = TranType{0x00, 0xCB} // 203
43 TranNewFolder = TranType{0x00, 0xCD} // 205
44 TranDeleteFile = TranType{0x00, 0xCC} // 204
45 TranGetFileInfo = TranType{0x00, 0xCE} // 206
46 TranSetFileInfo = TranType{0x00, 0xCF} // 207
47 TranMoveFile = TranType{0x00, 0xD0} // 208
48 TranMakeFileAlias = TranType{0x00, 0xD1} // 209
49 TranDownloadFldr = TranType{0x00, 0xD2} // 210
50 TranDownloadInfo = TranType{0x00, 0xD3} // 211
51 TranDownloadBanner = TranType{0x00, 0xD4} // 212
52 TranUploadFldr = TranType{0x00, 0xD5} // 213
53 TranGetUserNameList = TranType{0x01, 0x2C} // 300
54 TranNotifyChangeUser = TranType{0x01, 0x2D} // 301
55 TranNotifyDeleteUser = TranType{0x01, 0x2E} // 302
56 TranGetClientInfoText = TranType{0x01, 0x2F} // 303
57 TranSetClientUserInfo = TranType{0x01, 0x30} // 304
58 TranListUsers = TranType{0x01, 0x5C} // 348
59 TranUpdateUser = TranType{0x01, 0x5D} // 349
60 TranNewUser = TranType{0x01, 0x5E} // 350
61 TranDeleteUser = TranType{0x01, 0x5F} // 351
62 TranGetUser = TranType{0x01, 0x60} // 352
63 TranSetUser = TranType{0x01, 0x61} // 353
64 TranUserAccess = TranType{0x01, 0x62} // 354
65 TranUserBroadcast = TranType{0x01, 0x63} // 355
66 TranGetNewsCatNameList = TranType{0x01, 0x72} // 370
67 TranGetNewsArtNameList = TranType{0x01, 0x73} // 371
68 TranDelNewsItem = TranType{0x01, 0x7C} // 380
69 TranNewNewsFldr = TranType{0x01, 0x7D} // 381
70 TranNewNewsCat = TranType{0x01, 0x7E} // 382
71 TranGetNewsArtData = TranType{0x01, 0x90} // 400
72 TranPostNewsArt = TranType{0x01, 0x9A} // 410
73 TranDelNewsArt = TranType{0x01, 0x9B} // 411
74 TranKeepAlive = TranType{0x01, 0xF4} // 500
6988a057
JH
75)
76
77type Transaction struct {
a2ef262a
JH
78 Flags byte // Reserved (should be 0)
79 IsReply byte // Request (0) or reply (1)
80 Type TranType // Requested operation (user defined)
81 ID [4]byte // Unique transaction ID (must be != 0)
82 ErrorCode [4]byte // Used in the reply (user defined, 0 = no error)
83 TotalSize [4]byte // Total data size for the fields in this transaction.
84 DataSize [4]byte // Size of data in this transaction part. This allows splitting large transactions into smaller parts.
85 ParamCount [2]byte // Number of the parameters for this transaction
6988a057 86 Fields []Field
95159e55 87
a2ef262a 88 clientID [2]byte // Internal identifier for target client
95159e55 89 readOffset int // Internal offset to track read progress
6988a057
JH
90}
91
a2ef262a 92var tranTypeNames = map[TranType]string{
d9bc63a1
JH
93 TranChatMsg: "Receive chat",
94 TranNotifyChangeUser: "User change",
95 TranError: "Error",
96 TranShowAgreement: "Show Agreement",
97 TranUserAccess: "User access",
98 TranNotifyDeleteUser: "User left",
a2ef262a 99 TranAgreed: "TranAgreed",
d9bc63a1
JH
100 TranChatSend: "Send chat",
101 TranDelNewsArt: "Delete news article",
102 TranDelNewsItem: "Delete news item",
103 TranDeleteFile: "Delete file",
104 TranDeleteUser: "Delete user",
105 TranDisconnectUser: "Disconnect user",
106 TranDownloadFile: "Download file",
107 TranDownloadFldr: "Download folder",
108 TranGetClientInfoText: "Get client info",
109 TranGetFileInfo: "Get file info",
110 TranGetFileNameList: "Get file list",
111 TranGetMsgs: "Get messages",
112 TranGetNewsArtData: "Get news article",
113 TranGetNewsArtNameList: "Get news article list",
114 TranGetNewsCatNameList: "Get news categories",
115 TranGetUser: "Get user",
116 TranGetUserNameList: "Get user list",
117 TranInviteNewChat: "Invite to new chat",
118 TranInviteToChat: "Invite to chat",
119 TranJoinChat: "Join chat",
120 TranKeepAlive: "Keepalive",
121 TranLeaveChat: "Leave chat",
122 TranListUsers: "List user accounts",
123 TranMoveFile: "Move file",
124 TranNewFolder: "Create folder",
125 TranNewNewsCat: "Create news category",
126 TranNewNewsFldr: "Create news bundle",
127 TranNewUser: "Create user account",
128 TranUpdateUser: "Update user account",
129 TranOldPostNews: "Post to message board",
130 TranPostNewsArt: "Create news article",
131 TranRejectChatInvite: "Decline chat invite",
132 TranSendInstantMsg: "Send message",
133 TranSetChatSubject: "Set chat subject",
134 TranMakeFileAlias: "Make file alias",
135 TranSetClientUserInfo: "Set client user info",
136 TranSetFileInfo: "Set file info",
137 TranSetUser: "Set user",
138 TranUploadFile: "Upload file",
139 TranUploadFldr: "Upload folder",
140 TranUserBroadcast: "Send broadcast",
141 TranDownloadBanner: "Download banner",
a2ef262a 142}
6988a057 143
d9bc63a1
JH
144//func (t TranType) LogValue() slog.Value {
145// return slog.StringValue(tranTypeNames[t])
146//}
6988a057 147
d9bc63a1
JH
148// NewTransaction creates a new Transaction with the specified type, client Type, and optional fields.
149func NewTransaction(t TranType, clientID [2]byte, fields ...Field) Transaction {
a2ef262a
JH
150 transaction := Transaction{
151 Type: t,
153e2eac 152 clientID: clientID,
153e2eac 153 Fields: fields,
6988a057 154 }
a2ef262a
JH
155
156 binary.BigEndian.PutUint32(transaction.ID[:], rand.Uint32())
157
158 return transaction
6988a057
JH
159}
160
a2ef262a
JH
161// Write implements io.Writer interface for Transaction.
162// Transactions read from the network are read as complete tokens with a bufio.Scanner, so
163// the arg p is guaranteed to have the full byte payload of a complete transaction.
854a92fc 164func (t *Transaction) Write(p []byte) (n int, err error) {
a2ef262a
JH
165 // Make sure we have the minimum number of bytes for a transaction.
166 if len(p) < 22 {
167 return 0, errors.New("buffer too small")
168 }
6988a057 169
a2ef262a
JH
170 // Read the total size field.
171 totalSize := binary.BigEndian.Uint32(p[12:16])
6988a057
JH
172 tranLen := int(20 + totalSize)
173
a2ef262a
JH
174 paramCount := binary.BigEndian.Uint16(p[20:22])
175
176 t.Flags = p[0]
177 t.IsReply = p[1]
178 copy(t.Type[:], p[2:4])
179 copy(t.ID[:], p[4:8])
180 copy(t.ErrorCode[:], p[8:12])
181 copy(t.TotalSize[:], p[12:16])
182 copy(t.DataSize[:], p[16:20])
183 copy(t.ParamCount[:], p[20:22])
95159e55 184
95159e55
JH
185 scanner := bufio.NewScanner(bytes.NewReader(p[22:tranLen]))
186 scanner.Split(fieldScanner)
187
a2ef262a
JH
188 for i := 0; i < int(paramCount); i++ {
189 if !scanner.Scan() {
190 return 0, fmt.Errorf("error scanning field: %w", scanner.Err())
191 }
95159e55
JH
192
193 var field Field
194 if _, err := field.Write(scanner.Bytes()); err != nil {
195 return 0, fmt.Errorf("error reading field: %w", err)
196 }
197 t.Fields = append(t.Fields, field)
6988a057
JH
198 }
199
a2ef262a
JH
200 if err := scanner.Err(); err != nil {
201 return 0, fmt.Errorf("scanner error: %w", err)
202 }
203
204 return len(p), nil
6988a057
JH
205}
206
3178ae58 207const tranHeaderLen = 20 // fixed length of transaction fields before the variable length fields
6988a057 208
3178ae58
JH
209// transactionScanner implements bufio.SplitFunc for parsing incoming byte slices into complete tokens
210func transactionScanner(data []byte, _ bool) (advance int, token []byte, err error) {
211 // The bytes that contain the size of a transaction are from 12:16, so we need at least 16 bytes
212 if len(data) < 16 {
213 return 0, nil, nil
214 }
6988a057 215
3178ae58 216 totalSize := binary.BigEndian.Uint32(data[12:16])
6988a057 217
3178ae58
JH
218 // tranLen represents the length of bytes that are part of the transaction
219 tranLen := int(tranHeaderLen + totalSize)
220 if tranLen > len(data) {
221 return 0, nil, nil
6988a057
JH
222 }
223
3178ae58 224 return tranLen, data[0:tranLen], nil
6988a057
JH
225}
226
227const minFieldLen = 4
228
95159e55
JH
229// Read implements the io.Reader interface for Transaction
230func (t *Transaction) Read(p []byte) (int, error) {
6988a057
JH
231 payloadSize := t.Size()
232
233 fieldCount := make([]byte, 2)
234 binary.BigEndian.PutUint16(fieldCount, uint16(len(t.Fields)))
235
95159e55
JH
236 bbuf := new(bytes.Buffer)
237
6988a057 238 for _, field := range t.Fields {
0ed51327
JH
239 f := field
240 _, err := bbuf.ReadFrom(&f)
95159e55
JH
241 if err != nil {
242 return 0, fmt.Errorf("error reading field: %w", err)
243 }
6988a057
JH
244 }
245
95159e55 246 buf := slices.Concat(
6988a057 247 []byte{t.Flags, t.IsReply},
153e2eac
JH
248 t.Type[:],
249 t.ID[:],
250 t.ErrorCode[:],
6988a057
JH
251 payloadSize,
252 payloadSize, // this is the dataSize field, but seeming the same as totalSize
253 fieldCount,
95159e55
JH
254 bbuf.Bytes(),
255 )
256
257 if t.readOffset >= len(buf) {
258 return 0, io.EOF // All bytes have been read
259 }
260
261 n := copy(p, buf[t.readOffset:])
262 t.readOffset += n
263
264 return n, nil
6988a057
JH
265}
266
267// Size returns the total size of the transaction payload
0a92e50b 268func (t *Transaction) Size() []byte {
6988a057
JH
269 bs := make([]byte, 4)
270
271 fieldSize := 0
272 for _, field := range t.Fields {
273 fieldSize += len(field.Data) + 4
274 }
275
276 binary.BigEndian.PutUint32(bs, uint32(fieldSize+2))
277
278 return bs
279}
280
d9bc63a1 281func (t *Transaction) GetField(id [2]byte) *Field {
6988a057 282 for _, field := range t.Fields {
d9bc63a1
JH
283 if id == field.Type {
284 return &field
6988a057
JH
285 }
286 }
287
d9bc63a1 288 return &Field{}
6988a057 289}