Browse Source

implement SNAC 01,1E SetUserInfoFields

This commit fixes the INVALID SNAC error that appears upon successful
login on 2.0.1 & 4.0.9 for macOS.
Mike 2 năm trước cách đây
mục cha
commit
2aca4690ca

+ 16 - 0
foodgroup/oservice.go

@@ -465,6 +465,22 @@ func (s OServiceService) IdleNotification(ctx context.Context, sess *state.Sessi
 	return broadcastArrival(ctx, sess, s.messageRelayer, s.feedbagManager, s.legacyBuddyListManager)
 }
 
+// SetPrivacyFlags sets client privacy settings. Currently, there's no action
+// to take when these flags are set. This method simply logs the flags set by
+// the client.
+func (s OServiceService) SetPrivacyFlags(ctx context.Context, bodyIn wire.SNAC_0x01_0x14_OServiceSetPrivacyFlags) {
+	attrs := slog.Group("request",
+		slog.String("food_group", wire.FoodGroupName(wire.OService)),
+		slog.String("sub_group", wire.SubGroupName(wire.OService, wire.OServiceSetPrivacyFlags)))
+
+	if bodyIn.MemberFlag() {
+		s.logger.LogAttrs(ctx, slog.LevelDebug, "client set member privacy flag, but we're not going to do anything", attrs)
+	}
+	if bodyIn.IdleFlag() {
+		s.logger.LogAttrs(ctx, slog.LevelDebug, "client set idle privacy flag, but we're not going to do anything", attrs)
+	}
+}
+
 // RateParamsSubAdd exists to capture the SNAC input in unit tests to
 // verify it's correctly unmarshalled.
 func (s OServiceService) RateParamsSubAdd(context.Context, wire.SNAC_0x01_0x08_OServiceRateParamsSubAdd) {

+ 9 - 0
foodgroup/oservice_test.go

@@ -1,6 +1,7 @@
 package foodgroup
 
 import (
+	"context"
 	"log/slog"
 	"testing"
 	"time"
@@ -1936,3 +1937,11 @@ func TestOServiceServiceForAlert_HostOnline(t *testing.T) {
 	have := svc.HostOnline()
 	assert.Equal(t, want, have)
 }
+
+func TestOServiceService_SetPrivacyFlags(t *testing.T) {
+	svc := NewOServiceServiceForAlert(*NewOServiceService(config.Config{}, nil, nil, nil, slog.Default()))
+	body := wire.SNAC_0x01_0x14_OServiceSetPrivacyFlags{
+		PrivacyFlags: wire.OServicePrivacyFlagMember | wire.OServicePrivacyFlagIdle,
+	}
+	svc.SetPrivacyFlags(context.Background(), body)
+}

+ 34 - 0
server/oscar/handler/mock_oservice_alert_service_test.go

@@ -246,6 +246,40 @@ func (_c *mockOServiceAlertService_RateParamsSubAdd_Call) RunAndReturn(run func(
 	return _c
 }
 
+// SetPrivacyFlags provides a mock function with given fields: ctx, bodyIn
+func (_m *mockOServiceAlertService) SetPrivacyFlags(ctx context.Context, bodyIn wire.SNAC_0x01_0x14_OServiceSetPrivacyFlags) {
+	_m.Called(ctx, bodyIn)
+}
+
+// mockOServiceAlertService_SetPrivacyFlags_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetPrivacyFlags'
+type mockOServiceAlertService_SetPrivacyFlags_Call struct {
+	*mock.Call
+}
+
+// SetPrivacyFlags is a helper method to define mock.On call
+//   - ctx context.Context
+//   - bodyIn wire.SNAC_0x01_0x14_OServiceSetPrivacyFlags
+func (_e *mockOServiceAlertService_Expecter) SetPrivacyFlags(ctx interface{}, bodyIn interface{}) *mockOServiceAlertService_SetPrivacyFlags_Call {
+	return &mockOServiceAlertService_SetPrivacyFlags_Call{Call: _e.mock.On("SetPrivacyFlags", ctx, bodyIn)}
+}
+
+func (_c *mockOServiceAlertService_SetPrivacyFlags_Call) Run(run func(ctx context.Context, bodyIn wire.SNAC_0x01_0x14_OServiceSetPrivacyFlags)) *mockOServiceAlertService_SetPrivacyFlags_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context), args[1].(wire.SNAC_0x01_0x14_OServiceSetPrivacyFlags))
+	})
+	return _c
+}
+
+func (_c *mockOServiceAlertService_SetPrivacyFlags_Call) Return() *mockOServiceAlertService_SetPrivacyFlags_Call {
+	_c.Call.Return()
+	return _c
+}
+
+func (_c *mockOServiceAlertService_SetPrivacyFlags_Call) RunAndReturn(run func(context.Context, wire.SNAC_0x01_0x14_OServiceSetPrivacyFlags)) *mockOServiceAlertService_SetPrivacyFlags_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
 // SetUserInfoFields provides a mock function with given fields: ctx, sess, frame, bodyIn
 func (_m *mockOServiceAlertService) SetUserInfoFields(ctx context.Context, sess *state.Session, frame wire.SNACFrame, bodyIn wire.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (wire.SNACMessage, error) {
 	ret := _m.Called(ctx, sess, frame, bodyIn)

+ 34 - 0
server/oscar/handler/mock_oservice_bos_test.go

@@ -353,6 +353,40 @@ func (_c *mockOServiceBOSService_ServiceRequest_Call) RunAndReturn(run func(cont
 	return _c
 }
 
+// SetPrivacyFlags provides a mock function with given fields: ctx, bodyIn
+func (_m *mockOServiceBOSService) SetPrivacyFlags(ctx context.Context, bodyIn wire.SNAC_0x01_0x14_OServiceSetPrivacyFlags) {
+	_m.Called(ctx, bodyIn)
+}
+
+// mockOServiceBOSService_SetPrivacyFlags_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetPrivacyFlags'
+type mockOServiceBOSService_SetPrivacyFlags_Call struct {
+	*mock.Call
+}
+
+// SetPrivacyFlags is a helper method to define mock.On call
+//   - ctx context.Context
+//   - bodyIn wire.SNAC_0x01_0x14_OServiceSetPrivacyFlags
+func (_e *mockOServiceBOSService_Expecter) SetPrivacyFlags(ctx interface{}, bodyIn interface{}) *mockOServiceBOSService_SetPrivacyFlags_Call {
+	return &mockOServiceBOSService_SetPrivacyFlags_Call{Call: _e.mock.On("SetPrivacyFlags", ctx, bodyIn)}
+}
+
+func (_c *mockOServiceBOSService_SetPrivacyFlags_Call) Run(run func(ctx context.Context, bodyIn wire.SNAC_0x01_0x14_OServiceSetPrivacyFlags)) *mockOServiceBOSService_SetPrivacyFlags_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context), args[1].(wire.SNAC_0x01_0x14_OServiceSetPrivacyFlags))
+	})
+	return _c
+}
+
+func (_c *mockOServiceBOSService_SetPrivacyFlags_Call) Return() *mockOServiceBOSService_SetPrivacyFlags_Call {
+	_c.Call.Return()
+	return _c
+}
+
+func (_c *mockOServiceBOSService_SetPrivacyFlags_Call) RunAndReturn(run func(context.Context, wire.SNAC_0x01_0x14_OServiceSetPrivacyFlags)) *mockOServiceBOSService_SetPrivacyFlags_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
 // SetUserInfoFields provides a mock function with given fields: ctx, sess, frame, bodyIn
 func (_m *mockOServiceBOSService) SetUserInfoFields(ctx context.Context, sess *state.Session, frame wire.SNACFrame, bodyIn wire.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (wire.SNACMessage, error) {
 	ret := _m.Called(ctx, sess, frame, bodyIn)

+ 34 - 0
server/oscar/handler/mock_oservice_chat_nav_test.go

@@ -246,6 +246,40 @@ func (_c *mockOServiceChatNavService_RateParamsSubAdd_Call) RunAndReturn(run fun
 	return _c
 }
 
+// SetPrivacyFlags provides a mock function with given fields: ctx, bodyIn
+func (_m *mockOServiceChatNavService) SetPrivacyFlags(ctx context.Context, bodyIn wire.SNAC_0x01_0x14_OServiceSetPrivacyFlags) {
+	_m.Called(ctx, bodyIn)
+}
+
+// mockOServiceChatNavService_SetPrivacyFlags_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetPrivacyFlags'
+type mockOServiceChatNavService_SetPrivacyFlags_Call struct {
+	*mock.Call
+}
+
+// SetPrivacyFlags is a helper method to define mock.On call
+//   - ctx context.Context
+//   - bodyIn wire.SNAC_0x01_0x14_OServiceSetPrivacyFlags
+func (_e *mockOServiceChatNavService_Expecter) SetPrivacyFlags(ctx interface{}, bodyIn interface{}) *mockOServiceChatNavService_SetPrivacyFlags_Call {
+	return &mockOServiceChatNavService_SetPrivacyFlags_Call{Call: _e.mock.On("SetPrivacyFlags", ctx, bodyIn)}
+}
+
+func (_c *mockOServiceChatNavService_SetPrivacyFlags_Call) Run(run func(ctx context.Context, bodyIn wire.SNAC_0x01_0x14_OServiceSetPrivacyFlags)) *mockOServiceChatNavService_SetPrivacyFlags_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context), args[1].(wire.SNAC_0x01_0x14_OServiceSetPrivacyFlags))
+	})
+	return _c
+}
+
+func (_c *mockOServiceChatNavService_SetPrivacyFlags_Call) Return() *mockOServiceChatNavService_SetPrivacyFlags_Call {
+	_c.Call.Return()
+	return _c
+}
+
+func (_c *mockOServiceChatNavService_SetPrivacyFlags_Call) RunAndReturn(run func(context.Context, wire.SNAC_0x01_0x14_OServiceSetPrivacyFlags)) *mockOServiceChatNavService_SetPrivacyFlags_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
 // SetUserInfoFields provides a mock function with given fields: ctx, sess, frame, bodyIn
 func (_m *mockOServiceChatNavService) SetUserInfoFields(ctx context.Context, sess *state.Session, frame wire.SNACFrame, bodyIn wire.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (wire.SNACMessage, error) {
 	ret := _m.Called(ctx, sess, frame, bodyIn)

+ 34 - 0
server/oscar/handler/mock_oservice_chat_test.go

@@ -293,6 +293,40 @@ func (_c *mockOServiceChatService_RateParamsSubAdd_Call) RunAndReturn(run func(c
 	return _c
 }
 
+// SetPrivacyFlags provides a mock function with given fields: ctx, bodyIn
+func (_m *mockOServiceChatService) SetPrivacyFlags(ctx context.Context, bodyIn wire.SNAC_0x01_0x14_OServiceSetPrivacyFlags) {
+	_m.Called(ctx, bodyIn)
+}
+
+// mockOServiceChatService_SetPrivacyFlags_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetPrivacyFlags'
+type mockOServiceChatService_SetPrivacyFlags_Call struct {
+	*mock.Call
+}
+
+// SetPrivacyFlags is a helper method to define mock.On call
+//   - ctx context.Context
+//   - bodyIn wire.SNAC_0x01_0x14_OServiceSetPrivacyFlags
+func (_e *mockOServiceChatService_Expecter) SetPrivacyFlags(ctx interface{}, bodyIn interface{}) *mockOServiceChatService_SetPrivacyFlags_Call {
+	return &mockOServiceChatService_SetPrivacyFlags_Call{Call: _e.mock.On("SetPrivacyFlags", ctx, bodyIn)}
+}
+
+func (_c *mockOServiceChatService_SetPrivacyFlags_Call) Run(run func(ctx context.Context, bodyIn wire.SNAC_0x01_0x14_OServiceSetPrivacyFlags)) *mockOServiceChatService_SetPrivacyFlags_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context), args[1].(wire.SNAC_0x01_0x14_OServiceSetPrivacyFlags))
+	})
+	return _c
+}
+
+func (_c *mockOServiceChatService_SetPrivacyFlags_Call) Return() *mockOServiceChatService_SetPrivacyFlags_Call {
+	_c.Call.Return()
+	return _c
+}
+
+func (_c *mockOServiceChatService_SetPrivacyFlags_Call) RunAndReturn(run func(context.Context, wire.SNAC_0x01_0x14_OServiceSetPrivacyFlags)) *mockOServiceChatService_SetPrivacyFlags_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
 // SetUserInfoFields provides a mock function with given fields: ctx, sess, frame, bodyIn
 func (_m *mockOServiceChatService) SetUserInfoFields(ctx context.Context, sess *state.Session, frame wire.SNACFrame, bodyIn wire.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (wire.SNACMessage, error) {
 	ret := _m.Called(ctx, sess, frame, bodyIn)

+ 34 - 0
server/oscar/handler/mock_oservice_test.go

@@ -201,6 +201,40 @@ func (_c *mockOServiceService_RateParamsSubAdd_Call) RunAndReturn(run func(conte
 	return _c
 }
 
+// SetPrivacyFlags provides a mock function with given fields: ctx, bodyIn
+func (_m *mockOServiceService) SetPrivacyFlags(ctx context.Context, bodyIn wire.SNAC_0x01_0x14_OServiceSetPrivacyFlags) {
+	_m.Called(ctx, bodyIn)
+}
+
+// mockOServiceService_SetPrivacyFlags_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetPrivacyFlags'
+type mockOServiceService_SetPrivacyFlags_Call struct {
+	*mock.Call
+}
+
+// SetPrivacyFlags is a helper method to define mock.On call
+//   - ctx context.Context
+//   - bodyIn wire.SNAC_0x01_0x14_OServiceSetPrivacyFlags
+func (_e *mockOServiceService_Expecter) SetPrivacyFlags(ctx interface{}, bodyIn interface{}) *mockOServiceService_SetPrivacyFlags_Call {
+	return &mockOServiceService_SetPrivacyFlags_Call{Call: _e.mock.On("SetPrivacyFlags", ctx, bodyIn)}
+}
+
+func (_c *mockOServiceService_SetPrivacyFlags_Call) Run(run func(ctx context.Context, bodyIn wire.SNAC_0x01_0x14_OServiceSetPrivacyFlags)) *mockOServiceService_SetPrivacyFlags_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context), args[1].(wire.SNAC_0x01_0x14_OServiceSetPrivacyFlags))
+	})
+	return _c
+}
+
+func (_c *mockOServiceService_SetPrivacyFlags_Call) Return() *mockOServiceService_SetPrivacyFlags_Call {
+	_c.Call.Return()
+	return _c
+}
+
+func (_c *mockOServiceService_SetPrivacyFlags_Call) RunAndReturn(run func(context.Context, wire.SNAC_0x01_0x14_OServiceSetPrivacyFlags)) *mockOServiceService_SetPrivacyFlags_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
 // SetUserInfoFields provides a mock function with given fields: ctx, sess, frame, bodyIn
 func (_m *mockOServiceService) SetUserInfoFields(ctx context.Context, sess *state.Session, frame wire.SNACFrame, bodyIn wire.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (wire.SNACMessage, error) {
 	ret := _m.Called(ctx, sess, frame, bodyIn)

+ 11 - 0
server/oscar/handler/oservice.go

@@ -17,6 +17,7 @@ type OServiceService interface {
 	IdleNotification(ctx context.Context, sess *state.Session, bodyIn wire.SNAC_0x01_0x11_OServiceIdleNotification) error
 	RateParamsQuery(ctx context.Context, frame wire.SNACFrame) wire.SNACMessage
 	RateParamsSubAdd(context.Context, wire.SNAC_0x01_0x08_OServiceRateParamsSubAdd)
+	SetPrivacyFlags(ctx context.Context, bodyIn wire.SNAC_0x01_0x14_OServiceSetPrivacyFlags)
 	SetUserInfoFields(ctx context.Context, sess *state.Session, frame wire.SNACFrame, bodyIn wire.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (wire.SNACMessage, error)
 	UserInfoQuery(ctx context.Context, sess *state.Session, frame wire.SNACFrame) wire.SNACMessage
 }
@@ -109,6 +110,16 @@ func (h OServiceHandler) Noop(ctx context.Context, sess *state.Session, inFrame
 	return nil
 }
 
+func (h OServiceHandler) SetPrivacyFlags(ctx context.Context, sess *state.Session, inFrame wire.SNACFrame, r io.Reader, _ oscar.ResponseWriter) error {
+	inBody := wire.SNAC_0x01_0x14_OServiceSetPrivacyFlags{}
+	if err := wire.Unmarshal(&inBody, r); err != nil {
+		return err
+	}
+	h.OServiceService.SetPrivacyFlags(ctx, inBody)
+	h.LogRequest(ctx, inFrame, inBody)
+	return nil
+}
+
 func NewOServiceHandlerForBOS(logger *slog.Logger, oServiceService OServiceService, oServiceBOSService OServiceBOSService) OServiceBOSHandler {
 	return OServiceBOSHandler{
 		OServiceHandler: OServiceHandler{

+ 30 - 0
server/oscar/handler/oservice_test.go

@@ -401,6 +401,36 @@ func TestOServiceHandler_Noop(t *testing.T) {
 	assert.NoError(t, h.Noop(nil, nil, input.Frame, buf, responseWriter))
 }
 
+func TestOServiceHandler_SetPrivacyFlags(t *testing.T) {
+	input := wire.SNACMessage{
+		Frame: wire.SNACFrame{
+			FoodGroup: wire.OService,
+			SubGroup:  wire.OServiceSetPrivacyFlags,
+		},
+		Body: wire.SNAC_0x01_0x14_OServiceSetPrivacyFlags{
+			PrivacyFlags: wire.OServicePrivacyFlagMember,
+		},
+	}
+
+	svc := newMockOServiceService(t)
+	svc.EXPECT().
+		SetPrivacyFlags(mock.Anything, input.Body)
+
+	h := OServiceHandler{
+		OServiceService: svc,
+		RouteLogger: middleware.RouteLogger{
+			Logger: slog.Default(),
+		},
+	}
+
+	responseWriter := newMockResponseWriter(t)
+
+	buf := &bytes.Buffer{}
+	assert.NoError(t, wire.Marshal(input.Body, buf))
+
+	assert.NoError(t, h.SetPrivacyFlags(nil, nil, input.Frame, buf, responseWriter))
+}
+
 func TestOServiceChatNavHandler_ClientOnline(t *testing.T) {
 	input := wire.SNACMessage{
 		Frame: wire.SNACFrame{

+ 1 - 0
server/oscar/handler/routes.go

@@ -77,6 +77,7 @@ func NewBOSRouter(h Handlers) oscar.Router {
 	router.Register(wire.OService, wire.OServiceServiceRequest, h.OServiceBOSHandler.ServiceRequest)
 	router.Register(wire.OService, wire.OServiceSetUserInfoFields, h.OServiceBOSHandler.OServiceHandler.SetUserInfoFields)
 	router.Register(wire.OService, wire.OServiceUserInfoQuery, h.OServiceBOSHandler.OServiceHandler.UserInfoQuery)
+	router.Register(wire.OService, wire.OServiceSetPrivacyFlags, h.OServiceBOSHandler.OServiceHandler.SetPrivacyFlags)
 
 	return router
 }

+ 19 - 0
wire/snacs.go

@@ -159,6 +159,9 @@ const (
 	OServiceUserFlagLunch       uint32 = 0x00002001 // user is having a lunch
 	OServiceUserFlagBirthday    uint32 = 0x00080000 // user is having a birthday :DDD
 
+	OServicePrivacyFlagIdle   uint32 = 0x00000001
+	OServicePrivacyFlagMember uint32 = 0x00000002
+
 	OServiceStatusWebAware          uint32 = 0x00010000
 	OServiceStatusHideIP            uint32 = 0x00020000
 	OServiceStatusICQHomePage       uint32 = 0x00200000
@@ -265,6 +268,22 @@ type SNAC_0x01_0x11_OServiceIdleNotification struct {
 	IdleTime uint32
 }
 
+type SNAC_0x01_0x14_OServiceSetPrivacyFlags struct {
+	PrivacyFlags uint32
+}
+
+// IdleFlag returns whether other AIM users can see how long the user has been
+// idle.
+func (s SNAC_0x01_0x14_OServiceSetPrivacyFlags) IdleFlag() bool {
+	return s.PrivacyFlags&OServicePrivacyFlagIdle == OServicePrivacyFlagIdle
+}
+
+// MemberFlag returns whether other AIM users can see how long the user has been
+// a member.
+func (s SNAC_0x01_0x14_OServiceSetPrivacyFlags) MemberFlag() bool {
+	return s.PrivacyFlags&OServicePrivacyFlagMember == OServicePrivacyFlagMember
+}
+
 type SNAC_0x01_0x17_OServiceClientVersions struct {
 	Versions []uint16
 }

+ 68 - 0
wire/snacs_test.go

@@ -31,3 +31,71 @@ func TestBARTInfo_HasClearIconHash(t *testing.T) {
 		})
 	}
 }
+
+func TestSNAC_0x01_0x14_OServiceSetPrivacyFlags_IdleFlag(t *testing.T) {
+	type fields struct {
+		PrivacyFlags uint32
+	}
+	tests := []struct {
+		name   string
+		fields fields
+		want   bool
+	}{
+		{
+			name: "flag is set",
+			fields: fields{
+				PrivacyFlags: OServicePrivacyFlagIdle | OServicePrivacyFlagMember,
+			},
+			want: true,
+		},
+		{
+			name: "flag is not set",
+			fields: fields{
+				PrivacyFlags: OServicePrivacyFlagMember,
+			},
+			want: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			s := SNAC_0x01_0x14_OServiceSetPrivacyFlags{
+				PrivacyFlags: tt.fields.PrivacyFlags,
+			}
+			assert.Equal(t, tt.want, s.IdleFlag())
+		})
+	}
+}
+
+func TestSNAC_0x01_0x14_OServiceSetPrivacyFlags_MemberFlag(t *testing.T) {
+	type fields struct {
+		PrivacyFlags uint32
+	}
+	tests := []struct {
+		name   string
+		fields fields
+		want   bool
+	}{
+		{
+			name: "flag is set",
+			fields: fields{
+				PrivacyFlags: OServicePrivacyFlagIdle | OServicePrivacyFlagMember,
+			},
+			want: true,
+		},
+		{
+			name: "flag is not set",
+			fields: fields{
+				PrivacyFlags: OServicePrivacyFlagIdle,
+			},
+			want: false,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			s := SNAC_0x01_0x14_OServiceSetPrivacyFlags{
+				PrivacyFlags: tt.fields.PrivacyFlags,
+			}
+			assert.Equal(t, tt.want, s.MemberFlag())
+		})
+	}
+}