diff options
| author | Jeff Halter <868228+jhalter@users.noreply.github.com> | 2026-06-12 08:47:54 -0700 |
|---|---|---|
| committer | Jeff Halter <868228+jhalter@users.noreply.github.com> | 2026-06-12 08:47:54 -0700 |
| commit | c15f8510fbd5ccf9a122f88d6e975d1ef69e00b3 (patch) | |
| tree | 1ba7e392610637ed792d0003568cf9edb6adc526 /internal | |
| parent | b2c462a3a1353f0653a5964b3a6924538ce83523 (diff) | |
Fix data races on ClientConn state, banner reload, and rate limiter growth
ClientConn's mutable session state (Flags, UserName, Icon, IdleTime,
AutoReply) was guarded inconsistently: two mutexes (FlagsMU and mu)
covered some paths while others mutated or read the fields with no
locking at all, including HandleSetClientUserInfo, HandleUpdateUser
(which writes other clients' admin flag), the login flow, the HTTP API
handlers, and the keepalive loop. Consolidate on a single mutex with
accessor methods (SetFlag/IsFlagSet/FlagBytes, SetUserName/GetUserName,
and so on) used by all production code; direct field access remains for
test construction. The idle/away logic moves into incrementIdleTime
and clearIdleAndAway helpers that report whether a notification is
needed, so SendAll is no longer called while holding the lock.
HandleRejectChatInvite also no longer appends to the username slice,
which could write past its length into the backing buffer.
The server banner is now behind Banner/SetBanner with an RWMutex: the
SIGHUP reload previously reassigned the field while banner download
goroutines read it, and nilled it when the file read failed. Reload
now keeps the previous banner on failure.
Per-IP rate limiter entries now record a last-seen time, and the
keepalive ticker evicts entries idle for over seven days, so the map
no longer grows unboundedly with each unique client IP.
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/mobius/api.go | 4 | ||||
| -rw-r--r-- | internal/mobius/handlers_accounts.go | 10 | ||||
| -rw-r--r-- | internal/mobius/handlers_chat.go | 57 | ||||
| -rw-r--r-- | internal/mobius/handlers_news.go | 4 | ||||
| -rw-r--r-- | internal/mobius/handlers_session.go | 63 | ||||
| -rw-r--r-- | internal/mobius/handlers_transfers.go | 2 | ||||
| -rw-r--r-- | internal/mobius/handlers_transfers_test.go | 10 |
7 files changed, 73 insertions, 77 deletions
diff --git a/internal/mobius/api.go b/internal/mobius/api.go index a574bf8..cf0a46f 100644 --- a/internal/mobius/api.go +++ b/internal/mobius/api.go @@ -115,7 +115,7 @@ func (srv *APIServer) OnlineHandler(w http.ResponseWriter, r *http.Request) { for _, c := range srv.hlServer.ClientMgr.List() { users = append(users, map[string]string{ "login": string(c.Account.Login), - "nickname": string(c.UserName), + "nickname": string(c.GetUserName()), "ip": c.RemoteAddr, }) } @@ -169,7 +169,7 @@ func (srv *APIServer) BanHandler(w http.ResponseWriter, r *http.Request) { // Disconnect user if online for _, c := range srv.hlServer.ClientMgr.List() { if (req.Username != "" && c.Account.Login == req.Username) || - (req.Nickname != "" && string(c.UserName) == req.Nickname) || + (req.Nickname != "" && string(c.GetUserName()) == req.Nickname) || (req.IP != "" && c.IP() == req.IP) { c.Disconnect() } diff --git a/internal/mobius/handlers_accounts.go b/internal/mobius/handlers_accounts.go index 871e632..02b7c77 100644 --- a/internal/mobius/handlers_accounts.go +++ b/internal/mobius/handlers_accounts.go @@ -60,9 +60,9 @@ func HandleSetUser(cc *hotline.ClientConn, t *hotline.Transaction) (res []hotlin res = append(res, newT) if c.Authorize(hotline.AccessDisconUser) { - c.Flags.Set(hotline.UserFlagAdmin, 1) + c.SetFlag(hotline.UserFlagAdmin, 1) } else { - c.Flags.Set(hotline.UserFlagAdmin, 0) + c.SetFlag(hotline.UserFlagAdmin, 0) } c.Account.Access = account.Access @@ -70,9 +70,9 @@ func HandleSetUser(cc *hotline.ClientConn, t *hotline.Transaction) (res []hotlin cc.SendAll( hotline.TranNotifyChangeUser, hotline.NewField(hotline.FieldUserID, c.ID[:]), - hotline.NewField(hotline.FieldUserFlags, c.Flags[:]), - hotline.NewField(hotline.FieldUserName, c.UserName), - hotline.NewField(hotline.FieldUserIconID, c.Icon), + hotline.NewField(hotline.FieldUserFlags, c.FlagBytes()), + hotline.NewField(hotline.FieldUserName, c.GetUserName()), + hotline.NewField(hotline.FieldUserIconID, c.GetIcon()), ) } } diff --git a/internal/mobius/handlers_chat.go b/internal/mobius/handlers_chat.go index bda4e58..b81cfa5 100644 --- a/internal/mobius/handlers_chat.go +++ b/internal/mobius/handlers_chat.go @@ -2,10 +2,8 @@ package mobius import ( "bytes" - "encoding/binary" "fmt" "io" - "math/big" "github.com/jhalter/mobius/hotline" ) @@ -28,14 +26,14 @@ func HandleChatSend(cc *hotline.ClientConn, t *hotline.Transaction) (res []hotli // Truncate long usernames // %13.13s: This means a string that is right-aligned in a field of 13 characters. // If the string is longer than 13 characters, it will be truncated to 13 characters. - formattedMsg := fmt.Sprintf("\r%13.13s: %s", cc.UserName, t.GetField(hotline.FieldData).Data) + formattedMsg := fmt.Sprintf("\r%13.13s: %s", cc.GetUserName(), t.GetField(hotline.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 set to a value of 1. // Most clients do not send this option for normal chat messages. if t.GetField(hotline.FieldChatOptions).Data != nil && bytes.Equal(t.GetField(hotline.FieldChatOptions).Data, []byte{0, 1}) { - formattedMsg = fmt.Sprintf("\r*** %s %s", cc.UserName, t.GetField(hotline.FieldData).Data) + formattedMsg = fmt.Sprintf("\r*** %s %s", cc.GetUserName(), t.GetField(hotline.FieldData).Data) } // Truncate the message to the limit. This does not handle the edge case of a string ending on multibyte character. @@ -103,7 +101,7 @@ func HandleSendInstantMsg(cc *hotline.ClientConn, t *hotline.Transaction) (res [ hotline.TranServerMsg, targetID, hotline.NewField(hotline.FieldData, msg.Data), - hotline.NewField(hotline.FieldUserName, cc.UserName), + hotline.NewField(hotline.FieldUserName, cc.GetUserName()), hotline.NewField(hotline.FieldUserID, cc.ID[:]), hotline.NewField(hotline.FieldOptions, []byte{0, 1}), ) @@ -122,13 +120,13 @@ func HandleSendInstantMsg(cc *hotline.ClientConn, t *hotline.Transaction) (res [ } // Check if target user has "Refuse private messages" flag - if otherClient.Flags.IsSet(hotline.UserFlagRefusePM) { + if otherClient.IsFlagSet(hotline.UserFlagRefusePM) { res = append(res, hotline.NewTransaction( hotline.TranServerMsg, cc.ID, - hotline.NewField(hotline.FieldData, []byte(fmt.Sprintf(ErrMsgDoesNotAcceptTemplate, string(otherClient.UserName), "private messages"))), - hotline.NewField(hotline.FieldUserName, otherClient.UserName), + hotline.NewField(hotline.FieldData, []byte(fmt.Sprintf(ErrMsgDoesNotAcceptTemplate, string(otherClient.GetUserName()), "private messages"))), + hotline.NewField(hotline.FieldUserName, otherClient.GetUserName()), hotline.NewField(hotline.FieldUserID, otherClient.ID[:]), hotline.NewField(hotline.FieldOptions, []byte{0, 2}), ), @@ -138,13 +136,13 @@ func HandleSendInstantMsg(cc *hotline.ClientConn, t *hotline.Transaction) (res [ } // Respond with auto reply if other client has it enabled - if len(otherClient.AutoReply) > 0 { + if len(otherClient.GetAutoReply()) > 0 { res = append(res, hotline.NewTransaction( hotline.TranServerMsg, cc.ID, - hotline.NewField(hotline.FieldData, otherClient.AutoReply), - hotline.NewField(hotline.FieldUserName, otherClient.UserName), + hotline.NewField(hotline.FieldData, otherClient.GetAutoReply()), + hotline.NewField(hotline.FieldUserName, otherClient.GetUserName()), hotline.NewField(hotline.FieldUserID, otherClient.ID[:]), hotline.NewField(hotline.FieldOptions, []byte{0, 1}), ), @@ -185,14 +183,13 @@ func HandleInviteNewChat(cc *hotline.ClientConn, t *hotline.Transaction) (res [] // Create a new chat with self as initial member. newChatID := cc.Server.ChatMgr.New(cc) - flagBitmap := big.NewInt(int64(binary.BigEndian.Uint16(targetClient.Flags[:]))) - if flagBitmap.Bit(hotline.UserFlagRefusePChat) == 1 { + if targetClient.IsFlagSet(hotline.UserFlagRefusePChat) { res = append(res, hotline.NewTransaction( hotline.TranServerMsg, cc.ID, - hotline.NewField(hotline.FieldData, []byte(fmt.Sprintf(ErrMsgDoesNotAcceptTemplate, string(targetClient.UserName), "private chats"))), - hotline.NewField(hotline.FieldUserName, targetClient.UserName), + hotline.NewField(hotline.FieldData, []byte(fmt.Sprintf(ErrMsgDoesNotAcceptTemplate, string(targetClient.GetUserName()), "private chats"))), + hotline.NewField(hotline.FieldUserName, targetClient.GetUserName()), hotline.NewField(hotline.FieldUserID, targetClient.ID[:]), hotline.NewField(hotline.FieldOptions, []byte{0, 2}), ), @@ -203,7 +200,7 @@ func HandleInviteNewChat(cc *hotline.ClientConn, t *hotline.Transaction) (res [] hotline.TranInviteToChat, targetID, hotline.NewField(hotline.FieldChatID, newChatID[:]), - hotline.NewField(hotline.FieldUserName, cc.UserName), + hotline.NewField(hotline.FieldUserName, cc.GetUserName()), hotline.NewField(hotline.FieldUserID, cc.ID[:]), ), ) @@ -213,10 +210,10 @@ func HandleInviteNewChat(cc *hotline.ClientConn, t *hotline.Transaction) (res [] res, cc.NewReply(t, hotline.NewField(hotline.FieldChatID, newChatID[:]), - hotline.NewField(hotline.FieldUserName, cc.UserName), + hotline.NewField(hotline.FieldUserName, cc.GetUserName()), hotline.NewField(hotline.FieldUserID, cc.ID[:]), - hotline.NewField(hotline.FieldUserIconID, cc.Icon), - hotline.NewField(hotline.FieldUserFlags, cc.Flags[:]), + hotline.NewField(hotline.FieldUserIconID, cc.GetIcon()), + hotline.NewField(hotline.FieldUserFlags, cc.FlagBytes()), ), ) } @@ -245,16 +242,16 @@ func HandleInviteToChat(cc *hotline.ClientConn, t *hotline.Transaction) (res []h hotline.TranInviteToChat, targetID, hotline.NewField(hotline.FieldChatID, chatID), - hotline.NewField(hotline.FieldUserName, cc.UserName), + hotline.NewField(hotline.FieldUserName, cc.GetUserName()), hotline.NewField(hotline.FieldUserID, cc.ID[:]), ), cc.NewReply( t, hotline.NewField(hotline.FieldChatID, chatID), - hotline.NewField(hotline.FieldUserName, cc.UserName), + hotline.NewField(hotline.FieldUserName, cc.GetUserName()), hotline.NewField(hotline.FieldUserID, cc.ID[:]), - hotline.NewField(hotline.FieldUserIconID, cc.Icon), - hotline.NewField(hotline.FieldUserFlags, cc.Flags[:]), + hotline.NewField(hotline.FieldUserIconID, cc.GetIcon()), + hotline.NewField(hotline.FieldUserFlags, cc.FlagBytes()), ), } } @@ -278,7 +275,7 @@ func HandleRejectChatInvite(cc *hotline.ClientConn, t *hotline.Transaction) (res hotline.TranChatMsg, c.ID, hotline.NewField(hotline.FieldChatID, chatID[:]), - hotline.NewField(hotline.FieldData, append(cc.UserName, []byte(" declined invitation to chat")...)), + hotline.NewField(hotline.FieldData, fmt.Appendf(nil, "%s declined invitation to chat", cc.GetUserName())), ), ) } @@ -307,10 +304,10 @@ func HandleJoinChat(cc *hotline.ClientConn, t *hotline.Transaction) (res []hotli hotline.TranNotifyChatChangeUser, c.ID, hotline.NewField(hotline.FieldChatID, chatID[:]), - hotline.NewField(hotline.FieldUserName, cc.UserName), + hotline.NewField(hotline.FieldUserName, cc.GetUserName()), hotline.NewField(hotline.FieldUserID, cc.ID[:]), - hotline.NewField(hotline.FieldUserIconID, cc.Icon), - hotline.NewField(hotline.FieldUserFlags, cc.Flags[:]), + hotline.NewField(hotline.FieldUserIconID, cc.GetIcon()), + hotline.NewField(hotline.FieldUserFlags, cc.FlagBytes()), ), ) } @@ -323,9 +320,9 @@ func HandleJoinChat(cc *hotline.ClientConn, t *hotline.Transaction) (res []hotli for _, c := range cc.Server.ChatMgr.Members(chatID) { b, err := io.ReadAll(&hotline.User{ ID: c.ID, - Icon: c.Icon, - Flags: c.Flags[:], - Name: string(c.UserName), + Icon: c.GetIcon(), + Flags: c.FlagBytes(), + Name: string(c.GetUserName()), }) if err != nil { cc.Logger.Error("join chat: read member info", "err", err) diff --git a/internal/mobius/handlers_news.go b/internal/mobius/handlers_news.go index 5946fa5..29b0ae2 100644 --- a/internal/mobius/handlers_news.go +++ b/internal/mobius/handlers_news.go @@ -33,7 +33,7 @@ func HandleTranOldPostNews(cc *hotline.ClientConn, t *hotline.Transaction) (res newsTemplate = cc.Server.Config.NewsDelimiter } - newsPost := fmt.Sprintf(newsTemplate+"\r", cc.UserName, time.Now().Format(newsDateTemplate), t.GetField(hotline.FieldData).Data) + newsPost := fmt.Sprintf(newsTemplate+"\r", cc.GetUserName(), time.Now().Format(newsDateTemplate), t.GetField(hotline.FieldData).Data) newsPost = strings.ReplaceAll(newsPost, "\n", "\r") _, err := cc.Server.MessageBoard.Write([]byte(newsPost)) @@ -341,7 +341,7 @@ func HandlePostNewsArt(cc *hotline.ClientConn, t *hotline.Transaction) (res []ho uint32(parentArticleID), hotline.NewsArtData{ Title: string(t.GetField(hotline.FieldNewsArtTitle).Data), - Poster: string(cc.UserName), + Poster: string(cc.GetUserName()), Date: hotline.NewTime(time.Now()), DataFlav: hotline.NewsFlavor, Data: string(t.GetField(hotline.FieldNewsArtData).Data), diff --git a/internal/mobius/handlers_session.go b/internal/mobius/handlers_session.go index ad6773b..f17ffa3 100644 --- a/internal/mobius/handlers_session.go +++ b/internal/mobius/handlers_session.go @@ -59,7 +59,7 @@ func HandleGetClientInfoText(cc *hotline.ClientConn, t *hotline.Transaction) (re return append(res, cc.NewReply(t, hotline.NewField(hotline.FieldData, []byte(clientConn.String())), - hotline.NewField(hotline.FieldUserName, clientConn.UserName), + hotline.NewField(hotline.FieldUserName, clientConn.GetUserName()), )) } @@ -74,9 +74,9 @@ func HandleGetUserNameList(cc *hotline.ClientConn, t *hotline.Transaction) (res for _, c := range cc.Server.ClientMgr.List() { b, err := io.ReadAll(&hotline.User{ ID: c.ID, - Icon: c.Icon, - Flags: c.Flags[:], - Name: string(c.UserName), + Icon: c.GetIcon(), + Flags: c.FlagBytes(), + Name: string(c.GetUserName()), }) if err != nil { cc.Logger.Error("get user name list: read user info", "err", err) @@ -101,9 +101,9 @@ func HandleGetUserNameList(cc *hotline.ClientConn, t *hotline.Transaction) (res func HandleTranAgreed(cc *hotline.ClientConn, t *hotline.Transaction) (res []hotline.Transaction) { if t.GetField(hotline.FieldUserName).Data != nil { if cc.Authorize(hotline.AccessAnyName) { - cc.UserName = t.GetField(hotline.FieldUserName).Data + cc.SetUserName(t.GetField(hotline.FieldUserName).Data) } else { - cc.UserName = []byte(cc.Account.Name) + cc.SetUserName([]byte(cc.Account.Name)) } } @@ -114,15 +114,15 @@ func HandleTranAgreed(cc *hotline.ClientConn, t *hotline.Transaction) (res []hot // Remove old entry (login::ip) cc.Server.Redis.SRem(context.Background(), hotline.RedisKeyOnline, login+"::"+ip) // Add new entry with login, nickname, ip - cc.Server.Redis.SAdd(context.Background(), hotline.RedisKeyOnline, login+":"+string(cc.UserName)+":"+ip) + cc.Server.Redis.SAdd(context.Background(), hotline.RedisKeyOnline, login+":"+string(cc.GetUserName())+":"+ip) } // Ban check for nickname - if cc.Server.BanList != nil && cc.Server.BanList.IsNicknameBanned(string(cc.UserName)) { + if cc.Server.BanList != nil && cc.Server.BanList.IsNicknameBanned(string(cc.GetUserName())) { if cc.Server.Redis != nil { // Remove all possible online entries for this login and IP cc.Server.Redis.SRem(context.Background(), hotline.RedisKeyOnline, login+"::"+ip) - cc.Server.Redis.SRem(context.Background(), hotline.RedisKeyOnline, login+":"+string(cc.UserName)+":"+ip) + cc.Server.Redis.SRem(context.Background(), hotline.RedisKeyOnline, login+":"+string(cc.GetUserName())+":"+ip) } if err := cc.Server.BanList.Add(ip, nil); err != nil { cc.Logger.Error("Failed to ban IP for banned nickname", "ip", ip, "err", err) @@ -132,35 +132,32 @@ func HandleTranAgreed(cc *hotline.ClientConn, t *hotline.Transaction) (res []hot return res } - cc.Icon = t.GetField(hotline.FieldUserIconID).Data + cc.SetIcon(t.GetField(hotline.FieldUserIconID).Data) - cc.Logger = cc.Logger.With("Name", string(cc.UserName)) + cc.Logger = cc.Logger.With("Name", string(cc.GetUserName())) cc.Logger.Info("Login successful") options := t.GetField(hotline.FieldOptions).Data optBitmap := big.NewInt(int64(binary.BigEndian.Uint16(options))) // Check refuse private PM option - - cc.FlagsMU.Lock() - defer cc.FlagsMU.Unlock() - cc.Flags.Set(hotline.UserFlagRefusePM, optBitmap.Bit(hotline.UserOptRefusePM)) + cc.SetFlag(hotline.UserFlagRefusePM, optBitmap.Bit(hotline.UserOptRefusePM)) // Check refuse private chat option - cc.Flags.Set(hotline.UserFlagRefusePChat, optBitmap.Bit(hotline.UserOptRefuseChat)) + cc.SetFlag(hotline.UserFlagRefusePChat, optBitmap.Bit(hotline.UserOptRefuseChat)) // Check auto response if optBitmap.Bit(hotline.UserOptAutoResponse) == 1 { - cc.AutoReply = t.GetField(hotline.FieldAutomaticResponse).Data + cc.SetAutoReply(t.GetField(hotline.FieldAutomaticResponse).Data) } trans := cc.NotifyOthers( hotline.NewTransaction( hotline.TranNotifyChangeUser, [2]byte{0, 0}, - hotline.NewField(hotline.FieldUserName, cc.UserName), + hotline.NewField(hotline.FieldUserName, cc.GetUserName()), hotline.NewField(hotline.FieldUserID, cc.ID[:]), - hotline.NewField(hotline.FieldUserIconID, cc.Icon), - hotline.NewField(hotline.FieldUserFlags, cc.Flags[:]), + hotline.NewField(hotline.FieldUserIconID, cc.GetIcon()), + hotline.NewField(hotline.FieldUserFlags, cc.FlagBytes()), ), ) res = append(res, trans...) @@ -210,7 +207,7 @@ func HandleDisconnectUser(cc *hotline.ClientConn, t *hotline.Transaction) (res [ switch options[1] { case 1: // send message: "You are temporarily banned on this server" - cc.Logger.Info("Disconnect & temporarily ban user", "username", string(clientConn.UserName)) + cc.Logger.Info("Disconnect & temporarily ban user", "username", string(clientConn.GetUserName())) res = append(res, hotline.NewTransaction( hotline.TranServerMsg, @@ -229,7 +226,7 @@ func HandleDisconnectUser(cc *hotline.ClientConn, t *hotline.Transaction) (res [ } case 2: // send message: "You are permanently banned on this server" - cc.Logger.Info("Disconnect & ban user", "username", string(clientConn.UserName)) + cc.Logger.Info("Disconnect & ban user", "username", string(clientConn.GetUserName())) res = append(res, hotline.NewTransaction( hotline.TranServerMsg, @@ -266,14 +263,14 @@ func HandleDisconnectUser(cc *hotline.ClientConn, t *hotline.Transaction) (res [ // Reply is not expected. func HandleSetClientUserInfo(cc *hotline.ClientConn, t *hotline.Transaction) (res []hotline.Transaction) { if len(t.GetField(hotline.FieldUserIconID).Data) == 4 { - cc.Icon = t.GetField(hotline.FieldUserIconID).Data[2:] + cc.SetIcon(t.GetField(hotline.FieldUserIconID).Data[2:]) } else { - cc.Icon = t.GetField(hotline.FieldUserIconID).Data + cc.SetIcon(t.GetField(hotline.FieldUserIconID).Data) } if cc.Authorize(hotline.AccessAnyName) { - oldNickname := string(cc.UserName) + oldNickname := string(cc.GetUserName()) newNickname := string(t.GetField(hotline.FieldUserName).Data) - cc.UserName = t.GetField(hotline.FieldUserName).Data + cc.SetUserName(t.GetField(hotline.FieldUserName).Data) login := cc.Account.Login ip := cc.IP() @@ -312,14 +309,14 @@ func HandleSetClientUserInfo(cc *hotline.ClientConn, t *hotline.Transaction) (re if options != nil { optBitmap := big.NewInt(int64(binary.BigEndian.Uint16(options))) - cc.Flags.Set(hotline.UserFlagRefusePM, optBitmap.Bit(hotline.UserOptRefusePM)) - cc.Flags.Set(hotline.UserFlagRefusePChat, optBitmap.Bit(hotline.UserOptRefuseChat)) + cc.SetFlag(hotline.UserFlagRefusePM, optBitmap.Bit(hotline.UserOptRefusePM)) + cc.SetFlag(hotline.UserFlagRefusePChat, optBitmap.Bit(hotline.UserOptRefuseChat)) // Check auto response if optBitmap.Bit(hotline.UserOptAutoResponse) == 1 { - cc.AutoReply = t.GetField(hotline.FieldAutomaticResponse).Data + cc.SetAutoReply(t.GetField(hotline.FieldAutomaticResponse).Data) } else { - cc.AutoReply = []byte{} + cc.SetAutoReply([]byte{}) } } @@ -328,9 +325,9 @@ func HandleSetClientUserInfo(cc *hotline.ClientConn, t *hotline.Transaction) (re hotline.TranNotifyChangeUser, c.ID, hotline.NewField(hotline.FieldUserID, cc.ID[:]), - hotline.NewField(hotline.FieldUserIconID, cc.Icon), - hotline.NewField(hotline.FieldUserFlags, cc.Flags[:]), - hotline.NewField(hotline.FieldUserName, cc.UserName), + hotline.NewField(hotline.FieldUserIconID, cc.GetIcon()), + hotline.NewField(hotline.FieldUserFlags, cc.FlagBytes()), + hotline.NewField(hotline.FieldUserName, cc.GetUserName()), )) } diff --git a/internal/mobius/handlers_transfers.go b/internal/mobius/handlers_transfers.go index dfb2389..315135e 100644 --- a/internal/mobius/handlers_transfers.go +++ b/internal/mobius/handlers_transfers.go @@ -277,7 +277,7 @@ func HandleUploadFile(cc *hotline.ClientConn, t *hotline.Transaction) (res []hot // - 108 Transfer size Size of data to be downloaded func HandleDownloadBanner(cc *hotline.ClientConn, t *hotline.Transaction) (res []hotline.Transaction) { ft := cc.NewFileTransfer(hotline.BannerDownload, "", []byte{}, []byte{}, make([]byte, 4)) - binary.BigEndian.PutUint32(ft.TransferSize, uint32(len(cc.Server.Banner))) + binary.BigEndian.PutUint32(ft.TransferSize, uint32(len(cc.Server.Banner()))) return append(res, cc.NewReply(t, hotline.NewField(hotline.FieldRefNum, ft.RefNum[:]), diff --git a/internal/mobius/handlers_transfers_test.go b/internal/mobius/handlers_transfers_test.go index 90eea2d..28c2768 100644 --- a/internal/mobius/handlers_transfers_test.go +++ b/internal/mobius/handlers_transfers_test.go @@ -318,12 +318,14 @@ func TestHandleDownloadFile(t *testing.T) { func TestHandleDownloadBanner(t *testing.T) { t.Run("returns banner transfer info", func(t *testing.T) { + srv := &hotline.Server{ + FileTransferMgr: hotline.NewMemFileTransferMgr(), + } + srv.SetBanner([]byte("test-banner-data")) + cc := &hotline.ClientConn{ ClientFileTransferMgr: hotline.NewClientFileTransferMgr(), - Server: &hotline.Server{ - Banner: []byte("test-banner-data"), - FileTransferMgr: hotline.NewMemFileTransferMgr(), - }, + Server: srv, } tran := hotline.NewTransaction(hotline.TranDownloadBanner, [2]byte{0, 1}) |