Mike 1 год назад
Родитель
Сommit
a6f9a6aee3
3 измененных файлов с 217 добавлено и 9 удалено
  1. 41 0
      server/toc/cmd_client.go
  2. 161 0
      server/toc/cmd_client_test.go
  3. 15 9
      server/toc/cmd_server.go

+ 41 - 0
server/toc/cmd_client.go

@@ -151,6 +151,8 @@ func (s OSCARProxy) RecvClientCmd(
 		return s.InitDone(ctx, sessBOS, payload), true
 	case "toc_add_buddy":
 		return s.AddBuddy(ctx, sessBOS, payload), true
+	case "toc_get_status":
+		return s.GetStatus(ctx, sessBOS, payload), true
 	case "toc_remove_buddy":
 		return s.RemoveBuddy(ctx, sessBOS, payload), true
 	case "toc_add_permit":
@@ -929,6 +931,45 @@ func (s OSCARProxy) GetInfoURL(ctx context.Context, me *state.Session, cmd []byt
 	return fmt.Sprintf("GOTO_URL:profile:info?%s", p.Encode())
 }
 
+// GetStatus handles the toc_get_status TOC command.
+//
+// From the TOC2 documentation:
+//
+//	This useful command wasn't ever really documented. It returns either an
+//	UPDATE_BUDDY message or an ERROR message depending on whether or not the
+//	guy appears to be online.
+//
+// Command syntax: toc_get_status <screenname>
+func (s OSCARProxy) GetStatus(ctx context.Context, me *state.Session, cmd []byte) string {
+	var them string
+
+	if _, err := parseArgs(cmd, "toc_get_status", &them); err != nil {
+		return s.runtimeErr(ctx, fmt.Errorf("parseArgs: %w", err))
+	}
+
+	inBody := wire.SNAC_0x02_0x05_LocateUserInfoQuery{
+		ScreenName: them,
+	}
+
+	info, err := s.LocateService.UserInfoQuery(ctx, me, wire.SNACFrame{}, inBody)
+	if err != nil {
+		return s.runtimeErr(ctx, fmt.Errorf("LocateService.UserInfoQuery: %w", err))
+	}
+
+	switch v := info.Body.(type) {
+	case wire.SNACError:
+		if v.Code == wire.ErrorCodeNotLoggedOn {
+			return fmt.Sprintf("ERROR:901:%s", them)
+		} else {
+			return s.runtimeErr(ctx, fmt.Errorf("LocateService.UserInfoQuery error code: %d", v.Code))
+		}
+	case wire.SNAC_0x02_0x06_LocateUserInfoReply:
+		return userInfoToUpdateBuddy(v.TLVUserInfo)
+	default:
+		return s.runtimeErr(ctx, fmt.Errorf("AdminService.InfoChangeRequest: unexpected response type %v", v))
+	}
+}
+
 // InitDone handles the toc_init_done TOC command.
 //
 // From the TiK documentation:

+ 161 - 0
server/toc/cmd_client_test.go

@@ -2092,6 +2092,167 @@ func TestOSCARProxy_GetInfoURL(t *testing.T) {
 	}
 }
 
+func TestOSCARProxy_GetStatus(t *testing.T) {
+	cases := []struct {
+		// name is the unit test name
+		name string
+		// me is the TOC user session
+		me *state.Session
+		// givenCmd is the TOC command
+		givenCmd []byte
+		// wantMsg is the expected TOC response
+		wantMsg string
+		// mockParams is the list of params sent to mocks that satisfy this
+		// method's dependencies
+		mockParams mockParams
+	}{
+		{
+			name:     "successfully request status",
+			me:       newTestSession("me"),
+			givenCmd: []byte("toc_get_status them"),
+			mockParams: mockParams{
+				locateParams: locateParams{
+					userInfoQueryParams: userInfoQueryParams{
+						{
+							me: state.NewIdentScreenName("me"),
+							inBody: wire.SNAC_0x02_0x05_LocateUserInfoQuery{
+								ScreenName: "them",
+							},
+							msg: wire.SNACMessage{
+								Body: wire.SNAC_0x02_0x06_LocateUserInfoReply{
+									TLVUserInfo: wire.TLVUserInfo{
+										ScreenName:   "them",
+										WarningLevel: 0,
+										TLVBlock: wire.TLVBlock{
+											TLVList: wire.TLVList{
+												wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1234)),
+												wire.NewTLVBE(wire.OServiceUserInfoIdleTime, uint16(5678)),
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			wantMsg: "UPDATE_BUDDY:them:T:0:1234:5678: O ",
+		},
+		{
+			name:     "request status, receive err from locate svc",
+			me:       newTestSession("me"),
+			givenCmd: []byte("toc_get_status them"),
+			mockParams: mockParams{
+				locateParams: locateParams{
+					userInfoQueryParams: userInfoQueryParams{
+						{
+							me: state.NewIdentScreenName("me"),
+							inBody: wire.SNAC_0x02_0x05_LocateUserInfoQuery{
+								ScreenName: "them",
+							},
+							err: io.EOF,
+						},
+					},
+				},
+			},
+			wantMsg: cmdInternalSvcErr,
+		},
+		{
+			name:     "request status, user not online",
+			me:       newTestSession("me"),
+			givenCmd: []byte("toc_get_status them"),
+			mockParams: mockParams{
+				locateParams: locateParams{
+					userInfoQueryParams: userInfoQueryParams{
+						{
+							me: state.NewIdentScreenName("me"),
+							inBody: wire.SNAC_0x02_0x05_LocateUserInfoQuery{
+								ScreenName: "them",
+							},
+							msg: wire.SNACMessage{
+								Body: wire.SNACError{
+									Code: wire.ErrorCodeNotLoggedOn,
+								},
+							},
+						},
+					},
+				},
+			},
+			wantMsg: "ERROR:901:them",
+		},
+		{
+			name:     "request status, receive unexpected error code",
+			me:       newTestSession("me"),
+			givenCmd: []byte("toc_get_status them"),
+			mockParams: mockParams{
+				locateParams: locateParams{
+					userInfoQueryParams: userInfoQueryParams{
+						{
+							me: state.NewIdentScreenName("me"),
+							inBody: wire.SNAC_0x02_0x05_LocateUserInfoQuery{
+								ScreenName: "them",
+							},
+							msg: wire.SNACMessage{
+								Body: wire.SNACError{
+									Code: wire.ErrorCodeInvalidSnac,
+								},
+							},
+						},
+					},
+				},
+			},
+			wantMsg: cmdInternalSvcErr,
+		},
+		{
+			name:     "request status, unexpected response from locate svc",
+			me:       newTestSession("me"),
+			givenCmd: []byte("toc_get_status them"),
+			mockParams: mockParams{
+				locateParams: locateParams{
+					userInfoQueryParams: userInfoQueryParams{
+						{
+							me: state.NewIdentScreenName("me"),
+							inBody: wire.SNAC_0x02_0x05_LocateUserInfoQuery{
+								ScreenName: "them",
+							},
+							msg: wire.SNACMessage{
+								Body: wire.SNAC_0x0E_0x04_ChatUsersLeft{},
+							},
+						},
+					},
+				},
+			},
+			wantMsg: cmdInternalSvcErr,
+		},
+		{
+			name:     "bad command",
+			givenCmd: []byte(`toc_get_status`),
+			wantMsg:  cmdInternalSvcErr,
+		},
+	}
+
+	for _, tc := range cases {
+		t.Run(tc.name, func(t *testing.T) {
+			ctx := context.Background()
+
+			locateSvc := newMockLocateService(t)
+			for _, params := range tc.mockParams.userInfoQueryParams {
+				locateSvc.EXPECT().
+					UserInfoQuery(mock.Anything, matchSession(params.me), wire.SNACFrame{}, params.inBody).
+					Return(params.msg, params.err)
+			}
+
+			svc := OSCARProxy{
+				Logger:        slog.Default(),
+				LocateService: locateSvc,
+			}
+			msg := svc.GetStatus(ctx, tc.me, tc.givenCmd)
+
+			assert.Equal(t, tc.wantMsg, msg)
+		})
+	}
+}
+
 func TestOSCARProxy_InitDone(t *testing.T) {
 	cases := []struct {
 		// name is the unit test name

+ 15 - 9
server/toc/cmd_server.go

@@ -237,15 +237,7 @@ func (s OSCARProxy) IMIn(ctx context.Context, chatRegistry *ChatRegistry, snac w
 //
 // Command syntax: UPDATE_BUDDY:<Buddy User>:<Online? T/F>:<Evil Amount>:<Signon Time>:<IdleTime>:<UC>
 func (s OSCARProxy) UpdateBuddyArrival(snac wire.SNAC_0x03_0x0B_BuddyArrived) string {
-	online, _ := snac.Uint32BE(wire.OServiceUserInfoSignonTOD)
-	idle, _ := snac.Uint16BE(wire.OServiceUserInfoIdleTime)
-	uc := [3]string{" ", "O", " "}
-	if snac.IsAway() {
-		uc[2] = "U"
-	}
-	warning := fmt.Sprintf("%d", snac.WarningLevel/10)
-	class := strings.Join(uc[:], "")
-	return fmt.Sprintf("UPDATE_BUDDY:%s:%s:%s:%d:%d:%s", snac.ScreenName, "T", warning, online, idle, class)
+	return userInfoToUpdateBuddy(snac.TLVUserInfo)
 }
 
 // UpdateBuddyDeparted handles the UPDATE_BUDDY TOC command for buddy departure events.
@@ -281,3 +273,17 @@ func sendOrCancel(ctx context.Context, ch chan<- []byte, msg string) {
 		return
 	}
 }
+
+// userInfoToUpdateBuddy creates an UPDATE_BUDDY server reply from a User
+// Info TLV.
+func userInfoToUpdateBuddy(snac wire.TLVUserInfo) string {
+	online, _ := snac.Uint32BE(wire.OServiceUserInfoSignonTOD)
+	idle, _ := snac.Uint16BE(wire.OServiceUserInfoIdleTime)
+	uc := [3]string{" ", "O", " "}
+	if snac.IsAway() {
+		uc[2] = "U"
+	}
+	warning := fmt.Sprintf("%d", snac.WarningLevel/10)
+	class := strings.Join(uc[:], "")
+	return fmt.Sprintf("UPDATE_BUDDY:%s:%s:%s:%d:%d:%s", snac.ScreenName, "T", warning, online, idle, class)
+}