From: Jeff Halter Date: Sun, 9 Jun 2024 20:56:29 +0000 (-0700) Subject: Convert more bespoke methods to io.Reader/io.Writer interfaces X-Git-Url: https://git.r.bdr.sh/rbdr/mobius/commitdiff_plain/9cf66aeafbcbb9237fedc2efc97cc2856eb60f7f Convert more bespoke methods to io.Reader/io.Writer interfaces --- diff --git a/hotline/client.go b/hotline/client.go index e056438..3969e26 100644 --- a/hotline/client.go +++ b/hotline/client.go @@ -405,11 +405,12 @@ func handleClientGetUserNameList(ctx context.Context, c *Client, t *Transaction) // fields, but shxd sneaks in FieldChatSubject (115) so it's important to filter explicitly for the expected // field type. Probably a good idea to do everywhere. if bytes.Equal(field.ID, []byte{0x01, 0x2c}) { - u, err := ReadUser(field.Data) - if err != nil { - return res, err + var user User + if _, err := user.Write(field.Data); err != nil { + return res, fmt.Errorf("unable to read user data: %w", err) } - users = append(users, *u) + + users = append(users, user) } } c.UserList = users diff --git a/hotline/file_header.go b/hotline/file_header.go index 61b8e28..71a1c00 100644 --- a/hotline/file_header.go +++ b/hotline/file_header.go @@ -2,35 +2,36 @@ package hotline import ( "encoding/binary" + "io" "slices" ) type FileHeader struct { - Size []byte // Total size of FileHeader payload - Type []byte // 0 for file, 1 for dir - FilePath []byte // encoded file path + Size [2]byte // Total size of FileHeader payload + Type [2]byte // 0 for file, 1 for dir + FilePath []byte // encoded file path } func NewFileHeader(fileName string, isDir bool) FileHeader { fh := FileHeader{ - Size: make([]byte, 2), - Type: []byte{0x00, 0x00}, + Type: [2]byte{0x00, 0x00}, FilePath: EncodeFilePath(fileName), } if isDir { - fh.Type = []byte{0x00, 0x01} + fh.Type = [2]byte{0x00, 0x01} } encodedPathLen := uint16(len(fh.FilePath) + len(fh.Type)) - binary.BigEndian.PutUint16(fh.Size, encodedPathLen) + binary.BigEndian.PutUint16(fh.Size[:], encodedPathLen) return fh } -func (fh *FileHeader) Payload() []byte { - return slices.Concat( - fh.Size, - fh.Type, +func (fh *FileHeader) Read(p []byte) (int, error) { + return copy(p, slices.Concat( + fh.Size[:], + fh.Type[:], fh.FilePath, - ) + ), + ), io.EOF } diff --git a/hotline/file_header_test.go b/hotline/file_header_test.go index 99fe4e4..308d6b9 100644 --- a/hotline/file_header_test.go +++ b/hotline/file_header_test.go @@ -1,6 +1,7 @@ package hotline import ( + "io" "reflect" "testing" ) @@ -22,8 +23,8 @@ func TestNewFileHeader(t *testing.T) { isDir: false, }, want: FileHeader{ - Size: []byte{0x00, 0x0a}, - Type: []byte{0x00, 0x00}, + Size: [2]byte{0x00, 0x0a}, + Type: [2]byte{0x00, 0x00}, FilePath: EncodeFilePath("foo"), }, }, @@ -34,8 +35,8 @@ func TestNewFileHeader(t *testing.T) { isDir: true, }, want: FileHeader{ - Size: []byte{0x00, 0x0a}, - Type: []byte{0x00, 0x01}, + Size: [2]byte{0x00, 0x0a}, + Type: [2]byte{0x00, 0x01}, FilePath: EncodeFilePath("foo"), }, }, @@ -51,8 +52,8 @@ func TestNewFileHeader(t *testing.T) { func TestFileHeader_Payload(t *testing.T) { type fields struct { - Size []byte - Type []byte + Size [2]byte + Type [2]byte FilePath []byte } tests := []struct { @@ -63,8 +64,8 @@ func TestFileHeader_Payload(t *testing.T) { { name: "has expected payload bytes", fields: fields{ - Size: []byte{0x00, 0x0a}, - Type: []byte{0x00, 0x00}, + Size: [2]byte{0x00, 0x0a}, + Type: [2]byte{0x00, 0x00}, FilePath: EncodeFilePath("foo"), }, want: []byte{ @@ -84,7 +85,8 @@ func TestFileHeader_Payload(t *testing.T) { Type: tt.fields.Type, FilePath: tt.fields.FilePath, } - if got := fh.Payload(); !reflect.DeepEqual(got, tt.want) { + got, _ := io.ReadAll(fh) + if !reflect.DeepEqual(got, tt.want) { t.Errorf("Read() = %v, want %v", got, tt.want) } }) diff --git a/hotline/file_wrapper.go b/hotline/file_wrapper.go index 2c796ca..ec025dc 100644 --- a/hotline/file_wrapper.go +++ b/hotline/file_wrapper.go @@ -1,6 +1,7 @@ package hotline import ( + "bytes" "encoding/binary" "errors" "fmt" @@ -243,9 +244,11 @@ func (f *fileWrapper) flattenedFileObject() (*flattenedFileObject, error) { f.ffo.FlatFileHeader.ForkCount[1] = 3 - if err := f.ffo.FlatFileInformationFork.UnmarshalBinary(b); err != nil { + _, err = io.Copy(&f.ffo.FlatFileInformationFork, bytes.NewReader(b)) + if err != nil { return nil, err } + } else { f.ffo.FlatFileInformationFork = FlatFileInformationFork{ Platform: []byte("AMAC"), // TODO: Remove hardcode to support "AWIN" Platform (maybe?) diff --git a/hotline/flattened_file_object.go b/hotline/flattened_file_object.go index c57de19..dfa86e0 100644 --- a/hotline/flattened_file_object.go +++ b/hotline/flattened_file_object.go @@ -1,8 +1,10 @@ package hotline import ( + "bytes" "encoding/binary" "io" + "slices" ) type flattenedFileObject struct { @@ -101,7 +103,8 @@ func (ffif *FlatFileInformationFork) Size() [4]byte { func (ffo *flattenedFileObject) TransferSize(offset int64) []byte { // get length of the flattenedFileObject, including the info fork - payloadSize := len(ffo.BinaryMarshal()) + b, _ := io.ReadAll(ffo) + payloadSize := len(b) // length of data fork dataSize := binary.BigEndian.Uint32(ffo.FlatFileDataForkHeader.DataSize[:]) @@ -129,23 +132,57 @@ type FlatFileForkHeader struct { DataSize [4]byte } -func (ffif *FlatFileInformationFork) MarshalBinary() []byte { - var b []byte - b = append(b, ffif.Platform...) - b = append(b, ffif.TypeSignature...) - b = append(b, ffif.CreatorSignature...) - b = append(b, ffif.Flags...) - b = append(b, ffif.PlatformFlags...) - b = append(b, ffif.RSVD...) - b = append(b, ffif.CreateDate...) - b = append(b, ffif.ModifyDate...) - b = append(b, ffif.NameScript...) - b = append(b, ffif.ReadNameSize()...) - b = append(b, ffif.Name...) - b = append(b, ffif.CommentSize...) - b = append(b, ffif.Comment...) +func (ffif *FlatFileInformationFork) Read(p []byte) (int, error) { + return copy(p, + slices.Concat( + ffif.Platform, + ffif.TypeSignature, + ffif.CreatorSignature, + ffif.Flags, + ffif.PlatformFlags, + ffif.RSVD, + ffif.CreateDate, + ffif.ModifyDate, + ffif.NameScript, + ffif.ReadNameSize(), + ffif.Name, + ffif.CommentSize, + ffif.Comment, + ), + ), io.EOF +} + +// Write implements the io.Writeer interface for FlatFileInformationFork +func (ffif *FlatFileInformationFork) Write(p []byte) (int, error) { + nameSize := p[70:72] + bs := binary.BigEndian.Uint16(nameSize) + total := 72 + bs + + ffif.Platform = p[0:4] + ffif.TypeSignature = p[4:8] + ffif.CreatorSignature = p[8:12] + ffif.Flags = p[12:16] + ffif.PlatformFlags = p[16:20] + ffif.RSVD = p[20:52] + ffif.CreateDate = p[52:60] + ffif.ModifyDate = p[60:68] + ffif.NameScript = p[68:70] + ffif.NameSize = p[70:72] + ffif.Name = p[72:total] + + if len(p) > int(total) { + ffif.CommentSize = p[total : total+2] + commentLen := binary.BigEndian.Uint16(ffif.CommentSize) + + commentStartPos := int(total) + 2 + commentEndPos := int(total) + 2 + int(commentLen) + + ffif.Comment = p[commentStartPos:commentEndPos] - return b + total = uint16(commentEndPos) + } + + return int(total), nil } func (ffif *FlatFileInformationFork) UnmarshalBinary(b []byte) error { @@ -178,42 +215,40 @@ func (ffif *FlatFileInformationFork) UnmarshalBinary(b []byte) error { return nil } -func (ffo *flattenedFileObject) BinaryMarshal() []byte { - var out []byte - out = append(out, ffo.FlatFileHeader.Format[:]...) - out = append(out, ffo.FlatFileHeader.Version[:]...) - out = append(out, ffo.FlatFileHeader.RSVD[:]...) - out = append(out, ffo.FlatFileHeader.ForkCount[:]...) - - out = append(out, []byte("INFO")...) - out = append(out, []byte{0, 0, 0, 0}...) - out = append(out, make([]byte, 4)...) - out = append(out, ffo.FlatFileInformationFork.DataSize()...) - - out = append(out, ffo.FlatFileInformationFork.Platform...) - out = append(out, ffo.FlatFileInformationFork.TypeSignature...) - out = append(out, ffo.FlatFileInformationFork.CreatorSignature...) - out = append(out, ffo.FlatFileInformationFork.Flags...) - out = append(out, ffo.FlatFileInformationFork.PlatformFlags...) - out = append(out, ffo.FlatFileInformationFork.RSVD...) - out = append(out, ffo.FlatFileInformationFork.CreateDate...) - out = append(out, ffo.FlatFileInformationFork.ModifyDate...) - out = append(out, ffo.FlatFileInformationFork.NameScript...) - out = append(out, ffo.FlatFileInformationFork.ReadNameSize()...) - out = append(out, ffo.FlatFileInformationFork.Name...) - out = append(out, ffo.FlatFileInformationFork.CommentSize...) - out = append(out, ffo.FlatFileInformationFork.Comment...) - - out = append(out, ffo.FlatFileDataForkHeader.ForkType[:]...) - out = append(out, ffo.FlatFileDataForkHeader.CompressionType[:]...) - out = append(out, ffo.FlatFileDataForkHeader.RSVD[:]...) - out = append(out, ffo.FlatFileDataForkHeader.DataSize[:]...) - - return out +// Read implements the io.Reader interface for flattenedFileObject +func (ffo *flattenedFileObject) Read(p []byte) (int, error) { + return copy(p, slices.Concat( + ffo.FlatFileHeader.Format[:], + ffo.FlatFileHeader.Version[:], + ffo.FlatFileHeader.RSVD[:], + ffo.FlatFileHeader.ForkCount[:], + []byte("INFO"), + []byte{0, 0, 0, 0}, + make([]byte, 4), + ffo.FlatFileInformationFork.DataSize(), + ffo.FlatFileInformationFork.Platform, + ffo.FlatFileInformationFork.TypeSignature, + ffo.FlatFileInformationFork.CreatorSignature, + ffo.FlatFileInformationFork.Flags, + ffo.FlatFileInformationFork.PlatformFlags, + ffo.FlatFileInformationFork.RSVD, + ffo.FlatFileInformationFork.CreateDate, + ffo.FlatFileInformationFork.ModifyDate, + ffo.FlatFileInformationFork.NameScript, + ffo.FlatFileInformationFork.ReadNameSize(), + ffo.FlatFileInformationFork.Name, + ffo.FlatFileInformationFork.CommentSize, + ffo.FlatFileInformationFork.Comment, + ffo.FlatFileDataForkHeader.ForkType[:], + ffo.FlatFileDataForkHeader.CompressionType[:], + ffo.FlatFileDataForkHeader.RSVD[:], + ffo.FlatFileDataForkHeader.DataSize[:], + ), + ), io.EOF } -func (ffo *flattenedFileObject) ReadFrom(r io.Reader) (int, error) { - var n int +func (ffo *flattenedFileObject) ReadFrom(r io.Reader) (int64, error) { + var n int64 if err := binary.Read(r, binary.BigEndian, &ffo.FlatFileHeader); err != nil { return n, err @@ -229,7 +264,8 @@ func (ffo *flattenedFileObject) ReadFrom(r io.Reader) (int, error) { return n, err } - if err := ffo.FlatFileInformationFork.UnmarshalBinary(ffifBuf); err != nil { + _, err := io.Copy(&ffo.FlatFileInformationFork, bytes.NewReader(ffifBuf)) + if err != nil { return n, err } diff --git a/hotline/news.go b/hotline/news.go index 38db97a..8e13a29 100644 --- a/hotline/news.go +++ b/hotline/news.go @@ -4,6 +4,8 @@ import ( "bytes" "crypto/rand" "encoding/binary" + "io" + "slices" "sort" ) @@ -20,8 +22,8 @@ type ThreadedNews struct { } type NewsCategoryListData15 struct { - Type []byte `yaml:"Type"` // Size 2 ; Bundle (2) or category (3) - Count []byte // Article or SubCategory count Size 2 + Type [2]byte `yaml:"Type"` // Size 2 ; Bundle (2) or category (3) + Count []byte // Article or SubCategory count Size 2 NameSize byte Name string `yaml:"Name"` // Articles map[uint32]*NewsArtData `yaml:"Articles"` // Optional, if Type is Category @@ -36,11 +38,11 @@ func (newscat *NewsCategoryListData15) GetNewsArtListData() NewsArtListData { var newsArtsPayload []byte for i, art := range newscat.Articles { - ID := make([]byte, 4) - binary.BigEndian.PutUint32(ID, i) + id := make([]byte, 4) + binary.BigEndian.PutUint32(id, i) newArt := NewsArtList{ - ID: ID, + ID: id, TimeStamp: art.Date, ParentID: art.ParentArt, Flags: []byte{0, 0, 0, 0}, @@ -59,7 +61,7 @@ func (newscat *NewsCategoryListData15) GetNewsArtListData() NewsArtListData { } nald := NewsArtListData{ - ID: []byte{0, 0, 0, 0}, + ID: [4]byte{0, 0, 0, 0}, Count: len(newsArts), Name: []byte{}, Description: []byte{}, @@ -90,25 +92,30 @@ func (art *NewsArtData) DataSize() []byte { } type NewsArtListData struct { - ID []byte `yaml:"ID"` // Size 4 - Name []byte `yaml:"Name"` - Description []byte `yaml:"Description"` // not used? - NewsArtList []byte // List of articles Optional (if article count > 0) + ID [4]byte `yaml:"ID"` // Size 4 + Name []byte `yaml:"Name"` + Description []byte `yaml:"Description"` // not used? + NewsArtList []byte // List of articles Optional (if article count > 0) Count int } -func (nald *NewsArtListData) Payload() []byte { +func (nald *NewsArtListData) Read(p []byte) (int, error) { count := make([]byte, 4) binary.BigEndian.PutUint32(count, uint32(nald.Count)) - out := append(nald.ID, count...) - out = append(out, []byte{uint8(len(nald.Name))}...) - out = append(out, nald.Name...) - out = append(out, []byte{uint8(len(nald.Description))}...) - out = append(out, nald.Description...) - out = append(out, nald.NewsArtList...) - - return out + return copy( + p, + slices.Concat( + nald.ID[:], + count, + []byte{uint8(len(nald.Name))}, + nald.Name, + []byte{uint8(len(nald.Description))}, + nald.Description, + nald.NewsArtList, + ), + ), + io.EOF } // NewsArtList is a summarized version of a NewArtData record for display in list view @@ -167,9 +174,9 @@ func (newscat *NewsCategoryListData15) MarshalBinary() (data []byte, err error) count := make([]byte, 2) binary.BigEndian.PutUint16(count, uint16(len(newscat.Articles)+len(newscat.SubCats))) - out := append(newscat.Type, count...) + out := append(newscat.Type[:], count...) - if bytes.Equal(newscat.Type, []byte{0, 3}) { + if bytes.Equal(newscat.Type[:], []byte{0, 3}) { // Generate a random GUID // TODO: does this need to be random? b := make([]byte, 16) _, err := rand.Read(b) @@ -188,26 +195,6 @@ func (newscat *NewsCategoryListData15) MarshalBinary() (data []byte, err error) return out, err } -// ReadNewsCategoryListData parses a byte slice into a NewsCategoryListData15 struct -// For use on the client side -func ReadNewsCategoryListData(payload []byte) NewsCategoryListData15 { - ncld := NewsCategoryListData15{ - Type: payload[0:2], - Count: payload[2:4], - } - - if bytes.Equal(ncld.Type, []byte{0, 3}) { - ncld.GUID = payload[4:20] - ncld.AddSN = payload[20:24] - ncld.AddSN = payload[24:28] - ncld.Name = string(payload[29:]) - } else { - ncld.Name = string(payload[5:]) - } - - return ncld -} - func (newscat *NewsCategoryListData15) nameLen() []byte { return []byte{uint8(len(newscat.Name))} } diff --git a/hotline/news_test.go b/hotline/news_test.go index 02e11a9..44776dd 100644 --- a/hotline/news_test.go +++ b/hotline/news_test.go @@ -1,14 +1,13 @@ package hotline import ( - "bytes" "reflect" "testing" ) func TestNewsCategoryListData15_MarshalBinary(t *testing.T) { type fields struct { - Type []byte + Type [2]byte Name string Articles map[uint32]*NewsArtData SubCats map[string]NewsCategoryListData15 @@ -26,7 +25,7 @@ func TestNewsCategoryListData15_MarshalBinary(t *testing.T) { { name: "returns expected bytes when type is a bundle", fields: fields{ - Type: []byte{0x00, 0x02}, + Type: [2]byte{0x00, 0x02}, Articles: map[uint32]*NewsArtData{ uint32(1): { Title: "", @@ -47,7 +46,7 @@ func TestNewsCategoryListData15_MarshalBinary(t *testing.T) { { name: "returns expected bytes when type is a category", fields: fields{ - Type: []byte{0x00, 0x03}, + Type: [2]byte{0x00, 0x03}, Articles: map[uint32]*NewsArtData{ uint32(1): { Title: "", @@ -82,7 +81,7 @@ func TestNewsCategoryListData15_MarshalBinary(t *testing.T) { GUID: tt.fields.GUID, } gotData, err := newscat.MarshalBinary() - if bytes.Equal(newscat.Type, []byte{0, 3}) { + if newscat.Type == [2]byte{0, 3} { // zero out the random GUID before comparison for i := 4; i < 20; i++ { gotData[i] = 0 diff --git a/hotline/server.go b/hotline/server.go index 78b7a37..dde469d 100644 --- a/hotline/server.go +++ b/hotline/server.go @@ -494,13 +494,16 @@ func (s *Server) connectedUsers() []Field { var connectedUsers []Field for _, c := range sortedClients(s.Clients) { - user := User{ + b, err := io.ReadAll(&User{ ID: *c.ID, Icon: c.Icon, Flags: c.Flags, Name: string(c.UserName), + }) + if err != nil { + return nil } - connectedUsers = append(connectedUsers, NewField(FieldUsernameWithInfo, user.Payload())) + connectedUsers = append(connectedUsers, NewField(FieldUsernameWithInfo, b)) } return connectedUsers } @@ -861,8 +864,8 @@ func (s *Server) handleFileTransfer(ctx context.Context, rwc io.ReadWriter) erro // if file transfer options are included, that means this is a "quick preview" request from a 1.5+ client if fileTransfer.options == nil { - // Start by sending flat file object to client - if _, err := rwc.Write(fw.ffo.BinaryMarshal()); err != nil { + _, err = io.Copy(rwc, fw.ffo) + if err != nil { return err } } @@ -1027,11 +1030,8 @@ func (s *Server) handleFileTransfer(ctx context.Context, rwc io.ReadWriter) erro } fileHeader := NewFileHeader(subPath, info.IsDir()) - - // Send the fileWrapper header to client - if _, err := rwc.Write(fileHeader.Payload()); err != nil { - s.Logger.Errorf("error sending file header: %v", err) - return err + if _, err := io.Copy(rwc, &fileHeader); err != nil { + return fmt.Errorf("error sending file header: %w", err) } // Read the client's Next Action request @@ -1083,8 +1083,8 @@ func (s *Server) handleFileTransfer(ctx context.Context, rwc io.ReadWriter) erro } // Send ffo bytes to client - if _, err := rwc.Write(hlFile.ffo.BinaryMarshal()); err != nil { - s.Logger.Error(err) + _, err = io.Copy(rwc, hlFile.ffo) + if err != nil { return err } diff --git a/hotline/transaction_handlers.go b/hotline/transaction_handlers.go index 793bb74..4bb956a 100644 --- a/hotline/transaction_handlers.go +++ b/hotline/transaction_handlers.go @@ -460,7 +460,7 @@ func HandleSetFileInfo(cc *ClientConn, t *Transaction) (res []Transaction, err e if err != nil { return res, err } - _, err = w.Write(hlFile.ffo.FlatFileInformationFork.MarshalBinary()) + _, err = io.Copy(w, &hlFile.ffo.FlatFileInformationFork) if err != nil { return res, err } @@ -1194,7 +1194,7 @@ func HandleNewNewsCat(cc *ClientConn, t *Transaction) (res []Transaction, err er cats := cc.Server.GetNewsCatByPath(pathStrs) cats[name] = NewsCategoryListData15{ Name: name, - Type: []byte{0, 3}, + Type: [2]byte{0, 3}, Articles: map[uint32]*NewsArtData{}, SubCats: make(map[string]NewsCategoryListData15), } @@ -1223,7 +1223,7 @@ func HandleNewNewsFldr(cc *ClientConn, t *Transaction) (res []Transaction, err e cats := cc.Server.GetNewsCatByPath(pathStrs) cats[name] = NewsCategoryListData15{ Name: name, - Type: []byte{0, 2}, + Type: [2]byte{0, 2}, Articles: map[uint32]*NewsArtData{}, SubCats: make(map[string]NewsCategoryListData15), } @@ -1258,7 +1258,12 @@ func HandleGetNewsArtNameList(cc *ClientConn, t *Transaction) (res []Transaction nald := cat.GetNewsArtListData() - res = append(res, cc.NewReply(t, NewField(FieldNewsArtListData, nald.Payload()))) + b, err := io.ReadAll(&nald) + if err != nil { + + } + + res = append(res, cc.NewReply(t, NewField(FieldNewsArtListData, b))) return res, err } @@ -1337,7 +1342,7 @@ func HandleDelNewsItem(cc *ClientConn, t *Transaction) (res []Transaction, err e } } - if bytes.Equal(cats[delName].Type, []byte{0, 3}) { + if cats[delName].Type == [2]byte{0, 3} { if !cc.Authorize(accessNewsDeleteCat) { return append(res, cc.NewErrReply(t, "You are not allowed to delete news categories.")), nil } @@ -1918,14 +1923,17 @@ func HandleJoinChat(cc *ClientConn, t *Transaction) (res []Transaction, err erro replyFields := []Field{NewField(FieldChatSubject, []byte(privChat.Subject))} for _, c := range sortedClients(privChat.ClientConn) { - user := User{ + + b, err := io.ReadAll(&User{ ID: *c.ID, Icon: c.Icon, Flags: c.Flags, Name: string(c.UserName), + }) + if err != nil { + return res, nil } - - replyFields = append(replyFields, NewField(FieldUsernameWithInfo, user.Payload())) + replyFields = append(replyFields, NewField(FieldUsernameWithInfo, b)) } res = append(res, cc.NewReply(t, replyFields...)) diff --git a/hotline/transaction_handlers_test.go b/hotline/transaction_handlers_test.go index 94854f2..ca1722f 100644 --- a/hotline/transaction_handlers_test.go +++ b/hotline/transaction_handlers_test.go @@ -3191,7 +3191,7 @@ func TestHandleDelNewsItem(t *testing.T) { Server: &Server{ ThreadedNews: &ThreadedNews{Categories: map[string]NewsCategoryListData15{ "test": { - Type: []byte{0, 3}, + Type: [2]byte{0, 3}, Count: nil, NameSize: 0, Name: "zz", @@ -3237,7 +3237,7 @@ func TestHandleDelNewsItem(t *testing.T) { Server: &Server{ ThreadedNews: &ThreadedNews{Categories: map[string]NewsCategoryListData15{ "testcat": { - Type: []byte{0, 2}, + Type: [2]byte{0, 2}, Count: nil, NameSize: 0, Name: "test", @@ -3293,7 +3293,7 @@ func TestHandleDelNewsItem(t *testing.T) { }(), ThreadedNews: &ThreadedNews{Categories: map[string]NewsCategoryListData15{ "testcat": { - Type: []byte{0, 2}, + Type: [2]byte{0, 2}, Count: nil, NameSize: 0, Name: "test", @@ -3847,7 +3847,7 @@ func TestHandleNewNewsFldr(t *testing.T) { }(), ThreadedNews: &ThreadedNews{Categories: map[string]NewsCategoryListData15{ "test": { - Type: []byte{0, 2}, + Type: [2]byte{0, 2}, Count: nil, NameSize: 0, Name: "test", diff --git a/hotline/transfer.go b/hotline/transfer.go index 2acb5d6..543b00c 100644 --- a/hotline/transfer.go +++ b/hotline/transfer.go @@ -37,7 +37,7 @@ func receiveFile(r io.Reader, targetFile, resForkFile, infoFork, counterWriter i } // Write the information fork - _, err := infoFork.Write(ffo.FlatFileInformationFork.MarshalBinary()) + _, err := io.Copy(infoFork, &ffo.FlatFileInformationFork) if err != nil { return err } @@ -58,6 +58,7 @@ func receiveFile(r io.Reader, targetFile, resForkFile, infoFork, counterWriter i return nil } +// TODO: read the banner once on startup instead of once per banner fetch func (s *Server) bannerDownload(w io.Writer) error { bannerBytes, err := os.ReadFile(filepath.Join(s.ConfigDir, s.Config.BannerFile)) if err != nil { diff --git a/hotline/transfer_test.go b/hotline/transfer_test.go index fb3e0da..2bcee11 100644 --- a/hotline/transfer_test.go +++ b/hotline/transfer_test.go @@ -135,7 +135,7 @@ func Test_receiveFile(t *testing.T) { }, } fakeFileData := []byte{1, 2, 3} - b := testFile.BinaryMarshal() + b, _ := io.ReadAll(&testFile) b = append(b, fakeFileData...) return bytes.NewReader(b) }(), diff --git a/hotline/user.go b/hotline/user.go index 5b1b705..7d6f851 100644 --- a/hotline/user.go +++ b/hotline/user.go @@ -2,6 +2,8 @@ package hotline import ( "encoding/binary" + "io" + "slices" ) // User flags are stored as a 2 byte bitmap and represent various user states @@ -26,7 +28,7 @@ type User struct { Name string // Variable length user name } -func (u User) Payload() []byte { +func (u *User) Read(p []byte) (int, error) { nameLen := make([]byte, 2) binary.BigEndian.PutUint16(nameLen, uint16(len(u.Name))) @@ -43,17 +45,23 @@ func (u User) Payload() []byte { out = append(out, nameLen...) out = append(out, u.Name...) - return out + return copy(p, slices.Concat( + u.ID, + u.Icon, + u.Flags, + nameLen, + []byte(u.Name), + )), io.EOF } -func ReadUser(b []byte) (*User, error) { - u := &User{ - ID: b[0:2], - Icon: b[2:4], - Flags: b[4:6], - Name: string(b[8:]), - } - return u, nil +func (u *User) Write(p []byte) (int, error) { + namelen := int(binary.BigEndian.Uint16(p[6:8])) + u.ID = p[0:2] + u.Icon = p[2:4] + u.Flags = p[4:6] + u.Name = string(p[8 : 8+namelen]) + + return 8 + namelen, nil } // decodeString decodes an obfuscated user string from a client diff --git a/hotline/user_test.go b/hotline/user_test.go index 214b1fb..6258316 100644 --- a/hotline/user_test.go +++ b/hotline/user_test.go @@ -44,13 +44,14 @@ func TestReadUser(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := ReadUser(tt.args.b) + var user User + _, err := user.Write(tt.args.b) if (err != nil) != tt.wantErr { t.Errorf("ReadUser() error = %v, wantErr %v", err, tt.wantErr) return } - if !assert.Equal(t, tt.want, got) { - t.Errorf("ReadUser() got = %v, want %v", got, tt.want) + if !assert.Equal(t, tt.want, &user) { + t.Errorf("ReadUser() got = %v, want %v", user, tt.want) } }) }