Просмотр исходного кода

toc: cleanup argument parsing

Remove reundant command extraction from the TOC handlers and
consolidate it in RecvClientCmd. Point all existing unit tests to
RecvClientCmd, which boosts tests coverage.
Mike 1 год назад
Родитель
Сommit
c8eb3b7daf
3 измененных файлов с 362 добавлено и 272 удалено
  1. 120 113
      server/toc/cmd_client.go
  2. 230 154
      server/toc/cmd_client_test.go
  3. 12 5
      server/toc/server.go

+ 120 - 113
server/toc/cmd_client.go

@@ -146,58 +146,58 @@ func (s OSCARProxy) RecvClientCmd(
 	payload []byte,
 	toCh chan<- []byte,
 	doAsync func(f func() error),
-) (reply string, ok bool) {
+) (reply string) {
 	cmd := payload
+	var args []byte
 	if idx := bytes.IndexByte(payload, ' '); idx > -1 {
-		cmd = cmd[:idx]
+		cmd, args = payload[:idx], payload[idx:]
 	}
 
 	if s.Logger.Enabled(ctx, slog.LevelDebug) {
-		s.Logger.DebugContext(ctx, "client request", "command", payload)
+		s.Logger.DebugContext(ctx, "client request", "command", args)
 	} else {
 		s.Logger.InfoContext(ctx, "client request", "command", cmd)
 	}
 
 	switch string(cmd) {
 	case "toc_send_im":
-		return s.SendIM(ctx, sessBOS, payload), true
+		return s.SendIM(ctx, sessBOS, args)
 	case "toc_init_done":
-		return s.InitDone(ctx, sessBOS, payload), true
+		return s.InitDone(ctx, sessBOS)
 	case "toc_add_buddy":
-		return s.AddBuddy(ctx, sessBOS, payload), true
+		return s.AddBuddy(ctx, sessBOS, args)
 	case "toc_get_status":
-		return s.GetStatus(ctx, sessBOS, payload), true
+		return s.GetStatus(ctx, sessBOS, args)
 	case "toc_remove_buddy":
-		return s.RemoveBuddy(ctx, sessBOS, payload), true
+		return s.RemoveBuddy(ctx, sessBOS, args)
 	case "toc_add_permit":
-		return s.AddPermit(ctx, sessBOS, payload), true
+		return s.AddPermit(ctx, sessBOS, args)
 	case "toc_add_deny":
-		return s.AddDeny(ctx, sessBOS, payload), true
+		return s.AddDeny(ctx, sessBOS, args)
 	case "toc_set_away":
-		return s.SetAway(ctx, sessBOS, payload), true
+		return s.SetAway(ctx, sessBOS, args)
 	case "toc_set_caps":
-		return s.SetCaps(ctx, sessBOS, payload), true
+		return s.SetCaps(ctx, sessBOS, args)
 	case "toc_evil":
-		return s.Evil(ctx, sessBOS, payload), true
+		return s.Evil(ctx, sessBOS, args)
 	case "toc_get_info":
-		return s.GetInfoURL(ctx, sessBOS, payload), true
+		return s.GetInfoURL(ctx, sessBOS, args)
 	case "toc_change_passwd":
-		return s.ChangePassword(ctx, sessBOS, payload), true
+		return s.ChangePassword(ctx, sessBOS, args)
 	case "toc_format_nickname":
-		return s.FormatNickname(ctx, sessBOS, payload), true
+		return s.FormatNickname(ctx, sessBOS, args)
 	case "toc_chat_join", "toc_chat_accept":
 		var chatID int
 		var msg string
 
 		if string(cmd) == "toc_chat_join" {
-			chatID, msg = s.ChatJoin(ctx, sessBOS, chatRegistry, payload)
+			chatID, msg = s.ChatJoin(ctx, sessBOS, chatRegistry, args)
 		} else {
-			chatID, msg = s.ChatAccept(ctx, sessBOS, chatRegistry, payload)
+			chatID, msg = s.ChatAccept(ctx, sessBOS, chatRegistry, args)
 		}
 
 		if msg == cmdInternalSvcErr {
-			// todo idk if this is worth cancelling the connection over
-			return "", false
+			return msg
 		}
 
 		doAsync(func() error {
@@ -206,35 +206,35 @@ func (s OSCARProxy) RecvClientCmd(
 			return nil
 		})
 
-		return msg, true
+		return msg
 	case "toc_chat_send":
-		return s.ChatSend(ctx, chatRegistry, payload), true
+		return s.ChatSend(ctx, chatRegistry, args)
 	case "toc_chat_whisper":
-		return s.ChatWhisper(ctx, chatRegistry, payload), true
+		return s.ChatWhisper(ctx, chatRegistry, args)
 	case "toc_chat_leave":
-		return s.ChatLeave(ctx, chatRegistry, payload), true
+		return s.ChatLeave(ctx, chatRegistry, args)
 	case "toc_set_info":
-		return s.SetInfo(ctx, sessBOS, payload), true
+		return s.SetInfo(ctx, sessBOS, args)
 	case "toc_set_dir":
-		return s.SetDir(ctx, sessBOS, payload), true
+		return s.SetDir(ctx, sessBOS, args)
 	case "toc_set_idle":
-		return s.SetIdle(ctx, sessBOS, payload), true
+		return s.SetIdle(ctx, sessBOS, args)
 	case "toc_set_config":
-		return s.SetConfig(ctx, sessBOS, payload), true
+		return s.SetConfig(ctx, sessBOS, args)
 	case "toc_chat_invite":
-		return s.ChatInvite(ctx, sessBOS, chatRegistry, payload), true
+		return s.ChatInvite(ctx, sessBOS, chatRegistry, args)
 	case "toc_dir_search":
-		return s.GetDirSearchURL(ctx, sessBOS, payload), true
+		return s.GetDirSearchURL(ctx, sessBOS, args)
 	case "toc_get_dir":
-		return s.GetDirURL(ctx, sessBOS, payload), true
+		return s.GetDirURL(ctx, sessBOS, args)
 	case "toc_rvous_accept":
-		return s.RvousAccept(ctx, sessBOS, payload), true
+		return s.RvousAccept(ctx, sessBOS, args)
 	case "toc_rvous_cancel":
-		return s.RvousCancel(ctx, sessBOS, payload), true
+		return s.RvousCancel(ctx, sessBOS, args)
 	}
 
 	s.Logger.ErrorContext(ctx, fmt.Sprintf("unsupported TOC command %s", cmd))
-	return "", true
+	return cmdInternalSvcErr
 }
 
 // AddBuddy handles the toc_add_buddy TOC command.
@@ -244,8 +244,8 @@ func (s OSCARProxy) RecvClientCmd(
 //	Add buddies to your buddy list. This does not change your saved config.
 //
 // Command syntax: toc_add_buddy <Buddy User 1> [<Buddy User2> [<Buddy User 3> [...]]]
-func (s OSCARProxy) AddBuddy(ctx context.Context, me *state.Session, cmd []byte) string {
-	users, err := parseArgs(cmd, "toc_add_buddy")
+func (s OSCARProxy) AddBuddy(ctx context.Context, me *state.Session, args []byte) string {
+	users, err := parseArgs(args)
 	if err != nil {
 		return s.runtimeErr(ctx, fmt.Errorf("parseArgs: %w", err))
 	}
@@ -274,8 +274,8 @@ func (s OSCARProxy) AddBuddy(ctx context.Context, me *state.Session, cmd []byte)
 //	arguments does nothing and your permit list remains the same.
 //
 // Command syntax: toc_add_permit [ <User 1> [<User 2> [...]]]
-func (s OSCARProxy) AddPermit(ctx context.Context, me *state.Session, cmd []byte) string {
-	users, err := parseArgs(cmd, "toc_add_permit")
+func (s OSCARProxy) AddPermit(ctx context.Context, me *state.Session, args []byte) string {
+	users, err := parseArgs(args)
 	if err != nil {
 		return s.runtimeErr(ctx, fmt.Errorf("parseArgs: %w", err))
 	}
@@ -303,8 +303,8 @@ func (s OSCARProxy) AddPermit(ctx context.Context, me *state.Session, cmd []byte
 //	does nothing and your deny list remains unchanged.
 //
 // Command syntax: toc_add_deny [ <User 1> [<User 2> [...]]]
-func (s OSCARProxy) AddDeny(ctx context.Context, me *state.Session, cmd []byte) string {
-	users, err := parseArgs(cmd, "toc_add_deny")
+func (s OSCARProxy) AddDeny(ctx context.Context, me *state.Session, args []byte) string {
+	users, err := parseArgs(args)
 	if err != nil {
 		return s.runtimeErr(ctx, fmt.Errorf("parseArgs: %w", err))
 	}
@@ -330,10 +330,10 @@ func (s OSCARProxy) AddDeny(ctx context.Context, me *state.Session, cmd []byte)
 //	sent back to the client.
 //
 // Command syntax: toc_change_passwd <existing_passwd> <new_passwd>
-func (s OSCARProxy) ChangePassword(ctx context.Context, me *state.Session, cmd []byte) string {
+func (s OSCARProxy) ChangePassword(ctx context.Context, me *state.Session, args []byte) string {
 	var oldPass, newPass string
 
-	if _, err := parseArgs(cmd, "toc_change_passwd", &oldPass, &newPass); err != nil {
+	if _, err := parseArgs(args, &oldPass, &newPass); err != nil {
 		return s.runtimeErr(ctx, fmt.Errorf("parseArgs: %w", err))
 	}
 
@@ -383,11 +383,11 @@ func (s OSCARProxy) ChatAccept(
 	ctx context.Context,
 	me *state.Session,
 	chatRegistry *ChatRegistry,
-	cmd []byte,
+	args []byte,
 ) (int, string) {
 	var chatIDStr string
 
-	if _, err := parseArgs(cmd, "toc_chat_accept", &chatIDStr); err != nil {
+	if _, err := parseArgs(args, &chatIDStr); err != nil {
 		return 0, s.runtimeErr(ctx, fmt.Errorf("parseArgs: %w", err))
 	}
 
@@ -412,7 +412,10 @@ func (s OSCARProxy) ChatAccept(
 
 	reqRoomReplyBody, ok := reqRoomReply.Body.(wire.SNAC_0x0D_0x09_ChatNavNavInfo)
 	if !ok {
-		return 0, s.runtimeErr(ctx, fmt.Errorf("chatNavService.RequestRoomInfo: unexpected response type %v", reqRoomReplyBody))
+		return 0, s.runtimeErr(
+			ctx,
+			fmt.Errorf("chatNavService.RequestRoomInfo: unexpected response type %v", reqRoomReplyBody),
+		)
 	}
 	b, hasInfo := reqRoomReplyBody.Bytes(wire.ChatNavTLVRoomInfo)
 	if !hasInfo {
@@ -446,7 +449,10 @@ func (s OSCARProxy) ChatAccept(
 
 	svcReqReplyBody, ok := svcReqReply.Body.(wire.SNAC_0x01_0x05_OServiceServiceResponse)
 	if !ok {
-		return 0, s.runtimeErr(ctx, fmt.Errorf("OServiceServiceBOS.ServiceRequest: unexpected response type %v", svcReqReplyBody))
+		return 0, s.runtimeErr(
+			ctx,
+			fmt.Errorf("OServiceServiceBOS.ServiceRequest: unexpected response type %v", svcReqReplyBody),
+		)
 	}
 
 	loginCookie, hasCookie := svcReqReplyBody.Bytes(wire.OServiceTLVTagsLoginCookie)
@@ -459,12 +465,12 @@ func (s OSCARProxy) ChatAccept(
 		return 0, s.runtimeErr(ctx, fmt.Errorf("AuthService.RegisterChatSession: %w", err))
 	}
 
-	chatRegistry.RegisterSess(chatID, chatSess)
-
 	if err := s.OServiceServiceChat.ClientOnline(ctx, wire.SNAC_0x01_0x02_OServiceClientOnline{}, chatSess); err != nil {
 		return 0, s.runtimeErr(ctx, fmt.Errorf("OServiceServiceChat.ClientOnline: %w", err))
 	}
 
+	chatRegistry.RegisterSess(chatID, chatSess)
+
 	return chatID, fmt.Sprintf("CHAT_JOIN:%d:%s", chatID, roomName)
 }
 
@@ -476,10 +482,10 @@ func (s OSCARProxy) ChatAccept(
 //	Remember to quote and encode the invite message.
 //
 // Command syntax: toc_chat_invite <Chat Room ID> <Invite Msg> <buddy1> [<buddy2> [<buddy3> [...]]]
-func (s OSCARProxy) ChatInvite(ctx context.Context, me *state.Session, chatRegistry *ChatRegistry, cmd []byte) string {
+func (s OSCARProxy) ChatInvite(ctx context.Context, me *state.Session, chatRegistry *ChatRegistry, args []byte) string {
 	var chatRoomIDStr, msg string
 
-	users, err := parseArgs(cmd, "toc_chat_invite", &chatRoomIDStr, &msg)
+	users, err := parseArgs(args, &chatRoomIDStr, &msg)
 	if err != nil {
 		return s.runtimeErr(ctx, fmt.Errorf("parseArgs: %w", err))
 	}
@@ -543,11 +549,11 @@ func (s OSCARProxy) ChatJoin(
 	ctx context.Context,
 	me *state.Session,
 	chatRegistry *ChatRegistry,
-	cmd []byte,
+	args []byte,
 ) (int, string) {
 	var exchangeStr, roomName string
 
-	if _, err := parseArgs(cmd, "toc_chat_join", &exchangeStr, &roomName); err != nil {
+	if _, err := parseArgs(args, &exchangeStr, &roomName); err != nil {
 		return 0, s.runtimeErr(ctx, fmt.Errorf("parseArgs: %w", err))
 	}
 
@@ -573,7 +579,10 @@ func (s OSCARProxy) ChatJoin(
 
 	mkRoomReplyBody, ok := mkRoomReply.Body.(wire.SNAC_0x0D_0x09_ChatNavNavInfo)
 	if !ok {
-		return 0, s.runtimeErr(ctx, fmt.Errorf("chatNavService.CreateRoom: unexpected response type %v", mkRoomReplyBody))
+		return 0, s.runtimeErr(
+			ctx,
+			fmt.Errorf("chatNavService.CreateRoom: unexpected response type %v", mkRoomReplyBody),
+		)
 	}
 	buf, ok := mkRoomReplyBody.Bytes(wire.ChatNavTLVRoomInfo)
 	if !ok {
@@ -602,7 +611,10 @@ func (s OSCARProxy) ChatJoin(
 
 	svcReqReplyBody, ok := svcReqReply.Body.(wire.SNAC_0x01_0x05_OServiceServiceResponse)
 	if !ok {
-		return 0, s.runtimeErr(ctx, fmt.Errorf("OServiceServiceBOS.ServiceRequest: unexpected response type %v", svcReqReplyBody))
+		return 0, s.runtimeErr(
+			ctx,
+			fmt.Errorf("OServiceServiceBOS.ServiceRequest: unexpected response type %v", svcReqReplyBody),
+		)
 	}
 
 	loginCookie, hasCookie := svcReqReplyBody.Bytes(wire.OServiceTLVTagsLoginCookie)
@@ -615,6 +627,10 @@ func (s OSCARProxy) ChatJoin(
 		return 0, s.runtimeErr(ctx, fmt.Errorf("AuthService.RegisterChatSession: %w", err))
 	}
 
+	if err := s.OServiceServiceChat.ClientOnline(ctx, wire.SNAC_0x01_0x02_OServiceClientOnline{}, chatSess); err != nil {
+		return 0, s.runtimeErr(ctx, fmt.Errorf("OServiceServiceChat.ClientOnline: %w", err))
+	}
+
 	roomInfo := wire.ICBMRoomInfo{
 		Exchange: inBody.Exchange,
 		Cookie:   inBody.Cookie,
@@ -623,10 +639,6 @@ func (s OSCARProxy) ChatJoin(
 	chatID := chatRegistry.Add(roomInfo)
 	chatRegistry.RegisterSess(chatID, chatSess)
 
-	if err := s.OServiceServiceChat.ClientOnline(ctx, wire.SNAC_0x01_0x02_OServiceClientOnline{}, chatSess); err != nil {
-		return 0, s.runtimeErr(ctx, fmt.Errorf("OServiceServiceChat.ClientOnline: %w", err))
-	}
-
 	return chatID, fmt.Sprintf("CHAT_JOIN:%d:%s", chatID, roomName)
 }
 
@@ -637,10 +649,10 @@ func (s OSCARProxy) ChatJoin(
 //	Leave the chat room.
 //
 // Command syntax: toc_chat_leave <Chat Room ID>
-func (s OSCARProxy) ChatLeave(ctx context.Context, chatRegistry *ChatRegistry, cmd []byte) string {
+func (s OSCARProxy) ChatLeave(ctx context.Context, chatRegistry *ChatRegistry, args []byte) string {
 	var chatIDStr string
 
-	if _, err := parseArgs(cmd, "toc_chat_leave", &chatIDStr); err != nil {
+	if _, err := parseArgs(args, &chatIDStr); err != nil {
 		return s.runtimeErr(ctx, fmt.Errorf("parseArgs: %w", err))
 	}
 
@@ -673,10 +685,10 @@ func (s OSCARProxy) ChatLeave(ctx context.Context, chatRegistry *ChatRegistry, c
 //	and encode the message.
 //
 // Command syntax: toc_chat_send <Chat Room ID> <Message>
-func (s OSCARProxy) ChatSend(ctx context.Context, chatRegistry *ChatRegistry, cmd []byte) string {
+func (s OSCARProxy) ChatSend(ctx context.Context, chatRegistry *ChatRegistry, args []byte) string {
 	var chatIDStr, msg string
 
-	if _, err := parseArgs(cmd, "toc_chat_send", &chatIDStr, &msg); err != nil {
+	if _, err := parseArgs(args, &chatIDStr, &msg); err != nil {
 		return s.runtimeErr(ctx, fmt.Errorf("parseArgs: %w", err))
 	}
 
@@ -753,10 +765,10 @@ func (s OSCARProxy) ChatSend(ctx context.Context, chatRegistry *ChatRegistry, cm
 //	be displayed in the chat room UI.
 //
 // Command syntax: toc_chat_whisper <Chat Room ID> <dst_user> <Message>
-func (s OSCARProxy) ChatWhisper(ctx context.Context, chatRegistry *ChatRegistry, cmd []byte) string {
+func (s OSCARProxy) ChatWhisper(ctx context.Context, chatRegistry *ChatRegistry, args []byte) string {
 	var chatIDStr, recip, msg string
 
-	if _, err := parseArgs(cmd, "toc_chat_whisper", &chatIDStr, &recip, &msg); err != nil {
+	if _, err := parseArgs(args, &chatIDStr, &recip, &msg); err != nil {
 		return s.runtimeErr(ctx, fmt.Errorf("parseArgs: %w", err))
 	}
 
@@ -800,10 +812,10 @@ func (s OSCARProxy) ChatWhisper(ctx context.Context, chatRegistry *ChatRegistry,
 //	slower they can send message.
 //
 // Command syntax: toc_evil <User> <norm|anon>
-func (s OSCARProxy) Evil(ctx context.Context, me *state.Session, cmd []byte) string {
+func (s OSCARProxy) Evil(ctx context.Context, me *state.Session, args []byte) string {
 	var user, scope string
 
-	if _, err := parseArgs(cmd, "toc_evil", &user, &scope); err != nil {
+	if _, err := parseArgs(args, &user, &scope); err != nil {
 		return s.runtimeErr(ctx, fmt.Errorf("parseArgs: %w", err))
 	}
 
@@ -845,10 +857,10 @@ func (s OSCARProxy) Evil(ctx context.Context, me *state.Session, cmd []byte) str
 //	sent back to the client.
 //
 // Command syntax: toc_format_nickname <new_format>
-func (s OSCARProxy) FormatNickname(ctx context.Context, me *state.Session, cmd []byte) string {
+func (s OSCARProxy) FormatNickname(ctx context.Context, me *state.Session, args []byte) string {
 	var newFormat string
 
-	if _, err := parseArgs(cmd, "toc_format_nickname", &newFormat); err != nil {
+	if _, err := parseArgs(args, &newFormat); err != nil {
 		return s.runtimeErr(ctx, fmt.Errorf("parseArgs: %w", err))
 	}
 
@@ -902,10 +914,10 @@ func (s OSCARProxy) FormatNickname(ctx context.Context, me *state.Session, cmd [
 //	Returns either a GOTO_URL or ERROR msg.
 //
 // Command syntax: toc_dir_search <info information>
-func (s OSCARProxy) GetDirSearchURL(ctx context.Context, me *state.Session, cmd []byte) string {
+func (s OSCARProxy) GetDirSearchURL(ctx context.Context, me *state.Session, args []byte) string {
 	var info string
 
-	if _, err := parseArgs(cmd, "toc_dir_search", &info); err != nil {
+	if _, err := parseArgs(args, &info); err != nil {
 		return s.runtimeErr(ctx, fmt.Errorf("parseArgs: %w", err))
 	}
 
@@ -955,10 +967,10 @@ func (s OSCARProxy) GetDirSearchURL(ctx context.Context, me *state.Session, cmd
 //	Gets a user's dir info a GOTO_URL or ERROR message will be sent back to the client.
 //
 // Command syntax: toc_get_dir <username>
-func (s OSCARProxy) GetDirURL(ctx context.Context, me *state.Session, cmd []byte) string {
+func (s OSCARProxy) GetDirURL(ctx context.Context, me *state.Session, args []byte) string {
 	var user string
 
-	if _, err := parseArgs(cmd, "toc_get_dir", &user); err != nil {
+	if _, err := parseArgs(args, &user); err != nil {
 		return s.runtimeErr(ctx, fmt.Errorf("parseArgs: %w", err))
 	}
 
@@ -981,10 +993,10 @@ func (s OSCARProxy) GetDirURL(ctx context.Context, me *state.Session, cmd []byte
 //	Gets a user's info a GOTO_URL or ERROR message will be sent back to the client.
 //
 // Command syntax: toc_get_info <username>
-func (s OSCARProxy) GetInfoURL(ctx context.Context, me *state.Session, cmd []byte) string {
+func (s OSCARProxy) GetInfoURL(ctx context.Context, me *state.Session, args []byte) string {
 	var user string
 
-	if _, err := parseArgs(cmd, "toc_get_info", &user); err != nil {
+	if _, err := parseArgs(args, &user); err != nil {
 		return s.runtimeErr(ctx, fmt.Errorf("parseArgs: %w", err))
 	}
 
@@ -1010,10 +1022,10 @@ func (s OSCARProxy) GetInfoURL(ctx context.Context, me *state.Session, cmd []byt
 //	guy appears to be online.
 //
 // Command syntax: toc_get_status <screenname>
-func (s OSCARProxy) GetStatus(ctx context.Context, me *state.Session, cmd []byte) string {
+func (s OSCARProxy) GetStatus(ctx context.Context, me *state.Session, args []byte) string {
 	var them string
 
-	if _, err := parseArgs(cmd, "toc_get_status", &them); err != nil {
+	if _, err := parseArgs(args, &them); err != nil {
 		return s.runtimeErr(ctx, fmt.Errorf("parseArgs: %w", err))
 	}
 
@@ -1055,10 +1067,7 @@ func (s OSCARProxy) GetStatus(ctx context.Context, me *state.Session, cmd []byte
 // implemented.
 //
 // Command syntax: toc_init_done
-func (s OSCARProxy) InitDone(ctx context.Context, sess *state.Session, cmd []byte) string {
-	if _, err := parseArgs(cmd, "toc_init_done"); err != nil {
-		return s.runtimeErr(ctx, fmt.Errorf("parseArgs: %w", err))
-	}
+func (s OSCARProxy) InitDone(ctx context.Context, sess *state.Session) string {
 	if err := s.OServiceServiceBOS.ClientOnline(ctx, wire.SNAC_0x01_0x02_OServiceClientOnline{}, sess); err != nil {
 		return s.runtimeErr(ctx, fmt.Errorf("OServiceServiceBOS.ClientOnliney: %w", err))
 	}
@@ -1072,8 +1081,8 @@ func (s OSCARProxy) InitDone(ctx context.Context, sess *state.Session, cmd []byt
 //	Remove buddies from your buddy list. This does not change your saved config.
 //
 // Command syntax: toc_remove_buddy <Buddy User 1> [<Buddy User2> [<Buddy User 3> [...]]]
-func (s OSCARProxy) RemoveBuddy(ctx context.Context, me *state.Session, cmd []byte) string {
-	users, err := parseArgs(cmd, "toc_remove_buddy")
+func (s OSCARProxy) RemoveBuddy(ctx context.Context, me *state.Session, args []byte) string {
+	users, err := parseArgs(args)
 	if err != nil {
 		return s.runtimeErr(ctx, fmt.Errorf("parseArgs: %w", err))
 	}
@@ -1103,10 +1112,10 @@ func (s OSCARProxy) RemoveBuddy(ctx context.Context, me *state.Session, cmd []by
 // passed in the TiK client, the reference implementation.
 //
 // Command syntax: toc_rvous_accept <nick> <cookie> <service>
-func (s OSCARProxy) RvousAccept(ctx context.Context, me *state.Session, cmd []byte) string {
+func (s OSCARProxy) RvousAccept(ctx context.Context, me *state.Session, args []byte) string {
 	var nick, cookie, service string
 
-	if _, err := parseArgs(cmd, "toc_rvous_accept", &nick, &cookie, &service); err != nil {
+	if _, err := parseArgs(args, &nick, &cookie, &service); err != nil {
 		return s.runtimeErr(ctx, fmt.Errorf("parseArgs: %w", err))
 	}
 
@@ -1156,10 +1165,10 @@ func (s OSCARProxy) RvousAccept(ctx context.Context, me *state.Session, cmd []by
 // passed in the TiK client, the reference implementation.
 //
 // Command syntax: toc_rvous_cancel <nick> <cookie> <service>
-func (s OSCARProxy) RvousCancel(ctx context.Context, me *state.Session, cmd []byte) string {
+func (s OSCARProxy) RvousCancel(ctx context.Context, me *state.Session, args []byte) string {
 	var nick, cookie, service string
 
-	if _, err := parseArgs(cmd, "toc_rvous_cancel", &nick, &cookie, &service); err != nil {
+	if _, err := parseArgs(args, &nick, &cookie, &service); err != nil {
 		return s.runtimeErr(ctx, fmt.Errorf("parseArgs: %w", err))
 	}
 
@@ -1211,10 +1220,10 @@ func (s OSCARProxy) RvousCancel(ctx context.Context, me *state.Session, cmd []by
 //	flag will be turned on for the IM.
 //
 // Command syntax: toc_send_im <Destination User> <Message> [auto]
-func (s OSCARProxy) SendIM(ctx context.Context, sender *state.Session, cmd []byte) string {
+func (s OSCARProxy) SendIM(ctx context.Context, sender *state.Session, args []byte) string {
 	var recip, msg string
 
-	autoReply, err := parseArgs(cmd, "toc_send_im", &recip, &msg)
+	autoReply, err := parseArgs(args, &recip, &msg)
 	if err != nil {
 		return s.runtimeErr(ctx, fmt.Errorf("parseArgs: %w", err))
 	}
@@ -1258,8 +1267,8 @@ func (s OSCARProxy) SendIM(ctx context.Context, sender *state.Session, cmd []byt
 //	information.
 //
 // Command syntax: toc_set_away [<away message>]
-func (s OSCARProxy) SetAway(ctx context.Context, me *state.Session, cmd []byte) string {
-	maybeMsg, err := parseArgs(cmd, "toc_set_away")
+func (s OSCARProxy) SetAway(ctx context.Context, me *state.Session, args []byte) string {
+	maybeMsg, err := parseArgs(args)
 	if err != nil {
 		return s.runtimeErr(ctx, fmt.Errorf("parseArgs: %w", err))
 	}
@@ -1296,8 +1305,8 @@ func (s OSCARProxy) SetAway(ctx context.Context, me *state.Session, cmd []byte)
 // chat.
 //
 // Command syntax: toc_set_caps [ <Capability 1> [<Capability 2> [...]]]
-func (s OSCARProxy) SetCaps(ctx context.Context, me *state.Session, cmd []byte) string {
-	params, err := parseArgs(cmd, "toc_set_caps")
+func (s OSCARProxy) SetCaps(ctx context.Context, me *state.Session, args []byte) string {
+	params, err := parseArgs(args)
 	if err != nil {
 		return s.runtimeErr(ctx, fmt.Errorf("parseArgs: %w", err))
 	}
@@ -1310,6 +1319,8 @@ func (s OSCARProxy) SetCaps(ctx context.Context, me *state.Session, cmd []byte)
 		}
 		caps = append(caps, uid)
 	}
+	// assume client supports chat, although we may want to do this according
+	// to client ID
 	caps = append(caps, wire.CapChat)
 
 	snac := wire.SNAC_0x02_0x04_LocateSetInfo{
@@ -1349,18 +1360,18 @@ func (s OSCARProxy) SetCaps(ctx context.Context, me *state.Session, cmd []byte)
 //		- 4 - Deny Some
 //
 // Command syntax: toc_set_config <Config Info>
-func (s OSCARProxy) SetConfig(ctx context.Context, me *state.Session, cmd []byte) string {
+func (s OSCARProxy) SetConfig(ctx context.Context, me *state.Session, args []byte) string {
 	// replace curly braces with quotes so that the string can be properly
 	// split up by the space-delimited reader
-	for i, c := range cmd {
+	for i, c := range args {
 		if c == '{' || c == '}' {
-			cmd[i] = '"'
+			args[i] = '"'
 		}
 	}
-	cmd = bytes.TrimSpace(cmd)
+	args = bytes.TrimSpace(args)
 
 	var info string
-	if _, err := parseArgs(cmd, "toc_set_config", &info); err != nil {
+	if _, err := parseArgs(args, &info); err != nil {
 		return s.runtimeErr(ctx, fmt.Errorf("parseArgs: %w", err))
 	}
 
@@ -1486,10 +1497,10 @@ func (s OSCARProxy) SetConfig(ctx context.Context, me *state.Session, cmd []byte
 // The fields "email" and "allow web searches" are ignored by this method.
 //
 // Command syntax: toc_set_dir <info information>
-func (s OSCARProxy) SetDir(ctx context.Context, me *state.Session, cmd []byte) string {
+func (s OSCARProxy) SetDir(ctx context.Context, me *state.Session, args []byte) string {
 	var info string
 
-	if _, err := parseArgs(cmd, "toc_set_dir", &info); err != nil {
+	if _, err := parseArgs(args, &info); err != nil {
 		return s.runtimeErr(ctx, fmt.Errorf("parseArgs: %w", err))
 	}
 
@@ -1534,10 +1545,10 @@ func (s OSCARProxy) SetDir(ctx context.Context, me *state.Session, cmd []byte) s
 //	incrementing this number, so do not repeatedly call with new idle times.
 //
 // Command syntax: toc_set_idle <idle secs>
-func (s OSCARProxy) SetIdle(ctx context.Context, me *state.Session, cmd []byte) string {
+func (s OSCARProxy) SetIdle(ctx context.Context, me *state.Session, args []byte) string {
 	var idleTimeStr string
 
-	if _, err := parseArgs(cmd, "toc_set_idle", &idleTimeStr); err != nil {
+	if _, err := parseArgs(args, &idleTimeStr); err != nil {
 		return s.runtimeErr(ctx, fmt.Errorf("parseArgs: %w", err))
 	}
 
@@ -1563,10 +1574,10 @@ func (s OSCARProxy) SetIdle(ctx context.Context, me *state.Session, cmd []byte)
 //	Set the LOCATE user information. This is basic HTML. Remember to encode the info.
 //
 // Command syntax: toc_set_info <info information>
-func (s OSCARProxy) SetInfo(ctx context.Context, me *state.Session, cmd []byte) string {
+func (s OSCARProxy) SetInfo(ctx context.Context, me *state.Session, args []byte) string {
 	var info string
 
-	if _, err := parseArgs(cmd, "toc_set_info", &info); err != nil {
+	if _, err := parseArgs(args, &info); err != nil {
 		return s.runtimeErr(ctx, fmt.Errorf("parseArgs: %w", err))
 	}
 
@@ -1606,10 +1617,10 @@ func (s OSCARProxy) SetInfo(ctx context.Context, me *state.Session, cmd []byte)
 //	The Roasting String is Tic/Toc.
 //
 // Command syntax: toc_signon <authorizer host> <authorizer port> <User Name> <Password> <language> <version>
-func (s OSCARProxy) Signon(ctx context.Context, cmd []byte) (*state.Session, []string) {
+func (s OSCARProxy) Signon(ctx context.Context, args []byte) (*state.Session, []string) {
 	var userName, password string
 
-	if _, err := parseArgs(cmd, "toc_signon", nil, nil, &userName, &password); err != nil {
+	if _, err := parseArgs(args, nil, nil, &userName, &password); err != nil {
 		return nil, []string{s.runtimeErr(ctx, fmt.Errorf("parseArgs: %w", err))}
 	}
 
@@ -1691,7 +1702,10 @@ func (s OSCARProxy) newHTTPAuthToken(me state.IdentScreenName) (string, error) {
 // parseArgs extracts arguments from a TOC command. Each positional argument is
 // assigned to its corresponding args pointer. It returns the remaining
 // arguments as varargs.
-func parseArgs(payload []byte, cmd string, args ...*string) (varArgs []string, err error) {
+func parseArgs(payload []byte, args ...*string) (varArgs []string, err error) {
+	if len(payload) == 0 && len(args) == 0 {
+		return []string{}, nil
+	}
 	reader := csv.NewReader(bytes.NewReader(payload))
 	reader.Comma = ' '
 	reader.LazyQuotes = true
@@ -1702,13 +1716,6 @@ func parseArgs(payload []byte, cmd string, args ...*string) (varArgs []string, e
 		return []string{}, fmt.Errorf("CSV reader error: %w", err)
 	}
 
-	// sanity check the command name
-	if segs[0] != cmd {
-		return []string{}, fmt.Errorf("command mismatch. expected %s, got %s", cmd, segs[0])
-	}
-
-	// all elements after the command are arguments
-	segs = segs[1:]
 	if len(segs) < len(args) {
 		return []string{}, fmt.Errorf("command contains fewer arguments than expected")
 	}

Разница между файлами не показана из-за своего большого размера
+ 230 - 154
server/toc/cmd_client_test.go


+ 12 - 5
server/toc/server.go

@@ -287,10 +287,7 @@ func (rt Server) runClientCommands(ctx context.Context, doAsync func(f func() er
 				return errors.New("TOC command exceeds maximum length (2048)")
 			}
 
-			msg, ok := rt.BOSProxy.RecvClientCmd(ctx, sessBOS, chatRegistry, clientFrame.Payload, toCh, doAsync)
-			if !ok {
-				return io.EOF
-			}
+			msg := rt.BOSProxy.RecvClientCmd(ctx, sessBOS, chatRegistry, clientFrame.Payload, toCh, doAsync)
 			if len(msg) > 0 {
 				select {
 				case toCh <- []byte(msg):
@@ -336,7 +333,17 @@ func (rt Server) login(ctx context.Context, clientFlap *wire.FlapClient) (*state
 		return nil, fmt.Errorf("clientFlap.ReceiveFLAP: %w", err)
 	}
 
-	sessBOS, reply := rt.BOSProxy.Signon(ctx, clientFrame.Payload)
+	cmd := clientFrame.Payload
+	var args []byte
+
+	if idx := bytes.IndexByte(clientFrame.Payload, ' '); idx > -1 {
+		cmd, args = clientFrame.Payload[:idx], clientFrame.Payload[idx:]
+	}
+	if string(cmd) != "toc_signon" {
+		return nil, errors.New("expected toc_signon")
+	}
+
+	sessBOS, reply := rt.BOSProxy.Signon(ctx, args)
 	for _, m := range reply {
 		if err := clientFlap.SendDataFrame([]byte(m)); err != nil {
 			return nil, fmt.Errorf("clientFlap.SendDataFrame: %w", err)

Некоторые файлы не были показаны из-за большого количества измененных файлов