diff options
| author | Jeff Halter <868228+jhalter@users.noreply.github.com> | 2025-06-30 13:31:56 -0700 |
|---|---|---|
| committer | Jeff Halter <868228+jhalter@users.noreply.github.com> | 2025-06-30 13:31:56 -0700 |
| commit | 437415ca92a98783d03e71c689bdbb58fe0a8d51 (patch) | |
| tree | 0fcba8be50cc1fee3e26a9bc80ff3c81330bf9e1 /hotline | |
| parent | 2bef32a36300e64106a27851fe1eab349d2dbeef (diff) | |
Add type safety to field system with FieldType alias
- Define FieldType as typed alias for [2]byte to improve type safety
- Update all 47 field constants to use FieldType instead of raw [2]byte
- Update Field struct to use FieldType for Type field
- Update function signatures: NewField, GetField, Transaction.GetField
- Fix field_test.go to use new FieldType in test cases
- Maintains backward compatibility with zero runtime overhead
- Enhances API clarity and prevents accidental field type misuse
Diffstat (limited to 'hotline')
| -rw-r--r-- | hotline/field.go | 125 | ||||
| -rw-r--r-- | hotline/field_test.go | 10 | ||||
| -rw-r--r-- | hotline/transaction.go | 2 |
3 files changed, 70 insertions, 67 deletions
diff --git a/hotline/field.go b/hotline/field.go index 65ff394..1e6fc16 100644 --- a/hotline/field.go +++ b/hotline/field.go @@ -9,77 +9,80 @@ import ( "slices" ) +// FieldType represents a Hotline protocol field type identifier +type FieldType [2]byte + // List of Hotline protocol field types taken from the official 1.9 protocol document var ( - FieldError = [2]byte{0x00, 0x64} // 100 - FieldData = [2]byte{0x00, 0x65} // 101 - FieldUserName = [2]byte{0x00, 0x66} // 102 - FieldUserID = [2]byte{0x00, 0x67} // 103 - FieldUserIconID = [2]byte{0x00, 0x68} // 104 - FieldUserLogin = [2]byte{0x00, 0x69} // 105 - FieldUserPassword = [2]byte{0x00, 0x6A} // 106 - FieldRefNum = [2]byte{0x00, 0x6B} // 107 - FieldTransferSize = [2]byte{0x00, 0x6C} // 108 - FieldChatOptions = [2]byte{0x00, 0x6D} // 109 - FieldUserAccess = [2]byte{0x00, 0x6E} // 110 - FieldUserFlags = [2]byte{0x00, 0x70} // 112 - FieldOptions = [2]byte{0x00, 0x71} // 113 - FieldChatID = [2]byte{0x00, 0x72} // 114 - FieldChatSubject = [2]byte{0x00, 0x73} // 115 - FieldWaitingCount = [2]byte{0x00, 0x74} // 116 - FieldBannerType = [2]byte{0x00, 0x98} // 152 - FieldNoServerAgreement = [2]byte{0x00, 0x98} // 152 - FieldVersion = [2]byte{0x00, 0xA0} // 160 - FieldCommunityBannerID = [2]byte{0x00, 0xA1} // 161 - FieldServerName = [2]byte{0x00, 0xA2} // 162 - FieldFileNameWithInfo = [2]byte{0x00, 0xC8} // 200 - FieldFileName = [2]byte{0x00, 0xC9} // 201 - FieldFilePath = [2]byte{0x00, 0xCA} // 202 - FieldFileResumeData = [2]byte{0x00, 0xCB} // 203 - FieldFileTransferOptions = [2]byte{0x00, 0xCC} // 204 - FieldFileTypeString = [2]byte{0x00, 0xCD} // 205 - FieldFileCreatorString = [2]byte{0x00, 0xCE} // 206 - FieldFileSize = [2]byte{0x00, 0xCF} // 207 - FieldFileCreateDate = [2]byte{0x00, 0xD0} // 208 - FieldFileModifyDate = [2]byte{0x00, 0xD1} // 209 - FieldFileComment = [2]byte{0x00, 0xD2} // 210 - FieldFileNewName = [2]byte{0x00, 0xD3} // 211 - FieldFileNewPath = [2]byte{0x00, 0xD4} // 212 - FieldFileType = [2]byte{0x00, 0xD5} // 213 - FieldQuotingMsg = [2]byte{0x00, 0xD6} // 214 - FieldAutomaticResponse = [2]byte{0x00, 0xD7} // 215 - FieldFolderItemCount = [2]byte{0x00, 0xDC} // 220 - FieldUsernameWithInfo = [2]byte{0x01, 0x2C} // 300 - FieldNewsArtListData = [2]byte{0x01, 0x41} // 321 - FieldNewsCatName = [2]byte{0x01, 0x42} // 322 - FieldNewsCatListData15 = [2]byte{0x01, 0x43} // 323 - FieldNewsPath = [2]byte{0x01, 0x45} // 325 - FieldNewsArtID = [2]byte{0x01, 0x46} // 326 - FieldNewsArtDataFlav = [2]byte{0x01, 0x47} // 327 - FieldNewsArtTitle = [2]byte{0x01, 0x48} // 328 - FieldNewsArtPoster = [2]byte{0x01, 0x49} // 329 - FieldNewsArtDate = [2]byte{0x01, 0x4A} // 330 - FieldNewsArtPrevArt = [2]byte{0x01, 0x4B} // 331 - FieldNewsArtNextArt = [2]byte{0x01, 0x4C} // 332 - FieldNewsArtData = [2]byte{0x01, 0x4D} // 333 - FieldNewsArtParentArt = [2]byte{0x01, 0x4F} // 335 - FieldNewsArt1stChildArt = [2]byte{0x01, 0x50} // 336 - FieldNewsArtRecurseDel = [2]byte{0x01, 0x51} // 337 + FieldError = FieldType{0x00, 0x64} // 100 + FieldData = FieldType{0x00, 0x65} // 101 + FieldUserName = FieldType{0x00, 0x66} // 102 + FieldUserID = FieldType{0x00, 0x67} // 103 + FieldUserIconID = FieldType{0x00, 0x68} // 104 + FieldUserLogin = FieldType{0x00, 0x69} // 105 + FieldUserPassword = FieldType{0x00, 0x6A} // 106 + FieldRefNum = FieldType{0x00, 0x6B} // 107 + FieldTransferSize = FieldType{0x00, 0x6C} // 108 + FieldChatOptions = FieldType{0x00, 0x6D} // 109 + FieldUserAccess = FieldType{0x00, 0x6E} // 110 + FieldUserFlags = FieldType{0x00, 0x70} // 112 + FieldOptions = FieldType{0x00, 0x71} // 113 + FieldChatID = FieldType{0x00, 0x72} // 114 + FieldChatSubject = FieldType{0x00, 0x73} // 115 + FieldWaitingCount = FieldType{0x00, 0x74} // 116 + FieldBannerType = FieldType{0x00, 0x98} // 152 + FieldNoServerAgreement = FieldType{0x00, 0x98} // 152 + FieldVersion = FieldType{0x00, 0xA0} // 160 + FieldCommunityBannerID = FieldType{0x00, 0xA1} // 161 + FieldServerName = FieldType{0x00, 0xA2} // 162 + FieldFileNameWithInfo = FieldType{0x00, 0xC8} // 200 + FieldFileName = FieldType{0x00, 0xC9} // 201 + FieldFilePath = FieldType{0x00, 0xCA} // 202 + FieldFileResumeData = FieldType{0x00, 0xCB} // 203 + FieldFileTransferOptions = FieldType{0x00, 0xCC} // 204 + FieldFileTypeString = FieldType{0x00, 0xCD} // 205 + FieldFileCreatorString = FieldType{0x00, 0xCE} // 206 + FieldFileSize = FieldType{0x00, 0xCF} // 207 + FieldFileCreateDate = FieldType{0x00, 0xD0} // 208 + FieldFileModifyDate = FieldType{0x00, 0xD1} // 209 + FieldFileComment = FieldType{0x00, 0xD2} // 210 + FieldFileNewName = FieldType{0x00, 0xD3} // 211 + FieldFileNewPath = FieldType{0x00, 0xD4} // 212 + FieldFileType = FieldType{0x00, 0xD5} // 213 + FieldQuotingMsg = FieldType{0x00, 0xD6} // 214 + FieldAutomaticResponse = FieldType{0x00, 0xD7} // 215 + FieldFolderItemCount = FieldType{0x00, 0xDC} // 220 + FieldUsernameWithInfo = FieldType{0x01, 0x2C} // 300 + FieldNewsArtListData = FieldType{0x01, 0x41} // 321 + FieldNewsCatName = FieldType{0x01, 0x42} // 322 + FieldNewsCatListData15 = FieldType{0x01, 0x43} // 323 + FieldNewsPath = FieldType{0x01, 0x45} // 325 + FieldNewsArtID = FieldType{0x01, 0x46} // 326 + FieldNewsArtDataFlav = FieldType{0x01, 0x47} // 327 + FieldNewsArtTitle = FieldType{0x01, 0x48} // 328 + FieldNewsArtPoster = FieldType{0x01, 0x49} // 329 + FieldNewsArtDate = FieldType{0x01, 0x4A} // 330 + FieldNewsArtPrevArt = FieldType{0x01, 0x4B} // 331 + FieldNewsArtNextArt = FieldType{0x01, 0x4C} // 332 + FieldNewsArtData = FieldType{0x01, 0x4D} // 333 + FieldNewsArtParentArt = FieldType{0x01, 0x4F} // 335 + FieldNewsArt1stChildArt = FieldType{0x01, 0x50} // 336 + FieldNewsArtRecurseDel = FieldType{0x01, 0x51} // 337 // These fields are documented, but seemingly unused. - // FieldUserAlias = [2]byte{0x00, 0x6F} // 111 - // FieldNewsArtFlags = [2]byte{0x01, 0x4E} // 334 + // FieldUserAlias = FieldType{0x00, 0x6F} // 111 + // FieldNewsArtFlags = FieldType{0x01, 0x4E} // 334 ) type Field struct { - Type [2]byte // Type of field - FieldSize [2]byte // Size of the data field - Data []byte // Field data + Type FieldType // Type of field + FieldSize [2]byte // Size of the data field + Data []byte // Field data readOffset int // Internal offset to track read progress } -func NewField(fieldType [2]byte, data []byte) Field { +func NewField(fieldType FieldType, data []byte) Field { f := Field{ Type: fieldType, Data: make([]byte, len(data)), @@ -185,7 +188,7 @@ func (f *Field) Write(p []byte) (int, error) { return minFieldLen + dataSize, nil } -func GetField(id [2]byte, fields *[]Field) *Field { +func GetField(id FieldType, fields *[]Field) *Field { for _, field := range *fields { if id == field.Type { return &field diff --git a/hotline/field_test.go b/hotline/field_test.go index 213d9c3..676098c 100644 --- a/hotline/field_test.go +++ b/hotline/field_test.go @@ -102,7 +102,7 @@ func Test_fieldScanner(t *testing.T) { func TestField_Read(t *testing.T) { type fields struct { - ID [2]byte + Type FieldType FieldSize [2]byte Data []byte readOffset int @@ -121,7 +121,7 @@ func TestField_Read(t *testing.T) { { name: "returns field bytes", fields: fields{ - ID: [2]byte{0x00, 0x62}, + Type: FieldType{0x00, 0x62}, FieldSize: [2]byte{0x00, 0x03}, Data: []byte("hai!"), }, @@ -139,7 +139,7 @@ func TestField_Read(t *testing.T) { { name: "returns field bytes from readOffset", fields: fields{ - ID: [2]byte{0x00, 0x62}, + Type: FieldType{0x00, 0x62}, FieldSize: [2]byte{0x00, 0x03}, Data: []byte("hai!"), readOffset: 4, @@ -156,7 +156,7 @@ func TestField_Read(t *testing.T) { { name: "returns io.EOF when all bytes read", fields: fields{ - ID: [2]byte{0x00, 0x62}, + Type: FieldType{0x00, 0x62}, FieldSize: [2]byte{0x00, 0x03}, Data: []byte("hai!"), readOffset: 8, @@ -172,7 +172,7 @@ func TestField_Read(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { f := &Field{ - Type: tt.fields.ID, + Type: tt.fields.Type, FieldSize: tt.fields.FieldSize, Data: tt.fields.Data, readOffset: tt.fields.readOffset, diff --git a/hotline/transaction.go b/hotline/transaction.go index fa1e96d..a03030d 100644 --- a/hotline/transaction.go +++ b/hotline/transaction.go @@ -275,7 +275,7 @@ func (t *Transaction) Size() []byte { return bs } -func (t *Transaction) GetField(id [2]byte) *Field { +func (t *Transaction) GetField(id FieldType) *Field { for _, field := range t.Fields { if id == field.Type { return &field |