]> git.r.bdr.sh - rbdr/mobius/blob - hotline/field.go
2fcb41a1a5451e070ba187fe17695af4d3f90171
[rbdr/mobius] / hotline / field.go
1 package hotline
2
3 import (
4 "encoding/binary"
5 "io"
6 "slices"
7 )
8
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 )
68
69 type Field struct {
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
75 }
76
77 type requiredField struct {
78 ID int
79 minLen int
80 }
81
82 func NewField(id uint16, data []byte) Field {
83 f := Field{Data: data}
84 binary.BigEndian.PutUint16(f.ID[:], id)
85 binary.BigEndian.PutUint16(f.FieldSize[:], uint16(len(data)))
86
87 return f
88 }
89
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
128 }
129
130 func getField(id int, fields *[]Field) *Field {
131 for _, field := range *fields {
132 if id == int(binary.BigEndian.Uint16(field.ID[:])) {
133 return &field
134 }
135 }
136 return nil
137 }