From: Jeff Halter Date: Sun, 16 Jun 2024 23:03:54 +0000 (-0700) Subject: Fix broken io.Reader implementations X-Git-Url: https://git.r.bdr.sh/rbdr/mobius/commitdiff_plain/0ed5132769e88cb385b5240986b706430f0ccd72?ds=sidebyside Fix broken io.Reader implementations --- diff --git a/hotline/account.go b/hotline/account.go index 3ad0687..18965ed 100644 --- a/hotline/account.go +++ b/hotline/account.go @@ -15,7 +15,7 @@ type Account struct { Login string `yaml:"Login"` Name string `yaml:"Name"` Password string `yaml:"Password"` - Access accessBitmap `yaml:"Access"` + Access accessBitmap `yaml:"Access,flow"` readOffset int // Internal offset to track read progress } diff --git a/hotline/client_conn.go b/hotline/client_conn.go index 8e66218..e21d8b1 100644 --- a/hotline/client_conn.go +++ b/hotline/client_conn.go @@ -167,7 +167,6 @@ func (cc *ClientConn) notifyOthers(t Transaction) (trans []Transaction) { // NewReply returns a reply Transaction with fields for the ClientConn func (cc *ClientConn) NewReply(t *Transaction, fields ...Field) Transaction { return Transaction{ - Flags: 0x00, IsReply: 0x01, Type: []byte{0x00, 0x00}, ID: t.ID, @@ -181,7 +180,6 @@ func (cc *ClientConn) NewReply(t *Transaction, fields ...Field) Transaction { func (cc *ClientConn) NewErrReply(t *Transaction, errMsg string) Transaction { return Transaction{ clientID: cc.ID, - Flags: 0x00, IsReply: 0x01, Type: []byte{0, 0}, ID: t.ID, diff --git a/hotline/file_transfer.go b/hotline/file_transfer.go index ba4a1e1..7c24109 100644 --- a/hotline/file_transfer.go +++ b/hotline/file_transfer.go @@ -11,11 +11,11 @@ import ( // File transfer types const ( - FileDownload = 0 - FileUpload = 1 - FolderDownload = 2 - FolderUpload = 3 - bannerDownload = 4 + FileDownload = iota + FileUpload + FolderDownload + FolderUpload + bannerDownload ) type FileTransfer struct { diff --git a/hotline/news.go b/hotline/news.go index e6b7567..cd3b6af 100644 --- a/hotline/news.go +++ b/hotline/news.go @@ -2,7 +2,6 @@ package hotline import ( "bytes" - "crypto/rand" "encoding/binary" "io" "slices" @@ -17,20 +16,19 @@ const defaultNewsTemplate = `From %s (%s): __________________________________________________________` +// ThreadedNews is the top level struct containing all threaded news categories, bundles, and articles type ThreadedNews struct { Categories map[string]NewsCategoryListData15 `yaml:"Categories"` } type NewsCategoryListData15 struct { - 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"` // + Type [2]byte `yaml:"Type,flow"` // Bundle (2) or category (3) + Name string `yaml:"Name"` Articles map[uint32]*NewsArtData `yaml:"Articles"` // Optional, if Type is Category SubCats map[string]NewsCategoryListData15 `yaml:"SubCats"` - GUID []byte // Size 16 - AddSN []byte // Size 4 - DeleteSN []byte // Size 4 + GUID [16]byte `yaml:"-"` // What does this do? Undocumented and seeming unused. + AddSN [4]byte `yaml:"-"` // What does this do? Undocumented and seeming unused. + DeleteSN [4]byte `yaml:"-"` // What does this do? Undocumented and seeming unused. } func (newscat *NewsCategoryListData15) GetNewsArtListData() NewsArtListData { @@ -42,15 +40,14 @@ func (newscat *NewsCategoryListData15) GetNewsArtListData() NewsArtListData { binary.BigEndian.PutUint32(id, i) newArt := NewsArtList{ - ID: id, - TimeStamp: art.Date[:], - ParentID: art.ParentArt[:], - Flags: []byte{0, 0, 0, 0}, - FlavorCount: []byte{0, 0}, + ID: [4]byte(id), + TimeStamp: art.Date, + ParentID: art.ParentArt, Title: []byte(art.Title), Poster: []byte(art.Poster), ArticleSize: art.DataSize(), } + newsArts = append(newsArts, newArt) } @@ -60,31 +57,29 @@ func (newscat *NewsCategoryListData15) GetNewsArtListData() NewsArtListData { b, err := io.ReadAll(&v) if err != nil { // TODO + panic(err) } newsArtsPayload = append(newsArtsPayload, b...) } - nald := NewsArtListData{ - ID: [4]byte{0, 0, 0, 0}, + return NewsArtListData{ Count: len(newsArts), Name: []byte{}, Description: []byte{}, NewsArtList: newsArtsPayload, } - - return nald } // NewsArtData represents single news article type NewsArtData struct { Title string `yaml:"Title"` Poster string `yaml:"Poster"` - Date [8]byte `yaml:"Date"` // size 8 - PrevArt [4]byte `yaml:"PrevArt"` // size 4 - NextArt [4]byte `yaml:"NextArt"` // size 4 - ParentArt [4]byte `yaml:"ParentArt"` // size 4 - FirstChildArt [4]byte `yaml:"FirstChildArtArt"` // size 4 - DataFlav []byte `yaml:"DataFlav"` // "text/plain" + Date [8]byte `yaml:"Date,flow"` + PrevArt [4]byte `yaml:"PrevArt,flow"` + NextArt [4]byte `yaml:"NextArt,flow"` + ParentArt [4]byte `yaml:"ParentArt,flow"` + FirstChildArt [4]byte `yaml:"FirstChildArtArt,flow"` + DataFlav []byte `yaml:"-"` // "text/plain" Data string `yaml:"Data"` } @@ -96,39 +91,45 @@ func (art *NewsArtData) DataSize() []byte { } type NewsArtListData struct { - ID [4]byte `yaml:"ID"` // Size 4 + ID [4]byte `yaml:"ID"` Name []byte `yaml:"Name"` Description []byte `yaml:"Description"` // not used? NewsArtList []byte // List of articles Optional (if article count > 0) Count int + + readOffset int // Internal offset to track read progress } func (nald *NewsArtListData) Read(p []byte) (int, error) { count := make([]byte, 4) binary.BigEndian.PutUint32(count, uint32(nald.Count)) - 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 + buf := slices.Concat( + nald.ID[:], + count, + []byte{uint8(len(nald.Name))}, + nald.Name, + []byte{uint8(len(nald.Description))}, + nald.Description, + nald.NewsArtList, + ) + + if nald.readOffset >= len(buf) { + return 0, io.EOF // All bytes have been read + } + n := copy(p, buf[nald.readOffset:]) + nald.readOffset += n + + return n, nil } // NewsArtList is a summarized version of a NewArtData record for display in list view type NewsArtList struct { - ID []byte // Size 4 - TimeStamp []byte // Year (2 bytes), milliseconds (2 bytes) and seconds (4 bytes) - ParentID []byte // Size 4 - Flags []byte // Size 4 - FlavorCount []byte // Size 2 + ID [4]byte + TimeStamp [8]byte // Year (2 bytes), milliseconds (2 bytes) and seconds (4 bytes) + ParentID [4]byte + Flags [4]byte + FlavorCount [2]byte // Title size 1 Title []byte // string // Poster size 1 @@ -150,21 +151,27 @@ func (s byID) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s byID) Less(i, j int) bool { - return binary.BigEndian.Uint32(s[i].ID) < binary.BigEndian.Uint32(s[j].ID) + return binary.BigEndian.Uint32(s[i].ID[:]) < binary.BigEndian.Uint32(s[j].ID[:]) } +var ( + NewsFlavorLen = []byte{0x0a} + NewsFlavor = []byte("text/plain") +) + func (nal *NewsArtList) Read(p []byte) (int, error) { out := slices.Concat( - nal.ID, - nal.TimeStamp, - nal.ParentID, - nal.Flags, - []byte{0, 1}, + nal.ID[:], + nal.TimeStamp[:], + nal.ParentID[:], + nal.Flags[:], + []byte{0, 1}, // Flavor Count []byte{uint8(len(nal.Title))}, nal.Title, []byte{uint8(len(nal.Poster))}, nal.Poster, - []byte{0x0a, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x70, 0x6c, 0x61, 0x69, 0x6e}, + NewsFlavorLen, + NewsFlavor, nal.ArticleSize, ) @@ -190,17 +197,11 @@ func (newscat *NewsCategoryListData15) MarshalBinary() (data []byte, err error) out := append(newscat.Type[:], count...) + // If type is category 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) - if err != nil { - return data, err - } - - out = append(out, b...) // GUID - out = append(out, []byte{0, 0, 0, 1}...) // Add SN (TODO: not sure what this is) - out = append(out, []byte{0, 0, 0, 2}...) // Delete SN (TODO: not sure what this is) + out = append(out, newscat.GUID[:]...) // GUID + out = append(out, newscat.AddSN[:]...) // Add SN + out = append(out, newscat.DeleteSN[:]...) // Delete SN } out = append(out, newscat.nameLen()...) diff --git a/hotline/news_test.go b/hotline/news_test.go index 44776dd..a2102b2 100644 --- a/hotline/news_test.go +++ b/hotline/news_test.go @@ -12,9 +12,9 @@ func TestNewsCategoryListData15_MarshalBinary(t *testing.T) { Articles map[uint32]*NewsArtData SubCats map[string]NewsCategoryListData15 Count []byte - AddSN []byte - DeleteSN []byte - GUID []byte + AddSN [4]byte + DeleteSN [4]byte + GUID [16]byte } tests := []struct { name string @@ -75,7 +75,6 @@ func TestNewsCategoryListData15_MarshalBinary(t *testing.T) { Name: tt.fields.Name, Articles: tt.fields.Articles, SubCats: tt.fields.SubCats, - Count: tt.fields.Count, AddSN: tt.fields.AddSN, DeleteSN: tt.fields.DeleteSN, GUID: tt.fields.GUID, diff --git a/hotline/server.go b/hotline/server.go index ff8daf3..448aab1 100644 --- a/hotline/server.go +++ b/hotline/server.go @@ -246,7 +246,7 @@ func NewServer(configDir, netInterface string, netPort int, logger *slog.Logger, _ = server.loadBanList(filepath.Join(configDir, "Banlist.yaml")) if err := server.loadThreadedNews(filepath.Join(configDir, "ThreadedNews.yaml")); err != nil { - return nil, err + return nil, fmt.Errorf("error loading threaded news: %w", err) } if err := server.loadConfig(filepath.Join(configDir, "config.yaml")); err != nil { diff --git a/hotline/transaction.go b/hotline/transaction.go index 7883bfb..39dcd81 100644 --- a/hotline/transaction.go +++ b/hotline/transaction.go @@ -214,7 +214,8 @@ func (t *Transaction) Read(p []byte) (int, error) { bbuf := new(bytes.Buffer) for _, field := range t.Fields { - _, err := bbuf.ReadFrom(&field) + f := field + _, err := bbuf.ReadFrom(&f) if err != nil { return 0, fmt.Errorf("error reading field: %w", err) } diff --git a/hotline/transaction_handlers.go b/hotline/transaction_handlers.go index 1a079f7..2d4eabd 100644 --- a/hotline/transaction_handlers.go +++ b/hotline/transaction_handlers.go @@ -749,7 +749,8 @@ func HandleListUsers(cc *ClientConn, t *Transaction) (res []Transaction, err err var userFields []Field for _, acc := range cc.Server.Accounts { - b, err := io.ReadAll(acc) + accCopy := *acc + b, err := io.ReadAll(&accCopy) if err != nil { return res, err } @@ -1432,6 +1433,9 @@ func HandlePostNewsArt(cc *ClientConn, t *Transaction) (res []Transaction, err e bs := make([]byte, 4) binary.BigEndian.PutUint32(bs, convertedArtID) + cc.Server.mux.Lock() + defer cc.Server.mux.Unlock() + newArt := NewsArtData{ Title: string(t.GetField(FieldNewsArtTitle).Data), Poster: string(cc.UserName), @@ -2053,19 +2057,11 @@ func HandleMakeAlias(cc *ClientConn, t *Transaction) (res []Transaction, err err // 107 FieldRefNum Used later for transfer // 108 FieldTransferSize Size of data to be downloaded func HandleDownloadBanner(cc *ClientConn, t *Transaction) (res []Transaction, err error) { - fi, err := cc.Server.FS.Stat(filepath.Join(cc.Server.ConfigDir, cc.Server.Config.BannerFile)) - if err != nil { - return res, err - } - ft := cc.newFileTransfer(bannerDownload, []byte{}, []byte{}, make([]byte, 4)) + binary.BigEndian.PutUint32(ft.TransferSize, uint32(len(cc.Server.banner))) - binary.BigEndian.PutUint32(ft.TransferSize, uint32(fi.Size())) - - res = append(res, cc.NewReply(t, + return append(res, cc.NewReply(t, NewField(FieldRefNum, ft.refNum[:]), NewField(FieldTransferSize, ft.TransferSize), - )) - - return res, err + )), err } diff --git a/hotline/transaction_handlers_test.go b/hotline/transaction_handlers_test.go index 2420649..9c0359f 100644 --- a/hotline/transaction_handlers_test.go +++ b/hotline/transaction_handlers_test.go @@ -3191,10 +3191,8 @@ func TestHandleDelNewsItem(t *testing.T) { Server: &Server{ ThreadedNews: &ThreadedNews{Categories: map[string]NewsCategoryListData15{ "test": { - Type: [2]byte{0, 3}, - Count: nil, - NameSize: 0, - Name: "zz", + Type: [2]byte{0, 3}, + Name: "zz", }, }}, }, @@ -3237,10 +3235,8 @@ func TestHandleDelNewsItem(t *testing.T) { Server: &Server{ ThreadedNews: &ThreadedNews{Categories: map[string]NewsCategoryListData15{ "testcat": { - Type: [2]byte{0, 2}, - Count: nil, - NameSize: 0, - Name: "test", + Type: [2]byte{0, 2}, + Name: "test", }, }}, }, @@ -3293,10 +3289,8 @@ func TestHandleDelNewsItem(t *testing.T) { }(), ThreadedNews: &ThreadedNews{Categories: map[string]NewsCategoryListData15{ "testcat": { - Type: [2]byte{0, 2}, - Count: nil, - NameSize: 0, - Name: "test", + Type: [2]byte{0, 2}, + Name: "test", }, }}, }, @@ -3781,11 +3775,9 @@ func TestHandleNewNewsFldr(t *testing.T) { }(), ThreadedNews: &ThreadedNews{Categories: map[string]NewsCategoryListData15{ "test": { - Type: [2]byte{0, 2}, - Count: nil, - NameSize: 0, - Name: "test", - SubCats: make(map[string]NewsCategoryListData15), + Type: [2]byte{0, 2}, + Name: "test", + SubCats: make(map[string]NewsCategoryListData15), }, }}, }, @@ -3885,3 +3877,27 @@ func TestHandleNewNewsFldr(t *testing.T) { }) } } + +func TestHandleDownloadBanner(t *testing.T) { + type args struct { + cc *ClientConn + t *Transaction + } + tests := []struct { + name string + args args + wantRes []Transaction + wantErr assert.ErrorAssertionFunc + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotRes, err := HandleDownloadBanner(tt.args.cc, tt.args.t) + if !tt.wantErr(t, err, fmt.Sprintf("HandleDownloadBanner(%v, %v)", tt.args.cc, tt.args.t)) { + return + } + assert.Equalf(t, tt.wantRes, gotRes, "HandleDownloadBanner(%v, %v)", tt.args.cc, tt.args.t) + }) + } +}