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

split OService services into separate BOS/CHAT services

Mike 2 лет назад
Родитель
Сommit
1d808d9d6f

+ 6 - 0
.mockery.yaml

@@ -29,6 +29,12 @@ packages:
       OServiceHandler:
       OServiceHandler:
         config:
         config:
           filename: "oservice_mock.go"
           filename: "oservice_mock.go"
+      OServiceBOSHandler:
+        config:
+          filename: "oservice_bos_mock.go"
+      OServiceChatHandler:
+        config:
+          filename: "oservice_chat_mock.go"
       ProfileManager:
       ProfileManager:
         config:
         config:
           filename: "profile_manager_mock.go"
           filename: "profile_manager_mock.go"

+ 4 - 2
cmd/main.go

@@ -35,11 +35,13 @@ func main() {
 		wg.Done()
 		wg.Done()
 	}()
 	}()
 	go func() {
 	go func() {
-		server.ListenBOS(cfg, sm, fm, cr, logger.With("svc", "BOS"))
+		router := server.NewBOSServiceRouter(logger, cfg, fm, sm, cr, fm)
+		server.ListenBOS(cfg, router, logger.With("svc", "BOS"))
 		wg.Done()
 		wg.Done()
 	}()
 	}()
 	go func() {
 	go func() {
-		server.ListenChat(cfg, fm, cr, logger.With("svc", "CHAT"))
+		router := server.NewChatServiceRouter(logger, cfg, fm, sm)
+		server.ListenChat(cfg, router, cr, logger.With("svc", "CHAT"))
 		wg.Done()
 		wg.Done()
 	}()
 	}()
 	go func() {
 	go func() {

+ 1 - 1
server/buddy.go

@@ -88,7 +88,7 @@ func BroadcastArrival(ctx context.Context, sess *Session, sm SessionManager, fm
 	return nil
 	return nil
 }
 }
 
 
-func BroadcastDeparture(ctx context.Context, sess *Session, sm SessionManager, fm *FeedbagStore) error {
+func BroadcastDeparture(ctx context.Context, sess *Session, sm SessionManager, fm FeedbagManager) error {
 	screenNames, err := fm.InterestedUsers(sess.ScreenName)
 	screenNames, err := fm.InterestedUsers(sess.ScreenName)
 	if err != nil {
 	if err != nil {
 		return err
 		return err

+ 7 - 7
server/chat.go

@@ -9,7 +9,7 @@ import (
 )
 )
 
 
 type ChatHandler interface {
 type ChatHandler interface {
-	ChannelMsgToHostHandler(ctx context.Context, sess *Session, sm SessionManager, snacPayloadIn oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost) (*XMessage, error)
+	ChannelMsgToHostHandler(ctx context.Context, sess *Session, room ChatRoom, snacPayloadIn oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost) (*XMessage, error)
 }
 }
 
 
 func NewChatRouter(logger *slog.Logger) ChatRouter {
 func NewChatRouter(logger *slog.Logger) ChatRouter {
@@ -26,14 +26,14 @@ type ChatRouter struct {
 	RouteLogger
 	RouteLogger
 }
 }
 
 
-func (rt *ChatRouter) RouteChat(ctx context.Context, sess *Session, sm SessionManager, SNACFrame oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
+func (rt *ChatRouter) RouteChat(ctx context.Context, sess *Session, room ChatRoom, SNACFrame oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
 	switch SNACFrame.SubGroup {
 	switch SNACFrame.SubGroup {
 	case oscar.ChatChannelMsgToHost:
 	case oscar.ChatChannelMsgToHost:
 		inSNAC := oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost{}
 		inSNAC := oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost{}
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
 			return err
 			return err
 		}
 		}
-		outSNAC, err := rt.ChannelMsgToHostHandler(ctx, sess, sm, inSNAC)
+		outSNAC, err := rt.ChannelMsgToHostHandler(ctx, sess, room, inSNAC)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
@@ -51,7 +51,7 @@ func (rt *ChatRouter) RouteChat(ctx context.Context, sess *Session, sm SessionMa
 type ChatService struct {
 type ChatService struct {
 }
 }
 
 
-func (s ChatService) ChannelMsgToHostHandler(ctx context.Context, sess *Session, sm SessionManager, snacPayloadIn oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost) (*XMessage, error) {
+func (s ChatService) ChannelMsgToHostHandler(ctx context.Context, sess *Session, room ChatRoom, snacPayloadIn oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost) (*XMessage, error) {
 	snacFrameOut := oscar.SnacFrame{
 	snacFrameOut := oscar.SnacFrame{
 		FoodGroup: oscar.CHAT,
 		FoodGroup: oscar.CHAT,
 		SubGroup:  oscar.ChatChannelMsgToClient,
 		SubGroup:  oscar.ChatChannelMsgToClient,
@@ -74,7 +74,7 @@ func (s ChatService) ChannelMsgToHostHandler(ctx context.Context, sess *Session,
 	)
 	)
 
 
 	// send message to all the participants except sender
 	// send message to all the participants except sender
-	sm.BroadcastExcept(ctx, sess, XMessage{
+	room.BroadcastExcept(ctx, sess, XMessage{
 		snacFrame: snacFrameOut,
 		snacFrame: snacFrameOut,
 		snacOut:   snacPayloadOut,
 		snacOut:   snacPayloadOut,
 	})
 	})
@@ -154,8 +154,8 @@ func AlertUserLeft(ctx context.Context, sess *Session, sm SessionManager) {
 	})
 	})
 }
 }
 
 
-func SendChatRoomInfoUpdate(ctx context.Context, sess *Session, sm SessionManager, room ChatRoom) {
-	sm.SendToScreenName(ctx, sess.ScreenName, XMessage{
+func SendChatRoomInfoUpdate(ctx context.Context, sess *Session, room ChatRoom) {
+	room.SendToScreenName(ctx, sess.ScreenName, XMessage{
 		snacFrame: oscar.SnacFrame{
 		snacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.CHAT,
 			FoodGroup: oscar.CHAT,
 			SubGroup:  oscar.ChatRoomInfoUpdate,
 			SubGroup:  oscar.ChatRoomInfoUpdate,

+ 15 - 15
server/chat_mock.go

@@ -22,25 +22,25 @@ func (_m *MockChatHandler) EXPECT() *MockChatHandler_Expecter {
 	return &MockChatHandler_Expecter{mock: &_m.Mock}
 	return &MockChatHandler_Expecter{mock: &_m.Mock}
 }
 }
 
 
-// ChannelMsgToHostHandler provides a mock function with given fields: ctx, sess, sm, snacPayloadIn
-func (_m *MockChatHandler) ChannelMsgToHostHandler(ctx context.Context, sess *Session, sm SessionManager, snacPayloadIn oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost) (*XMessage, error) {
-	ret := _m.Called(ctx, sess, sm, snacPayloadIn)
+// ChannelMsgToHostHandler provides a mock function with given fields: ctx, sess, room, snacPayloadIn
+func (_m *MockChatHandler) ChannelMsgToHostHandler(ctx context.Context, sess *Session, room ChatRoom, snacPayloadIn oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost) (*XMessage, error) {
+	ret := _m.Called(ctx, sess, room, snacPayloadIn)
 
 
 	var r0 *XMessage
 	var r0 *XMessage
 	var r1 error
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, SessionManager, oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost) (*XMessage, error)); ok {
-		return rf(ctx, sess, sm, snacPayloadIn)
+	if rf, ok := ret.Get(0).(func(context.Context, *Session, ChatRoom, oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost) (*XMessage, error)); ok {
+		return rf(ctx, sess, room, snacPayloadIn)
 	}
 	}
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, SessionManager, oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost) *XMessage); ok {
-		r0 = rf(ctx, sess, sm, snacPayloadIn)
+	if rf, ok := ret.Get(0).(func(context.Context, *Session, ChatRoom, oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost) *XMessage); ok {
+		r0 = rf(ctx, sess, room, snacPayloadIn)
 	} else {
 	} else {
 		if ret.Get(0) != nil {
 		if ret.Get(0) != nil {
 			r0 = ret.Get(0).(*XMessage)
 			r0 = ret.Get(0).(*XMessage)
 		}
 		}
 	}
 	}
 
 
-	if rf, ok := ret.Get(1).(func(context.Context, *Session, SessionManager, oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost) error); ok {
-		r1 = rf(ctx, sess, sm, snacPayloadIn)
+	if rf, ok := ret.Get(1).(func(context.Context, *Session, ChatRoom, oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost) error); ok {
+		r1 = rf(ctx, sess, room, snacPayloadIn)
 	} else {
 	} else {
 		r1 = ret.Error(1)
 		r1 = ret.Error(1)
 	}
 	}
@@ -56,15 +56,15 @@ type MockChatHandler_ChannelMsgToHostHandler_Call struct {
 // ChannelMsgToHostHandler is a helper method to define mock.On call
 // ChannelMsgToHostHandler is a helper method to define mock.On call
 //   - ctx context.Context
 //   - ctx context.Context
 //   - sess *Session
 //   - sess *Session
-//   - sm SessionManager
+//   - room ChatRoom
 //   - snacPayloadIn oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost
 //   - snacPayloadIn oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost
-func (_e *MockChatHandler_Expecter) ChannelMsgToHostHandler(ctx interface{}, sess interface{}, sm interface{}, snacPayloadIn interface{}) *MockChatHandler_ChannelMsgToHostHandler_Call {
-	return &MockChatHandler_ChannelMsgToHostHandler_Call{Call: _e.mock.On("ChannelMsgToHostHandler", ctx, sess, sm, snacPayloadIn)}
+func (_e *MockChatHandler_Expecter) ChannelMsgToHostHandler(ctx interface{}, sess interface{}, room interface{}, snacPayloadIn interface{}) *MockChatHandler_ChannelMsgToHostHandler_Call {
+	return &MockChatHandler_ChannelMsgToHostHandler_Call{Call: _e.mock.On("ChannelMsgToHostHandler", ctx, sess, room, snacPayloadIn)}
 }
 }
 
 
-func (_c *MockChatHandler_ChannelMsgToHostHandler_Call) Run(run func(ctx context.Context, sess *Session, sm SessionManager, snacPayloadIn oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost)) *MockChatHandler_ChannelMsgToHostHandler_Call {
+func (_c *MockChatHandler_ChannelMsgToHostHandler_Call) Run(run func(ctx context.Context, sess *Session, room ChatRoom, snacPayloadIn oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost)) *MockChatHandler_ChannelMsgToHostHandler_Call {
 	_c.Call.Run(func(args mock.Arguments) {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(*Session), args[2].(SessionManager), args[3].(oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost))
+		run(args[0].(context.Context), args[1].(*Session), args[2].(ChatRoom), args[3].(oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost))
 	})
 	})
 	return _c
 	return _c
 }
 }
@@ -74,7 +74,7 @@ func (_c *MockChatHandler_ChannelMsgToHostHandler_Call) Return(_a0 *XMessage, _a
 	return _c
 	return _c
 }
 }
 
 
-func (_c *MockChatHandler_ChannelMsgToHostHandler_Call) RunAndReturn(run func(context.Context, *Session, SessionManager, oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost) (*XMessage, error)) *MockChatHandler_ChannelMsgToHostHandler_Call {
+func (_c *MockChatHandler_ChannelMsgToHostHandler_Call) RunAndReturn(run func(context.Context, *Session, ChatRoom, oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost) (*XMessage, error)) *MockChatHandler_ChannelMsgToHostHandler_Call {
 	_c.Call.Return(run)
 	_c.Call.Return(run)
 	return _c
 	return _c
 }
 }

+ 12 - 10
server/chat_nav.go

@@ -11,15 +11,16 @@ import (
 )
 )
 
 
 type ChatNavHandler interface {
 type ChatNavHandler interface {
-	CreateRoomHandler(ctx context.Context, sess *Session, cr *ChatRegistry, newChatRoom ChatRoomFactory, snacPayloadIn oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) (XMessage, error)
+	CreateRoomHandler(ctx context.Context, sess *Session, newChatRoom ChatRoomFactory, snacPayloadIn oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) (XMessage, error)
 	RequestChatRightsHandler(ctx context.Context) XMessage
 	RequestChatRightsHandler(ctx context.Context) XMessage
-	RequestRoomInfoHandler(ctx context.Context, cr *ChatRegistry, snacPayloadIn oscar.SNAC_0x0D_0x04_ChatNavRequestRoomInfo) (XMessage, error)
+	RequestRoomInfoHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x0D_0x04_ChatNavRequestRoomInfo) (XMessage, error)
 }
 }
 
 
-func NewChatNavRouter(logger *slog.Logger) ChatNavRouter {
+func NewChatNavRouter(logger *slog.Logger, cr *ChatRegistry) ChatNavRouter {
 	return ChatNavRouter{
 	return ChatNavRouter{
 		ChatNavHandler: ChatNavService{
 		ChatNavHandler: ChatNavService{
 			Logger: logger,
 			Logger: logger,
+			cr:     cr,
 		},
 		},
 		RouteLogger: RouteLogger{
 		RouteLogger: RouteLogger{
 			Logger: logger,
 			Logger: logger,
@@ -32,7 +33,7 @@ type ChatNavRouter struct {
 	RouteLogger
 	RouteLogger
 }
 }
 
 
-func (rt *ChatNavRouter) RouteChatNav(ctx context.Context, sess *Session, cr *ChatRegistry, SNACFrame oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
+func (rt *ChatNavRouter) RouteChatNav(ctx context.Context, sess *Session, SNACFrame oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
 	switch SNACFrame.SubGroup {
 	switch SNACFrame.SubGroup {
 	case oscar.ChatNavRequestChatRights:
 	case oscar.ChatNavRequestChatRights:
 		outSNAC := rt.RequestChatRightsHandler(ctx)
 		outSNAC := rt.RequestChatRightsHandler(ctx)
@@ -43,7 +44,7 @@ func (rt *ChatNavRouter) RouteChatNav(ctx context.Context, sess *Session, cr *Ch
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
 			return err
 			return err
 		}
 		}
-		outSNAC, err := rt.RequestRoomInfoHandler(ctx, cr, inSNAC)
+		outSNAC, err := rt.RequestRoomInfoHandler(ctx, inSNAC)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
@@ -54,7 +55,7 @@ func (rt *ChatNavRouter) RouteChatNav(ctx context.Context, sess *Session, cr *Ch
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
 			return err
 			return err
 		}
 		}
-		outSNAC, err := rt.CreateRoomHandler(ctx, sess, cr, NewChatRoom, inSNAC)
+		outSNAC, err := rt.CreateRoomHandler(ctx, sess, NewChatRoom, inSNAC)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
@@ -69,6 +70,7 @@ func (rt *ChatNavRouter) RouteChatNav(ctx context.Context, sess *Session, cr *Ch
 
 
 type ChatNavService struct {
 type ChatNavService struct {
 	Logger *slog.Logger
 	Logger *slog.Logger
+	cr     *ChatRegistry
 }
 }
 
 
 type ChatCookie struct {
 type ChatCookie struct {
@@ -117,7 +119,7 @@ func NewChatRoom(logger *slog.Logger) ChatRoom {
 
 
 type ChatRoomFactory func(logger *slog.Logger) ChatRoom
 type ChatRoomFactory func(logger *slog.Logger) ChatRoom
 
 
-func (s ChatNavService) CreateRoomHandler(_ context.Context, sess *Session, cr *ChatRegistry, newChatRoom ChatRoomFactory, snacPayloadIn oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) (XMessage, error) {
+func (s ChatNavService) CreateRoomHandler(_ context.Context, sess *Session, newChatRoom ChatRoomFactory, snacPayloadIn oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) (XMessage, error) {
 	name, hasName := snacPayloadIn.GetString(oscar.ChatTLVRoomName)
 	name, hasName := snacPayloadIn.GetString(oscar.ChatTLVRoomName)
 	if !hasName {
 	if !hasName {
 		return XMessage{}, errors.New("unable to find chat name")
 		return XMessage{}, errors.New("unable to find chat name")
@@ -128,7 +130,7 @@ func (s ChatNavService) CreateRoomHandler(_ context.Context, sess *Session, cr *
 	room.Exchange = snacPayloadIn.Exchange
 	room.Exchange = snacPayloadIn.Exchange
 	room.InstanceNumber = snacPayloadIn.InstanceNumber
 	room.InstanceNumber = snacPayloadIn.InstanceNumber
 	room.Name = name
 	room.Name = name
-	cr.Register(room)
+	s.cr.Register(room)
 
 
 	// add user to chat room
 	// add user to chat room
 	room.NewSessionWithSN(sess.ID, sess.ScreenName)
 	room.NewSessionWithSN(sess.ID, sess.ScreenName)
@@ -156,8 +158,8 @@ func (s ChatNavService) CreateRoomHandler(_ context.Context, sess *Session, cr *
 	}, nil
 	}, nil
 }
 }
 
 
-func (s ChatNavService) RequestRoomInfoHandler(_ context.Context, cr *ChatRegistry, snacPayloadIn oscar.SNAC_0x0D_0x04_ChatNavRequestRoomInfo) (XMessage, error) {
-	room, err := cr.Retrieve(string(snacPayloadIn.Cookie))
+func (s ChatNavService) RequestRoomInfoHandler(_ context.Context, snacPayloadIn oscar.SNAC_0x0D_0x04_ChatNavRequestRoomInfo) (XMessage, error) {
+	room, err := s.cr.Retrieve(string(snacPayloadIn.Cookie))
 	if err != nil {
 	if err != nil {
 		return XMessage{}, err
 		return XMessage{}, err
 	}
 	}

+ 28 - 30
server/chat_nav_mock.go

@@ -22,23 +22,23 @@ func (_m *MockChatNavHandler) EXPECT() *MockChatNavHandler_Expecter {
 	return &MockChatNavHandler_Expecter{mock: &_m.Mock}
 	return &MockChatNavHandler_Expecter{mock: &_m.Mock}
 }
 }
 
 
-// CreateRoomHandler provides a mock function with given fields: ctx, sess, cr, newChatRoom, snacPayloadIn
-func (_m *MockChatNavHandler) CreateRoomHandler(ctx context.Context, sess *Session, cr *ChatRegistry, newChatRoom ChatRoomFactory, snacPayloadIn oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) (XMessage, error) {
-	ret := _m.Called(ctx, sess, cr, newChatRoom, snacPayloadIn)
+// CreateRoomHandler provides a mock function with given fields: ctx, sess, newChatRoom, snacPayloadIn
+func (_m *MockChatNavHandler) CreateRoomHandler(ctx context.Context, sess *Session, newChatRoom ChatRoomFactory, snacPayloadIn oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) (XMessage, error) {
+	ret := _m.Called(ctx, sess, newChatRoom, snacPayloadIn)
 
 
 	var r0 XMessage
 	var r0 XMessage
 	var r1 error
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, *ChatRegistry, ChatRoomFactory, oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) (XMessage, error)); ok {
-		return rf(ctx, sess, cr, newChatRoom, snacPayloadIn)
+	if rf, ok := ret.Get(0).(func(context.Context, *Session, ChatRoomFactory, oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) (XMessage, error)); ok {
+		return rf(ctx, sess, newChatRoom, snacPayloadIn)
 	}
 	}
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, *ChatRegistry, ChatRoomFactory, oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) XMessage); ok {
-		r0 = rf(ctx, sess, cr, newChatRoom, snacPayloadIn)
+	if rf, ok := ret.Get(0).(func(context.Context, *Session, ChatRoomFactory, oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) XMessage); ok {
+		r0 = rf(ctx, sess, newChatRoom, snacPayloadIn)
 	} else {
 	} else {
 		r0 = ret.Get(0).(XMessage)
 		r0 = ret.Get(0).(XMessage)
 	}
 	}
 
 
-	if rf, ok := ret.Get(1).(func(context.Context, *Session, *ChatRegistry, ChatRoomFactory, oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) error); ok {
-		r1 = rf(ctx, sess, cr, newChatRoom, snacPayloadIn)
+	if rf, ok := ret.Get(1).(func(context.Context, *Session, ChatRoomFactory, oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) error); ok {
+		r1 = rf(ctx, sess, newChatRoom, snacPayloadIn)
 	} else {
 	} else {
 		r1 = ret.Error(1)
 		r1 = ret.Error(1)
 	}
 	}
@@ -54,16 +54,15 @@ type MockChatNavHandler_CreateRoomHandler_Call struct {
 // CreateRoomHandler is a helper method to define mock.On call
 // CreateRoomHandler is a helper method to define mock.On call
 //   - ctx context.Context
 //   - ctx context.Context
 //   - sess *Session
 //   - sess *Session
-//   - cr *ChatRegistry
 //   - newChatRoom ChatRoomFactory
 //   - newChatRoom ChatRoomFactory
 //   - snacPayloadIn oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate
 //   - snacPayloadIn oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate
-func (_e *MockChatNavHandler_Expecter) CreateRoomHandler(ctx interface{}, sess interface{}, cr interface{}, newChatRoom interface{}, snacPayloadIn interface{}) *MockChatNavHandler_CreateRoomHandler_Call {
-	return &MockChatNavHandler_CreateRoomHandler_Call{Call: _e.mock.On("CreateRoomHandler", ctx, sess, cr, newChatRoom, snacPayloadIn)}
+func (_e *MockChatNavHandler_Expecter) CreateRoomHandler(ctx interface{}, sess interface{}, newChatRoom interface{}, snacPayloadIn interface{}) *MockChatNavHandler_CreateRoomHandler_Call {
+	return &MockChatNavHandler_CreateRoomHandler_Call{Call: _e.mock.On("CreateRoomHandler", ctx, sess, newChatRoom, snacPayloadIn)}
 }
 }
 
 
-func (_c *MockChatNavHandler_CreateRoomHandler_Call) Run(run func(ctx context.Context, sess *Session, cr *ChatRegistry, newChatRoom ChatRoomFactory, snacPayloadIn oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate)) *MockChatNavHandler_CreateRoomHandler_Call {
+func (_c *MockChatNavHandler_CreateRoomHandler_Call) Run(run func(ctx context.Context, sess *Session, newChatRoom ChatRoomFactory, snacPayloadIn oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate)) *MockChatNavHandler_CreateRoomHandler_Call {
 	_c.Call.Run(func(args mock.Arguments) {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(*Session), args[2].(*ChatRegistry), args[3].(ChatRoomFactory), args[4].(oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate))
+		run(args[0].(context.Context), args[1].(*Session), args[2].(ChatRoomFactory), args[3].(oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate))
 	})
 	})
 	return _c
 	return _c
 }
 }
@@ -73,7 +72,7 @@ func (_c *MockChatNavHandler_CreateRoomHandler_Call) Return(_a0 XMessage, _a1 er
 	return _c
 	return _c
 }
 }
 
 
-func (_c *MockChatNavHandler_CreateRoomHandler_Call) RunAndReturn(run func(context.Context, *Session, *ChatRegistry, ChatRoomFactory, oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) (XMessage, error)) *MockChatNavHandler_CreateRoomHandler_Call {
+func (_c *MockChatNavHandler_CreateRoomHandler_Call) RunAndReturn(run func(context.Context, *Session, ChatRoomFactory, oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) (XMessage, error)) *MockChatNavHandler_CreateRoomHandler_Call {
 	_c.Call.Return(run)
 	_c.Call.Return(run)
 	return _c
 	return _c
 }
 }
@@ -120,23 +119,23 @@ func (_c *MockChatNavHandler_RequestChatRightsHandler_Call) RunAndReturn(run fun
 	return _c
 	return _c
 }
 }
 
 
-// RequestRoomInfoHandler provides a mock function with given fields: ctx, cr, snacPayloadIn
-func (_m *MockChatNavHandler) RequestRoomInfoHandler(ctx context.Context, cr *ChatRegistry, snacPayloadIn oscar.SNAC_0x0D_0x04_ChatNavRequestRoomInfo) (XMessage, error) {
-	ret := _m.Called(ctx, cr, snacPayloadIn)
+// RequestRoomInfoHandler provides a mock function with given fields: ctx, snacPayloadIn
+func (_m *MockChatNavHandler) RequestRoomInfoHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x0D_0x04_ChatNavRequestRoomInfo) (XMessage, error) {
+	ret := _m.Called(ctx, snacPayloadIn)
 
 
 	var r0 XMessage
 	var r0 XMessage
 	var r1 error
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, *ChatRegistry, oscar.SNAC_0x0D_0x04_ChatNavRequestRoomInfo) (XMessage, error)); ok {
-		return rf(ctx, cr, snacPayloadIn)
+	if rf, ok := ret.Get(0).(func(context.Context, oscar.SNAC_0x0D_0x04_ChatNavRequestRoomInfo) (XMessage, error)); ok {
+		return rf(ctx, snacPayloadIn)
 	}
 	}
-	if rf, ok := ret.Get(0).(func(context.Context, *ChatRegistry, oscar.SNAC_0x0D_0x04_ChatNavRequestRoomInfo) XMessage); ok {
-		r0 = rf(ctx, cr, snacPayloadIn)
+	if rf, ok := ret.Get(0).(func(context.Context, oscar.SNAC_0x0D_0x04_ChatNavRequestRoomInfo) XMessage); ok {
+		r0 = rf(ctx, snacPayloadIn)
 	} else {
 	} else {
 		r0 = ret.Get(0).(XMessage)
 		r0 = ret.Get(0).(XMessage)
 	}
 	}
 
 
-	if rf, ok := ret.Get(1).(func(context.Context, *ChatRegistry, oscar.SNAC_0x0D_0x04_ChatNavRequestRoomInfo) error); ok {
-		r1 = rf(ctx, cr, snacPayloadIn)
+	if rf, ok := ret.Get(1).(func(context.Context, oscar.SNAC_0x0D_0x04_ChatNavRequestRoomInfo) error); ok {
+		r1 = rf(ctx, snacPayloadIn)
 	} else {
 	} else {
 		r1 = ret.Error(1)
 		r1 = ret.Error(1)
 	}
 	}
@@ -151,15 +150,14 @@ type MockChatNavHandler_RequestRoomInfoHandler_Call struct {
 
 
 // RequestRoomInfoHandler is a helper method to define mock.On call
 // RequestRoomInfoHandler is a helper method to define mock.On call
 //   - ctx context.Context
 //   - ctx context.Context
-//   - cr *ChatRegistry
 //   - snacPayloadIn oscar.SNAC_0x0D_0x04_ChatNavRequestRoomInfo
 //   - snacPayloadIn oscar.SNAC_0x0D_0x04_ChatNavRequestRoomInfo
-func (_e *MockChatNavHandler_Expecter) RequestRoomInfoHandler(ctx interface{}, cr interface{}, snacPayloadIn interface{}) *MockChatNavHandler_RequestRoomInfoHandler_Call {
-	return &MockChatNavHandler_RequestRoomInfoHandler_Call{Call: _e.mock.On("RequestRoomInfoHandler", ctx, cr, snacPayloadIn)}
+func (_e *MockChatNavHandler_Expecter) RequestRoomInfoHandler(ctx interface{}, snacPayloadIn interface{}) *MockChatNavHandler_RequestRoomInfoHandler_Call {
+	return &MockChatNavHandler_RequestRoomInfoHandler_Call{Call: _e.mock.On("RequestRoomInfoHandler", ctx, snacPayloadIn)}
 }
 }
 
 
-func (_c *MockChatNavHandler_RequestRoomInfoHandler_Call) Run(run func(ctx context.Context, cr *ChatRegistry, snacPayloadIn oscar.SNAC_0x0D_0x04_ChatNavRequestRoomInfo)) *MockChatNavHandler_RequestRoomInfoHandler_Call {
+func (_c *MockChatNavHandler_RequestRoomInfoHandler_Call) Run(run func(ctx context.Context, snacPayloadIn oscar.SNAC_0x0D_0x04_ChatNavRequestRoomInfo)) *MockChatNavHandler_RequestRoomInfoHandler_Call {
 	_c.Call.Run(func(args mock.Arguments) {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(*ChatRegistry), args[2].(oscar.SNAC_0x0D_0x04_ChatNavRequestRoomInfo))
+		run(args[0].(context.Context), args[1].(oscar.SNAC_0x0D_0x04_ChatNavRequestRoomInfo))
 	})
 	})
 	return _c
 	return _c
 }
 }
@@ -169,7 +167,7 @@ func (_c *MockChatNavHandler_RequestRoomInfoHandler_Call) Return(_a0 XMessage, _
 	return _c
 	return _c
 }
 }
 
 
-func (_c *MockChatNavHandler_RequestRoomInfoHandler_Call) RunAndReturn(run func(context.Context, *ChatRegistry, oscar.SNAC_0x0D_0x04_ChatNavRequestRoomInfo) (XMessage, error)) *MockChatNavHandler_RequestRoomInfoHandler_Call {
+func (_c *MockChatNavHandler_RequestRoomInfoHandler_Call) RunAndReturn(run func(context.Context, oscar.SNAC_0x0D_0x04_ChatNavRequestRoomInfo) (XMessage, error)) *MockChatNavHandler_RequestRoomInfoHandler_Call {
 	_c.Call.Return(run)
 	_c.Call.Return(run)
 	return _c
 	return _c
 }
 }

+ 7 - 5
server/chat_nav_test.go

@@ -49,8 +49,10 @@ func TestSendAndReceiveCreateRoom(t *testing.T) {
 			},
 			},
 		},
 		},
 	}
 	}
-	svc := ChatNavService{}
-	outputSNAC, err := svc.CreateRoomHandler(context.Background(), userSess, cr, crf, inputSNAC)
+	svc := ChatNavService{
+		cr: cr,
+	}
+	outputSNAC, err := svc.CreateRoomHandler(context.Background(), userSess, crf, inputSNAC)
 	assert.NoError(t, err)
 	assert.NoError(t, err)
 
 
 	//
 	//
@@ -208,11 +210,11 @@ func TestChatNavRouter_RouteChatNavRouter(t *testing.T) {
 				Return(tc.output).
 				Return(tc.output).
 				Maybe()
 				Maybe()
 			svc.EXPECT().
 			svc.EXPECT().
-				RequestRoomInfoHandler(mock.Anything, mock.Anything, tc.input.snacOut).
+				RequestRoomInfoHandler(mock.Anything, tc.input.snacOut).
 				Return(tc.output, tc.handlerErr).
 				Return(tc.output, tc.handlerErr).
 				Maybe()
 				Maybe()
 			svc.EXPECT().
 			svc.EXPECT().
-				CreateRoomHandler(mock.Anything, mock.Anything, mock.Anything, mock.Anything, tc.input.snacOut).
+				CreateRoomHandler(mock.Anything, mock.Anything, mock.Anything, tc.input.snacOut).
 				Return(tc.output, tc.handlerErr).
 				Return(tc.output, tc.handlerErr).
 				Maybe()
 				Maybe()
 
 
@@ -229,7 +231,7 @@ func TestChatNavRouter_RouteChatNavRouter(t *testing.T) {
 			bufOut := &bytes.Buffer{}
 			bufOut := &bytes.Buffer{}
 			seq := uint32(0)
 			seq := uint32(0)
 
 
-			err := router.RouteChatNav(nil, nil, nil, tc.input.snacFrame, bufIn, bufOut, &seq)
+			err := router.RouteChatNav(nil, nil, tc.input.snacFrame, bufIn, bufOut, &seq)
 			assert.ErrorIs(t, err, tc.expectErr)
 			assert.ErrorIs(t, err, tc.expectErr)
 			if tc.expectErr != nil {
 			if tc.expectErr != nil {
 				return
 				return

+ 3 - 2
server/chat_test.go

@@ -134,7 +134,8 @@ func TestSendAndReceiveChatChannelMsgToHost(t *testing.T) {
 			// send input SNAC
 			// send input SNAC
 			//
 			//
 			svc := ChatService{}
 			svc := ChatService{}
-			outputSNAC, err := svc.ChannelMsgToHostHandler(context.Background(), tc.userSession, crm, tc.inputSNAC)
+			room := ChatRoom{SessionManager: crm}
+			outputSNAC, err := svc.ChannelMsgToHostHandler(context.Background(), tc.userSession, room, tc.inputSNAC)
 			assert.NoError(t, err)
 			assert.NoError(t, err)
 
 
 			if tc.expectOutput.snacFrame == (oscar.SnacFrame{}) {
 			if tc.expectOutput.snacFrame == (oscar.SnacFrame{}) {
@@ -228,7 +229,7 @@ func TestChatRouter_RouteChat(t *testing.T) {
 			bufOut := &bytes.Buffer{}
 			bufOut := &bytes.Buffer{}
 			seq := uint32(0)
 			seq := uint32(0)
 
 
-			err := router.RouteChat(nil, nil, nil, tc.input.snacFrame, bufIn, bufOut, &seq)
+			err := router.RouteChat(nil, nil, ChatRoom{}, tc.input.snacFrame, bufIn, bufOut, &seq)
 			assert.ErrorIs(t, err, tc.expectErr)
 			assert.ErrorIs(t, err, tc.expectErr)
 			if tc.expectErr != nil {
 			if tc.expectErr != nil {
 				return
 				return

+ 27 - 17
server/connection.go

@@ -91,11 +91,9 @@ func Signout(ctx context.Context, logger *slog.Logger, sess *Session, sm Session
 	sm.Remove(sess)
 	sm.Remove(sess)
 }
 }
 
 
-func ReadBos(ctx context.Context, cfg Config, sess *Session, seq uint32, sm SessionManager, fm *FeedbagStore, cr *ChatRegistry, rwc io.ReadWriter, room ChatRoom, router Router, logger *slog.Logger) {
-	if err := router.WriteOServiceHostOnline(rwc, &seq); err != nil {
-		logger.ErrorContext(ctx, "error WriteOServiceHostOnline")
-	}
+type RouteSig func(ctx context.Context, rwc io.ReadWriter, u *uint32, snac oscar.SnacFrame, buf io.Reader) error
 
 
+func ReadBos(ctx context.Context, cfg Config, sess *Session, seq uint32, rwc io.ReadWriter, logger *slog.Logger, fn RouteSig) {
 	// buffered so that the go routine has room to exit
 	// buffered so that the go routine has room to exit
 	msgCh := make(chan IncomingMessage, 1)
 	msgCh := make(chan IncomingMessage, 1)
 	errCh := make(chan error, 1)
 	errCh := make(chan error, 1)
@@ -108,7 +106,7 @@ func ReadBos(ctx context.Context, cfg Config, sess *Session, seq uint32, sm Sess
 	for {
 	for {
 		select {
 		select {
 		case m := <-msgCh:
 		case m := <-msgCh:
-			if err := router.routeIncomingRequests(ctx, cfg, sm, sess, fm, cr, rwc, &seq, m.snac, m.buf, room); err != nil {
+			if err := fn(ctx, rwc, &seq, m.snac, m.buf); err != nil {
 				if errors.Is(err, ErrUnsupportedSubGroup) || errors.Is(err, ErrUnsupportedFoodGroup) {
 				if errors.Is(err, ErrUnsupportedSubGroup) || errors.Is(err, ErrUnsupportedFoodGroup) {
 					if err1 := sendInvalidSNACErr(m.snac, rwc, &seq); err1 != nil {
 					if err1 := sendInvalidSNACErr(m.snac, rwc, &seq); err1 != nil {
 						err = errors.Join(err1, err)
 						err = errors.Join(err1, err)
@@ -163,7 +161,7 @@ func gracefulDisconnect(seq uint32, rwc io.ReadWriter) error {
 	}, rwc)
 	}, rwc)
 }
 }
 
 
-func HandleChatConnection(ctx context.Context, cfg Config, fm *FeedbagStore, cr *ChatRegistry, conn net.Conn, router Router, logger *slog.Logger) {
+func HandleChatConnection(ctx context.Context, cfg Config, cr *ChatRegistry, conn net.Conn, router ChatServiceRouter, logger *slog.Logger) {
 	cookie, seq, err := VerifyChatLogin(conn)
 	cookie, seq, err := VerifyChatLogin(conn)
 	if err != nil {
 	if err != nil {
 		logger.ErrorContext(ctx, "user disconnected with error", "err", err.Error())
 		logger.ErrorContext(ctx, "user disconnected with error", "err", err.Error())
@@ -193,7 +191,14 @@ func HandleChatConnection(ctx context.Context, cfg Config, fm *FeedbagStore, cr
 
 
 	ctx = context.WithValue(ctx, "screenName", chatSess.ScreenName)
 	ctx = context.WithValue(ctx, "screenName", chatSess.ScreenName)
 
 
-	ReadBos(ctx, cfg, chatSess, seq, room.SessionManager, fm, cr, conn, room, router, logger)
+	if err := router.WriteOServiceHostOnline(conn, &seq); err != nil {
+		logger.ErrorContext(ctx, "error WriteOServiceHostOnline")
+	}
+
+	fn := func(ctx context.Context, rwc io.ReadWriter, u *uint32, snac oscar.SnacFrame, buf io.Reader) error {
+		return router.Route(ctx, chatSess, rwc, u, snac, buf, room)
+	}
+	ReadBos(ctx, cfg, chatSess, seq, conn, logger, fn)
 }
 }
 
 
 func HandleAuthConnection(cfg Config, sm *InMemorySessionManager, fm *FeedbagStore, conn net.Conn) {
 func HandleAuthConnection(cfg Config, sm *InMemorySessionManager, fm *FeedbagStore, conn net.Conn) {
@@ -218,8 +223,8 @@ func HandleAuthConnection(cfg Config, sm *InMemorySessionManager, fm *FeedbagSto
 	}
 	}
 }
 }
 
 
-func HandleBOSConnection(ctx context.Context, cfg Config, sm *InMemorySessionManager, fm *FeedbagStore, cr *ChatRegistry, conn net.Conn, router Router, logger *slog.Logger) {
-	sess, seq, err := VerifyLogin(sm, conn)
+func HandleBOSConnection(ctx context.Context, cfg Config, conn net.Conn, router BOSServiceRouter, logger *slog.Logger) {
+	sess, seq, err := router.VerifyLogin(conn)
 	if err != nil {
 	if err != nil {
 		logger.ErrorContext(ctx, "user disconnected with error", "err", err.Error())
 		logger.ErrorContext(ctx, "user disconnected with error", "err", err.Error())
 		return
 		return
@@ -230,15 +235,22 @@ func HandleBOSConnection(ctx context.Context, cfg Config, sm *InMemorySessionMan
 
 
 	go func() {
 	go func() {
 		<-sess.Closed()
 		<-sess.Closed()
-		Signout(ctx, logger, sess, sm, fm)
+		router.Signout(ctx, logger, sess)
 	}()
 	}()
 
 
 	ctx = context.WithValue(ctx, "screenName", sess.ScreenName)
 	ctx = context.WithValue(ctx, "screenName", sess.ScreenName)
 
 
-	ReadBos(ctx, cfg, sess, seq, sm, fm, cr, conn, ChatRoom{}, router, logger)
+	if err := router.WriteOServiceHostOnline(conn, &seq); err != nil {
+		logger.ErrorContext(ctx, "error WriteOServiceHostOnline")
+	}
+
+	fn := func(ctx context.Context, rwc io.ReadWriter, u *uint32, snac oscar.SnacFrame, buf io.Reader) error {
+		return router.Route(ctx, sess, rwc, u, snac, buf)
+	}
+	ReadBos(ctx, cfg, sess, seq, conn, logger, fn)
 }
 }
 
 
-func ListenChat(cfg Config, fm *FeedbagStore, cr *ChatRegistry, logger *slog.Logger) {
+func ListenChat(cfg Config, router ChatServiceRouter, cr *ChatRegistry, logger *slog.Logger) {
 	addr := Address("", cfg.ChatPort)
 	addr := Address("", cfg.ChatPort)
 	listener, err := net.Listen("tcp", addr)
 	listener, err := net.Listen("tcp", addr)
 	if err != nil {
 	if err != nil {
@@ -249,7 +261,6 @@ func ListenChat(cfg Config, fm *FeedbagStore, cr *ChatRegistry, logger *slog.Log
 
 
 	logger.Info("starting service", "addr", addr)
 	logger.Info("starting service", "addr", addr)
 
 
-	router := NewRouterForChat(logger)
 	for {
 	for {
 		conn, err := listener.Accept()
 		conn, err := listener.Accept()
 		if err != nil {
 		if err != nil {
@@ -259,11 +270,11 @@ func ListenChat(cfg Config, fm *FeedbagStore, cr *ChatRegistry, logger *slog.Log
 		ctx := context.Background()
 		ctx := context.Background()
 		ctx = context.WithValue(ctx, "ip", conn.RemoteAddr().String())
 		ctx = context.WithValue(ctx, "ip", conn.RemoteAddr().String())
 		logger.DebugContext(ctx, "accepted connection")
 		logger.DebugContext(ctx, "accepted connection")
-		go HandleChatConnection(ctx, cfg, fm, cr, conn, router, logger)
+		go HandleChatConnection(ctx, cfg, cr, conn, router, logger)
 	}
 	}
 }
 }
 
 
-func ListenBOS(cfg Config, sm *InMemorySessionManager, fm *FeedbagStore, cr *ChatRegistry, logger *slog.Logger) {
+func ListenBOS(cfg Config, router BOSServiceRouter, logger *slog.Logger) {
 	addr := Address("", cfg.BOSPort)
 	addr := Address("", cfg.BOSPort)
 	listener, err := net.Listen("tcp", addr)
 	listener, err := net.Listen("tcp", addr)
 	if err != nil {
 	if err != nil {
@@ -274,7 +285,6 @@ func ListenBOS(cfg Config, sm *InMemorySessionManager, fm *FeedbagStore, cr *Cha
 
 
 	logger.Info("starting service", "addr", addr)
 	logger.Info("starting service", "addr", addr)
 
 
-	router := NewRouter(logger)
 	for {
 	for {
 		conn, err := listener.Accept()
 		conn, err := listener.Accept()
 		if err != nil {
 		if err != nil {
@@ -284,7 +294,7 @@ func ListenBOS(cfg Config, sm *InMemorySessionManager, fm *FeedbagStore, cr *Cha
 		ctx := context.Background()
 		ctx := context.Background()
 		ctx = context.WithValue(ctx, "ip", conn.RemoteAddr().String())
 		ctx = context.WithValue(ctx, "ip", conn.RemoteAddr().String())
 		logger.DebugContext(ctx, "accepted connection")
 		logger.DebugContext(ctx, "accepted connection")
-		go HandleBOSConnection(ctx, cfg, sm, fm, cr, conn, router, logger)
+		go HandleBOSConnection(ctx, cfg, conn, router, logger)
 	}
 	}
 }
 }
 
 

+ 36 - 31
server/feedbag.go

@@ -11,18 +11,21 @@ import (
 )
 )
 
 
 type FeedbagHandler interface {
 type FeedbagHandler interface {
-	DeleteItemHandler(ctx context.Context, sm SessionManager, sess *Session, fm FeedbagManager, snacPayloadIn oscar.SNAC_0x13_0x0A_FeedbagDeleteItem) (XMessage, error)
-	InsertItemHandler(ctx context.Context, sm SessionManager, sess *Session, fm FeedbagManager, snacPayloadIn oscar.SNAC_0x13_0x08_FeedbagInsertItem) (XMessage, error)
-	QueryHandler(ctx context.Context, sess *Session, fm FeedbagManager) (XMessage, error)
-	QueryIfModifiedHandler(ctx context.Context, sess *Session, fm FeedbagManager, snacPayloadIn oscar.SNAC_0x13_0x05_FeedbagQueryIfModified) (XMessage, error)
+	DeleteItemHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x13_0x0A_FeedbagDeleteItem) (XMessage, error)
+	InsertItemHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x13_0x08_FeedbagInsertItem) (XMessage, error)
+	QueryHandler(ctx context.Context, sess *Session) (XMessage, error)
+	QueryIfModifiedHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x13_0x05_FeedbagQueryIfModified) (XMessage, error)
 	RightsQueryHandler(context.Context) XMessage
 	RightsQueryHandler(context.Context) XMessage
 	StartClusterHandler(context.Context, oscar.SNAC_0x13_0x11_FeedbagStartCluster)
 	StartClusterHandler(context.Context, oscar.SNAC_0x13_0x11_FeedbagStartCluster)
-	UpdateItemHandler(ctx context.Context, sm SessionManager, sess *Session, fm FeedbagManager, snacPayloadIn oscar.SNAC_0x13_0x09_FeedbagUpdateItem) (XMessage, error)
+	UpdateItemHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x13_0x09_FeedbagUpdateItem) (XMessage, error)
 }
 }
 
 
-func NewFeedbagRouter(logger *slog.Logger) FeedbagRouter {
+func NewFeedbagRouter(logger *slog.Logger, sm SessionManager, fm FeedbagManager) FeedbagRouter {
 	return FeedbagRouter{
 	return FeedbagRouter{
-		FeedbagHandler: FeedbagService{},
+		FeedbagHandler: FeedbagService{
+			sm: sm,
+			fm: fm,
+		},
 		RouteLogger: RouteLogger{
 		RouteLogger: RouteLogger{
 			Logger: logger,
 			Logger: logger,
 		},
 		},
@@ -34,7 +37,7 @@ type FeedbagRouter struct {
 	RouteLogger
 	RouteLogger
 }
 }
 
 
-func (rt FeedbagRouter) RouteFeedbag(ctx context.Context, sm SessionManager, sess *Session, fm FeedbagManager, SNACFrame oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
+func (rt FeedbagRouter) RouteFeedbag(ctx context.Context, sess *Session, SNACFrame oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
 	switch SNACFrame.SubGroup {
 	switch SNACFrame.SubGroup {
 	case oscar.FeedbagRightsQuery:
 	case oscar.FeedbagRightsQuery:
 		inSNAC := oscar.SNAC_0x13_0x02_FeedbagRightsQuery{}
 		inSNAC := oscar.SNAC_0x13_0x02_FeedbagRightsQuery{}
@@ -45,7 +48,7 @@ func (rt FeedbagRouter) RouteFeedbag(ctx context.Context, sm SessionManager, ses
 		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.snacFrame, outSNAC.snacOut)
 		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.snacFrame, outSNAC.snacOut)
 		return writeOutSNAC(SNACFrame, outSNAC.snacFrame, outSNAC.snacOut, sequence, w)
 		return writeOutSNAC(SNACFrame, outSNAC.snacFrame, outSNAC.snacOut, sequence, w)
 	case oscar.FeedbagQuery:
 	case oscar.FeedbagQuery:
-		inSNAC, err := rt.QueryHandler(ctx, sess, fm)
+		inSNAC, err := rt.QueryHandler(ctx, sess)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
@@ -56,7 +59,7 @@ func (rt FeedbagRouter) RouteFeedbag(ctx context.Context, sm SessionManager, ses
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
 			return err
 			return err
 		}
 		}
-		outSNAC, err := rt.QueryIfModifiedHandler(ctx, sess, fm, inSNAC)
+		outSNAC, err := rt.QueryIfModifiedHandler(ctx, sess, inSNAC)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
@@ -70,7 +73,7 @@ func (rt FeedbagRouter) RouteFeedbag(ctx context.Context, sm SessionManager, ses
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
 			return err
 			return err
 		}
 		}
-		outSNAC, err := rt.InsertItemHandler(ctx, sm, sess, fm, inSNAC)
+		outSNAC, err := rt.InsertItemHandler(ctx, sess, inSNAC)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
@@ -81,7 +84,7 @@ func (rt FeedbagRouter) RouteFeedbag(ctx context.Context, sm SessionManager, ses
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
 			return err
 			return err
 		}
 		}
-		outSNAC, err := rt.UpdateItemHandler(ctx, sm, sess, fm, inSNAC)
+		outSNAC, err := rt.UpdateItemHandler(ctx, sess, inSNAC)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
@@ -92,7 +95,7 @@ func (rt FeedbagRouter) RouteFeedbag(ctx context.Context, sm SessionManager, ses
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
 			return err
 			return err
 		}
 		}
-		outSNAC, err := rt.DeleteItemHandler(ctx, sm, sess, fm, inSNAC)
+		outSNAC, err := rt.DeleteItemHandler(ctx, sess, inSNAC)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
@@ -115,6 +118,8 @@ func (rt FeedbagRouter) RouteFeedbag(ctx context.Context, sm SessionManager, ses
 }
 }
 
 
 type FeedbagService struct {
 type FeedbagService struct {
+	sm SessionManager
+	fm FeedbagManager
 }
 }
 
 
 func (s FeedbagService) RightsQueryHandler(context.Context) XMessage {
 func (s FeedbagService) RightsQueryHandler(context.Context) XMessage {
@@ -165,8 +170,8 @@ func (s FeedbagService) RightsQueryHandler(context.Context) XMessage {
 	}
 	}
 }
 }
 
 
-func (s FeedbagService) QueryHandler(ctx context.Context, sess *Session, fm FeedbagManager) (XMessage, error) {
-	fb, err := fm.Retrieve(sess.ScreenName)
+func (s FeedbagService) QueryHandler(_ context.Context, sess *Session) (XMessage, error) {
+	fb, err := s.fm.Retrieve(sess.ScreenName)
 	if err != nil {
 	if err != nil {
 		return XMessage{}, err
 		return XMessage{}, err
 	}
 	}
@@ -174,7 +179,7 @@ func (s FeedbagService) QueryHandler(ctx context.Context, sess *Session, fm Feed
 	lm := time.UnixMilli(0)
 	lm := time.UnixMilli(0)
 
 
 	if len(fb) > 0 {
 	if len(fb) > 0 {
-		lm, err = fm.LastModified(sess.ScreenName)
+		lm, err = s.fm.LastModified(sess.ScreenName)
 		if err != nil {
 		if err != nil {
 			return XMessage{}, err
 			return XMessage{}, err
 		}
 		}
@@ -193,8 +198,8 @@ func (s FeedbagService) QueryHandler(ctx context.Context, sess *Session, fm Feed
 	}, nil
 	}, nil
 }
 }
 
 
-func (s FeedbagService) QueryIfModifiedHandler(ctx context.Context, sess *Session, fm FeedbagManager, snacPayloadIn oscar.SNAC_0x13_0x05_FeedbagQueryIfModified) (XMessage, error) {
-	fb, err := fm.Retrieve(sess.ScreenName)
+func (s FeedbagService) QueryIfModifiedHandler(_ context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x13_0x05_FeedbagQueryIfModified) (XMessage, error) {
+	fb, err := s.fm.Retrieve(sess.ScreenName)
 	if err != nil {
 	if err != nil {
 		return XMessage{}, err
 		return XMessage{}, err
 	}
 	}
@@ -202,7 +207,7 @@ func (s FeedbagService) QueryIfModifiedHandler(ctx context.Context, sess *Sessio
 	lm := time.UnixMilli(0)
 	lm := time.UnixMilli(0)
 
 
 	if len(fb) > 0 {
 	if len(fb) > 0 {
-		lm, err = fm.LastModified(sess.ScreenName)
+		lm, err = s.fm.LastModified(sess.ScreenName)
 		if err != nil {
 		if err != nil {
 			return XMessage{}, err
 			return XMessage{}, err
 		}
 		}
@@ -233,7 +238,7 @@ func (s FeedbagService) QueryIfModifiedHandler(ctx context.Context, sess *Sessio
 	}, nil
 	}, nil
 }
 }
 
 
-func (s FeedbagService) InsertItemHandler(ctx context.Context, sm SessionManager, sess *Session, fm FeedbagManager, snacPayloadIn oscar.SNAC_0x13_0x08_FeedbagInsertItem) (XMessage, error) {
+func (s FeedbagService) InsertItemHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x13_0x08_FeedbagInsertItem) (XMessage, error) {
 	for _, item := range snacPayloadIn.Items {
 	for _, item := range snacPayloadIn.Items {
 		// don't let users block themselves, it causes the AIM client to go
 		// don't let users block themselves, it causes the AIM client to go
 		// into a weird state.
 		// into a weird state.
@@ -250,14 +255,14 @@ func (s FeedbagService) InsertItemHandler(ctx context.Context, sm SessionManager
 		}
 		}
 	}
 	}
 
 
-	if err := fm.Upsert(sess.ScreenName, snacPayloadIn.Items); err != nil {
+	if err := s.fm.Upsert(sess.ScreenName, snacPayloadIn.Items); err != nil {
 		return XMessage{}, nil
 		return XMessage{}, nil
 	}
 	}
 
 
 	for _, item := range snacPayloadIn.Items {
 	for _, item := range snacPayloadIn.Items {
 		switch item.ClassID {
 		switch item.ClassID {
 		case oscar.FeedbagClassIdBuddy, oscar.FeedbagClassIDPermit: // add new buddy
 		case oscar.FeedbagClassIdBuddy, oscar.FeedbagClassIDPermit: // add new buddy
-			err := UnicastArrival(ctx, item.Name, sess.ScreenName, sm)
+			err := UnicastArrival(ctx, item.Name, sess.ScreenName, s.sm)
 			switch {
 			switch {
 			case errors.Is(err, ErrSessNotFound):
 			case errors.Is(err, ErrSessNotFound):
 				continue
 				continue
@@ -266,7 +271,7 @@ func (s FeedbagService) InsertItemHandler(ctx context.Context, sm SessionManager
 			}
 			}
 		case oscar.FeedbagClassIDDeny: // block buddy
 		case oscar.FeedbagClassIDDeny: // block buddy
 			// notify this user that buddy is offline
 			// notify this user that buddy is offline
-			err := UnicastDeparture(ctx, item.Name, sess.ScreenName, sm)
+			err := UnicastDeparture(ctx, item.Name, sess.ScreenName, s.sm)
 			switch {
 			switch {
 			case errors.Is(err, ErrSessNotFound):
 			case errors.Is(err, ErrSessNotFound):
 				continue
 				continue
@@ -274,7 +279,7 @@ func (s FeedbagService) InsertItemHandler(ctx context.Context, sm SessionManager
 				return XMessage{}, err
 				return XMessage{}, err
 			}
 			}
 			// notify former buddy that this user is offline
 			// notify former buddy that this user is offline
-			if err := UnicastDeparture(ctx, sess.ScreenName, item.Name, sm); err != nil {
+			if err := UnicastDeparture(ctx, sess.ScreenName, item.Name, s.sm); err != nil {
 				return XMessage{}, err
 				return XMessage{}, err
 			}
 			}
 		}
 		}
@@ -294,15 +299,15 @@ func (s FeedbagService) InsertItemHandler(ctx context.Context, sm SessionManager
 	}, nil
 	}, nil
 }
 }
 
 
-func (s FeedbagService) UpdateItemHandler(ctx context.Context, sm SessionManager, sess *Session, fm FeedbagManager, snacPayloadIn oscar.SNAC_0x13_0x09_FeedbagUpdateItem) (XMessage, error) {
-	if err := fm.Upsert(sess.ScreenName, snacPayloadIn.Items); err != nil {
+func (s FeedbagService) UpdateItemHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x13_0x09_FeedbagUpdateItem) (XMessage, error) {
+	if err := s.fm.Upsert(sess.ScreenName, snacPayloadIn.Items); err != nil {
 		return XMessage{}, nil
 		return XMessage{}, nil
 	}
 	}
 
 
 	for _, item := range snacPayloadIn.Items {
 	for _, item := range snacPayloadIn.Items {
 		switch item.ClassID {
 		switch item.ClassID {
 		case oscar.FeedbagClassIdBuddy, oscar.FeedbagClassIDPermit:
 		case oscar.FeedbagClassIdBuddy, oscar.FeedbagClassIDPermit:
-			err := UnicastArrival(ctx, item.Name, sess.ScreenName, sm)
+			err := UnicastArrival(ctx, item.Name, sess.ScreenName, s.sm)
 			switch {
 			switch {
 			case errors.Is(err, ErrSessNotFound):
 			case errors.Is(err, ErrSessNotFound):
 				continue
 				continue
@@ -326,21 +331,21 @@ func (s FeedbagService) UpdateItemHandler(ctx context.Context, sm SessionManager
 	}, nil
 	}, nil
 }
 }
 
 
-func (s FeedbagService) DeleteItemHandler(ctx context.Context, sm SessionManager, sess *Session, fm FeedbagManager, snacPayloadIn oscar.SNAC_0x13_0x0A_FeedbagDeleteItem) (XMessage, error) {
-	if err := fm.Delete(sess.ScreenName, snacPayloadIn.Items); err != nil {
+func (s FeedbagService) DeleteItemHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x13_0x0A_FeedbagDeleteItem) (XMessage, error) {
+	if err := s.fm.Delete(sess.ScreenName, snacPayloadIn.Items); err != nil {
 		return XMessage{}, err
 		return XMessage{}, err
 	}
 	}
 
 
 	for _, item := range snacPayloadIn.Items {
 	for _, item := range snacPayloadIn.Items {
 		if item.ClassID == oscar.FeedbagClassIDDeny {
 		if item.ClassID == oscar.FeedbagClassIDDeny {
-			err := UnicastArrival(ctx, item.Name, sess.ScreenName, sm)
+			err := UnicastArrival(ctx, item.Name, sess.ScreenName, s.sm)
 			switch {
 			switch {
 			case errors.Is(err, ErrSessNotFound):
 			case errors.Is(err, ErrSessNotFound):
 				continue
 				continue
 			case err != nil:
 			case err != nil:
 				return XMessage{}, err
 				return XMessage{}, err
 			}
 			}
-			err = UnicastArrival(ctx, sess.ScreenName, item.Name, sm)
+			err = UnicastArrival(ctx, sess.ScreenName, item.Name, s.sm)
 			switch {
 			switch {
 			case errors.Is(err, ErrSessNotFound):
 			case errors.Is(err, ErrSessNotFound):
 				continue
 				continue

+ 70 - 78
server/feedbag_mock.go

@@ -22,23 +22,23 @@ func (_m *MockFeedbagHandler) EXPECT() *MockFeedbagHandler_Expecter {
 	return &MockFeedbagHandler_Expecter{mock: &_m.Mock}
 	return &MockFeedbagHandler_Expecter{mock: &_m.Mock}
 }
 }
 
 
-// DeleteItemHandler provides a mock function with given fields: ctx, sm, sess, fm, snacPayloadIn
-func (_m *MockFeedbagHandler) DeleteItemHandler(ctx context.Context, sm SessionManager, sess *Session, fm FeedbagManager, snacPayloadIn oscar.SNAC_0x13_0x0A_FeedbagDeleteItem) (XMessage, error) {
-	ret := _m.Called(ctx, sm, sess, fm, snacPayloadIn)
+// DeleteItemHandler provides a mock function with given fields: ctx, sess, snacPayloadIn
+func (_m *MockFeedbagHandler) DeleteItemHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x13_0x0A_FeedbagDeleteItem) (XMessage, error) {
+	ret := _m.Called(ctx, sess, snacPayloadIn)
 
 
 	var r0 XMessage
 	var r0 XMessage
 	var r1 error
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, SessionManager, *Session, FeedbagManager, oscar.SNAC_0x13_0x0A_FeedbagDeleteItem) (XMessage, error)); ok {
-		return rf(ctx, sm, sess, fm, snacPayloadIn)
+	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x13_0x0A_FeedbagDeleteItem) (XMessage, error)); ok {
+		return rf(ctx, sess, snacPayloadIn)
 	}
 	}
-	if rf, ok := ret.Get(0).(func(context.Context, SessionManager, *Session, FeedbagManager, oscar.SNAC_0x13_0x0A_FeedbagDeleteItem) XMessage); ok {
-		r0 = rf(ctx, sm, sess, fm, snacPayloadIn)
+	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x13_0x0A_FeedbagDeleteItem) XMessage); ok {
+		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
 	} else {
 		r0 = ret.Get(0).(XMessage)
 		r0 = ret.Get(0).(XMessage)
 	}
 	}
 
 
-	if rf, ok := ret.Get(1).(func(context.Context, SessionManager, *Session, FeedbagManager, oscar.SNAC_0x13_0x0A_FeedbagDeleteItem) error); ok {
-		r1 = rf(ctx, sm, sess, fm, snacPayloadIn)
+	if rf, ok := ret.Get(1).(func(context.Context, *Session, oscar.SNAC_0x13_0x0A_FeedbagDeleteItem) error); ok {
+		r1 = rf(ctx, sess, snacPayloadIn)
 	} else {
 	} else {
 		r1 = ret.Error(1)
 		r1 = ret.Error(1)
 	}
 	}
@@ -53,17 +53,15 @@ type MockFeedbagHandler_DeleteItemHandler_Call struct {
 
 
 // DeleteItemHandler is a helper method to define mock.On call
 // DeleteItemHandler is a helper method to define mock.On call
 //   - ctx context.Context
 //   - ctx context.Context
-//   - sm SessionManager
 //   - sess *Session
 //   - sess *Session
-//   - fm FeedbagManager
 //   - snacPayloadIn oscar.SNAC_0x13_0x0A_FeedbagDeleteItem
 //   - snacPayloadIn oscar.SNAC_0x13_0x0A_FeedbagDeleteItem
-func (_e *MockFeedbagHandler_Expecter) DeleteItemHandler(ctx interface{}, sm interface{}, sess interface{}, fm interface{}, snacPayloadIn interface{}) *MockFeedbagHandler_DeleteItemHandler_Call {
-	return &MockFeedbagHandler_DeleteItemHandler_Call{Call: _e.mock.On("DeleteItemHandler", ctx, sm, sess, fm, snacPayloadIn)}
+func (_e *MockFeedbagHandler_Expecter) DeleteItemHandler(ctx interface{}, sess interface{}, snacPayloadIn interface{}) *MockFeedbagHandler_DeleteItemHandler_Call {
+	return &MockFeedbagHandler_DeleteItemHandler_Call{Call: _e.mock.On("DeleteItemHandler", ctx, sess, snacPayloadIn)}
 }
 }
 
 
-func (_c *MockFeedbagHandler_DeleteItemHandler_Call) Run(run func(ctx context.Context, sm SessionManager, sess *Session, fm FeedbagManager, snacPayloadIn oscar.SNAC_0x13_0x0A_FeedbagDeleteItem)) *MockFeedbagHandler_DeleteItemHandler_Call {
+func (_c *MockFeedbagHandler_DeleteItemHandler_Call) Run(run func(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x13_0x0A_FeedbagDeleteItem)) *MockFeedbagHandler_DeleteItemHandler_Call {
 	_c.Call.Run(func(args mock.Arguments) {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(SessionManager), args[2].(*Session), args[3].(FeedbagManager), args[4].(oscar.SNAC_0x13_0x0A_FeedbagDeleteItem))
+		run(args[0].(context.Context), args[1].(*Session), args[2].(oscar.SNAC_0x13_0x0A_FeedbagDeleteItem))
 	})
 	})
 	return _c
 	return _c
 }
 }
@@ -73,28 +71,28 @@ func (_c *MockFeedbagHandler_DeleteItemHandler_Call) Return(_a0 XMessage, _a1 er
 	return _c
 	return _c
 }
 }
 
 
-func (_c *MockFeedbagHandler_DeleteItemHandler_Call) RunAndReturn(run func(context.Context, SessionManager, *Session, FeedbagManager, oscar.SNAC_0x13_0x0A_FeedbagDeleteItem) (XMessage, error)) *MockFeedbagHandler_DeleteItemHandler_Call {
+func (_c *MockFeedbagHandler_DeleteItemHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x13_0x0A_FeedbagDeleteItem) (XMessage, error)) *MockFeedbagHandler_DeleteItemHandler_Call {
 	_c.Call.Return(run)
 	_c.Call.Return(run)
 	return _c
 	return _c
 }
 }
 
 
-// InsertItemHandler provides a mock function with given fields: ctx, sm, sess, fm, snacPayloadIn
-func (_m *MockFeedbagHandler) InsertItemHandler(ctx context.Context, sm SessionManager, sess *Session, fm FeedbagManager, snacPayloadIn oscar.SNAC_0x13_0x08_FeedbagInsertItem) (XMessage, error) {
-	ret := _m.Called(ctx, sm, sess, fm, snacPayloadIn)
+// InsertItemHandler provides a mock function with given fields: ctx, sess, snacPayloadIn
+func (_m *MockFeedbagHandler) InsertItemHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x13_0x08_FeedbagInsertItem) (XMessage, error) {
+	ret := _m.Called(ctx, sess, snacPayloadIn)
 
 
 	var r0 XMessage
 	var r0 XMessage
 	var r1 error
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, SessionManager, *Session, FeedbagManager, oscar.SNAC_0x13_0x08_FeedbagInsertItem) (XMessage, error)); ok {
-		return rf(ctx, sm, sess, fm, snacPayloadIn)
+	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x13_0x08_FeedbagInsertItem) (XMessage, error)); ok {
+		return rf(ctx, sess, snacPayloadIn)
 	}
 	}
-	if rf, ok := ret.Get(0).(func(context.Context, SessionManager, *Session, FeedbagManager, oscar.SNAC_0x13_0x08_FeedbagInsertItem) XMessage); ok {
-		r0 = rf(ctx, sm, sess, fm, snacPayloadIn)
+	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x13_0x08_FeedbagInsertItem) XMessage); ok {
+		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
 	} else {
 		r0 = ret.Get(0).(XMessage)
 		r0 = ret.Get(0).(XMessage)
 	}
 	}
 
 
-	if rf, ok := ret.Get(1).(func(context.Context, SessionManager, *Session, FeedbagManager, oscar.SNAC_0x13_0x08_FeedbagInsertItem) error); ok {
-		r1 = rf(ctx, sm, sess, fm, snacPayloadIn)
+	if rf, ok := ret.Get(1).(func(context.Context, *Session, oscar.SNAC_0x13_0x08_FeedbagInsertItem) error); ok {
+		r1 = rf(ctx, sess, snacPayloadIn)
 	} else {
 	} else {
 		r1 = ret.Error(1)
 		r1 = ret.Error(1)
 	}
 	}
@@ -109,17 +107,15 @@ type MockFeedbagHandler_InsertItemHandler_Call struct {
 
 
 // InsertItemHandler is a helper method to define mock.On call
 // InsertItemHandler is a helper method to define mock.On call
 //   - ctx context.Context
 //   - ctx context.Context
-//   - sm SessionManager
 //   - sess *Session
 //   - sess *Session
-//   - fm FeedbagManager
 //   - snacPayloadIn oscar.SNAC_0x13_0x08_FeedbagInsertItem
 //   - snacPayloadIn oscar.SNAC_0x13_0x08_FeedbagInsertItem
-func (_e *MockFeedbagHandler_Expecter) InsertItemHandler(ctx interface{}, sm interface{}, sess interface{}, fm interface{}, snacPayloadIn interface{}) *MockFeedbagHandler_InsertItemHandler_Call {
-	return &MockFeedbagHandler_InsertItemHandler_Call{Call: _e.mock.On("InsertItemHandler", ctx, sm, sess, fm, snacPayloadIn)}
+func (_e *MockFeedbagHandler_Expecter) InsertItemHandler(ctx interface{}, sess interface{}, snacPayloadIn interface{}) *MockFeedbagHandler_InsertItemHandler_Call {
+	return &MockFeedbagHandler_InsertItemHandler_Call{Call: _e.mock.On("InsertItemHandler", ctx, sess, snacPayloadIn)}
 }
 }
 
 
-func (_c *MockFeedbagHandler_InsertItemHandler_Call) Run(run func(ctx context.Context, sm SessionManager, sess *Session, fm FeedbagManager, snacPayloadIn oscar.SNAC_0x13_0x08_FeedbagInsertItem)) *MockFeedbagHandler_InsertItemHandler_Call {
+func (_c *MockFeedbagHandler_InsertItemHandler_Call) Run(run func(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x13_0x08_FeedbagInsertItem)) *MockFeedbagHandler_InsertItemHandler_Call {
 	_c.Call.Run(func(args mock.Arguments) {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(SessionManager), args[2].(*Session), args[3].(FeedbagManager), args[4].(oscar.SNAC_0x13_0x08_FeedbagInsertItem))
+		run(args[0].(context.Context), args[1].(*Session), args[2].(oscar.SNAC_0x13_0x08_FeedbagInsertItem))
 	})
 	})
 	return _c
 	return _c
 }
 }
@@ -129,28 +125,28 @@ func (_c *MockFeedbagHandler_InsertItemHandler_Call) Return(_a0 XMessage, _a1 er
 	return _c
 	return _c
 }
 }
 
 
-func (_c *MockFeedbagHandler_InsertItemHandler_Call) RunAndReturn(run func(context.Context, SessionManager, *Session, FeedbagManager, oscar.SNAC_0x13_0x08_FeedbagInsertItem) (XMessage, error)) *MockFeedbagHandler_InsertItemHandler_Call {
+func (_c *MockFeedbagHandler_InsertItemHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x13_0x08_FeedbagInsertItem) (XMessage, error)) *MockFeedbagHandler_InsertItemHandler_Call {
 	_c.Call.Return(run)
 	_c.Call.Return(run)
 	return _c
 	return _c
 }
 }
 
 
-// QueryHandler provides a mock function with given fields: ctx, sess, fm
-func (_m *MockFeedbagHandler) QueryHandler(ctx context.Context, sess *Session, fm FeedbagManager) (XMessage, error) {
-	ret := _m.Called(ctx, sess, fm)
+// QueryHandler provides a mock function with given fields: ctx, sess
+func (_m *MockFeedbagHandler) QueryHandler(ctx context.Context, sess *Session) (XMessage, error) {
+	ret := _m.Called(ctx, sess)
 
 
 	var r0 XMessage
 	var r0 XMessage
 	var r1 error
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, FeedbagManager) (XMessage, error)); ok {
-		return rf(ctx, sess, fm)
+	if rf, ok := ret.Get(0).(func(context.Context, *Session) (XMessage, error)); ok {
+		return rf(ctx, sess)
 	}
 	}
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, FeedbagManager) XMessage); ok {
-		r0 = rf(ctx, sess, fm)
+	if rf, ok := ret.Get(0).(func(context.Context, *Session) XMessage); ok {
+		r0 = rf(ctx, sess)
 	} else {
 	} else {
 		r0 = ret.Get(0).(XMessage)
 		r0 = ret.Get(0).(XMessage)
 	}
 	}
 
 
-	if rf, ok := ret.Get(1).(func(context.Context, *Session, FeedbagManager) error); ok {
-		r1 = rf(ctx, sess, fm)
+	if rf, ok := ret.Get(1).(func(context.Context, *Session) error); ok {
+		r1 = rf(ctx, sess)
 	} else {
 	} else {
 		r1 = ret.Error(1)
 		r1 = ret.Error(1)
 	}
 	}
@@ -166,14 +162,13 @@ type MockFeedbagHandler_QueryHandler_Call struct {
 // QueryHandler is a helper method to define mock.On call
 // QueryHandler is a helper method to define mock.On call
 //   - ctx context.Context
 //   - ctx context.Context
 //   - sess *Session
 //   - sess *Session
-//   - fm FeedbagManager
-func (_e *MockFeedbagHandler_Expecter) QueryHandler(ctx interface{}, sess interface{}, fm interface{}) *MockFeedbagHandler_QueryHandler_Call {
-	return &MockFeedbagHandler_QueryHandler_Call{Call: _e.mock.On("QueryHandler", ctx, sess, fm)}
+func (_e *MockFeedbagHandler_Expecter) QueryHandler(ctx interface{}, sess interface{}) *MockFeedbagHandler_QueryHandler_Call {
+	return &MockFeedbagHandler_QueryHandler_Call{Call: _e.mock.On("QueryHandler", ctx, sess)}
 }
 }
 
 
-func (_c *MockFeedbagHandler_QueryHandler_Call) Run(run func(ctx context.Context, sess *Session, fm FeedbagManager)) *MockFeedbagHandler_QueryHandler_Call {
+func (_c *MockFeedbagHandler_QueryHandler_Call) Run(run func(ctx context.Context, sess *Session)) *MockFeedbagHandler_QueryHandler_Call {
 	_c.Call.Run(func(args mock.Arguments) {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(*Session), args[2].(FeedbagManager))
+		run(args[0].(context.Context), args[1].(*Session))
 	})
 	})
 	return _c
 	return _c
 }
 }
@@ -183,28 +178,28 @@ func (_c *MockFeedbagHandler_QueryHandler_Call) Return(_a0 XMessage, _a1 error)
 	return _c
 	return _c
 }
 }
 
 
-func (_c *MockFeedbagHandler_QueryHandler_Call) RunAndReturn(run func(context.Context, *Session, FeedbagManager) (XMessage, error)) *MockFeedbagHandler_QueryHandler_Call {
+func (_c *MockFeedbagHandler_QueryHandler_Call) RunAndReturn(run func(context.Context, *Session) (XMessage, error)) *MockFeedbagHandler_QueryHandler_Call {
 	_c.Call.Return(run)
 	_c.Call.Return(run)
 	return _c
 	return _c
 }
 }
 
 
-// QueryIfModifiedHandler provides a mock function with given fields: ctx, sess, fm, snacPayloadIn
-func (_m *MockFeedbagHandler) QueryIfModifiedHandler(ctx context.Context, sess *Session, fm FeedbagManager, snacPayloadIn oscar.SNAC_0x13_0x05_FeedbagQueryIfModified) (XMessage, error) {
-	ret := _m.Called(ctx, sess, fm, snacPayloadIn)
+// QueryIfModifiedHandler provides a mock function with given fields: ctx, sess, snacPayloadIn
+func (_m *MockFeedbagHandler) QueryIfModifiedHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x13_0x05_FeedbagQueryIfModified) (XMessage, error) {
+	ret := _m.Called(ctx, sess, snacPayloadIn)
 
 
 	var r0 XMessage
 	var r0 XMessage
 	var r1 error
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, FeedbagManager, oscar.SNAC_0x13_0x05_FeedbagQueryIfModified) (XMessage, error)); ok {
-		return rf(ctx, sess, fm, snacPayloadIn)
+	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x13_0x05_FeedbagQueryIfModified) (XMessage, error)); ok {
+		return rf(ctx, sess, snacPayloadIn)
 	}
 	}
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, FeedbagManager, oscar.SNAC_0x13_0x05_FeedbagQueryIfModified) XMessage); ok {
-		r0 = rf(ctx, sess, fm, snacPayloadIn)
+	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x13_0x05_FeedbagQueryIfModified) XMessage); ok {
+		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
 	} else {
 		r0 = ret.Get(0).(XMessage)
 		r0 = ret.Get(0).(XMessage)
 	}
 	}
 
 
-	if rf, ok := ret.Get(1).(func(context.Context, *Session, FeedbagManager, oscar.SNAC_0x13_0x05_FeedbagQueryIfModified) error); ok {
-		r1 = rf(ctx, sess, fm, snacPayloadIn)
+	if rf, ok := ret.Get(1).(func(context.Context, *Session, oscar.SNAC_0x13_0x05_FeedbagQueryIfModified) error); ok {
+		r1 = rf(ctx, sess, snacPayloadIn)
 	} else {
 	} else {
 		r1 = ret.Error(1)
 		r1 = ret.Error(1)
 	}
 	}
@@ -220,15 +215,14 @@ type MockFeedbagHandler_QueryIfModifiedHandler_Call struct {
 // QueryIfModifiedHandler is a helper method to define mock.On call
 // QueryIfModifiedHandler is a helper method to define mock.On call
 //   - ctx context.Context
 //   - ctx context.Context
 //   - sess *Session
 //   - sess *Session
-//   - fm FeedbagManager
 //   - snacPayloadIn oscar.SNAC_0x13_0x05_FeedbagQueryIfModified
 //   - snacPayloadIn oscar.SNAC_0x13_0x05_FeedbagQueryIfModified
-func (_e *MockFeedbagHandler_Expecter) QueryIfModifiedHandler(ctx interface{}, sess interface{}, fm interface{}, snacPayloadIn interface{}) *MockFeedbagHandler_QueryIfModifiedHandler_Call {
-	return &MockFeedbagHandler_QueryIfModifiedHandler_Call{Call: _e.mock.On("QueryIfModifiedHandler", ctx, sess, fm, snacPayloadIn)}
+func (_e *MockFeedbagHandler_Expecter) QueryIfModifiedHandler(ctx interface{}, sess interface{}, snacPayloadIn interface{}) *MockFeedbagHandler_QueryIfModifiedHandler_Call {
+	return &MockFeedbagHandler_QueryIfModifiedHandler_Call{Call: _e.mock.On("QueryIfModifiedHandler", ctx, sess, snacPayloadIn)}
 }
 }
 
 
-func (_c *MockFeedbagHandler_QueryIfModifiedHandler_Call) Run(run func(ctx context.Context, sess *Session, fm FeedbagManager, snacPayloadIn oscar.SNAC_0x13_0x05_FeedbagQueryIfModified)) *MockFeedbagHandler_QueryIfModifiedHandler_Call {
+func (_c *MockFeedbagHandler_QueryIfModifiedHandler_Call) Run(run func(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x13_0x05_FeedbagQueryIfModified)) *MockFeedbagHandler_QueryIfModifiedHandler_Call {
 	_c.Call.Run(func(args mock.Arguments) {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(*Session), args[2].(FeedbagManager), args[3].(oscar.SNAC_0x13_0x05_FeedbagQueryIfModified))
+		run(args[0].(context.Context), args[1].(*Session), args[2].(oscar.SNAC_0x13_0x05_FeedbagQueryIfModified))
 	})
 	})
 	return _c
 	return _c
 }
 }
@@ -238,7 +232,7 @@ func (_c *MockFeedbagHandler_QueryIfModifiedHandler_Call) Return(_a0 XMessage, _
 	return _c
 	return _c
 }
 }
 
 
-func (_c *MockFeedbagHandler_QueryIfModifiedHandler_Call) RunAndReturn(run func(context.Context, *Session, FeedbagManager, oscar.SNAC_0x13_0x05_FeedbagQueryIfModified) (XMessage, error)) *MockFeedbagHandler_QueryIfModifiedHandler_Call {
+func (_c *MockFeedbagHandler_QueryIfModifiedHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x13_0x05_FeedbagQueryIfModified) (XMessage, error)) *MockFeedbagHandler_QueryIfModifiedHandler_Call {
 	_c.Call.Return(run)
 	_c.Call.Return(run)
 	return _c
 	return _c
 }
 }
@@ -319,23 +313,23 @@ func (_c *MockFeedbagHandler_StartClusterHandler_Call) RunAndReturn(run func(con
 	return _c
 	return _c
 }
 }
 
 
-// UpdateItemHandler provides a mock function with given fields: ctx, sm, sess, fm, snacPayloadIn
-func (_m *MockFeedbagHandler) UpdateItemHandler(ctx context.Context, sm SessionManager, sess *Session, fm FeedbagManager, snacPayloadIn oscar.SNAC_0x13_0x09_FeedbagUpdateItem) (XMessage, error) {
-	ret := _m.Called(ctx, sm, sess, fm, snacPayloadIn)
+// UpdateItemHandler provides a mock function with given fields: ctx, sess, snacPayloadIn
+func (_m *MockFeedbagHandler) UpdateItemHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x13_0x09_FeedbagUpdateItem) (XMessage, error) {
+	ret := _m.Called(ctx, sess, snacPayloadIn)
 
 
 	var r0 XMessage
 	var r0 XMessage
 	var r1 error
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, SessionManager, *Session, FeedbagManager, oscar.SNAC_0x13_0x09_FeedbagUpdateItem) (XMessage, error)); ok {
-		return rf(ctx, sm, sess, fm, snacPayloadIn)
+	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x13_0x09_FeedbagUpdateItem) (XMessage, error)); ok {
+		return rf(ctx, sess, snacPayloadIn)
 	}
 	}
-	if rf, ok := ret.Get(0).(func(context.Context, SessionManager, *Session, FeedbagManager, oscar.SNAC_0x13_0x09_FeedbagUpdateItem) XMessage); ok {
-		r0 = rf(ctx, sm, sess, fm, snacPayloadIn)
+	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x13_0x09_FeedbagUpdateItem) XMessage); ok {
+		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
 	} else {
 		r0 = ret.Get(0).(XMessage)
 		r0 = ret.Get(0).(XMessage)
 	}
 	}
 
 
-	if rf, ok := ret.Get(1).(func(context.Context, SessionManager, *Session, FeedbagManager, oscar.SNAC_0x13_0x09_FeedbagUpdateItem) error); ok {
-		r1 = rf(ctx, sm, sess, fm, snacPayloadIn)
+	if rf, ok := ret.Get(1).(func(context.Context, *Session, oscar.SNAC_0x13_0x09_FeedbagUpdateItem) error); ok {
+		r1 = rf(ctx, sess, snacPayloadIn)
 	} else {
 	} else {
 		r1 = ret.Error(1)
 		r1 = ret.Error(1)
 	}
 	}
@@ -350,17 +344,15 @@ type MockFeedbagHandler_UpdateItemHandler_Call struct {
 
 
 // UpdateItemHandler is a helper method to define mock.On call
 // UpdateItemHandler is a helper method to define mock.On call
 //   - ctx context.Context
 //   - ctx context.Context
-//   - sm SessionManager
 //   - sess *Session
 //   - sess *Session
-//   - fm FeedbagManager
 //   - snacPayloadIn oscar.SNAC_0x13_0x09_FeedbagUpdateItem
 //   - snacPayloadIn oscar.SNAC_0x13_0x09_FeedbagUpdateItem
-func (_e *MockFeedbagHandler_Expecter) UpdateItemHandler(ctx interface{}, sm interface{}, sess interface{}, fm interface{}, snacPayloadIn interface{}) *MockFeedbagHandler_UpdateItemHandler_Call {
-	return &MockFeedbagHandler_UpdateItemHandler_Call{Call: _e.mock.On("UpdateItemHandler", ctx, sm, sess, fm, snacPayloadIn)}
+func (_e *MockFeedbagHandler_Expecter) UpdateItemHandler(ctx interface{}, sess interface{}, snacPayloadIn interface{}) *MockFeedbagHandler_UpdateItemHandler_Call {
+	return &MockFeedbagHandler_UpdateItemHandler_Call{Call: _e.mock.On("UpdateItemHandler", ctx, sess, snacPayloadIn)}
 }
 }
 
 
-func (_c *MockFeedbagHandler_UpdateItemHandler_Call) Run(run func(ctx context.Context, sm SessionManager, sess *Session, fm FeedbagManager, snacPayloadIn oscar.SNAC_0x13_0x09_FeedbagUpdateItem)) *MockFeedbagHandler_UpdateItemHandler_Call {
+func (_c *MockFeedbagHandler_UpdateItemHandler_Call) Run(run func(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x13_0x09_FeedbagUpdateItem)) *MockFeedbagHandler_UpdateItemHandler_Call {
 	_c.Call.Run(func(args mock.Arguments) {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(SessionManager), args[2].(*Session), args[3].(FeedbagManager), args[4].(oscar.SNAC_0x13_0x09_FeedbagUpdateItem))
+		run(args[0].(context.Context), args[1].(*Session), args[2].(oscar.SNAC_0x13_0x09_FeedbagUpdateItem))
 	})
 	})
 	return _c
 	return _c
 }
 }
@@ -370,7 +362,7 @@ func (_c *MockFeedbagHandler_UpdateItemHandler_Call) Return(_a0 XMessage, _a1 er
 	return _c
 	return _c
 }
 }
 
 
-func (_c *MockFeedbagHandler_UpdateItemHandler_Call) RunAndReturn(run func(context.Context, SessionManager, *Session, FeedbagManager, oscar.SNAC_0x13_0x09_FeedbagUpdateItem) (XMessage, error)) *MockFeedbagHandler_UpdateItemHandler_Call {
+func (_c *MockFeedbagHandler_UpdateItemHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x13_0x09_FeedbagUpdateItem) (XMessage, error)) *MockFeedbagHandler_UpdateItemHandler_Call {
 	_c.Call.Return(run)
 	_c.Call.Return(run)
 	return _c
 	return _c
 }
 }

+ 19 - 12
server/feedbag_test.go

@@ -91,8 +91,10 @@ func TestQueryHandler(t *testing.T) {
 			senderSession := &Session{
 			senderSession := &Session{
 				ScreenName: tc.screenName,
 				ScreenName: tc.screenName,
 			}
 			}
-			svc := FeedbagService{}
-			outputSNAC, err := svc.QueryHandler(nil, senderSession, fm)
+			svc := FeedbagService{
+				fm: fm,
+			}
+			outputSNAC, err := svc.QueryHandler(nil, senderSession)
 			assert.NoError(t, err)
 			assert.NoError(t, err)
 			assert.Equal(t, tc.expectOutput, outputSNAC)
 			assert.Equal(t, tc.expectOutput, outputSNAC)
 		})
 		})
@@ -215,8 +217,10 @@ func TestQueryIfModifiedHandler(t *testing.T) {
 			senderSession := &Session{
 			senderSession := &Session{
 				ScreenName: tc.screenName,
 				ScreenName: tc.screenName,
 			}
 			}
-			svc := FeedbagService{}
-			outputSNAC, err := svc.QueryIfModifiedHandler(nil, senderSession, fm, tc.inputSNAC)
+			svc := FeedbagService{
+				fm: fm,
+			}
+			outputSNAC, err := svc.QueryIfModifiedHandler(nil, senderSession, tc.inputSNAC)
 			assert.NoError(t, err)
 			assert.NoError(t, err)
 			//
 			//
 			// verify output
 			// verify output
@@ -553,8 +557,11 @@ func TestInsertItemHandler(t *testing.T) {
 			//
 			//
 			// send input SNAC
 			// send input SNAC
 			//
 			//
-			svc := FeedbagService{}
-			output, err := svc.InsertItemHandler(nil, sm, tc.userSession, fm, tc.inputSNAC)
+			svc := FeedbagService{
+				fm: fm,
+				sm: sm,
+			}
+			output, err := svc.InsertItemHandler(nil, tc.userSession, tc.inputSNAC)
 			assert.NoError(t, err)
 			assert.NoError(t, err)
 			//
 			//
 			// verify response
 			// verify response
@@ -796,15 +803,15 @@ func TestFeedbagRouter_RouteFeedbag(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			svc := NewMockFeedbagHandler(t)
 			svc := NewMockFeedbagHandler(t)
 			svc.EXPECT().
 			svc.EXPECT().
-				DeleteItemHandler(mock.Anything, mock.Anything, mock.Anything, mock.Anything, tc.input.snacOut).
+				DeleteItemHandler(mock.Anything, mock.Anything, tc.input.snacOut).
 				Return(tc.output, tc.handlerErr).
 				Return(tc.output, tc.handlerErr).
 				Maybe()
 				Maybe()
 			svc.EXPECT().
 			svc.EXPECT().
-				QueryHandler(mock.Anything, mock.Anything, mock.Anything).
+				QueryHandler(mock.Anything, mock.Anything).
 				Return(tc.output, tc.handlerErr).
 				Return(tc.output, tc.handlerErr).
 				Maybe()
 				Maybe()
 			svc.EXPECT().
 			svc.EXPECT().
-				QueryIfModifiedHandler(mock.Anything, mock.Anything, mock.Anything, tc.input.snacOut).
+				QueryIfModifiedHandler(mock.Anything, mock.Anything, tc.input.snacOut).
 				Return(tc.output, tc.handlerErr).
 				Return(tc.output, tc.handlerErr).
 				Maybe()
 				Maybe()
 			svc.EXPECT().
 			svc.EXPECT().
@@ -812,11 +819,11 @@ func TestFeedbagRouter_RouteFeedbag(t *testing.T) {
 				Return(tc.output).
 				Return(tc.output).
 				Maybe()
 				Maybe()
 			svc.EXPECT().
 			svc.EXPECT().
-				InsertItemHandler(mock.Anything, mock.Anything, mock.Anything, mock.Anything, tc.input.snacOut).
+				InsertItemHandler(mock.Anything, mock.Anything, tc.input.snacOut).
 				Return(tc.output, tc.handlerErr).
 				Return(tc.output, tc.handlerErr).
 				Maybe()
 				Maybe()
 			svc.EXPECT().
 			svc.EXPECT().
-				UpdateItemHandler(mock.Anything, mock.Anything, mock.Anything, mock.Anything, tc.input.snacOut).
+				UpdateItemHandler(mock.Anything, mock.Anything, tc.input.snacOut).
 				Return(tc.output, tc.handlerErr).
 				Return(tc.output, tc.handlerErr).
 				Maybe()
 				Maybe()
 			svc.EXPECT().
 			svc.EXPECT().
@@ -836,7 +843,7 @@ func TestFeedbagRouter_RouteFeedbag(t *testing.T) {
 			bufOut := &bytes.Buffer{}
 			bufOut := &bytes.Buffer{}
 			seq := uint32(0)
 			seq := uint32(0)
 
 
-			err := router.RouteFeedbag(nil, nil, nil, nil, tc.input.snacFrame, bufIn, bufOut, &seq)
+			err := router.RouteFeedbag(nil, nil, tc.input.snacFrame, bufIn, bufOut, &seq)
 			assert.ErrorIs(t, err, tc.expectErr)
 			assert.ErrorIs(t, err, tc.expectErr)
 			if tc.expectErr != nil {
 			if tc.expectErr != nil {
 				return
 				return

+ 26 - 21
server/icbm.go

@@ -15,15 +15,18 @@ const (
 )
 )
 
 
 type ICBMHandler interface {
 type ICBMHandler interface {
-	ChannelMsgToHostHandler(ctx context.Context, sm SessionManager, fm FeedbagManager, sess *Session, snacPayloadIn oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost) (*XMessage, error)
-	ClientEventHandler(ctx context.Context, sm SessionManager, fm FeedbagManager, sess *Session, snacPayloadIn oscar.SNAC_0x04_0x14_ICBMClientEvent) error
-	EvilRequestHandler(ctx context.Context, sm SessionManager, fm FeedbagManager, sess *Session, snacPayloadIn oscar.SNAC_0x04_0x08_ICBMEvilRequest) (XMessage, error)
+	ChannelMsgToHostHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost) (*XMessage, error)
+	ClientEventHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x04_0x14_ICBMClientEvent) error
+	EvilRequestHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x04_0x08_ICBMEvilRequest) (XMessage, error)
 	ParameterQueryHandler(context.Context) XMessage
 	ParameterQueryHandler(context.Context) XMessage
 }
 }
 
 
-func NewICBMRouter(logger *slog.Logger) ICBMRouter {
+func NewICBMRouter(logger *slog.Logger, sm SessionManager, fm FeedbagManager) ICBMRouter {
 	return ICBMRouter{
 	return ICBMRouter{
-		ICBMHandler: ICBMService{},
+		ICBMHandler: ICBMService{
+			sm: sm,
+			fm: fm,
+		},
 		RouteLogger: RouteLogger{
 		RouteLogger: RouteLogger{
 			Logger: logger,
 			Logger: logger,
 		},
 		},
@@ -35,7 +38,7 @@ type ICBMRouter struct {
 	RouteLogger
 	RouteLogger
 }
 }
 
 
-func (rt *ICBMRouter) RouteICBM(ctx context.Context, sm SessionManager, fm FeedbagManager, sess *Session, SNACFrame oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
+func (rt *ICBMRouter) RouteICBM(ctx context.Context, sess *Session, SNACFrame oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
 	switch SNACFrame.SubGroup {
 	switch SNACFrame.SubGroup {
 	case oscar.ICBMAddParameters:
 	case oscar.ICBMAddParameters:
 		inSNAC := oscar.SNAC_0x04_0x02_ICBMAddParameters{}
 		inSNAC := oscar.SNAC_0x04_0x02_ICBMAddParameters{}
@@ -50,7 +53,7 @@ func (rt *ICBMRouter) RouteICBM(ctx context.Context, sm SessionManager, fm Feedb
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
 			return err
 			return err
 		}
 		}
-		outSNAC, err := rt.ChannelMsgToHostHandler(ctx, sm, fm, sess, inSNAC)
+		outSNAC, err := rt.ChannelMsgToHostHandler(ctx, sess, inSNAC)
 		if err != nil || outSNAC == nil {
 		if err != nil || outSNAC == nil {
 			return err
 			return err
 		}
 		}
@@ -62,7 +65,7 @@ func (rt *ICBMRouter) RouteICBM(ctx context.Context, sm SessionManager, fm Feedb
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
 			return err
 			return err
 		}
 		}
-		outSNAC, err := rt.EvilRequestHandler(ctx, sm, fm, sess, inSNAC)
+		outSNAC, err := rt.EvilRequestHandler(ctx, sess, inSNAC)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
@@ -78,13 +81,15 @@ func (rt *ICBMRouter) RouteICBM(ctx context.Context, sm SessionManager, fm Feedb
 			return err
 			return err
 		}
 		}
 		rt.logRequest(ctx, SNACFrame, inSNAC)
 		rt.logRequest(ctx, SNACFrame, inSNAC)
-		return rt.ClientEventHandler(ctx, sm, fm, sess, inSNAC)
+		return rt.ClientEventHandler(ctx, sess, inSNAC)
 	default:
 	default:
 		return ErrUnsupportedSubGroup
 		return ErrUnsupportedSubGroup
 	}
 	}
 }
 }
 
 
 type ICBMService struct {
 type ICBMService struct {
+	sm SessionManager
+	fm FeedbagManager
 }
 }
 
 
 func (s ICBMService) ParameterQueryHandler(context.Context) XMessage {
 func (s ICBMService) ParameterQueryHandler(context.Context) XMessage {
@@ -104,8 +109,8 @@ func (s ICBMService) ParameterQueryHandler(context.Context) XMessage {
 	}
 	}
 }
 }
 
 
-func (s ICBMService) ChannelMsgToHostHandler(ctx context.Context, sm SessionManager, fm FeedbagManager, sess *Session, snacPayloadIn oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost) (*XMessage, error) {
-	blocked, err := fm.Blocked(sess.ScreenName, snacPayloadIn.ScreenName)
+func (s ICBMService) ChannelMsgToHostHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost) (*XMessage, error) {
+	blocked, err := s.fm.Blocked(sess.ScreenName, snacPayloadIn.ScreenName)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -126,7 +131,7 @@ func (s ICBMService) ChannelMsgToHostHandler(ctx context.Context, sm SessionMana
 		}, nil
 		}, nil
 	}
 	}
 
 
-	recipSess, err := sm.RetrieveByScreenName(snacPayloadIn.ScreenName)
+	recipSess, err := s.sm.RetrieveByScreenName(snacPayloadIn.ScreenName)
 	switch {
 	switch {
 	case errors.Is(err, ErrSessNotFound):
 	case errors.Is(err, ErrSessNotFound):
 		return &XMessage{
 		return &XMessage{
@@ -163,7 +168,7 @@ func (s ICBMService) ChannelMsgToHostHandler(ctx context.Context, sm SessionMana
 	// far as I can tell.
 	// far as I can tell.
 	clientIM.AddTLVList(snacPayloadIn.TLVRestBlock.TLVList)
 	clientIM.AddTLVList(snacPayloadIn.TLVRestBlock.TLVList)
 
 
-	sm.SendToScreenName(ctx, recipSess.ScreenName, XMessage{
+	s.sm.SendToScreenName(ctx, recipSess.ScreenName, XMessage{
 		snacFrame: oscar.SnacFrame{
 		snacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.ICBM,
 			FoodGroup: oscar.ICBM,
 			SubGroup:  oscar.ICBMChannelMsgToclient,
 			SubGroup:  oscar.ICBMChannelMsgToclient,
@@ -190,8 +195,8 @@ func (s ICBMService) ChannelMsgToHostHandler(ctx context.Context, sm SessionMana
 	}, nil
 	}, nil
 }
 }
 
 
-func (s ICBMService) ClientEventHandler(ctx context.Context, sm SessionManager, fm FeedbagManager, sess *Session, snacPayloadIn oscar.SNAC_0x04_0x14_ICBMClientEvent) error {
-	blocked, err := fm.Blocked(sess.ScreenName, snacPayloadIn.ScreenName)
+func (s ICBMService) ClientEventHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x04_0x14_ICBMClientEvent) error {
+	blocked, err := s.fm.Blocked(sess.ScreenName, snacPayloadIn.ScreenName)
 
 
 	switch {
 	switch {
 	case err != nil:
 	case err != nil:
@@ -199,7 +204,7 @@ func (s ICBMService) ClientEventHandler(ctx context.Context, sm SessionManager,
 	case blocked != BlockedNo:
 	case blocked != BlockedNo:
 		return nil
 		return nil
 	default:
 	default:
-		sm.SendToScreenName(ctx, snacPayloadIn.ScreenName, XMessage{
+		s.sm.SendToScreenName(ctx, snacPayloadIn.ScreenName, XMessage{
 			snacFrame: oscar.SnacFrame{
 			snacFrame: oscar.SnacFrame{
 				FoodGroup: oscar.ICBM,
 				FoodGroup: oscar.ICBM,
 				SubGroup:  oscar.ICBMClientEvent,
 				SubGroup:  oscar.ICBMClientEvent,
@@ -215,7 +220,7 @@ func (s ICBMService) ClientEventHandler(ctx context.Context, sm SessionManager,
 	}
 	}
 }
 }
 
 
-func (s ICBMService) EvilRequestHandler(ctx context.Context, sm SessionManager, fm FeedbagManager, sess *Session, snacPayloadIn oscar.SNAC_0x04_0x08_ICBMEvilRequest) (XMessage, error) {
+func (s ICBMService) EvilRequestHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x04_0x08_ICBMEvilRequest) (XMessage, error) {
 	// don't let users warn themselves, it causes the AIM client to go into a
 	// don't let users warn themselves, it causes the AIM client to go into a
 	// weird state.
 	// weird state.
 	if snacPayloadIn.ScreenName == sess.ScreenName {
 	if snacPayloadIn.ScreenName == sess.ScreenName {
@@ -230,7 +235,7 @@ func (s ICBMService) EvilRequestHandler(ctx context.Context, sm SessionManager,
 		}, nil
 		}, nil
 	}
 	}
 
 
-	blocked, err := fm.Blocked(sess.ScreenName, snacPayloadIn.ScreenName)
+	blocked, err := s.fm.Blocked(sess.ScreenName, snacPayloadIn.ScreenName)
 	if err != nil {
 	if err != nil {
 		return XMessage{}, nil
 		return XMessage{}, nil
 	}
 	}
@@ -246,7 +251,7 @@ func (s ICBMService) EvilRequestHandler(ctx context.Context, sm SessionManager,
 		}, nil
 		}, nil
 	}
 	}
 
 
-	recipSess, err := sm.RetrieveByScreenName(snacPayloadIn.ScreenName)
+	recipSess, err := s.sm.RetrieveByScreenName(snacPayloadIn.ScreenName)
 	if err != nil {
 	if err != nil {
 		return XMessage{}, nil
 		return XMessage{}, nil
 	}
 	}
@@ -272,7 +277,7 @@ func (s ICBMService) EvilRequestHandler(ctx context.Context, sm SessionManager,
 		}
 		}
 	}
 	}
 
 
-	sm.SendToScreenName(ctx, recipSess.ScreenName, XMessage{
+	s.sm.SendToScreenName(ctx, recipSess.ScreenName, XMessage{
 		snacFrame: oscar.SnacFrame{
 		snacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.OSERVICE,
 			FoodGroup: oscar.OSERVICE,
 			SubGroup:  oscar.OServiceEvilNotification,
 			SubGroup:  oscar.OServiceEvilNotification,
@@ -280,7 +285,7 @@ func (s ICBMService) EvilRequestHandler(ctx context.Context, sm SessionManager,
 		snacOut: notif,
 		snacOut: notif,
 	})
 	})
 
 
-	if err := BroadcastArrival(ctx, recipSess, sm, fm); err != nil {
+	if err := BroadcastArrival(ctx, recipSess, s.sm, s.fm); err != nil {
 		return XMessage{}, nil
 		return XMessage{}, nil
 	}
 	}
 
 

+ 38 - 44
server/icbm_mock.go

@@ -22,25 +22,25 @@ func (_m *MockICBMHandler) EXPECT() *MockICBMHandler_Expecter {
 	return &MockICBMHandler_Expecter{mock: &_m.Mock}
 	return &MockICBMHandler_Expecter{mock: &_m.Mock}
 }
 }
 
 
-// ChannelMsgToHostHandler provides a mock function with given fields: ctx, sm, fm, sess, snacPayloadIn
-func (_m *MockICBMHandler) ChannelMsgToHostHandler(ctx context.Context, sm SessionManager, fm FeedbagManager, sess *Session, snacPayloadIn oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost) (*XMessage, error) {
-	ret := _m.Called(ctx, sm, fm, sess, snacPayloadIn)
+// ChannelMsgToHostHandler provides a mock function with given fields: ctx, sess, snacPayloadIn
+func (_m *MockICBMHandler) ChannelMsgToHostHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost) (*XMessage, error) {
+	ret := _m.Called(ctx, sess, snacPayloadIn)
 
 
 	var r0 *XMessage
 	var r0 *XMessage
 	var r1 error
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, SessionManager, FeedbagManager, *Session, oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost) (*XMessage, error)); ok {
-		return rf(ctx, sm, fm, sess, snacPayloadIn)
+	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost) (*XMessage, error)); ok {
+		return rf(ctx, sess, snacPayloadIn)
 	}
 	}
-	if rf, ok := ret.Get(0).(func(context.Context, SessionManager, FeedbagManager, *Session, oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost) *XMessage); ok {
-		r0 = rf(ctx, sm, fm, sess, snacPayloadIn)
+	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost) *XMessage); ok {
+		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
 	} else {
 		if ret.Get(0) != nil {
 		if ret.Get(0) != nil {
 			r0 = ret.Get(0).(*XMessage)
 			r0 = ret.Get(0).(*XMessage)
 		}
 		}
 	}
 	}
 
 
-	if rf, ok := ret.Get(1).(func(context.Context, SessionManager, FeedbagManager, *Session, oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost) error); ok {
-		r1 = rf(ctx, sm, fm, sess, snacPayloadIn)
+	if rf, ok := ret.Get(1).(func(context.Context, *Session, oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost) error); ok {
+		r1 = rf(ctx, sess, snacPayloadIn)
 	} else {
 	} else {
 		r1 = ret.Error(1)
 		r1 = ret.Error(1)
 	}
 	}
@@ -55,17 +55,15 @@ type MockICBMHandler_ChannelMsgToHostHandler_Call struct {
 
 
 // ChannelMsgToHostHandler is a helper method to define mock.On call
 // ChannelMsgToHostHandler is a helper method to define mock.On call
 //   - ctx context.Context
 //   - ctx context.Context
-//   - sm SessionManager
-//   - fm FeedbagManager
 //   - sess *Session
 //   - sess *Session
 //   - snacPayloadIn oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost
 //   - snacPayloadIn oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost
-func (_e *MockICBMHandler_Expecter) ChannelMsgToHostHandler(ctx interface{}, sm interface{}, fm interface{}, sess interface{}, snacPayloadIn interface{}) *MockICBMHandler_ChannelMsgToHostHandler_Call {
-	return &MockICBMHandler_ChannelMsgToHostHandler_Call{Call: _e.mock.On("ChannelMsgToHostHandler", ctx, sm, fm, sess, snacPayloadIn)}
+func (_e *MockICBMHandler_Expecter) ChannelMsgToHostHandler(ctx interface{}, sess interface{}, snacPayloadIn interface{}) *MockICBMHandler_ChannelMsgToHostHandler_Call {
+	return &MockICBMHandler_ChannelMsgToHostHandler_Call{Call: _e.mock.On("ChannelMsgToHostHandler", ctx, sess, snacPayloadIn)}
 }
 }
 
 
-func (_c *MockICBMHandler_ChannelMsgToHostHandler_Call) Run(run func(ctx context.Context, sm SessionManager, fm FeedbagManager, sess *Session, snacPayloadIn oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost)) *MockICBMHandler_ChannelMsgToHostHandler_Call {
+func (_c *MockICBMHandler_ChannelMsgToHostHandler_Call) Run(run func(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost)) *MockICBMHandler_ChannelMsgToHostHandler_Call {
 	_c.Call.Run(func(args mock.Arguments) {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(SessionManager), args[2].(FeedbagManager), args[3].(*Session), args[4].(oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost))
+		run(args[0].(context.Context), args[1].(*Session), args[2].(oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost))
 	})
 	})
 	return _c
 	return _c
 }
 }
@@ -75,18 +73,18 @@ func (_c *MockICBMHandler_ChannelMsgToHostHandler_Call) Return(_a0 *XMessage, _a
 	return _c
 	return _c
 }
 }
 
 
-func (_c *MockICBMHandler_ChannelMsgToHostHandler_Call) RunAndReturn(run func(context.Context, SessionManager, FeedbagManager, *Session, oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost) (*XMessage, error)) *MockICBMHandler_ChannelMsgToHostHandler_Call {
+func (_c *MockICBMHandler_ChannelMsgToHostHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost) (*XMessage, error)) *MockICBMHandler_ChannelMsgToHostHandler_Call {
 	_c.Call.Return(run)
 	_c.Call.Return(run)
 	return _c
 	return _c
 }
 }
 
 
-// ClientEventHandler provides a mock function with given fields: ctx, sm, fm, sess, snacPayloadIn
-func (_m *MockICBMHandler) ClientEventHandler(ctx context.Context, sm SessionManager, fm FeedbagManager, sess *Session, snacPayloadIn oscar.SNAC_0x04_0x14_ICBMClientEvent) error {
-	ret := _m.Called(ctx, sm, fm, sess, snacPayloadIn)
+// ClientEventHandler provides a mock function with given fields: ctx, sess, snacPayloadIn
+func (_m *MockICBMHandler) ClientEventHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x04_0x14_ICBMClientEvent) error {
+	ret := _m.Called(ctx, sess, snacPayloadIn)
 
 
 	var r0 error
 	var r0 error
-	if rf, ok := ret.Get(0).(func(context.Context, SessionManager, FeedbagManager, *Session, oscar.SNAC_0x04_0x14_ICBMClientEvent) error); ok {
-		r0 = rf(ctx, sm, fm, sess, snacPayloadIn)
+	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x04_0x14_ICBMClientEvent) error); ok {
+		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
 	} else {
 		r0 = ret.Error(0)
 		r0 = ret.Error(0)
 	}
 	}
@@ -101,17 +99,15 @@ type MockICBMHandler_ClientEventHandler_Call struct {
 
 
 // ClientEventHandler is a helper method to define mock.On call
 // ClientEventHandler is a helper method to define mock.On call
 //   - ctx context.Context
 //   - ctx context.Context
-//   - sm SessionManager
-//   - fm FeedbagManager
 //   - sess *Session
 //   - sess *Session
 //   - snacPayloadIn oscar.SNAC_0x04_0x14_ICBMClientEvent
 //   - snacPayloadIn oscar.SNAC_0x04_0x14_ICBMClientEvent
-func (_e *MockICBMHandler_Expecter) ClientEventHandler(ctx interface{}, sm interface{}, fm interface{}, sess interface{}, snacPayloadIn interface{}) *MockICBMHandler_ClientEventHandler_Call {
-	return &MockICBMHandler_ClientEventHandler_Call{Call: _e.mock.On("ClientEventHandler", ctx, sm, fm, sess, snacPayloadIn)}
+func (_e *MockICBMHandler_Expecter) ClientEventHandler(ctx interface{}, sess interface{}, snacPayloadIn interface{}) *MockICBMHandler_ClientEventHandler_Call {
+	return &MockICBMHandler_ClientEventHandler_Call{Call: _e.mock.On("ClientEventHandler", ctx, sess, snacPayloadIn)}
 }
 }
 
 
-func (_c *MockICBMHandler_ClientEventHandler_Call) Run(run func(ctx context.Context, sm SessionManager, fm FeedbagManager, sess *Session, snacPayloadIn oscar.SNAC_0x04_0x14_ICBMClientEvent)) *MockICBMHandler_ClientEventHandler_Call {
+func (_c *MockICBMHandler_ClientEventHandler_Call) Run(run func(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x04_0x14_ICBMClientEvent)) *MockICBMHandler_ClientEventHandler_Call {
 	_c.Call.Run(func(args mock.Arguments) {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(SessionManager), args[2].(FeedbagManager), args[3].(*Session), args[4].(oscar.SNAC_0x04_0x14_ICBMClientEvent))
+		run(args[0].(context.Context), args[1].(*Session), args[2].(oscar.SNAC_0x04_0x14_ICBMClientEvent))
 	})
 	})
 	return _c
 	return _c
 }
 }
@@ -121,28 +117,28 @@ func (_c *MockICBMHandler_ClientEventHandler_Call) Return(_a0 error) *MockICBMHa
 	return _c
 	return _c
 }
 }
 
 
-func (_c *MockICBMHandler_ClientEventHandler_Call) RunAndReturn(run func(context.Context, SessionManager, FeedbagManager, *Session, oscar.SNAC_0x04_0x14_ICBMClientEvent) error) *MockICBMHandler_ClientEventHandler_Call {
+func (_c *MockICBMHandler_ClientEventHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x04_0x14_ICBMClientEvent) error) *MockICBMHandler_ClientEventHandler_Call {
 	_c.Call.Return(run)
 	_c.Call.Return(run)
 	return _c
 	return _c
 }
 }
 
 
-// EvilRequestHandler provides a mock function with given fields: ctx, sm, fm, sess, snacPayloadIn
-func (_m *MockICBMHandler) EvilRequestHandler(ctx context.Context, sm SessionManager, fm FeedbagManager, sess *Session, snacPayloadIn oscar.SNAC_0x04_0x08_ICBMEvilRequest) (XMessage, error) {
-	ret := _m.Called(ctx, sm, fm, sess, snacPayloadIn)
+// EvilRequestHandler provides a mock function with given fields: ctx, sess, snacPayloadIn
+func (_m *MockICBMHandler) EvilRequestHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x04_0x08_ICBMEvilRequest) (XMessage, error) {
+	ret := _m.Called(ctx, sess, snacPayloadIn)
 
 
 	var r0 XMessage
 	var r0 XMessage
 	var r1 error
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, SessionManager, FeedbagManager, *Session, oscar.SNAC_0x04_0x08_ICBMEvilRequest) (XMessage, error)); ok {
-		return rf(ctx, sm, fm, sess, snacPayloadIn)
+	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x04_0x08_ICBMEvilRequest) (XMessage, error)); ok {
+		return rf(ctx, sess, snacPayloadIn)
 	}
 	}
-	if rf, ok := ret.Get(0).(func(context.Context, SessionManager, FeedbagManager, *Session, oscar.SNAC_0x04_0x08_ICBMEvilRequest) XMessage); ok {
-		r0 = rf(ctx, sm, fm, sess, snacPayloadIn)
+	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x04_0x08_ICBMEvilRequest) XMessage); ok {
+		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
 	} else {
 		r0 = ret.Get(0).(XMessage)
 		r0 = ret.Get(0).(XMessage)
 	}
 	}
 
 
-	if rf, ok := ret.Get(1).(func(context.Context, SessionManager, FeedbagManager, *Session, oscar.SNAC_0x04_0x08_ICBMEvilRequest) error); ok {
-		r1 = rf(ctx, sm, fm, sess, snacPayloadIn)
+	if rf, ok := ret.Get(1).(func(context.Context, *Session, oscar.SNAC_0x04_0x08_ICBMEvilRequest) error); ok {
+		r1 = rf(ctx, sess, snacPayloadIn)
 	} else {
 	} else {
 		r1 = ret.Error(1)
 		r1 = ret.Error(1)
 	}
 	}
@@ -157,17 +153,15 @@ type MockICBMHandler_EvilRequestHandler_Call struct {
 
 
 // EvilRequestHandler is a helper method to define mock.On call
 // EvilRequestHandler is a helper method to define mock.On call
 //   - ctx context.Context
 //   - ctx context.Context
-//   - sm SessionManager
-//   - fm FeedbagManager
 //   - sess *Session
 //   - sess *Session
 //   - snacPayloadIn oscar.SNAC_0x04_0x08_ICBMEvilRequest
 //   - snacPayloadIn oscar.SNAC_0x04_0x08_ICBMEvilRequest
-func (_e *MockICBMHandler_Expecter) EvilRequestHandler(ctx interface{}, sm interface{}, fm interface{}, sess interface{}, snacPayloadIn interface{}) *MockICBMHandler_EvilRequestHandler_Call {
-	return &MockICBMHandler_EvilRequestHandler_Call{Call: _e.mock.On("EvilRequestHandler", ctx, sm, fm, sess, snacPayloadIn)}
+func (_e *MockICBMHandler_Expecter) EvilRequestHandler(ctx interface{}, sess interface{}, snacPayloadIn interface{}) *MockICBMHandler_EvilRequestHandler_Call {
+	return &MockICBMHandler_EvilRequestHandler_Call{Call: _e.mock.On("EvilRequestHandler", ctx, sess, snacPayloadIn)}
 }
 }
 
 
-func (_c *MockICBMHandler_EvilRequestHandler_Call) Run(run func(ctx context.Context, sm SessionManager, fm FeedbagManager, sess *Session, snacPayloadIn oscar.SNAC_0x04_0x08_ICBMEvilRequest)) *MockICBMHandler_EvilRequestHandler_Call {
+func (_c *MockICBMHandler_EvilRequestHandler_Call) Run(run func(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x04_0x08_ICBMEvilRequest)) *MockICBMHandler_EvilRequestHandler_Call {
 	_c.Call.Run(func(args mock.Arguments) {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(SessionManager), args[2].(FeedbagManager), args[3].(*Session), args[4].(oscar.SNAC_0x04_0x08_ICBMEvilRequest))
+		run(args[0].(context.Context), args[1].(*Session), args[2].(oscar.SNAC_0x04_0x08_ICBMEvilRequest))
 	})
 	})
 	return _c
 	return _c
 }
 }
@@ -177,7 +171,7 @@ func (_c *MockICBMHandler_EvilRequestHandler_Call) Return(_a0 XMessage, _a1 erro
 	return _c
 	return _c
 }
 }
 
 
-func (_c *MockICBMHandler_EvilRequestHandler_Call) RunAndReturn(run func(context.Context, SessionManager, FeedbagManager, *Session, oscar.SNAC_0x04_0x08_ICBMEvilRequest) (XMessage, error)) *MockICBMHandler_EvilRequestHandler_Call {
+func (_c *MockICBMHandler_EvilRequestHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x04_0x08_ICBMEvilRequest) (XMessage, error)) *MockICBMHandler_EvilRequestHandler_Call {
 	_c.Call.Return(run)
 	_c.Call.Return(run)
 	return _c
 	return _c
 }
 }

+ 19 - 10
server/icbm_test.go

@@ -243,8 +243,11 @@ func TestSendAndReceiveChannelMsgTohost(t *testing.T) {
 			//
 			//
 			// send input SNAC
 			// send input SNAC
 			//
 			//
-			svc := ICBMService{}
-			outputSNAC, err := svc.ChannelMsgToHostHandler(nil, sm, fm, tc.senderSession, tc.inputSNAC)
+			svc := ICBMService{
+				sm: sm,
+				fm: fm,
+			}
+			outputSNAC, err := svc.ChannelMsgToHostHandler(nil, tc.senderSession, tc.inputSNAC)
 			assert.NoError(t, err)
 			assert.NoError(t, err)
 			//
 			//
 			// verify output
 			// verify output
@@ -322,8 +325,11 @@ func TestSendAndReceiveClientEvent(t *testing.T) {
 			senderSession := &Session{
 			senderSession := &Session{
 				ScreenName: tc.senderScreenName,
 				ScreenName: tc.senderScreenName,
 			}
 			}
-			svc := ICBMService{}
-			assert.NoError(t, svc.ClientEventHandler(nil, sm, fm, senderSession, tc.inputSNAC))
+			svc := ICBMService{
+				sm: sm,
+				fm: fm,
+			}
+			assert.NoError(t, svc.ClientEventHandler(nil, senderSession, tc.inputSNAC))
 		})
 		})
 	}
 	}
 }
 }
@@ -554,8 +560,11 @@ func TestSendAndReceiveEvilRequest(t *testing.T) {
 			senderSession := &Session{
 			senderSession := &Session{
 				ScreenName: tc.senderSession.ScreenName,
 				ScreenName: tc.senderSession.ScreenName,
 			}
 			}
-			svc := ICBMService{}
-			outputSNAC, err := svc.EvilRequestHandler(nil, sm, fm, senderSession, tc.inputSNAC)
+			svc := ICBMService{
+				sm: sm,
+				fm: fm,
+			}
+			outputSNAC, err := svc.EvilRequestHandler(nil, senderSession, tc.inputSNAC)
 			assert.NoError(t, err)
 			assert.NoError(t, err)
 			assert.Equal(t, tc.expectOutput, outputSNAC)
 			assert.Equal(t, tc.expectOutput, outputSNAC)
 		})
 		})
@@ -706,16 +715,16 @@ func TestICBMRouter_RouteICBM(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			svc := NewMockICBMHandler(t)
 			svc := NewMockICBMHandler(t)
 			svc.EXPECT().
 			svc.EXPECT().
-				ChannelMsgToHostHandler(mock.Anything, mock.Anything, mock.Anything, mock.Anything, tc.input.snacOut).
+				ChannelMsgToHostHandler(mock.Anything, mock.Anything, tc.input.snacOut).
 				Return(tc.output, tc.handlerErr).
 				Return(tc.output, tc.handlerErr).
 				Maybe()
 				Maybe()
 			svc.EXPECT().
 			svc.EXPECT().
-				ClientEventHandler(mock.Anything, mock.Anything, mock.Anything, mock.Anything, tc.input.snacOut).
+				ClientEventHandler(mock.Anything, mock.Anything, tc.input.snacOut).
 				Return(tc.handlerErr).
 				Return(tc.handlerErr).
 				Maybe()
 				Maybe()
 			if tc.output != nil {
 			if tc.output != nil {
 				svc.EXPECT().
 				svc.EXPECT().
-					EvilRequestHandler(mock.Anything, mock.Anything, mock.Anything, mock.Anything, tc.input.snacOut).
+					EvilRequestHandler(mock.Anything, mock.Anything, tc.input.snacOut).
 					Return(*tc.output, tc.handlerErr).
 					Return(*tc.output, tc.handlerErr).
 					Maybe()
 					Maybe()
 				svc.EXPECT().
 				svc.EXPECT().
@@ -737,7 +746,7 @@ func TestICBMRouter_RouteICBM(t *testing.T) {
 			bufOut := &bytes.Buffer{}
 			bufOut := &bytes.Buffer{}
 			seq := uint32(1)
 			seq := uint32(1)
 
 
-			err := router.RouteICBM(nil, nil, nil, nil, tc.input.snacFrame, bufIn, bufOut, &seq)
+			err := router.RouteICBM(nil, nil, tc.input.snacFrame, bufIn, bufOut, &seq)
 			assert.ErrorIs(t, err, tc.expectErr)
 			assert.ErrorIs(t, err, tc.expectErr)
 			if tc.expectErr != nil {
 			if tc.expectErr != nil {
 				return
 				return

+ 23 - 16
server/locate.go

@@ -12,14 +12,18 @@ import (
 type LocateHandler interface {
 type LocateHandler interface {
 	RightsQueryHandler(ctx context.Context) XMessage
 	RightsQueryHandler(ctx context.Context) XMessage
 	SetDirInfoHandler(ctx context.Context) XMessage
 	SetDirInfoHandler(ctx context.Context) XMessage
-	SetInfoHandler(ctx context.Context, sess *Session, sm SessionManager, fm FeedbagManager, pm ProfileManager, snacPayloadIn oscar.SNAC_0x02_0x04_LocateSetInfo) error
+	SetInfoHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x02_0x04_LocateSetInfo) error
 	SetKeywordInfoHandler(ctx context.Context) XMessage
 	SetKeywordInfoHandler(ctx context.Context) XMessage
-	UserInfoQuery2Handler(ctx context.Context, sess *Session, sm SessionManager, fm FeedbagManager, pm ProfileManager, snacPayloadIn oscar.SNAC_0x02_0x15_LocateUserInfoQuery2) (XMessage, error)
+	UserInfoQuery2Handler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x02_0x15_LocateUserInfoQuery2) (XMessage, error)
 }
 }
 
 
-func NewLocateRouter(logger *slog.Logger) LocateRouter {
+func NewLocateRouter(logger *slog.Logger, sm SessionManager, fm FeedbagManager, pm ProfileManager) LocateRouter {
 	return LocateRouter{
 	return LocateRouter{
-		LocateHandler: LocateService{},
+		LocateHandler: LocateService{
+			sm: sm,
+			fm: fm,
+			pm: pm,
+		},
 		RouteLogger: RouteLogger{
 		RouteLogger: RouteLogger{
 			Logger: logger,
 			Logger: logger,
 		},
 		},
@@ -31,7 +35,7 @@ type LocateRouter struct {
 	RouteLogger
 	RouteLogger
 }
 }
 
 
-func (rt LocateRouter) RouteLocate(ctx context.Context, sess *Session, sm SessionManager, fm *FeedbagStore, SNACFrame oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
+func (rt LocateRouter) RouteLocate(ctx context.Context, sess *Session, SNACFrame oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
 	switch SNACFrame.SubGroup {
 	switch SNACFrame.SubGroup {
 	case oscar.LocateRightsQuery:
 	case oscar.LocateRightsQuery:
 		outSNAC := rt.RightsQueryHandler(ctx)
 		outSNAC := rt.RightsQueryHandler(ctx)
@@ -43,7 +47,7 @@ func (rt LocateRouter) RouteLocate(ctx context.Context, sess *Session, sm Sessio
 			return err
 			return err
 		}
 		}
 		rt.logRequest(ctx, SNACFrame, inSNAC)
 		rt.logRequest(ctx, SNACFrame, inSNAC)
-		return rt.SetInfoHandler(ctx, sess, sm, fm, fm, inSNAC)
+		return rt.SetInfoHandler(ctx, sess, inSNAC)
 	case oscar.LocateSetDirInfo:
 	case oscar.LocateSetDirInfo:
 		inSNAC := oscar.SNAC_0x02_0x09_LocateSetDirInfo{}
 		inSNAC := oscar.SNAC_0x02_0x09_LocateSetDirInfo{}
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
@@ -69,7 +73,7 @@ func (rt LocateRouter) RouteLocate(ctx context.Context, sess *Session, sm Sessio
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
 			return err
 			return err
 		}
 		}
-		outSNAC, err := rt.UserInfoQuery2Handler(ctx, sess, sm, fm, fm, inSNAC)
+		outSNAC, err := rt.UserInfoQuery2Handler(ctx, sess, inSNAC)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
@@ -81,6 +85,9 @@ func (rt LocateRouter) RouteLocate(ctx context.Context, sess *Session, sm Sessio
 }
 }
 
 
 type LocateService struct {
 type LocateService struct {
+	sm SessionManager
+	fm FeedbagManager
+	pm ProfileManager
 }
 }
 
 
 func (s LocateService) RightsQueryHandler(context.Context) XMessage {
 func (s LocateService) RightsQueryHandler(context.Context) XMessage {
@@ -103,10 +110,10 @@ func (s LocateService) RightsQueryHandler(context.Context) XMessage {
 	}
 	}
 }
 }
 
 
-func (s LocateService) SetInfoHandler(ctx context.Context, sess *Session, sm SessionManager, fm FeedbagManager, pm ProfileManager, snacPayloadIn oscar.SNAC_0x02_0x04_LocateSetInfo) error {
+func (s LocateService) SetInfoHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x02_0x04_LocateSetInfo) error {
 	// update profile
 	// update profile
 	if profile, hasProfile := snacPayloadIn.GetString(oscar.LocateTLVTagsInfoSigData); hasProfile {
 	if profile, hasProfile := snacPayloadIn.GetString(oscar.LocateTLVTagsInfoSigData); hasProfile {
-		if err := pm.UpsertProfile(sess.ScreenName, profile); err != nil {
+		if err := s.pm.UpsertProfile(sess.ScreenName, profile); err != nil {
 			return err
 			return err
 		}
 		}
 	}
 	}
@@ -114,15 +121,15 @@ func (s LocateService) SetInfoHandler(ctx context.Context, sess *Session, sm Ses
 	// broadcast away message change to buddies
 	// broadcast away message change to buddies
 	if awayMsg, hasAwayMsg := snacPayloadIn.GetString(oscar.LocateTLVTagsInfoUnavailableData); hasAwayMsg {
 	if awayMsg, hasAwayMsg := snacPayloadIn.GetString(oscar.LocateTLVTagsInfoUnavailableData); hasAwayMsg {
 		sess.SetAwayMessage(awayMsg)
 		sess.SetAwayMessage(awayMsg)
-		if err := BroadcastArrival(ctx, sess, sm, fm); err != nil {
+		if err := BroadcastArrival(ctx, sess, s.sm, s.fm); err != nil {
 			return err
 			return err
 		}
 		}
 	}
 	}
 	return nil
 	return nil
 }
 }
 
 
-func (s LocateService) UserInfoQuery2Handler(ctx context.Context, sess *Session, sm SessionManager, fm FeedbagManager, pm ProfileManager, snacPayloadIn oscar.SNAC_0x02_0x15_LocateUserInfoQuery2) (XMessage, error) {
-	blocked, err := fm.Blocked(sess.ScreenName, snacPayloadIn.ScreenName)
+func (s LocateService) UserInfoQuery2Handler(_ context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x02_0x15_LocateUserInfoQuery2) (XMessage, error) {
+	blocked, err := s.fm.Blocked(sess.ScreenName, snacPayloadIn.ScreenName)
 	switch {
 	switch {
 	case err != nil:
 	case err != nil:
 		return XMessage{}, err
 		return XMessage{}, err
@@ -138,7 +145,7 @@ func (s LocateService) UserInfoQuery2Handler(ctx context.Context, sess *Session,
 		}, nil
 		}, nil
 	}
 	}
 
 
-	buddySess, err := sm.RetrieveByScreenName(snacPayloadIn.ScreenName)
+	buddySess, err := s.sm.RetrieveByScreenName(snacPayloadIn.ScreenName)
 	switch {
 	switch {
 	case errors.Is(err, ErrSessNotFound):
 	case errors.Is(err, ErrSessNotFound):
 		return XMessage{
 		return XMessage{
@@ -157,7 +164,7 @@ func (s LocateService) UserInfoQuery2Handler(ctx context.Context, sess *Session,
 	var list oscar.TLVList
 	var list oscar.TLVList
 
 
 	if snacPayloadIn.RequestProfile() {
 	if snacPayloadIn.RequestProfile() {
-		profile, err := pm.RetrieveProfile(snacPayloadIn.ScreenName)
+		profile, err := s.pm.RetrieveProfile(snacPayloadIn.ScreenName)
 		if err != nil {
 		if err != nil {
 			return XMessage{}, err
 			return XMessage{}, err
 		}
 		}
@@ -188,7 +195,7 @@ func (s LocateService) UserInfoQuery2Handler(ctx context.Context, sess *Session,
 	}, nil
 	}, nil
 }
 }
 
 
-func (s LocateService) SetDirInfoHandler(ctx context.Context) XMessage {
+func (s LocateService) SetDirInfoHandler(_ context.Context) XMessage {
 	return XMessage{
 	return XMessage{
 		snacFrame: oscar.SnacFrame{
 		snacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.LOCATE,
 			FoodGroup: oscar.LOCATE,
@@ -200,7 +207,7 @@ func (s LocateService) SetDirInfoHandler(ctx context.Context) XMessage {
 	}
 	}
 }
 }
 
 
-func (s LocateService) SetKeywordInfoHandler(ctx context.Context) XMessage {
+func (s LocateService) SetKeywordInfoHandler(_ context.Context) XMessage {
 	return XMessage{
 	return XMessage{
 		snacFrame: oscar.SnacFrame{
 		snacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.LOCATE,
 			FoodGroup: oscar.LOCATE,

+ 24 - 30
server/locate_mock.go

@@ -106,13 +106,13 @@ func (_c *MockLocateHandler_SetDirInfoHandler_Call) RunAndReturn(run func(contex
 	return _c
 	return _c
 }
 }
 
 
-// SetInfoHandler provides a mock function with given fields: ctx, sess, sm, fm, pm, snacPayloadIn
-func (_m *MockLocateHandler) SetInfoHandler(ctx context.Context, sess *Session, sm SessionManager, fm FeedbagManager, pm ProfileManager, snacPayloadIn oscar.SNAC_0x02_0x04_LocateSetInfo) error {
-	ret := _m.Called(ctx, sess, sm, fm, pm, snacPayloadIn)
+// SetInfoHandler provides a mock function with given fields: ctx, sess, snacPayloadIn
+func (_m *MockLocateHandler) SetInfoHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x02_0x04_LocateSetInfo) error {
+	ret := _m.Called(ctx, sess, snacPayloadIn)
 
 
 	var r0 error
 	var r0 error
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, SessionManager, FeedbagManager, ProfileManager, oscar.SNAC_0x02_0x04_LocateSetInfo) error); ok {
-		r0 = rf(ctx, sess, sm, fm, pm, snacPayloadIn)
+	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x02_0x04_LocateSetInfo) error); ok {
+		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
 	} else {
 		r0 = ret.Error(0)
 		r0 = ret.Error(0)
 	}
 	}
@@ -128,17 +128,14 @@ type MockLocateHandler_SetInfoHandler_Call struct {
 // SetInfoHandler is a helper method to define mock.On call
 // SetInfoHandler is a helper method to define mock.On call
 //   - ctx context.Context
 //   - ctx context.Context
 //   - sess *Session
 //   - sess *Session
-//   - sm SessionManager
-//   - fm FeedbagManager
-//   - pm ProfileManager
 //   - snacPayloadIn oscar.SNAC_0x02_0x04_LocateSetInfo
 //   - snacPayloadIn oscar.SNAC_0x02_0x04_LocateSetInfo
-func (_e *MockLocateHandler_Expecter) SetInfoHandler(ctx interface{}, sess interface{}, sm interface{}, fm interface{}, pm interface{}, snacPayloadIn interface{}) *MockLocateHandler_SetInfoHandler_Call {
-	return &MockLocateHandler_SetInfoHandler_Call{Call: _e.mock.On("SetInfoHandler", ctx, sess, sm, fm, pm, snacPayloadIn)}
+func (_e *MockLocateHandler_Expecter) SetInfoHandler(ctx interface{}, sess interface{}, snacPayloadIn interface{}) *MockLocateHandler_SetInfoHandler_Call {
+	return &MockLocateHandler_SetInfoHandler_Call{Call: _e.mock.On("SetInfoHandler", ctx, sess, snacPayloadIn)}
 }
 }
 
 
-func (_c *MockLocateHandler_SetInfoHandler_Call) Run(run func(ctx context.Context, sess *Session, sm SessionManager, fm FeedbagManager, pm ProfileManager, snacPayloadIn oscar.SNAC_0x02_0x04_LocateSetInfo)) *MockLocateHandler_SetInfoHandler_Call {
+func (_c *MockLocateHandler_SetInfoHandler_Call) Run(run func(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x02_0x04_LocateSetInfo)) *MockLocateHandler_SetInfoHandler_Call {
 	_c.Call.Run(func(args mock.Arguments) {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(*Session), args[2].(SessionManager), args[3].(FeedbagManager), args[4].(ProfileManager), args[5].(oscar.SNAC_0x02_0x04_LocateSetInfo))
+		run(args[0].(context.Context), args[1].(*Session), args[2].(oscar.SNAC_0x02_0x04_LocateSetInfo))
 	})
 	})
 	return _c
 	return _c
 }
 }
@@ -148,7 +145,7 @@ func (_c *MockLocateHandler_SetInfoHandler_Call) Return(_a0 error) *MockLocateHa
 	return _c
 	return _c
 }
 }
 
 
-func (_c *MockLocateHandler_SetInfoHandler_Call) RunAndReturn(run func(context.Context, *Session, SessionManager, FeedbagManager, ProfileManager, oscar.SNAC_0x02_0x04_LocateSetInfo) error) *MockLocateHandler_SetInfoHandler_Call {
+func (_c *MockLocateHandler_SetInfoHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x02_0x04_LocateSetInfo) error) *MockLocateHandler_SetInfoHandler_Call {
 	_c.Call.Return(run)
 	_c.Call.Return(run)
 	return _c
 	return _c
 }
 }
@@ -195,23 +192,23 @@ func (_c *MockLocateHandler_SetKeywordInfoHandler_Call) RunAndReturn(run func(co
 	return _c
 	return _c
 }
 }
 
 
-// UserInfoQuery2Handler provides a mock function with given fields: ctx, sess, sm, fm, pm, snacPayloadIn
-func (_m *MockLocateHandler) UserInfoQuery2Handler(ctx context.Context, sess *Session, sm SessionManager, fm FeedbagManager, pm ProfileManager, snacPayloadIn oscar.SNAC_0x02_0x15_LocateUserInfoQuery2) (XMessage, error) {
-	ret := _m.Called(ctx, sess, sm, fm, pm, snacPayloadIn)
+// UserInfoQuery2Handler provides a mock function with given fields: ctx, sess, snacPayloadIn
+func (_m *MockLocateHandler) UserInfoQuery2Handler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x02_0x15_LocateUserInfoQuery2) (XMessage, error) {
+	ret := _m.Called(ctx, sess, snacPayloadIn)
 
 
 	var r0 XMessage
 	var r0 XMessage
 	var r1 error
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, SessionManager, FeedbagManager, ProfileManager, oscar.SNAC_0x02_0x15_LocateUserInfoQuery2) (XMessage, error)); ok {
-		return rf(ctx, sess, sm, fm, pm, snacPayloadIn)
+	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x02_0x15_LocateUserInfoQuery2) (XMessage, error)); ok {
+		return rf(ctx, sess, snacPayloadIn)
 	}
 	}
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, SessionManager, FeedbagManager, ProfileManager, oscar.SNAC_0x02_0x15_LocateUserInfoQuery2) XMessage); ok {
-		r0 = rf(ctx, sess, sm, fm, pm, snacPayloadIn)
+	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x02_0x15_LocateUserInfoQuery2) XMessage); ok {
+		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
 	} else {
 		r0 = ret.Get(0).(XMessage)
 		r0 = ret.Get(0).(XMessage)
 	}
 	}
 
 
-	if rf, ok := ret.Get(1).(func(context.Context, *Session, SessionManager, FeedbagManager, ProfileManager, oscar.SNAC_0x02_0x15_LocateUserInfoQuery2) error); ok {
-		r1 = rf(ctx, sess, sm, fm, pm, snacPayloadIn)
+	if rf, ok := ret.Get(1).(func(context.Context, *Session, oscar.SNAC_0x02_0x15_LocateUserInfoQuery2) error); ok {
+		r1 = rf(ctx, sess, snacPayloadIn)
 	} else {
 	} else {
 		r1 = ret.Error(1)
 		r1 = ret.Error(1)
 	}
 	}
@@ -227,17 +224,14 @@ type MockLocateHandler_UserInfoQuery2Handler_Call struct {
 // UserInfoQuery2Handler is a helper method to define mock.On call
 // UserInfoQuery2Handler is a helper method to define mock.On call
 //   - ctx context.Context
 //   - ctx context.Context
 //   - sess *Session
 //   - sess *Session
-//   - sm SessionManager
-//   - fm FeedbagManager
-//   - pm ProfileManager
 //   - snacPayloadIn oscar.SNAC_0x02_0x15_LocateUserInfoQuery2
 //   - snacPayloadIn oscar.SNAC_0x02_0x15_LocateUserInfoQuery2
-func (_e *MockLocateHandler_Expecter) UserInfoQuery2Handler(ctx interface{}, sess interface{}, sm interface{}, fm interface{}, pm interface{}, snacPayloadIn interface{}) *MockLocateHandler_UserInfoQuery2Handler_Call {
-	return &MockLocateHandler_UserInfoQuery2Handler_Call{Call: _e.mock.On("UserInfoQuery2Handler", ctx, sess, sm, fm, pm, snacPayloadIn)}
+func (_e *MockLocateHandler_Expecter) UserInfoQuery2Handler(ctx interface{}, sess interface{}, snacPayloadIn interface{}) *MockLocateHandler_UserInfoQuery2Handler_Call {
+	return &MockLocateHandler_UserInfoQuery2Handler_Call{Call: _e.mock.On("UserInfoQuery2Handler", ctx, sess, snacPayloadIn)}
 }
 }
 
 
-func (_c *MockLocateHandler_UserInfoQuery2Handler_Call) Run(run func(ctx context.Context, sess *Session, sm SessionManager, fm FeedbagManager, pm ProfileManager, snacPayloadIn oscar.SNAC_0x02_0x15_LocateUserInfoQuery2)) *MockLocateHandler_UserInfoQuery2Handler_Call {
+func (_c *MockLocateHandler_UserInfoQuery2Handler_Call) Run(run func(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x02_0x15_LocateUserInfoQuery2)) *MockLocateHandler_UserInfoQuery2Handler_Call {
 	_c.Call.Run(func(args mock.Arguments) {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(*Session), args[2].(SessionManager), args[3].(FeedbagManager), args[4].(ProfileManager), args[5].(oscar.SNAC_0x02_0x15_LocateUserInfoQuery2))
+		run(args[0].(context.Context), args[1].(*Session), args[2].(oscar.SNAC_0x02_0x15_LocateUserInfoQuery2))
 	})
 	})
 	return _c
 	return _c
 }
 }
@@ -247,7 +241,7 @@ func (_c *MockLocateHandler_UserInfoQuery2Handler_Call) Return(_a0 XMessage, _a1
 	return _c
 	return _c
 }
 }
 
 
-func (_c *MockLocateHandler_UserInfoQuery2Handler_Call) RunAndReturn(run func(context.Context, *Session, SessionManager, FeedbagManager, ProfileManager, oscar.SNAC_0x02_0x15_LocateUserInfoQuery2) (XMessage, error)) *MockLocateHandler_UserInfoQuery2Handler_Call {
+func (_c *MockLocateHandler_UserInfoQuery2Handler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x02_0x15_LocateUserInfoQuery2) (XMessage, error)) *MockLocateHandler_UserInfoQuery2Handler_Call {
 	_c.Call.Return(run)
 	_c.Call.Return(run)
 	return _c
 	return _c
 }
 }

+ 9 - 5
server/locate_test.go

@@ -284,8 +284,12 @@ func TestSendAndReceiveUserInfoQuery2(t *testing.T) {
 					Maybe()
 					Maybe()
 			}
 			}
 
 
-			svc := LocateService{}
-			outputSNAC, err := svc.UserInfoQuery2Handler(context.Background(), tc.userSession, sm, fm, pm, tc.inputSNAC)
+			svc := LocateService{
+				sm: sm,
+				fm: fm,
+				pm: pm,
+			}
+			outputSNAC, err := svc.UserInfoQuery2Handler(context.Background(), tc.userSession, tc.inputSNAC)
 			assert.NoError(t, err)
 			assert.NoError(t, err)
 			assert.Equal(t, tc.expectOutput, outputSNAC)
 			assert.Equal(t, tc.expectOutput, outputSNAC)
 		})
 		})
@@ -478,7 +482,7 @@ func TestLocateRouter_RouteLocate(t *testing.T) {
 				Return(tc.output).
 				Return(tc.output).
 				Maybe()
 				Maybe()
 			svc.EXPECT().
 			svc.EXPECT().
-				SetInfoHandler(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, tc.input.snacOut).
+				SetInfoHandler(mock.Anything, mock.Anything, tc.input.snacOut).
 				Return(tc.handlerErr).
 				Return(tc.handlerErr).
 				Maybe()
 				Maybe()
 			svc.EXPECT().
 			svc.EXPECT().
@@ -486,7 +490,7 @@ func TestLocateRouter_RouteLocate(t *testing.T) {
 				Return(tc.output).
 				Return(tc.output).
 				Maybe()
 				Maybe()
 			svc.EXPECT().
 			svc.EXPECT().
-				UserInfoQuery2Handler(mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, tc.input.snacOut).
+				UserInfoQuery2Handler(mock.Anything, mock.Anything, tc.input.snacOut).
 				Return(tc.output, tc.handlerErr).
 				Return(tc.output, tc.handlerErr).
 				Maybe()
 				Maybe()
 
 
@@ -503,7 +507,7 @@ func TestLocateRouter_RouteLocate(t *testing.T) {
 			bufOut := &bytes.Buffer{}
 			bufOut := &bytes.Buffer{}
 			seq := uint32(1)
 			seq := uint32(1)
 
 
-			err := router.RouteLocate(nil, nil, nil, nil, tc.input.snacFrame, bufIn, bufOut, &seq)
+			err := router.RouteLocate(nil, nil, tc.input.snacFrame, bufIn, bufOut, &seq)
 			assert.ErrorIs(t, err, tc.expectErr)
 			assert.ErrorIs(t, err, tc.expectErr)
 			if tc.expectErr != nil {
 			if tc.expectErr != nil {
 				return
 				return

+ 186 - 96
server/oservice.go

@@ -12,24 +12,26 @@ import (
 )
 )
 
 
 type OServiceHandler interface {
 type OServiceHandler interface {
-	WriteOServiceHostOnline(w io.Writer, sequence *uint32) error
-	ClientOnlineHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *Session, sm SessionManager, fm FeedbagManager, room ChatRoom) error
 	ClientVersionsHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x17_OServiceClientVersions) XMessage
 	ClientVersionsHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x17_OServiceClientVersions) XMessage
-	IdleNotificationHandler(ctx context.Context, sess *Session, sm SessionManager, fm *FeedbagStore, snacPayloadIn oscar.SNAC_0x01_0x11_OServiceIdleNotification) error
+	IdleNotificationHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x01_0x11_OServiceIdleNotification) error
 	RateParamsQueryHandler(ctx context.Context) XMessage
 	RateParamsQueryHandler(ctx context.Context) XMessage
 	RateParamsSubAddHandler(context.Context, oscar.SNAC_0x01_0x08_OServiceRateParamsSubAdd)
 	RateParamsSubAddHandler(context.Context, oscar.SNAC_0x01_0x08_OServiceRateParamsSubAdd)
-	ServiceRequestHandler(ctx context.Context, cfg Config, cr *ChatRegistry, sess *Session, snacPayloadIn oscar.SNAC_0x01_0x04_OServiceServiceRequest) (XMessage, error)
-	SetUserInfoFieldsHandler(ctx context.Context, sess *Session, sm SessionManager, fm *FeedbagStore, snacPayloadIn oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (XMessage, error)
+	SetUserInfoFieldsHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (XMessage, error)
 	UserInfoQueryHandler(ctx context.Context, sess *Session) XMessage
 	UserInfoQueryHandler(ctx context.Context, sess *Session) XMessage
 }
 }
 
 
-func NewOServiceRouter(logger *slog.Logger) OServiceRouter {
-	return OServiceRouter{
-		OServiceHandler: OServiceService{},
-		RouteLogger: RouteLogger{
-			Logger: logger,
-		},
-	}
+type OServiceBOSHandler interface {
+	OServiceHandler
+	WriteOServiceHostOnline(w io.Writer, sequence *uint32) error
+	ServiceRequestHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x01_0x04_OServiceServiceRequest) (XMessage, error)
+	ClientOnlineHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *Session) error
+}
+
+type OServiceChatHandler interface {
+	OServiceHandler
+	WriteOServiceHostOnline(w io.Writer, sequence *uint32) error
+	ServiceRequestHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x01_0x04_OServiceServiceRequest) (XMessage, error)
+	ClientOnlineHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *Session, room ChatRoom) error
 }
 }
 
 
 type OServiceRouter struct {
 type OServiceRouter struct {
@@ -37,30 +39,8 @@ type OServiceRouter struct {
 	RouteLogger
 	RouteLogger
 }
 }
 
 
-func (rt OServiceRouter) RouteOService(ctx context.Context, cfg Config, cr *ChatRegistry, sm SessionManager, fm *FeedbagStore, sess *Session, room ChatRoom, SNACFrame oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
+func (rt OServiceRouter) RouteOService(ctx context.Context, sess *Session, SNACFrame oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
 	switch SNACFrame.SubGroup {
 	switch SNACFrame.SubGroup {
-	case oscar.OServiceClientOnline:
-		inSNAC := oscar.SNAC_0x01_0x02_OServiceClientOnline{}
-		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
-			return err
-		}
-		rt.Logger.InfoContext(ctx, "user signed on")
-		rt.logRequest(ctx, SNACFrame, inSNAC)
-		return rt.ClientOnlineHandler(ctx, inSNAC, sess, sm, fm, room)
-	case oscar.OServiceServiceRequest:
-		inSNAC := oscar.SNAC_0x01_0x04_OServiceServiceRequest{}
-		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
-			return err
-		}
-		outSNAC, err := rt.ServiceRequestHandler(ctx, cfg, cr, sess, inSNAC)
-		switch {
-		case errors.Is(err, ErrUnsupportedSubGroup):
-			return sendInvalidSNACErr(SNACFrame, w, sequence)
-		case err != nil:
-			return err
-		}
-		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.snacFrame, outSNAC.snacOut)
-		return writeOutSNAC(SNACFrame, outSNAC.snacFrame, outSNAC.snacOut, sequence, w)
 	case oscar.OServiceRateParamsQuery:
 	case oscar.OServiceRateParamsQuery:
 		outSNAC := rt.RateParamsQueryHandler(ctx)
 		outSNAC := rt.RateParamsQueryHandler(ctx)
 		rt.logRequestAndResponse(ctx, SNACFrame, nil, outSNAC.snacFrame, outSNAC.snacOut)
 		rt.logRequestAndResponse(ctx, SNACFrame, nil, outSNAC.snacFrame, outSNAC.snacOut)
@@ -83,7 +63,7 @@ func (rt OServiceRouter) RouteOService(ctx context.Context, cfg Config, cr *Chat
 			return err
 			return err
 		}
 		}
 		rt.logRequest(ctx, SNACFrame, inSNAC)
 		rt.logRequest(ctx, SNACFrame, inSNAC)
-		return rt.IdleNotificationHandler(ctx, sess, sm, fm, inSNAC)
+		return rt.IdleNotificationHandler(ctx, sess, inSNAC)
 	case oscar.OServiceClientVersions:
 	case oscar.OServiceClientVersions:
 		inSNAC := oscar.SNAC_0x01_0x17_OServiceClientVersions{}
 		inSNAC := oscar.SNAC_0x01_0x17_OServiceClientVersions{}
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
@@ -97,7 +77,7 @@ func (rt OServiceRouter) RouteOService(ctx context.Context, cfg Config, cr *Chat
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
 			return err
 			return err
 		}
 		}
-		outSNAC, err := rt.SetUserInfoFieldsHandler(ctx, sess, sm, fm, inSNAC)
+		outSNAC, err := rt.SetUserInfoFieldsHandler(ctx, sess, inSNAC)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
@@ -108,29 +88,101 @@ func (rt OServiceRouter) RouteOService(ctx context.Context, cfg Config, cr *Chat
 	}
 	}
 }
 }
 
 
-type OServiceService struct {
+func NewOServiceRouterForBOS(logger *slog.Logger, cfg Config, fm FeedbagManager, sm SessionManager, cr *ChatRegistry) OServiceBOSRouter {
+	oss := OServiceService{
+		cfg: cfg,
+		fm:  fm,
+		sm:  sm,
+	}
+	return OServiceBOSRouter{
+		OServiceRouter: OServiceRouter{
+			OServiceHandler: oss,
+			RouteLogger: RouteLogger{
+				Logger: logger,
+			},
+		},
+		OServiceBOSHandler: OServiceServiceForBOS{
+			OServiceService: oss,
+			cr:              cr,
+		},
+	}
 }
 }
 
 
-func (s OServiceService) WriteOServiceHostOnline(w io.Writer, sequence *uint32) error {
-	snacFrameOut := oscar.SnacFrame{
-		FoodGroup: oscar.OSERVICE,
-		SubGroup:  oscar.OServiceHostOnline,
+type OServiceBOSRouter struct {
+	OServiceRouter
+	OServiceBOSHandler
+}
+
+func (rt OServiceBOSRouter) RouteOService(ctx context.Context, sess *Session, SNACFrame oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
+	switch SNACFrame.SubGroup {
+	case oscar.OServiceServiceRequest:
+		inSNAC := oscar.SNAC_0x01_0x04_OServiceServiceRequest{}
+		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
+			return err
+		}
+		outSNAC, err := rt.ServiceRequestHandler(ctx, sess, inSNAC)
+		switch {
+		case errors.Is(err, ErrUnsupportedSubGroup):
+			return sendInvalidSNACErr(SNACFrame, w, sequence)
+		case err != nil:
+			return err
+		}
+		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.snacFrame, outSNAC.snacOut)
+		return writeOutSNAC(SNACFrame, outSNAC.snacFrame, outSNAC.snacOut, sequence, w)
+	case oscar.OServiceClientOnline:
+		inSNAC := oscar.SNAC_0x01_0x02_OServiceClientOnline{}
+		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
+			return err
+		}
+		rt.Logger.InfoContext(ctx, "user signed on")
+		rt.logRequest(ctx, SNACFrame, inSNAC)
+		return rt.OServiceBOSHandler.ClientOnlineHandler(ctx, inSNAC, sess)
+	default:
+		return rt.OServiceRouter.RouteOService(ctx, sess, SNACFrame, r, w, sequence)
 	}
 	}
-	snacPayloadOut := oscar.SNAC_0x01_0x03_OServiceHostOnline{
-		FoodGroups: []uint16{
-			oscar.ALERT,
-			oscar.BUDDY,
-			oscar.CHAT_NAV,
-			oscar.FEEDBAG,
-			oscar.ICBM,
-			oscar.LOCATE,
-			oscar.OSERVICE,
-		},
+}
+
+type OServiceChatRouter struct {
+	OServiceRouter
+	OServiceChatHandler
+}
+
+func (rt OServiceChatRouter) RouteOService(ctx context.Context, sess *Session, room ChatRoom, SNACFrame oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
+	switch SNACFrame.SubGroup {
+	case oscar.OServiceServiceRequest:
+		inSNAC := oscar.SNAC_0x01_0x04_OServiceServiceRequest{}
+		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
+			return err
+		}
+		outSNAC, err := rt.ServiceRequestHandler(ctx, sess, inSNAC)
+		switch {
+		case errors.Is(err, ErrUnsupportedSubGroup):
+			return sendInvalidSNACErr(SNACFrame, w, sequence)
+		case err != nil:
+			return err
+		}
+		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.snacFrame, outSNAC.snacOut)
+		return writeOutSNAC(SNACFrame, outSNAC.snacFrame, outSNAC.snacOut, sequence, w)
+	case oscar.OServiceClientOnline:
+		inSNAC := oscar.SNAC_0x01_0x02_OServiceClientOnline{}
+		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
+			return err
+		}
+		rt.Logger.InfoContext(ctx, "user signed on")
+		rt.logRequest(ctx, SNACFrame, inSNAC)
+		return rt.OServiceChatHandler.ClientOnlineHandler(ctx, inSNAC, sess, room)
+	default:
+		return rt.OServiceRouter.RouteOService(ctx, sess, SNACFrame, r, w, sequence)
 	}
 	}
-	return writeOutSNAC(oscar.SnacFrame{}, snacFrameOut, snacPayloadOut, sequence, w)
 }
 }
 
 
-func (s OServiceService) ClientVersionsHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x17_OServiceClientVersions) XMessage {
+type OServiceService struct {
+	cfg Config
+	fm  FeedbagManager
+	sm  SessionManager
+}
+
+func (s OServiceService) ClientVersionsHandler(_ context.Context, snacPayloadIn oscar.SNAC_0x01_0x17_OServiceClientVersions) XMessage {
 	return XMessage{
 	return XMessage{
 		snacFrame: oscar.SnacFrame{
 		snacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.OSERVICE,
 			FoodGroup: oscar.OSERVICE,
@@ -142,7 +194,7 @@ func (s OServiceService) ClientVersionsHandler(ctx context.Context, snacPayloadI
 	}
 	}
 }
 }
 
 
-func (s OServiceService) RateParamsQueryHandler(ctx context.Context) XMessage {
+func (s OServiceService) RateParamsQueryHandler(_ context.Context) XMessage {
 	snacFrameOut := oscar.SnacFrame{
 	snacFrameOut := oscar.SnacFrame{
 		FoodGroup: oscar.OSERVICE,
 		FoodGroup: oscar.OSERVICE,
 		SubGroup:  oscar.OServiceRateParamsReply,
 		SubGroup:  oscar.OServiceRateParamsReply,
@@ -209,7 +261,7 @@ func (s OServiceService) RateParamsQueryHandler(ctx context.Context) XMessage {
 	}
 	}
 }
 }
 
 
-func (s OServiceService) UserInfoQueryHandler(ctx context.Context, sess *Session) XMessage {
+func (s OServiceService) UserInfoQueryHandler(_ context.Context, sess *Session) XMessage {
 	return XMessage{
 	return XMessage{
 		snacFrame: oscar.SnacFrame{
 		snacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.OSERVICE,
 			FoodGroup: oscar.OSERVICE,
@@ -221,37 +273,17 @@ func (s OServiceService) UserInfoQueryHandler(ctx context.Context, sess *Session
 	}
 	}
 }
 }
 
 
-func (s OServiceService) ClientOnlineHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *Session, sm SessionManager, fm FeedbagManager, room ChatRoom) error {
-	if err := BroadcastArrival(ctx, sess, sm, fm); err != nil {
-		return err
-	}
-	buddies, err := fm.Buddies(sess.ScreenName)
-	if err != nil {
-		return err
-	}
-	for _, buddy := range buddies {
-		err := UnicastArrival(ctx, buddy, sess.ScreenName, sm)
-		switch {
-		case errors.Is(err, ErrSessNotFound):
-			continue
-		case err != nil:
-			return err
-		}
-	}
-	return nil
-}
-
-func (s OServiceService) SetUserInfoFieldsHandler(ctx context.Context, sess *Session, sm SessionManager, fm *FeedbagStore, snacPayloadIn oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (XMessage, error) {
+func (s OServiceService) SetUserInfoFieldsHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (XMessage, error) {
 	if status, hasStatus := snacPayloadIn.GetUint32(0x06); hasStatus {
 	if status, hasStatus := snacPayloadIn.GetUint32(0x06); hasStatus {
 		switch status {
 		switch status {
 		case 0x000:
 		case 0x000:
 			sess.SetInvisible(false)
 			sess.SetInvisible(false)
-			if err := BroadcastArrival(ctx, sess, sm, fm); err != nil {
+			if err := BroadcastArrival(ctx, sess, s.sm, s.fm); err != nil {
 				return XMessage{}, err
 				return XMessage{}, err
 			}
 			}
 		case 0x100:
 		case 0x100:
 			sess.SetInvisible(true)
 			sess.SetInvisible(true)
-			if err := BroadcastDeparture(ctx, sess, sm, fm); err != nil {
+			if err := BroadcastDeparture(ctx, sess, s.sm, s.fm); err != nil {
 				return XMessage{}, err
 				return XMessage{}, err
 			}
 			}
 		default:
 		default:
@@ -269,16 +301,26 @@ func (s OServiceService) SetUserInfoFieldsHandler(ctx context.Context, sess *Ses
 	}, nil
 	}, nil
 }
 }
 
 
-func (s OServiceService) IdleNotificationHandler(ctx context.Context, sess *Session, sm SessionManager, fm *FeedbagStore, snacPayloadIn oscar.SNAC_0x01_0x11_OServiceIdleNotification) error {
+func (s OServiceService) IdleNotificationHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x01_0x11_OServiceIdleNotification) error {
 	if snacPayloadIn.IdleTime == 0 {
 	if snacPayloadIn.IdleTime == 0 {
 		sess.SetActive()
 		sess.SetActive()
 	} else {
 	} else {
 		sess.SetIdle(time.Duration(snacPayloadIn.IdleTime) * time.Second)
 		sess.SetIdle(time.Duration(snacPayloadIn.IdleTime) * time.Second)
 	}
 	}
-	return BroadcastArrival(ctx, sess, sm, fm)
+	return BroadcastArrival(ctx, sess, s.sm, s.fm)
+}
+
+// RateParamsSubAddHandler exists to capture the SNAC input in unit tests to
+// verify it's correctly unmarshalled.
+func (s OServiceService) RateParamsSubAddHandler(context.Context, oscar.SNAC_0x01_0x08_OServiceRateParamsSubAdd) {
 }
 }
 
 
-func (s OServiceService) ServiceRequestHandler(ctx context.Context, cfg Config, cr *ChatRegistry, sess *Session, snacPayloadIn oscar.SNAC_0x01_0x04_OServiceServiceRequest) (XMessage, error) {
+type OServiceServiceForBOS struct {
+	OServiceService
+	cr *ChatRegistry
+}
+
+func (s OServiceServiceForBOS) ServiceRequestHandler(_ context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x01_0x04_OServiceServiceRequest) (XMessage, error) {
 	if snacPayloadIn.FoodGroup != oscar.CHAT {
 	if snacPayloadIn.FoodGroup != oscar.CHAT {
 		return XMessage{}, ErrUnsupportedSubGroup
 		return XMessage{}, ErrUnsupportedSubGroup
 	}
 	}
@@ -293,7 +335,7 @@ func (s OServiceService) ServiceRequestHandler(ctx context.Context, cfg Config,
 		return XMessage{}, err
 		return XMessage{}, err
 	}
 	}
 
 
-	room, err := cr.Retrieve(string(roomSnac.Cookie))
+	room, err := s.cr.Retrieve(string(roomSnac.Cookie))
 	if err != nil {
 	if err != nil {
 		return XMessage{}, ErrUnsupportedSubGroup
 		return XMessage{}, ErrUnsupportedSubGroup
 	}
 	}
@@ -307,7 +349,7 @@ func (s OServiceService) ServiceRequestHandler(ctx context.Context, cfg Config,
 		snacOut: oscar.SNAC_0x01_0x05_OServiceServiceResponse{
 		snacOut: oscar.SNAC_0x01_0x05_OServiceServiceResponse{
 			TLVRestBlock: oscar.TLVRestBlock{
 			TLVRestBlock: oscar.TLVRestBlock{
 				TLVList: oscar.TLVList{
 				TLVList: oscar.TLVList{
-					oscar.NewTLV(oscar.OServiceTLVTagsReconnectHere, Address(cfg.OSCARHost, cfg.ChatPort)),
+					oscar.NewTLV(oscar.OServiceTLVTagsReconnectHere, Address(s.cfg.OSCARHost, s.cfg.ChatPort)),
 					oscar.NewTLV(oscar.OServiceTLVTagsLoginCookie, ChatCookie{
 					oscar.NewTLV(oscar.OServiceTLVTagsLoginCookie, ChatCookie{
 						Cookie: []byte(room.Cookie),
 						Cookie: []byte(room.Cookie),
 						SessID: sess.ID,
 						SessID: sess.ID,
@@ -321,16 +363,60 @@ func (s OServiceService) ServiceRequestHandler(ctx context.Context, cfg Config,
 	}, nil
 	}, nil
 }
 }
 
 
-// RateParamsSubAddHandler exists to capture the SNAC input in unit tests to
-// verify it's correctly unmarshalled.
-func (s OServiceService) RateParamsSubAddHandler(context.Context, oscar.SNAC_0x01_0x08_OServiceRateParamsSubAdd) {
+func (s OServiceServiceForBOS) WriteOServiceHostOnline(w io.Writer, sequence *uint32) error {
+	snacFrameOut := oscar.SnacFrame{
+		FoodGroup: oscar.OSERVICE,
+		SubGroup:  oscar.OServiceHostOnline,
+	}
+	snacPayloadOut := oscar.SNAC_0x01_0x03_OServiceHostOnline{
+		FoodGroups: []uint16{
+			oscar.ALERT,
+			oscar.BUDDY,
+			oscar.CHAT_NAV,
+			oscar.FEEDBAG,
+			oscar.ICBM,
+			oscar.LOCATE,
+			oscar.OSERVICE,
+		},
+	}
+	return writeOutSNAC(oscar.SnacFrame{}, snacFrameOut, snacPayloadOut, sequence, w)
 }
 }
 
 
-func NewOServiceRouterForChat(logger *slog.Logger) OServiceRouter {
-	return OServiceRouter{
-		OServiceHandler: OServiceServiceForChat{},
-		RouteLogger: RouteLogger{
-			Logger: logger,
+func (s OServiceServiceForBOS) ClientOnlineHandler(ctx context.Context, _ oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *Session) error {
+	if err := BroadcastArrival(ctx, sess, s.sm, s.fm); err != nil {
+		return err
+	}
+	buddies, err := s.fm.Buddies(sess.ScreenName)
+	if err != nil {
+		return err
+	}
+	for _, buddy := range buddies {
+		err := UnicastArrival(ctx, buddy, sess.ScreenName, s.sm)
+		switch {
+		case errors.Is(err, ErrSessNotFound):
+			continue
+		case err != nil:
+			return err
+		}
+	}
+	return nil
+}
+
+func NewOServiceRouterForChat(logger *slog.Logger, cfg Config, fm FeedbagManager, sm SessionManager) OServiceChatRouter {
+	oss := OServiceService{
+		cfg: cfg,
+		fm:  fm,
+		sm:  sm,
+	}
+	return OServiceChatRouter{
+		OServiceRouter: OServiceRouter{
+			OServiceHandler: oss,
+			RouteLogger: RouteLogger{
+				Logger: logger,
+			},
+		},
+		OServiceChatHandler: OServiceServiceForChat{
+			OServiceService: oss,
 		},
 		},
 	}
 	}
 }
 }
@@ -340,6 +426,10 @@ type OServiceServiceForChat struct {
 	RouteLogger
 	RouteLogger
 }
 }
 
 
+func (s OServiceServiceForChat) ServiceRequestHandler(_ context.Context, _ *Session, _ oscar.SNAC_0x01_0x04_OServiceServiceRequest) (XMessage, error) {
+	return XMessage{}, ErrUnsupportedSubGroup
+}
+
 func (s OServiceServiceForChat) WriteOServiceHostOnline(w io.Writer, sequence *uint32) error {
 func (s OServiceServiceForChat) WriteOServiceHostOnline(w io.Writer, sequence *uint32) error {
 	snacFrameOut := oscar.SnacFrame{
 	snacFrameOut := oscar.SnacFrame{
 		FoodGroup: oscar.OSERVICE,
 		FoodGroup: oscar.OSERVICE,
@@ -351,9 +441,9 @@ func (s OServiceServiceForChat) WriteOServiceHostOnline(w io.Writer, sequence *u
 	return writeOutSNAC(oscar.SnacFrame{}, snacFrameOut, snacPayloadOut, sequence, w)
 	return writeOutSNAC(oscar.SnacFrame{}, snacFrameOut, snacPayloadOut, sequence, w)
 }
 }
 
 
-func (s OServiceServiceForChat) ClientOnlineHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *Session, sm SessionManager, fm FeedbagManager, room ChatRoom) error {
-	SendChatRoomInfoUpdate(ctx, sess, sm, room)
-	AlertUserJoined(ctx, sess, sm)
-	SetOnlineChatUsers(ctx, sess, sm)
+func (s OServiceServiceForChat) ClientOnlineHandler(ctx context.Context, _ oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *Session, room ChatRoom) error {
+	SendChatRoomInfoUpdate(ctx, sess, room)
+	AlertUserJoined(ctx, sess, room)
+	SetOnlineChatUsers(ctx, sess, room)
 	return nil
 	return nil
 }
 }

+ 440 - 0
server/oservice_bos_mock.go

@@ -0,0 +1,440 @@
+// Code generated by mockery v2.35.2. DO NOT EDIT.
+
+package server
+
+import (
+	context "context"
+	io "io"
+
+	mock "github.com/stretchr/testify/mock"
+
+	oscar "github.com/mkaminski/goaim/oscar"
+)
+
+// MockOServiceBOSHandler is an autogenerated mock type for the OServiceBOSHandler type
+type MockOServiceBOSHandler struct {
+	mock.Mock
+}
+
+type MockOServiceBOSHandler_Expecter struct {
+	mock *mock.Mock
+}
+
+func (_m *MockOServiceBOSHandler) EXPECT() *MockOServiceBOSHandler_Expecter {
+	return &MockOServiceBOSHandler_Expecter{mock: &_m.Mock}
+}
+
+// ClientOnlineHandler provides a mock function with given fields: ctx, snacPayloadIn, sess
+func (_m *MockOServiceBOSHandler) ClientOnlineHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *Session) error {
+	ret := _m.Called(ctx, snacPayloadIn, sess)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, oscar.SNAC_0x01_0x02_OServiceClientOnline, *Session) error); ok {
+		r0 = rf(ctx, snacPayloadIn, sess)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// MockOServiceBOSHandler_ClientOnlineHandler_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ClientOnlineHandler'
+type MockOServiceBOSHandler_ClientOnlineHandler_Call struct {
+	*mock.Call
+}
+
+// ClientOnlineHandler is a helper method to define mock.On call
+//   - ctx context.Context
+//   - snacPayloadIn oscar.SNAC_0x01_0x02_OServiceClientOnline
+//   - sess *Session
+func (_e *MockOServiceBOSHandler_Expecter) ClientOnlineHandler(ctx interface{}, snacPayloadIn interface{}, sess interface{}) *MockOServiceBOSHandler_ClientOnlineHandler_Call {
+	return &MockOServiceBOSHandler_ClientOnlineHandler_Call{Call: _e.mock.On("ClientOnlineHandler", ctx, snacPayloadIn, sess)}
+}
+
+func (_c *MockOServiceBOSHandler_ClientOnlineHandler_Call) Run(run func(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *Session)) *MockOServiceBOSHandler_ClientOnlineHandler_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context), args[1].(oscar.SNAC_0x01_0x02_OServiceClientOnline), args[2].(*Session))
+	})
+	return _c
+}
+
+func (_c *MockOServiceBOSHandler_ClientOnlineHandler_Call) Return(_a0 error) *MockOServiceBOSHandler_ClientOnlineHandler_Call {
+	_c.Call.Return(_a0)
+	return _c
+}
+
+func (_c *MockOServiceBOSHandler_ClientOnlineHandler_Call) RunAndReturn(run func(context.Context, oscar.SNAC_0x01_0x02_OServiceClientOnline, *Session) error) *MockOServiceBOSHandler_ClientOnlineHandler_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// ClientVersionsHandler provides a mock function with given fields: ctx, snacPayloadIn
+func (_m *MockOServiceBOSHandler) ClientVersionsHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x17_OServiceClientVersions) XMessage {
+	ret := _m.Called(ctx, snacPayloadIn)
+
+	var r0 XMessage
+	if rf, ok := ret.Get(0).(func(context.Context, oscar.SNAC_0x01_0x17_OServiceClientVersions) XMessage); ok {
+		r0 = rf(ctx, snacPayloadIn)
+	} else {
+		r0 = ret.Get(0).(XMessage)
+	}
+
+	return r0
+}
+
+// MockOServiceBOSHandler_ClientVersionsHandler_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ClientVersionsHandler'
+type MockOServiceBOSHandler_ClientVersionsHandler_Call struct {
+	*mock.Call
+}
+
+// ClientVersionsHandler is a helper method to define mock.On call
+//   - ctx context.Context
+//   - snacPayloadIn oscar.SNAC_0x01_0x17_OServiceClientVersions
+func (_e *MockOServiceBOSHandler_Expecter) ClientVersionsHandler(ctx interface{}, snacPayloadIn interface{}) *MockOServiceBOSHandler_ClientVersionsHandler_Call {
+	return &MockOServiceBOSHandler_ClientVersionsHandler_Call{Call: _e.mock.On("ClientVersionsHandler", ctx, snacPayloadIn)}
+}
+
+func (_c *MockOServiceBOSHandler_ClientVersionsHandler_Call) Run(run func(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x17_OServiceClientVersions)) *MockOServiceBOSHandler_ClientVersionsHandler_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context), args[1].(oscar.SNAC_0x01_0x17_OServiceClientVersions))
+	})
+	return _c
+}
+
+func (_c *MockOServiceBOSHandler_ClientVersionsHandler_Call) Return(_a0 XMessage) *MockOServiceBOSHandler_ClientVersionsHandler_Call {
+	_c.Call.Return(_a0)
+	return _c
+}
+
+func (_c *MockOServiceBOSHandler_ClientVersionsHandler_Call) RunAndReturn(run func(context.Context, oscar.SNAC_0x01_0x17_OServiceClientVersions) XMessage) *MockOServiceBOSHandler_ClientVersionsHandler_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// IdleNotificationHandler provides a mock function with given fields: ctx, sess, snacPayloadIn
+func (_m *MockOServiceBOSHandler) IdleNotificationHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x01_0x11_OServiceIdleNotification) error {
+	ret := _m.Called(ctx, sess, snacPayloadIn)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x01_0x11_OServiceIdleNotification) error); ok {
+		r0 = rf(ctx, sess, snacPayloadIn)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// MockOServiceBOSHandler_IdleNotificationHandler_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IdleNotificationHandler'
+type MockOServiceBOSHandler_IdleNotificationHandler_Call struct {
+	*mock.Call
+}
+
+// IdleNotificationHandler is a helper method to define mock.On call
+//   - ctx context.Context
+//   - sess *Session
+//   - snacPayloadIn oscar.SNAC_0x01_0x11_OServiceIdleNotification
+func (_e *MockOServiceBOSHandler_Expecter) IdleNotificationHandler(ctx interface{}, sess interface{}, snacPayloadIn interface{}) *MockOServiceBOSHandler_IdleNotificationHandler_Call {
+	return &MockOServiceBOSHandler_IdleNotificationHandler_Call{Call: _e.mock.On("IdleNotificationHandler", ctx, sess, snacPayloadIn)}
+}
+
+func (_c *MockOServiceBOSHandler_IdleNotificationHandler_Call) Run(run func(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x01_0x11_OServiceIdleNotification)) *MockOServiceBOSHandler_IdleNotificationHandler_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context), args[1].(*Session), args[2].(oscar.SNAC_0x01_0x11_OServiceIdleNotification))
+	})
+	return _c
+}
+
+func (_c *MockOServiceBOSHandler_IdleNotificationHandler_Call) Return(_a0 error) *MockOServiceBOSHandler_IdleNotificationHandler_Call {
+	_c.Call.Return(_a0)
+	return _c
+}
+
+func (_c *MockOServiceBOSHandler_IdleNotificationHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x01_0x11_OServiceIdleNotification) error) *MockOServiceBOSHandler_IdleNotificationHandler_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// RateParamsQueryHandler provides a mock function with given fields: ctx
+func (_m *MockOServiceBOSHandler) RateParamsQueryHandler(ctx context.Context) XMessage {
+	ret := _m.Called(ctx)
+
+	var r0 XMessage
+	if rf, ok := ret.Get(0).(func(context.Context) XMessage); ok {
+		r0 = rf(ctx)
+	} else {
+		r0 = ret.Get(0).(XMessage)
+	}
+
+	return r0
+}
+
+// MockOServiceBOSHandler_RateParamsQueryHandler_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RateParamsQueryHandler'
+type MockOServiceBOSHandler_RateParamsQueryHandler_Call struct {
+	*mock.Call
+}
+
+// RateParamsQueryHandler is a helper method to define mock.On call
+//   - ctx context.Context
+func (_e *MockOServiceBOSHandler_Expecter) RateParamsQueryHandler(ctx interface{}) *MockOServiceBOSHandler_RateParamsQueryHandler_Call {
+	return &MockOServiceBOSHandler_RateParamsQueryHandler_Call{Call: _e.mock.On("RateParamsQueryHandler", ctx)}
+}
+
+func (_c *MockOServiceBOSHandler_RateParamsQueryHandler_Call) Run(run func(ctx context.Context)) *MockOServiceBOSHandler_RateParamsQueryHandler_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context))
+	})
+	return _c
+}
+
+func (_c *MockOServiceBOSHandler_RateParamsQueryHandler_Call) Return(_a0 XMessage) *MockOServiceBOSHandler_RateParamsQueryHandler_Call {
+	_c.Call.Return(_a0)
+	return _c
+}
+
+func (_c *MockOServiceBOSHandler_RateParamsQueryHandler_Call) RunAndReturn(run func(context.Context) XMessage) *MockOServiceBOSHandler_RateParamsQueryHandler_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// RateParamsSubAddHandler provides a mock function with given fields: _a0, _a1
+func (_m *MockOServiceBOSHandler) RateParamsSubAddHandler(_a0 context.Context, _a1 oscar.SNAC_0x01_0x08_OServiceRateParamsSubAdd) {
+	_m.Called(_a0, _a1)
+}
+
+// MockOServiceBOSHandler_RateParamsSubAddHandler_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RateParamsSubAddHandler'
+type MockOServiceBOSHandler_RateParamsSubAddHandler_Call struct {
+	*mock.Call
+}
+
+// RateParamsSubAddHandler is a helper method to define mock.On call
+//   - _a0 context.Context
+//   - _a1 oscar.SNAC_0x01_0x08_OServiceRateParamsSubAdd
+func (_e *MockOServiceBOSHandler_Expecter) RateParamsSubAddHandler(_a0 interface{}, _a1 interface{}) *MockOServiceBOSHandler_RateParamsSubAddHandler_Call {
+	return &MockOServiceBOSHandler_RateParamsSubAddHandler_Call{Call: _e.mock.On("RateParamsSubAddHandler", _a0, _a1)}
+}
+
+func (_c *MockOServiceBOSHandler_RateParamsSubAddHandler_Call) Run(run func(_a0 context.Context, _a1 oscar.SNAC_0x01_0x08_OServiceRateParamsSubAdd)) *MockOServiceBOSHandler_RateParamsSubAddHandler_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context), args[1].(oscar.SNAC_0x01_0x08_OServiceRateParamsSubAdd))
+	})
+	return _c
+}
+
+func (_c *MockOServiceBOSHandler_RateParamsSubAddHandler_Call) Return() *MockOServiceBOSHandler_RateParamsSubAddHandler_Call {
+	_c.Call.Return()
+	return _c
+}
+
+func (_c *MockOServiceBOSHandler_RateParamsSubAddHandler_Call) RunAndReturn(run func(context.Context, oscar.SNAC_0x01_0x08_OServiceRateParamsSubAdd)) *MockOServiceBOSHandler_RateParamsSubAddHandler_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// ServiceRequestHandler provides a mock function with given fields: ctx, sess, snacPayloadIn
+func (_m *MockOServiceBOSHandler) ServiceRequestHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x01_0x04_OServiceServiceRequest) (XMessage, error) {
+	ret := _m.Called(ctx, sess, snacPayloadIn)
+
+	var r0 XMessage
+	var r1 error
+	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x01_0x04_OServiceServiceRequest) (XMessage, error)); ok {
+		return rf(ctx, sess, snacPayloadIn)
+	}
+	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x01_0x04_OServiceServiceRequest) XMessage); ok {
+		r0 = rf(ctx, sess, snacPayloadIn)
+	} else {
+		r0 = ret.Get(0).(XMessage)
+	}
+
+	if rf, ok := ret.Get(1).(func(context.Context, *Session, oscar.SNAC_0x01_0x04_OServiceServiceRequest) error); ok {
+		r1 = rf(ctx, sess, snacPayloadIn)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// MockOServiceBOSHandler_ServiceRequestHandler_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ServiceRequestHandler'
+type MockOServiceBOSHandler_ServiceRequestHandler_Call struct {
+	*mock.Call
+}
+
+// ServiceRequestHandler is a helper method to define mock.On call
+//   - ctx context.Context
+//   - sess *Session
+//   - snacPayloadIn oscar.SNAC_0x01_0x04_OServiceServiceRequest
+func (_e *MockOServiceBOSHandler_Expecter) ServiceRequestHandler(ctx interface{}, sess interface{}, snacPayloadIn interface{}) *MockOServiceBOSHandler_ServiceRequestHandler_Call {
+	return &MockOServiceBOSHandler_ServiceRequestHandler_Call{Call: _e.mock.On("ServiceRequestHandler", ctx, sess, snacPayloadIn)}
+}
+
+func (_c *MockOServiceBOSHandler_ServiceRequestHandler_Call) Run(run func(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x01_0x04_OServiceServiceRequest)) *MockOServiceBOSHandler_ServiceRequestHandler_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context), args[1].(*Session), args[2].(oscar.SNAC_0x01_0x04_OServiceServiceRequest))
+	})
+	return _c
+}
+
+func (_c *MockOServiceBOSHandler_ServiceRequestHandler_Call) Return(_a0 XMessage, _a1 error) *MockOServiceBOSHandler_ServiceRequestHandler_Call {
+	_c.Call.Return(_a0, _a1)
+	return _c
+}
+
+func (_c *MockOServiceBOSHandler_ServiceRequestHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x01_0x04_OServiceServiceRequest) (XMessage, error)) *MockOServiceBOSHandler_ServiceRequestHandler_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// SetUserInfoFieldsHandler provides a mock function with given fields: ctx, sess, snacPayloadIn
+func (_m *MockOServiceBOSHandler) SetUserInfoFieldsHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (XMessage, error) {
+	ret := _m.Called(ctx, sess, snacPayloadIn)
+
+	var r0 XMessage
+	var r1 error
+	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (XMessage, error)); ok {
+		return rf(ctx, sess, snacPayloadIn)
+	}
+	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) XMessage); ok {
+		r0 = rf(ctx, sess, snacPayloadIn)
+	} else {
+		r0 = ret.Get(0).(XMessage)
+	}
+
+	if rf, ok := ret.Get(1).(func(context.Context, *Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) error); ok {
+		r1 = rf(ctx, sess, snacPayloadIn)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// MockOServiceBOSHandler_SetUserInfoFieldsHandler_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetUserInfoFieldsHandler'
+type MockOServiceBOSHandler_SetUserInfoFieldsHandler_Call struct {
+	*mock.Call
+}
+
+// SetUserInfoFieldsHandler is a helper method to define mock.On call
+//   - ctx context.Context
+//   - sess *Session
+//   - snacPayloadIn oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields
+func (_e *MockOServiceBOSHandler_Expecter) SetUserInfoFieldsHandler(ctx interface{}, sess interface{}, snacPayloadIn interface{}) *MockOServiceBOSHandler_SetUserInfoFieldsHandler_Call {
+	return &MockOServiceBOSHandler_SetUserInfoFieldsHandler_Call{Call: _e.mock.On("SetUserInfoFieldsHandler", ctx, sess, snacPayloadIn)}
+}
+
+func (_c *MockOServiceBOSHandler_SetUserInfoFieldsHandler_Call) Run(run func(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields)) *MockOServiceBOSHandler_SetUserInfoFieldsHandler_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context), args[1].(*Session), args[2].(oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields))
+	})
+	return _c
+}
+
+func (_c *MockOServiceBOSHandler_SetUserInfoFieldsHandler_Call) Return(_a0 XMessage, _a1 error) *MockOServiceBOSHandler_SetUserInfoFieldsHandler_Call {
+	_c.Call.Return(_a0, _a1)
+	return _c
+}
+
+func (_c *MockOServiceBOSHandler_SetUserInfoFieldsHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (XMessage, error)) *MockOServiceBOSHandler_SetUserInfoFieldsHandler_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// UserInfoQueryHandler provides a mock function with given fields: ctx, sess
+func (_m *MockOServiceBOSHandler) UserInfoQueryHandler(ctx context.Context, sess *Session) XMessage {
+	ret := _m.Called(ctx, sess)
+
+	var r0 XMessage
+	if rf, ok := ret.Get(0).(func(context.Context, *Session) XMessage); ok {
+		r0 = rf(ctx, sess)
+	} else {
+		r0 = ret.Get(0).(XMessage)
+	}
+
+	return r0
+}
+
+// MockOServiceBOSHandler_UserInfoQueryHandler_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UserInfoQueryHandler'
+type MockOServiceBOSHandler_UserInfoQueryHandler_Call struct {
+	*mock.Call
+}
+
+// UserInfoQueryHandler is a helper method to define mock.On call
+//   - ctx context.Context
+//   - sess *Session
+func (_e *MockOServiceBOSHandler_Expecter) UserInfoQueryHandler(ctx interface{}, sess interface{}) *MockOServiceBOSHandler_UserInfoQueryHandler_Call {
+	return &MockOServiceBOSHandler_UserInfoQueryHandler_Call{Call: _e.mock.On("UserInfoQueryHandler", ctx, sess)}
+}
+
+func (_c *MockOServiceBOSHandler_UserInfoQueryHandler_Call) Run(run func(ctx context.Context, sess *Session)) *MockOServiceBOSHandler_UserInfoQueryHandler_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context), args[1].(*Session))
+	})
+	return _c
+}
+
+func (_c *MockOServiceBOSHandler_UserInfoQueryHandler_Call) Return(_a0 XMessage) *MockOServiceBOSHandler_UserInfoQueryHandler_Call {
+	_c.Call.Return(_a0)
+	return _c
+}
+
+func (_c *MockOServiceBOSHandler_UserInfoQueryHandler_Call) RunAndReturn(run func(context.Context, *Session) XMessage) *MockOServiceBOSHandler_UserInfoQueryHandler_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// WriteOServiceHostOnline provides a mock function with given fields: w, sequence
+func (_m *MockOServiceBOSHandler) WriteOServiceHostOnline(w io.Writer, sequence *uint32) error {
+	ret := _m.Called(w, sequence)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(io.Writer, *uint32) error); ok {
+		r0 = rf(w, sequence)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// MockOServiceBOSHandler_WriteOServiceHostOnline_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WriteOServiceHostOnline'
+type MockOServiceBOSHandler_WriteOServiceHostOnline_Call struct {
+	*mock.Call
+}
+
+// WriteOServiceHostOnline is a helper method to define mock.On call
+//   - w io.Writer
+//   - sequence *uint32
+func (_e *MockOServiceBOSHandler_Expecter) WriteOServiceHostOnline(w interface{}, sequence interface{}) *MockOServiceBOSHandler_WriteOServiceHostOnline_Call {
+	return &MockOServiceBOSHandler_WriteOServiceHostOnline_Call{Call: _e.mock.On("WriteOServiceHostOnline", w, sequence)}
+}
+
+func (_c *MockOServiceBOSHandler_WriteOServiceHostOnline_Call) Run(run func(w io.Writer, sequence *uint32)) *MockOServiceBOSHandler_WriteOServiceHostOnline_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(io.Writer), args[1].(*uint32))
+	})
+	return _c
+}
+
+func (_c *MockOServiceBOSHandler_WriteOServiceHostOnline_Call) Return(_a0 error) *MockOServiceBOSHandler_WriteOServiceHostOnline_Call {
+	_c.Call.Return(_a0)
+	return _c
+}
+
+func (_c *MockOServiceBOSHandler_WriteOServiceHostOnline_Call) RunAndReturn(run func(io.Writer, *uint32) error) *MockOServiceBOSHandler_WriteOServiceHostOnline_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// NewMockOServiceBOSHandler creates a new instance of MockOServiceBOSHandler. 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 NewMockOServiceBOSHandler(t interface {
+	mock.TestingT
+	Cleanup(func())
+}) *MockOServiceBOSHandler {
+	mock := &MockOServiceBOSHandler{}
+	mock.Mock.Test(t)
+
+	t.Cleanup(func() { mock.AssertExpectations(t) })
+
+	return mock
+}

+ 441 - 0
server/oservice_chat_mock.go

@@ -0,0 +1,441 @@
+// Code generated by mockery v2.35.2. DO NOT EDIT.
+
+package server
+
+import (
+	context "context"
+	io "io"
+
+	mock "github.com/stretchr/testify/mock"
+
+	oscar "github.com/mkaminski/goaim/oscar"
+)
+
+// MockOServiceChatHandler is an autogenerated mock type for the OServiceChatHandler type
+type MockOServiceChatHandler struct {
+	mock.Mock
+}
+
+type MockOServiceChatHandler_Expecter struct {
+	mock *mock.Mock
+}
+
+func (_m *MockOServiceChatHandler) EXPECT() *MockOServiceChatHandler_Expecter {
+	return &MockOServiceChatHandler_Expecter{mock: &_m.Mock}
+}
+
+// ClientOnlineHandler provides a mock function with given fields: ctx, snacPayloadIn, sess, room
+func (_m *MockOServiceChatHandler) ClientOnlineHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *Session, room ChatRoom) error {
+	ret := _m.Called(ctx, snacPayloadIn, sess, room)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, oscar.SNAC_0x01_0x02_OServiceClientOnline, *Session, ChatRoom) error); ok {
+		r0 = rf(ctx, snacPayloadIn, sess, room)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// MockOServiceChatHandler_ClientOnlineHandler_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ClientOnlineHandler'
+type MockOServiceChatHandler_ClientOnlineHandler_Call struct {
+	*mock.Call
+}
+
+// ClientOnlineHandler is a helper method to define mock.On call
+//   - ctx context.Context
+//   - snacPayloadIn oscar.SNAC_0x01_0x02_OServiceClientOnline
+//   - sess *Session
+//   - room ChatRoom
+func (_e *MockOServiceChatHandler_Expecter) ClientOnlineHandler(ctx interface{}, snacPayloadIn interface{}, sess interface{}, room interface{}) *MockOServiceChatHandler_ClientOnlineHandler_Call {
+	return &MockOServiceChatHandler_ClientOnlineHandler_Call{Call: _e.mock.On("ClientOnlineHandler", ctx, snacPayloadIn, sess, room)}
+}
+
+func (_c *MockOServiceChatHandler_ClientOnlineHandler_Call) Run(run func(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *Session, room ChatRoom)) *MockOServiceChatHandler_ClientOnlineHandler_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context), args[1].(oscar.SNAC_0x01_0x02_OServiceClientOnline), args[2].(*Session), args[3].(ChatRoom))
+	})
+	return _c
+}
+
+func (_c *MockOServiceChatHandler_ClientOnlineHandler_Call) Return(_a0 error) *MockOServiceChatHandler_ClientOnlineHandler_Call {
+	_c.Call.Return(_a0)
+	return _c
+}
+
+func (_c *MockOServiceChatHandler_ClientOnlineHandler_Call) RunAndReturn(run func(context.Context, oscar.SNAC_0x01_0x02_OServiceClientOnline, *Session, ChatRoom) error) *MockOServiceChatHandler_ClientOnlineHandler_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// ClientVersionsHandler provides a mock function with given fields: ctx, snacPayloadIn
+func (_m *MockOServiceChatHandler) ClientVersionsHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x17_OServiceClientVersions) XMessage {
+	ret := _m.Called(ctx, snacPayloadIn)
+
+	var r0 XMessage
+	if rf, ok := ret.Get(0).(func(context.Context, oscar.SNAC_0x01_0x17_OServiceClientVersions) XMessage); ok {
+		r0 = rf(ctx, snacPayloadIn)
+	} else {
+		r0 = ret.Get(0).(XMessage)
+	}
+
+	return r0
+}
+
+// MockOServiceChatHandler_ClientVersionsHandler_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ClientVersionsHandler'
+type MockOServiceChatHandler_ClientVersionsHandler_Call struct {
+	*mock.Call
+}
+
+// ClientVersionsHandler is a helper method to define mock.On call
+//   - ctx context.Context
+//   - snacPayloadIn oscar.SNAC_0x01_0x17_OServiceClientVersions
+func (_e *MockOServiceChatHandler_Expecter) ClientVersionsHandler(ctx interface{}, snacPayloadIn interface{}) *MockOServiceChatHandler_ClientVersionsHandler_Call {
+	return &MockOServiceChatHandler_ClientVersionsHandler_Call{Call: _e.mock.On("ClientVersionsHandler", ctx, snacPayloadIn)}
+}
+
+func (_c *MockOServiceChatHandler_ClientVersionsHandler_Call) Run(run func(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x17_OServiceClientVersions)) *MockOServiceChatHandler_ClientVersionsHandler_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context), args[1].(oscar.SNAC_0x01_0x17_OServiceClientVersions))
+	})
+	return _c
+}
+
+func (_c *MockOServiceChatHandler_ClientVersionsHandler_Call) Return(_a0 XMessage) *MockOServiceChatHandler_ClientVersionsHandler_Call {
+	_c.Call.Return(_a0)
+	return _c
+}
+
+func (_c *MockOServiceChatHandler_ClientVersionsHandler_Call) RunAndReturn(run func(context.Context, oscar.SNAC_0x01_0x17_OServiceClientVersions) XMessage) *MockOServiceChatHandler_ClientVersionsHandler_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// IdleNotificationHandler provides a mock function with given fields: ctx, sess, snacPayloadIn
+func (_m *MockOServiceChatHandler) IdleNotificationHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x01_0x11_OServiceIdleNotification) error {
+	ret := _m.Called(ctx, sess, snacPayloadIn)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x01_0x11_OServiceIdleNotification) error); ok {
+		r0 = rf(ctx, sess, snacPayloadIn)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// MockOServiceChatHandler_IdleNotificationHandler_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IdleNotificationHandler'
+type MockOServiceChatHandler_IdleNotificationHandler_Call struct {
+	*mock.Call
+}
+
+// IdleNotificationHandler is a helper method to define mock.On call
+//   - ctx context.Context
+//   - sess *Session
+//   - snacPayloadIn oscar.SNAC_0x01_0x11_OServiceIdleNotification
+func (_e *MockOServiceChatHandler_Expecter) IdleNotificationHandler(ctx interface{}, sess interface{}, snacPayloadIn interface{}) *MockOServiceChatHandler_IdleNotificationHandler_Call {
+	return &MockOServiceChatHandler_IdleNotificationHandler_Call{Call: _e.mock.On("IdleNotificationHandler", ctx, sess, snacPayloadIn)}
+}
+
+func (_c *MockOServiceChatHandler_IdleNotificationHandler_Call) Run(run func(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x01_0x11_OServiceIdleNotification)) *MockOServiceChatHandler_IdleNotificationHandler_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context), args[1].(*Session), args[2].(oscar.SNAC_0x01_0x11_OServiceIdleNotification))
+	})
+	return _c
+}
+
+func (_c *MockOServiceChatHandler_IdleNotificationHandler_Call) Return(_a0 error) *MockOServiceChatHandler_IdleNotificationHandler_Call {
+	_c.Call.Return(_a0)
+	return _c
+}
+
+func (_c *MockOServiceChatHandler_IdleNotificationHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x01_0x11_OServiceIdleNotification) error) *MockOServiceChatHandler_IdleNotificationHandler_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// RateParamsQueryHandler provides a mock function with given fields: ctx
+func (_m *MockOServiceChatHandler) RateParamsQueryHandler(ctx context.Context) XMessage {
+	ret := _m.Called(ctx)
+
+	var r0 XMessage
+	if rf, ok := ret.Get(0).(func(context.Context) XMessage); ok {
+		r0 = rf(ctx)
+	} else {
+		r0 = ret.Get(0).(XMessage)
+	}
+
+	return r0
+}
+
+// MockOServiceChatHandler_RateParamsQueryHandler_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RateParamsQueryHandler'
+type MockOServiceChatHandler_RateParamsQueryHandler_Call struct {
+	*mock.Call
+}
+
+// RateParamsQueryHandler is a helper method to define mock.On call
+//   - ctx context.Context
+func (_e *MockOServiceChatHandler_Expecter) RateParamsQueryHandler(ctx interface{}) *MockOServiceChatHandler_RateParamsQueryHandler_Call {
+	return &MockOServiceChatHandler_RateParamsQueryHandler_Call{Call: _e.mock.On("RateParamsQueryHandler", ctx)}
+}
+
+func (_c *MockOServiceChatHandler_RateParamsQueryHandler_Call) Run(run func(ctx context.Context)) *MockOServiceChatHandler_RateParamsQueryHandler_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context))
+	})
+	return _c
+}
+
+func (_c *MockOServiceChatHandler_RateParamsQueryHandler_Call) Return(_a0 XMessage) *MockOServiceChatHandler_RateParamsQueryHandler_Call {
+	_c.Call.Return(_a0)
+	return _c
+}
+
+func (_c *MockOServiceChatHandler_RateParamsQueryHandler_Call) RunAndReturn(run func(context.Context) XMessage) *MockOServiceChatHandler_RateParamsQueryHandler_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// RateParamsSubAddHandler provides a mock function with given fields: _a0, _a1
+func (_m *MockOServiceChatHandler) RateParamsSubAddHandler(_a0 context.Context, _a1 oscar.SNAC_0x01_0x08_OServiceRateParamsSubAdd) {
+	_m.Called(_a0, _a1)
+}
+
+// MockOServiceChatHandler_RateParamsSubAddHandler_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RateParamsSubAddHandler'
+type MockOServiceChatHandler_RateParamsSubAddHandler_Call struct {
+	*mock.Call
+}
+
+// RateParamsSubAddHandler is a helper method to define mock.On call
+//   - _a0 context.Context
+//   - _a1 oscar.SNAC_0x01_0x08_OServiceRateParamsSubAdd
+func (_e *MockOServiceChatHandler_Expecter) RateParamsSubAddHandler(_a0 interface{}, _a1 interface{}) *MockOServiceChatHandler_RateParamsSubAddHandler_Call {
+	return &MockOServiceChatHandler_RateParamsSubAddHandler_Call{Call: _e.mock.On("RateParamsSubAddHandler", _a0, _a1)}
+}
+
+func (_c *MockOServiceChatHandler_RateParamsSubAddHandler_Call) Run(run func(_a0 context.Context, _a1 oscar.SNAC_0x01_0x08_OServiceRateParamsSubAdd)) *MockOServiceChatHandler_RateParamsSubAddHandler_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context), args[1].(oscar.SNAC_0x01_0x08_OServiceRateParamsSubAdd))
+	})
+	return _c
+}
+
+func (_c *MockOServiceChatHandler_RateParamsSubAddHandler_Call) Return() *MockOServiceChatHandler_RateParamsSubAddHandler_Call {
+	_c.Call.Return()
+	return _c
+}
+
+func (_c *MockOServiceChatHandler_RateParamsSubAddHandler_Call) RunAndReturn(run func(context.Context, oscar.SNAC_0x01_0x08_OServiceRateParamsSubAdd)) *MockOServiceChatHandler_RateParamsSubAddHandler_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// ServiceRequestHandler provides a mock function with given fields: ctx, sess, snacPayloadIn
+func (_m *MockOServiceChatHandler) ServiceRequestHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x01_0x04_OServiceServiceRequest) (XMessage, error) {
+	ret := _m.Called(ctx, sess, snacPayloadIn)
+
+	var r0 XMessage
+	var r1 error
+	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x01_0x04_OServiceServiceRequest) (XMessage, error)); ok {
+		return rf(ctx, sess, snacPayloadIn)
+	}
+	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x01_0x04_OServiceServiceRequest) XMessage); ok {
+		r0 = rf(ctx, sess, snacPayloadIn)
+	} else {
+		r0 = ret.Get(0).(XMessage)
+	}
+
+	if rf, ok := ret.Get(1).(func(context.Context, *Session, oscar.SNAC_0x01_0x04_OServiceServiceRequest) error); ok {
+		r1 = rf(ctx, sess, snacPayloadIn)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// MockOServiceChatHandler_ServiceRequestHandler_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ServiceRequestHandler'
+type MockOServiceChatHandler_ServiceRequestHandler_Call struct {
+	*mock.Call
+}
+
+// ServiceRequestHandler is a helper method to define mock.On call
+//   - ctx context.Context
+//   - sess *Session
+//   - snacPayloadIn oscar.SNAC_0x01_0x04_OServiceServiceRequest
+func (_e *MockOServiceChatHandler_Expecter) ServiceRequestHandler(ctx interface{}, sess interface{}, snacPayloadIn interface{}) *MockOServiceChatHandler_ServiceRequestHandler_Call {
+	return &MockOServiceChatHandler_ServiceRequestHandler_Call{Call: _e.mock.On("ServiceRequestHandler", ctx, sess, snacPayloadIn)}
+}
+
+func (_c *MockOServiceChatHandler_ServiceRequestHandler_Call) Run(run func(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x01_0x04_OServiceServiceRequest)) *MockOServiceChatHandler_ServiceRequestHandler_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context), args[1].(*Session), args[2].(oscar.SNAC_0x01_0x04_OServiceServiceRequest))
+	})
+	return _c
+}
+
+func (_c *MockOServiceChatHandler_ServiceRequestHandler_Call) Return(_a0 XMessage, _a1 error) *MockOServiceChatHandler_ServiceRequestHandler_Call {
+	_c.Call.Return(_a0, _a1)
+	return _c
+}
+
+func (_c *MockOServiceChatHandler_ServiceRequestHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x01_0x04_OServiceServiceRequest) (XMessage, error)) *MockOServiceChatHandler_ServiceRequestHandler_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// SetUserInfoFieldsHandler provides a mock function with given fields: ctx, sess, snacPayloadIn
+func (_m *MockOServiceChatHandler) SetUserInfoFieldsHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (XMessage, error) {
+	ret := _m.Called(ctx, sess, snacPayloadIn)
+
+	var r0 XMessage
+	var r1 error
+	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (XMessage, error)); ok {
+		return rf(ctx, sess, snacPayloadIn)
+	}
+	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) XMessage); ok {
+		r0 = rf(ctx, sess, snacPayloadIn)
+	} else {
+		r0 = ret.Get(0).(XMessage)
+	}
+
+	if rf, ok := ret.Get(1).(func(context.Context, *Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) error); ok {
+		r1 = rf(ctx, sess, snacPayloadIn)
+	} else {
+		r1 = ret.Error(1)
+	}
+
+	return r0, r1
+}
+
+// MockOServiceChatHandler_SetUserInfoFieldsHandler_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetUserInfoFieldsHandler'
+type MockOServiceChatHandler_SetUserInfoFieldsHandler_Call struct {
+	*mock.Call
+}
+
+// SetUserInfoFieldsHandler is a helper method to define mock.On call
+//   - ctx context.Context
+//   - sess *Session
+//   - snacPayloadIn oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields
+func (_e *MockOServiceChatHandler_Expecter) SetUserInfoFieldsHandler(ctx interface{}, sess interface{}, snacPayloadIn interface{}) *MockOServiceChatHandler_SetUserInfoFieldsHandler_Call {
+	return &MockOServiceChatHandler_SetUserInfoFieldsHandler_Call{Call: _e.mock.On("SetUserInfoFieldsHandler", ctx, sess, snacPayloadIn)}
+}
+
+func (_c *MockOServiceChatHandler_SetUserInfoFieldsHandler_Call) Run(run func(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields)) *MockOServiceChatHandler_SetUserInfoFieldsHandler_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context), args[1].(*Session), args[2].(oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields))
+	})
+	return _c
+}
+
+func (_c *MockOServiceChatHandler_SetUserInfoFieldsHandler_Call) Return(_a0 XMessage, _a1 error) *MockOServiceChatHandler_SetUserInfoFieldsHandler_Call {
+	_c.Call.Return(_a0, _a1)
+	return _c
+}
+
+func (_c *MockOServiceChatHandler_SetUserInfoFieldsHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (XMessage, error)) *MockOServiceChatHandler_SetUserInfoFieldsHandler_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// UserInfoQueryHandler provides a mock function with given fields: ctx, sess
+func (_m *MockOServiceChatHandler) UserInfoQueryHandler(ctx context.Context, sess *Session) XMessage {
+	ret := _m.Called(ctx, sess)
+
+	var r0 XMessage
+	if rf, ok := ret.Get(0).(func(context.Context, *Session) XMessage); ok {
+		r0 = rf(ctx, sess)
+	} else {
+		r0 = ret.Get(0).(XMessage)
+	}
+
+	return r0
+}
+
+// MockOServiceChatHandler_UserInfoQueryHandler_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UserInfoQueryHandler'
+type MockOServiceChatHandler_UserInfoQueryHandler_Call struct {
+	*mock.Call
+}
+
+// UserInfoQueryHandler is a helper method to define mock.On call
+//   - ctx context.Context
+//   - sess *Session
+func (_e *MockOServiceChatHandler_Expecter) UserInfoQueryHandler(ctx interface{}, sess interface{}) *MockOServiceChatHandler_UserInfoQueryHandler_Call {
+	return &MockOServiceChatHandler_UserInfoQueryHandler_Call{Call: _e.mock.On("UserInfoQueryHandler", ctx, sess)}
+}
+
+func (_c *MockOServiceChatHandler_UserInfoQueryHandler_Call) Run(run func(ctx context.Context, sess *Session)) *MockOServiceChatHandler_UserInfoQueryHandler_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(context.Context), args[1].(*Session))
+	})
+	return _c
+}
+
+func (_c *MockOServiceChatHandler_UserInfoQueryHandler_Call) Return(_a0 XMessage) *MockOServiceChatHandler_UserInfoQueryHandler_Call {
+	_c.Call.Return(_a0)
+	return _c
+}
+
+func (_c *MockOServiceChatHandler_UserInfoQueryHandler_Call) RunAndReturn(run func(context.Context, *Session) XMessage) *MockOServiceChatHandler_UserInfoQueryHandler_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// WriteOServiceHostOnline provides a mock function with given fields: w, sequence
+func (_m *MockOServiceChatHandler) WriteOServiceHostOnline(w io.Writer, sequence *uint32) error {
+	ret := _m.Called(w, sequence)
+
+	var r0 error
+	if rf, ok := ret.Get(0).(func(io.Writer, *uint32) error); ok {
+		r0 = rf(w, sequence)
+	} else {
+		r0 = ret.Error(0)
+	}
+
+	return r0
+}
+
+// MockOServiceChatHandler_WriteOServiceHostOnline_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WriteOServiceHostOnline'
+type MockOServiceChatHandler_WriteOServiceHostOnline_Call struct {
+	*mock.Call
+}
+
+// WriteOServiceHostOnline is a helper method to define mock.On call
+//   - w io.Writer
+//   - sequence *uint32
+func (_e *MockOServiceChatHandler_Expecter) WriteOServiceHostOnline(w interface{}, sequence interface{}) *MockOServiceChatHandler_WriteOServiceHostOnline_Call {
+	return &MockOServiceChatHandler_WriteOServiceHostOnline_Call{Call: _e.mock.On("WriteOServiceHostOnline", w, sequence)}
+}
+
+func (_c *MockOServiceChatHandler_WriteOServiceHostOnline_Call) Run(run func(w io.Writer, sequence *uint32)) *MockOServiceChatHandler_WriteOServiceHostOnline_Call {
+	_c.Call.Run(func(args mock.Arguments) {
+		run(args[0].(io.Writer), args[1].(*uint32))
+	})
+	return _c
+}
+
+func (_c *MockOServiceChatHandler_WriteOServiceHostOnline_Call) Return(_a0 error) *MockOServiceChatHandler_WriteOServiceHostOnline_Call {
+	_c.Call.Return(_a0)
+	return _c
+}
+
+func (_c *MockOServiceChatHandler_WriteOServiceHostOnline_Call) RunAndReturn(run func(io.Writer, *uint32) error) *MockOServiceChatHandler_WriteOServiceHostOnline_Call {
+	_c.Call.Return(run)
+	return _c
+}
+
+// NewMockOServiceChatHandler creates a new instance of MockOServiceChatHandler. 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 NewMockOServiceChatHandler(t interface {
+	mock.TestingT
+	Cleanup(func())
+}) *MockOServiceChatHandler {
+	mock := &MockOServiceChatHandler{}
+	mock.Mock.Test(t)
+
+	t.Cleanup(func() { mock.AssertExpectations(t) })
+
+	return mock
+}

+ 25 - 177
server/oservice_mock.go

@@ -4,11 +4,9 @@ package server
 
 
 import (
 import (
 	context "context"
 	context "context"
-	io "io"
-
-	mock "github.com/stretchr/testify/mock"
 
 
 	oscar "github.com/mkaminski/goaim/oscar"
 	oscar "github.com/mkaminski/goaim/oscar"
+	mock "github.com/stretchr/testify/mock"
 )
 )
 
 
 // MockOServiceHandler is an autogenerated mock type for the OServiceHandler type
 // MockOServiceHandler is an autogenerated mock type for the OServiceHandler type
@@ -24,53 +22,6 @@ func (_m *MockOServiceHandler) EXPECT() *MockOServiceHandler_Expecter {
 	return &MockOServiceHandler_Expecter{mock: &_m.Mock}
 	return &MockOServiceHandler_Expecter{mock: &_m.Mock}
 }
 }
 
 
-// ClientOnlineHandler provides a mock function with given fields: ctx, snacPayloadIn, sess, sm, fm, room
-func (_m *MockOServiceHandler) ClientOnlineHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *Session, sm SessionManager, fm FeedbagManager, room ChatRoom) error {
-	ret := _m.Called(ctx, snacPayloadIn, sess, sm, fm, room)
-
-	var r0 error
-	if rf, ok := ret.Get(0).(func(context.Context, oscar.SNAC_0x01_0x02_OServiceClientOnline, *Session, SessionManager, FeedbagManager, ChatRoom) error); ok {
-		r0 = rf(ctx, snacPayloadIn, sess, sm, fm, room)
-	} else {
-		r0 = ret.Error(0)
-	}
-
-	return r0
-}
-
-// MockOServiceHandler_ClientOnlineHandler_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ClientOnlineHandler'
-type MockOServiceHandler_ClientOnlineHandler_Call struct {
-	*mock.Call
-}
-
-// ClientOnlineHandler is a helper method to define mock.On call
-//   - ctx context.Context
-//   - snacPayloadIn oscar.SNAC_0x01_0x02_OServiceClientOnline
-//   - sess *Session
-//   - sm SessionManager
-//   - fm FeedbagManager
-//   - room ChatRoom
-func (_e *MockOServiceHandler_Expecter) ClientOnlineHandler(ctx interface{}, snacPayloadIn interface{}, sess interface{}, sm interface{}, fm interface{}, room interface{}) *MockOServiceHandler_ClientOnlineHandler_Call {
-	return &MockOServiceHandler_ClientOnlineHandler_Call{Call: _e.mock.On("ClientOnlineHandler", ctx, snacPayloadIn, sess, sm, fm, room)}
-}
-
-func (_c *MockOServiceHandler_ClientOnlineHandler_Call) Run(run func(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *Session, sm SessionManager, fm FeedbagManager, room ChatRoom)) *MockOServiceHandler_ClientOnlineHandler_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(oscar.SNAC_0x01_0x02_OServiceClientOnline), args[2].(*Session), args[3].(SessionManager), args[4].(FeedbagManager), args[5].(ChatRoom))
-	})
-	return _c
-}
-
-func (_c *MockOServiceHandler_ClientOnlineHandler_Call) Return(_a0 error) *MockOServiceHandler_ClientOnlineHandler_Call {
-	_c.Call.Return(_a0)
-	return _c
-}
-
-func (_c *MockOServiceHandler_ClientOnlineHandler_Call) RunAndReturn(run func(context.Context, oscar.SNAC_0x01_0x02_OServiceClientOnline, *Session, SessionManager, FeedbagManager, ChatRoom) error) *MockOServiceHandler_ClientOnlineHandler_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
 // ClientVersionsHandler provides a mock function with given fields: ctx, snacPayloadIn
 // ClientVersionsHandler provides a mock function with given fields: ctx, snacPayloadIn
 func (_m *MockOServiceHandler) ClientVersionsHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x17_OServiceClientVersions) XMessage {
 func (_m *MockOServiceHandler) ClientVersionsHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x17_OServiceClientVersions) XMessage {
 	ret := _m.Called(ctx, snacPayloadIn)
 	ret := _m.Called(ctx, snacPayloadIn)
@@ -114,13 +65,13 @@ func (_c *MockOServiceHandler_ClientVersionsHandler_Call) RunAndReturn(run func(
 	return _c
 	return _c
 }
 }
 
 
-// IdleNotificationHandler provides a mock function with given fields: ctx, sess, sm, fm, snacPayloadIn
-func (_m *MockOServiceHandler) IdleNotificationHandler(ctx context.Context, sess *Session, sm SessionManager, fm *FeedbagStore, snacPayloadIn oscar.SNAC_0x01_0x11_OServiceIdleNotification) error {
-	ret := _m.Called(ctx, sess, sm, fm, snacPayloadIn)
+// IdleNotificationHandler provides a mock function with given fields: ctx, sess, snacPayloadIn
+func (_m *MockOServiceHandler) IdleNotificationHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x01_0x11_OServiceIdleNotification) error {
+	ret := _m.Called(ctx, sess, snacPayloadIn)
 
 
 	var r0 error
 	var r0 error
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, SessionManager, *FeedbagStore, oscar.SNAC_0x01_0x11_OServiceIdleNotification) error); ok {
-		r0 = rf(ctx, sess, sm, fm, snacPayloadIn)
+	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x01_0x11_OServiceIdleNotification) error); ok {
+		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
 	} else {
 		r0 = ret.Error(0)
 		r0 = ret.Error(0)
 	}
 	}
@@ -136,16 +87,14 @@ type MockOServiceHandler_IdleNotificationHandler_Call struct {
 // IdleNotificationHandler is a helper method to define mock.On call
 // IdleNotificationHandler is a helper method to define mock.On call
 //   - ctx context.Context
 //   - ctx context.Context
 //   - sess *Session
 //   - sess *Session
-//   - sm SessionManager
-//   - fm *FeedbagStore
 //   - snacPayloadIn oscar.SNAC_0x01_0x11_OServiceIdleNotification
 //   - snacPayloadIn oscar.SNAC_0x01_0x11_OServiceIdleNotification
-func (_e *MockOServiceHandler_Expecter) IdleNotificationHandler(ctx interface{}, sess interface{}, sm interface{}, fm interface{}, snacPayloadIn interface{}) *MockOServiceHandler_IdleNotificationHandler_Call {
-	return &MockOServiceHandler_IdleNotificationHandler_Call{Call: _e.mock.On("IdleNotificationHandler", ctx, sess, sm, fm, snacPayloadIn)}
+func (_e *MockOServiceHandler_Expecter) IdleNotificationHandler(ctx interface{}, sess interface{}, snacPayloadIn interface{}) *MockOServiceHandler_IdleNotificationHandler_Call {
+	return &MockOServiceHandler_IdleNotificationHandler_Call{Call: _e.mock.On("IdleNotificationHandler", ctx, sess, snacPayloadIn)}
 }
 }
 
 
-func (_c *MockOServiceHandler_IdleNotificationHandler_Call) Run(run func(ctx context.Context, sess *Session, sm SessionManager, fm *FeedbagStore, snacPayloadIn oscar.SNAC_0x01_0x11_OServiceIdleNotification)) *MockOServiceHandler_IdleNotificationHandler_Call {
+func (_c *MockOServiceHandler_IdleNotificationHandler_Call) Run(run func(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x01_0x11_OServiceIdleNotification)) *MockOServiceHandler_IdleNotificationHandler_Call {
 	_c.Call.Run(func(args mock.Arguments) {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(*Session), args[2].(SessionManager), args[3].(*FeedbagStore), args[4].(oscar.SNAC_0x01_0x11_OServiceIdleNotification))
+		run(args[0].(context.Context), args[1].(*Session), args[2].(oscar.SNAC_0x01_0x11_OServiceIdleNotification))
 	})
 	})
 	return _c
 	return _c
 }
 }
@@ -155,7 +104,7 @@ func (_c *MockOServiceHandler_IdleNotificationHandler_Call) Return(_a0 error) *M
 	return _c
 	return _c
 }
 }
 
 
-func (_c *MockOServiceHandler_IdleNotificationHandler_Call) RunAndReturn(run func(context.Context, *Session, SessionManager, *FeedbagStore, oscar.SNAC_0x01_0x11_OServiceIdleNotification) error) *MockOServiceHandler_IdleNotificationHandler_Call {
+func (_c *MockOServiceHandler_IdleNotificationHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x01_0x11_OServiceIdleNotification) error) *MockOServiceHandler_IdleNotificationHandler_Call {
 	_c.Call.Return(run)
 	_c.Call.Return(run)
 	return _c
 	return _c
 }
 }
@@ -236,79 +185,23 @@ func (_c *MockOServiceHandler_RateParamsSubAddHandler_Call) RunAndReturn(run fun
 	return _c
 	return _c
 }
 }
 
 
-// ServiceRequestHandler provides a mock function with given fields: ctx, cfg, cr, sess, snacPayloadIn
-func (_m *MockOServiceHandler) ServiceRequestHandler(ctx context.Context, cfg Config, cr *ChatRegistry, sess *Session, snacPayloadIn oscar.SNAC_0x01_0x04_OServiceServiceRequest) (XMessage, error) {
-	ret := _m.Called(ctx, cfg, cr, sess, snacPayloadIn)
-
-	var r0 XMessage
-	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, Config, *ChatRegistry, *Session, oscar.SNAC_0x01_0x04_OServiceServiceRequest) (XMessage, error)); ok {
-		return rf(ctx, cfg, cr, sess, snacPayloadIn)
-	}
-	if rf, ok := ret.Get(0).(func(context.Context, Config, *ChatRegistry, *Session, oscar.SNAC_0x01_0x04_OServiceServiceRequest) XMessage); ok {
-		r0 = rf(ctx, cfg, cr, sess, snacPayloadIn)
-	} else {
-		r0 = ret.Get(0).(XMessage)
-	}
-
-	if rf, ok := ret.Get(1).(func(context.Context, Config, *ChatRegistry, *Session, oscar.SNAC_0x01_0x04_OServiceServiceRequest) error); ok {
-		r1 = rf(ctx, cfg, cr, sess, snacPayloadIn)
-	} else {
-		r1 = ret.Error(1)
-	}
-
-	return r0, r1
-}
-
-// MockOServiceHandler_ServiceRequestHandler_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ServiceRequestHandler'
-type MockOServiceHandler_ServiceRequestHandler_Call struct {
-	*mock.Call
-}
-
-// ServiceRequestHandler is a helper method to define mock.On call
-//   - ctx context.Context
-//   - cfg Config
-//   - cr *ChatRegistry
-//   - sess *Session
-//   - snacPayloadIn oscar.SNAC_0x01_0x04_OServiceServiceRequest
-func (_e *MockOServiceHandler_Expecter) ServiceRequestHandler(ctx interface{}, cfg interface{}, cr interface{}, sess interface{}, snacPayloadIn interface{}) *MockOServiceHandler_ServiceRequestHandler_Call {
-	return &MockOServiceHandler_ServiceRequestHandler_Call{Call: _e.mock.On("ServiceRequestHandler", ctx, cfg, cr, sess, snacPayloadIn)}
-}
-
-func (_c *MockOServiceHandler_ServiceRequestHandler_Call) Run(run func(ctx context.Context, cfg Config, cr *ChatRegistry, sess *Session, snacPayloadIn oscar.SNAC_0x01_0x04_OServiceServiceRequest)) *MockOServiceHandler_ServiceRequestHandler_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(Config), args[2].(*ChatRegistry), args[3].(*Session), args[4].(oscar.SNAC_0x01_0x04_OServiceServiceRequest))
-	})
-	return _c
-}
-
-func (_c *MockOServiceHandler_ServiceRequestHandler_Call) Return(_a0 XMessage, _a1 error) *MockOServiceHandler_ServiceRequestHandler_Call {
-	_c.Call.Return(_a0, _a1)
-	return _c
-}
-
-func (_c *MockOServiceHandler_ServiceRequestHandler_Call) RunAndReturn(run func(context.Context, Config, *ChatRegistry, *Session, oscar.SNAC_0x01_0x04_OServiceServiceRequest) (XMessage, error)) *MockOServiceHandler_ServiceRequestHandler_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
-// SetUserInfoFieldsHandler provides a mock function with given fields: ctx, sess, sm, fm, snacPayloadIn
-func (_m *MockOServiceHandler) SetUserInfoFieldsHandler(ctx context.Context, sess *Session, sm SessionManager, fm *FeedbagStore, snacPayloadIn oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (XMessage, error) {
-	ret := _m.Called(ctx, sess, sm, fm, snacPayloadIn)
+// SetUserInfoFieldsHandler provides a mock function with given fields: ctx, sess, snacPayloadIn
+func (_m *MockOServiceHandler) SetUserInfoFieldsHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (XMessage, error) {
+	ret := _m.Called(ctx, sess, snacPayloadIn)
 
 
 	var r0 XMessage
 	var r0 XMessage
 	var r1 error
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, SessionManager, *FeedbagStore, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (XMessage, error)); ok {
-		return rf(ctx, sess, sm, fm, snacPayloadIn)
+	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (XMessage, error)); ok {
+		return rf(ctx, sess, snacPayloadIn)
 	}
 	}
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, SessionManager, *FeedbagStore, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) XMessage); ok {
-		r0 = rf(ctx, sess, sm, fm, snacPayloadIn)
+	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) XMessage); ok {
+		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
 	} else {
 		r0 = ret.Get(0).(XMessage)
 		r0 = ret.Get(0).(XMessage)
 	}
 	}
 
 
-	if rf, ok := ret.Get(1).(func(context.Context, *Session, SessionManager, *FeedbagStore, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) error); ok {
-		r1 = rf(ctx, sess, sm, fm, snacPayloadIn)
+	if rf, ok := ret.Get(1).(func(context.Context, *Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) error); ok {
+		r1 = rf(ctx, sess, snacPayloadIn)
 	} else {
 	} else {
 		r1 = ret.Error(1)
 		r1 = ret.Error(1)
 	}
 	}
@@ -324,16 +217,14 @@ type MockOServiceHandler_SetUserInfoFieldsHandler_Call struct {
 // SetUserInfoFieldsHandler is a helper method to define mock.On call
 // SetUserInfoFieldsHandler is a helper method to define mock.On call
 //   - ctx context.Context
 //   - ctx context.Context
 //   - sess *Session
 //   - sess *Session
-//   - sm SessionManager
-//   - fm *FeedbagStore
 //   - snacPayloadIn oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields
 //   - snacPayloadIn oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields
-func (_e *MockOServiceHandler_Expecter) SetUserInfoFieldsHandler(ctx interface{}, sess interface{}, sm interface{}, fm interface{}, snacPayloadIn interface{}) *MockOServiceHandler_SetUserInfoFieldsHandler_Call {
-	return &MockOServiceHandler_SetUserInfoFieldsHandler_Call{Call: _e.mock.On("SetUserInfoFieldsHandler", ctx, sess, sm, fm, snacPayloadIn)}
+func (_e *MockOServiceHandler_Expecter) SetUserInfoFieldsHandler(ctx interface{}, sess interface{}, snacPayloadIn interface{}) *MockOServiceHandler_SetUserInfoFieldsHandler_Call {
+	return &MockOServiceHandler_SetUserInfoFieldsHandler_Call{Call: _e.mock.On("SetUserInfoFieldsHandler", ctx, sess, snacPayloadIn)}
 }
 }
 
 
-func (_c *MockOServiceHandler_SetUserInfoFieldsHandler_Call) Run(run func(ctx context.Context, sess *Session, sm SessionManager, fm *FeedbagStore, snacPayloadIn oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields)) *MockOServiceHandler_SetUserInfoFieldsHandler_Call {
+func (_c *MockOServiceHandler_SetUserInfoFieldsHandler_Call) Run(run func(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields)) *MockOServiceHandler_SetUserInfoFieldsHandler_Call {
 	_c.Call.Run(func(args mock.Arguments) {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(*Session), args[2].(SessionManager), args[3].(*FeedbagStore), args[4].(oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields))
+		run(args[0].(context.Context), args[1].(*Session), args[2].(oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields))
 	})
 	})
 	return _c
 	return _c
 }
 }
@@ -343,7 +234,7 @@ func (_c *MockOServiceHandler_SetUserInfoFieldsHandler_Call) Return(_a0 XMessage
 	return _c
 	return _c
 }
 }
 
 
-func (_c *MockOServiceHandler_SetUserInfoFieldsHandler_Call) RunAndReturn(run func(context.Context, *Session, SessionManager, *FeedbagStore, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (XMessage, error)) *MockOServiceHandler_SetUserInfoFieldsHandler_Call {
+func (_c *MockOServiceHandler_SetUserInfoFieldsHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (XMessage, error)) *MockOServiceHandler_SetUserInfoFieldsHandler_Call {
 	_c.Call.Return(run)
 	_c.Call.Return(run)
 	return _c
 	return _c
 }
 }
@@ -391,49 +282,6 @@ func (_c *MockOServiceHandler_UserInfoQueryHandler_Call) RunAndReturn(run func(c
 	return _c
 	return _c
 }
 }
 
 
-// WriteOServiceHostOnline provides a mock function with given fields: w, sequence
-func (_m *MockOServiceHandler) WriteOServiceHostOnline(w io.Writer, sequence *uint32) error {
-	ret := _m.Called(w, sequence)
-
-	var r0 error
-	if rf, ok := ret.Get(0).(func(io.Writer, *uint32) error); ok {
-		r0 = rf(w, sequence)
-	} else {
-		r0 = ret.Error(0)
-	}
-
-	return r0
-}
-
-// MockOServiceHandler_WriteOServiceHostOnline_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WriteOServiceHostOnline'
-type MockOServiceHandler_WriteOServiceHostOnline_Call struct {
-	*mock.Call
-}
-
-// WriteOServiceHostOnline is a helper method to define mock.On call
-//   - w io.Writer
-//   - sequence *uint32
-func (_e *MockOServiceHandler_Expecter) WriteOServiceHostOnline(w interface{}, sequence interface{}) *MockOServiceHandler_WriteOServiceHostOnline_Call {
-	return &MockOServiceHandler_WriteOServiceHostOnline_Call{Call: _e.mock.On("WriteOServiceHostOnline", w, sequence)}
-}
-
-func (_c *MockOServiceHandler_WriteOServiceHostOnline_Call) Run(run func(w io.Writer, sequence *uint32)) *MockOServiceHandler_WriteOServiceHostOnline_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(io.Writer), args[1].(*uint32))
-	})
-	return _c
-}
-
-func (_c *MockOServiceHandler_WriteOServiceHostOnline_Call) Return(_a0 error) *MockOServiceHandler_WriteOServiceHostOnline_Call {
-	_c.Call.Return(_a0)
-	return _c
-}
-
-func (_c *MockOServiceHandler_WriteOServiceHostOnline_Call) RunAndReturn(run func(io.Writer, *uint32) error) *MockOServiceHandler_WriteOServiceHostOnline_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
 // NewMockOServiceHandler creates a new instance of MockOServiceHandler. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
 // NewMockOServiceHandler creates a new instance of MockOServiceHandler. 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.
 // The first argument is typically a *testing.T value.
 func NewMockOServiceHandler(t interface {
 func NewMockOServiceHandler(t interface {

+ 321 - 15
server/oservice_test.go

@@ -135,8 +135,15 @@ func TestReceiveAndSendServiceRequest(t *testing.T) {
 			//
 			//
 			// send input SNAC
 			// send input SNAC
 			//
 			//
-			svc := OServiceService{}
-			outputSNAC, err := svc.ServiceRequestHandler(nil, tc.cfg, cr, tc.userSession, tc.inputSNAC)
+			svc := OServiceServiceForBOS{
+				OServiceService: OServiceService{
+					cfg: tc.cfg,
+					sm:  sm,
+				},
+				cr: cr,
+			}
+
+			outputSNAC, err := svc.ServiceRequestHandler(nil, tc.userSession, tc.inputSNAC)
 			assert.ErrorIs(t, err, tc.expectErr)
 			assert.ErrorIs(t, err, tc.expectErr)
 			if tc.expectErr != nil {
 			if tc.expectErr != nil {
 				return
 				return
@@ -149,7 +156,7 @@ func TestReceiveAndSendServiceRequest(t *testing.T) {
 	}
 	}
 }
 }
 
 
-func TestOServiceRouter_RouteOService(t *testing.T) {
+func TestOServiceRouter_RouteOService_ForBOS(t *testing.T) {
 	cases := []struct {
 	cases := []struct {
 		// name is the unit test name
 		// name is the unit test name
 		name string
 		name string
@@ -358,10 +365,6 @@ func TestOServiceRouter_RouteOService(t *testing.T) {
 	for _, tc := range cases {
 	for _, tc := range cases {
 		t.Run(tc.name, func(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			svc := NewMockOServiceHandler(t)
 			svc := NewMockOServiceHandler(t)
-			svc.EXPECT().
-				ServiceRequestHandler(mock.Anything, mock.Anything, mock.Anything, mock.Anything, tc.input.snacOut).
-				Return(tc.output, tc.handlerErr).
-				Maybe()
 			svc.EXPECT().
 			svc.EXPECT().
 				RateParamsQueryHandler(mock.Anything).
 				RateParamsQueryHandler(mock.Anything).
 				Return(tc.output).
 				Return(tc.output).
@@ -375,26 +378,329 @@ func TestOServiceRouter_RouteOService(t *testing.T) {
 				Return(tc.output).
 				Return(tc.output).
 				Maybe()
 				Maybe()
 			svc.EXPECT().
 			svc.EXPECT().
-				SetUserInfoFieldsHandler(mock.Anything, mock.Anything, mock.Anything, mock.Anything, tc.input.snacOut).
+				SetUserInfoFieldsHandler(mock.Anything, mock.Anything, tc.input.snacOut).
 				Return(tc.output, tc.handlerErr).
 				Return(tc.output, tc.handlerErr).
 				Maybe()
 				Maybe()
 			svc.EXPECT().
 			svc.EXPECT().
-				ClientOnlineHandler(mock.Anything, tc.input.snacOut, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
+				RateParamsSubAddHandler(mock.Anything, tc.input.snacOut).
+				Maybe()
+			svc.EXPECT().
+				IdleNotificationHandler(mock.Anything, mock.Anything, tc.input.snacOut).
 				Return(tc.handlerErr).
 				Return(tc.handlerErr).
 				Maybe()
 				Maybe()
+
+			svcBOS := NewMockOServiceBOSHandler(t)
+			svcBOS.EXPECT().
+				ServiceRequestHandler(mock.Anything, mock.Anything, tc.input.snacOut).
+				Return(tc.output, tc.handlerErr).
+				Maybe()
+			svcBOS.EXPECT().
+				ClientOnlineHandler(mock.Anything, tc.input.snacOut, mock.Anything).
+				Return(tc.handlerErr).
+				Maybe()
+
+			router := OServiceBOSRouter{
+				OServiceRouter: OServiceRouter{
+					OServiceHandler: svc,
+					RouteLogger: RouteLogger{
+						Logger: NewLogger(Config{}),
+					},
+				},
+				OServiceBOSHandler: svcBOS,
+			}
+
+			bufIn := &bytes.Buffer{}
+			assert.NoError(t, oscar.Marshal(tc.input.snacOut, bufIn))
+
+			bufOut := &bytes.Buffer{}
+			seq := uint32(1)
+
+			err := router.RouteOService(nil, nil, tc.input.snacFrame, bufIn, bufOut, &seq)
+			assert.ErrorIs(t, err, tc.expectErr)
+			if tc.expectErr != nil {
+				return
+			}
+
+			if tc.output == (XMessage{}) {
+				// make sure no response was sent
+				assert.Empty(t, bufOut.Bytes())
+				return
+			}
+
+			// verify the FLAP frame
+			flap := oscar.FlapFrame{}
+			assert.NoError(t, oscar.Unmarshal(&flap, bufOut))
+
+			// make sure the sequence number was incremented
+			assert.Equal(t, uint32(2), seq)
+
+			flapBuf, err := flap.SNACBuffer(bufOut)
+			assert.NoError(t, err)
+
+			// verify the SNAC frame
+			snacFrame := oscar.SnacFrame{}
+			assert.NoError(t, oscar.Unmarshal(&snacFrame, flapBuf))
+			assert.Equal(t, tc.output.snacFrame, snacFrame)
+
+			// verify the SNAC message
+			snacBuf := &bytes.Buffer{}
+			assert.NoError(t, oscar.Marshal(tc.output.snacOut, snacBuf))
+			assert.Equal(t, snacBuf.Bytes(), flapBuf.Bytes())
+		})
+	}
+}
+
+func TestOServiceRouter_RouteOService_ForChat(t *testing.T) {
+	cases := []struct {
+		// name is the unit test name
+		name string
+		// input is the request payload
+		input XMessage
+		// output is the response payload
+		output XMessage
+		// handlerErr is the mocked handler error response
+		handlerErr error
+		// expectErr is the expected error returned by the router
+		expectErr error
+	}{
+		{
+			name: "receive OServiceClientOnline, return no response",
+			input: XMessage{
+				snacFrame: oscar.SnacFrame{
+					FoodGroup: oscar.OSERVICE,
+					SubGroup:  oscar.OServiceClientOnline,
+				},
+				snacOut: oscar.SNAC_0x01_0x02_OServiceClientOnline{
+					GroupVersions: []struct {
+						FoodGroup   uint16
+						Version     uint16
+						ToolID      uint16
+						ToolVersion uint16
+					}{
+						{
+							FoodGroup: 10,
+						},
+					},
+				},
+			},
+			output: XMessage{},
+		},
+		{
+			name: "receive OServiceServiceRequest, return OServiceServiceResponse",
+			input: XMessage{
+				snacFrame: oscar.SnacFrame{
+					FoodGroup: oscar.OSERVICE,
+					SubGroup:  oscar.OServiceServiceRequest,
+				},
+				snacOut: oscar.SNAC_0x01_0x04_OServiceServiceRequest{
+					FoodGroup: 10,
+				},
+			},
+			output: XMessage{
+				snacFrame: oscar.SnacFrame{
+					FoodGroup: oscar.OSERVICE,
+					SubGroup:  oscar.OServiceServiceResponse,
+				},
+				snacOut: oscar.SNAC_0x01_0x05_OServiceServiceResponse{
+					TLVRestBlock: oscar.TLVRestBlock{
+						TLVList: oscar.TLVList{
+							oscar.NewTLV(0x01, uint16(1000)),
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "receive OServiceRateParamsQuery, return OServiceRateParamsReply",
+			input: XMessage{
+				snacFrame: oscar.SnacFrame{
+					FoodGroup: oscar.OSERVICE,
+					SubGroup:  oscar.OServiceRateParamsQuery,
+				},
+				snacOut: struct{}{},
+			},
+			output: XMessage{
+				snacFrame: oscar.SnacFrame{
+					FoodGroup: oscar.OSERVICE,
+					SubGroup:  oscar.OServiceRateParamsReply,
+				},
+				snacOut: oscar.SNAC_0x01_0x07_OServiceRateParamsReply{
+					RateGroups: []struct {
+						ID    uint16
+						Pairs []struct {
+							FoodGroup uint16
+							SubGroup  uint16
+						} `count_prefix:"uint16"`
+					}{
+						{
+							ID: 1,
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "receive OServiceRateParamsSubAdd, return no response",
+			input: XMessage{
+				snacFrame: oscar.SnacFrame{
+					FoodGroup: oscar.OSERVICE,
+					SubGroup:  oscar.OServiceRateParamsSubAdd,
+				},
+				snacOut: oscar.SNAC_0x01_0x08_OServiceRateParamsSubAdd{
+					TLVRestBlock: oscar.TLVRestBlock{
+						TLVList: oscar.TLVList{
+							oscar.NewTLV(0x01, []byte{1, 2, 3, 4}),
+						},
+					},
+				},
+			},
+			output: XMessage{},
+		},
+		{
+			name: "receive OServiceUserInfoQuery, return OServiceUserInfoUpdate",
+			input: XMessage{
+				snacFrame: oscar.SnacFrame{
+					FoodGroup: oscar.OSERVICE,
+					SubGroup:  oscar.OServiceUserInfoQuery,
+				},
+				snacOut: struct{}{},
+			},
+			output: XMessage{
+				snacFrame: oscar.SnacFrame{
+					FoodGroup: oscar.OSERVICE,
+					SubGroup:  oscar.OServiceUserInfoUpdate,
+				},
+				snacOut: oscar.SNAC_0x01_0x0F_OServiceUserInfoUpdate{
+					TLVUserInfo: oscar.TLVUserInfo{
+						ScreenName: "screen-name",
+					},
+				},
+			},
+		},
+		{
+			name: "receive OServiceIdleNotification, return no response",
+			input: XMessage{
+				snacFrame: oscar.SnacFrame{
+					FoodGroup: oscar.OSERVICE,
+					SubGroup:  oscar.OServiceIdleNotification,
+				},
+				snacOut: oscar.SNAC_0x01_0x11_OServiceIdleNotification{
+					IdleTime: 10,
+				},
+			},
+			output: XMessage{},
+		},
+		{
+			name: "receive OServiceClientVersions, return OServiceHostVersions",
+			input: XMessage{
+				snacFrame: oscar.SnacFrame{
+					FoodGroup: oscar.OSERVICE,
+					SubGroup:  oscar.OServiceClientVersions,
+				},
+				snacOut: oscar.SNAC_0x01_0x17_OServiceClientVersions{
+					Versions: []uint16{
+						10,
+					},
+				},
+			},
+			output: XMessage{
+				snacFrame: oscar.SnacFrame{
+					FoodGroup: oscar.OSERVICE,
+					SubGroup:  oscar.OServiceHostVersions,
+				},
+				snacOut: oscar.SNAC_0x01_0x18_OServiceHostVersions{
+					Versions: []uint16{
+						10,
+					},
+				},
+			},
+		},
+		{
+			name: "receive OServiceSetUserInfoFields, return OServiceUserInfoUpdate",
+			input: XMessage{
+				snacFrame: oscar.SnacFrame{
+					FoodGroup: oscar.OSERVICE,
+					SubGroup:  oscar.OServiceSetUserInfoFields,
+				},
+				snacOut: oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields{
+					TLVRestBlock: oscar.TLVRestBlock{
+						TLVList: oscar.TLVList{
+							oscar.NewTLV(0x01, []byte{1, 2, 3, 4}),
+						},
+					},
+				},
+			},
+			output: XMessage{
+				snacFrame: oscar.SnacFrame{
+					FoodGroup: oscar.OSERVICE,
+					SubGroup:  oscar.OServiceUserInfoUpdate,
+				},
+				snacOut: oscar.SNAC_0x01_0x0F_OServiceUserInfoUpdate{
+					TLVUserInfo: oscar.TLVUserInfo{
+						ScreenName: "screen-name",
+					},
+				},
+			},
+		},
+		{
+			name: "receive OServicePauseReq, expect ErrUnsupportedSubGroup",
+			input: XMessage{
+				snacFrame: oscar.SnacFrame{
+					FoodGroup: oscar.OSERVICE,
+					SubGroup:  oscar.OServicePauseReq,
+				},
+				snacOut: struct{}{}, // empty SNAC
+			},
+			output:    XMessage{}, // empty SNAC
+			expectErr: ErrUnsupportedSubGroup,
+		},
+	}
+
+	for _, tc := range cases {
+		t.Run(tc.name, func(t *testing.T) {
+			svc := NewMockOServiceHandler(t)
+			svc.EXPECT().
+				RateParamsQueryHandler(mock.Anything).
+				Return(tc.output).
+				Maybe()
+			svc.EXPECT().
+				UserInfoQueryHandler(mock.Anything, mock.Anything).
+				Return(tc.output).
+				Maybe()
+			svc.EXPECT().
+				ClientVersionsHandler(mock.Anything, tc.input.snacOut).
+				Return(tc.output).
+				Maybe()
+			svc.EXPECT().
+				SetUserInfoFieldsHandler(mock.Anything, mock.Anything, tc.input.snacOut).
+				Return(tc.output, tc.handlerErr).
+				Maybe()
 			svc.EXPECT().
 			svc.EXPECT().
 				RateParamsSubAddHandler(mock.Anything, tc.input.snacOut).
 				RateParamsSubAddHandler(mock.Anything, tc.input.snacOut).
 				Maybe()
 				Maybe()
 			svc.EXPECT().
 			svc.EXPECT().
-				IdleNotificationHandler(mock.Anything, mock.Anything, mock.Anything, mock.Anything, tc.input.snacOut).
+				IdleNotificationHandler(mock.Anything, mock.Anything, tc.input.snacOut).
 				Return(tc.handlerErr).
 				Return(tc.handlerErr).
 				Maybe()
 				Maybe()
 
 
-			router := OServiceRouter{
-				OServiceHandler: svc,
-				RouteLogger: RouteLogger{
-					Logger: NewLogger(Config{}),
+			svcBOS := NewMockOServiceChatHandler(t)
+			svcBOS.EXPECT().
+				ServiceRequestHandler(mock.Anything, mock.Anything, tc.input.snacOut).
+				Return(tc.output, tc.handlerErr).
+				Maybe()
+			svcBOS.EXPECT().
+				ClientOnlineHandler(mock.Anything, tc.input.snacOut, mock.Anything, mock.Anything).
+				Return(tc.handlerErr).
+				Maybe()
+
+			router := OServiceChatRouter{
+				OServiceRouter: OServiceRouter{
+					OServiceHandler: svc,
+					RouteLogger: RouteLogger{
+						Logger: NewLogger(Config{}),
+					},
 				},
 				},
+				OServiceChatHandler: svcBOS,
 			}
 			}
 
 
 			bufIn := &bytes.Buffer{}
 			bufIn := &bytes.Buffer{}
@@ -403,7 +709,7 @@ func TestOServiceRouter_RouteOService(t *testing.T) {
 			bufOut := &bytes.Buffer{}
 			bufOut := &bytes.Buffer{}
 			seq := uint32(1)
 			seq := uint32(1)
 
 
-			err := router.RouteOService(nil, Config{}, nil, nil, nil, nil, ChatRoom{}, tc.input.snacFrame, bufIn, bufOut, &seq)
+			err := router.RouteOService(nil, nil, ChatRoom{}, tc.input.snacFrame, bufIn, bufOut, &seq)
 			assert.ErrorIs(t, err, tc.expectErr)
 			assert.ErrorIs(t, err, tc.expectErr)
 			if tc.expectErr != nil {
 			if tc.expectErr != nil {
 				return
 				return

+ 75 - 26
server/router.go

@@ -3,59 +3,63 @@ package server
 import (
 import (
 	"bytes"
 	"bytes"
 	"context"
 	"context"
+	"errors"
+	"fmt"
 	"github.com/mkaminski/goaim/oscar"
 	"github.com/mkaminski/goaim/oscar"
 	"io"
 	"io"
 	"log/slog"
 	"log/slog"
+	"net"
 )
 )
 
 
-func NewRouter(logger *slog.Logger) Router {
-	return Router{
-		AlertRouter:    NewAlertRouter(logger),
-		BuddyRouter:    NewBuddyRouter(logger),
-		ChatNavRouter:  NewChatNavRouter(logger),
-		ChatRouter:     NewChatRouter(logger),
-		FeedbagRouter:  NewFeedbagRouter(logger),
-		ICBMRouter:     NewICBMRouter(logger),
-		LocateRouter:   NewLocateRouter(logger),
-		OServiceRouter: NewOServiceRouter(logger),
+func NewBOSServiceRouter(logger *slog.Logger, cfg Config, fm FeedbagManager, sm SessionManager, cr *ChatRegistry, pm ProfileManager) BOSServiceRouter {
+	return BOSServiceRouter{
+		AlertRouter:       NewAlertRouter(logger),
+		BuddyRouter:       NewBuddyRouter(logger),
+		ChatNavRouter:     NewChatNavRouter(logger, cr),
+		FeedbagRouter:     NewFeedbagRouter(logger, sm, fm),
+		ICBMRouter:        NewICBMRouter(logger, sm, fm),
+		LocateRouter:      NewLocateRouter(logger, sm, fm, pm),
+		OServiceBOSRouter: NewOServiceRouterForBOS(logger, cfg, fm, sm, cr),
+		sm:                sm,
+		fm:                fm,
 	}
 	}
 }
 }
 
 
-func NewRouterForChat(logger *slog.Logger) Router {
-	r := NewRouter(logger)
-	r.OServiceRouter = NewOServiceRouterForChat(logger)
-	return r
+func NewChatServiceRouter(logger *slog.Logger, cfg Config, fm FeedbagManager, sm SessionManager) ChatServiceRouter {
+	return ChatServiceRouter{
+		OServiceChatRouter: NewOServiceRouterForChat(logger, cfg, fm, sm),
+		ChatRouter:         NewChatRouter(logger),
+	}
 }
 }
 
 
-type Router struct {
+type BOSServiceRouter struct {
 	AlertRouter
 	AlertRouter
 	BuddyRouter
 	BuddyRouter
 	ChatNavRouter
 	ChatNavRouter
-	ChatRouter
 	FeedbagRouter
 	FeedbagRouter
 	ICBMRouter
 	ICBMRouter
 	LocateRouter
 	LocateRouter
-	OServiceRouter
+	OServiceBOSRouter
+	sm SessionManager
+	fm FeedbagManager
 }
 }
 
 
-func (rt *Router) routeIncomingRequests(ctx context.Context, cfg Config, sm SessionManager, sess *Session, fm *FeedbagStore, cr *ChatRegistry, rw io.ReadWriter, sequence *uint32, snac oscar.SnacFrame, buf io.Reader, room ChatRoom) error {
+func (rt *BOSServiceRouter) Route(ctx context.Context, sess *Session, w io.Writer, sequence *uint32, snac oscar.SnacFrame, buf io.Reader) error {
 	switch snac.FoodGroup {
 	switch snac.FoodGroup {
 	case oscar.OSERVICE:
 	case oscar.OSERVICE:
-		return rt.RouteOService(ctx, cfg, cr, sm, fm, sess, room, snac, buf, rw, sequence)
+		return rt.RouteOService(ctx, sess, snac, buf, w, sequence)
 	case oscar.LOCATE:
 	case oscar.LOCATE:
-		return rt.RouteLocate(ctx, sess, sm, fm, snac, buf, rw, sequence)
+		return rt.RouteLocate(ctx, sess, snac, buf, w, sequence)
 	case oscar.BUDDY:
 	case oscar.BUDDY:
-		return rt.RouteBuddy(ctx, snac, buf, rw, sequence)
+		return rt.RouteBuddy(ctx, snac, buf, w, sequence)
 	case oscar.ICBM:
 	case oscar.ICBM:
-		return rt.RouteICBM(ctx, sm, fm, sess, snac, buf, rw, sequence)
+		return rt.RouteICBM(ctx, sess, snac, buf, w, sequence)
 	case oscar.CHAT_NAV:
 	case oscar.CHAT_NAV:
-		return rt.RouteChatNav(ctx, sess, cr, snac, buf, rw, sequence)
+		return rt.RouteChatNav(ctx, sess, snac, buf, w, sequence)
 	case oscar.FEEDBAG:
 	case oscar.FEEDBAG:
-		return rt.RouteFeedbag(ctx, sm, sess, fm, snac, buf, rw, sequence)
+		return rt.RouteFeedbag(ctx, sess, snac, buf, w, sequence)
 	case oscar.BUCP:
 	case oscar.BUCP:
 		return routeBUCP(ctx)
 		return routeBUCP(ctx)
-	case oscar.CHAT:
-		return rt.RouteChat(ctx, sess, sm, snac, buf, rw, sequence)
 	case oscar.ALERT:
 	case oscar.ALERT:
 		return rt.RouteAlert(ctx, snac)
 		return rt.RouteAlert(ctx, snac)
 	default:
 	default:
@@ -63,6 +67,35 @@ func (rt *Router) routeIncomingRequests(ctx context.Context, cfg Config, sm Sess
 	}
 	}
 }
 }
 
 
+func (rt *BOSServiceRouter) Signout(ctx context.Context, logger *slog.Logger, sess *Session) {
+	if err := BroadcastDeparture(ctx, sess, rt.sm, rt.fm); err != nil {
+		logger.ErrorContext(ctx, "error notifying departure", "err", err.Error())
+	}
+	rt.sm.Remove(sess)
+}
+
+func (rt *BOSServiceRouter) VerifyLogin(conn net.Conn) (*Session, uint32, error) {
+	seq := uint32(100)
+
+	flap, err := SendAndReceiveSignonFrame(conn, &seq)
+	if err != nil {
+		return nil, 0, err
+	}
+
+	var ok bool
+	ID, ok := flap.GetSlice(oscar.OServiceTLVTagsLoginCookie)
+	if !ok {
+		return nil, 0, errors.New("unable to get session ID from payload")
+	}
+
+	sess, ok := rt.sm.Retrieve(string(ID))
+	if !ok {
+		return nil, 0, fmt.Errorf("unable to find session by ID %s", ID)
+	}
+
+	return sess, seq, nil
+}
+
 func writeOutSNAC(originsnac oscar.SnacFrame, snacFrame oscar.SnacFrame, snacOut any, sequence *uint32, w io.Writer) error {
 func writeOutSNAC(originsnac oscar.SnacFrame, snacFrame oscar.SnacFrame, snacOut any, sequence *uint32, w io.Writer) error {
 	if originsnac.RequestID != 0 {
 	if originsnac.RequestID != 0 {
 		snacFrame.RequestID = originsnac.RequestID
 		snacFrame.RequestID = originsnac.RequestID
@@ -110,3 +143,19 @@ func sendInvalidSNACErr(snac oscar.SnacFrame, w io.Writer, sequence *uint32) err
 	}
 	}
 	return writeOutSNAC(snac, snacFrameOut, snacPayloadOut, sequence, w)
 	return writeOutSNAC(snac, snacFrameOut, snacPayloadOut, sequence, w)
 }
 }
+
+type ChatServiceRouter struct {
+	ChatRouter
+	OServiceChatRouter
+}
+
+func (rt *ChatServiceRouter) Route(ctx context.Context, sess *Session, w io.Writer, sequence *uint32, snac oscar.SnacFrame, buf io.Reader, room ChatRoom) error {
+	switch snac.FoodGroup {
+	case oscar.OSERVICE:
+		return rt.RouteOService(ctx, sess, room, snac, buf, w, sequence)
+	case oscar.CHAT:
+		return rt.RouteChat(ctx, sess, room, snac, buf, w, sequence)
+	default:
+		return ErrUnsupportedFoodGroup
+	}
+}