7 "github.com/jhalter/mobius/concat"
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
29 tranNotifyChatChangeUser = 117
30 tranNotifyChatDeleteUser = 118
31 tranNotifyChatSubject = 119
32 tranSetChatSubject = 120
34 tranGetFileNameList = 200
35 tranDownloadFile = 202
42 tranMakeFileAlias = 209 // TODO: implement file alias command
43 tranDownloadFldr = 210
44 // tranDownloadInfo = 211 TODO: implement file transfer queue
45 // tranDownloadBanner = 212 TODO: figure out what this is used for
47 tranGetUserNameList = 300
48 tranNotifyChangeUser = 301
49 tranNotifyDeleteUser = 302
50 tranGetClientInfoText = 303
51 tranSetClientUserInfo = 304
59 tranUserBroadcast = 355
60 tranGetNewsCatNameList = 370
61 tranGetNewsArtNameList = 371
65 tranGetNewsArtData = 400
71 type Transaction struct {
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
85 func NewTransaction(t int, clientID *[]byte, fields ...Field) *Transaction {
86 typeSlice := make([]byte, 2)
87 binary.BigEndian.PutUint16(typeSlice, uint16(t))
89 idSlice := make([]byte, 4)
90 binary.BigEndian.PutUint32(idSlice, rand.Uint32())
98 ErrorCode: []byte{0, 0, 0, 0},
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.
105 func ReadTransaction(buf []byte) (*Transaction, int, error) {
106 totalSize := binary.BigEndian.Uint32(buf[12:16])
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)
112 if tranLen > len(buf) {
113 return nil, 0, errors.New("buflen too small for tranLen")
115 fields, err := ReadFields(buf[20:22], buf[22:tranLen])
125 ErrorCode: buf[8:12],
126 TotalSize: buf[12:16],
127 DataSize: buf[16:20],
128 ParamCount: buf[20:22],
133 func readTransactions(buf []byte) ([]Transaction, int, error) {
134 var transactions []Transaction
139 for bytesRead < bufLen {
140 t, tReadLen, err := ReadTransaction(buf[bytesRead:])
142 return transactions, bytesRead, err
144 bytesRead += tReadLen
146 transactions = append(transactions, *t)
149 return transactions, bytesRead, nil
152 const minFieldLen = 4
154 func 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))
160 // A Field consists of:
163 // Data: FieldSize number of bytes
165 for i := 0; i < paramCountInt; i++ {
166 if len(buf) < minFieldLen {
167 return []Field{}, fmt.Errorf("invalid field length %v", len(buf))
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")
177 fields = append(fields, Field{
179 FieldSize: fieldSize,
180 Data: buf[4 : 4+fieldSizeInt],
183 buf = buf[fieldSizeInt+4:]
187 return []Field{}, fmt.Errorf("extra field bytes")
193 func (t *Transaction) MarshalBinary() (data []byte, err error) {
194 payloadSize := t.Size()
196 fieldCount := make([]byte, 2)
197 binary.BigEndian.PutUint16(fieldCount, uint16(len(t.Fields)))
199 var fieldPayload []byte
200 for _, field := range t.Fields {
201 fieldPayload = append(fieldPayload, field.Payload()...)
204 return concat.Slices(
205 []byte{t.Flags, t.IsReply},
210 payloadSize, // this is the dataSize field, but seeming the same as totalSize
216 // Size returns the total size of the transaction payload
217 func (t *Transaction) Size() []byte {
218 bs := make([]byte, 4)
221 for _, field := range t.Fields {
222 fieldSize += len(field.Data) + 4
225 binary.BigEndian.PutUint32(bs, uint32(fieldSize+2))
230 func (t *Transaction) GetField(id int) Field {
231 for _, field := range t.Fields {
232 if id == int(binary.BigEndian.Uint16(field.ID)) {