X-Git-Url: https://git.r.bdr.sh/rbdr/mobius/blobdiff_plain/270abe9a991a57084650424a0238ef4f062b0b64..a55350daaf83498b7a237c027ad0dd2377f06fee:/hotline/field.go diff --git a/hotline/field.go b/hotline/field.go index 3076017..2fcb41a 100644 --- a/hotline/field.go +++ b/hotline/field.go @@ -2,99 +2,134 @@ package hotline import ( "encoding/binary" - "github.com/jhalter/mobius/concat" + "io" + "slices" ) -const fieldError = 100 -const fieldData = 101 -const fieldUserName = 102 -const fieldUserID = 103 -const fieldUserIconID = 104 -const fieldUserLogin = 105 -const fieldUserPassword = 106 -const fieldRefNum = 107 -const fieldTransferSize = 108 -const fieldChatOptions = 109 -const fieldUserAccess = 110 - -// const fieldUserAlias = 111 TODO: implement -const fieldUserFlags = 112 -const fieldOptions = 113 // Server message (1) or admin message (any other value) -const fieldChatID = 114 -const fieldChatSubject = 115 -const fieldWaitingCount = 116 -const fieldBannerType = 152 -const fieldVersion = 160 -const fieldCommunityBannerID = 161 -const fieldServerName = 162 -const fieldFileNameWithInfo = 200 -const fieldFileName = 201 -const fieldFilePath = 202 -const fieldFileResumeData = 203 -const fieldFileTransferOptions = 204 -const fieldFileTypeString = 205 -const fieldFileCreatorString = 206 -const fieldFileSize = 207 -const fieldFileCreateDate = 208 -const fieldFileModifyDate = 209 -const fieldFileComment = 210 -const fieldFileNewName = 211 -const fieldFileNewPath = 212 -const fieldFileType = 213 -const fieldQuotingMsg = 214 -const fieldAutomaticResponse = 215 -const fieldFolderItemCount = 220 -const fieldUsernameWithInfo = 300 -const fieldNewsArtListData = 321 -const fieldNewsCatName = 322 -const fieldNewsCatListData15 = 323 -const fieldNewsPath = 325 -const fieldNewsArtID = 326 -const fieldNewsArtDataFlav = 327 -const fieldNewsArtTitle = 328 -const fieldNewsArtPoster = 329 -const fieldNewsArtDate = 330 -const fieldNewsArtPrevArt = 331 -const fieldNewsArtNextArt = 332 -const fieldNewsArtData = 333 -const fieldNewsArtFlags = 334 -const fieldNewsArtParentArt = 335 -const fieldNewsArt1stChildArt = 336 -const fieldNewsArtRecurseDel = 337 +// List of Hotline protocol field types taken from the official 1.9 protocol document +const ( + FieldError = 100 + FieldData = 101 + FieldUserName = 102 + FieldUserID = 103 + FieldUserIconID = 104 + FieldUserLogin = 105 + FieldUserPassword = 106 + FieldRefNum = 107 + FieldTransferSize = 108 + FieldChatOptions = 109 + FieldUserAccess = 110 + FieldUserAlias = 111 // TODO: implement + FieldUserFlags = 112 + FieldOptions = 113 + FieldChatID = 114 + FieldChatSubject = 115 + FieldWaitingCount = 116 + FieldBannerType = 152 + FieldNoServerAgreement = 152 + FieldVersion = 160 + FieldCommunityBannerID = 161 + FieldServerName = 162 + FieldFileNameWithInfo = 200 + FieldFileName = 201 + FieldFilePath = 202 + FieldFileResumeData = 203 + FieldFileTransferOptions = 204 + FieldFileTypeString = 205 + FieldFileCreatorString = 206 + FieldFileSize = 207 + FieldFileCreateDate = 208 + FieldFileModifyDate = 209 + FieldFileComment = 210 + FieldFileNewName = 211 + FieldFileNewPath = 212 + FieldFileType = 213 + FieldQuotingMsg = 214 + FieldAutomaticResponse = 215 + FieldFolderItemCount = 220 + FieldUsernameWithInfo = 300 + FieldNewsArtListData = 321 + FieldNewsCatName = 322 + FieldNewsCatListData15 = 323 + FieldNewsPath = 325 + FieldNewsArtID = 326 + FieldNewsArtDataFlav = 327 + FieldNewsArtTitle = 328 + FieldNewsArtPoster = 329 + FieldNewsArtDate = 330 + FieldNewsArtPrevArt = 331 + FieldNewsArtNextArt = 332 + FieldNewsArtData = 333 + FieldNewsArtFlags = 334 // TODO: what is this used for? + FieldNewsArtParentArt = 335 + FieldNewsArt1stChildArt = 336 + FieldNewsArtRecurseDel = 337 // TODO: implement news article recusive deletion +) type Field struct { - ID []byte // Type of field - FieldSize []byte // Size of the data part - Data []byte // Actual field content + ID [2]byte // Type of field + FieldSize [2]byte // Size of the data part + Data []byte // Actual field content + + readOffset int // Internal offset to track read progress } type requiredField struct { ID int minLen int - maxLen int } func NewField(id uint16, data []byte) Field { - idBytes := make([]byte, 2) - binary.BigEndian.PutUint16(idBytes, id) + f := Field{Data: data} + binary.BigEndian.PutUint16(f.ID[:], id) + binary.BigEndian.PutUint16(f.FieldSize[:], uint16(len(data))) + + return f +} + +// fieldScanner implements bufio.SplitFunc for parsing byte slices into complete tokens +func fieldScanner(data []byte, _ bool) (advance int, token []byte, err error) { + if len(data) < minFieldLen { + return 0, nil, nil + } + + // tranLen represents the length of bytes that are part of the transaction + neededSize := minFieldLen + int(binary.BigEndian.Uint16(data[2:4])) + if neededSize > len(data) { + return 0, nil, nil + } + + return neededSize, data[0:neededSize], nil +} - bs := make([]byte, 2) - binary.BigEndian.PutUint16(bs, uint16(len(data))) +// Read implements io.Reader for Field +func (f *Field) Read(p []byte) (int, error) { + buf := slices.Concat(f.ID[:], f.FieldSize[:], f.Data) - return Field{ - ID: idBytes, - FieldSize: bs, - Data: data, + if f.readOffset >= len(buf) { + return 0, io.EOF // All bytes have been read } + + n := copy(p, buf[f.readOffset:]) + f.readOffset += n + + return n, nil } -func (f Field) Payload() []byte { - return concat.Slices(f.ID, f.FieldSize, f.Data) +// Write implements io.Writer for Field +func (f *Field) Write(p []byte) (int, error) { + f.ID = [2]byte(p[0:2]) + f.FieldSize = [2]byte(p[2:4]) + + i := int(binary.BigEndian.Uint16(f.FieldSize[:])) + f.Data = p[4 : 4+i] + + return minFieldLen + i, nil } func getField(id int, fields *[]Field) *Field { for _, field := range *fields { - if id == int(binary.BigEndian.Uint16(field.ID)) { + if id == int(binary.BigEndian.Uint16(field.ID[:])) { return &field } }