]>
Commit | Line | Data |
---|---|---|
6988a057 JH |
1 | package hotline |
2 | ||
3 | import ( | |
4 | "encoding/binary" | |
95159e55 | 5 | "io" |
9c44621e | 6 | "slices" |
6988a057 JH |
7 | ) |
8 | ||
d005ef04 JH |
9 | // List of Hotline protocol field types taken from the official 1.9 protocol document |
10 | const ( | |
11 | FieldError = 100 | |
12 | FieldData = 101 | |
13 | FieldUserName = 102 | |
14 | FieldUserID = 103 | |
15 | FieldUserIconID = 104 | |
16 | FieldUserLogin = 105 | |
17 | FieldUserPassword = 106 | |
18 | FieldRefNum = 107 | |
19 | FieldTransferSize = 108 | |
20 | FieldChatOptions = 109 | |
21 | FieldUserAccess = 110 | |
22 | FieldUserAlias = 111 // TODO: implement | |
23 | FieldUserFlags = 112 | |
24 | FieldOptions = 113 | |
25 | FieldChatID = 114 | |
26 | FieldChatSubject = 115 | |
27 | FieldWaitingCount = 116 | |
28 | FieldBannerType = 152 | |
29 | FieldNoServerAgreement = 152 | |
30 | FieldVersion = 160 | |
31 | FieldCommunityBannerID = 161 | |
32 | FieldServerName = 162 | |
33 | FieldFileNameWithInfo = 200 | |
34 | FieldFileName = 201 | |
35 | FieldFilePath = 202 | |
36 | FieldFileResumeData = 203 | |
37 | FieldFileTransferOptions = 204 | |
38 | FieldFileTypeString = 205 | |
39 | FieldFileCreatorString = 206 | |
40 | FieldFileSize = 207 | |
41 | FieldFileCreateDate = 208 | |
42 | FieldFileModifyDate = 209 | |
43 | FieldFileComment = 210 | |
44 | FieldFileNewName = 211 | |
45 | FieldFileNewPath = 212 | |
46 | FieldFileType = 213 | |
47 | FieldQuotingMsg = 214 | |
48 | FieldAutomaticResponse = 215 | |
49 | FieldFolderItemCount = 220 | |
50 | FieldUsernameWithInfo = 300 | |
51 | FieldNewsArtListData = 321 | |
52 | FieldNewsCatName = 322 | |
53 | FieldNewsCatListData15 = 323 | |
54 | FieldNewsPath = 325 | |
55 | FieldNewsArtID = 326 | |
56 | FieldNewsArtDataFlav = 327 | |
57 | FieldNewsArtTitle = 328 | |
58 | FieldNewsArtPoster = 329 | |
59 | FieldNewsArtDate = 330 | |
60 | FieldNewsArtPrevArt = 331 | |
61 | FieldNewsArtNextArt = 332 | |
62 | FieldNewsArtData = 333 | |
63 | FieldNewsArtFlags = 334 // TODO: what is this used for? | |
64 | FieldNewsArtParentArt = 335 | |
65 | FieldNewsArt1stChildArt = 336 | |
66 | FieldNewsArtRecurseDel = 337 // TODO: implement news article recusive deletion | |
67 | ) | |
6988a057 JH |
68 | |
69 | type Field struct { | |
95159e55 JH |
70 | ID [2]byte // Type of field |
71 | FieldSize [2]byte // Size of the data part | |
72 | Data []byte // Actual field content | |
73 | ||
74 | readOffset int // Internal offset to track read progress | |
6988a057 JH |
75 | } |
76 | ||
77 | type requiredField struct { | |
78 | ID int | |
79 | minLen int | |
6988a057 JH |
80 | } |
81 | ||
82 | func NewField(id uint16, data []byte) Field { | |
a55350da JH |
83 | f := Field{Data: data} |
84 | binary.BigEndian.PutUint16(f.ID[:], id) | |
85 | binary.BigEndian.PutUint16(f.FieldSize[:], uint16(len(data))) | |
6988a057 | 86 | |
a55350da | 87 | return f |
6988a057 JH |
88 | } |
89 | ||
95159e55 JH |
90 | // fieldScanner implements bufio.SplitFunc for parsing byte slices into complete tokens |
91 | func fieldScanner(data []byte, _ bool) (advance int, token []byte, err error) { | |
92 | if len(data) < minFieldLen { | |
93 | return 0, nil, nil | |
94 | } | |
95 | ||
96 | // tranLen represents the length of bytes that are part of the transaction | |
97 | neededSize := minFieldLen + int(binary.BigEndian.Uint16(data[2:4])) | |
98 | if neededSize > len(data) { | |
99 | return 0, nil, nil | |
100 | } | |
101 | ||
102 | return neededSize, data[0:neededSize], nil | |
103 | } | |
104 | ||
105 | // Read implements io.Reader for Field | |
106 | func (f *Field) Read(p []byte) (int, error) { | |
107 | buf := slices.Concat(f.ID[:], f.FieldSize[:], f.Data) | |
108 | ||
109 | if f.readOffset >= len(buf) { | |
110 | return 0, io.EOF // All bytes have been read | |
111 | } | |
112 | ||
113 | n := copy(p, buf[f.readOffset:]) | |
114 | f.readOffset += n | |
115 | ||
116 | return n, nil | |
117 | } | |
118 | ||
119 | // Write implements io.Writer for Field | |
120 | func (f *Field) Write(p []byte) (int, error) { | |
121 | f.ID = [2]byte(p[0:2]) | |
122 | f.FieldSize = [2]byte(p[2:4]) | |
123 | ||
124 | i := int(binary.BigEndian.Uint16(f.FieldSize[:])) | |
125 | f.Data = p[4 : 4+i] | |
126 | ||
127 | return minFieldLen + i, nil | |
6988a057 | 128 | } |
d2810ae9 JH |
129 | |
130 | func getField(id int, fields *[]Field) *Field { | |
131 | for _, field := range *fields { | |
95159e55 | 132 | if id == int(binary.BigEndian.Uint16(field.ID[:])) { |
d2810ae9 JH |
133 | return &field |
134 | } | |
135 | } | |
136 | return nil | |
137 | } |