Просмотр исходного кода

ensure toc session initialization mirrors oscar

Mike 4 месяцев назад
Родитель
Сommit
8c15f05607

+ 86 - 40
server/toc/cmd_client.go

@@ -2288,7 +2288,7 @@ func (s OSCARProxy) SetInfo(ctx context.Context, me *state.SessionInstance, args
 // Command syntax: toc_signon <authorizer host> <authorizer port> <User Name> <Password> <language> <version>
 // Command syntax: toc2_signon <authorizer host> <authorizer port> <User Name> <Password> <language> <version>
 // Command syntax: toc2_login <authorizer host> <authorizer port> <User Name> <Password> <language> <version> [additional params...]
-func (s OSCARProxy) Signon(ctx context.Context, args []byte) (*state.SessionInstance, []string) {
+func (s OSCARProxy) Signon(ctx context.Context, args []byte, recalcWarning func(ctx context.Context, instance *state.SessionInstance) error, lowerWarnLevel func(ctx context.Context, instance *state.SessionInstance), chatRegistry *ChatRegistry) (*state.SessionInstance, []string) {
 	var cmd, userName, password string
 
 	if _, err := parseArgs(args, &cmd, nil, nil, &userName, &password); err != nil {
@@ -2338,54 +2338,117 @@ func (s OSCARProxy) Signon(ctx context.Context, args []byte) (*state.SessionInst
 		return nil, s.runtimeErr(ctx, fmt.Errorf("AuthService.CrackCookie: %w", err))
 	}
 
-	sess, err := s.AuthService.RegisterBOSSession(ctx, serverCookie)
+	instance, err := s.AuthService.RegisterBOSSession(ctx, serverCookie)
 	if err != nil {
 		return nil, s.runtimeErr(ctx, fmt.Errorf("AuthService.RegisterBOSSession: %w", err))
 	}
 
-	// set chat capability so that... tk
-	sess.SetCaps([][16]byte{wire.CapChat})
+	if err = instance.Session().RunOnce(func() error {
+		// make buddy list visible to other users
+		if err := s.BuddyListRegistry.RegisterBuddyList(ctx, instance.IdentScreenName()); err != nil {
+			return fmt.Errorf("unable to init buddy list: %w", err)
+		}
+		// restore warning level from last session
+		if err := recalcWarning(ctx, instance); err != nil {
+			return fmt.Errorf("failed to recalculate warning level: %w", err)
+		}
+		// periodically decay warning level
+		go lowerWarnLevel(ctx, instance)
+		return nil
+	}); err != nil {
+		return nil, s.runtimeErr(ctx, fmt.Errorf("Session.RunOnce: %w", err))
+	}
+
+	// Update user visibility when an instance closes, as the user's overall status may change.
+	// Example: With 1 away and 1 non-away instance, the user appears available. If the non-away
+	// instance closes, the user should appear away.
+	instance.OnClose(func() {
+		if shuttingDown(ctx) {
+			return
+		}
+		if instance.Session().Invisible() {
+			if err := s.BuddyService.BroadcastBuddyDeparted(ctx, instance); err != nil {
+				s.Logger.ErrorContext(ctx, "error sending buddy departure notifications", "err", err.Error())
+			}
+		} else {
+			if err := s.BuddyService.BroadcastBuddyArrived(ctx, instance.IdentScreenName(), instance.Session().TLVUserInfo()); err != nil {
+				s.Logger.ErrorContext(ctx, "error sending buddy arrival notifications", "err", err.Error())
+			}
+		}
+	})
 
-	if err := s.BuddyListRegistry.RegisterBuddyList(ctx, sess.IdentScreenName()); err != nil {
-		return nil, s.runtimeErr(ctx, fmt.Errorf("BuddyListRegistry.RegisterBuddyList: %w", err))
-	}
+	instance.Session().OnSessionClose(func() {
+		if !shuttingDown(ctx) {
+			if err := s.BuddyService.BroadcastBuddyDeparted(ctx, instance); err != nil {
+				s.Logger.ErrorContext(ctx, "error sending buddy departure notifications", "err", err.Error())
+			}
+		}
 
-	u, err := s.TOCConfigStore.User(ctx, sess.IdentScreenName())
-	if err != nil {
-		return nil, s.runtimeErr(ctx, fmt.Errorf("TOCConfigStore.User: %w", err))
-	}
-	if u == nil {
-		return nil, s.runtimeErr(ctx, fmt.Errorf("TOCConfigStore.User: user not found"))
-	}
+		ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
+		defer cancel()
+
+		// buddy list must be cleared before session is closed, otherwise
+		// there will be a race condition that could cause the buddy list
+		// be prematurely deleted.
+		if err := s.BuddyListRegistry.UnregisterBuddyList(ctx, instance.IdentScreenName()); err != nil {
+			s.Logger.ErrorContext(ctx, "error removing buddy list entry", "err", err.Error())
+		}
+		for _, sess := range chatRegistry.Sessions() {
+			s.AuthService.SignoutChat(ctx, sess)
+			sess.CloseInstance() // stop async server SNAC reply handler for this chat room
+		}
+		s.AuthService.Signout(ctx, instance)
+	})
+
+	// set chat capability so that... tk
+	instance.SetCaps([][16]byte{wire.CapChat})
 
 	if cmd == "toc_signon" {
-		return sess, []string{"SIGN_ON:TOC1.0", fmt.Sprintf("CONFIG:%s", u.TOCConfig), fmt.Sprintf("NICK:%s", sess.DisplayScreenName().String())}
+		u, err := s.TOCConfigStore.User(ctx, instance.IdentScreenName())
+		if err != nil {
+			return nil, s.runtimeErr(ctx, fmt.Errorf("TOCConfigStore.User: %w", err))
+		}
+		if u == nil {
+			return nil, s.runtimeErr(ctx, fmt.Errorf("TOCConfigStore.User: user not found"))
+		}
+
+		return instance, []string{"SIGN_ON:TOC1.0", fmt.Sprintf("CONFIG:%s", u.TOCConfig), fmt.Sprintf("NICK:%s", instance.DisplayScreenName().String())}
 	}
 
 	supportsTOC2MsgEnc := cmd == "toc2_login"
-	sess.SetTOC2(supportsTOC2MsgEnc)
+	instance.SetTOC2(supportsTOC2MsgEnc)
 
-	if err := s.FeedbagService.Use(ctx, sess); err != nil {
-		return sess, s.runtimeErr(ctx, fmt.Errorf("FeedbagService.Use: %w", err))
+	if err := s.FeedbagService.Use(ctx, instance); err != nil {
+		return instance, s.runtimeErr(ctx, fmt.Errorf("FeedbagService.Use: %w", err))
 	}
 
-	fb, err := s.FeedbagManager.Feedbag(ctx, sess.IdentScreenName())
+	fb, err := s.FeedbagManager.Feedbag(ctx, instance.IdentScreenName())
 	if err != nil {
-		return sess, s.runtimeErr(ctx, fmt.Errorf("FeedbagManager.Feedbag: %w", err))
+		return instance, s.runtimeErr(ctx, fmt.Errorf("FeedbagManager.Feedbag: %w", err))
 	}
 
 	signon := []string{
 		"SIGN_ON:TOC2.0",
-		fmt.Sprintf("NICK:%s", sess.DisplayScreenName().String()),
+		fmt.Sprintf("NICK:%s", instance.DisplayScreenName().String()),
 	}
 
 	cfg, err := buildToc2Config(fb)
 	if err != nil {
-		return sess, s.runtimeErr(ctx, fmt.Errorf("buildToc2Config: %w", err))
+		return instance, s.runtimeErr(ctx, fmt.Errorf("buildToc2Config: %w", err))
 	}
 	signon = append(signon, cfg...)
 
-	return sess, signon
+	return instance, signon
+}
+
+func shuttingDown(ctx context.Context) bool {
+	select {
+	case <-ctx.Done():
+		// server is shutting down, don't send buddy notifications
+		return true
+	default:
+	}
+	return false
 }
 
 // buildToc2Config constructs configuration for CONFIG2
@@ -2491,23 +2554,6 @@ func buildToc2Config(fb []wire.FeedbagItem) ([]string, error) {
 	return []string{"CONFIG2:" + strings.Join(cfg, "\n") + "\n"}, nil
 }
 
-// Signout terminates a TOC session. It sends departure notifications to
-// buddies, de-registers buddy list and session.
-func (s OSCARProxy) Signout(ctx context.Context, me *state.SessionInstance, chatRegistry *ChatRegistry) {
-	if err := s.BuddyService.BroadcastBuddyDeparted(ctx, me); err != nil {
-		s.Logger.ErrorContext(ctx, "error sending departure notifications", "err", err.Error())
-	}
-	if err := s.BuddyListRegistry.UnregisterBuddyList(ctx, me.IdentScreenName()); err != nil {
-		s.Logger.ErrorContext(ctx, "error removing buddy list entry", "err", err.Error())
-	}
-	s.AuthService.Signout(ctx, me)
-
-	for _, sess := range chatRegistry.Sessions() {
-		s.AuthService.SignoutChat(ctx, sess)
-		sess.CloseInstance() // stop async server SNAC reply handler for this chat room
-	}
-}
-
 // newHTTPAuthToken creates a HMAC token for authenticating TOC HTTP requests
 func (s OSCARProxy) newHTTPAuthToken(me state.IdentScreenName) (string, error) {
 	cookie, err := s.CookieBaker.Issue([]byte(me.String()))

+ 8 - 188
server/toc/cmd_client_test.go

@@ -6488,14 +6488,6 @@ func TestOSCARProxy_Signon(t *testing.T) {
 						},
 					},
 				},
-				tocConfigParams: tocConfigParams{
-					userParams: userParams{
-						{
-							screenName:   state.NewIdentScreenName("me"),
-							returnedUser: &state.User{}, // TOC2 path calls User but does not use result
-						},
-					},
-				},
 				feedBagParams: feedBagParams{
 					feedbagServiceUseParams: feedbagServiceUseParams{{err: nil}},
 					feedbagParams: feedbagParams{
@@ -6558,14 +6550,6 @@ func TestOSCARProxy_Signon(t *testing.T) {
 						},
 					},
 				},
-				tocConfigParams: tocConfigParams{
-					userParams: userParams{
-						{
-							screenName:   state.NewIdentScreenName("me"),
-							returnedUser: &state.User{},
-						},
-					},
-				},
 				feedBagParams: feedBagParams{
 					feedbagServiceUseParams: feedbagServiceUseParams{{err: nil}},
 					feedbagParams: feedbagParams{
@@ -6622,11 +6606,6 @@ func TestOSCARProxy_Signon(t *testing.T) {
 						{user: state.NewIdentScreenName("me")},
 					},
 				},
-				tocConfigParams: tocConfigParams{
-					userParams: userParams{
-						{screenName: state.NewIdentScreenName("me"), returnedUser: &state.User{}},
-					},
-				},
 				feedBagParams: feedBagParams{
 					feedbagServiceUseParams: feedbagServiceUseParams{{err: io.EOF}},
 				},
@@ -6676,11 +6655,6 @@ func TestOSCARProxy_Signon(t *testing.T) {
 						{user: state.NewIdentScreenName("me")},
 					},
 				},
-				tocConfigParams: tocConfigParams{
-					userParams: userParams{
-						{screenName: state.NewIdentScreenName("me"), returnedUser: &state.User{}},
-					},
-				},
 				feedBagParams: feedBagParams{
 					feedbagServiceUseParams: feedbagServiceUseParams{{err: nil}},
 					feedbagParams: feedbagParams{
@@ -6984,15 +6958,22 @@ func TestOSCARProxy_Signon(t *testing.T) {
 					Return(params.results, params.err)
 			}
 
+			buddySvc := newMockBuddyService(t)
+
 			svc := OSCARProxy{
 				AuthService:       authSvc,
 				BuddyListRegistry: buddyRegistry,
+				BuddyService:      buddySvc,
 				Logger:            slog.Default(),
 				TOCConfigStore:    tocCfg,
 				FeedbagService:    fbSvc,
 				FeedbagManager:    fbMgr,
 			}
-			sess, msg := svc.Signon(ctx, tc.givenCmd)
+			sess, msg := svc.Signon(ctx, tc.givenCmd,
+				func(ctx context.Context, instance *state.SessionInstance) error { return nil },
+				func(ctx context.Context, instance *state.SessionInstance) {},
+				NewChatRegistry(),
+			)
 
 			assert.Equal(t, tc.wantMsg, msg)
 			if tc.checkSession != nil {
@@ -7004,167 +6985,6 @@ func TestOSCARProxy_Signon(t *testing.T) {
 	}
 }
 
-func TestOSCARProxy_Signout(t *testing.T) {
-	cases := []struct {
-		// name is the unit test name
-		name string
-		// me is the TOC user session
-		me *state.SessionInstance
-		// givenChatRegistry is the chat registry passed to the function
-		chatRegistry *ChatRegistry
-		// mockParams is the list of params sent to mocks that satisfy this
-		// method's dependencies
-		mockParams mockParams
-	}{
-		{
-			name: "successfully sign out",
-			me:   newTestSession("me"),
-			chatRegistry: func() *ChatRegistry {
-				cr := NewChatRegistry()
-
-				s1 := state.NewSession().AddInstance()
-				s1.Session().SetIdentScreenName(state.NewIdentScreenName("me1"))
-				cr.RegisterSess(0, s1)
-
-				s2 := state.NewSession().AddInstance()
-				s2.Session().SetIdentScreenName(state.NewIdentScreenName("me2"))
-				cr.RegisterSess(1, s2)
-
-				return cr
-			}(),
-			mockParams: mockParams{
-				buddyParams: buddyParams{
-					broadcastBuddyDepartedParams: broadcastBuddyDepartedParams{
-						{
-							me: state.NewIdentScreenName("me"),
-						},
-					},
-				},
-				buddyListRegistryParams: buddyListRegistryParams{
-					unregisterBuddyListParams: unregisterBuddyListParams{
-						{
-							user: state.NewIdentScreenName("me"),
-						},
-					},
-				},
-				authParams: authParams{
-					signoutParams: signoutParams{
-						{
-							me: state.NewIdentScreenName("me"),
-						},
-					},
-					signoutChatParams: signoutChatParams{
-						{
-							me: state.NewIdentScreenName("me1"),
-						},
-						{
-							me: state.NewIdentScreenName("me2"),
-						},
-					},
-				},
-			},
-		},
-		{
-			name: "sign out, receive error from buddy service",
-			me:   newTestSession("me"),
-			chatRegistry: func() *ChatRegistry {
-				return NewChatRegistry()
-			}(),
-			mockParams: mockParams{
-				buddyParams: buddyParams{
-					broadcastBuddyDepartedParams: broadcastBuddyDepartedParams{
-						{
-							me:  state.NewIdentScreenName("me"),
-							err: io.EOF,
-						},
-					},
-				},
-				buddyListRegistryParams: buddyListRegistryParams{
-					unregisterBuddyListParams: unregisterBuddyListParams{
-						{
-							user: state.NewIdentScreenName("me"),
-						},
-					},
-				},
-				authParams: authParams{
-					signoutParams: signoutParams{
-						{
-							me: state.NewIdentScreenName("me"),
-						},
-					},
-				},
-			},
-		},
-		{
-			name: "sign out, receive error from buddy list registry",
-			me:   newTestSession("me"),
-			chatRegistry: func() *ChatRegistry {
-				return NewChatRegistry()
-			}(),
-			mockParams: mockParams{
-				buddyParams: buddyParams{
-					broadcastBuddyDepartedParams: broadcastBuddyDepartedParams{
-						{
-							me: state.NewIdentScreenName("me"),
-						},
-					},
-				},
-				buddyListRegistryParams: buddyListRegistryParams{
-					unregisterBuddyListParams: unregisterBuddyListParams{
-						{
-							user: state.NewIdentScreenName("me"),
-							err:  io.EOF,
-						},
-					},
-				},
-				authParams: authParams{
-					signoutParams: signoutParams{
-						{
-							me: state.NewIdentScreenName("me"),
-						},
-					},
-				},
-			},
-		},
-	}
-
-	for _, tc := range cases {
-		t.Run(tc.name, func(t *testing.T) {
-			ctx := context.Background()
-
-			buddySvc := newMockBuddyService(t)
-			for _, params := range tc.mockParams.broadcastBuddyDepartedParams {
-				buddySvc.EXPECT().
-					BroadcastBuddyDeparted(ctx, matchSession(params.me)).
-					Return(params.err)
-			}
-
-			buddyListSvc := newMockBuddyListRegistry(t)
-			for _, params := range tc.mockParams.unregisterBuddyListParams {
-				buddyListSvc.EXPECT().
-					UnregisterBuddyList(matchContext(), params.user).
-					Return(params.err)
-			}
-
-			authSvc := newMockAuthService(t)
-			for _, params := range tc.mockParams.signoutParams {
-				authSvc.EXPECT().Signout(ctx, matchSession(params.me))
-			}
-			for _, params := range tc.mockParams.signoutChatParams {
-				authSvc.EXPECT().SignoutChat(ctx, matchSession(params.me))
-			}
-
-			svc := OSCARProxy{
-				AuthService:       authSvc,
-				BuddyListRegistry: buddyListSvc,
-				BuddyService:      buddySvc,
-				Logger:            slog.Default(),
-			}
-			svc.Signout(ctx, tc.me, tc.chatRegistry)
-		})
-	}
-}
-
 func TestOSCARProxy_RecvClientCmd_UnknownCmd(t *testing.T) {
 	ctx := context.Background()
 

+ 1 - 6
server/toc/cmd_server.go

@@ -9,7 +9,6 @@ import (
 	"log/slog"
 	"net"
 	"strings"
-	"time"
 
 	"github.com/google/uuid"
 
@@ -32,11 +31,7 @@ func (s OSCARProxy) RecvBOS(ctx context.Context, me *state.SessionInstance, chat
 	for {
 		select {
 		case <-ctx.Done():
-			func() {
-				shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
-				defer cancel()
-				s.Signout(shutdownCtx, me, chatRegistry)
-			}()
+			me.CloseInstance()
 			return nil
 		case <-me.Closed():
 			return errDisconnect

+ 63 - 0
server/toc/mock_buddy_service_test.go

@@ -102,6 +102,69 @@ func (_c *mockBuddyService_AddBuddies_Call) RunAndReturn(run func(ctx context.Co
 	return _c
 }
 
+// BroadcastBuddyArrived provides a mock function for the type mockBuddyService
+func (_mock *mockBuddyService) BroadcastBuddyArrived(ctx context.Context, screenName state.IdentScreenName, userInfo wire.TLVUserInfo) error {
+	ret := _mock.Called(ctx, screenName, userInfo)
+
+	if len(ret) == 0 {
+		panic("no return value specified for BroadcastBuddyArrived")
+	}
+
+	var r0 error
+	if returnFunc, ok := ret.Get(0).(func(context.Context, state.IdentScreenName, wire.TLVUserInfo) error); ok {
+		r0 = returnFunc(ctx, screenName, userInfo)
+	} else {
+		r0 = ret.Error(0)
+	}
+	return r0
+}
+
+// mockBuddyService_BroadcastBuddyArrived_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BroadcastBuddyArrived'
+type mockBuddyService_BroadcastBuddyArrived_Call struct {
+	*mock.Call
+}
+
+// BroadcastBuddyArrived is a helper method to define mock.On call
+//   - ctx context.Context
+//   - screenName state.IdentScreenName
+//   - userInfo wire.TLVUserInfo
+func (_e *mockBuddyService_Expecter) BroadcastBuddyArrived(ctx interface{}, screenName interface{}, userInfo interface{}) *mockBuddyService_BroadcastBuddyArrived_Call {
+	return &mockBuddyService_BroadcastBuddyArrived_Call{Call: _e.mock.On("BroadcastBuddyArrived", ctx, screenName, userInfo)}
+}
+
+func (_c *mockBuddyService_BroadcastBuddyArrived_Call) Run(run func(ctx context.Context, screenName state.IdentScreenName, userInfo wire.TLVUserInfo)) *mockBuddyService_BroadcastBuddyArrived_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		var arg0 context.Context
+		if args[0] != nil {
+			arg0 = args[0].(context.Context)
+		}
+		var arg1 state.IdentScreenName
+		if args[1] != nil {
+			arg1 = args[1].(state.IdentScreenName)
+		}
+		var arg2 wire.TLVUserInfo
+		if args[2] != nil {
+			arg2 = args[2].(wire.TLVUserInfo)
+		}
+		run(
+			arg0,
+			arg1,
+			arg2,
+		)
+	})
+	return _c
+}
+
+func (_c *mockBuddyService_BroadcastBuddyArrived_Call) Return(err error) *mockBuddyService_BroadcastBuddyArrived_Call {
+	_c.Call.Return(err)
+	return _c
+}
+
+func (_c *mockBuddyService_BroadcastBuddyArrived_Call) RunAndReturn(run func(ctx context.Context, screenName state.IdentScreenName, userInfo wire.TLVUserInfo) error) *mockBuddyService_BroadcastBuddyArrived_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
 // BroadcastBuddyDeparted provides a mock function for the type mockBuddyService
 func (_mock *mockBuddyService) BroadcastBuddyDeparted(ctx context.Context, instance *state.SessionInstance) error {
 	ret := _mock.Called(ctx, instance)

+ 5 - 15
server/toc/server.go

@@ -359,7 +359,9 @@ func (s *Server) dispatchFLAP(ctx context.Context, conn net.Conn) error {
 		return nil
 	}
 
-	sessBOS, err := s.login(ctx, clientFlap)
+	chatRegistry := NewChatRegistry()
+
+	sessBOS, err := s.login(ctx, clientFlap, chatRegistry)
 	if err != nil {
 		return fmt.Errorf("s.login: %w", err)
 	}
@@ -378,8 +380,6 @@ func (s *Server) dispatchFLAP(ctx context.Context, conn net.Conn) error {
 		sessBOS.SetRemoteAddr(&ip)
 	}
 
-	chatRegistry := NewChatRegistry()
-
 	return s.handleTOCRequest(ctx, closeConn, sessBOS, chatRegistry, clientFlap)
 }
 
@@ -398,10 +398,6 @@ func (s *Server) handleTOCRequest(
 	chatRegistry *ChatRegistry,
 	clientFlap *wire.FlapClient,
 ) error {
-	if err := s.recalcWarning(ctx, sessBOS); err != nil {
-		return fmt.Errorf("failed to recalculate warning level: %w", err)
-	}
-
 	// TOC response queue
 	msgCh := make(chan []string, 1)
 
@@ -427,12 +423,6 @@ func (s *Server) handleTOCRequest(
 		return errors.Join(err, errServerWrite)
 	})
 
-	// process warning limits
-	g.Go(func() error {
-		s.lowerWarnLevel(ctx, sessBOS)
-		return nil
-	})
-
 	return g.Wait()
 }
 
@@ -497,7 +487,7 @@ func (s *Server) sendToClient(ctx context.Context, toClient <-chan []string, cli
 	}
 }
 
-func (s *Server) login(ctx context.Context, clientFlap *wire.FlapClient) (*state.SessionInstance, error) {
+func (s *Server) login(ctx context.Context, clientFlap *wire.FlapClient, chatRegistry *ChatRegistry) (*state.SessionInstance, error) {
 	clientFrame, err := clientFlap.ReceiveFLAP()
 	if err != nil {
 		if errors.Is(err, io.EOF) {
@@ -506,7 +496,7 @@ func (s *Server) login(ctx context.Context, clientFlap *wire.FlapClient) (*state
 		return nil, fmt.Errorf("clientFlap.ReceiveFLAP: %w", err)
 	}
 
-	sessBOS, reply := s.bosProxy.Signon(ctx, clientFrame.Payload)
+	sessBOS, reply := s.bosProxy.Signon(ctx, clientFrame.Payload, s.recalcWarning, s.lowerWarnLevel, chatRegistry)
 
 	for _, m := range reply {
 		if err := clientFlap.SendDataFrame([]byte(m)); err != nil {

+ 1 - 0
server/toc/types.go

@@ -13,6 +13,7 @@ import (
 
 type BuddyService interface {
 	AddBuddies(ctx context.Context, instance *state.SessionInstance, inBody wire.SNAC_0x03_0x04_BuddyAddBuddies) error
+	BroadcastBuddyArrived(ctx context.Context, screenName state.IdentScreenName, userInfo wire.TLVUserInfo) error
 	BroadcastBuddyDeparted(ctx context.Context, instance *state.SessionInstance) error
 	DelBuddies(ctx context.Context, instance *state.SessionInstance, inBody wire.SNAC_0x03_0x05_BuddyDelBuddies) error
 	RightsQuery(ctx context.Context, inFrame wire.SNACFrame) wire.SNACMessage