7 "github.com/jhalter/mobius/concat"
21 tranSendInstantMsg = 108
22 tranShowAgreement = 109
23 tranDisconnectUser = 110
24 // tranDisconnectMsg = 111 TODO: implement friendly disconnect
25 tranInviteNewChat = 112
26 tranInviteToChat = 113
27 tranRejectChatInvite = 114
30 tranNotifyChatChangeUser = 117
31 tranNotifyChatDeleteUser = 118
32 tranNotifyChatSubject = 119
33 tranSetChatSubject = 120
35 tranGetFileNameList = 200
36 tranDownloadFile = 202
43 tranMakeFileAlias = 209 // TODO: implement file alias command
44 tranDownloadFldr = 210
45 // tranDownloadInfo = 211 TODO: implement file transfer queue
46 // tranDownloadBanner = 212 TODO: figure out what this is used for
48 tranGetUserNameList = 300
49 tranNotifyChangeUser = 301
50 tranNotifyDeleteUser = 302
51 tranGetClientInfoText = 303
52 tranSetClientUserInfo = 304
54 // tranUpdateUser = 349 TODO: implement user updates from the > 1.5 account editor
60 tranUserBroadcast = 355
61 tranGetNewsCatNameList = 370
62 tranGetNewsArtNameList = 371
66 tranGetNewsArtData = 400
72 type Transaction struct {
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
86 func NewTransaction(t int, clientID *[]byte, fields ...Field) *Transaction {
87 typeSlice := make([]byte, 2)
88 binary.BigEndian.PutUint16(typeSlice, uint16(t))
90 idSlice := make([]byte, 4)
91 binary.BigEndian.PutUint32(idSlice, rand.Uint32())
99 ErrorCode: []byte{0, 0, 0, 0},
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.
106 func ReadTransaction(buf []byte) (*Transaction, int, error) {
107 totalSize := binary.BigEndian.Uint32(buf[12:16])
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)
113 if tranLen > len(buf) {
114 return nil, 0, errors.New("buflen too small for tranLen")
116 fields, err := ReadFields(buf[20:22], buf[22:tranLen])
126 ErrorCode: buf[8:12],
127 TotalSize: buf[12:16],
128 DataSize: buf[16:20],
129 ParamCount: buf[20:22],
134 func readN(conn net.Conn, n int) ([]Transaction, error) {
135 buf := make([]byte, 1400)
138 readLen, err := conn.Read(buf)
143 transactions, _, err := readTransactions(buf[:readLen])
144 // spew.Fdump(os.Stderr, transactions)
149 i += len(transactions)
152 return transactions, nil
157 func readTransactions(buf []byte) ([]Transaction, int, error) {
158 var transactions []Transaction
163 for bytesRead < bufLen {
164 t, tReadLen, err := ReadTransaction(buf[bytesRead:])
166 return transactions, bytesRead, err
168 bytesRead += tReadLen
170 transactions = append(transactions, *t)
173 return transactions, bytesRead, nil
176 const minFieldLen = 4
178 func ReadFields(paramCount []byte, buf []byte) ([]Field, error) {
179 paramCountInt := int(binary.BigEndian.Uint16(paramCount))
180 if paramCountInt > 0 && len(buf) < minFieldLen {
181 return []Field{}, fmt.Errorf("invalid field length %v", len(buf))
184 // A Field consists of:
187 // Data: FieldSize number of bytes
189 for i := 0; i < paramCountInt; i++ {
190 if len(buf) < minFieldLen {
191 return []Field{}, fmt.Errorf("invalid field length %v", len(buf))
194 fieldSize := buf[2:4]
195 fieldSizeInt := int(binary.BigEndian.Uint16(buf[2:4]))
196 expectedLen := minFieldLen + fieldSizeInt
197 if len(buf) < expectedLen {
198 return []Field{}, fmt.Errorf("field length too short")
201 fields = append(fields, Field{
203 FieldSize: fieldSize,
204 Data: buf[4 : 4+fieldSizeInt],
207 buf = buf[fieldSizeInt+4:]
211 return []Field{}, fmt.Errorf("extra field bytes")
217 func (t Transaction) MarshalBinary() (data []byte, err error) {
218 payloadSize := t.Size()
220 fieldCount := make([]byte, 2)
221 binary.BigEndian.PutUint16(fieldCount, uint16(len(t.Fields)))
223 var fieldPayload []byte
224 for _, field := range t.Fields {
225 fieldPayload = append(fieldPayload, field.Payload()...)
228 return concat.Slices(
229 []byte{t.Flags, t.IsReply},
234 payloadSize, // this is the dataSize field, but seeming the same as totalSize
240 // Size returns the total size of the transaction payload
241 func (t Transaction) Size() []byte {
242 bs := make([]byte, 4)
245 for _, field := range t.Fields {
246 fieldSize += len(field.Data) + 4
249 binary.BigEndian.PutUint32(bs, uint32(fieldSize+2))
254 func (t Transaction) GetField(id int) Field {
255 for _, field := range t.Fields {
256 if id == int(binary.BigEndian.Uint16(field.ID)) {