diff --git a/foodgroup/auth.go b/foodgroup/auth.go index f4108d3..21530b0 100644 --- a/foodgroup/auth.go +++ b/foodgroup/auth.go @@ -76,8 +76,11 @@ func (s AuthService) RegisterChatSession(authCookie []byte) (*state.Session, err return s.chatSessionRegistry.AddSession(c.ChatCookie, c.ScreenName), nil } +// bosCookie represents a token containing client metadata passed to the BOS +// service upon connection. type bosCookie struct { ScreenName state.DisplayScreenName `oscar:"len_prefix=uint8"` + ClientID string `oscar:"len_prefix=uint8"` } // RegisterBOSSession adds a new session to the session registry. @@ -108,6 +111,9 @@ func (s AuthService) RegisterBOSSession(authCookie []byte) (*state.Session, erro sess.SetUserInfoFlag(wire.OServiceUserFlagUnconfirmed) } + // set string containing OSCAR client name and version + sess.SetClientID(c.ClientID) + if u.DisplayScreenName.IsUIN() { sess.SetUserInfoFlag(wire.OServiceUserFlagICQ) @@ -229,7 +235,10 @@ func (s AuthService) BUCPChallenge( // (wire.LoginTLVTagsReconnectHere) and an authorization cookie // (wire.LoginTLVTagsAuthorizationCookie). Else, an error code is set // (wire.LoginTLVTagsErrorSubcode). -func (s AuthService) BUCPLogin(bodyIn wire.SNAC_0x17_0x02_BUCPLoginRequest, newUserFn func(screenName state.DisplayScreenName) (state.User, error)) (wire.SNACMessage, error) { +func (s AuthService) BUCPLogin( + bodyIn wire.SNAC_0x17_0x02_BUCPLoginRequest, + newUserFn func(screenName state.DisplayScreenName) (state.User, error), +) (wire.SNACMessage, error) { block, err := s.login(bodyIn.TLVList, newUserFn) if err != nil { @@ -257,82 +266,132 @@ func (s AuthService) BUCPLogin(bodyIn wire.SNAC_0x17_0x02_BUCPLoginRequest, newU // (wire.LoginTLVTagsReconnectHere) and an authorization cookie // (wire.LoginTLVTagsAuthorizationCookie). Else, an error code is set // (wire.LoginTLVTagsErrorSubcode). -func (s AuthService) FLAPLogin(frame wire.FLAPSignonFrame, newUserFn func(screenName state.DisplayScreenName) (state.User, error)) (wire.TLVRestBlock, error) { +func (s AuthService) FLAPLogin( + frame wire.FLAPSignonFrame, + newUserFn func(screenName state.DisplayScreenName) (state.User, error), +) (wire.TLVRestBlock, error) { return s.login(frame.TLVList, newUserFn) } +// loginProperties represents the properties sent by the client at login. +type loginProperties struct { + screenName state.DisplayScreenName + clientID string + isBUCPAuth bool + passwordHash []byte + roastedPass []byte +} + +// fromTLV creates an instance of loginProperties from a TLV list. +func (l *loginProperties) fromTLV(list wire.TLVList) error { + // extract screen name + if screenName, found := list.String(wire.LoginTLVTagsScreenName); found { + l.screenName = state.DisplayScreenName(screenName) + } else { + return errors.New("screen name doesn't exist in tlv") + } + + // extract client name and version + if clientID, found := list.String(wire.LoginTLVTagsClientIdentity); found { + l.clientID = clientID + } + + // get the password from the appropriate TLV. older clients have a + // roasted password, newer clients have a hashed password. ICQ may omit + // the password TLV when logging in without saved password. + + // extract password hash for BUCP login + if passwordHash, found := list.Bytes(wire.LoginTLVTagsPasswordHash); found { + l.passwordHash = passwordHash + l.isBUCPAuth = true + } + + // extract roasted password for FLAP login + if roastedPass, found := list.Bytes(wire.LoginTLVTagsRoastedPassword); found { + l.roastedPass = roastedPass + } + + return nil +} + // login validates a user's credentials and creates their session. it returns // metadata used in both BUCP and FLAP authentication responses. func (s AuthService) login( - TLVList wire.TLVList, + tlv wire.TLVList, newUserFn func(screenName state.DisplayScreenName) (state.User, error), ) (wire.TLVRestBlock, error) { - screenName, found := TLVList.String(wire.LoginTLVTagsScreenName) - if !found { - return wire.TLVRestBlock{}, errors.New("screen name doesn't exist in tlv") + props := loginProperties{} + if err := props.fromTLV(tlv); err != nil { + return wire.TLVRestBlock{}, err } - sn := state.DisplayScreenName(screenName) - - user, err := s.userManager.User(sn.IdentScreenName()) + user, err := s.userManager.User(props.screenName.IdentScreenName()) if err != nil { return wire.TLVRestBlock{}, err } if user == nil { + // user not found if s.config.DisableAuth { - handleValid := false - if sn.IsUIN() { - handleValid = sn.ValidateUIN() == nil - } else { - handleValid = sn.ValidateAIMHandle() == nil - } - if !handleValid { - return loginFailureResponse(sn, wire.LoginErrInvalidUsernameOrPassword), nil - } - - newUser, err := newUserFn(sn) - if err != nil { + // auth disabled, create the user and return success + if err := s.createUser(props, newUserFn); err != nil { return wire.TLVRestBlock{}, err } - if err := s.userManager.InsertUser(newUser); err != nil { - return wire.TLVRestBlock{}, err - } - - return s.loginSuccessResponse(sn, err) + return s.loginSuccessResponse(props) } - + // auth enabled, return separate login errors for ICQ and AIM loginErr := wire.LoginErrInvalidUsernameOrPassword - if sn.IsUIN() { + if props.screenName.IsUIN() { loginErr = wire.LoginErrICQUserErr } - return loginFailureResponse(sn, loginErr), nil + return loginFailureResponse(props, loginErr), nil } if s.config.DisableAuth { - return s.loginSuccessResponse(sn, err) + // user exists, but don't validate + return s.loginSuccessResponse(props) } var loginOK bool - // get the password from the appropriate TLV. older clients have a - // roasted password, newer clients have a hashed password. ICQ may omit - // the password TLV when logging in without saved password. - if md5Hash, hasMD5 := TLVList.Bytes(wire.LoginTLVTagsPasswordHash); hasMD5 { - loginOK = user.ValidateHash(md5Hash) - } else if roastedPass, hasRoasted := TLVList.Bytes(wire.LoginTLVTagsRoastedPassword); hasRoasted { - loginOK = user.ValidateRoastedPass(roastedPass) + if props.isBUCPAuth { + loginOK = user.ValidateHash(props.passwordHash) + } else { + loginOK = user.ValidateRoastedPass(props.roastedPass) } if !loginOK { - return loginFailureResponse(sn, wire.LoginErrInvalidPassword), nil + return loginFailureResponse(props, wire.LoginErrInvalidPassword), nil } - return s.loginSuccessResponse(sn, err) + return s.loginSuccessResponse(props) +} + +func (s AuthService) createUser( + props loginProperties, + newUserFn func(screenName state.DisplayScreenName) (state.User, error), +) error { + + handleValid := false + if props.screenName.IsUIN() { + handleValid = props.screenName.ValidateUIN() == nil + } else { + handleValid = props.screenName.ValidateAIMHandle() == nil + } + if !handleValid { + return nil + } + + newUser, err := newUserFn(props.screenName) + if err != nil { + return err + } + return s.userManager.InsertUser(newUser) } -func (s AuthService) loginSuccessResponse(screenName state.DisplayScreenName, err error) (wire.TLVRestBlock, error) { +func (s AuthService) loginSuccessResponse(props loginProperties) (wire.TLVRestBlock, error) { loginCookie := bosCookie{ - ScreenName: screenName, + ScreenName: props.screenName, + ClientID: props.clientID, } buf := &bytes.Buffer{} @@ -346,18 +405,18 @@ func (s AuthService) loginSuccessResponse(screenName state.DisplayScreenName, er return wire.TLVRestBlock{ TLVList: []wire.TLV{ - wire.NewTLVBE(wire.LoginTLVTagsScreenName, screenName), + wire.NewTLVBE(wire.LoginTLVTagsScreenName, props.screenName), wire.NewTLVBE(wire.LoginTLVTagsReconnectHere, net.JoinHostPort(s.config.OSCARHost, s.config.BOSPort)), wire.NewTLVBE(wire.LoginTLVTagsAuthorizationCookie, cookie), }, }, nil } -func loginFailureResponse(screenName state.DisplayScreenName, code uint16) wire.TLVRestBlock { +func loginFailureResponse(props loginProperties, errCode uint16) wire.TLVRestBlock { return wire.TLVRestBlock{ TLVList: []wire.TLV{ - wire.NewTLVBE(wire.LoginTLVTagsScreenName, screenName), - wire.NewTLVBE(wire.LoginTLVTagsErrorSubcode, code), + wire.NewTLVBE(wire.LoginTLVTagsScreenName, props.screenName), + wire.NewTLVBE(wire.LoginTLVTagsErrorSubcode, errCode), }, } } diff --git a/foodgroup/auth_test.go b/foodgroup/auth_test.go index 1d4139f..aa3113d 100644 --- a/foodgroup/auth_test.go +++ b/foodgroup/auth_test.go @@ -125,6 +125,7 @@ func TestAuthService_BUCPLoginRequest(t *testing.T) { dataIn: func() []byte { loginCookie := bosCookie{ ScreenName: user.DisplayScreenName, + ClientID: "ICQ 2000b", } buf := &bytes.Buffer{} assert.NoError(t, wire.MarshalBE(loginCookie, buf)) @@ -554,6 +555,7 @@ func TestAuthService_FLAPLoginResponse(t *testing.T) { dataIn: func() []byte { loginCookie := bosCookie{ ScreenName: user.DisplayScreenName, + ClientID: "ICQ 2000b", } buf := &bytes.Buffer{} assert.NoError(t, wire.MarshalBE(loginCookie, buf)) diff --git a/foodgroup/oservice.go b/foodgroup/oservice.go index d9aa1e3..07ac81b 100644 --- a/foodgroup/oservice.go +++ b/foodgroup/oservice.go @@ -7,6 +7,7 @@ import ( "fmt" "log/slog" "net" + "strings" "time" "github.com/mk6i/retro-aim-server/config" @@ -44,7 +45,9 @@ func (s OServiceService) ClientVersions(_ context.Context, frame wire.SNACFrame, } } -var rateLimitSNAC = wire.SNAC_0x01_0x07_OServiceRateParamsReply{ +// rateLimitSNACV1 is the rate params reply sent to AIM 1.x clients that does +// not contain LastTime and CurrentState fields. +var rateLimitSNACV1 = wire.SNAC_0x01_0x07_OServiceRateParamsReply{ RateClasses: []struct { ID uint16 WindowSize uint32 @@ -54,8 +57,10 @@ var rateLimitSNAC = wire.SNAC_0x01_0x07_OServiceRateParamsReply{ DisconnectLevel uint32 CurrentLevel uint32 MaxLevel uint32 - LastTime uint32 - CurrentState uint8 + V2Params *struct { + LastTime uint32 + CurrentState uint8 + } `oscar:"optional"` }{ { ID: 0x01, @@ -66,8 +71,58 @@ var rateLimitSNAC = wire.SNAC_0x01_0x07_OServiceRateParamsReply{ DisconnectLevel: 0x0320, CurrentLevel: 0x0D69, MaxLevel: 0x1770, - LastTime: 0x0000, - CurrentState: 0x0, + V2Params: nil, + }, + }, + RateGroups: []struct { + ID uint16 + Pairs []struct { + FoodGroup uint16 + SubGroup uint16 + } `oscar:"count_prefix=uint16"` + }{ + { + ID: 1, + Pairs: []struct { + FoodGroup uint16 + SubGroup uint16 + }{}, + }, + }, +} + +// rateLimitSNACV2 is the rate params reply sent to non-AIM 1.x clients. +var rateLimitSNACV2 = wire.SNAC_0x01_0x07_OServiceRateParamsReply{ + RateClasses: []struct { + ID uint16 + WindowSize uint32 + ClearLevel uint32 + AlertLevel uint32 + LimitLevel uint32 + DisconnectLevel uint32 + CurrentLevel uint32 + MaxLevel uint32 + V2Params *struct { + LastTime uint32 + CurrentState uint8 + } `oscar:"optional"` + }{ + { + ID: 0x01, + WindowSize: 0x0050, + ClearLevel: 0x09C4, + AlertLevel: 0x07D0, + LimitLevel: 0x05DC, + DisconnectLevel: 0x0320, + CurrentLevel: 0x0D69, + MaxLevel: 0x1770, + V2Params: &struct { + LastTime uint32 + CurrentState uint8 + }{ + LastTime: 0x0000, + CurrentState: 0x0, + }, }, }, RateGroups: []struct { @@ -374,7 +429,16 @@ func init() { } { subGroups := foodGroupToSubgroup[foodGroup] for _, subGroup := range subGroups { - rateLimitSNAC.RateGroups[0].Pairs = append(rateLimitSNAC.RateGroups[0].Pairs, struct { + // build response for AIM 1.x clients + rateLimitSNACV1.RateGroups[0].Pairs = append(rateLimitSNACV1.RateGroups[0].Pairs, struct { + FoodGroup uint16 + SubGroup uint16 + }{ + FoodGroup: foodGroup, + SubGroup: subGroup, + }) + // build response for all other clients + rateLimitSNACV2.RateGroups[0].Pairs = append(rateLimitSNACV2.RateGroups[0].Pairs, struct { FoodGroup uint16 SubGroup uint16 }{ @@ -404,14 +468,18 @@ func init() { // AIM clients silently fail when they expect a rate limit rule that does not // exist in this response. When support for a new food group is added to the // server, update this function accordingly. -func (s OServiceService) RateParamsQuery(_ context.Context, inFrame wire.SNACFrame) wire.SNACMessage { +func (s OServiceService) RateParamsQuery(ctx context.Context, sess *state.Session, inFrame wire.SNACFrame) wire.SNACMessage { + limits := rateLimitSNACV2 + if strings.Contains(sess.ClientID(), "AOL Instant Messenger (TM), version 1.") { + limits = rateLimitSNACV1 + } return wire.SNACMessage{ Frame: wire.SNACFrame{ FoodGroup: wire.OService, SubGroup: wire.OServiceRateParamsReply, RequestID: inFrame.RequestID, }, - Body: rateLimitSNAC, + Body: limits, } } diff --git a/foodgroup/oservice_test.go b/foodgroup/oservice_test.go index cc63b2c..57fa734 100644 --- a/foodgroup/oservice_test.go +++ b/foodgroup/oservice_test.go @@ -99,6 +99,7 @@ func TestOServiceServiceForBOS_ServiceRequest(t *testing.T) { { dataIn: []byte{ 0x10, 'u', 's', 'e', 'r', '_', 's', 'c', 'r', 'e', 'e', 'n', '_', 'n', 'a', 'm', 'e', + 0x0, // no client ID }, cookieOut: []byte("the-cookie"), }, @@ -145,6 +146,7 @@ func TestOServiceServiceForBOS_ServiceRequest(t *testing.T) { { dataIn: []byte{ 0x10, 'u', 's', 'e', 'r', '_', 's', 'c', 'r', 'e', 'e', 'n', '_', 'n', 'a', 'm', 'e', + 0x0, // no client ID }, cookieOut: []byte("the-cookie"), }, @@ -191,6 +193,7 @@ func TestOServiceServiceForBOS_ServiceRequest(t *testing.T) { { dataIn: []byte{ 0x10, 'u', 's', 'e', 'r', '_', 's', 'c', 'r', 'e', 'e', 'n', '_', 'n', 'a', 'm', 'e', + 0x0, // no client ID }, cookieOut: []byte("the-cookie"), }, @@ -471,1011 +474,1089 @@ func TestSetUserInfoFields(t *testing.T) { } func TestOServiceService_RateParamsQuery(t *testing.T) { - svc := OServiceService{ - cfg: config.Config{}, - logger: slog.Default(), - } - - have := svc.RateParamsQuery(nil, wire.SNACFrame{RequestID: 1234}) - want := wire.SNACMessage{ - Frame: wire.SNACFrame{ - FoodGroup: wire.OService, - SubGroup: wire.OServiceRateParamsReply, - RequestID: 1234, - }, - Body: wire.SNAC_0x01_0x07_OServiceRateParamsReply{ - RateClasses: []struct { - ID uint16 - WindowSize uint32 - ClearLevel uint32 - AlertLevel uint32 - LimitLevel uint32 - DisconnectLevel uint32 - CurrentLevel uint32 - MaxLevel uint32 - LastTime uint32 - CurrentState uint8 + expectRateGroups := []struct { + ID uint16 + Pairs []struct { + FoodGroup uint16 + SubGroup uint16 + } `oscar:"count_prefix=uint16"` + }{ + { + ID: 1, + Pairs: []struct { + FoodGroup uint16 + SubGroup uint16 }{ { - ID: 0x0001, - WindowSize: 0x00000050, - ClearLevel: 0x000009C4, - AlertLevel: 0x000007D0, - LimitLevel: 0x000005DC, - DisconnectLevel: 0x00000320, - CurrentLevel: 0x00000D69, - MaxLevel: 0x00001770, - LastTime: 0x00000000, - CurrentState: 0x00, + FoodGroup: wire.OService, + SubGroup: wire.OServiceErr, }, - }, - RateGroups: []struct { - ID uint16 - Pairs []struct { - FoodGroup uint16 - SubGroup uint16 - } `oscar:"count_prefix=uint16"` - }{ { - ID: 1, - Pairs: []struct { - FoodGroup uint16 - SubGroup uint16 - }{ - { - FoodGroup: wire.OService, - SubGroup: wire.OServiceErr, - }, - { - FoodGroup: wire.OService, - SubGroup: wire.OServiceClientOnline, - }, - { - FoodGroup: wire.OService, - SubGroup: wire.OServiceHostOnline, - }, - { - FoodGroup: wire.OService, - SubGroup: wire.OServiceServiceRequest, - }, - { - FoodGroup: wire.OService, - SubGroup: wire.OServiceServiceResponse, - }, - { - FoodGroup: wire.OService, - SubGroup: wire.OServiceRateParamsQuery, - }, - { - FoodGroup: wire.OService, - SubGroup: wire.OServiceRateParamsReply, - }, - { - FoodGroup: wire.OService, - SubGroup: wire.OServiceRateParamsSubAdd, - }, - { - FoodGroup: wire.OService, - SubGroup: wire.OServiceRateDelParamSub, - }, - { - FoodGroup: wire.OService, - SubGroup: wire.OServiceRateParamChange, - }, - { - FoodGroup: wire.OService, - SubGroup: wire.OServicePauseReq, - }, - { - FoodGroup: wire.OService, - SubGroup: wire.OServicePauseAck, - }, - { - FoodGroup: wire.OService, - SubGroup: wire.OServiceResume, - }, - { - FoodGroup: wire.OService, - SubGroup: wire.OServiceUserInfoQuery, - }, - { - FoodGroup: wire.OService, - SubGroup: wire.OServiceUserInfoUpdate, - }, - { - FoodGroup: wire.OService, - SubGroup: wire.OServiceEvilNotification, - }, - { - FoodGroup: wire.OService, - SubGroup: wire.OServiceIdleNotification, - }, - { - FoodGroup: wire.OService, - SubGroup: wire.OServiceMigrateGroups, - }, - { - FoodGroup: wire.OService, - SubGroup: wire.OServiceMotd, - }, - { - FoodGroup: wire.OService, - SubGroup: wire.OServiceSetPrivacyFlags, - }, - { - FoodGroup: wire.OService, - SubGroup: wire.OServiceWellKnownUrls, - }, - { - FoodGroup: wire.OService, - SubGroup: wire.OServiceNoop, - }, - { - FoodGroup: wire.OService, - SubGroup: wire.OServiceClientVersions, - }, - { - FoodGroup: wire.OService, - SubGroup: wire.OServiceHostVersions, - }, - { - FoodGroup: wire.OService, - SubGroup: wire.OServiceMaxConfigQuery, - }, - { - FoodGroup: wire.OService, - SubGroup: wire.OServiceMaxConfigReply, - }, - { - FoodGroup: wire.OService, - SubGroup: wire.OServiceStoreConfig, - }, - { - FoodGroup: wire.OService, - SubGroup: wire.OServiceConfigQuery, - }, - { - FoodGroup: wire.OService, - SubGroup: wire.OServiceConfigReply, - }, - { - FoodGroup: wire.OService, - SubGroup: wire.OServiceSetUserInfoFields, - }, - { - FoodGroup: wire.OService, - SubGroup: wire.OServiceProbeReq, - }, - { - FoodGroup: wire.OService, - SubGroup: wire.OServiceProbeAck, - }, - { - FoodGroup: wire.OService, - SubGroup: wire.OServiceBartReply, - }, - { - FoodGroup: wire.OService, - SubGroup: wire.OServiceBartQuery2, - }, - { - FoodGroup: wire.OService, - SubGroup: wire.OServiceBartReply2, - }, - { - FoodGroup: wire.Locate, - SubGroup: wire.LocateErr, - }, - { - FoodGroup: wire.Locate, - SubGroup: wire.LocateRightsQuery, - }, - { - FoodGroup: wire.Locate, - SubGroup: wire.LocateRightsReply, - }, - { - FoodGroup: wire.Locate, - SubGroup: wire.LocateSetInfo, - }, - { - FoodGroup: wire.Locate, - SubGroup: wire.LocateUserInfoQuery, - }, - { - FoodGroup: wire.Locate, - SubGroup: wire.LocateUserInfoReply, - }, - { - FoodGroup: wire.Locate, - SubGroup: wire.LocateWatcherSubRequest, - }, - { - FoodGroup: wire.Locate, - SubGroup: wire.LocateWatcherNotification, - }, - { - FoodGroup: wire.Locate, - SubGroup: wire.LocateSetDirInfo, - }, - { - FoodGroup: wire.Locate, - SubGroup: wire.LocateSetDirReply, - }, - { - FoodGroup: wire.Locate, - SubGroup: wire.LocateGetDirInfo, - }, - { - FoodGroup: wire.Locate, - SubGroup: wire.LocateGetDirReply, - }, - { - FoodGroup: wire.Locate, - SubGroup: wire.LocateGroupCapabilityQuery, - }, - { - FoodGroup: wire.Locate, - SubGroup: wire.LocateGroupCapabilityReply, - }, - { - FoodGroup: wire.Locate, - SubGroup: wire.LocateSetKeywordInfo, - }, - { - FoodGroup: wire.Locate, - SubGroup: wire.LocateSetKeywordReply, - }, - { - FoodGroup: wire.Locate, - SubGroup: wire.LocateGetKeywordInfo, - }, - { - FoodGroup: wire.Locate, - SubGroup: wire.LocateGetKeywordReply, - }, - { - FoodGroup: wire.Locate, - SubGroup: wire.LocateFindListByEmail, - }, - { - FoodGroup: wire.Locate, - SubGroup: wire.LocateFindListReply, - }, - { - FoodGroup: wire.Locate, - SubGroup: wire.LocateUserInfoQuery2, - }, - - { - FoodGroup: wire.Buddy, - SubGroup: wire.BuddyErr, - }, - { - FoodGroup: wire.Buddy, - SubGroup: wire.BuddyRightsQuery, - }, - { - FoodGroup: wire.Buddy, - SubGroup: wire.BuddyRightsReply, - }, - { - FoodGroup: wire.Buddy, - SubGroup: wire.BuddyAddBuddies, - }, - { - FoodGroup: wire.Buddy, - SubGroup: wire.BuddyDelBuddies, - }, - { - FoodGroup: wire.Buddy, - SubGroup: wire.BuddyWatcherListQuery, - }, - { - FoodGroup: wire.Buddy, - SubGroup: wire.BuddyWatcherListResponse, - }, - { - FoodGroup: wire.Buddy, - SubGroup: wire.BuddyWatcherSubRequest, - }, - { - FoodGroup: wire.Buddy, - SubGroup: wire.BuddyWatcherNotification, - }, - { - FoodGroup: wire.Buddy, - SubGroup: wire.BuddyRejectNotification, - }, - { - FoodGroup: wire.Buddy, - SubGroup: wire.BuddyArrived, - }, - { - FoodGroup: wire.Buddy, - SubGroup: wire.BuddyDeparted, - }, - { - FoodGroup: wire.Buddy, - SubGroup: wire.BuddyAddTempBuddies, - }, - { - FoodGroup: wire.Buddy, - SubGroup: wire.BuddyDelTempBuddies, - }, - - { - FoodGroup: wire.ICBM, - SubGroup: wire.ICBMErr, - }, - { - FoodGroup: wire.ICBM, - SubGroup: wire.ICBMAddParameters, - }, - { - FoodGroup: wire.ICBM, - SubGroup: wire.ICBMDelParameters, - }, - { - FoodGroup: wire.ICBM, - SubGroup: wire.ICBMParameterQuery, - }, - { - FoodGroup: wire.ICBM, - SubGroup: wire.ICBMParameterReply, - }, - { - FoodGroup: wire.ICBM, - SubGroup: wire.ICBMChannelMsgToHost, - }, - { - FoodGroup: wire.ICBM, - SubGroup: wire.ICBMChannelMsgToClient, - }, - { - FoodGroup: wire.ICBM, - SubGroup: wire.ICBMEvilRequest, - }, - { - FoodGroup: wire.ICBM, - SubGroup: wire.ICBMEvilReply, - }, - { - FoodGroup: wire.ICBM, - SubGroup: wire.ICBMMissedCalls, - }, - { - FoodGroup: wire.ICBM, - SubGroup: wire.ICBMClientErr, - }, - { - FoodGroup: wire.ICBM, - SubGroup: wire.ICBMHostAck, - }, - { - FoodGroup: wire.ICBM, - SubGroup: wire.ICBMSinStored, - }, - { - FoodGroup: wire.ICBM, - SubGroup: wire.ICBMSinListQuery, - }, - { - FoodGroup: wire.ICBM, - SubGroup: wire.ICBMSinListReply, - }, - { - FoodGroup: wire.ICBM, - SubGroup: wire.ICBMSinRetrieve, - }, - { - FoodGroup: wire.ICBM, - SubGroup: wire.ICBMSinDelete, - }, - { - FoodGroup: wire.ICBM, - SubGroup: wire.ICBMNotifyRequest, - }, - { - FoodGroup: wire.ICBM, - SubGroup: wire.ICBMNotifyReply, - }, - { - FoodGroup: wire.ICBM, - SubGroup: wire.ICBMClientEvent, - }, - { - FoodGroup: wire.ICBM, - SubGroup: wire.ICBMSinReply, - }, - { - FoodGroup: wire.ChatNav, - SubGroup: wire.ChatNavErr, - }, - { - FoodGroup: wire.ChatNav, - SubGroup: wire.ChatNavRequestChatRights, - }, - { - FoodGroup: wire.ChatNav, - SubGroup: wire.ChatNavRequestExchangeInfo, - }, - { - FoodGroup: wire.ChatNav, - SubGroup: wire.ChatNavRequestRoomInfo, - }, - { - FoodGroup: wire.ChatNav, - SubGroup: wire.ChatNavRequestMoreRoomInfo, - }, - { - FoodGroup: wire.ChatNav, - SubGroup: wire.ChatNavRequestOccupantList, - }, - { - FoodGroup: wire.ChatNav, - SubGroup: wire.ChatNavSearchForRoom, - }, - { - FoodGroup: wire.ChatNav, - SubGroup: wire.ChatNavCreateRoom, - }, - { - FoodGroup: wire.ChatNav, - SubGroup: wire.ChatNavNavInfo, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatErr, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatRoomInfoUpdate, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatUsersJoined, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatUsersLeft, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatChannelMsgToHost, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatChannelMsgToClient, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatEvilRequest, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatEvilReply, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatClientErr, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatPauseRoomReq, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatPauseRoomAck, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatResumeRoom, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatShowMyRow, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatShowRowByUsername, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatShowRowByNumber, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatShowRowByName, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatRowInfo, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatListRows, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatRowListInfo, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatMoreRows, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatMoveToRow, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatToggleChat, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatSendQuestion, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatSendComment, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatTallyVote, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatAcceptBid, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatSendInvite, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatDeclineInvite, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatAcceptInvite, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatNotifyMessage, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatGotoRow, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatStageUserJoin, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatStageUserLeft, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatUnnamedSnac22, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatClose, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatUserBan, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatUserUnban, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatJoined, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatUnnamedSnac27, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatUnnamedSnac28, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatUnnamedSnac29, - }, - { - FoodGroup: wire.Chat, - SubGroup: wire.ChatRoomInfoOwner, - }, + FoodGroup: wire.OService, + SubGroup: wire.OServiceClientOnline, + }, + { + FoodGroup: wire.OService, + SubGroup: wire.OServiceHostOnline, + }, + { + FoodGroup: wire.OService, + SubGroup: wire.OServiceServiceRequest, + }, + { + FoodGroup: wire.OService, + SubGroup: wire.OServiceServiceResponse, + }, + { + FoodGroup: wire.OService, + SubGroup: wire.OServiceRateParamsQuery, + }, + { + FoodGroup: wire.OService, + SubGroup: wire.OServiceRateParamsReply, + }, + { + FoodGroup: wire.OService, + SubGroup: wire.OServiceRateParamsSubAdd, + }, + { + FoodGroup: wire.OService, + SubGroup: wire.OServiceRateDelParamSub, + }, + { + FoodGroup: wire.OService, + SubGroup: wire.OServiceRateParamChange, + }, + { + FoodGroup: wire.OService, + SubGroup: wire.OServicePauseReq, + }, + { + FoodGroup: wire.OService, + SubGroup: wire.OServicePauseAck, + }, + { + FoodGroup: wire.OService, + SubGroup: wire.OServiceResume, + }, + { + FoodGroup: wire.OService, + SubGroup: wire.OServiceUserInfoQuery, + }, + { + FoodGroup: wire.OService, + SubGroup: wire.OServiceUserInfoUpdate, + }, + { + FoodGroup: wire.OService, + SubGroup: wire.OServiceEvilNotification, + }, + { + FoodGroup: wire.OService, + SubGroup: wire.OServiceIdleNotification, + }, + { + FoodGroup: wire.OService, + SubGroup: wire.OServiceMigrateGroups, + }, + { + FoodGroup: wire.OService, + SubGroup: wire.OServiceMotd, + }, + { + FoodGroup: wire.OService, + SubGroup: wire.OServiceSetPrivacyFlags, + }, + { + FoodGroup: wire.OService, + SubGroup: wire.OServiceWellKnownUrls, + }, + { + FoodGroup: wire.OService, + SubGroup: wire.OServiceNoop, + }, + { + FoodGroup: wire.OService, + SubGroup: wire.OServiceClientVersions, + }, + { + FoodGroup: wire.OService, + SubGroup: wire.OServiceHostVersions, + }, + { + FoodGroup: wire.OService, + SubGroup: wire.OServiceMaxConfigQuery, + }, + { + FoodGroup: wire.OService, + SubGroup: wire.OServiceMaxConfigReply, + }, + { + FoodGroup: wire.OService, + SubGroup: wire.OServiceStoreConfig, + }, + { + FoodGroup: wire.OService, + SubGroup: wire.OServiceConfigQuery, + }, + { + FoodGroup: wire.OService, + SubGroup: wire.OServiceConfigReply, + }, + { + FoodGroup: wire.OService, + SubGroup: wire.OServiceSetUserInfoFields, + }, + { + FoodGroup: wire.OService, + SubGroup: wire.OServiceProbeReq, + }, + { + FoodGroup: wire.OService, + SubGroup: wire.OServiceProbeAck, + }, + { + FoodGroup: wire.OService, + SubGroup: wire.OServiceBartReply, + }, + { + FoodGroup: wire.OService, + SubGroup: wire.OServiceBartQuery2, + }, + { + FoodGroup: wire.OService, + SubGroup: wire.OServiceBartReply2, + }, + { + FoodGroup: wire.Locate, + SubGroup: wire.LocateErr, + }, + { + FoodGroup: wire.Locate, + SubGroup: wire.LocateRightsQuery, + }, + { + FoodGroup: wire.Locate, + SubGroup: wire.LocateRightsReply, + }, + { + FoodGroup: wire.Locate, + SubGroup: wire.LocateSetInfo, + }, + { + FoodGroup: wire.Locate, + SubGroup: wire.LocateUserInfoQuery, + }, + { + FoodGroup: wire.Locate, + SubGroup: wire.LocateUserInfoReply, + }, + { + FoodGroup: wire.Locate, + SubGroup: wire.LocateWatcherSubRequest, + }, + { + FoodGroup: wire.Locate, + SubGroup: wire.LocateWatcherNotification, + }, + { + FoodGroup: wire.Locate, + SubGroup: wire.LocateSetDirInfo, + }, + { + FoodGroup: wire.Locate, + SubGroup: wire.LocateSetDirReply, + }, + { + FoodGroup: wire.Locate, + SubGroup: wire.LocateGetDirInfo, + }, + { + FoodGroup: wire.Locate, + SubGroup: wire.LocateGetDirReply, + }, + { + FoodGroup: wire.Locate, + SubGroup: wire.LocateGroupCapabilityQuery, + }, + { + FoodGroup: wire.Locate, + SubGroup: wire.LocateGroupCapabilityReply, + }, + { + FoodGroup: wire.Locate, + SubGroup: wire.LocateSetKeywordInfo, + }, + { + FoodGroup: wire.Locate, + SubGroup: wire.LocateSetKeywordReply, + }, + { + FoodGroup: wire.Locate, + SubGroup: wire.LocateGetKeywordInfo, + }, + { + FoodGroup: wire.Locate, + SubGroup: wire.LocateGetKeywordReply, + }, + { + FoodGroup: wire.Locate, + SubGroup: wire.LocateFindListByEmail, + }, + { + FoodGroup: wire.Locate, + SubGroup: wire.LocateFindListReply, + }, + { + FoodGroup: wire.Locate, + SubGroup: wire.LocateUserInfoQuery2, + }, + + { + FoodGroup: wire.Buddy, + SubGroup: wire.BuddyErr, + }, + { + FoodGroup: wire.Buddy, + SubGroup: wire.BuddyRightsQuery, + }, + { + FoodGroup: wire.Buddy, + SubGroup: wire.BuddyRightsReply, + }, + { + FoodGroup: wire.Buddy, + SubGroup: wire.BuddyAddBuddies, + }, + { + FoodGroup: wire.Buddy, + SubGroup: wire.BuddyDelBuddies, + }, + { + FoodGroup: wire.Buddy, + SubGroup: wire.BuddyWatcherListQuery, + }, + { + FoodGroup: wire.Buddy, + SubGroup: wire.BuddyWatcherListResponse, + }, + { + FoodGroup: wire.Buddy, + SubGroup: wire.BuddyWatcherSubRequest, + }, + { + FoodGroup: wire.Buddy, + SubGroup: wire.BuddyWatcherNotification, + }, + { + FoodGroup: wire.Buddy, + SubGroup: wire.BuddyRejectNotification, + }, + { + FoodGroup: wire.Buddy, + SubGroup: wire.BuddyArrived, + }, + { + FoodGroup: wire.Buddy, + SubGroup: wire.BuddyDeparted, + }, + { + FoodGroup: wire.Buddy, + SubGroup: wire.BuddyAddTempBuddies, + }, + { + FoodGroup: wire.Buddy, + SubGroup: wire.BuddyDelTempBuddies, + }, + + { + FoodGroup: wire.ICBM, + SubGroup: wire.ICBMErr, + }, + { + FoodGroup: wire.ICBM, + SubGroup: wire.ICBMAddParameters, + }, + { + FoodGroup: wire.ICBM, + SubGroup: wire.ICBMDelParameters, + }, + { + FoodGroup: wire.ICBM, + SubGroup: wire.ICBMParameterQuery, + }, + { + FoodGroup: wire.ICBM, + SubGroup: wire.ICBMParameterReply, + }, + { + FoodGroup: wire.ICBM, + SubGroup: wire.ICBMChannelMsgToHost, + }, + { + FoodGroup: wire.ICBM, + SubGroup: wire.ICBMChannelMsgToClient, + }, + { + FoodGroup: wire.ICBM, + SubGroup: wire.ICBMEvilRequest, + }, + { + FoodGroup: wire.ICBM, + SubGroup: wire.ICBMEvilReply, + }, + { + FoodGroup: wire.ICBM, + SubGroup: wire.ICBMMissedCalls, + }, + { + FoodGroup: wire.ICBM, + SubGroup: wire.ICBMClientErr, + }, + { + FoodGroup: wire.ICBM, + SubGroup: wire.ICBMHostAck, + }, + { + FoodGroup: wire.ICBM, + SubGroup: wire.ICBMSinStored, + }, + { + FoodGroup: wire.ICBM, + SubGroup: wire.ICBMSinListQuery, + }, + { + FoodGroup: wire.ICBM, + SubGroup: wire.ICBMSinListReply, + }, + { + FoodGroup: wire.ICBM, + SubGroup: wire.ICBMSinRetrieve, + }, + { + FoodGroup: wire.ICBM, + SubGroup: wire.ICBMSinDelete, + }, + { + FoodGroup: wire.ICBM, + SubGroup: wire.ICBMNotifyRequest, + }, + { + FoodGroup: wire.ICBM, + SubGroup: wire.ICBMNotifyReply, + }, + { + FoodGroup: wire.ICBM, + SubGroup: wire.ICBMClientEvent, + }, + { + FoodGroup: wire.ICBM, + SubGroup: wire.ICBMSinReply, + }, + { + FoodGroup: wire.ChatNav, + SubGroup: wire.ChatNavErr, + }, + { + FoodGroup: wire.ChatNav, + SubGroup: wire.ChatNavRequestChatRights, + }, + { + FoodGroup: wire.ChatNav, + SubGroup: wire.ChatNavRequestExchangeInfo, + }, + { + FoodGroup: wire.ChatNav, + SubGroup: wire.ChatNavRequestRoomInfo, + }, + { + FoodGroup: wire.ChatNav, + SubGroup: wire.ChatNavRequestMoreRoomInfo, + }, + { + FoodGroup: wire.ChatNav, + SubGroup: wire.ChatNavRequestOccupantList, + }, + { + FoodGroup: wire.ChatNav, + SubGroup: wire.ChatNavSearchForRoom, + }, + { + FoodGroup: wire.ChatNav, + SubGroup: wire.ChatNavCreateRoom, + }, + { + FoodGroup: wire.ChatNav, + SubGroup: wire.ChatNavNavInfo, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatErr, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatRoomInfoUpdate, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatUsersJoined, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatUsersLeft, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatChannelMsgToHost, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatChannelMsgToClient, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatEvilRequest, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatEvilReply, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatClientErr, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatPauseRoomReq, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatPauseRoomAck, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatResumeRoom, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatShowMyRow, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatShowRowByUsername, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatShowRowByNumber, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatShowRowByName, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatRowInfo, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatListRows, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatRowListInfo, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatMoreRows, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatMoveToRow, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatToggleChat, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatSendQuestion, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatSendComment, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatTallyVote, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatAcceptBid, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatSendInvite, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatDeclineInvite, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatAcceptInvite, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatNotifyMessage, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatGotoRow, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatStageUserJoin, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatStageUserLeft, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatUnnamedSnac22, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatClose, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatUserBan, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatUserUnban, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatJoined, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatUnnamedSnac27, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatUnnamedSnac28, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatUnnamedSnac29, + }, + { + FoodGroup: wire.Chat, + SubGroup: wire.ChatRoomInfoOwner, + }, + + { + FoodGroup: wire.BART, + SubGroup: wire.BARTErr, + }, + { + FoodGroup: wire.BART, + SubGroup: wire.BARTUploadQuery, + }, + { + FoodGroup: wire.BART, + SubGroup: wire.BARTUploadReply, + }, + { + FoodGroup: wire.BART, + SubGroup: wire.BARTDownloadQuery, + }, + { + FoodGroup: wire.BART, + SubGroup: wire.BARTDownloadReply, + }, + { + FoodGroup: wire.BART, + SubGroup: wire.BARTDownload2Query, + }, + { + FoodGroup: wire.BART, + SubGroup: wire.BARTDownload2Reply, + }, + { + FoodGroup: wire.Feedbag, + SubGroup: wire.FeedbagErr, + }, + { + FoodGroup: wire.Feedbag, + SubGroup: wire.FeedbagRightsQuery, + }, + { + FoodGroup: wire.Feedbag, + SubGroup: wire.FeedbagRightsReply, + }, + { + FoodGroup: wire.Feedbag, + SubGroup: wire.FeedbagQuery, + }, + { + FoodGroup: wire.Feedbag, + SubGroup: wire.FeedbagQueryIfModified, + }, + { + FoodGroup: wire.Feedbag, + SubGroup: wire.FeedbagReply, + }, + { + FoodGroup: wire.Feedbag, + SubGroup: wire.FeedbagUse, + }, + { + FoodGroup: wire.Feedbag, + SubGroup: wire.FeedbagInsertItem, + }, + { + FoodGroup: wire.Feedbag, + SubGroup: wire.FeedbagUpdateItem, + }, + { + FoodGroup: wire.Feedbag, + SubGroup: wire.FeedbagDeleteItem, + }, + { + FoodGroup: wire.Feedbag, + SubGroup: wire.FeedbagInsertClass, + }, + { + FoodGroup: wire.Feedbag, + SubGroup: wire.FeedbagUpdateClass, + }, + { + FoodGroup: wire.Feedbag, + SubGroup: wire.FeedbagDeleteClass, + }, + { + FoodGroup: wire.Feedbag, + SubGroup: wire.FeedbagStatus, + }, + { + FoodGroup: wire.Feedbag, + SubGroup: wire.FeedbagReplyNotModified, + }, + { + FoodGroup: wire.Feedbag, + SubGroup: wire.FeedbagDeleteUser, + }, + { + FoodGroup: wire.Feedbag, + SubGroup: wire.FeedbagStartCluster, + }, + { + FoodGroup: wire.Feedbag, + SubGroup: wire.FeedbagEndCluster, + }, + { + FoodGroup: wire.Feedbag, + SubGroup: wire.FeedbagAuthorizeBuddy, + }, + { + FoodGroup: wire.Feedbag, + SubGroup: wire.FeedbagPreAuthorizeBuddy, + }, + { + FoodGroup: wire.Feedbag, + SubGroup: wire.FeedbagPreAuthorizedBuddy, + }, + { + FoodGroup: wire.Feedbag, + SubGroup: wire.FeedbagRemoveMe, + }, + { + FoodGroup: wire.Feedbag, + SubGroup: wire.FeedbagRemoveMe2, + }, + { + FoodGroup: wire.Feedbag, + SubGroup: wire.FeedbagRequestAuthorizeToHost, + }, + { + FoodGroup: wire.Feedbag, + SubGroup: wire.FeedbagRequestAuthorizeToClient, + }, + { + FoodGroup: wire.Feedbag, + SubGroup: wire.FeedbagRespondAuthorizeToHost, + }, + { + FoodGroup: wire.Feedbag, + SubGroup: wire.FeedbagRespondAuthorizeToClient, + }, + { + FoodGroup: wire.Feedbag, + SubGroup: wire.FeedbagBuddyAdded, + }, + { + FoodGroup: wire.Feedbag, + SubGroup: wire.FeedbagRequestAuthorizeToBadog, + }, + { + FoodGroup: wire.Feedbag, + SubGroup: wire.FeedbagRespondAuthorizeToBadog, + }, + { + FoodGroup: wire.Feedbag, + SubGroup: wire.FeedbagBuddyAddedToBadog, + }, + { + FoodGroup: wire.Feedbag, + SubGroup: wire.FeedbagTestSnac, + }, + { + FoodGroup: wire.Feedbag, + SubGroup: wire.FeedbagForwardMsg, + }, + { + FoodGroup: wire.Feedbag, + SubGroup: wire.FeedbagIsAuthRequiredQuery, + }, + { + FoodGroup: wire.Feedbag, + SubGroup: wire.FeedbagIsAuthRequiredReply, + }, + { + FoodGroup: wire.Feedbag, + SubGroup: wire.FeedbagRecentBuddyUpdate, + }, + { + FoodGroup: wire.BUCP, + SubGroup: wire.BUCPErr, + }, + { + FoodGroup: wire.BUCP, + SubGroup: wire.BUCPLoginRequest, + }, + { + FoodGroup: wire.BUCP, + SubGroup: wire.BUCPLoginResponse, + }, + { + FoodGroup: wire.BUCP, + SubGroup: wire.BUCPRegisterRequest, + }, + { + FoodGroup: wire.BUCP, + SubGroup: wire.BUCPChallengeRequest, + }, + { + FoodGroup: wire.BUCP, + SubGroup: wire.BUCPChallengeResponse, + }, + { + FoodGroup: wire.BUCP, + SubGroup: wire.BUCPAsasnRequest, + }, + { + FoodGroup: wire.BUCP, + SubGroup: wire.BUCPSecuridRequest, + }, + { + FoodGroup: wire.BUCP, + SubGroup: wire.BUCPRegistrationImageRequest, + }, + { + FoodGroup: wire.Alert, + SubGroup: wire.AlertErr, + }, + { + FoodGroup: wire.Alert, + SubGroup: wire.AlertSetAlertRequest, + }, + { + FoodGroup: wire.Alert, + SubGroup: wire.AlertSetAlertReply, + }, + { + FoodGroup: wire.Alert, + SubGroup: wire.AlertGetSubsRequest, + }, + { + FoodGroup: wire.Alert, + SubGroup: wire.AlertGetSubsResponse, + }, + { + FoodGroup: wire.Alert, + SubGroup: wire.AlertNotifyCapabilities, + }, + { + FoodGroup: wire.Alert, + SubGroup: wire.AlertNotify, + }, + { + FoodGroup: wire.Alert, + SubGroup: wire.AlertGetRuleRequest, + }, + { + FoodGroup: wire.Alert, + SubGroup: wire.AlertGetRuleReply, + }, + { + FoodGroup: wire.Alert, + SubGroup: wire.AlertGetFeedRequest, + }, + { + FoodGroup: wire.Alert, + SubGroup: wire.AlertGetFeedReply, + }, + { + FoodGroup: wire.Alert, + SubGroup: wire.AlertRefreshFeed, + }, + { + FoodGroup: wire.Alert, + SubGroup: wire.AlertEvent, + }, + { + FoodGroup: wire.Alert, + SubGroup: wire.AlertQogSnac, + }, + { + FoodGroup: wire.Alert, + SubGroup: wire.AlertRefreshFeedStock, + }, + { + FoodGroup: wire.Alert, + SubGroup: wire.AlertNotifyTransport, + }, + { + FoodGroup: wire.Alert, + SubGroup: wire.AlertSetAlertRequestV2, + }, + { + FoodGroup: wire.Alert, + SubGroup: wire.AlertSetAlertReplyV2, + }, + { + FoodGroup: wire.Alert, + SubGroup: wire.AlertTransitReply, + }, + { + FoodGroup: wire.Alert, + SubGroup: wire.AlertNotifyAck, + }, + { + FoodGroup: wire.Alert, + SubGroup: wire.AlertNotifyDisplayCapabilities, + }, + { + FoodGroup: wire.Alert, + SubGroup: wire.AlertUserOnline, + }, + { + FoodGroup: wire.ICQ, + SubGroup: wire.ICQErr, + }, + { + FoodGroup: wire.ICQ, + SubGroup: wire.ICQDBQuery, + }, + { + FoodGroup: wire.ICQ, + SubGroup: wire.ICQDBReply, + }, + { + FoodGroup: wire.PermitDeny, + SubGroup: wire.PermitDenyErr, + }, + { + FoodGroup: wire.PermitDeny, + SubGroup: wire.PermitDenyRightsQuery, + }, + { + FoodGroup: wire.PermitDeny, + SubGroup: wire.PermitDenyRightsReply, + }, + { + FoodGroup: wire.PermitDeny, + SubGroup: wire.PermitDenySetGroupPermitMask, + }, + { + FoodGroup: wire.PermitDeny, + SubGroup: wire.PermitDenyAddPermListEntries, + }, + { + FoodGroup: wire.PermitDeny, + SubGroup: wire.PermitDenyDelPermListEntries, + }, + { + FoodGroup: wire.PermitDeny, + SubGroup: wire.PermitDenyAddDenyListEntries, + }, + { + FoodGroup: wire.PermitDeny, + SubGroup: wire.PermitDenyDelDenyListEntries, + }, + { + FoodGroup: wire.PermitDeny, + SubGroup: wire.PermitDenyBosErr, + }, + { + FoodGroup: wire.PermitDeny, + SubGroup: wire.PermitDenyAddTempPermitListEntries, + }, + { + FoodGroup: wire.PermitDeny, + SubGroup: wire.PermitDenyDelTempPermitListEntries, + }, + { + FoodGroup: wire.ODir, + SubGroup: wire.ODirErr, + }, + { + FoodGroup: wire.ODir, + SubGroup: wire.ODirInfoQuery, + }, + { + FoodGroup: wire.ODir, + SubGroup: wire.ODirInfoReply, + }, + { + FoodGroup: wire.ODir, + SubGroup: wire.ODirKeywordListQuery, + }, + { + FoodGroup: wire.ODir, + SubGroup: wire.ODirKeywordListReply, + }, + { + FoodGroup: wire.UserLookup, + SubGroup: wire.UserLookupFindByEmail, + }, + }, + }, + } + cases := []struct { + // name is the unit test name + name string + // config is the application config + cfg config.Config + // userSession is the session of the user requesting the chat service + // info + userSession *state.Session + // inputSNAC is the SNAC sent by the sender client + inputSNAC wire.SNACMessage + // expectSNACFrame is the SNAC frame sent from the server to the recipient + // client + expectOutput wire.SNACMessage + // expectErr is the expected error returned by the router + expectErr error + }{ + { + name: "get rate limits for non-AIM 1.x client", + userSession: newTestSession("user_screen_name"), + inputSNAC: wire.SNACMessage{ + Frame: wire.SNACFrame{RequestID: 1234}, + }, + expectOutput: wire.SNACMessage{ + Frame: wire.SNACFrame{ + FoodGroup: wire.OService, + SubGroup: wire.OServiceRateParamsReply, + RequestID: 1234, + }, + Body: wire.SNAC_0x01_0x07_OServiceRateParamsReply{ + RateClasses: []struct { + ID uint16 + WindowSize uint32 + ClearLevel uint32 + AlertLevel uint32 + LimitLevel uint32 + DisconnectLevel uint32 + CurrentLevel uint32 + MaxLevel uint32 + V2Params *struct { + LastTime uint32 + CurrentState uint8 + } `oscar:"optional"` + }{ { - FoodGroup: wire.BART, - SubGroup: wire.BARTErr, - }, - { - FoodGroup: wire.BART, - SubGroup: wire.BARTUploadQuery, - }, - { - FoodGroup: wire.BART, - SubGroup: wire.BARTUploadReply, - }, - { - FoodGroup: wire.BART, - SubGroup: wire.BARTDownloadQuery, - }, - { - FoodGroup: wire.BART, - SubGroup: wire.BARTDownloadReply, - }, - { - FoodGroup: wire.BART, - SubGroup: wire.BARTDownload2Query, - }, - { - FoodGroup: wire.BART, - SubGroup: wire.BARTDownload2Reply, - }, - { - FoodGroup: wire.Feedbag, - SubGroup: wire.FeedbagErr, - }, - { - FoodGroup: wire.Feedbag, - SubGroup: wire.FeedbagRightsQuery, - }, - { - FoodGroup: wire.Feedbag, - SubGroup: wire.FeedbagRightsReply, - }, - { - FoodGroup: wire.Feedbag, - SubGroup: wire.FeedbagQuery, - }, - { - FoodGroup: wire.Feedbag, - SubGroup: wire.FeedbagQueryIfModified, - }, - { - FoodGroup: wire.Feedbag, - SubGroup: wire.FeedbagReply, - }, - { - FoodGroup: wire.Feedbag, - SubGroup: wire.FeedbagUse, - }, - { - FoodGroup: wire.Feedbag, - SubGroup: wire.FeedbagInsertItem, - }, - { - FoodGroup: wire.Feedbag, - SubGroup: wire.FeedbagUpdateItem, - }, - { - FoodGroup: wire.Feedbag, - SubGroup: wire.FeedbagDeleteItem, - }, - { - FoodGroup: wire.Feedbag, - SubGroup: wire.FeedbagInsertClass, - }, - { - FoodGroup: wire.Feedbag, - SubGroup: wire.FeedbagUpdateClass, - }, - { - FoodGroup: wire.Feedbag, - SubGroup: wire.FeedbagDeleteClass, - }, - { - FoodGroup: wire.Feedbag, - SubGroup: wire.FeedbagStatus, - }, - { - FoodGroup: wire.Feedbag, - SubGroup: wire.FeedbagReplyNotModified, - }, - { - FoodGroup: wire.Feedbag, - SubGroup: wire.FeedbagDeleteUser, - }, - { - FoodGroup: wire.Feedbag, - SubGroup: wire.FeedbagStartCluster, - }, - { - FoodGroup: wire.Feedbag, - SubGroup: wire.FeedbagEndCluster, - }, - { - FoodGroup: wire.Feedbag, - SubGroup: wire.FeedbagAuthorizeBuddy, - }, - { - FoodGroup: wire.Feedbag, - SubGroup: wire.FeedbagPreAuthorizeBuddy, - }, - { - FoodGroup: wire.Feedbag, - SubGroup: wire.FeedbagPreAuthorizedBuddy, - }, - { - FoodGroup: wire.Feedbag, - SubGroup: wire.FeedbagRemoveMe, - }, - { - FoodGroup: wire.Feedbag, - SubGroup: wire.FeedbagRemoveMe2, - }, - { - FoodGroup: wire.Feedbag, - SubGroup: wire.FeedbagRequestAuthorizeToHost, - }, - { - FoodGroup: wire.Feedbag, - SubGroup: wire.FeedbagRequestAuthorizeToClient, - }, - { - FoodGroup: wire.Feedbag, - SubGroup: wire.FeedbagRespondAuthorizeToHost, - }, - { - FoodGroup: wire.Feedbag, - SubGroup: wire.FeedbagRespondAuthorizeToClient, - }, - { - FoodGroup: wire.Feedbag, - SubGroup: wire.FeedbagBuddyAdded, - }, - { - FoodGroup: wire.Feedbag, - SubGroup: wire.FeedbagRequestAuthorizeToBadog, - }, - { - FoodGroup: wire.Feedbag, - SubGroup: wire.FeedbagRespondAuthorizeToBadog, - }, - { - FoodGroup: wire.Feedbag, - SubGroup: wire.FeedbagBuddyAddedToBadog, - }, - { - FoodGroup: wire.Feedbag, - SubGroup: wire.FeedbagTestSnac, - }, - { - FoodGroup: wire.Feedbag, - SubGroup: wire.FeedbagForwardMsg, - }, - { - FoodGroup: wire.Feedbag, - SubGroup: wire.FeedbagIsAuthRequiredQuery, - }, - { - FoodGroup: wire.Feedbag, - SubGroup: wire.FeedbagIsAuthRequiredReply, - }, - { - FoodGroup: wire.Feedbag, - SubGroup: wire.FeedbagRecentBuddyUpdate, - }, - { - FoodGroup: wire.BUCP, - SubGroup: wire.BUCPErr, - }, - { - FoodGroup: wire.BUCP, - SubGroup: wire.BUCPLoginRequest, - }, - { - FoodGroup: wire.BUCP, - SubGroup: wire.BUCPLoginResponse, - }, - { - FoodGroup: wire.BUCP, - SubGroup: wire.BUCPRegisterRequest, - }, - { - FoodGroup: wire.BUCP, - SubGroup: wire.BUCPChallengeRequest, - }, - { - FoodGroup: wire.BUCP, - SubGroup: wire.BUCPChallengeResponse, - }, - { - FoodGroup: wire.BUCP, - SubGroup: wire.BUCPAsasnRequest, - }, - { - FoodGroup: wire.BUCP, - SubGroup: wire.BUCPSecuridRequest, - }, - { - FoodGroup: wire.BUCP, - SubGroup: wire.BUCPRegistrationImageRequest, - }, - { - FoodGroup: wire.Alert, - SubGroup: wire.AlertErr, - }, - { - FoodGroup: wire.Alert, - SubGroup: wire.AlertSetAlertRequest, - }, - { - FoodGroup: wire.Alert, - SubGroup: wire.AlertSetAlertReply, - }, - { - FoodGroup: wire.Alert, - SubGroup: wire.AlertGetSubsRequest, - }, - { - FoodGroup: wire.Alert, - SubGroup: wire.AlertGetSubsResponse, - }, - { - FoodGroup: wire.Alert, - SubGroup: wire.AlertNotifyCapabilities, - }, - { - FoodGroup: wire.Alert, - SubGroup: wire.AlertNotify, - }, - { - FoodGroup: wire.Alert, - SubGroup: wire.AlertGetRuleRequest, - }, - { - FoodGroup: wire.Alert, - SubGroup: wire.AlertGetRuleReply, - }, - { - FoodGroup: wire.Alert, - SubGroup: wire.AlertGetFeedRequest, - }, - { - FoodGroup: wire.Alert, - SubGroup: wire.AlertGetFeedReply, - }, - { - FoodGroup: wire.Alert, - SubGroup: wire.AlertRefreshFeed, - }, - { - FoodGroup: wire.Alert, - SubGroup: wire.AlertEvent, - }, - { - FoodGroup: wire.Alert, - SubGroup: wire.AlertQogSnac, - }, - { - FoodGroup: wire.Alert, - SubGroup: wire.AlertRefreshFeedStock, - }, - { - FoodGroup: wire.Alert, - SubGroup: wire.AlertNotifyTransport, - }, - { - FoodGroup: wire.Alert, - SubGroup: wire.AlertSetAlertRequestV2, - }, - { - FoodGroup: wire.Alert, - SubGroup: wire.AlertSetAlertReplyV2, - }, - { - FoodGroup: wire.Alert, - SubGroup: wire.AlertTransitReply, - }, - { - FoodGroup: wire.Alert, - SubGroup: wire.AlertNotifyAck, - }, - { - FoodGroup: wire.Alert, - SubGroup: wire.AlertNotifyDisplayCapabilities, - }, - { - FoodGroup: wire.Alert, - SubGroup: wire.AlertUserOnline, - }, - { - FoodGroup: wire.ICQ, - SubGroup: wire.ICQErr, - }, - { - FoodGroup: wire.ICQ, - SubGroup: wire.ICQDBQuery, - }, - { - FoodGroup: wire.ICQ, - SubGroup: wire.ICQDBReply, - }, - { - FoodGroup: wire.PermitDeny, - SubGroup: wire.PermitDenyErr, - }, - { - FoodGroup: wire.PermitDeny, - SubGroup: wire.PermitDenyRightsQuery, - }, - { - FoodGroup: wire.PermitDeny, - SubGroup: wire.PermitDenyRightsReply, - }, - { - FoodGroup: wire.PermitDeny, - SubGroup: wire.PermitDenySetGroupPermitMask, - }, - { - FoodGroup: wire.PermitDeny, - SubGroup: wire.PermitDenyAddPermListEntries, - }, - { - FoodGroup: wire.PermitDeny, - SubGroup: wire.PermitDenyDelPermListEntries, - }, - { - FoodGroup: wire.PermitDeny, - SubGroup: wire.PermitDenyAddDenyListEntries, - }, - { - FoodGroup: wire.PermitDeny, - SubGroup: wire.PermitDenyDelDenyListEntries, - }, - { - FoodGroup: wire.PermitDeny, - SubGroup: wire.PermitDenyBosErr, - }, - { - FoodGroup: wire.PermitDeny, - SubGroup: wire.PermitDenyAddTempPermitListEntries, - }, - { - FoodGroup: wire.PermitDeny, - SubGroup: wire.PermitDenyDelTempPermitListEntries, - }, - { - FoodGroup: wire.ODir, - SubGroup: wire.ODirErr, - }, - { - FoodGroup: wire.ODir, - SubGroup: wire.ODirInfoQuery, - }, - { - FoodGroup: wire.ODir, - SubGroup: wire.ODirInfoReply, - }, - { - FoodGroup: wire.ODir, - SubGroup: wire.ODirKeywordListQuery, - }, - { - FoodGroup: wire.ODir, - SubGroup: wire.ODirKeywordListReply, + ID: 0x0001, + WindowSize: 0x00000050, + ClearLevel: 0x000009C4, + AlertLevel: 0x000007D0, + LimitLevel: 0x000005DC, + DisconnectLevel: 0x00000320, + CurrentLevel: 0x00000D69, + MaxLevel: 0x00001770, + V2Params: &struct { + LastTime uint32 + CurrentState uint8 + }{ + LastTime: 0x00000000, + CurrentState: 0x00, + }, }, + }, + RateGroups: expectRateGroups, + }, + }, + }, + { + name: "get rate limits for AIM 1.x client", + userSession: newTestSession("user_screen_name", sessClientID("AOL Instant Messenger (TM), version 1.")), + inputSNAC: wire.SNACMessage{ + Frame: wire.SNACFrame{RequestID: 1234}, + }, + expectOutput: wire.SNACMessage{ + Frame: wire.SNACFrame{ + FoodGroup: wire.OService, + SubGroup: wire.OServiceRateParamsReply, + RequestID: 1234, + }, + Body: wire.SNAC_0x01_0x07_OServiceRateParamsReply{ + RateClasses: []struct { + ID uint16 + WindowSize uint32 + ClearLevel uint32 + AlertLevel uint32 + LimitLevel uint32 + DisconnectLevel uint32 + CurrentLevel uint32 + MaxLevel uint32 + V2Params *struct { + LastTime uint32 + CurrentState uint8 + } `oscar:"optional"` + }{ { - FoodGroup: wire.UserLookup, - SubGroup: wire.UserLookupFindByEmail, + ID: 0x0001, + WindowSize: 0x00000050, + ClearLevel: 0x000009C4, + AlertLevel: 0x000007D0, + LimitLevel: 0x000005DC, + DisconnectLevel: 0x00000320, + CurrentLevel: 0x00000D69, + MaxLevel: 0x00001770, }, }, + RateGroups: expectRateGroups, }, }, }, } - assert.Equal(t, want, have) + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + svc := OServiceService{ + cfg: config.Config{}, + logger: slog.Default(), + } + have := svc.RateParamsQuery(nil, tc.userSession, tc.inputSNAC.Frame) + assert.Equal(t, tc.expectOutput, have) + }) + } } func TestOServiceServiceForBOS_OServiceHostOnline(t *testing.T) { diff --git a/foodgroup/test_helpers.go b/foodgroup/test_helpers.go index 409a6ae..627512a 100644 --- a/foodgroup/test_helpers.go +++ b/foodgroup/test_helpers.go @@ -738,6 +738,13 @@ func sessOptUIN(UIN uint32) func(session *state.Session) { } } +// sessClientID sets the client ID +func sessClientID(clientID string) func(session *state.Session) { + return func(session *state.Session) { + session.SetClientID(clientID) + } +} + // newTestSession creates a session object with 0 or more functional options // applied func newTestSession(screenName state.DisplayScreenName, options ...func(session *state.Session)) *state.Session { diff --git a/server/oscar/handler/mock_oservice_test.go b/server/oscar/handler/mock_oservice_test.go index f68ec59..72a5503 100644 --- a/server/oscar/handler/mock_oservice_test.go +++ b/server/oscar/handler/mock_oservice_test.go @@ -213,17 +213,17 @@ func (_c *mockOServiceService_IdleNotification_Call) RunAndReturn(run func(conte return _c } -// RateParamsQuery provides a mock function with given fields: ctx, frame -func (_m *mockOServiceService) RateParamsQuery(ctx context.Context, frame wire.SNACFrame) wire.SNACMessage { - ret := _m.Called(ctx, frame) +// RateParamsQuery provides a mock function with given fields: ctx, sess, frame +func (_m *mockOServiceService) RateParamsQuery(ctx context.Context, sess *state.Session, frame wire.SNACFrame) wire.SNACMessage { + ret := _m.Called(ctx, sess, frame) if len(ret) == 0 { panic("no return value specified for RateParamsQuery") } var r0 wire.SNACMessage - if rf, ok := ret.Get(0).(func(context.Context, wire.SNACFrame) wire.SNACMessage); ok { - r0 = rf(ctx, frame) + if rf, ok := ret.Get(0).(func(context.Context, *state.Session, wire.SNACFrame) wire.SNACMessage); ok { + r0 = rf(ctx, sess, frame) } else { r0 = ret.Get(0).(wire.SNACMessage) } @@ -238,14 +238,15 @@ type mockOServiceService_RateParamsQuery_Call struct { // RateParamsQuery is a helper method to define mock.On call // - ctx context.Context +// - sess *state.Session // - frame wire.SNACFrame -func (_e *mockOServiceService_Expecter) RateParamsQuery(ctx interface{}, frame interface{}) *mockOServiceService_RateParamsQuery_Call { - return &mockOServiceService_RateParamsQuery_Call{Call: _e.mock.On("RateParamsQuery", ctx, frame)} +func (_e *mockOServiceService_Expecter) RateParamsQuery(ctx interface{}, sess interface{}, frame interface{}) *mockOServiceService_RateParamsQuery_Call { + return &mockOServiceService_RateParamsQuery_Call{Call: _e.mock.On("RateParamsQuery", ctx, sess, frame)} } -func (_c *mockOServiceService_RateParamsQuery_Call) Run(run func(ctx context.Context, frame wire.SNACFrame)) *mockOServiceService_RateParamsQuery_Call { +func (_c *mockOServiceService_RateParamsQuery_Call) Run(run func(ctx context.Context, sess *state.Session, frame wire.SNACFrame)) *mockOServiceService_RateParamsQuery_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(wire.SNACFrame)) + run(args[0].(context.Context), args[1].(*state.Session), args[2].(wire.SNACFrame)) }) return _c } @@ -255,7 +256,7 @@ func (_c *mockOServiceService_RateParamsQuery_Call) Return(_a0 wire.SNACMessage) return _c } -func (_c *mockOServiceService_RateParamsQuery_Call) RunAndReturn(run func(context.Context, wire.SNACFrame) wire.SNACMessage) *mockOServiceService_RateParamsQuery_Call { +func (_c *mockOServiceService_RateParamsQuery_Call) RunAndReturn(run func(context.Context, *state.Session, wire.SNACFrame) wire.SNACMessage) *mockOServiceService_RateParamsQuery_Call { _c.Call.Return(run) return _c } diff --git a/server/oscar/handler/oservice.go b/server/oscar/handler/oservice.go index c9655f3..e13bf3b 100644 --- a/server/oscar/handler/oservice.go +++ b/server/oscar/handler/oservice.go @@ -17,7 +17,7 @@ type OServiceService interface { ClientVersions(ctx context.Context, frame wire.SNACFrame, bodyIn wire.SNAC_0x01_0x17_OServiceClientVersions) wire.SNACMessage HostOnline() wire.SNACMessage IdleNotification(ctx context.Context, sess *state.Session, bodyIn wire.SNAC_0x01_0x11_OServiceIdleNotification) error - RateParamsQuery(ctx context.Context, frame wire.SNACFrame) wire.SNACMessage + RateParamsQuery(ctx context.Context, sess *state.Session, frame wire.SNACFrame) wire.SNACMessage RateParamsSubAdd(context.Context, wire.SNAC_0x01_0x08_OServiceRateParamsSubAdd) ServiceRequest(ctx context.Context, sess *state.Session, frame wire.SNACFrame, bodyIn wire.SNAC_0x01_0x04_OServiceServiceRequest) (wire.SNACMessage, error) SetPrivacyFlags(ctx context.Context, bodyIn wire.SNAC_0x01_0x14_OServiceSetPrivacyFlags) @@ -39,8 +39,8 @@ type OServiceHandler struct { middleware.RouteLogger } -func (h OServiceHandler) RateParamsQuery(ctx context.Context, _ *state.Session, inFrame wire.SNACFrame, _ io.Reader, rw oscar.ResponseWriter) error { - outSNAC := h.OServiceService.RateParamsQuery(ctx, inFrame) +func (h OServiceHandler) RateParamsQuery(ctx context.Context, sess *state.Session, inFrame wire.SNACFrame, _ io.Reader, rw oscar.ResponseWriter) error { + outSNAC := h.OServiceService.RateParamsQuery(ctx, sess, inFrame) h.LogRequestAndResponse(ctx, inFrame, nil, outSNAC.Frame, outSNAC.Body) return rw.SendSNAC(outSNAC.Frame, outSNAC.Body) } diff --git a/server/oscar/handler/oservice_test.go b/server/oscar/handler/oservice_test.go index db523b6..4a7e152 100644 --- a/server/oscar/handler/oservice_test.go +++ b/server/oscar/handler/oservice_test.go @@ -232,7 +232,7 @@ func TestOServiceHandler_RateParamsQuery(t *testing.T) { svc := newMockOServiceService(t) svc.EXPECT(). - RateParamsQuery(mock.Anything, input.Frame). + RateParamsQuery(mock.Anything, mock.Anything, input.Frame). Return(output) h := OServiceHandler{ diff --git a/state/session.go b/state/session.go index 282f79c..df824c1 100644 --- a/state/session.go +++ b/state/session.go @@ -42,6 +42,7 @@ type Session struct { warning uint16 userInfoBitmask uint16 userStatusBitmask uint32 + clientID string } // NewSession returns a new instance of Session. By default, the user may have @@ -348,3 +349,17 @@ func (s *Session) Close() { func (s *Session) Closed() <-chan struct{} { return s.stopCh } + +// SetClientID sets the client ID. +func (s *Session) SetClientID(clientID string) { + s.mutex.Lock() + defer s.mutex.Unlock() + s.clientID = clientID +} + +// ClientID retrieves the client ID. +func (s *Session) ClientID() string { + s.mutex.RLock() + defer s.mutex.RUnlock() + return s.clientID +} diff --git a/state/session_test.go b/state/session_test.go index 41f1137..ac7b3e1 100644 --- a/state/session_test.go +++ b/state/session_test.go @@ -58,6 +58,14 @@ func TestSession_SetAndGetUIN(t *testing.T) { assert.Equal(t, uin, s.UIN()) } +func TestSession_SetAndGetClientID(t *testing.T) { + s := NewSession() + assert.Empty(t, s.ClientID()) + clientID := "AIM Client ID" + s.SetClientID(clientID) + assert.Equal(t, clientID, s.ClientID()) +} + func TestSession_TLVUserInfo(t *testing.T) { tests := []struct { name string diff --git a/wire/snacs.go b/wire/snacs.go index 1e221b9..6a6a159 100644 --- a/wire/snacs.go +++ b/wire/snacs.go @@ -250,8 +250,10 @@ type SNAC_0x01_0x07_OServiceRateParamsReply struct { DisconnectLevel uint32 CurrentLevel uint32 MaxLevel uint32 - LastTime uint32 - CurrentState uint8 + V2Params *struct { + LastTime uint32 + CurrentState uint8 + } `oscar:"optional"` } `oscar:"count_prefix=uint16"` RateGroups []struct { ID uint16