]> git.r.bdr.sh - rbdr/mobius/blame - hotline/field.go
Fix Windows compatibility for -init flag
[rbdr/mobius] / hotline / field.go
CommitLineData
6988a057
JH
1package hotline
2
3import (
4 "encoding/binary"
a2ef262a 5 "errors"
95159e55 6 "io"
9c44621e 7 "slices"
6988a057
JH
8)
9
d005ef04 10// List of Hotline protocol field types taken from the official 1.9 protocol document
a2ef262a
JH
11var (
12 FieldError = [2]byte{0x00, 0x64} // 100
13 FieldData = [2]byte{0x00, 0x65} // 101
14 FieldUserName = [2]byte{0x00, 0x66} // 102
15 FieldUserID = [2]byte{0x00, 0x67} // 103
16 FieldUserIconID = [2]byte{0x00, 0x68} // 104
17 FieldUserLogin = [2]byte{0x00, 0x69} // 105
18 FieldUserPassword = [2]byte{0x00, 0x6A} // 106
19 FieldRefNum = [2]byte{0x00, 0x6B} // 107
20 FieldTransferSize = [2]byte{0x00, 0x6C} // 108
21 FieldChatOptions = [2]byte{0x00, 0x6D} // 109
22 FieldUserAccess = [2]byte{0x00, 0x6E} // 110
23 FieldUserFlags = [2]byte{0x00, 0x70} // 112
24 FieldOptions = [2]byte{0x00, 0x71} // 113
25 FieldChatID = [2]byte{0x00, 0x72} // 114
26 FieldChatSubject = [2]byte{0x00, 0x73} // 115
27 FieldWaitingCount = [2]byte{0x00, 0x74} // 116
28 FieldBannerType = [2]byte{0x00, 0x98} // 152
29 FieldNoServerAgreement = [2]byte{0x00, 0x98} // 152
30 FieldVersion = [2]byte{0x00, 0xA0} // 160
31 FieldCommunityBannerID = [2]byte{0x00, 0xA1} // 161
32 FieldServerName = [2]byte{0x00, 0xA2} // 162
33 FieldFileNameWithInfo = [2]byte{0x00, 0xC8} // 200
34 FieldFileName = [2]byte{0x00, 0xC9} // 201
35 FieldFilePath = [2]byte{0x00, 0xCA} // 202
36 FieldFileResumeData = [2]byte{0x00, 0xCB} // 203
37 FieldFileTransferOptions = [2]byte{0x00, 0xCC} // 204
38 FieldFileTypeString = [2]byte{0x00, 0xCD} // 205
39 FieldFileCreatorString = [2]byte{0x00, 0xCE} // 206
40 FieldFileSize = [2]byte{0x00, 0xCF} // 207
41 FieldFileCreateDate = [2]byte{0x00, 0xD0} // 208
42 FieldFileModifyDate = [2]byte{0x00, 0xD1} // 209
43 FieldFileComment = [2]byte{0x00, 0xD2} // 210
44 FieldFileNewName = [2]byte{0x00, 0xD3} // 211
45 FieldFileNewPath = [2]byte{0x00, 0xD4} // 212
46 FieldFileType = [2]byte{0x00, 0xD5} // 213
47 FieldQuotingMsg = [2]byte{0x00, 0xD6} // 214
48 FieldAutomaticResponse = [2]byte{0x00, 0xD7} // 215
49 FieldFolderItemCount = [2]byte{0x00, 0xDC} // 220
50 FieldUsernameWithInfo = [2]byte{0x01, 0x2C} // 300
51 FieldNewsArtListData = [2]byte{0x01, 0x41} // 321
52 FieldNewsCatName = [2]byte{0x01, 0x42} // 322
53 FieldNewsCatListData15 = [2]byte{0x01, 0x43} // 323
54 FieldNewsPath = [2]byte{0x01, 0x45} // 325
55 FieldNewsArtID = [2]byte{0x01, 0x46} // 326
56 FieldNewsArtDataFlav = [2]byte{0x01, 0x47} // 327
57 FieldNewsArtTitle = [2]byte{0x01, 0x48} // 328
58 FieldNewsArtPoster = [2]byte{0x01, 0x49} // 329
59 FieldNewsArtDate = [2]byte{0x01, 0x4A} // 330
60 FieldNewsArtPrevArt = [2]byte{0x01, 0x4B} // 331
61 FieldNewsArtNextArt = [2]byte{0x01, 0x4C} // 332
62 FieldNewsArtData = [2]byte{0x01, 0x4D} // 333
63 FieldNewsArtParentArt = [2]byte{0x01, 0x4F} // 335
64 FieldNewsArt1stChildArt = [2]byte{0x01, 0x50} // 336
65
66 // These fields are documented, but seemingly unused.
67 // FieldUserAlias = [2]byte{0x00, 0x6F} // 111
68 // FieldNewsArtFlags = [2]byte{0x01, 0x4E} // 334
69 // FieldNewsArtRecurseDel = [2]byte{0x01, 0x51} // 337
d005ef04 70)
6988a057
JH
71
72type Field struct {
95159e55
JH
73 ID [2]byte // Type of field
74 FieldSize [2]byte // Size of the data part
75 Data []byte // Actual field content
76
77 readOffset int // Internal offset to track read progress
6988a057
JH
78}
79
a2ef262a
JH
80func NewField(id [2]byte, data []byte) Field {
81 f := Field{
82 ID: id,
83 Data: make([]byte, len(data)),
84 }
6988a057 85
a2ef262a
JH
86 // Copy instead of assigning to avoid data race when the field is read in another go routine.
87 copy(f.Data, data)
6988a057 88
a2ef262a 89 binary.BigEndian.PutUint16(f.FieldSize[:], uint16(len(data)))
a55350da 90 return f
6988a057
JH
91}
92
95159e55
JH
93// fieldScanner implements bufio.SplitFunc for parsing byte slices into complete tokens
94func fieldScanner(data []byte, _ bool) (advance int, token []byte, err error) {
95 if len(data) < minFieldLen {
96 return 0, nil, nil
97 }
98
a2ef262a 99 // neededSize represents the length of bytes that are part of the field token.
95159e55
JH
100 neededSize := minFieldLen + int(binary.BigEndian.Uint16(data[2:4]))
101 if neededSize > len(data) {
102 return 0, nil, nil
103 }
104
105 return neededSize, data[0:neededSize], nil
106}
107
108// Read implements io.Reader for Field
109func (f *Field) Read(p []byte) (int, error) {
110 buf := slices.Concat(f.ID[:], f.FieldSize[:], f.Data)
111
112 if f.readOffset >= len(buf) {
113 return 0, io.EOF // All bytes have been read
114 }
115
116 n := copy(p, buf[f.readOffset:])
117 f.readOffset += n
118
119 return n, nil
120}
121
122// Write implements io.Writer for Field
123func (f *Field) Write(p []byte) (int, error) {
a2ef262a
JH
124 if len(p) < minFieldLen {
125 return 0, errors.New("input slice too short")
126 }
127
128 copy(f.ID[:], p[0:2])
129 copy(f.FieldSize[:], p[2:4])
130
131 dataSize := int(binary.BigEndian.Uint16(f.FieldSize[:]))
132 if len(p) < minFieldLen+dataSize {
133 return 0, errors.New("input slice too short for data size")
134 }
95159e55 135
a2ef262a
JH
136 f.Data = make([]byte, dataSize)
137 copy(f.Data, p[4:4+dataSize])
95159e55 138
a2ef262a 139 return minFieldLen + dataSize, nil
6988a057 140}
d2810ae9 141
a2ef262a 142func getField(id [2]byte, fields *[]Field) *Field {
d2810ae9 143 for _, field := range *fields {
a2ef262a 144 if id == field.ID {
d2810ae9
JH
145 return &field
146 }
147 }
148 return nil
149}