package hotline
import (
+ "bytes"
"encoding/binary"
"errors"
"fmt"
)
const (
- tranError = 0
- tranGetMsgs = 101
- tranNewMsg = 102
- tranOldPostNews = 103
- tranServerMsg = 104
- tranChatSend = 105
- tranChatMsg = 106
- tranLogin = 107
- tranSendInstantMsg = 108
- tranShowAgreement = 109
- tranDisconnectUser = 110
- // tranDisconnectMsg = 111 TODO: implement friendly disconnect
- tranInviteNewChat = 112
- tranInviteToChat = 113
- tranRejectChatInvite = 114
- tranJoinChat = 115
- tranLeaveChat = 116
- tranNotifyChatChangeUser = 117
- tranNotifyChatDeleteUser = 118
- tranNotifyChatSubject = 119
- tranSetChatSubject = 120
- tranAgreed = 121
- tranServerBanner = 122
- tranGetFileNameList = 200
- tranDownloadFile = 202
- tranUploadFile = 203
- tranNewFolder = 205
- tranDeleteFile = 204
- tranGetFileInfo = 206
- tranSetFileInfo = 207
- tranMoveFile = 208
- tranMakeFileAlias = 209
- tranDownloadFldr = 210
- // tranDownloadInfo = 211 TODO: implement file transfer queue
- tranDownloadBanner = 212
- tranUploadFldr = 213
- tranGetUserNameList = 300
- tranNotifyChangeUser = 301
- tranNotifyDeleteUser = 302
- tranGetClientInfoText = 303
- tranSetClientUserInfo = 304
- tranListUsers = 348
- tranUpdateUser = 349
- tranNewUser = 350
- tranDeleteUser = 351
- tranGetUser = 352
- tranSetUser = 353
- tranUserAccess = 354
- tranUserBroadcast = 355
- tranGetNewsCatNameList = 370
- tranGetNewsArtNameList = 371
- tranDelNewsItem = 380
- tranNewNewsFldr = 381
- tranNewNewsCat = 382
- tranGetNewsArtData = 400
- tranPostNewsArt = 410
- tranDelNewsArt = 411
- tranKeepAlive = 500
+ TranError = 0
+ TranGetMsgs = 101
+ TranNewMsg = 102
+ TranOldPostNews = 103
+ TranServerMsg = 104
+ TranChatSend = 105
+ TranChatMsg = 106
+ TranLogin = 107
+ TranSendInstantMsg = 108
+ TranShowAgreement = 109
+ TranDisconnectUser = 110
+ TranDisconnectMsg = 111 // TODO: implement server initiated friendly disconnect
+ TranInviteNewChat = 112
+ TranInviteToChat = 113
+ TranRejectChatInvite = 114
+ TranJoinChat = 115
+ TranLeaveChat = 116
+ TranNotifyChatChangeUser = 117
+ TranNotifyChatDeleteUser = 118
+ TranNotifyChatSubject = 119
+ TranSetChatSubject = 120
+ TranAgreed = 121
+ TranServerBanner = 122
+ TranGetFileNameList = 200
+ TranDownloadFile = 202
+ TranUploadFile = 203
+ TranNewFolder = 205
+ TranDeleteFile = 204
+ TranGetFileInfo = 206
+ TranSetFileInfo = 207
+ TranMoveFile = 208
+ TranMakeFileAlias = 209
+ TranDownloadFldr = 210
+ TranDownloadInfo = 211 // TODO: implement file transfer queue
+ TranDownloadBanner = 212
+ TranUploadFldr = 213
+ TranGetUserNameList = 300
+ TranNotifyChangeUser = 301
+ TranNotifyDeleteUser = 302
+ TranGetClientInfoText = 303
+ TranSetClientUserInfo = 304
+ TranListUsers = 348
+ TranUpdateUser = 349
+ TranNewUser = 350
+ TranDeleteUser = 351
+ TranGetUser = 352
+ TranSetUser = 353
+ TranUserAccess = 354
+ TranUserBroadcast = 355
+ TranGetNewsCatNameList = 370
+ TranGetNewsArtNameList = 371
+ TranDelNewsItem = 380
+ TranNewNewsFldr = 381
+ TranNewNewsCat = 382
+ TranGetNewsArtData = 400
+ TranPostNewsArt = 410
+ TranDelNewsArt = 411
+ TranKeepAlive = 500
)
type Transaction struct {
}
}
-// ReadTransaction parses a byte slice into a struct. The input slice may be shorter or longer
-// that the transaction size depending on what was read from the network connection.
-func ReadTransaction(buf []byte) (*Transaction, int, error) {
- totalSize := binary.BigEndian.Uint32(buf[12:16])
+// Write implements io.Writer interface for Transaction
+func (t *Transaction) Write(p []byte) (n int, err error) {
+ totalSize := binary.BigEndian.Uint32(p[12:16])
// the buf may include extra bytes that are not part of the transaction
// tranLen represents the length of bytes that are part of the transaction
tranLen := int(20 + totalSize)
- if tranLen > len(buf) {
- return nil, 0, errors.New("buflen too small for tranLen")
+ if tranLen > len(p) {
+ return n, errors.New("buflen too small for tranLen")
}
- fields, err := ReadFields(buf[20:22], buf[22:tranLen])
+ fields, err := ReadFields(p[20:22], p[22:tranLen])
if err != nil {
- return nil, 0, err
+ return n, err
}
- return &Transaction{
- Flags: buf[0],
- IsReply: buf[1],
- Type: buf[2:4],
- ID: buf[4:8],
- ErrorCode: buf[8:12],
- TotalSize: buf[12:16],
- DataSize: buf[16:20],
- ParamCount: buf[20:22],
- Fields: fields,
- }, tranLen, nil
+ t.Flags = p[0]
+ t.IsReply = p[1]
+ t.Type = p[2:4]
+ t.ID = p[4:8]
+ t.ErrorCode = p[8:12]
+ t.TotalSize = p[12:16]
+ t.DataSize = p[16:20]
+ t.ParamCount = p[20:22]
+ t.Fields = fields
+
+ return len(p), err
}
-func readTransactions(buf []byte) ([]Transaction, int, error) {
- var transactions []Transaction
+const tranHeaderLen = 20 // fixed length of transaction fields before the variable length fields
- bufLen := len(buf)
+// transactionScanner implements bufio.SplitFunc for parsing incoming byte slices into complete tokens
+func transactionScanner(data []byte, _ bool) (advance int, token []byte, err error) {
+ // The bytes that contain the size of a transaction are from 12:16, so we need at least 16 bytes
+ if len(data) < 16 {
+ return 0, nil, nil
+ }
- var bytesRead = 0
- for bytesRead < bufLen {
- t, tReadLen, err := ReadTransaction(buf[bytesRead:])
- if err != nil {
- return transactions, bytesRead, err
- }
- bytesRead += tReadLen
+ totalSize := binary.BigEndian.Uint32(data[12:16])
- transactions = append(transactions, *t)
+ // tranLen represents the length of bytes that are part of the transaction
+ tranLen := int(tranHeaderLen + totalSize)
+ if tranLen > len(data) {
+ return 0, nil, nil
}
- return transactions, bytesRead, nil
+ return tranLen, data[0:tranLen], nil
}
const minFieldLen = 4
return Field{}
}
+
+func (t *Transaction) IsError() bool {
+ return bytes.Equal(t.ErrorCode, []byte{0, 0, 0, 1})
+}