From: Jeff Halter Date: Wed, 11 Aug 2021 01:52:46 +0000 (-0700) Subject: Add tests and clean up X-Git-Url: https://git.r.bdr.sh/rbdr/mobius/commitdiff_plain/72dd37f1abb2b550aaaac48eac677403d5664797?ds=sidebyside Add tests and clean up --- diff --git a/hotline/client.go b/hotline/client.go index 493d59e..dd113c9 100644 --- a/hotline/client.go +++ b/hotline/client.go @@ -90,7 +90,7 @@ type Client struct { UI *UI - Inbox chan *Transaction + Inbox chan *Transaction } func NewClient(cfgPath string, logger *zap.SugaredLogger) *Client { @@ -264,10 +264,10 @@ func handleGetFileNameList(c *Client, t *Transaction) (res []Transaction, err er entry := selectedNode.GetReference().(*FileNameWithInfo) - if bytes.Equal(entry.Type, []byte("fldr")) { - c.Logger.Infow("get new directory listing", "name", string(entry.Name)) + if bytes.Equal(entry.Type[:], []byte("fldr")) { + c.Logger.Infow("get new directory listing", "name", string(entry.name)) - c.filePath = append(c.filePath, string(entry.Name)) + c.filePath = append(c.filePath, string(entry.name)) f := NewField(fieldFilePath, EncodeFilePath(strings.Join(c.filePath, "/"))) if err := c.UI.HLClient.Send(*NewTransaction(tranGetFileNameList, nil, f)); err != nil { @@ -275,7 +275,7 @@ func handleGetFileNameList(c *Client, t *Transaction) (res []Transaction, err er } } else { // TODO: initiate file download - c.Logger.Infow("download file", "name", string(entry.Name)) + c.Logger.Infow("download file", "name", string(entry.name)) } } @@ -289,16 +289,19 @@ func handleGetFileNameList(c *Client, t *Transaction) (res []Transaction, err er for _, f := range t.Fields { var fn FileNameWithInfo - _, _ = fn.Read(f.Data) + err = fn.UnmarshalBinary(f.Data) + if err != nil { + return nil, nil + } - if bytes.Equal(fn.Type, []byte("fldr")) { - node := tview.NewTreeNode(fmt.Sprintf("[blue::]📁 %s[-:-:-]", fn.Name)) + if bytes.Equal(fn.Type[:], []byte("fldr")) { + node := tview.NewTreeNode(fmt.Sprintf("[blue::]📁 %s[-:-:-]", fn.name)) node.SetReference(&fn) root.AddChild(node) } else { - size := binary.BigEndian.Uint32(fn.FileSize) / 1024 + size := binary.BigEndian.Uint32(fn.FileSize[:]) / 1024 - node := tview.NewTreeNode(fmt.Sprintf(" %-40s %10v KB", fn.Name, size)) + node := tview.NewTreeNode(fmt.Sprintf(" %-40s %10v KB", fn.name, size)) node.SetReference(&fn) root.AddChild(node) } @@ -494,7 +497,7 @@ func handleClientTranShowAgreement(c *Client, t *Transaction) (res []Transaction agreement := string(t.GetField(fieldData).Data) agreement = strings.ReplaceAll(agreement, "\r", "\n") - c.UI.agreeModal = tview.NewModal(). + agreeModal := tview.NewModal(). SetText(agreement). AddButtons([]string{"Agree", "Disagree"}). SetDoneFunc(func(buttonIndex int, buttonLabel string) { @@ -517,10 +520,7 @@ func handleClientTranShowAgreement(c *Client, t *Transaction) (res []Transaction }, ) - c.Logger.Debug("show agreement page") - c.UI.Pages.AddPage("agreement", c.UI.agreeModal, false, true) - c.UI.Pages.ShowPage("agreement ") - c.UI.App.Draw() + c.UI.Pages.AddPage("agreement", agreeModal, false, true) return res, err } @@ -619,7 +619,7 @@ func (c *Client) Handshake() error { return err } - if bytes.Compare(replyBuf, ServerHandshake) == 0 { + if bytes.Equal(replyBuf, ServerHandshake) { return nil } @@ -653,7 +653,11 @@ func (c *Client) Send(t Transaction) error { var n int var err error - if n, err = c.Connection.Write(t.Payload()); err != nil { + b, err := t.MarshalBinary() + if err != nil { + return err + } + if n, err = c.Connection.Write(b); err != nil { return err } c.Logger.Debugw("Sent Transaction", diff --git a/hotline/client_conn.go b/hotline/client_conn.go index eea3790..367b70f 100644 --- a/hotline/client_conn.go +++ b/hotline/client_conn.go @@ -29,7 +29,7 @@ type ClientConn struct { ID *[]byte Icon *[]byte Flags *[]byte - UserName *[]byte + UserName []byte Account *Account IdleTime *int Server *Server @@ -55,7 +55,7 @@ func (cc *ClientConn) handleTransaction(transaction *Transaction) error { if field.ID == nil { cc.Server.Logger.Infow( "Missing required field", - "Account", cc.Account.Login, "UserName", string(*cc.UserName), "RequestType", handler.Name, "FieldID", reqField.ID, + "Account", cc.Account.Login, "UserName", string(cc.UserName), "RequestType", handler.Name, "FieldID", reqField.ID, ) return nil } @@ -63,7 +63,7 @@ func (cc *ClientConn) handleTransaction(transaction *Transaction) error { if len(field.Data) < reqField.minLen { cc.Server.Logger.Infow( "Field does not meet minLen", - "Account", cc.Account.Login, "UserName", string(*cc.UserName), "RequestType", handler.Name, "FieldID", reqField.ID, + "Account", cc.Account.Login, "UserName", string(cc.UserName), "RequestType", handler.Name, "FieldID", reqField.ID, ) return nil } @@ -71,7 +71,7 @@ func (cc *ClientConn) handleTransaction(transaction *Transaction) error { if !authorize(cc.Account.Access, handler.Access) { cc.Server.Logger.Infow( "Unauthorized Action", - "Account", cc.Account.Login, "UserName", string(*cc.UserName), "RequestType", handler.Name, + "Account", cc.Account.Login, "UserName", string(cc.UserName), "RequestType", handler.Name, ) cc.Server.outbox <- cc.NewErrReply(transaction, handler.DenyMsg) @@ -81,7 +81,7 @@ func (cc *ClientConn) handleTransaction(transaction *Transaction) error { cc.Server.Logger.Infow( "Received Transaction", "login", cc.Account.Login, - "name", string(*cc.UserName), + "name", string(cc.UserName), "RequestType", handler.Name, ) @@ -95,7 +95,7 @@ func (cc *ClientConn) handleTransaction(transaction *Transaction) error { } else { cc.Server.Logger.Errorw( "Unimplemented transaction type received", - "UserName", string(*cc.UserName), "RequestID", requestNum, + "UserName", string(cc.UserName), "RequestID", requestNum, ) } @@ -114,7 +114,7 @@ func (cc *ClientConn) handleTransaction(transaction *Transaction) error { tranNotifyChangeUser, NewField(fieldUserID, *cc.ID), NewField(fieldUserFlags, *cc.Flags), - NewField(fieldUserName, *cc.UserName), + NewField(fieldUserName, cc.UserName), NewField(fieldUserIconID, *cc.Icon), ) diff --git a/hotline/client_conn_test.go b/hotline/client_conn_test.go index 80c22b4..6e3464f 100644 --- a/hotline/client_conn_test.go +++ b/hotline/client_conn_test.go @@ -11,7 +11,7 @@ func TestClientConn_handleTransaction(t *testing.T) { ID *[]byte Icon *[]byte Flags *[]byte - UserName *[]byte + UserName []byte Account *Account IdleTime *int Server *Server diff --git a/hotline/file_header.go b/hotline/file_header.go index 60c652e..7e0df16 100644 --- a/hotline/file_header.go +++ b/hotline/file_header.go @@ -27,6 +27,15 @@ func NewFileHeader(fileName string, isDir bool) FileHeader { return fh } +func (fh *FileHeader) Read(p []byte) (n int, err error) { + p = concat.Slices( + fh.Size, + fh.Type, + fh.FilePath, + ) + return len(p), nil +} + func (fh *FileHeader) Payload() []byte { return concat.Slices( fh.Size, diff --git a/hotline/file_name_with_info.go b/hotline/file_name_with_info.go index eecf5ec..d9add8c 100644 --- a/hotline/file_name_with_info.go +++ b/hotline/file_name_with_info.go @@ -1,53 +1,52 @@ package hotline import ( + "bytes" "encoding/binary" - "github.com/jhalter/mobius/concat" ) -// FileNameWithInfo field content is presented in this structure: -// Type 4 Folder (‘fldr’) or other -// Creator 4 -// File size 4 -// 4 Reserved? -// Name script 2 -// Name size 2 -// Name data size type FileNameWithInfo struct { - Type []byte // file type code - Creator []byte // File creator code - FileSize []byte // File Size in bytes - RSVD []byte - NameScript []byte // TODO: What is this? - NameSize []byte // Length of name field - Name []byte // File name + fileNameWithInfoHeader + name []byte // File name } -func (f FileNameWithInfo) Payload() []byte { - name := f.Name - nameSize := make([]byte, 2) - binary.BigEndian.PutUint16(nameSize, uint16(len(name))) - - return concat.Slices( - f.Type, - f.Creator, - f.FileSize, - []byte{0, 0, 0, 0}, - f.NameScript, - nameSize, - f.Name, - ) +// fileNameWithInfoHeader contains the fixed length fields of FileNameWithInfo +type fileNameWithInfoHeader struct { + Type [4]byte // file type code + Creator [4]byte // File creator code + FileSize [4]byte // File Size in bytes + RSVD [4]byte + NameScript [2]byte // ?? + NameSize [2]byte // Length of name field } -func (f *FileNameWithInfo) Read(p []byte) (n int, err error) { - // TODO: check p for expected len - f.Type = p[0:4] - f.Creator = p[4:8] - f.FileSize = p[8:12] - f.RSVD = p[12:16] - f.NameScript = p[16:18] - f.NameSize = p[18:20] - f.Name = p[20:] - - return len(p), err +func (f *fileNameWithInfoHeader) nameLen() int { + return int(binary.BigEndian.Uint16(f.NameSize[:])) } + +func (f *FileNameWithInfo) MarshalBinary() (data []byte, err error) { + var buf bytes.Buffer + err = binary.Write(&buf, binary.LittleEndian, f.fileNameWithInfoHeader) + if err != nil { + return data, err + } + + _, err = buf.Write(f.name) + if err != nil { + return data, err + } + + return buf.Bytes(), err +} + +func (f *FileNameWithInfo) UnmarshalBinary(data []byte) error { + err := binary.Read(bytes.NewReader(data), binary.BigEndian, &f.fileNameWithInfoHeader) + if err != nil { + return err + } + headerLen := binary.Size(f.fileNameWithInfoHeader) + f.name = data[headerLen : headerLen+f.nameLen()] + + return err +} + diff --git a/hotline/file_name_with_info_test.go b/hotline/file_name_with_info_test.go index 15ec0d3..3ca2c30 100644 --- a/hotline/file_name_with_info_test.go +++ b/hotline/file_name_with_info_test.go @@ -2,34 +2,83 @@ package hotline import ( "github.com/stretchr/testify/assert" + "reflect" "testing" ) -func TestFileNameWithInfo_Read(t *testing.T) { +func TestFileNameWithInfo_MarshalBinary(t *testing.T) { type fields struct { - Type []byte - Creator []byte - FileSize []byte - NameScript []byte - NameSize []byte - Name []byte + fileNameWithInfoHeader fileNameWithInfoHeader + name []byte + } + tests := []struct { + name string + fields fields + wantData []byte + wantErr bool + }{ + { + name: "returns expected bytes", + fields: fields{ + fileNameWithInfoHeader: fileNameWithInfoHeader{ + Type: [4]byte{0x54, 0x45, 0x58, 0x54}, // TEXT + Creator: [4]byte{0x54, 0x54, 0x58, 0x54}, // TTXT + FileSize: [4]byte{0x00, 0x43, 0x16, 0xd3}, // File Size + RSVD: [4]byte{0, 0, 0, 0}, + NameScript: [2]byte{0, 0}, + NameSize: [2]byte{0x00, 0x03}, + }, + name: []byte("foo"), + }, + wantData: []byte{ + 0x54, 0x45, 0x58, 0x54, + 0x54, 0x54, 0x58, 0x54, + 0x00, 0x43, 0x16, 0xd3, + 0, 0, 0, 0, + 0, 0, + 0x00, 0x03, + 0x66, 0x6f, 0x6f, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + f := &FileNameWithInfo{ + fileNameWithInfoHeader: tt.fields.fileNameWithInfoHeader, + name: tt.fields.name, + } + gotData, err := f.MarshalBinary() + if (err != nil) != tt.wantErr { + t.Errorf("MarshalBinary() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotData, tt.wantData) { + t.Errorf("MarshalBinary() gotData = %v, want %v", gotData, tt.wantData) + } + }) + } +} + +func TestFileNameWithInfo_UnmarshalBinary(t *testing.T) { + type fields struct { + fileNameWithInfoHeader fileNameWithInfoHeader + name []byte } type args struct { - p []byte + data []byte } tests := []struct { name string fields fields args args - want *FileNameWithInfo - wantN int + want *FileNameWithInfo wantErr bool }{ { - name: "reads bytes into struct", - fields: fields{}, + name: "writes bytes into struct", args: args{ - p: []byte{ + data: []byte{ 0x54, 0x45, 0x58, 0x54, // TEXT 0x54, 0x54, 0x58, 0x54, // TTXT 0x00, 0x43, 0x16, 0xd3, // File Size @@ -40,40 +89,31 @@ func TestFileNameWithInfo_Read(t *testing.T) { }, }, want: &FileNameWithInfo{ - Type: []byte("TEXT"), - Creator: []byte("TTXT"), - FileSize: []byte{0x00, 0x43, 0x16, 0xd3}, - RSVD: []byte{0, 0, 0, 0}, - NameScript: []byte{0, 0}, - NameSize: []byte{0x00, 0x0e}, - Name: []byte("Audion.app.zip"), + fileNameWithInfoHeader: fileNameWithInfoHeader{ + Type: [4]byte{0x54, 0x45, 0x58, 0x54}, // TEXT + Creator: [4]byte{0x54, 0x54, 0x58, 0x54}, // TTXT + FileSize: [4]byte{0x00, 0x43, 0x16, 0xd3}, // File Size + RSVD: [4]byte{0, 0, 0, 0}, + NameScript: [2]byte{0, 0}, + NameSize: [2]byte{0x00, 0x0e}, + }, + name: []byte("Audion.app.zip"), }, - wantN: 34, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { f := &FileNameWithInfo{ - Type: tt.fields.Type, - Creator: tt.fields.Creator, - FileSize: tt.fields.FileSize, - NameScript: tt.fields.NameScript, - NameSize: tt.fields.NameSize, - Name: tt.fields.Name, + fileNameWithInfoHeader: tt.fields.fileNameWithInfoHeader, + name: tt.fields.name, } - gotN, err := f.Read(tt.args.p) - if (err != nil) != tt.wantErr { - t.Errorf("Read() error = %v, wantErr %v", err, tt.wantErr) - return - } - if gotN != tt.wantN { - t.Errorf("Read() gotN = %v, want %v", gotN, tt.wantN) + if err := f.UnmarshalBinary(tt.args.data); (err != nil) != tt.wantErr { + t.Errorf("UnmarshalBinary() error = %v, wantErr %v", err, tt.wantErr) } if !assert.Equal(t, tt.want, f) { t.Errorf("Read() got = %v, want %v", f, tt.want) - } }) } -} +} \ No newline at end of file diff --git a/hotline/file_path.go b/hotline/file_path.go index 71168dc..ae96e52 100644 --- a/hotline/file_path.go +++ b/hotline/file_path.go @@ -24,32 +24,30 @@ func NewFilePathItem(b []byte) FilePathItem { } type FilePath struct { - PathItemCount []byte - PathItems []FilePathItem + ItemCount []byte + Items []FilePathItem } -func NewFilePath(b []byte) FilePath { - if b == nil { - return FilePath{} - } - - fp := FilePath{PathItemCount: b[0:2]} +func (fp *FilePath) UnmarshalBinary(b []byte) error { + fp.ItemCount = b[0:2] - // number of items in the path - pathItemLen := binary.BigEndian.Uint16(b[0:2]) pathData := b[2:] - for i := uint16(0); i < pathItemLen; i++ { + for i := uint16(0); i < fp.Len(); i++ { segLen := pathData[2] - fp.PathItems = append(fp.PathItems, NewFilePathItem(pathData[:segLen+3])) + fp.Items = append(fp.Items, NewFilePathItem(pathData[:segLen+3])) pathData = pathData[3+segLen:] } - return fp + return nil +} + +func (fp *FilePath) Len() uint16 { + return binary.BigEndian.Uint16(fp.ItemCount) } func (fp *FilePath) String() string { var out []string - for _, i := range fp.PathItems { + for _, i := range fp.Items { out = append(out, string(i.Name)) } return strings.Join(out, pathSeparator) diff --git a/hotline/file_path_test.go b/hotline/file_path_test.go new file mode 100644 index 0000000..a5f5b60 --- /dev/null +++ b/hotline/file_path_test.go @@ -0,0 +1,60 @@ +package hotline + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestFilePath_UnmarshalBinary(t *testing.T) { + type fields struct { + ItemCount []byte + Items []FilePathItem + } + type args struct { + b []byte + } + tests := []struct { + name string + args args + want FilePath + wantErr bool + }{ + { + name: "unmarshals bytes into struct", + args: args{b: []byte{ + 0x00, 0x02, + 0x00, 0x00, + 0x0f, + 0x46, 0x69, 0x72, 0x73, 0x74, 0x20, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x20, 0x44, 0x69, 0x72, + 0x00, 0x00, + 0x08, + 0x41, 0x20, 0x53, 0x75, 0x62, 0x44, 0x69, 0x72, + }}, + want: FilePath{ + ItemCount: []byte{0x00, 0x02}, + Items: []FilePathItem{ + { + Len: 0x0f, + Name: []byte("First Level Dir"), + }, + { + Len: 0x08, + Name: []byte("A SubDir"), + }, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var fp FilePath + if err := fp.UnmarshalBinary(tt.args.b); (err != nil) != tt.wantErr { + t.Errorf("UnmarshalBinary() error = %v, wantErr %v", err, tt.wantErr) + } + if !assert.Equal(t, tt.want, fp) { + t.Errorf("Read() got = %v, want %v", fp, tt.want) + } + }) + } +} diff --git a/hotline/files.go b/hotline/files.go index 1db698b..bccab35 100644 --- a/hotline/files.go +++ b/hotline/files.go @@ -53,13 +53,16 @@ func getFileNameList(filePath string) ([]Field, error) { for _, file := range files { var fileType []byte + var fnwi FileNameWithInfo fileCreator := make([]byte, 4) - fileSize := make([]byte, 4) + //fileSize := make([]byte, 4) if !file.IsDir() { fileType = []byte(fileTypeFromFilename(file.Name())) fileCreator = []byte(fileCreatorFromFilename(file.Name())) - binary.BigEndian.PutUint32(fileSize, uint32(file.Size())) + binary.BigEndian.PutUint32(fnwi.FileSize[:], uint32(file.Size())) + copy(fnwi.Type[:], fileType[:]) + copy(fnwi.Creator[:], fileCreator[:]) } else { fileType = []byte("fldr") @@ -67,19 +70,22 @@ func getFileNameList(filePath string) ([]Field, error) { if err != nil { return fields, err } - binary.BigEndian.PutUint32(fileSize, uint32(len(dir))) + binary.BigEndian.PutUint32(fnwi.FileSize[:], uint32(len(dir))) + copy(fnwi.Type[:], fileType[:]) + copy(fnwi.Creator[:], fileCreator[:]) } - fields = append(fields, NewField( - fieldFileNameWithInfo, - FileNameWithInfo{ - Type: fileType, - Creator: fileCreator, - FileSize: fileSize, - NameScript: []byte{0, 0}, - Name: []byte(file.Name()), - }.Payload(), - )) + nameSize := make([]byte, 2) + binary.BigEndian.PutUint16(nameSize, uint16(len(file.Name()))) + copy(fnwi.NameSize[:], nameSize[:]) + + fnwi.name = []byte(file.Name()) + + b, err := fnwi.MarshalBinary() + if err != nil { + return nil, err + } + fields = append(fields, NewField(fieldFileNameWithInfo, b)) } return fields, nil @@ -150,6 +156,10 @@ func EncodeFilePath(filePath string) []byte { } func ReadFilePath(filePathFieldData []byte) string { - fp := NewFilePath(filePathFieldData) + var fp FilePath + err := fp.UnmarshalBinary(filePathFieldData) + if err != nil { + // TODO + } return fp.String() } diff --git a/hotline/flattened_file_object.go b/hotline/flattened_file_object.go index dfdd8a8..8e2dbff 100644 --- a/hotline/flattened_file_object.go +++ b/hotline/flattened_file_object.go @@ -16,19 +16,19 @@ type flattenedFileObject struct { // FlatFileHeader is the first section of a "Flattened File Object". All fields have static values. type FlatFileHeader struct { - Format []byte // Always "FILP" - Version []byte // Always 1 - RSVD []byte // Always empty zeros - ForkCount []byte // Always 2 + Format [4]byte // Always "FILP" + Version [2]byte // Always 1 + RSVD [16]byte // Always empty zeros + ForkCount [2]byte // Always 2 } // NewFlatFileHeader returns a FlatFileHeader struct func NewFlatFileHeader() FlatFileHeader { return FlatFileHeader{ - Format: []byte("FILP"), - Version: []byte{0, 1}, - RSVD: make([]byte, 16), - ForkCount: []byte{0, 2}, + Format: [4]byte{0x46, 0x49, 0x4c, 0x50}, // FILP + Version: [2]byte{0, 1}, + RSVD: [16]byte{}, + ForkCount: [2]byte{0, 2}, } } @@ -138,12 +138,7 @@ func ReadFlattenedFileObject(bytes []byte) flattenedFileObject { //dataSize := binary.BigEndian.Uint32(dataSizeField) ffo := flattenedFileObject{ - FlatFileHeader: FlatFileHeader{ - Format: bytes[0:4], - Version: bytes[4:6], - RSVD: bytes[6:22], - ForkCount: bytes[22:24], - }, + FlatFileHeader: NewFlatFileHeader(), FlatFileInformationForkHeader: FlatFileInformationForkHeader{ ForkType: bytes[24:28], CompressionType: bytes[28:32], @@ -178,10 +173,10 @@ func ReadFlattenedFileObject(bytes []byte) flattenedFileObject { func (f flattenedFileObject) Payload() []byte { var out []byte - out = append(out, f.FlatFileHeader.Format...) - out = append(out, f.FlatFileHeader.Version...) - out = append(out, f.FlatFileHeader.RSVD...) - out = append(out, f.FlatFileHeader.ForkCount...) + out = append(out, f.FlatFileHeader.Format[:]...) + out = append(out, f.FlatFileHeader.Version[:]...) + out = append(out, f.FlatFileHeader.RSVD[:]...) + out = append(out, f.FlatFileHeader.ForkCount[:]...) out = append(out, []byte("INFO")...) out = append(out, []byte{0, 0, 0, 0}...) @@ -211,22 +206,22 @@ func (f flattenedFileObject) Payload() []byte { return out } -func NewFlattenedFileObject(filePath string, fileName string) (flattenedFileObject, error) { +func NewFlattenedFileObject(filePath, fileName string) (*flattenedFileObject, error) { file, err := os.Open(fmt.Sprintf("%v/%v", filePath, fileName)) if err != nil { - return flattenedFileObject{}, err + return nil, err } defer file.Close() fileInfo, err := file.Stat() if err != nil { - return flattenedFileObject{}, err + return nil, err } dataSize := make([]byte, 4) binary.BigEndian.PutUint32(dataSize, uint32(fileInfo.Size())) - return flattenedFileObject{ + return &flattenedFileObject{ FlatFileHeader: NewFlatFileHeader(), FlatFileInformationFork: NewFlatFileInformationFork(fileName), FlatFileDataForkHeader: FlatFileDataForkHeader{ diff --git a/hotline/flattened_file_object_test.go b/hotline/flattened_file_object_test.go index 63e7175..19b7c94 100644 --- a/hotline/flattened_file_object_test.go +++ b/hotline/flattened_file_object_test.go @@ -3,6 +3,8 @@ package hotline import ( "bytes" "encoding/hex" + "github.com/davecgh/go-spew/spew" + "reflect" "testing" ) @@ -11,7 +13,7 @@ func TestReadFlattenedFileObject(t *testing.T) { ffo := ReadFlattenedFileObject(testData) - format := ffo.FlatFileHeader.Format + format := ffo.FlatFileHeader.Format[:] want := []byte("FILP") if !bytes.Equal(format, want) { t.Errorf("ReadFlattenedFileObject() = %q, want %q", format, want) @@ -34,3 +36,59 @@ func TestReadFlattenedFileObject(t *testing.T) { // t.Errorf("%q, want %q", comment, want) // } //} + +func TestNewFlattenedFileObject(t *testing.T) { + type args struct { + filePath string + fileName string + } + tests := []struct { + name string + args args + want *flattenedFileObject + wantErr bool + }{ + { + name: "when file path is valid", + args: args{ + filePath: "./test/config/Files/", + fileName: "testfile.txt", + }, + want: &flattenedFileObject{ + FlatFileHeader: NewFlatFileHeader(), + FlatFileInformationForkHeader: FlatFileInformationForkHeader{}, + FlatFileInformationFork: NewFlatFileInformationFork("testfile.txt"), + FlatFileDataForkHeader: FlatFileDataForkHeader{ + ForkType: []byte("DATA"), + CompressionType: []byte{0, 0, 0, 0}, + RSVD: []byte{0, 0, 0, 0}, + DataSize: []byte{0x00, 0x00, 0x00, 0x17}, + }, + FileData: nil, + }, + wantErr: false, + }, + { + name: "when file path is invalid", + args: args{ + filePath: "./nope/", + fileName: "also-nope.txt", + }, + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := NewFlattenedFileObject(tt.args.filePath, tt.args.fileName) + spew.Dump(got) + if (err != nil) != tt.wantErr { + t.Errorf("NewFlattenedFileObject() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewFlattenedFileObject() got = %v, want %v", got, tt.want) + } + }) + } +} \ No newline at end of file diff --git a/hotline/news.go b/hotline/news.go index a9e5d72..7a5c30c 100644 --- a/hotline/news.go +++ b/hotline/news.go @@ -4,7 +4,6 @@ import ( "bytes" "crypto/rand" "encoding/binary" - "log" "sort" "time" ) @@ -14,14 +13,15 @@ type ThreadedNews struct { } type NewsCategoryListData15 struct { - Type []byte `yaml:"Type"` //Size 2 ; Bundle (2) or category (3) + Type []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 SubCats map[string]NewsCategoryListData15 `yaml:"SubCats"` - Count []byte // Article or SubCategory count Size 2 + GUID []byte // Size 16 AddSN []byte // Size 4 DeleteSN []byte // Size 4 - GUID []byte // Size 16 } func (newscat *NewsCategoryListData15) GetNewsArtListData() NewsArtListData { @@ -61,7 +61,7 @@ func (newscat *NewsCategoryListData15) GetNewsArtListData() NewsArtListData { return nald } -// NewsArtData repsents a single news article +// NewsArtData represents single news article type NewsArtData struct { Title string `yaml:"Title"` Poster string `yaml:"Poster"` @@ -102,7 +102,7 @@ func (nald *NewsArtListData) Payload() []byte { return out } -// NewsArtList is a summarized ver sion of a NewArtData record for display in list view +// 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) @@ -154,18 +154,18 @@ type NewsFlavorList struct { // Article size 2 } -func (newscat *NewsCategoryListData15) Payload() []byte { +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...) if bytes.Equal(newscat.Type, []byte{0, 3}) { - // Generate a random GUID + // Generate a random GUID // TODO: does this need to be random? b := make([]byte, 16) _, err := rand.Read(b) if err != nil { - log.Fatal(err) + return data, err } out = append(out, b...) // GUID @@ -176,7 +176,7 @@ func (newscat *NewsCategoryListData15) Payload() []byte { out = append(out, newscat.nameLen()...) out = append(out, []byte(newscat.Name)...) - return out + return out, err } // ReadNewsCategoryListData parses a byte slice into a NewsCategoryListData15 struct @@ -203,26 +203,26 @@ func (newscat *NewsCategoryListData15) nameLen() []byte { return []byte{uint8(len(newscat.Name))} } -type NewsPath struct { - Paths []string -} - -func (np *NewsPath) Payload() []byte { - var out []byte - - count := make([]byte, 2) - binary.BigEndian.PutUint16(count, uint16(len(np.Paths))) - - out = append(out, count...) - for _, p := range np.Paths { - pLen := byte(len(p)) - out = append(out, []byte{0, 0}...) // what is this? - out = append(out, pLen) - out = append(out, []byte(p)...) - } - - return out -} +//type NewsPath struct { +// Paths []string +//} +// +//func (np *NewsPath) Payload() []byte { +// var out []byte +// +// count := make([]byte, 2) +// binary.BigEndian.PutUint16(count, uint16(len(np.Paths))) +// +// out = append(out, count...) +// for _, p := range np.Paths { +// pLen := byte(len(p)) +// out = append(out, []byte{0, 0}...) // what is this? +// out = append(out, pLen) +// out = append(out, []byte(p)...) +// } +// +// return out +//} func ReadNewsPath(newsPath []byte) []string { if len(newsPath) == 0 { diff --git a/hotline/news_test.go b/hotline/news_test.go new file mode 100644 index 0000000..02e11a9 --- /dev/null +++ b/hotline/news_test.go @@ -0,0 +1,100 @@ +package hotline + +import ( + "bytes" + "reflect" + "testing" +) + +func TestNewsCategoryListData15_MarshalBinary(t *testing.T) { + type fields struct { + Type []byte + Name string + Articles map[uint32]*NewsArtData + SubCats map[string]NewsCategoryListData15 + Count []byte + AddSN []byte + DeleteSN []byte + GUID []byte + } + tests := []struct { + name string + fields fields + wantData []byte + wantErr bool + }{ + { + name: "returns expected bytes when type is a bundle", + fields: fields{ + Type: []byte{0x00, 0x02}, + Articles: map[uint32]*NewsArtData{ + uint32(1): { + Title: "", + Poster: "", + Data: "", + }, + }, + Name: "foo", + }, + wantData: []byte{ + 0x00, 0x02, + 0x00, 0x01, + 0x03, + 0x66, 0x6f, 0x6f, + }, + wantErr: false, + }, + { + name: "returns expected bytes when type is a category", + fields: fields{ + Type: []byte{0x00, 0x03}, + Articles: map[uint32]*NewsArtData{ + uint32(1): { + Title: "", + Poster: "", + Data: "", + }, + }, + Name: "foo", + }, + wantData: []byte{ + 0x00, 0x03, + 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x02, + 0x03, + 0x66, 0x6f, 0x6f, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + newscat := &NewsCategoryListData15{ + Type: tt.fields.Type, + 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, + } + gotData, err := newscat.MarshalBinary() + if bytes.Equal(newscat.Type, []byte{0, 3}) { + // zero out the random GUID before comparison + for i := 4; i < 20; i++ { + gotData[i] = 0 + } + } + if (err != nil) != tt.wantErr { + t.Errorf("MarshalBinary() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotData, tt.wantData) { + t.Errorf("MarshalBinary() gotData = %v, want %v", gotData, tt.wantData) + } + }) + } +} diff --git a/hotline/server.go b/hotline/server.go index 1429c62..8e0fb50 100644 --- a/hotline/server.go +++ b/hotline/server.go @@ -33,7 +33,6 @@ const ( ) type Server struct { - Interface string Port int Accounts map[string]*Account Agreement []byte @@ -115,13 +114,17 @@ func (s *Server) sendTransaction(t Transaction) error { if client == nil { return errors.New("invalid client") } - userName := string(*client.UserName) + userName := string(client.UserName) login := client.Account.Login handler := TransactionHandlers[requestNum] + b, err := t.MarshalBinary() + if err != nil { + return err + } var n int - if n, err = client.Connection.Write(t.Payload()); err != nil { + if n, err = client.Connection.Write(b); err != nil { return err } s.Logger.Debugw("Sent Transaction", @@ -168,8 +171,6 @@ func (s *Server) Serve(ctx context.Context, cancelRoot context.CancelFunc, ln ne const ( agreementFile = "Agreement.txt" - messageBoardFile = "MessageBoard.txt" - threadedNewsFile = "ThreadedNews.yaml" ) // NewServer constructs a new Server from a config dir @@ -290,7 +291,7 @@ func (s *Server) keepaliveHandler() { tranNotifyChangeUser, NewField(fieldUserID, *c.ID), NewField(fieldUserFlags, *c.Flags), - NewField(fieldUserName, *c.UserName), + NewField(fieldUserName, c.UserName), NewField(fieldUserIconID, *c.Icon), ) } @@ -323,7 +324,7 @@ func (s *Server) NewClientConn(conn net.Conn) *ClientConn { ID: &[]byte{0, 0}, Icon: &[]byte{0, 0}, Flags: &[]byte{0, 0}, - UserName: &[]byte{}, + UserName: []byte{}, Connection: conn, Server: s, Version: &[]byte{}, @@ -382,7 +383,7 @@ func (s *Server) connectedUsers() []Field { ID: *c.ID, Icon: *c.Icon, Flags: *c.Flags, - Name: string(*c.UserName), + Name: string(c.UserName), } connectedUsers = append(connectedUsers, NewField(fieldUsernameWithInfo, user.Payload())) } @@ -497,15 +498,19 @@ func (s *Server) handleNewConnection(conn net.Conn) error { // If authentication fails, send error reply and close connection if !c.Authenticate(login, encodedPassword) { - rep := c.NewErrReply(clientLogin, "Incorrect login.") - if _, err := conn.Write(rep.Payload()); err != nil { + t := c.NewErrReply(clientLogin, "Incorrect login.") + b, err := t.MarshalBinary() + if err != nil { + return err + } + if _, err := conn.Write(b); err != nil { return err } return fmt.Errorf("incorrect login") } if clientLogin.GetField(fieldUserName).Data != nil { - *c.UserName = clientLogin.GetField(fieldUserName).Data + c.UserName = clientLogin.GetField(fieldUserName).Data } if clientLogin.GetField(fieldUserIconID).Data != nil { @@ -753,7 +758,8 @@ func (s *Server) TransferFile(conn net.Conn) error { // // This notifies the server to send the next item header - fh := NewFilePath(fileTransfer.FilePath) + var fh FilePath + _ = fh.UnmarshalBinary(fileTransfer.FilePath) fullFilePath := fmt.Sprintf("%v/%v", s.Config.FileRoot+fh.String(), string(fileTransfer.FileName)) basePathLen := len(fullFilePath) diff --git a/hotline/transaction.go b/hotline/transaction.go index 8fbdf35..835d194 100644 --- a/hotline/transaction.go +++ b/hotline/transaction.go @@ -214,7 +214,7 @@ func ReadFields(paramCount []byte, buf []byte) ([]Field, error) { return fields, nil } -func (t Transaction) Payload() []byte { +func (t Transaction) MarshalBinary() (data []byte, err error) { payloadSize := t.Size() fieldCount := make([]byte, 2) @@ -234,7 +234,7 @@ func (t Transaction) Payload() []byte { payloadSize, // this is the dataSize field, but seeming the same as totalSize fieldCount, fieldPayload, - ) + ), err } // Size returns the total size of the transaction payload diff --git a/hotline/transaction_handlers.go b/hotline/transaction_handlers.go index 5773d80..46f96eb 100644 --- a/hotline/transaction_handlers.go +++ b/hotline/transaction_handlers.go @@ -5,7 +5,6 @@ import ( "encoding/binary" "errors" "fmt" - "github.com/davecgh/go-spew/spew" "gopkg.in/yaml.v2" "io/ioutil" "math/big" @@ -293,14 +292,14 @@ var TransactionHandlers = map[uint16]TransactionType{ func HandleChatSend(cc *ClientConn, t *Transaction) (res []Transaction, err error) { // Truncate long usernames - trunc := fmt.Sprintf("%13s", *cc.UserName) + trunc := fmt.Sprintf("%13s", cc.UserName) formattedMsg := fmt.Sprintf("\r%.14s: %s", trunc, t.GetField(fieldData).Data) // By holding the option key, Hotline chat allows users to send /me formatted messages like: // *** Halcyon does stuff // This is indicated by the presence of the optional field fieldChatOptions in the transaction payload if t.GetField(fieldChatOptions).Data != nil { - formattedMsg = fmt.Sprintf("\r*** %s %s", *cc.UserName, t.GetField(fieldData).Data) + formattedMsg = fmt.Sprintf("\r*** %s %s", cc.UserName, t.GetField(fieldData).Data) } if bytes.Equal(t.GetField(fieldData).Data, []byte("/stats")) { @@ -360,7 +359,7 @@ func HandleSendInstantMsg(cc *ClientConn, t *Transaction) (res []Transaction, er tranServerMsg, &ID.Data, NewField(fieldData, msg.Data), - NewField(fieldUserName, *cc.UserName), + NewField(fieldUserName, cc.UserName), NewField(fieldUserID, *cc.ID), NewField(fieldOptions, []byte{0, 1}), ), @@ -384,7 +383,7 @@ func HandleSendInstantMsg(cc *ClientConn, t *Transaction) (res []Transaction, er tranServerMsg, cc.ID, NewField(fieldData, *otherClient.AutoReply), - NewField(fieldUserName, *otherClient.UserName), + NewField(fieldUserName, otherClient.UserName), NewField(fieldUserID, *otherClient.ID), NewField(fieldOptions, []byte{0, 1}), ), @@ -399,7 +398,6 @@ func HandleSendInstantMsg(cc *ClientConn, t *Transaction) (res []Transaction, er func HandleGetFileInfo(cc *ClientConn, t *Transaction) (res []Transaction, err error) { fileName := string(t.GetField(fieldFileName).Data) filePath := cc.Server.Config.FileRoot + ReadFilePath(t.GetField(fieldFilePath).Data) - spew.Dump(cc.Server.Config.FileRoot) ffo, err := NewFlattenedFileObject(filePath, fileName) if err != nil { @@ -547,7 +545,8 @@ func HandleNewFolder(cc *ClientConn, t *Transaction) (res []Transaction, err err // fieldFilePath is only present for nested paths if t.GetField(fieldFilePath).Data != nil { - newFp := NewFilePath(t.GetField(fieldFilePath).Data) + var newFp FilePath + newFp.UnmarshalBinary(t.GetField(fieldFilePath).Data) newFolderPath += newFp.String() } newFolderPath += "/" + string(t.GetField(fieldFileName).Data) @@ -610,7 +609,7 @@ func HandleSetUser(cc *ClientConn, t *Transaction) (res []Transaction, err error tranNotifyChangeUser, NewField(fieldUserID, *c.ID), NewField(fieldUserFlags, *c.Flags), - NewField(fieldUserName, *c.UserName), + NewField(fieldUserName, c.UserName), NewField(fieldUserIconID, *c.Icon), ) } @@ -756,7 +755,7 @@ None. template = fmt.Sprintf( template, - *clientConn.UserName, + clientConn.UserName, clientConn.Account.Name, clientConn.Account.Login, clientConn.Connection.RemoteAddr().String(), @@ -766,7 +765,7 @@ None. res = append(res, cc.NewReply(t, NewField(fieldData, []byte(template)), - NewField(fieldUserName, *clientConn.UserName), + NewField(fieldUserName, clientConn.UserName), )) return res, err } @@ -782,7 +781,7 @@ func (cc *ClientConn) notifyNewUserHasJoined() (res []Transaction, err error) { cc.NotifyOthers( *NewTransaction( tranNotifyChangeUser, nil, - NewField(fieldUserName, *cc.UserName), + NewField(fieldUserName, cc.UserName), NewField(fieldUserID, *cc.ID), NewField(fieldUserIconID, *cc.Icon), NewField(fieldUserFlags, *cc.Flags), @@ -796,7 +795,7 @@ func HandleTranAgreed(cc *ClientConn, t *Transaction) (res []Transaction, err er bs := make([]byte, 2) binary.BigEndian.PutUint16(bs, *cc.Server.NextGuestID) - *cc.UserName = t.GetField(fieldUserName).Data + cc.UserName = t.GetField(fieldUserName).Data *cc.ID = bs *cc.Icon = t.GetField(fieldUserIconID).Data @@ -857,7 +856,7 @@ func HandleTranOldPostNews(cc *ClientConn, t *Transaction) (res []Transaction, e newsTemplate = cc.Server.Config.NewsDelimiter } - newsPost := fmt.Sprintf(newsTemplate+"\r", *cc.UserName, time.Now().Format(newsDateTemplate), t.GetField(fieldData).Data) + newsPost := fmt.Sprintf(newsTemplate+"\r", cc.UserName, time.Now().Format(newsDateTemplate), t.GetField(fieldData).Data) newsPost = strings.Replace(newsPost, "\n", "\r", -1) // update news in memory @@ -916,9 +915,10 @@ func HandleGetNewsCatNameList(cc *ClientConn, t *Transaction) (res []Transaction var fieldData []Field for _, k := range keys { cat := cats[k] + b, _ := cat.MarshalBinary() fieldData = append(fieldData, NewField( fieldNewsCatListData15, - cat.Payload(), + b, )) } @@ -1113,7 +1113,7 @@ func HandlePostNewsArt(cc *ClientConn, t *Transaction) (res []Transaction, err e newArt := NewsArtData{ Title: string(t.GetField(fieldNewsArtTitle).Data), - Poster: string(*cc.UserName), + Poster: string(cc.UserName), Date: NewsDate(), PrevArt: []byte{0, 0, 0, 0}, NextArt: []byte{0, 0, 0, 0}, @@ -1239,7 +1239,8 @@ func HandleDownloadFolder(cc *ClientConn, t *Transaction) (res []Transaction, er cc.Server.FileTransfers[data] = fileTransfer cc.Transfers[FolderDownload] = append(cc.Transfers[FolderDownload], fileTransfer) - fp := NewFilePath(t.GetField(fieldFilePath).Data) + var fp FilePath + fp.UnmarshalBinary(t.GetField(fieldFilePath).Data) fullFilePath := fmt.Sprintf("%v%v", cc.Server.Config.FileRoot+fp.String(), string(fileTransfer.FileName)) transferSize, err := CalcTotalSize(fullFilePath) @@ -1319,7 +1320,7 @@ func HandleSetClientUserInfo(cc *ClientConn, t *Transaction) (res []Transaction, icon = t.GetField(fieldUserIconID).Data } *cc.Icon = icon - *cc.UserName = t.GetField(fieldUserName).Data + cc.UserName = t.GetField(fieldUserName).Data // the options field is only passed by the client versions > 1.2.3. options := t.GetField(fieldOptions).Data @@ -1354,7 +1355,7 @@ func HandleSetClientUserInfo(cc *ClientConn, t *Transaction) (res []Transaction, NewField(fieldUserID, *cc.ID), NewField(fieldUserIconID, *cc.Icon), NewField(fieldUserFlags, *cc.Flags), - NewField(fieldUserName, *cc.UserName), + NewField(fieldUserName, cc.UserName), ) return res, err @@ -1410,7 +1411,7 @@ func HandleInviteNewChat(cc *ClientConn, t *Transaction) (res []Transaction, err tranInviteToChat, &targetID, NewField(fieldChatID, newChatID), - NewField(fieldUserName, *cc.UserName), + NewField(fieldUserName, cc.UserName), NewField(fieldUserID, *cc.ID), ), ) @@ -1418,7 +1419,7 @@ func HandleInviteNewChat(cc *ClientConn, t *Transaction) (res []Transaction, err res = append(res, cc.NewReply(t, NewField(fieldChatID, newChatID), - NewField(fieldUserName, *cc.UserName), + NewField(fieldUserName, cc.UserName), NewField(fieldUserID, *cc.ID), NewField(fieldUserIconID, *cc.Icon), NewField(fieldUserFlags, *cc.Flags), @@ -1438,7 +1439,7 @@ func HandleInviteToChat(cc *ClientConn, t *Transaction) (res []Transaction, err tranInviteToChat, &targetID, NewField(fieldChatID, chatID), - NewField(fieldUserName, *cc.UserName), + NewField(fieldUserName, cc.UserName), NewField(fieldUserID, *cc.ID), ), ) @@ -1446,7 +1447,7 @@ func HandleInviteToChat(cc *ClientConn, t *Transaction) (res []Transaction, err cc.NewReply( t, NewField(fieldChatID, chatID), - NewField(fieldUserName, *cc.UserName), + NewField(fieldUserName, cc.UserName), NewField(fieldUserID, *cc.ID), NewField(fieldUserIconID, *cc.Icon), NewField(fieldUserFlags, *cc.Flags), @@ -1462,7 +1463,7 @@ func HandleRejectChatInvite(cc *ClientConn, t *Transaction) (res []Transaction, privChat := cc.Server.PrivateChats[chatInt] - resMsg := append(*cc.UserName, []byte(" declined invitation to chat")...) + resMsg := append(cc.UserName, []byte(" declined invitation to chat")...) for _, c := range sortedClients(privChat.ClientConn) { res = append(res, @@ -1496,7 +1497,7 @@ func HandleJoinChat(cc *ClientConn, t *Transaction) (res []Transaction, err erro tranNotifyChatChangeUser, c.ID, NewField(fieldChatID, chatID), - NewField(fieldUserName, *cc.UserName), + NewField(fieldUserName, cc.UserName), NewField(fieldUserID, *cc.ID), NewField(fieldUserIconID, *cc.Icon), NewField(fieldUserFlags, *cc.Flags), @@ -1512,7 +1513,7 @@ func HandleJoinChat(cc *ClientConn, t *Transaction) (res []Transaction, err erro ID: *c.ID, Icon: *c.Icon, Flags: *c.Flags, - Name: string(*c.UserName), + Name: string(c.UserName), } replyFields = append(replyFields, NewField(fieldUsernameWithInfo, user.Payload())) diff --git a/hotline/transaction_handlers_test.go b/hotline/transaction_handlers_test.go index cf05a99..e62cce2 100644 --- a/hotline/transaction_handlers_test.go +++ b/hotline/transaction_handlers_test.go @@ -22,7 +22,7 @@ func TestHandleSetChatSubject(t *testing.T) { name: "sends chat subject to private chat members", args: args{ cc: &ClientConn{ - UserName: &[]byte{0x00, 0x01}, + UserName: []byte{0x00, 0x01}, Server: &Server{ PrivateChats: map[uint32]*PrivateChat{ uint32(1): { @@ -224,7 +224,7 @@ func TestHandleGetUserNameList(t *testing.T) { ID: &[]byte{0, 1}, Icon: &[]byte{0, 2}, Flags: &[]byte{0, 3}, - UserName: &[]byte{0, 4}, + UserName: []byte{0, 4}, }, }, }, @@ -282,7 +282,7 @@ func TestHandleChatSend(t *testing.T) { name: "sends chat msg transaction to all clients", args: args{ cc: &ClientConn{ - UserName: &[]byte{0x00, 0x01}, + UserName: []byte{0x00, 0x01}, Server: &Server{ Clients: map[uint16]*ClientConn{ uint16(1): { @@ -332,11 +332,66 @@ func TestHandleChatSend(t *testing.T) { }, wantErr: false, }, + { + name: "sends chat msg as emote if fieldChatOptions is set", + args: args{ + cc: &ClientConn{ + UserName: []byte("Testy McTest"), + Server: &Server{ + Clients: map[uint16]*ClientConn{ + uint16(1): { + Account: &Account{ + Access: &[]byte{255, 255, 255, 255, 255, 255, 255, 255}, + }, + ID: &[]byte{0, 1}, + }, + uint16(2): { + Account: &Account{ + Access: &[]byte{255, 255, 255, 255, 255, 255, 255, 255}, + }, + ID: &[]byte{0, 2}, + }, + }, + }, + }, + t: &Transaction{ + Fields: []Field{ + NewField(fieldData, []byte("performed action")), + NewField(fieldChatOptions, []byte{0x00, 0x01}), + }, + }, + }, + want: []Transaction{ + { + clientID: &[]byte{0, 1}, + Flags: 0x00, + IsReply: 0x00, + Type: []byte{0, 0x6a}, + ID: []byte{0x9a, 0xcb, 0x04, 0x42}, // Random ID from rand.Seed(1) + ErrorCode: []byte{0, 0, 0, 0}, + Fields: []Field{ + NewField(fieldData, []byte("\r*** Testy McTest performed action")), + }, + }, + { + clientID: &[]byte{0, 2}, + Flags: 0x00, + IsReply: 0x00, + Type: []byte{0, 0x6a}, + ID: []byte{0xf0, 0xc5, 0x34, 0x1e}, // Random ID from rand.Seed(1) + ErrorCode: []byte{0, 0, 0, 0}, + Fields: []Field{ + NewField(fieldData, []byte("\r*** Testy McTest performed action")), + }, + }, + }, + wantErr: false, + }, { name: "only sends chat msg to clients with accessReadChat permission", args: args{ cc: &ClientConn{ - UserName: &[]byte{0x00, 0x01}, + UserName: []byte{0x00, 0x01}, Server: &Server{ Clients: map[uint16]*ClientConn{ uint16(1): { @@ -391,3 +446,85 @@ func TestHandleChatSend(t *testing.T) { }) } } + +func TestHandleGetFileInfo(t *testing.T) { + rand.Seed(1) // reset seed between tests to make transaction IDs predictable + + type args struct { + cc *ClientConn + t *Transaction + } + tests := []struct { + name string + args args + wantRes []Transaction + wantErr bool + }{ + { + name: "returns expected fields when a valid file is requested", + args: args{ + cc: &ClientConn{ + ID: &[]byte{0x00, 0x01}, + Server: &Server{ + Config: &Config{ + FileRoot: "./test/config/Files/", + }, + }, + }, + t: NewTransaction( + tranGetFileInfo, nil, + NewField(fieldFileName, []byte("testfile.txt")), + NewField(fieldFilePath, []byte{0x00, 0x00}), + //NewField(fieldFilePath, []byte{ + // 0x00, 0x03, + // 0x00, 0x00, + // 0x04, + // 0x74, 0x65, 0x73, 0x74, + // 0x00, 0x00, + // 0x06, + // 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + // + // 0x00, 0x00, + // 0x05, + // 0x46, 0x69, 0x6c, 0x65, 73}, + //), + ), + }, + wantRes: []Transaction{ + { + clientID: &[]byte{0, 1}, + Flags: 0x00, + IsReply: 0x01, + Type: []byte{0, 0xce}, + ID: []byte{0x9a, 0xcb, 0x04, 0x42}, // Random ID from rand.Seed(1) + ErrorCode: []byte{0, 0, 0, 0}, + Fields: []Field{ + NewField(fieldFileName, []byte("testfile.txt")), + NewField(fieldFileTypeString, []byte("TEXT")), + NewField(fieldFileCreatorString, []byte("TTXT")), + NewField(fieldFileComment, []byte("TODO")), + NewField(fieldFileType, []byte("TEXT")), + NewField(fieldFileCreateDate, []byte{0x07, 0x70, 0x00, 0x00, 0xba, 0x74, 0x24, 0x73}), + NewField(fieldFileModifyDate, []byte{0x07, 0x70, 0x00, 0x00, 0xba, 0x74, 0x24, 0x73}), + NewField(fieldFileSize, []byte{0x0, 0x0, 0x0, 0x17}), + }, + }, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + rand.Seed(1) // reset seed between tests to make transaction IDs predictable + + gotRes, err := HandleGetFileInfo(tt.args.cc, tt.args.t) + if (err != nil) != tt.wantErr { + t.Errorf("HandleGetFileInfo() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !assert.Equal(t, tt.wantRes, gotRes) { + t.Errorf("HandleGetFileInfo() gotRes = %v, want %v", gotRes, tt.wantRes) + } + }) + } +} diff --git a/hotline/transaction_test.go b/hotline/transaction_test.go index 4c7d372..1fade25 100644 --- a/hotline/transaction_test.go +++ b/hotline/transaction_test.go @@ -142,16 +142,25 @@ func TestReadTransaction(t *testing.T) { { name: "when buf contains all bytes for a single transaction", args: args{ - buf: sampleTransaction.Payload(), + buf: func() []byte { + b, _ := sampleTransaction.MarshalBinary() + return b + }(), }, want: sampleTransaction, - want1: len(sampleTransaction.Payload()), + want1: func() int { + b, _ := sampleTransaction.MarshalBinary() + return len(b) + }(), wantErr: false, }, { name: "when len(buf) is less than the length of the transaction", args: args{ - buf: sampleTransaction.Payload()[:len(sampleTransaction.Payload())-1], + buf: func() []byte { + b, _ := sampleTransaction.MarshalBinary() + return b[:len(b)-1] + }(), }, want: nil, want1: 0, diff --git a/hotline/ui.go b/hotline/ui.go index 3d0fa68..d0fd6c0 100644 --- a/hotline/ui.go +++ b/hotline/ui.go @@ -19,7 +19,6 @@ type UI struct { App *tview.Application Pages *tview.Pages userList *tview.TextView - agreeModal *tview.Modal trackerList *tview.List HLClient *Client } @@ -75,7 +74,6 @@ func NewUI(c *Client) *UI { chatInput: chatInput, userList: userList, trackerList: tview.NewList(), - agreeModal: tview.NewModal(), HLClient: c, } }