Parcourir la source

fix broken aim 6 signon flow

Mike il y a 1 an
Parent
commit
a421dca962

+ 1 - 3
foodgroup/admin.go

@@ -218,9 +218,7 @@ func (s AdminService) InfoChangeRequest(ctx context.Context, sess *state.Session
 				FoodGroup: wire.OService,
 				SubGroup:  wire.OServiceUserInfoUpdate,
 			},
-			Body: wire.SNAC_0x01_0x0F_OServiceUserInfoUpdate{
-				TLVUserInfo: sess.TLVUserInfo(),
-			},
+			Body: newOServiceUserInfoUpdate(sess),
 		})
 		tlvList.Append(wire.NewTLVBE(wire.AdminTLVScreenNameFormatted, proposedName.String()))
 		return getAdminChangeReply(tlvList), nil

+ 72 - 2
foodgroup/admin_test.go

@@ -472,7 +472,7 @@ func TestAdminService_InfoChangeRequest_ScreenName(t *testing.T) {
 		expectErr error
 	}{
 		{
-			name:        "user changes screen name format successfully",
+			name:        "user changes screen name format successfully aim < 6",
 			userSession: newTestSession("chattingchuck"),
 			mockParams: mockParams{
 				accountManagerParams: accountManagerParams{
@@ -499,7 +499,77 @@ func TestAdminService_InfoChangeRequest_ScreenName(t *testing.T) {
 									SubGroup:  wire.OServiceUserInfoUpdate,
 								},
 								Body: wire.SNAC_0x01_0x0F_OServiceUserInfoUpdate{
-									TLVUserInfo: newTestSession("Chatting Chuck").TLVUserInfo(),
+									UserInfo: []wire.TLVUserInfo{
+										newTestSession("Chatting Chuck").TLVUserInfo(),
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			inputSNAC: wire.SNACMessage{
+				Frame: wire.SNACFrame{
+					FoodGroup: wire.Admin,
+					SubGroup:  wire.AdminInfoChangeRequest,
+					RequestID: 1337,
+				},
+				Body: wire.SNAC_0x07_0x04_AdminInfoChangeRequest{
+					TLVRestBlock: wire.TLVRestBlock{
+						TLVList: wire.TLVList{
+							wire.NewTLVBE(wire.AdminTLVScreenNameFormatted, "Chatting Chuck"),
+						},
+					},
+				},
+			},
+			expectOutput: wire.SNACMessage{
+				Frame: wire.SNACFrame{
+					FoodGroup: wire.Admin,
+					SubGroup:  wire.AdminInfoChangeReply,
+					RequestID: 1337,
+				},
+				Body: wire.SNAC_0x07_0x05_AdminChangeReply{
+					Permissions: wire.AdminInfoPermissionsReadWrite,
+					TLVBlock: wire.TLVBlock{
+						TLVList: wire.TLVList{
+							wire.NewTLVBE(wire.AdminTLVScreenNameFormatted, "Chatting Chuck"),
+						},
+					},
+				},
+			},
+		},
+		{
+			name:        "user changes screen name format successfully aim >= 6",
+			userSession: newTestSession("chattingchuck", sessOptSetFoodGroupVersion(wire.OService, 4)),
+			mockParams: mockParams{
+				accountManagerParams: accountManagerParams{
+					accountManagerUpdateDisplayScreenNameParams: accountManagerUpdateDisplayScreenNameParams{
+						{
+							displayScreenName: state.DisplayScreenName("Chatting Chuck"),
+						},
+					},
+				},
+				buddyBroadcasterParams: buddyBroadcasterParams{
+					broadcastBuddyArrivedParams: broadcastBuddyArrivedParams{
+						{
+							screenName: state.NewIdentScreenName("Chatting Chuck"),
+						},
+					},
+				},
+				messageRelayerParams: messageRelayerParams{
+					relayToScreenNameParams: relayToScreenNameParams{
+						{
+							screenName: state.NewIdentScreenName("Chatting Chuck"),
+							message: wire.SNACMessage{
+								Frame: wire.SNACFrame{
+									FoodGroup: wire.OService,
+									SubGroup:  wire.OServiceUserInfoUpdate,
+								},
+								Body: wire.SNAC_0x01_0x0F_OServiceUserInfoUpdate{
+									UserInfo: []wire.TLVUserInfo{
+										newTestSession("Chatting Chuck").TLVUserInfo(),
+										newTestSession("Chatting Chuck").TLVUserInfo(),
+									},
 								},
 							},
 						},

+ 3 - 1
foodgroup/helpers_test.go

@@ -762,8 +762,10 @@ func sessOptUIN(UIN uint32) func(session *state.Session) {
 	}
 }
 
-func sessOptFoodGroupVersions(versions [wire.MDir + 1]uint16) func(session *state.Session) {
+func sessOptSetFoodGroupVersion(foodGroup uint16, version uint16) func(session *state.Session) {
 	return func(session *state.Session) {
+		var versions [wire.MDir + 1]uint16
+		versions[foodGroup] = version
 		session.SetFoodGroupVersions(versions)
 	}
 }

+ 21 - 6
foodgroup/oservice.go

@@ -189,9 +189,7 @@ func (s OServiceService) UserInfoQuery(_ context.Context, sess *state.Session, i
 			SubGroup:  wire.OServiceUserInfoUpdate,
 			RequestID: inFrame.RequestID,
 		},
-		Body: wire.SNAC_0x01_0x0F_OServiceUserInfoUpdate{
-			TLVUserInfo: sess.TLVUserInfo(),
-		},
+		Body: newOServiceUserInfoUpdate(sess),
 	}
 }
 
@@ -220,9 +218,7 @@ func (s OServiceService) SetUserInfoFields(ctx context.Context, sess *state.Sess
 			SubGroup:  wire.OServiceUserInfoUpdate,
 			RequestID: inFrame.RequestID,
 		},
-		Body: wire.SNAC_0x01_0x0F_OServiceUserInfoUpdate{
-			TLVUserInfo: sess.TLVUserInfo(),
-		},
+		Body: newOServiceUserInfoUpdate(sess),
 	}, nil
 }
 
@@ -847,3 +843,22 @@ func NewOServiceServiceForBART(
 		timeNow:          time.Now,
 	}
 }
+
+// newOServiceUserInfoUpdate constructs SNAC(0x01,0x0F) for user info updates.
+// For OService version 4 and above, it appends a duplicate TLVUserInfo block.
+// AIM 6+ expects at least two user info blocks to support multi-session:
+// the first represents overall state; subsequent ones represent client instances.
+func newOServiceUserInfoUpdate(sess *state.Session) wire.SNAC_0x01_0x0F_OServiceUserInfoUpdate {
+	info := sess.TLVUserInfo()
+	userInfo := []wire.TLVUserInfo{info}
+
+	if sess.FoodGroupVersions()[wire.OService] >= 4 {
+		// ideally, the second block should contain only instance-specific TLVs,
+		// but since the exact structure is unclear, we temporarily duplicate the first.
+		userInfo = append(userInfo, info)
+	}
+
+	return wire.SNAC_0x01_0x0F_OServiceUserInfoUpdate{
+		UserInfo: userInfo,
+	}
+}

+ 140 - 25
foodgroup/oservice_test.go

@@ -366,7 +366,7 @@ func TestSetUserInfoFields(t *testing.T) {
 		mockParams mockParams
 	}{
 		{
-			name:        "set user status to visible",
+			name:        "set user status to visible aim < 6",
 			userSession: newTestSession("me"),
 			inputSNAC: wire.SNACMessage{
 				Frame: wire.SNACFrame{
@@ -387,7 +387,9 @@ func TestSetUserInfoFields(t *testing.T) {
 					RequestID: 1234,
 				},
 				Body: wire.SNAC_0x01_0x0F_OServiceUserInfoUpdate{
-					TLVUserInfo: newTestSession("me").TLVUserInfo(),
+					UserInfo: []wire.TLVUserInfo{
+						newTestSession("me").TLVUserInfo(),
+					},
 				},
 			},
 			mockParams: mockParams{
@@ -401,7 +403,7 @@ func TestSetUserInfoFields(t *testing.T) {
 			},
 		},
 		{
-			name:        "set user status to invisible",
+			name:        "set user status to invisible aim < 6",
 			userSession: newTestSession("me"),
 			inputSNAC: wire.SNACMessage{
 				Frame: wire.SNACFrame{
@@ -422,7 +424,85 @@ func TestSetUserInfoFields(t *testing.T) {
 					RequestID: 1234,
 				},
 				Body: wire.SNAC_0x01_0x0F_OServiceUserInfoUpdate{
-					TLVUserInfo: newTestSession("me", sessOptInvisible).TLVUserInfo(),
+					UserInfo: []wire.TLVUserInfo{
+						newTestSession("me", sessOptInvisible).TLVUserInfo(),
+					},
+				},
+			},
+			mockParams: mockParams{
+				buddyBroadcasterParams: buddyBroadcasterParams{
+					broadcastBuddyDepartedParams: broadcastBuddyDepartedParams{
+						{
+							screenName: state.NewIdentScreenName("me"),
+						},
+					},
+				},
+			},
+		},
+		{
+			name:        "set user status to visible aim >= 6",
+			userSession: newTestSession("me", sessOptSetFoodGroupVersion(wire.OService, 4)),
+			inputSNAC: wire.SNACMessage{
+				Frame: wire.SNACFrame{
+					RequestID: 1234,
+				},
+				Body: wire.SNAC_0x01_0x1E_OServiceSetUserInfoFields{
+					TLVRestBlock: wire.TLVRestBlock{
+						TLVList: wire.TLVList{
+							wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0000)),
+						},
+					},
+				},
+			},
+			expectOutput: wire.SNACMessage{
+				Frame: wire.SNACFrame{
+					FoodGroup: wire.OService,
+					SubGroup:  wire.OServiceUserInfoUpdate,
+					RequestID: 1234,
+				},
+				Body: wire.SNAC_0x01_0x0F_OServiceUserInfoUpdate{
+					UserInfo: []wire.TLVUserInfo{
+						newTestSession("me").TLVUserInfo(),
+						newTestSession("me").TLVUserInfo(),
+					},
+				},
+			},
+			mockParams: mockParams{
+				buddyBroadcasterParams: buddyBroadcasterParams{
+					broadcastBuddyArrivedParams: broadcastBuddyArrivedParams{
+						{
+							screenName: state.NewIdentScreenName("me"),
+						},
+					},
+				},
+			},
+		},
+		{
+			name:        "set user status to invisible aim >= 6",
+			userSession: newTestSession("me", sessOptSetFoodGroupVersion(wire.OService, 4)),
+			inputSNAC: wire.SNACMessage{
+				Frame: wire.SNACFrame{
+					RequestID: 1234,
+				},
+				Body: wire.SNAC_0x01_0x1E_OServiceSetUserInfoFields{
+					TLVRestBlock: wire.TLVRestBlock{
+						TLVList: wire.TLVList{
+							wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0100)),
+						},
+					},
+				},
+			},
+			expectOutput: wire.SNACMessage{
+				Frame: wire.SNACFrame{
+					FoodGroup: wire.OService,
+					SubGroup:  wire.OServiceUserInfoUpdate,
+					RequestID: 1234,
+				},
+				Body: wire.SNAC_0x01_0x0F_OServiceUserInfoUpdate{
+					UserInfo: []wire.TLVUserInfo{
+						newTestSession("me", sessOptInvisible).TLVUserInfo(),
+						newTestSession("me", sessOptInvisible).TLVUserInfo(),
+					},
 				},
 			},
 			mockParams: mockParams{
@@ -754,11 +834,8 @@ func TestOServiceService_RateParamsQuery(t *testing.T) {
 		timeNow func() time.Time
 	}{
 		{
-			name: "get rate limits for AIM > 1.x clients",
-			userSession: newTestSession("me", sessOptFoodGroupVersions([wire.MDir + 1]uint16{
-				0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // set OService version
-				0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-			})),
+			name:        "get rate limits for AIM > 1.x clients",
+			userSession: newTestSession("me", sessOptSetFoodGroupVersion(wire.OService, 3)),
 			inputSNAC: wire.SNACMessage{
 				Frame: wire.SNACFrame{RequestID: 1234},
 			},
@@ -1081,26 +1158,64 @@ func TestOServiceService_ClientVersions(t *testing.T) {
 }
 
 func TestOServiceService_UserInfoQuery(t *testing.T) {
-	svc := OServiceService{
-		cfg:    config.Config{},
-		logger: slog.Default(),
-	}
-	sess := newTestSession("me")
-
-	want := wire.SNACMessage{
-		Frame: wire.SNACFrame{
-			FoodGroup: wire.OService,
-			SubGroup:  wire.OServiceUserInfoUpdate,
-			RequestID: 1234,
+	tests := []struct {
+		name    string
+		sess    *state.Session
+		given   wire.SNACMessage
+		want    wire.SNACMessage
+		wantErr error
+	}{
+		{
+			name: "happy path windows aim < 6",
+			sess: newTestSession("me"),
+			given: wire.SNACMessage{
+				Frame: wire.SNACFrame{RequestID: 1234},
+			},
+			want: wire.SNACMessage{
+				Frame: wire.SNACFrame{
+					FoodGroup: wire.OService,
+					SubGroup:  wire.OServiceUserInfoUpdate,
+					RequestID: 1234,
+				},
+				Body: wire.SNAC_0x01_0x0F_OServiceUserInfoUpdate{
+					UserInfo: []wire.TLVUserInfo{
+						newTestSession("me").TLVUserInfo(),
+					},
+				},
+			},
 		},
-		Body: wire.SNAC_0x01_0x0F_OServiceUserInfoUpdate{
-			TLVUserInfo: sess.TLVUserInfo(),
+		{
+			name: "happy path windows aim >= 6",
+			sess: newTestSession("me", sessOptSetFoodGroupVersion(wire.OService, 4)),
+			given: wire.SNACMessage{
+				Frame: wire.SNACFrame{RequestID: 1234},
+			},
+			want: wire.SNACMessage{
+				Frame: wire.SNACFrame{
+					FoodGroup: wire.OService,
+					SubGroup:  wire.OServiceUserInfoUpdate,
+					RequestID: 1234,
+				},
+				Body: wire.SNAC_0x01_0x0F_OServiceUserInfoUpdate{
+					UserInfo: []wire.TLVUserInfo{
+						newTestSession("me").TLVUserInfo(),
+						newTestSession("me").TLVUserInfo(),
+					},
+				},
+			},
 		},
 	}
 
-	have := svc.UserInfoQuery(nil, sess, wire.SNACFrame{RequestID: 1234})
-
-	assert.Equal(t, want, have)
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			svc := OServiceService{
+				cfg:    config.Config{},
+				logger: slog.Default(),
+			}
+			have := svc.UserInfoQuery(context.Background(), tt.sess, tt.given.Frame)
+			assert.Equal(t, tt.want, have)
+		})
+	}
 }
 
 func TestOServiceService_IdleNotification(t *testing.T) {

+ 6 - 4
server/oscar/handler/oservice_test.go

@@ -306,8 +306,9 @@ func TestOServiceHandler_SetUserInfoFields(t *testing.T) {
 			SubGroup:  wire.OServiceUserInfoUpdate,
 		},
 		Body: wire.SNAC_0x01_0x0F_OServiceUserInfoUpdate{
-			TLVUserInfo: wire.TLVUserInfo{
-				ScreenName: "screen-name",
+			UserInfo: []wire.TLVUserInfo{
+				{ScreenName: "screen-name"},
+				{ScreenName: "screen-name"},
 			},
 		},
 	}
@@ -349,8 +350,9 @@ func TestOServiceHandler_UserInfoQuery(t *testing.T) {
 			SubGroup:  wire.OServiceUserInfoUpdate,
 		},
 		Body: wire.SNAC_0x01_0x0F_OServiceUserInfoUpdate{
-			TLVUserInfo: wire.TLVUserInfo{
-				ScreenName: "screen-name",
+			UserInfo: []wire.TLVUserInfo{
+				{ScreenName: "screen-name"},
+				{ScreenName: "screen-name"},
 			},
 		},
 	}

+ 2 - 0
state/session.go

@@ -374,6 +374,8 @@ func (s *Session) userInfo() wire.TLVList {
 		tlvs.Append(wire.NewTLVBE(wire.OServiceUserInfoOscarCaps, s.caps))
 	}
 
+	tlvs.Append(wire.NewTLVBE(wire.OServiceUserInfoMySubscriptions, uint32(0)))
+
 	return tlvs
 }
 

+ 8 - 0
state/session_test.go

@@ -101,6 +101,7 @@ func TestSession_TLVUserInfo(t *testing.T) {
 						wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1)),
 						wire.NewTLVBE(wire.OServiceUserInfoUserFlags, uint16(0x0010)),
 						wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0000)),
+						wire.NewTLVBE(wire.OServiceUserInfoMySubscriptions, uint32(0)),
 					},
 				},
 			},
@@ -124,6 +125,7 @@ func TestSession_TLVUserInfo(t *testing.T) {
 						wire.NewTLVBE(wire.OServiceUserInfoUserFlags, wire.OServiceUserFlagOSCARFree|wire.OServiceUserFlagICQ),
 						wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0000)),
 						wire.NewTLVBE(wire.OServiceUserInfoICQDC, wire.ICQDCInfo{}),
+						wire.NewTLVBE(wire.OServiceUserInfoMySubscriptions, uint32(0)),
 					},
 				},
 			},
@@ -142,6 +144,7 @@ func TestSession_TLVUserInfo(t *testing.T) {
 						wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1)),
 						wire.NewTLVBE(wire.OServiceUserInfoUserFlags, uint16(0x30)),
 						wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0000)),
+						wire.NewTLVBE(wire.OServiceUserInfoMySubscriptions, uint32(0)),
 					},
 				},
 			},
@@ -160,6 +163,7 @@ func TestSession_TLVUserInfo(t *testing.T) {
 						wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1)),
 						wire.NewTLVBE(wire.OServiceUserInfoUserFlags, uint16(0x0010)),
 						wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0100)),
+						wire.NewTLVBE(wire.OServiceUserInfoMySubscriptions, uint32(0)),
 					},
 				},
 			},
@@ -187,6 +191,7 @@ func TestSession_TLVUserInfo(t *testing.T) {
 						wire.NewTLVBE(wire.OServiceUserInfoUserFlags, uint16(0x0010)),
 						wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0000)),
 						wire.NewTLVBE(wire.OServiceUserInfoIdleTime, uint16(6)),
+						wire.NewTLVBE(wire.OServiceUserInfoMySubscriptions, uint32(0)),
 					},
 				},
 			},
@@ -206,6 +211,7 @@ func TestSession_TLVUserInfo(t *testing.T) {
 						wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1)),
 						wire.NewTLVBE(wire.OServiceUserInfoUserFlags, uint16(0x0010)),
 						wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0000)),
+						wire.NewTLVBE(wire.OServiceUserInfoMySubscriptions, uint32(0)),
 					},
 				},
 			},
@@ -243,6 +249,7 @@ func TestSession_TLVUserInfo(t *testing.T) {
 							0x75, 0x8f, 0x24, 0x20, 0x62, 0x87, 0x11, 0xd1,
 							0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x01,
 						}),
+						wire.NewTLVBE(wire.OServiceUserInfoMySubscriptions, uint32(0)),
 					},
 				},
 			},
@@ -261,6 +268,7 @@ func TestSession_TLVUserInfo(t *testing.T) {
 						wire.NewTLVBE(wire.OServiceUserInfoSignonTOD, uint32(1)),
 						wire.NewTLVBE(wire.OServiceUserInfoUserFlags, uint16(0x0010)),
 						wire.NewTLVBE(wire.OServiceUserInfoStatus, uint32(0x0000)),
+						wire.NewTLVBE(wire.OServiceUserInfoMySubscriptions, uint32(0)),
 					},
 				},
 			},

+ 10 - 9
wire/snacs.go

@@ -161,14 +161,15 @@ const (
 	OServiceBartQuery2        uint16 = 0x0022
 	OServiceBartReply2        uint16 = 0x0023
 
-	OServiceUserInfoUserFlags  uint16 = 0x01
-	OServiceUserInfoSignonTOD  uint16 = 0x03
-	OServiceUserInfoIdleTime   uint16 = 0x04
-	OServiceUserInfoStatus     uint16 = 0x06
-	OServiceUserInfoICQDC      uint16 = 0x0C
-	OServiceUserInfoOscarCaps  uint16 = 0x0D
-	OServiceUserInfoBARTInfo   uint16 = 0x1D
-	OServiceUserInfoUserFlags2 uint16 = 0x1F
+	OServiceUserInfoUserFlags       uint16 = 0x01
+	OServiceUserInfoSignonTOD       uint16 = 0x03
+	OServiceUserInfoIdleTime        uint16 = 0x04
+	OServiceUserInfoStatus          uint16 = 0x06
+	OServiceUserInfoICQDC           uint16 = 0x0C
+	OServiceUserInfoOscarCaps       uint16 = 0x0D
+	OServiceUserInfoBARTInfo        uint16 = 0x1D
+	OServiceUserInfoMySubscriptions uint16 = 0x1E
+	OServiceUserInfoUserFlags2      uint16 = 0x1F
 
 	OServiceUserStatusAvailable         uint32 = 0x00000000 // user is available
 	OServiceUserStatusAway              uint32 = 0x00000001 // user is away
@@ -303,7 +304,7 @@ type SNAC_0x01_0x08_OServiceRateParamsSubAdd struct {
 }
 
 type SNAC_0x01_0x0F_OServiceUserInfoUpdate struct {
-	TLVUserInfo
+	UserInfo []TLVUserInfo
 }
 
 type SNAC_0x01_0x10_OServiceEvilNotification struct {