]> git.r.bdr.sh - rbdr/mobius/commitdiff
Convert more bespoke methods to io.Reader/io.Writer interfaces
authorJeff Halter <redacted>
Sun, 9 Jun 2024 20:56:29 +0000 (13:56 -0700)
committerJeff Halter <redacted>
Sun, 9 Jun 2024 21:05:15 +0000 (14:05 -0700)
14 files changed:
hotline/client.go
hotline/file_header.go
hotline/file_header_test.go
hotline/file_wrapper.go
hotline/flattened_file_object.go
hotline/news.go
hotline/news_test.go
hotline/server.go
hotline/transaction_handlers.go
hotline/transaction_handlers_test.go
hotline/transfer.go
hotline/transfer_test.go
hotline/user.go
hotline/user_test.go

index e056438fbdef7c1ddfcad4d0043508c75f52da7c..3969e2633e6667f56f15995705998fd33e7945a6 100644 (file)
@@ -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
index 61b8e288f4a8d79fb3ee3d42d51b43f9a993c08a..71a1c003044199913a553d14f6d41f50429a3521 100644 (file)
@@ -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
 }
index 99fe4e46231b3719e27a8cc70de81fe28e69b68a..308d6b9525046aa970f68d7840ebfc4bb84376be 100644 (file)
@@ -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)
                        }
                })
index 2c796ca05b2254c64728b74f2dca7f2ddcd1f6da..ec025dc006183dad1d28048a3e6587eae0b2413a 100644 (file)
@@ -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?)
index c57de19fea4063a91e5f5b9a8e1a27cf009550f7..dfa86e091a06fc2fc73f6973b3362e0678ad9439 100644 (file)
@@ -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
        }
 
index 38db97af42dee4f11a8731e0cdb740674a01b79b..8e13a297ab212c114bdf79d3a4b2536b2e929e6f 100644 (file)
@@ -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))}
 }
index 02e11a9e5ec29456cd429f5d5fc6af697a27d12b..44776dd3723a0eee0f5631be3d594d1fb19f306e 100644 (file)
@@ -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
index 78b7a371befa7a69041161848c91e900b678a5f4..dde469d8b63555d9c4ddaee664bedccf680b1ceb 100644 (file)
@@ -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
                        }
 
index 793bb7420cd234191b875ddf94c063959b21cec7..4bb956aabbc1cdcf5aa948b5bd742edd026ed34b 100644 (file)
@@ -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...))
index 94854f2f68d89ca825d23c8eb17ad07ed2f920c5..ca1722faa10342ed976806687261b476d9d1667a 100644 (file)
@@ -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",
index 2acb5d6b6e6b689c59e776daca8db8e2ef2f5f28..543b00c727240ea1e43890e95d2093f23f36263c 100644 (file)
@@ -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 {
index fb3e0da11b8628e538a305f79d27dbf28760fd34..2bcee11ae7f5b3106ce2c31ec133665afdcff5f5 100644 (file)
@@ -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)
                                }(),
index 5b1b705b0c718cad09b9cc8ab8631327e8100ffc..7d6f85118d5b06e154cb4c73e14de0f2338179f5 100644 (file)
@@ -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
index 214b1fbbcf3e6cb35555978e788353bde5859248..6258316f94d5f3186baa696008009d82d4e14acb 100644 (file)
@@ -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)
                        }
                })
        }