aboutsummaryrefslogtreecommitdiff
path: root/hotline
diff options
context:
space:
mode:
authorJeff Halter <868228+jhalter@users.noreply.github.com>2024-06-16 16:03:54 -0700
committerJeff Halter <868228+jhalter@users.noreply.github.com>2024-06-16 16:05:25 -0700
commit0ed5132769e88cb385b5240986b706430f0ccd72 (patch)
treebd622161546d9db3293ba69afc9646bcdb24b2f4 /hotline
parentf6f1d88969e12eadb7013397cdad3c4c5625988c (diff)
Fix broken io.Reader implementations
Diffstat (limited to 'hotline')
-rw-r--r--hotline/account.go2
-rw-r--r--hotline/client_conn.go2
-rw-r--r--hotline/file_transfer.go10
-rw-r--r--hotline/news.go119
-rw-r--r--hotline/news_test.go7
-rw-r--r--hotline/server.go2
-rw-r--r--hotline/transaction.go3
-rw-r--r--hotline/transaction_handlers.go20
-rw-r--r--hotline/transaction_handlers_test.go50
9 files changed, 113 insertions, 102 deletions
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)
+ })
+ }
+}