Explorar o código

implement SNAC(0x02,0x0C) - LocateGetDirReply

This make directory info lookup for a single user work.
Mike hai 1 ano
pai
achega
c4e0ac6fff

+ 4 - 4
foodgroup/auth_test.go

@@ -418,7 +418,7 @@ func TestAuthService_BUCPLoginRequest(t *testing.T) {
 	for _, tc := range cases {
 		t.Run(tc.name, func(t *testing.T) {
 			userManager := newMockUserManager(t)
-			for _, params := range tc.mockParams.getUserParams {
+			for _, params := range tc.mockParams.userManagerParams.getUserParams {
 				userManager.EXPECT().
 					User(params.screenName).
 					Return(params.result, params.err)
@@ -800,7 +800,7 @@ func TestAuthService_FLAPLoginResponse(t *testing.T) {
 	for _, tc := range cases {
 		t.Run(tc.name, func(t *testing.T) {
 			userManager := newMockUserManager(t)
-			for _, params := range tc.mockParams.getUserParams {
+			for _, params := range tc.mockParams.userManagerParams.getUserParams {
 				userManager.EXPECT().
 					User(params.screenName).
 					Return(params.result, params.err)
@@ -980,7 +980,7 @@ func TestAuthService_BUCPChallengeRequest(t *testing.T) {
 	for _, tc := range cases {
 		t.Run(tc.name, func(t *testing.T) {
 			userManager := newMockUserManager(t)
-			for _, params := range tc.mockParams.getUserParams {
+			for _, params := range tc.mockParams.userManagerParams.getUserParams {
 				userManager.EXPECT().
 					User(params.screenName).
 					Return(params.result, params.err)
@@ -1165,7 +1165,7 @@ func TestAuthService_RegisterBOSSession(t *testing.T) {
 					Return(params.dataOut, nil)
 			}
 			userManager := newMockUserManager(t)
-			for _, params := range tc.mockParams.getUserParams {
+			for _, params := range tc.mockParams.userManagerParams.getUserParams {
 				userManager.EXPECT().
 					User(params.screenName).
 					Return(params.result, nil)

+ 37 - 0
foodgroup/locate.go

@@ -234,3 +234,40 @@ func (s LocateService) SetKeywordInfo(ctx context.Context, sess *state.Session,
 		},
 	}, nil
 }
+
+// DirInfo returns directory information for a user.
+func (s LocateService) DirInfo(ctx context.Context, inFrame wire.SNACFrame, body wire.SNAC_0x02_0x0B_LocateGetDirInfo) (wire.SNACMessage, error) {
+	reply := wire.SNAC_0x02_0x0C_LocateGetDirReply{
+		Status: wire.LocateGetDirReplyOK,
+		TLVBlock: wire.TLVBlock{
+			TLVList: wire.TLVList{},
+		},
+	}
+
+	user, err := s.profileManager.User(state.NewIdentScreenName(body.WatcherScreenNames))
+	if err != nil {
+		return wire.SNACMessage{}, fmt.Errorf("User: %w", err)
+	}
+
+	if user != nil {
+		reply.Append(wire.NewTLVBE(wire.ODirTLVFirstName, user.AIMDirectoryInfo.FirstName))
+		reply.Append(wire.NewTLVBE(wire.ODirTLVLastName, user.AIMDirectoryInfo.LastName))
+		reply.Append(wire.NewTLVBE(wire.ODirTLVMiddleName, user.AIMDirectoryInfo.MiddleName))
+		reply.Append(wire.NewTLVBE(wire.ODirTLVMaidenName, user.AIMDirectoryInfo.MaidenName))
+		reply.Append(wire.NewTLVBE(wire.ODirTLVCountry, user.AIMDirectoryInfo.Country))
+		reply.Append(wire.NewTLVBE(wire.ODirTLVState, user.AIMDirectoryInfo.State))
+		reply.Append(wire.NewTLVBE(wire.ODirTLVCity, user.AIMDirectoryInfo.City))
+		reply.Append(wire.NewTLVBE(wire.ODirTLVNickName, user.AIMDirectoryInfo.NickName))
+		reply.Append(wire.NewTLVBE(wire.ODirTLVZIP, user.AIMDirectoryInfo.ZIPCode))
+		reply.Append(wire.NewTLVBE(wire.ODirTLVAddress, user.AIMDirectoryInfo.Address))
+	}
+
+	return wire.SNACMessage{
+		Frame: wire.SNACFrame{
+			FoodGroup: wire.Locate,
+			SubGroup:  wire.LocateGetDirReply,
+			RequestID: inFrame.RequestID,
+		},
+		Body: reply,
+	}, nil
+}

+ 127 - 0
foodgroup/locate_test.go

@@ -764,3 +764,130 @@ func TestLocateService_RightsQuery(t *testing.T) {
 
 	assert.Equal(t, expectSNAC, outputSNAC)
 }
+
+func TestLocateService_DirInfo(t *testing.T) {
+	tests := []struct {
+		// name is the unit test name
+		name string
+		// userSession is the session of the user setting info
+		userSession *state.Session
+		// inputSNAC is the SNAC sent from client to server
+		inputSNAC wire.SNACMessage
+		// expectOutput is the SNAC sent from the server to client
+		expectOutput wire.SNACMessage
+		// mockParams is the list of params sent to mocks that satisfy this
+		// method's dependencies
+		mockParams mockParams
+		// wantErr is the expected error
+		wantErr error
+	}{
+		{
+			name:        "happy path",
+			userSession: newTestSession("test-user"),
+			inputSNAC: wire.SNACMessage{
+				Frame: wire.SNACFrame{
+					RequestID: 1234,
+				},
+				Body: wire.SNAC_0x02_0x0B_LocateGetDirInfo{
+					WatcherScreenNames: "test-user",
+				},
+			},
+			expectOutput: wire.SNACMessage{
+				Frame: wire.SNACFrame{
+					FoodGroup: wire.Locate,
+					SubGroup:  wire.LocateGetDirReply,
+					RequestID: 1234,
+				},
+				Body: wire.SNAC_0x02_0x0C_LocateGetDirReply{
+					Status: wire.LocateGetDirReplyOK,
+					TLVBlock: wire.TLVBlock{
+						TLVList: wire.TLVList{
+							wire.NewTLVBE(wire.ODirTLVFirstName, "John"),
+							wire.NewTLVBE(wire.ODirTLVLastName, "Doe"),
+							wire.NewTLVBE(wire.ODirTLVMiddleName, "A"),
+							wire.NewTLVBE(wire.ODirTLVMaidenName, "Smith"),
+							wire.NewTLVBE(wire.ODirTLVCountry, "USA"),
+							wire.NewTLVBE(wire.ODirTLVState, "CA"),
+							wire.NewTLVBE(wire.ODirTLVCity, "San Francisco"),
+							wire.NewTLVBE(wire.ODirTLVNickName, "Johnny"),
+							wire.NewTLVBE(wire.ODirTLVZIP, "94107"),
+							wire.NewTLVBE(wire.ODirTLVAddress, "123 Main St"),
+						},
+					},
+				},
+			},
+			mockParams: mockParams{
+				profileManagerParams: profileManagerParams{
+					getUserParams: getUserParams{
+						{
+							screenName: state.NewIdentScreenName("test-user"),
+							result: &state.User{
+								AIMDirectoryInfo: state.AIMNameAndAddr{
+									FirstName:  "John",
+									LastName:   "Doe",
+									MiddleName: "A",
+									MaidenName: "Smith",
+									Country:    "USA",
+									State:      "CA",
+									City:       "San Francisco",
+									NickName:   "Johnny",
+									ZIPCode:    "94107",
+									Address:    "123 Main St",
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+		{
+			name:        "user not found",
+			userSession: newTestSession("test-user"),
+			inputSNAC: wire.SNACMessage{
+				Frame: wire.SNACFrame{
+					RequestID: 1234,
+				},
+				Body: wire.SNAC_0x02_0x0B_LocateGetDirInfo{
+					WatcherScreenNames: "test-user",
+				},
+			},
+			expectOutput: wire.SNACMessage{
+				Frame: wire.SNACFrame{
+					FoodGroup: wire.Locate,
+					SubGroup:  wire.LocateGetDirReply,
+					RequestID: 1234,
+				},
+				Body: wire.SNAC_0x02_0x0C_LocateGetDirReply{
+					Status: wire.LocateGetDirReplyOK,
+					TLVBlock: wire.TLVBlock{
+						TLVList: wire.TLVList{},
+					},
+				},
+			},
+			mockParams: mockParams{
+				profileManagerParams: profileManagerParams{
+					getUserParams: getUserParams{
+						{
+							screenName: state.NewIdentScreenName("test-user"),
+							result:     nil,
+						},
+					},
+				},
+			},
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			profileManager := newMockProfileManager(t)
+			for _, params := range tt.mockParams.profileManagerParams.getUserParams {
+				profileManager.EXPECT().
+					User(params.screenName).
+					Return(params.result, params.err)
+			}
+			svc := NewLocateService(nil, nil, profileManager, nil)
+			outputSNAC, err := svc.DirInfo(nil, tt.inputSNAC.Frame, tt.inputSNAC.Body.(wire.SNAC_0x02_0x0B_LocateGetDirInfo))
+			assert.NoError(t, err)
+			assert.Equal(t, tt.expectOutput, outputSNAC)
+		})
+	}
+}

+ 58 - 0
foodgroup/mock_profile_manager_test.go

@@ -448,6 +448,64 @@ func (_c *mockProfileManager_SetProfile_Call) RunAndReturn(run func(state.IdentS
 	return _c
 }
 
+// User provides a mock function with given fields: screenName
+func (_m *mockProfileManager) User(screenName state.IdentScreenName) (*state.User, error) {
+	ret := _m.Called(screenName)
+
+	if len(ret) == 0 {
+		panic("no return value specified for User")
+	}
+
+	var r0 *state.User
+	var r1 error
+	if rf, ok := ret.Get(0).(func(state.IdentScreenName) (*state.User, error)); ok {
+		return rf(screenName)
+	}
+	if rf, ok := ret.Get(0).(func(state.IdentScreenName) *state.User); ok {
+		r0 = rf(screenName)
+	} else {
+		if ret.Get(0) != nil {
+			r0 = ret.Get(0).(*state.User)
+		}
+	}
+
+	if rf, ok := ret.Get(1).(func(state.IdentScreenName) error); ok {
+		r1 = rf(screenName)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// mockProfileManager_User_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'User'
+type mockProfileManager_User_Call struct {
+	*mock.Call
+}
+
+// User is a helper method to define mock.On call
+//   - screenName state.IdentScreenName
+func (_e *mockProfileManager_Expecter) User(screenName interface{}) *mockProfileManager_User_Call {
+	return &mockProfileManager_User_Call{Call: _e.mock.On("User", screenName)}
+}
+
+func (_c *mockProfileManager_User_Call) Run(run func(screenName state.IdentScreenName)) *mockProfileManager_User_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(state.IdentScreenName))
+	})
+	return _c
+}
+
+func (_c *mockProfileManager_User_Call) Return(_a0 *state.User, _a1 error) *mockProfileManager_User_Call {
+	_c.Call.Return(_a0, _a1)
+	return _c
+}
+
+func (_c *mockProfileManager_User_Call) RunAndReturn(run func(state.IdentScreenName) (*state.User, error)) *mockProfileManager_User_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
 // newMockProfileManager creates a new instance of mockProfileManager. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
 // The first argument is typically a *testing.T value.
 func newMockProfileManager(t interface {

+ 1 - 0
foodgroup/test_helpers.go

@@ -364,6 +364,7 @@ type profileManagerParams struct {
 	findByAIMEmailParams
 	findByAIMKeywordParams
 	findByAIMNameAndAddrParams
+	getUserParams
 	interestListParams
 	retrieveProfileParams
 	setDirectoryInfoParams

+ 1 - 0
foodgroup/types.go

@@ -96,6 +96,7 @@ type ProfileManager interface {
 	SetDirectoryInfo(name state.IdentScreenName, info state.AIMNameAndAddr) error
 	SetKeywords(name state.IdentScreenName, keywords [5]string) error
 	SetProfile(screenName state.IdentScreenName, body string) error
+	User(screenName state.IdentScreenName) (*state.User, error)
 }
 
 type MessageRelayer interface {

+ 11 - 3
server/oscar/handler/locate.go

@@ -13,6 +13,7 @@ import (
 )
 
 type LocateService interface {
+	DirInfo(ctx context.Context, frame wire.SNACFrame, body wire.SNAC_0x02_0x0B_LocateGetDirInfo) (wire.SNACMessage, error)
 	RightsQuery(ctx context.Context, inFrame wire.SNACFrame) wire.SNACMessage
 	SetDirInfo(ctx context.Context, sess *state.Session, inFrame wire.SNACFrame, inBody wire.SNAC_0x02_0x09_LocateSetDirInfo) (wire.SNACMessage, error)
 	SetInfo(ctx context.Context, sess *state.Session, inBody wire.SNAC_0x02_0x04_LocateSetInfo) error
@@ -62,10 +63,17 @@ func (h LocateHandler) SetDirInfo(ctx context.Context, sess *state.Session, inFr
 	return rw.SendSNAC(outSNAC.Frame, outSNAC.Body)
 }
 
-func (h LocateHandler) GetDirInfo(ctx context.Context, _ *state.Session, inFrame wire.SNACFrame, r io.Reader, _ oscar.ResponseWriter) error {
+func (h LocateHandler) GetDirInfo(ctx context.Context, _ *state.Session, inFrame wire.SNACFrame, r io.Reader, rw oscar.ResponseWriter) error {
 	inBody := wire.SNAC_0x02_0x0B_LocateGetDirInfo{}
-	h.LogRequest(ctx, inFrame, inBody)
-	return wire.UnmarshalBE(&inBody, r)
+	if err := wire.UnmarshalBE(&inBody, r); err != nil {
+		return err
+	}
+	outSNAC, err := h.LocateService.DirInfo(ctx, inFrame, inBody)
+	if err != nil {
+		return err
+	}
+	h.LogRequestAndResponse(ctx, inFrame, inBody, outSNAC.Frame, outSNAC.Body)
+	return rw.SendSNAC(outSNAC.Frame, outSNAC.Body)
 }
 
 func (h LocateHandler) SetKeywordInfo(ctx context.Context, sess *state.Session, inFrame wire.SNACFrame, r io.Reader, rw oscar.ResponseWriter) error {

+ 17 - 0
server/oscar/handler/locate_test.go

@@ -21,10 +21,27 @@ func TestLocateHandler_GetDirInfo(t *testing.T) {
 			WatcherScreenNames: "screen-name",
 		},
 	}
+	output := wire.SNACMessage{
+		Frame: wire.SNACFrame{
+			FoodGroup: wire.Locate,
+			SubGroup:  wire.LocateGetDirReply,
+		},
+		Body: wire.SNAC_0x02_0x0C_LocateGetDirReply{
+			Status: 1,
+		},
+	}
 
 	svc := newMockLocateService(t)
+	svc.EXPECT().
+		DirInfo(mock.Anything, input.Frame, input.Body).
+		Return(output, nil)
+
 	h := NewLocateHandler(svc, slog.Default())
+
 	responseWriter := newMockResponseWriter(t)
+	responseWriter.EXPECT().
+		SendSNAC(output.Frame, output.Body).
+		Return(nil)
 
 	buf := &bytes.Buffer{}
 	assert.NoError(t, wire.MarshalBE(input.Body, buf))

+ 58 - 0
server/oscar/handler/mock_locate_test.go

@@ -24,6 +24,64 @@ func (_m *mockLocateService) EXPECT() *mockLocateService_Expecter {
 	return &mockLocateService_Expecter{mock: &_m.Mock}
 }
 
+// DirInfo provides a mock function with given fields: ctx, frame, body
+func (_m *mockLocateService) DirInfo(ctx context.Context, frame wire.SNACFrame, body wire.SNAC_0x02_0x0B_LocateGetDirInfo) (wire.SNACMessage, error) {
+	ret := _m.Called(ctx, frame, body)
+
+	if len(ret) == 0 {
+		panic("no return value specified for DirInfo")
+	}
+
+	var r0 wire.SNACMessage
+	var r1 error
+	if rf, ok := ret.Get(0).(func(context.Context, wire.SNACFrame, wire.SNAC_0x02_0x0B_LocateGetDirInfo) (wire.SNACMessage, error)); ok {
+		return rf(ctx, frame, body)
+	}
+	if rf, ok := ret.Get(0).(func(context.Context, wire.SNACFrame, wire.SNAC_0x02_0x0B_LocateGetDirInfo) wire.SNACMessage); ok {
+		r0 = rf(ctx, frame, body)
+	} else {
+		r0 = ret.Get(0).(wire.SNACMessage)
+	}
+
+	if rf, ok := ret.Get(1).(func(context.Context, wire.SNACFrame, wire.SNAC_0x02_0x0B_LocateGetDirInfo) error); ok {
+		r1 = rf(ctx, frame, body)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// mockLocateService_DirInfo_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DirInfo'
+type mockLocateService_DirInfo_Call struct {
+	*mock.Call
+}
+
+// DirInfo is a helper method to define mock.On call
+//   - ctx context.Context
+//   - frame wire.SNACFrame
+//   - body wire.SNAC_0x02_0x0B_LocateGetDirInfo
+func (_e *mockLocateService_Expecter) DirInfo(ctx interface{}, frame interface{}, body interface{}) *mockLocateService_DirInfo_Call {
+	return &mockLocateService_DirInfo_Call{Call: _e.mock.On("DirInfo", ctx, frame, body)}
+}
+
+func (_c *mockLocateService_DirInfo_Call) Run(run func(ctx context.Context, frame wire.SNACFrame, body wire.SNAC_0x02_0x0B_LocateGetDirInfo)) *mockLocateService_DirInfo_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context), args[1].(wire.SNACFrame), args[2].(wire.SNAC_0x02_0x0B_LocateGetDirInfo))
+	})
+	return _c
+}
+
+func (_c *mockLocateService_DirInfo_Call) Return(_a0 wire.SNACMessage, _a1 error) *mockLocateService_DirInfo_Call {
+	_c.Call.Return(_a0, _a1)
+	return _c
+}
+
+func (_c *mockLocateService_DirInfo_Call) RunAndReturn(run func(context.Context, wire.SNACFrame, wire.SNAC_0x02_0x0B_LocateGetDirInfo) (wire.SNACMessage, error)) *mockLocateService_DirInfo_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
 // RightsQuery provides a mock function with given fields: ctx, inFrame
 func (_m *mockLocateService) RightsQuery(ctx context.Context, inFrame wire.SNACFrame) wire.SNACMessage {
 	ret := _m.Called(ctx, inFrame)

+ 8 - 0
wire/snacs.go

@@ -370,6 +370,9 @@ const (
 	LocateTLVTagsRightsMaxCertsLen uint16 = 0x04
 	// LocateTLVTagsRightsMaxMaxShortCapabilities is the max allowed # of short UUID capabilities allowed
 	LocateTLVTagsRightsMaxMaxShortCapabilities uint16 = 0x05
+
+	LocateGetDirReplyOK          uint16 = 0x01 // Directory info lookup succeeded
+	LocateGetDirReplyUnavailable uint16 = 0x02 // Directory info lookup unavailable
 )
 
 type SNAC_0x02_0x03_LocateRightsReply struct {
@@ -397,6 +400,11 @@ type SNAC_0x02_0x0B_LocateGetDirInfo struct {
 	WatcherScreenNames string `oscar:"len_prefix=uint8"`
 }
 
+type SNAC_0x02_0x0C_LocateGetDirReply struct {
+	Status uint16
+	TLVBlock
+}
+
 type SNAC_0x02_0x0F_LocateSetKeywordInfo struct {
 	TLVRestBlock
 }