Explorar o código

split out state package

Mike %!s(int64=2) %!d(string=hai) anos
pai
achega
3792a2cae6
Modificáronse 52 ficheiros con 740 adicións e 822 borrados
  1. 10 8
      .mockery.yaml
  2. 11 10
      cmd/main.go
  3. 22 12
      handler/auth.go
  4. 8 7
      handler/auth_test.go
  5. 5 5
      handler/buddy.go
  6. 17 9
      handler/chat.go
  7. 24 8
      handler/chat_nav.go
  8. 15 17
      handler/chat_nav_test.go
  9. 38 36
      handler/chat_session_manager_mock.go
  10. 13 12
      handler/chat_test.go
  11. 9 9
      handler/feedbag.go
  12. 10 8
      handler/feedbag_manager_mock.go
  13. 12 12
      handler/feedbag_test.go
  14. 11 11
      handler/icbm.go
  15. 25 25
      handler/icbm_test.go
  16. 8 8
      handler/locate.go
  17. 18 18
      handler/locate_test.go
  18. 27 18
      handler/oservice.go
  19. 9 8
      handler/oservice_test.go
  20. 1 1
      handler/profile_manager_mock.go
  21. 27 25
      handler/session_manager_mock.go
  22. 11 11
      handler/test_helpers.go
  23. 14 13
      handler/types.go
  24. 18 15
      handler/user_manager_manager_mock.go
  25. 5 73
      server/chat.go
  26. 18 16
      server/chat_mock.go
  27. 4 11
      server/chat_nav.go
  28. 17 17
      server/chat_nav_mock.go
  29. 2 2
      server/chat_nav_test.go
  30. 0 160
      server/chat_registry_mock.go
  31. 1 1
      server/chat_test.go
  32. 19 12
      server/connection.go
  33. 10 9
      server/connection_test.go
  34. 7 6
      server/feedbag.go
  35. 42 40
      server/feedbag_mock.go
  36. 5 4
      server/icbm.go
  37. 24 22
      server/icbm_mock.go
  38. 4 3
      server/locate.go
  39. 16 14
      server/locate_mock.go
  40. 5 4
      server/mgmt_api.go
  41. 12 11
      server/oservice.go
  42. 36 34
      server/oservice_bos_mock.go
  43. 42 41
      server/oservice_chat_mock.go
  44. 22 20
      server/oservice_mock.go
  45. 2 2
      server/oservice_test.go
  46. 9 9
      server/router.go
  47. 70 0
      state/chat_registry.go
  48. 1 1
      state/feedbag_store.go
  49. 1 1
      state/feedbag_store_test.go
  50. 1 1
      state/session.go
  51. 1 1
      state/session_manager.go
  52. 1 1
      state/session_test.go

+ 10 - 8
.mockery.yaml

@@ -1,7 +1,7 @@
 with-expecter: True
+dir: "{{.InterfaceDir}}"
 mockname: "Mock{{.InterfaceName}}"
 inpackage: True
-dir: ./server
 packages:
   github.com/mkaminski/goaim/server:
     interfaces:
@@ -17,9 +17,6 @@ packages:
       FeedbagHandler:
         config:
           filename: "feedbag_mock.go"
-      FeedbagManager:
-        config:
-          filename: "feedbag_manager_mock.go"
       ICBMHandler:
         config:
           filename: "icbm_mock.go"
@@ -35,6 +32,14 @@ packages:
       OServiceChatHandler:
         config:
           filename: "oservice_chat_mock.go"
+  github.com/mkaminski/goaim/handler:
+    interfaces:
+      FeedbagManager:
+        config:
+          filename: "feedbag_manager_mock.go"
+      UserManager:
+        config:
+          filename: "user_manager_manager_mock.go"
       ProfileManager:
         config:
           filename: "profile_manager_mock.go"
@@ -43,7 +48,4 @@ packages:
           filename: "session_manager_mock.go"
       ChatSessionManager:
         config:
-          filename: "chat_session_manager_mock.go"
-      UserManager:
-        config:
-          filename: "user_manager_manager_mock.go"
+          filename: "chat_session_manager_mock.go"

+ 11 - 10
cmd/main.go

@@ -3,6 +3,7 @@ package main
 import (
 	"fmt"
 	"github.com/mkaminski/goaim/handler"
+	"github.com/mkaminski/goaim/state"
 	"os"
 	"sync"
 
@@ -18,15 +19,15 @@ func main() {
 		os.Exit(1)
 	}
 
-	fm, err := server.NewSQLiteFeedbagStore(cfg.DBPath)
+	fm, err := state.NewSQLiteFeedbagStore(cfg.DBPath)
 	if err != nil {
 		_, _ = fmt.Fprintf(os.Stderr, "unable to create feedbag store: %s", err.Error())
 		os.Exit(1)
 	}
 
 	logger := server.NewLogger(cfg)
-	sm := server.NewSessionManager(logger)
-	cr := server.NewChatRegistry()
+	sm := state.NewSessionManager(logger)
+	cr := state.NewChatRegistry()
 
 	wg := sync.WaitGroup{}
 	wg.Add(4)
@@ -36,12 +37,13 @@ func main() {
 		wg.Done()
 	}()
 	go func() {
-		authHandler := handler.NewAuthService(sm, fm, fm, cfg)
+		authHandler := handler.NewAuthService(cfg, sm, fm, fm, cr)
 		buddyHandler := handler.NewBuddyService()
 		oserviceHandler := handler.NewOServiceService(cfg, sm, fm)
 		oserviceBOSHandler := handler.NewOServiceServiceForBOS(*oserviceHandler, cr)
 		locateHandler := handler.NewLocateService(sm, fm, fm)
-		chatNavHandler := handler.NewChatNavService(logger, cr)
+		newChatSessMgr := func() handler.ChatSessionManager { return state.NewSessionManager(logger) }
+		chatNavHandler := handler.NewChatNavService(logger, cr, handler.NewChatRoom, newChatSessMgr)
 		feedbagHandler := handler.NewFeedbagService(sm, fm)
 		icbmHandler := handler.NewICBMService(sm, fm)
 
@@ -58,23 +60,22 @@ func main() {
 			RouteLogger: server.RouteLogger{
 				Logger: logger,
 			},
-			NewChatSessMgr: func() server.ChatSessionManager { return server.NewSessionManager(logger) },
 		}
 
 		server.ListenBOS(cfg, router, authHandler, logger.With("svc", "BOS"))
 		wg.Done()
 	}()
 	go func() {
-		authHandler := handler.NewAuthService(sm, fm, fm, cfg)
+		authHandler := handler.NewAuthService(cfg, sm, fm, fm, cr)
 		oserviceHandler := handler.NewOServiceService(cfg, sm, fm)
-		chatHandler := handler.NewChatService()
-		oserviceChatHandler := handler.NewOServiceServiceForChat(*oserviceHandler)
+		chatHandler := handler.NewChatService(cr)
+		oserviceChatHandler := handler.NewOServiceServiceForChat(*oserviceHandler, cr)
 		router := server.NewChatServiceRouter(logger, cfg, oserviceHandler, *chatHandler, oserviceChatHandler)
 		server.ListenChat(cfg, router, cr, authHandler, logger.With("svc", "CHAT"))
 		wg.Done()
 	}()
 	go func() {
-		authHandler := handler.NewAuthService(sm, fm, fm, cfg)
+		authHandler := handler.NewAuthService(cfg, sm, fm, fm, cr)
 		server.ListenBUCPLogin(cfg, err, logger, authHandler)
 		wg.Done()
 	}()

+ 22 - 12
handler/auth.go

@@ -8,21 +8,23 @@ import (
 	"github.com/google/uuid"
 	"github.com/mkaminski/goaim/oscar"
 	"github.com/mkaminski/goaim/server"
+	"github.com/mkaminski/goaim/state"
 	"io"
 	"net"
 )
 
-func NewAuthService(sm server.SessionManager, fm server.FeedbagManager, um server.UserManager, cfg server.Config) *AuthService {
+func NewAuthService(cfg server.Config, sm SessionManager, fm FeedbagManager, um UserManager, cr *state.ChatRegistry) *AuthService {
 	return &AuthService{
 		sm:  sm,
 		fm:  fm,
 		um:  um,
 		cfg: cfg,
+		cr:  cr,
 	}
 }
 
-func newStubUser(screenName string) (server.User, error) {
-	u := server.User{ScreenName: screenName}
+func newStubUser(screenName string) (state.User, error) {
+	u := state.User{ScreenName: screenName}
 
 	uid, err := uuid.NewRandom()
 	if err != nil {
@@ -37,13 +39,14 @@ func newStubUser(screenName string) (server.User, error) {
 }
 
 type AuthService struct {
-	sm  server.SessionManager
-	fm  server.FeedbagManager
-	um  server.UserManager
+	sm  SessionManager
+	fm  FeedbagManager
+	um  UserManager
 	cfg server.Config
+	cr  *state.ChatRegistry
 }
 
-func (s AuthService) Signout(ctx context.Context, sess *server.Session) error {
+func (s AuthService) Signout(ctx context.Context, sess *state.Session) error {
 	if err := broadcastDeparture(ctx, sess, s.sm, s.fm); err != nil {
 		return err
 	}
@@ -51,13 +54,20 @@ func (s AuthService) Signout(ctx context.Context, sess *server.Session) error {
 	return nil
 }
 
-func (s AuthService) SignoutChat(ctx context.Context, cr *server.ChatRegistry, chatRoom server.ChatRoom, chatSessManager server.ChatSessionManager, sess *server.Session) {
-	alertUserLeft(ctx, sess, chatSessManager)
-	chatSessManager.Remove(sess)
-	cr.MaybeRemoveRoom(chatRoom.Cookie)
+func (s AuthService) SignoutChat(ctx context.Context, sess *state.Session, chatID string) {
+	chatRoom, chatSessMgr, err := s.cr.Retrieve(chatID)
+	if err != nil {
+		fmt.Println("error getting chat room to remove")
+		return
+	}
+	alertUserLeft(ctx, sess, chatSessMgr.(ChatSessionManager))
+	chatSessMgr.(ChatSessionManager).Remove(sess)
+	if chatSessMgr.(ChatSessionManager).Empty() {
+		s.cr.RemoveRoom(chatRoom.Cookie)
+	}
 }
 
-func (s AuthService) VerifyLogin(conn net.Conn) (*server.Session, uint32, error) {
+func (s AuthService) VerifyLogin(conn net.Conn) (*state.Session, uint32, error) {
 	seq := uint32(100)
 
 	flap, err := s.SendAndReceiveSignonFrame(conn, &seq)

+ 8 - 7
handler/auth_test.go

@@ -4,13 +4,14 @@ import (
 	"github.com/google/uuid"
 	"github.com/mkaminski/goaim/oscar"
 	"github.com/mkaminski/goaim/server"
+	"github.com/mkaminski/goaim/state"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/mock"
 	"testing"
 )
 
 func TestReceiveAndSendBUCPLoginRequest(t *testing.T) {
-	userGoodPwd := server.User{
+	userGoodPwd := state.User{
 		ScreenName: "sn_user_a",
 		AuthKey:    "auth_key_user",
 	}
@@ -21,7 +22,7 @@ func TestReceiveAndSendBUCPLoginRequest(t *testing.T) {
 	cases := []struct {
 		name        string
 		cfg         server.Config
-		userInDB    server.User
+		userInDB    state.User
 		sessionUUID uuid.UUID
 		inputSNAC   oscar.SNAC_0x17_0x02_BUCPLoginRequest
 		// expectOutput is the SNAC payload sent from the server to the
@@ -129,7 +130,7 @@ func TestReceiveAndSendBUCPLoginRequest(t *testing.T) {
 	for _, tc := range cases {
 		t.Run(tc.name, func(t *testing.T) {
 			sess := newTestSession(tc.userInDB.ScreenName, sessOptID(tc.sessionUUID.String()))
-			um := server.NewMockUserManager(t)
+			um := NewMockUserManager(t)
 			um.EXPECT().
 				GetUser(tc.userInDB.ScreenName).
 				Return(&userGoodPwd, nil).
@@ -138,7 +139,7 @@ func TestReceiveAndSendBUCPLoginRequest(t *testing.T) {
 				UpsertUser(mock.Anything).
 				Return(nil).
 				Maybe()
-			sm := server.NewMockSessionManager(t)
+			sm := NewMockSessionManager(t)
 			sm.EXPECT().
 				NewSessionWithSN(tc.sessionUUID.String(), tc.userInDB.ScreenName).
 				Return(sess).
@@ -162,7 +163,7 @@ func TestReceiveAndSendAuthChallenge(t *testing.T) {
 	cases := []struct {
 		name         string
 		cfg          server.Config
-		userInDB     *server.User
+		userInDB     *state.User
 		fnNewUUID    uuid.UUID
 		inputSNAC    oscar.SNAC_0x17_0x06_BUCPChallengeRequest
 		expectOutput oscar.XMessage
@@ -173,7 +174,7 @@ func TestReceiveAndSendAuthChallenge(t *testing.T) {
 				OSCARHost: "127.0.0.1",
 				BOSPort:   1234,
 			},
-			userInDB: &server.User{
+			userInDB: &state.User{
 				ScreenName: "sn_user_a",
 				AuthKey:    "auth_key_user_a",
 			},
@@ -254,7 +255,7 @@ func TestReceiveAndSendAuthChallenge(t *testing.T) {
 
 	for _, tc := range cases {
 		t.Run(tc.name, func(t *testing.T) {
-			um := server.NewMockUserManager(t)
+			um := NewMockUserManager(t)
 			um.EXPECT().
 				GetUser(string(tc.inputSNAC.TLVList[0].Val)).
 				Return(tc.userInDB, nil).

+ 5 - 5
handler/buddy.go

@@ -3,7 +3,7 @@ package handler
 import (
 	"context"
 	"github.com/mkaminski/goaim/oscar"
-	"github.com/mkaminski/goaim/server"
+	"github.com/mkaminski/goaim/state"
 )
 
 func NewBuddyService() *BuddyService {
@@ -32,7 +32,7 @@ func (s BuddyService) RightsQueryHandler(context.Context) oscar.XMessage {
 	}
 }
 
-func broadcastArrival(ctx context.Context, sess *server.Session, sm server.SessionManager, fm server.FeedbagManager) error {
+func broadcastArrival(ctx context.Context, sess *state.Session, sm SessionManager, fm FeedbagManager) error {
 	screenNames, err := fm.InterestedUsers(sess.ScreenName())
 	if err != nil {
 		return err
@@ -57,7 +57,7 @@ func broadcastArrival(ctx context.Context, sess *server.Session, sm server.Sessi
 	return nil
 }
 
-func broadcastDeparture(ctx context.Context, sess *server.Session, sm server.SessionManager, fm server.FeedbagManager) error {
+func broadcastDeparture(ctx context.Context, sess *state.Session, sm SessionManager, fm FeedbagManager) error {
 	screenNames, err := fm.InterestedUsers(sess.ScreenName())
 	if err != nil {
 		return err
@@ -79,7 +79,7 @@ func broadcastDeparture(ctx context.Context, sess *server.Session, sm server.Ses
 	return nil
 }
 
-func unicastArrival(ctx context.Context, srcScreenName, destScreenName string, sm server.SessionManager) {
+func unicastArrival(ctx context.Context, srcScreenName, destScreenName string, sm SessionManager, ) {
 	sess := sm.RetrieveByScreenName(srcScreenName)
 	switch {
 	case sess == nil:
@@ -98,7 +98,7 @@ func unicastArrival(ctx context.Context, srcScreenName, destScreenName string, s
 	})
 }
 
-func unicastDeparture(ctx context.Context, srcScreenName, destScreenName string, sm server.SessionManager) {
+func unicastDeparture(ctx context.Context, srcScreenName, destScreenName string, sm SessionManager, ) {
 	sess := sm.RetrieveByScreenName(srcScreenName)
 	switch {
 	case sess == nil:

+ 17 - 9
handler/chat.go

@@ -3,17 +3,20 @@ package handler
 import (
 	"context"
 	"github.com/mkaminski/goaim/oscar"
-	"github.com/mkaminski/goaim/server"
+	"github.com/mkaminski/goaim/state"
 )
 
-func NewChatService() *ChatService {
-	return &ChatService{}
+func NewChatService(chatRegistry *state.ChatRegistry) *ChatService {
+	return &ChatService{
+		chatRegistry: chatRegistry,
+	}
 }
 
 type ChatService struct {
+	chatRegistry *state.ChatRegistry
 }
 
-func (s ChatService) ChannelMsgToHostHandler(ctx context.Context, sess *server.Session, chatSessMgr server.ChatSessionManager, snacPayloadIn oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost) (*oscar.XMessage, error) {
+func (s ChatService) ChannelMsgToHostHandler(ctx context.Context, sess *state.Session, chatID string, snacPayloadIn oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost) (*oscar.XMessage, error) {
 	snacFrameOut := oscar.SnacFrame{
 		FoodGroup: oscar.CHAT,
 		SubGroup:  oscar.ChatChannelMsgToClient,
@@ -35,8 +38,13 @@ func (s ChatService) ChannelMsgToHostHandler(ctx context.Context, sess *server.S
 		}),
 	)
 
+	_, chatSessMgr, err := s.chatRegistry.Retrieve(chatID)
+	if err != nil {
+		return nil, err
+	}
+
 	// send message to all the participants except sender
-	chatSessMgr.BroadcastExcept(ctx, sess, oscar.XMessage{
+	chatSessMgr.(ChatSessionManager).BroadcastExcept(ctx, sess, oscar.XMessage{
 		SnacFrame: snacFrameOut,
 		SnacOut:   snacPayloadOut,
 	})
@@ -53,7 +61,7 @@ func (s ChatService) ChannelMsgToHostHandler(ctx context.Context, sess *server.S
 	return ret, nil
 }
 
-func setOnlineChatUsers(ctx context.Context, sess *server.Session, chatSessMgr server.ChatSessionManager) {
+func setOnlineChatUsers(ctx context.Context, sess *state.Session, chatSessMgr ChatSessionManager) {
 	snacPayloadOut := oscar.SNAC_0x0E_0x03_ChatUsersJoined{}
 	sessions := chatSessMgr.Participants()
 
@@ -76,7 +84,7 @@ func setOnlineChatUsers(ctx context.Context, sess *server.Session, chatSessMgr s
 	})
 }
 
-func alertUserJoined(ctx context.Context, sess *server.Session, chatSessMgr server.ChatSessionManager) {
+func alertUserJoined(ctx context.Context, sess *state.Session, chatSessMgr ChatSessionManager) {
 	chatSessMgr.BroadcastExcept(ctx, sess, oscar.XMessage{
 		SnacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.CHAT,
@@ -96,7 +104,7 @@ func alertUserJoined(ctx context.Context, sess *server.Session, chatSessMgr serv
 	})
 }
 
-func alertUserLeft(ctx context.Context, sess *server.Session, chatSessMgr server.ChatSessionManager) {
+func alertUserLeft(ctx context.Context, sess *state.Session, chatSessMgr ChatSessionManager) {
 	chatSessMgr.BroadcastExcept(ctx, sess, oscar.XMessage{
 		SnacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.CHAT,
@@ -116,7 +124,7 @@ func alertUserLeft(ctx context.Context, sess *server.Session, chatSessMgr server
 	})
 }
 
-func sendChatRoomInfoUpdate(ctx context.Context, sess *server.Session, chatSessMgr server.ChatSessionManager, room server.ChatRoom) {
+func sendChatRoomInfoUpdate(ctx context.Context, sess *state.Session, chatSessMgr ChatSessionManager, room state.ChatRoom) {
 	chatSessMgr.SendToScreenName(ctx, sess.ScreenName(), oscar.XMessage{
 		SnacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.CHAT,

+ 24 - 8
handler/chat_nav.go

@@ -3,18 +3,34 @@ package handler
 import (
 	"context"
 	"errors"
+	"github.com/google/uuid"
 	"github.com/mkaminski/goaim/oscar"
-	"github.com/mkaminski/goaim/server"
+	"github.com/mkaminski/goaim/state"
 	"log/slog"
+	"time"
 )
 
-func NewChatNavService(logger *slog.Logger, cr *server.ChatRegistry) *ChatNavService {
-	return &ChatNavService{Logger: logger, cr: cr}
+func NewChatRoom() state.ChatRoom {
+	return state.ChatRoom{
+		Cookie:     uuid.New().String(),
+		CreateTime: time.Now(),
+	}
+}
+
+func NewChatNavService(logger *slog.Logger, cr *state.ChatRegistry, newChatRoom func() state.ChatRoom, newChatSessMgr func() ChatSessionManager) *ChatNavService {
+	return &ChatNavService{
+		Logger:         logger,
+		cr:             cr,
+		newChatRoom:    newChatRoom,
+		newChatSessMgr: newChatSessMgr,
+	}
 }
 
 type ChatNavService struct {
-	Logger *slog.Logger
-	cr     *server.ChatRegistry
+	Logger         *slog.Logger
+	cr             *state.ChatRegistry
+	newChatRoom    func() state.ChatRoom
+	newChatSessMgr func() ChatSessionManager
 }
 
 func (s ChatNavService) RequestChatRightsHandler(context.Context) oscar.XMessage {
@@ -48,19 +64,19 @@ func (s ChatNavService) RequestChatRightsHandler(context.Context) oscar.XMessage
 	}
 }
 
-func (s ChatNavService) CreateRoomHandler(ctx context.Context, sess *server.Session, newChatRoom func() server.ChatRoom, newChatSessMgr func() server.ChatSessionManager, snacPayloadIn oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) (oscar.XMessage, error) {
+func (s ChatNavService) CreateRoomHandler(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) (oscar.XMessage, error) {
 	name, hasName := snacPayloadIn.GetString(oscar.ChatTLVRoomName)
 	if !hasName {
 		return oscar.XMessage{}, errors.New("unable to find chat name")
 	}
 
-	room := newChatRoom()
+	room := s.newChatRoom()
 	room.DetailLevel = snacPayloadIn.DetailLevel
 	room.Exchange = snacPayloadIn.Exchange
 	room.InstanceNumber = snacPayloadIn.InstanceNumber
 	room.Name = name
 
-	chatSessMgr := newChatSessMgr()
+	chatSessMgr := s.newChatSessMgr()
 
 	s.cr.Register(room, chatSessMgr)
 

+ 15 - 17
handler/chat_nav_test.go

@@ -3,7 +3,7 @@ package handler
 import (
 	"context"
 	"github.com/mkaminski/goaim/oscar"
-	"github.com/mkaminski/goaim/server"
+	"github.com/mkaminski/goaim/state"
 	"github.com/stretchr/testify/assert"
 	"testing"
 	"time"
@@ -15,22 +15,11 @@ func TestSendAndReceiveCreateRoom(t *testing.T) {
 	//
 	userSess := newTestSession("user-screen-name", sessOptCannedID)
 
-	cr := server.NewChatRegistry()
+	cr := state.NewChatRegistry()
 
-	sm := server.NewMockChatSessionManager(t)
+	sm := NewMockChatSessionManager(t)
 	sm.EXPECT().NewSessionWithSN(userSess.ID(), userSess.ScreenName()).
-		Return(&server.Session{})
-
-	chatSessMgrFactory := func() server.ChatSessionManager {
-		return sm
-	}
-
-	newChatRoom := func() server.ChatRoom {
-		return server.ChatRoom{
-			Cookie:     "dummy-cookie",
-			CreateTime: time.UnixMilli(0),
-		}
-	}
+		Return(&state.Session{})
 
 	//
 	// send input SNAC
@@ -48,14 +37,23 @@ func TestSendAndReceiveCreateRoom(t *testing.T) {
 	}
 	svc := ChatNavService{
 		cr: cr,
+		newChatRoom: func() state.ChatRoom {
+			return state.ChatRoom{
+				Cookie:     "dummy-cookie",
+				CreateTime: time.UnixMilli(0),
+			}
+		},
+		newChatSessMgr: func() ChatSessionManager {
+			return sm
+		},
 	}
-	outputSNAC, err := svc.CreateRoomHandler(context.Background(), userSess, newChatRoom, chatSessMgrFactory, inputSNAC)
+	outputSNAC, err := svc.CreateRoomHandler(context.Background(), userSess, inputSNAC)
 	assert.NoError(t, err)
 
 	//
 	// verify chat room created by handler
 	//
-	expectChatRoom := server.ChatRoom{
+	expectChatRoom := state.ChatRoom{
 		Cookie:         "dummy-cookie",
 		CreateTime:     time.UnixMilli(0),
 		DetailLevel:    3,

+ 38 - 36
server/chat_session_manager_mock.go → handler/chat_session_manager_mock.go

@@ -1,12 +1,14 @@
 // Code generated by mockery v2.34.2. DO NOT EDIT.
 
-package server
+package handler
 
 import (
 	context "context"
 
 	oscar "github.com/mkaminski/goaim/oscar"
 	mock "github.com/stretchr/testify/mock"
+
+	state "github.com/mkaminski/goaim/state"
 )
 
 // MockChatSessionManager is an autogenerated mock type for the ChatSessionManager type
@@ -57,7 +59,7 @@ func (_c *MockChatSessionManager_Broadcast_Call) RunAndReturn(run func(context.C
 }
 
 // BroadcastExcept provides a mock function with given fields: ctx, except, msg
-func (_m *MockChatSessionManager) BroadcastExcept(ctx context.Context, except *Session, msg oscar.XMessage) {
+func (_m *MockChatSessionManager) BroadcastExcept(ctx context.Context, except *state.Session, msg oscar.XMessage) {
 	_m.Called(ctx, except, msg)
 }
 
@@ -68,15 +70,15 @@ type MockChatSessionManager_BroadcastExcept_Call struct {
 
 // BroadcastExcept is a helper method to define mock.On call
 //   - ctx context.Context
-//   - except *Session
+//   - except *state.Session
 //   - msg oscar.XMessage
 func (_e *MockChatSessionManager_Expecter) BroadcastExcept(ctx interface{}, except interface{}, msg interface{}) *MockChatSessionManager_BroadcastExcept_Call {
 	return &MockChatSessionManager_BroadcastExcept_Call{Call: _e.mock.On("BroadcastExcept", ctx, except, msg)}
 }
 
-func (_c *MockChatSessionManager_BroadcastExcept_Call) Run(run func(ctx context.Context, except *Session, msg oscar.XMessage)) *MockChatSessionManager_BroadcastExcept_Call {
+func (_c *MockChatSessionManager_BroadcastExcept_Call) Run(run func(ctx context.Context, except *state.Session, msg oscar.XMessage)) *MockChatSessionManager_BroadcastExcept_Call {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(*Session), args[2].(oscar.XMessage))
+		run(args[0].(context.Context), args[1].(*state.Session), args[2].(oscar.XMessage))
 	})
 	return _c
 }
@@ -86,7 +88,7 @@ func (_c *MockChatSessionManager_BroadcastExcept_Call) Return() *MockChatSession
 	return _c
 }
 
-func (_c *MockChatSessionManager_BroadcastExcept_Call) RunAndReturn(run func(context.Context, *Session, oscar.XMessage)) *MockChatSessionManager_BroadcastExcept_Call {
+func (_c *MockChatSessionManager_BroadcastExcept_Call) RunAndReturn(run func(context.Context, *state.Session, oscar.XMessage)) *MockChatSessionManager_BroadcastExcept_Call {
 	_c.Call.Return(run)
 	return _c
 }
@@ -168,15 +170,15 @@ func (_c *MockChatSessionManager_Empty_Call) RunAndReturn(run func() bool) *Mock
 }
 
 // NewSessionWithSN provides a mock function with given fields: sessID, screenName
-func (_m *MockChatSessionManager) NewSessionWithSN(sessID string, screenName string) *Session {
+func (_m *MockChatSessionManager) NewSessionWithSN(sessID string, screenName string) *state.Session {
 	ret := _m.Called(sessID, screenName)
 
-	var r0 *Session
-	if rf, ok := ret.Get(0).(func(string, string) *Session); ok {
+	var r0 *state.Session
+	if rf, ok := ret.Get(0).(func(string, string) *state.Session); ok {
 		r0 = rf(sessID, screenName)
 	} else {
 		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(*Session)
+			r0 = ret.Get(0).(*state.Session)
 		}
 	}
 
@@ -202,26 +204,26 @@ func (_c *MockChatSessionManager_NewSessionWithSN_Call) Run(run func(sessID stri
 	return _c
 }
 
-func (_c *MockChatSessionManager_NewSessionWithSN_Call) Return(_a0 *Session) *MockChatSessionManager_NewSessionWithSN_Call {
+func (_c *MockChatSessionManager_NewSessionWithSN_Call) Return(_a0 *state.Session) *MockChatSessionManager_NewSessionWithSN_Call {
 	_c.Call.Return(_a0)
 	return _c
 }
 
-func (_c *MockChatSessionManager_NewSessionWithSN_Call) RunAndReturn(run func(string, string) *Session) *MockChatSessionManager_NewSessionWithSN_Call {
+func (_c *MockChatSessionManager_NewSessionWithSN_Call) RunAndReturn(run func(string, string) *state.Session) *MockChatSessionManager_NewSessionWithSN_Call {
 	_c.Call.Return(run)
 	return _c
 }
 
 // Participants provides a mock function with given fields:
-func (_m *MockChatSessionManager) Participants() []*Session {
+func (_m *MockChatSessionManager) Participants() []*state.Session {
 	ret := _m.Called()
 
-	var r0 []*Session
-	if rf, ok := ret.Get(0).(func() []*Session); ok {
+	var r0 []*state.Session
+	if rf, ok := ret.Get(0).(func() []*state.Session); ok {
 		r0 = rf()
 	} else {
 		if ret.Get(0) != nil {
-			r0 = ret.Get(0).([]*Session)
+			r0 = ret.Get(0).([]*state.Session)
 		}
 	}
 
@@ -245,18 +247,18 @@ func (_c *MockChatSessionManager_Participants_Call) Run(run func()) *MockChatSes
 	return _c
 }
 
-func (_c *MockChatSessionManager_Participants_Call) Return(_a0 []*Session) *MockChatSessionManager_Participants_Call {
+func (_c *MockChatSessionManager_Participants_Call) Return(_a0 []*state.Session) *MockChatSessionManager_Participants_Call {
 	_c.Call.Return(_a0)
 	return _c
 }
 
-func (_c *MockChatSessionManager_Participants_Call) RunAndReturn(run func() []*Session) *MockChatSessionManager_Participants_Call {
+func (_c *MockChatSessionManager_Participants_Call) RunAndReturn(run func() []*state.Session) *MockChatSessionManager_Participants_Call {
 	_c.Call.Return(run)
 	return _c
 }
 
 // Remove provides a mock function with given fields: sess
-func (_m *MockChatSessionManager) Remove(sess *Session) {
+func (_m *MockChatSessionManager) Remove(sess *state.Session) {
 	_m.Called(sess)
 }
 
@@ -266,14 +268,14 @@ type MockChatSessionManager_Remove_Call struct {
 }
 
 // Remove is a helper method to define mock.On call
-//   - sess *Session
+//   - sess *state.Session
 func (_e *MockChatSessionManager_Expecter) Remove(sess interface{}) *MockChatSessionManager_Remove_Call {
 	return &MockChatSessionManager_Remove_Call{Call: _e.mock.On("Remove", sess)}
 }
 
-func (_c *MockChatSessionManager_Remove_Call) Run(run func(sess *Session)) *MockChatSessionManager_Remove_Call {
+func (_c *MockChatSessionManager_Remove_Call) Run(run func(sess *state.Session)) *MockChatSessionManager_Remove_Call {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(*Session))
+		run(args[0].(*state.Session))
 	})
 	return _c
 }
@@ -283,25 +285,25 @@ func (_c *MockChatSessionManager_Remove_Call) Return() *MockChatSessionManager_R
 	return _c
 }
 
-func (_c *MockChatSessionManager_Remove_Call) RunAndReturn(run func(*Session)) *MockChatSessionManager_Remove_Call {
+func (_c *MockChatSessionManager_Remove_Call) RunAndReturn(run func(*state.Session)) *MockChatSessionManager_Remove_Call {
 	_c.Call.Return(run)
 	return _c
 }
 
 // Retrieve provides a mock function with given fields: ID
-func (_m *MockChatSessionManager) Retrieve(ID string) (*Session, bool) {
+func (_m *MockChatSessionManager) Retrieve(ID string) (*state.Session, bool) {
 	ret := _m.Called(ID)
 
-	var r0 *Session
+	var r0 *state.Session
 	var r1 bool
-	if rf, ok := ret.Get(0).(func(string) (*Session, bool)); ok {
+	if rf, ok := ret.Get(0).(func(string) (*state.Session, bool)); ok {
 		return rf(ID)
 	}
-	if rf, ok := ret.Get(0).(func(string) *Session); ok {
+	if rf, ok := ret.Get(0).(func(string) *state.Session); ok {
 		r0 = rf(ID)
 	} else {
 		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(*Session)
+			r0 = ret.Get(0).(*state.Session)
 		}
 	}
 
@@ -332,26 +334,26 @@ func (_c *MockChatSessionManager_Retrieve_Call) Run(run func(ID string)) *MockCh
 	return _c
 }
 
-func (_c *MockChatSessionManager_Retrieve_Call) Return(_a0 *Session, _a1 bool) *MockChatSessionManager_Retrieve_Call {
+func (_c *MockChatSessionManager_Retrieve_Call) Return(_a0 *state.Session, _a1 bool) *MockChatSessionManager_Retrieve_Call {
 	_c.Call.Return(_a0, _a1)
 	return _c
 }
 
-func (_c *MockChatSessionManager_Retrieve_Call) RunAndReturn(run func(string) (*Session, bool)) *MockChatSessionManager_Retrieve_Call {
+func (_c *MockChatSessionManager_Retrieve_Call) RunAndReturn(run func(string) (*state.Session, bool)) *MockChatSessionManager_Retrieve_Call {
 	_c.Call.Return(run)
 	return _c
 }
 
 // RetrieveByScreenName provides a mock function with given fields: screenName
-func (_m *MockChatSessionManager) RetrieveByScreenName(screenName string) *Session {
+func (_m *MockChatSessionManager) RetrieveByScreenName(screenName string) *state.Session {
 	ret := _m.Called(screenName)
 
-	var r0 *Session
-	if rf, ok := ret.Get(0).(func(string) *Session); ok {
+	var r0 *state.Session
+	if rf, ok := ret.Get(0).(func(string) *state.Session); ok {
 		r0 = rf(screenName)
 	} else {
 		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(*Session)
+			r0 = ret.Get(0).(*state.Session)
 		}
 	}
 
@@ -376,12 +378,12 @@ func (_c *MockChatSessionManager_RetrieveByScreenName_Call) Run(run func(screenN
 	return _c
 }
 
-func (_c *MockChatSessionManager_RetrieveByScreenName_Call) Return(_a0 *Session) *MockChatSessionManager_RetrieveByScreenName_Call {
+func (_c *MockChatSessionManager_RetrieveByScreenName_Call) Return(_a0 *state.Session) *MockChatSessionManager_RetrieveByScreenName_Call {
 	_c.Call.Return(_a0)
 	return _c
 }
 
-func (_c *MockChatSessionManager_RetrieveByScreenName_Call) RunAndReturn(run func(string) *Session) *MockChatSessionManager_RetrieveByScreenName_Call {
+func (_c *MockChatSessionManager_RetrieveByScreenName_Call) RunAndReturn(run func(string) *state.Session) *MockChatSessionManager_RetrieveByScreenName_Call {
 	_c.Call.Return(run)
 	return _c
 }

+ 13 - 12
handler/chat_test.go

@@ -3,7 +3,7 @@ package handler
 import (
 	"context"
 	"github.com/mkaminski/goaim/oscar"
-	"github.com/mkaminski/goaim/server"
+	"github.com/mkaminski/goaim/state"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/mock"
 	"testing"
@@ -14,7 +14,7 @@ func TestSendAndReceiveChatChannelMsgToHost(t *testing.T) {
 		// name is the unit test name
 		name string
 		// userSession is the session of the user sending the chat message
-		userSession *server.Session
+		userSession *state.Session
 		// inputSNAC is the SNAC sent by the sender client
 		inputSNAC oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost
 		// expectSNACToParticipants is the message the server broadcast to chat
@@ -115,17 +115,18 @@ func TestSendAndReceiveChatChannelMsgToHost(t *testing.T) {
 
 	for _, tc := range cases {
 		t.Run(tc.name, func(t *testing.T) {
-			//
-			// initialize dependencies
-			//
-			crm := server.NewMockChatSessionManager(t)
-			crm.EXPECT().
+			chatID := "the-chat-id"
+
+			chatSessMgr := NewMockChatSessionManager(t)
+			chatSessMgr.EXPECT().
 				BroadcastExcept(mock.Anything, tc.userSession, tc.expectSNACToParticipants)
-			//
-			// send input SNAC
-			//
-			svc := ChatService{}
-			outputSNAC, err := svc.ChannelMsgToHostHandler(context.Background(), tc.userSession, crm, tc.inputSNAC)
+
+			svc := ChatService{
+				chatRegistry: state.NewChatRegistry(),
+			}
+			svc.chatRegistry.Register(state.ChatRoom{Cookie: chatID}, chatSessMgr)
+
+			outputSNAC, err := svc.ChannelMsgToHostHandler(context.Background(), tc.userSession, chatID, tc.inputSNAC)
 			assert.NoError(t, err)
 
 			if tc.expectOutput.SnacFrame == (oscar.SnacFrame{}) {

+ 9 - 9
handler/feedbag.go

@@ -3,17 +3,17 @@ package handler
 import (
 	"context"
 	"github.com/mkaminski/goaim/oscar"
-	"github.com/mkaminski/goaim/server"
+	"github.com/mkaminski/goaim/state"
 	"time"
 )
 
-func NewFeedbagService(sm server.SessionManager, fm server.FeedbagManager) *FeedbagService {
+func NewFeedbagService(sm SessionManager, fm FeedbagManager) *FeedbagService {
 	return &FeedbagService{sm: sm, fm: fm}
 }
 
 type FeedbagService struct {
-	sm server.SessionManager
-	fm server.FeedbagManager
+	sm SessionManager
+	fm FeedbagManager
 }
 
 func (s FeedbagService) RightsQueryHandler(context.Context) oscar.XMessage {
@@ -64,7 +64,7 @@ func (s FeedbagService) RightsQueryHandler(context.Context) oscar.XMessage {
 	}
 }
 
-func (s FeedbagService) QueryHandler(_ context.Context, sess *server.Session) (oscar.XMessage, error) {
+func (s FeedbagService) QueryHandler(_ context.Context, sess *state.Session) (oscar.XMessage, error) {
 	fb, err := s.fm.Retrieve(sess.ScreenName())
 	if err != nil {
 		return oscar.XMessage{}, err
@@ -92,7 +92,7 @@ func (s FeedbagService) QueryHandler(_ context.Context, sess *server.Session) (o
 	}, nil
 }
 
-func (s FeedbagService) QueryIfModifiedHandler(_ context.Context, sess *server.Session, snacPayloadIn oscar.SNAC_0x13_0x05_FeedbagQueryIfModified) (oscar.XMessage, error) {
+func (s FeedbagService) QueryIfModifiedHandler(_ context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x13_0x05_FeedbagQueryIfModified) (oscar.XMessage, error) {
 	fb, err := s.fm.Retrieve(sess.ScreenName())
 	if err != nil {
 		return oscar.XMessage{}, err
@@ -132,7 +132,7 @@ func (s FeedbagService) QueryIfModifiedHandler(_ context.Context, sess *server.S
 	}, nil
 }
 
-func (s FeedbagService) InsertItemHandler(ctx context.Context, sess *server.Session, snacPayloadIn oscar.SNAC_0x13_0x08_FeedbagInsertItem) (oscar.XMessage, error) {
+func (s FeedbagService) InsertItemHandler(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x13_0x08_FeedbagInsertItem) (oscar.XMessage, error) {
 	for _, item := range snacPayloadIn.Items {
 		// don't let users block themselves, it causes the AIM client to go
 		// into a weird state.
@@ -179,7 +179,7 @@ func (s FeedbagService) InsertItemHandler(ctx context.Context, sess *server.Sess
 	}, nil
 }
 
-func (s FeedbagService) UpdateItemHandler(ctx context.Context, sess *server.Session, snacPayloadIn oscar.SNAC_0x13_0x09_FeedbagUpdateItem) (oscar.XMessage, error) {
+func (s FeedbagService) UpdateItemHandler(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x13_0x09_FeedbagUpdateItem) (oscar.XMessage, error) {
 	if err := s.fm.Upsert(sess.ScreenName(), snacPayloadIn.Items); err != nil {
 		return oscar.XMessage{}, nil
 	}
@@ -205,7 +205,7 @@ func (s FeedbagService) UpdateItemHandler(ctx context.Context, sess *server.Sess
 	}, nil
 }
 
-func (s FeedbagService) DeleteItemHandler(ctx context.Context, sess *server.Session, snacPayloadIn oscar.SNAC_0x13_0x0A_FeedbagDeleteItem) (oscar.XMessage, error) {
+func (s FeedbagService) DeleteItemHandler(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x13_0x0A_FeedbagDeleteItem) (oscar.XMessage, error) {
 	if err := s.fm.Delete(sess.ScreenName(), snacPayloadIn.Items); err != nil {
 		return oscar.XMessage{}, err
 	}

+ 10 - 8
server/feedbag_manager_mock.go → handler/feedbag_manager_mock.go

@@ -1,11 +1,13 @@
 // Code generated by mockery v2.34.2. DO NOT EDIT.
 
-package server
+package handler
 
 import (
 	oscar "github.com/mkaminski/goaim/oscar"
 	mock "github.com/stretchr/testify/mock"
 
+	state "github.com/mkaminski/goaim/state"
+
 	time "time"
 )
 
@@ -23,18 +25,18 @@ func (_m *MockFeedbagManager) EXPECT() *MockFeedbagManager_Expecter {
 }
 
 // Blocked provides a mock function with given fields: sn1, sn2
-func (_m *MockFeedbagManager) Blocked(sn1 string, sn2 string) (BlockedState, error) {
+func (_m *MockFeedbagManager) Blocked(sn1 string, sn2 string) (state.BlockedState, error) {
 	ret := _m.Called(sn1, sn2)
 
-	var r0 BlockedState
+	var r0 state.BlockedState
 	var r1 error
-	if rf, ok := ret.Get(0).(func(string, string) (BlockedState, error)); ok {
+	if rf, ok := ret.Get(0).(func(string, string) (state.BlockedState, error)); ok {
 		return rf(sn1, sn2)
 	}
-	if rf, ok := ret.Get(0).(func(string, string) BlockedState); ok {
+	if rf, ok := ret.Get(0).(func(string, string) state.BlockedState); ok {
 		r0 = rf(sn1, sn2)
 	} else {
-		r0 = ret.Get(0).(BlockedState)
+		r0 = ret.Get(0).(state.BlockedState)
 	}
 
 	if rf, ok := ret.Get(1).(func(string, string) error); ok {
@@ -65,12 +67,12 @@ func (_c *MockFeedbagManager_Blocked_Call) Run(run func(sn1 string, sn2 string))
 	return _c
 }
 
-func (_c *MockFeedbagManager_Blocked_Call) Return(_a0 BlockedState, _a1 error) *MockFeedbagManager_Blocked_Call {
+func (_c *MockFeedbagManager_Blocked_Call) Return(_a0 state.BlockedState, _a1 error) *MockFeedbagManager_Blocked_Call {
 	_c.Call.Return(_a0, _a1)
 	return _c
 }
 
-func (_c *MockFeedbagManager_Blocked_Call) RunAndReturn(run func(string, string) (BlockedState, error)) *MockFeedbagManager_Blocked_Call {
+func (_c *MockFeedbagManager_Blocked_Call) RunAndReturn(run func(string, string) (state.BlockedState, error)) *MockFeedbagManager_Blocked_Call {
 	_c.Call.Return(run)
 	return _c
 }

+ 12 - 12
handler/feedbag_test.go

@@ -2,7 +2,7 @@ package handler
 
 import (
 	"github.com/mkaminski/goaim/oscar"
-	"github.com/mkaminski/goaim/server"
+	"github.com/mkaminski/goaim/state"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/mock"
 	"testing"
@@ -76,7 +76,7 @@ func TestQueryHandler(t *testing.T) {
 			//
 			// initialize dependencies
 			//
-			fm := server.NewMockFeedbagManager(t)
+			fm := NewMockFeedbagManager(t)
 			fm.EXPECT().
 				Retrieve(tc.screenName).
 				Return(tc.feedbagItems, nil).
@@ -200,7 +200,7 @@ func TestQueryIfModifiedHandler(t *testing.T) {
 			//
 			// initialize dependencies
 			//
-			fm := server.NewMockFeedbagManager(t)
+			fm := NewMockFeedbagManager(t)
 			fm.EXPECT().
 				Retrieve(tc.screenName).
 				Return(tc.feedbagItems, nil).
@@ -231,14 +231,14 @@ func TestInsertItemHandler(t *testing.T) {
 		// name is the unit test name
 		name string
 		// userSession is the session of the user managing buddy list
-		userSession *server.Session
+		userSession *state.Session
 		// feedbagItems is the list of items in user's buddy list
 		feedbagItems []oscar.FeedbagItem
 		// inputSNAC is the SNAC sent by the sender client
 		inputSNAC oscar.SNAC_0x13_0x08_FeedbagInsertItem
 		// screenNameLookups is the list of user's online buddies
 		screenNameLookups map[string]struct {
-			sess *server.Session
+			sess *state.Session
 			err  error
 		}
 		// clientResponse is the message returned to the client
@@ -265,7 +265,7 @@ func TestInsertItemHandler(t *testing.T) {
 				},
 			},
 			screenNameLookups: map[string]struct {
-				sess *server.Session
+				sess *state.Session
 				err  error
 			}{
 				"user_screen_name": {
@@ -339,7 +339,7 @@ func TestInsertItemHandler(t *testing.T) {
 				},
 			},
 			screenNameLookups: map[string]struct {
-				sess *server.Session
+				sess *state.Session
 				err  error
 			}{
 				"buddy_offline": {
@@ -368,7 +368,7 @@ func TestInsertItemHandler(t *testing.T) {
 				},
 			},
 			screenNameLookups: map[string]struct {
-				sess *server.Session
+				sess *state.Session
 				err  error
 			}{
 				"invisible_buddy_online": {
@@ -398,7 +398,7 @@ func TestInsertItemHandler(t *testing.T) {
 				},
 			},
 			screenNameLookups: map[string]struct {
-				sess *server.Session
+				sess *state.Session
 				err  error
 			}{
 				"user_screen_name": {
@@ -465,7 +465,7 @@ func TestInsertItemHandler(t *testing.T) {
 				},
 			},
 			screenNameLookups: map[string]struct {
-				sess *server.Session
+				sess *state.Session
 				err  error
 			}{
 				"user_screen_name": {
@@ -533,7 +533,7 @@ func TestInsertItemHandler(t *testing.T) {
 			//
 			// initialize dependencies
 			//
-			fm := server.NewMockFeedbagManager(t)
+			fm := NewMockFeedbagManager(t)
 			fm.EXPECT().
 				Upsert(tc.userSession.ScreenName(), tc.inputSNAC.Items).
 				Return(nil).
@@ -542,7 +542,7 @@ func TestInsertItemHandler(t *testing.T) {
 				Buddies(tc.userSession.ScreenName()).
 				Return([]string{}, nil).
 				Maybe()
-			sm := server.NewMockSessionManager(t)
+			sm := NewMockSessionManager(t)
 			for screenName, val := range tc.screenNameLookups {
 				sm.EXPECT().
 					RetrieveByScreenName(screenName).

+ 11 - 11
handler/icbm.go

@@ -3,7 +3,7 @@ package handler
 import (
 	"context"
 	"github.com/mkaminski/goaim/oscar"
-	"github.com/mkaminski/goaim/server"
+	"github.com/mkaminski/goaim/state"
 )
 
 const (
@@ -11,13 +11,13 @@ const (
 	evilDeltaAnon = uint16(30)
 )
 
-func NewICBMService(sm server.SessionManager, fm server.FeedbagManager) *ICBMService {
+func NewICBMService(sm SessionManager, fm FeedbagManager) *ICBMService {
 	return &ICBMService{sm: sm, fm: fm}
 }
 
 type ICBMService struct {
-	sm server.SessionManager
-	fm server.FeedbagManager
+	sm SessionManager
+	fm FeedbagManager
 }
 
 func (s ICBMService) ParameterQueryHandler(context.Context) oscar.XMessage {
@@ -37,15 +37,15 @@ func (s ICBMService) ParameterQueryHandler(context.Context) oscar.XMessage {
 	}
 }
 
-func (s ICBMService) ChannelMsgToHostHandler(ctx context.Context, sess *server.Session, snacPayloadIn oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost) (*oscar.XMessage, error) {
+func (s ICBMService) ChannelMsgToHostHandler(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost) (*oscar.XMessage, error) {
 	blocked, err := s.fm.Blocked(sess.ScreenName(), snacPayloadIn.ScreenName)
 	if err != nil {
 		return nil, err
 	}
 
-	if blocked != server.BlockedNo {
+	if blocked != state.BlockedNo {
 		code := oscar.ErrorCodeNotLoggedOn
-		if blocked == server.BlockedA {
+		if blocked == state.BlockedA {
 			code = oscar.ErrorCodeInLocalPermitDeny
 		}
 		return &oscar.XMessage{
@@ -120,13 +120,13 @@ func (s ICBMService) ChannelMsgToHostHandler(ctx context.Context, sess *server.S
 	}, nil
 }
 
-func (s ICBMService) ClientEventHandler(ctx context.Context, sess *server.Session, snacPayloadIn oscar.SNAC_0x04_0x14_ICBMClientEvent) error {
+func (s ICBMService) ClientEventHandler(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x04_0x14_ICBMClientEvent) error {
 	blocked, err := s.fm.Blocked(sess.ScreenName(), snacPayloadIn.ScreenName)
 
 	switch {
 	case err != nil:
 		return err
-	case blocked != server.BlockedNo:
+	case blocked != state.BlockedNo:
 		return nil
 	default:
 		s.sm.SendToScreenName(ctx, snacPayloadIn.ScreenName, oscar.XMessage{
@@ -145,7 +145,7 @@ func (s ICBMService) ClientEventHandler(ctx context.Context, sess *server.Sessio
 	}
 }
 
-func (s ICBMService) EvilRequestHandler(ctx context.Context, sess *server.Session, snacPayloadIn oscar.SNAC_0x04_0x08_ICBMEvilRequest) (oscar.XMessage, error) {
+func (s ICBMService) EvilRequestHandler(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x04_0x08_ICBMEvilRequest) (oscar.XMessage, error) {
 	// don't let users warn themselves, it causes the AIM client to go into a
 	// weird state.
 	if snacPayloadIn.ScreenName == sess.ScreenName() {
@@ -164,7 +164,7 @@ func (s ICBMService) EvilRequestHandler(ctx context.Context, sess *server.Sessio
 	if err != nil {
 		return oscar.XMessage{}, nil
 	}
-	if blocked != server.BlockedNo {
+	if blocked != state.BlockedNo {
 		return oscar.XMessage{
 			SnacFrame: oscar.SnacFrame{
 				FoodGroup: oscar.ICBM,

+ 25 - 25
handler/icbm_test.go

@@ -2,7 +2,7 @@ package handler
 
 import (
 	"github.com/mkaminski/goaim/oscar"
-	"github.com/mkaminski/goaim/server"
+	"github.com/mkaminski/goaim/state"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/mock"
 	"testing"
@@ -13,12 +13,12 @@ func TestSendAndReceiveChannelMsgTohost(t *testing.T) {
 		// name is the unit test name
 		name string
 		// blockedState is the response to the sender/recipient block check
-		blockedState server.BlockedState
+		blockedState state.BlockedState
 		// recipRetrieveErr is the error returned by the recipient session
 		// lookup
 		recipRetrieveErr error
-		senderSession    *server.Session
-		recipientSession *server.Session
+		senderSession    *state.Session
+		recipientSession *state.Session
 		// inputSNAC is the SNAC sent by the sender client
 		inputSNAC oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost
 		// expectSNACToClient is the SNAC sent from the server to the
@@ -30,7 +30,7 @@ func TestSendAndReceiveChannelMsgTohost(t *testing.T) {
 	}{
 		{
 			name:             "transmit message from sender to recipient, ack message back to sender",
-			blockedState:     server.BlockedNo,
+			blockedState:     state.BlockedNo,
 			senderSession:    newTestSession("sender-screen-name", sessOptWarning(10)),
 			recipientSession: newTestSession("recipient-screen-name", sessOptWarning(20)),
 			inputSNAC: oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost{
@@ -80,7 +80,7 @@ func TestSendAndReceiveChannelMsgTohost(t *testing.T) {
 		},
 		{
 			name:             "transmit message from sender to recipient, don't ack message back to sender",
-			blockedState:     server.BlockedNo,
+			blockedState:     state.BlockedNo,
 			senderSession:    newTestSession("sender-screen-name", sessOptWarning(10)),
 			recipientSession: newTestSession("recipient-screen-name", sessOptWarning(20)),
 			inputSNAC: oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost{
@@ -113,7 +113,7 @@ func TestSendAndReceiveChannelMsgTohost(t *testing.T) {
 		},
 		{
 			name:             "don't transmit message from sender to recipient because sender has blocked recipient",
-			blockedState:     server.BlockedA,
+			blockedState:     state.BlockedA,
 			senderSession:    newTestSession("sender-screen-name", sessOptWarning(10)),
 			recipientSession: newTestSession("recipient-screen-name", sessOptWarning(20)),
 			inputSNAC: oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost{
@@ -139,7 +139,7 @@ func TestSendAndReceiveChannelMsgTohost(t *testing.T) {
 		},
 		{
 			name:             "don't transmit message from sender to recipient because recipient has blocked sender",
-			blockedState:     server.BlockedB,
+			blockedState:     state.BlockedB,
 			senderSession:    newTestSession("sender-screen-name", sessOptWarning(10)),
 			recipientSession: newTestSession("recipient-screen-name", sessOptWarning(20)),
 			inputSNAC: oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost{
@@ -165,7 +165,7 @@ func TestSendAndReceiveChannelMsgTohost(t *testing.T) {
 		},
 		{
 			name:             "don't transmit message from sender to recipient because recipient doesn't exist",
-			blockedState:     server.BlockedNo,
+			blockedState:     state.BlockedNo,
 			senderSession:    newTestSession("sender-screen-name", sessOptWarning(10)),
 			recipientSession: nil,
 			inputSNAC: oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost{
@@ -196,12 +196,12 @@ func TestSendAndReceiveChannelMsgTohost(t *testing.T) {
 			//
 			// initialize dependencies
 			//
-			fm := server.NewMockFeedbagManager(t)
+			fm := NewMockFeedbagManager(t)
 			fm.EXPECT().
 				Blocked(tc.senderSession.ScreenName(), tc.inputSNAC.ScreenName).
 				Return(tc.blockedState, nil).
 				Maybe()
-			sm := server.NewMockSessionManager(t)
+			sm := NewMockSessionManager(t)
 			sm.EXPECT().
 				RetrieveByScreenName(tc.inputSNAC.ScreenName).
 				Return(tc.recipientSession).
@@ -233,7 +233,7 @@ func TestSendAndReceiveClientEvent(t *testing.T) {
 		// name is the unit test name
 		name string
 		// blockedState is the response to the sender/recipient block check
-		blockedState server.BlockedState
+		blockedState state.BlockedState
 		// senderScreenName is the screen name of the user sending the event
 		senderScreenName string
 		// inputSNAC is the SNAC sent by the sender client
@@ -244,7 +244,7 @@ func TestSendAndReceiveClientEvent(t *testing.T) {
 	}{
 		{
 			name:             "transmit message from sender to recipient",
-			blockedState:     server.BlockedNo,
+			blockedState:     state.BlockedNo,
 			senderScreenName: "sender-screen-name",
 			inputSNAC: oscar.SNAC_0x04_0x14_ICBMClientEvent{
 				Cookie:     [8]byte{1, 2, 3, 4, 5, 6, 7, 8},
@@ -267,7 +267,7 @@ func TestSendAndReceiveClientEvent(t *testing.T) {
 		},
 		{
 			name:             "don't transmit message from sender to recipient because sender has blocked recipient",
-			blockedState:     server.BlockedA,
+			blockedState:     state.BlockedA,
 			senderScreenName: "sender-screen-name",
 			inputSNAC: oscar.SNAC_0x04_0x14_ICBMClientEvent{
 				ScreenName: "recipient-screen-name",
@@ -280,13 +280,13 @@ func TestSendAndReceiveClientEvent(t *testing.T) {
 			//
 			// initialize dependencies
 			//
-			fm := server.NewMockFeedbagManager(t)
+			fm := NewMockFeedbagManager(t)
 			fm.EXPECT().
 				Blocked(tc.senderScreenName, tc.inputSNAC.ScreenName).
 				Return(tc.blockedState, nil).
 				Maybe()
-			sm := server.NewMockSessionManager(t)
-			if tc.blockedState == server.BlockedNo {
+			sm := NewMockSessionManager(t)
+			if tc.blockedState == state.BlockedNo {
 				sm.EXPECT().
 					SendToScreenName(mock.Anything, tc.inputSNAC.ScreenName, tc.expectSNACToClient)
 			}
@@ -308,12 +308,12 @@ func TestSendAndReceiveEvilRequest(t *testing.T) {
 		// name is the unit test name
 		name string
 		// blockedState is the response to the sender/recipient block check
-		blockedState server.BlockedState
+		blockedState state.BlockedState
 		// recipRetrieveErr is the error returned by the recipient session
 		// lookup
 		recipRetrieveErr error
 		// senderScreenName is the session name of the user sending the IM
-		senderSession *server.Session
+		senderSession *state.Session
 		// recipientScreenName is the screen name of the user receiving the IM
 		recipientScreenName string
 		// recipientBuddies is a list of the recipient's buddies that get
@@ -330,7 +330,7 @@ func TestSendAndReceiveEvilRequest(t *testing.T) {
 	}{
 		{
 			name:                "transmit anonymous warning from sender to recipient",
-			blockedState:        server.BlockedNo,
+			blockedState:        state.BlockedNo,
 			senderSession:       newTestSession("sender-screen-name"),
 			recipientScreenName: "recipient-screen-name",
 			broadcastMessage: oscar.XMessage{
@@ -375,7 +375,7 @@ func TestSendAndReceiveEvilRequest(t *testing.T) {
 		},
 		{
 			name:                "transmit non-anonymous warning from sender to recipient",
-			blockedState:        server.BlockedNo,
+			blockedState:        state.BlockedNo,
 			senderSession:       newTestSession("sender-screen-name"),
 			recipientScreenName: "recipient-screen-name",
 			recipientBuddies:    []string{"buddy1", "buddy2"},
@@ -424,7 +424,7 @@ func TestSendAndReceiveEvilRequest(t *testing.T) {
 		},
 		{
 			name:                "don't transmit non-anonymous warning from sender to recipient because sender has blocked recipient",
-			blockedState:        server.BlockedA,
+			blockedState:        state.BlockedA,
 			senderSession:       newTestSession("sender-screen-name"),
 			recipientScreenName: "recipient-screen-name",
 			recipientBuddies:    []string{"buddy1", "buddy2"},
@@ -444,7 +444,7 @@ func TestSendAndReceiveEvilRequest(t *testing.T) {
 		},
 		{
 			name:                "don't transmit non-anonymous warning from sender to recipient because recipient has blocked sender",
-			blockedState:        server.BlockedB,
+			blockedState:        state.BlockedB,
 			senderSession:       newTestSession("sender-screen-name"),
 			recipientScreenName: "recipient-screen-name",
 			recipientBuddies:    []string{"buddy1", "buddy2"},
@@ -487,7 +487,7 @@ func TestSendAndReceiveEvilRequest(t *testing.T) {
 			//
 			// initialize dependencies
 			//
-			fm := server.NewMockFeedbagManager(t)
+			fm := NewMockFeedbagManager(t)
 			fm.EXPECT().
 				Blocked(tc.senderSession.ScreenName(), tc.recipientScreenName).
 				Return(tc.blockedState, nil).
@@ -497,7 +497,7 @@ func TestSendAndReceiveEvilRequest(t *testing.T) {
 				Return(tc.recipientBuddies, nil).
 				Maybe()
 			recipSess := newTestSession(tc.recipientScreenName, sessOptCannedSignonTime)
-			sm := server.NewMockSessionManager(t)
+			sm := NewMockSessionManager(t)
 			sm.EXPECT().
 				RetrieveByScreenName(tc.recipientScreenName).
 				Return(recipSess).

+ 8 - 8
handler/locate.go

@@ -3,10 +3,10 @@ package handler
 import (
 	"context"
 	"github.com/mkaminski/goaim/oscar"
-	"github.com/mkaminski/goaim/server"
+	"github.com/mkaminski/goaim/state"
 )
 
-func NewLocateService(sm server.SessionManager, fm server.FeedbagManager, pm server.ProfileManager) LocateService {
+func NewLocateService(sm SessionManager, fm FeedbagManager, pm ProfileManager) LocateService {
 	return LocateService{
 		sm: sm,
 		fm: fm,
@@ -15,9 +15,9 @@ func NewLocateService(sm server.SessionManager, fm server.FeedbagManager, pm ser
 }
 
 type LocateService struct {
-	sm server.SessionManager
-	fm server.FeedbagManager
-	pm server.ProfileManager
+	sm SessionManager
+	fm FeedbagManager
+	pm ProfileManager
 }
 
 func (s LocateService) RightsQueryHandler(context.Context) oscar.XMessage {
@@ -40,7 +40,7 @@ func (s LocateService) RightsQueryHandler(context.Context) oscar.XMessage {
 	}
 }
 
-func (s LocateService) SetInfoHandler(ctx context.Context, sess *server.Session, snacPayloadIn oscar.SNAC_0x02_0x04_LocateSetInfo) error {
+func (s LocateService) SetInfoHandler(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x02_0x04_LocateSetInfo) error {
 	// update profile
 	if profile, hasProfile := snacPayloadIn.GetString(oscar.LocateTLVTagsInfoSigData); hasProfile {
 		if err := s.pm.UpsertProfile(sess.ScreenName(), profile); err != nil {
@@ -58,12 +58,12 @@ func (s LocateService) SetInfoHandler(ctx context.Context, sess *server.Session,
 	return nil
 }
 
-func (s LocateService) UserInfoQuery2Handler(_ context.Context, sess *server.Session, snacPayloadIn oscar.SNAC_0x02_0x15_LocateUserInfoQuery2) (oscar.XMessage, error) {
+func (s LocateService) UserInfoQuery2Handler(_ context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x02_0x15_LocateUserInfoQuery2) (oscar.XMessage, error) {
 	blocked, err := s.fm.Blocked(sess.ScreenName(), snacPayloadIn.ScreenName)
 	switch {
 	case err != nil:
 		return oscar.XMessage{}, err
-	case blocked != server.BlockedNo:
+	case blocked != state.BlockedNo:
 		return oscar.XMessage{
 			SnacFrame: oscar.SnacFrame{
 				FoodGroup: oscar.LOCATE,

+ 18 - 18
handler/locate_test.go

@@ -3,7 +3,7 @@ package handler
 import (
 	"context"
 	"github.com/mkaminski/goaim/oscar"
-	"github.com/mkaminski/goaim/server"
+	"github.com/mkaminski/goaim/state"
 	"github.com/stretchr/testify/assert"
 	"testing"
 )
@@ -13,10 +13,10 @@ func TestSendAndReceiveUserInfoQuery2(t *testing.T) {
 		// name is the unit test name
 		name string
 		// blockedState is the response to the sender/recipient block check
-		blockedState server.BlockedState
+		blockedState state.BlockedState
 		// screenNameLookups is the list of user session lookups
 		screenNameLookups map[string]struct {
-			sess *server.Session
+			sess *state.Session
 			err  error
 		}
 		// screenNameLookups is the list of user session lookups
@@ -25,16 +25,16 @@ func TestSendAndReceiveUserInfoQuery2(t *testing.T) {
 			err     error
 		}
 		// userSession is the session of the user requesting the user info
-		userSession *server.Session
+		userSession *state.Session
 		// inputSNAC is the SNAC sent by the sender client
 		inputSNAC    oscar.SNAC_0x02_0x15_LocateUserInfoQuery2
 		expectOutput oscar.XMessage
 	}{
 		{
 			name:         "request user info, expect user info response",
-			blockedState: server.BlockedNo,
+			blockedState: state.BlockedNo,
 			screenNameLookups: map[string]struct {
-				sess *server.Session
+				sess *state.Session
 				err  error
 			}{
 				"requested-user": {
@@ -64,9 +64,9 @@ func TestSendAndReceiveUserInfoQuery2(t *testing.T) {
 		},
 		{
 			name:         "request user info + profile, expect user info response + profile",
-			blockedState: server.BlockedNo,
+			blockedState: state.BlockedNo,
 			screenNameLookups: map[string]struct {
-				sess *server.Session
+				sess *state.Session
 				err  error
 			}{
 				"requested-user": {
@@ -110,9 +110,9 @@ func TestSendAndReceiveUserInfoQuery2(t *testing.T) {
 		},
 		{
 			name:         "request user info + profile, expect user info response + profile",
-			blockedState: server.BlockedNo,
+			blockedState: state.BlockedNo,
 			screenNameLookups: map[string]struct {
-				sess *server.Session
+				sess *state.Session
 				err  error
 			}{
 				"requested-user": {
@@ -156,9 +156,9 @@ func TestSendAndReceiveUserInfoQuery2(t *testing.T) {
 		},
 		{
 			name:         "request user info + away message, expect user info response + away message",
-			blockedState: server.BlockedNo,
+			blockedState: state.BlockedNo,
 			screenNameLookups: map[string]struct {
-				sess *server.Session
+				sess *state.Session
 				err  error
 			}{
 				"requested-user": {
@@ -194,7 +194,7 @@ func TestSendAndReceiveUserInfoQuery2(t *testing.T) {
 		},
 		{
 			name:         "request user info of user who blocked requester, expect not logged in error",
-			blockedState: server.BlockedB,
+			blockedState: state.BlockedB,
 			userSession:  newTestSession("user_screen_name"),
 			inputSNAC: oscar.SNAC_0x02_0x15_LocateUserInfoQuery2{
 				ScreenName: "requested-user",
@@ -211,9 +211,9 @@ func TestSendAndReceiveUserInfoQuery2(t *testing.T) {
 		},
 		{
 			name:         "request user info of user who does not exist, expect not logged in error",
-			blockedState: server.BlockedNo,
+			blockedState: state.BlockedNo,
 			screenNameLookups: map[string]struct {
-				sess *server.Session
+				sess *state.Session
 				err  error
 			}{
 				"non_existent_requested_user": {
@@ -238,19 +238,19 @@ func TestSendAndReceiveUserInfoQuery2(t *testing.T) {
 
 	for _, tc := range cases {
 		t.Run(tc.name, func(t *testing.T) {
-			fm := server.NewMockFeedbagManager(t)
+			fm := NewMockFeedbagManager(t)
 			fm.EXPECT().
 				Blocked(tc.userSession.ScreenName(), tc.inputSNAC.ScreenName).
 				Return(tc.blockedState, nil).
 				Maybe()
-			sm := server.NewMockSessionManager(t)
+			sm := NewMockSessionManager(t)
 			for screenName, val := range tc.screenNameLookups {
 				sm.EXPECT().
 					RetrieveByScreenName(screenName).
 					Return(val.sess).
 					Maybe()
 			}
-			pm := server.NewMockProfileManager(t)
+			pm := NewMockProfileManager(t)
 			for screenName, val := range tc.profileLookups {
 				pm.EXPECT().
 					RetrieveProfile(screenName).

+ 27 - 18
handler/oservice.go

@@ -7,17 +7,18 @@ import (
 	"fmt"
 	"github.com/mkaminski/goaim/oscar"
 	"github.com/mkaminski/goaim/server"
+	"github.com/mkaminski/goaim/state"
 	"time"
 )
 
-func NewOServiceService(cfg server.Config, sm server.SessionManager, fm server.FeedbagManager) *OServiceService {
+func NewOServiceService(cfg server.Config, sm SessionManager, fm FeedbagManager) *OServiceService {
 	return &OServiceService{cfg: cfg, sm: sm, fm: fm}
 }
 
 type OServiceService struct {
 	cfg server.Config
-	fm  server.FeedbagManager
-	sm  server.SessionManager
+	fm  FeedbagManager
+	sm  SessionManager
 }
 
 func (s OServiceService) ClientVersionsHandler(_ context.Context, snacPayloadIn oscar.SNAC_0x01_0x17_OServiceClientVersions) oscar.XMessage {
@@ -99,7 +100,7 @@ func (s OServiceService) RateParamsQueryHandler(_ context.Context) oscar.XMessag
 	}
 }
 
-func (s OServiceService) UserInfoQueryHandler(_ context.Context, sess *server.Session) oscar.XMessage {
+func (s OServiceService) UserInfoQueryHandler(_ context.Context, sess *state.Session) oscar.XMessage {
 	return oscar.XMessage{
 		SnacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.OSERVICE,
@@ -111,7 +112,7 @@ func (s OServiceService) UserInfoQueryHandler(_ context.Context, sess *server.Se
 	}
 }
 
-func (s OServiceService) SetUserInfoFieldsHandler(ctx context.Context, sess *server.Session, snacPayloadIn oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (oscar.XMessage, error) {
+func (s OServiceService) SetUserInfoFieldsHandler(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (oscar.XMessage, error) {
 	if status, hasStatus := snacPayloadIn.GetUint32(0x06); hasStatus {
 		switch status {
 		case 0x000:
@@ -139,7 +140,7 @@ func (s OServiceService) SetUserInfoFieldsHandler(ctx context.Context, sess *ser
 	}, nil
 }
 
-func (s OServiceService) IdleNotificationHandler(ctx context.Context, sess *server.Session, snacPayloadIn oscar.SNAC_0x01_0x11_OServiceIdleNotification) error {
+func (s OServiceService) IdleNotificationHandler(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x01_0x11_OServiceIdleNotification) error {
 	if snacPayloadIn.IdleTime == 0 {
 		sess.SetActive()
 	} else {
@@ -153,16 +154,16 @@ func (s OServiceService) IdleNotificationHandler(ctx context.Context, sess *serv
 func (s OServiceService) RateParamsSubAddHandler(context.Context, oscar.SNAC_0x01_0x08_OServiceRateParamsSubAdd) {
 }
 
-func NewOServiceServiceForBOS(oserviceService OServiceService, cr *server.ChatRegistry) *OServiceServiceForBOS {
+func NewOServiceServiceForBOS(oserviceService OServiceService, cr *state.ChatRegistry) *OServiceServiceForBOS {
 	return &OServiceServiceForBOS{OServiceService: oserviceService, cr: cr}
 }
 
 type OServiceServiceForBOS struct {
 	OServiceService
-	cr *server.ChatRegistry
+	cr *state.ChatRegistry
 }
 
-func (s OServiceServiceForBOS) ServiceRequestHandler(_ context.Context, sess *server.Session, snacPayloadIn oscar.SNAC_0x01_0x04_OServiceServiceRequest) (oscar.XMessage, error) {
+func (s OServiceServiceForBOS) ServiceRequestHandler(_ context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x01_0x04_OServiceServiceRequest) (oscar.XMessage, error) {
 	if snacPayloadIn.FoodGroup != oscar.CHAT {
 		return oscar.XMessage{}, server.ErrUnsupportedSubGroup
 	}
@@ -181,7 +182,7 @@ func (s OServiceServiceForBOS) ServiceRequestHandler(_ context.Context, sess *se
 	if err != nil {
 		return oscar.XMessage{}, server.ErrUnsupportedSubGroup
 	}
-	chatSessMgr.NewSessionWithSN(sess.ID(), sess.ScreenName())
+	chatSessMgr.(ChatSessionManager).NewSessionWithSN(sess.ID(), sess.ScreenName())
 
 	return oscar.XMessage{
 		SnacFrame: oscar.SnacFrame{
@@ -225,7 +226,7 @@ func (s OServiceServiceForBOS) WriteOServiceHostOnline() oscar.XMessage {
 	}
 }
 
-func (s OServiceServiceForBOS) ClientOnlineHandler(ctx context.Context, _ oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *server.Session) error {
+func (s OServiceServiceForBOS) ClientOnlineHandler(ctx context.Context, _ oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *state.Session) error {
 	if err := broadcastArrival(ctx, sess, s.sm, s.fm); err != nil {
 		return err
 	}
@@ -239,15 +240,19 @@ func (s OServiceServiceForBOS) ClientOnlineHandler(ctx context.Context, _ oscar.
 	return nil
 }
 
-func NewOServiceServiceForChat(oserviceService OServiceService) *OServiceServiceForChat {
-	return &OServiceServiceForChat{OServiceService: oserviceService}
+func NewOServiceServiceForChat(oserviceService OServiceService, chatRegistry *state.ChatRegistry) *OServiceServiceForChat {
+	return &OServiceServiceForChat{
+		OServiceService: oserviceService,
+		chatRegistry:    chatRegistry,
+	}
 }
 
 type OServiceServiceForChat struct {
 	OServiceService
+	chatRegistry *state.ChatRegistry
 }
 
-func (s OServiceServiceForChat) ServiceRequestHandler(_ context.Context, _ *server.Session, _ oscar.SNAC_0x01_0x04_OServiceServiceRequest) (oscar.XMessage, error) {
+func (s OServiceServiceForChat) ServiceRequestHandler(_ context.Context, _ *state.Session, _ oscar.SNAC_0x01_0x04_OServiceServiceRequest) (oscar.XMessage, error) {
 	return oscar.XMessage{}, server.ErrUnsupportedSubGroup
 }
 
@@ -263,9 +268,13 @@ func (s OServiceServiceForChat) WriteOServiceHostOnline() oscar.XMessage {
 	}
 }
 
-func (s OServiceServiceForChat) ClientOnlineHandler(ctx context.Context, _ oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *server.Session, chatSessMgr server.ChatSessionManager, room server.ChatRoom) error {
-	sendChatRoomInfoUpdate(ctx, sess, chatSessMgr, room)
-	alertUserJoined(ctx, sess, chatSessMgr)
-	setOnlineChatUsers(ctx, sess, chatSessMgr)
+func (s OServiceServiceForChat) ClientOnlineHandler(ctx context.Context, _ oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *state.Session, chatID string) error {
+	room, chatSessMgr, err := s.chatRegistry.Retrieve(chatID)
+	if err != nil {
+		return err
+	}
+	sendChatRoomInfoUpdate(ctx, sess, chatSessMgr.(ChatSessionManager), room)
+	alertUserJoined(ctx, sess, chatSessMgr.(ChatSessionManager))
+	setOnlineChatUsers(ctx, sess, chatSessMgr.(ChatSessionManager))
 	return nil
 }

+ 9 - 8
handler/oservice_test.go

@@ -4,6 +4,7 @@ import (
 	"bytes"
 	"github.com/mkaminski/goaim/oscar"
 	"github.com/mkaminski/goaim/server"
+	"github.com/mkaminski/goaim/state"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/mock"
 	"testing"
@@ -17,10 +18,10 @@ func TestReceiveAndSendServiceRequest(t *testing.T) {
 		// cfg is the application config
 		cfg server.Config
 		// chatRoom is the chat room the user connects to
-		chatRoom *server.ChatRoom
+		chatRoom *state.ChatRoom
 		// userSession is the session of the user requesting the chat service
 		// info
-		userSession *server.Session
+		userSession *state.Session
 		// inputSNAC is the SNAC sent by the sender client
 		inputSNAC oscar.SNAC_0x01_0x04_OServiceServiceRequest
 		// expectSNACFrame is the SNAC frame sent from the server to the recipient
@@ -43,7 +44,7 @@ func TestReceiveAndSendServiceRequest(t *testing.T) {
 				OSCARHost: "127.0.0.1",
 				ChatPort:  1234,
 			},
-			chatRoom: &server.ChatRoom{
+			chatRoom: &state.ChatRoom{
 				CreateTime:     time.UnixMilli(0),
 				DetailLevel:    4,
 				Exchange:       8,
@@ -114,12 +115,12 @@ func TestReceiveAndSendServiceRequest(t *testing.T) {
 			//
 			// initialize dependencies
 			//
-			sm := server.NewMockChatSessionManager(t)
-			cr := server.NewChatRegistry()
+			sm := NewMockChatSessionManager(t)
+			cr := state.NewChatRegistry()
 			if tc.chatRoom != nil {
 				sm.EXPECT().
 					NewSessionWithSN(tc.userSession.ID(), tc.userSession.ScreenName()).
-					Return(&server.Session{}).
+					Return(&state.Session{}).
 					Maybe()
 				cr.Register(*tc.chatRoom, sm)
 			}
@@ -680,7 +681,7 @@ func TestOServiceRouter_RouteOService_ForChat(t *testing.T) {
 				Return(tc.output, tc.handlerErr).
 				Maybe()
 			svcBOS.EXPECT().
-				ClientOnlineHandler(mock.Anything, tc.input.SnacOut, mock.Anything, mock.Anything, mock.Anything).
+				ClientOnlineHandler(mock.Anything, tc.input.SnacOut, mock.Anything, mock.Anything).
 				Return(tc.handlerErr).
 				Maybe()
 
@@ -700,7 +701,7 @@ func TestOServiceRouter_RouteOService_ForChat(t *testing.T) {
 			bufOut := &bytes.Buffer{}
 			seq := uint32(1)
 
-			err := router.RouteOService(nil, nil, nil, server.ChatRoom{}, tc.input.SnacFrame, bufIn, bufOut, &seq)
+			err := router.RouteOService(nil, nil, "", tc.input.SnacFrame, bufIn, bufOut, &seq)
 			assert.ErrorIs(t, err, tc.expectErr)
 			if tc.expectErr != nil {
 				return

+ 1 - 1
server/profile_manager_mock.go → handler/profile_manager_mock.go

@@ -1,6 +1,6 @@
 // Code generated by mockery v2.34.2. DO NOT EDIT.
 
-package server
+package handler
 
 import mock "github.com/stretchr/testify/mock"
 

+ 27 - 25
server/session_manager_mock.go → handler/session_manager_mock.go

@@ -1,12 +1,14 @@
 // Code generated by mockery v2.34.2. DO NOT EDIT.
 
-package server
+package handler
 
 import (
 	context "context"
 
 	oscar "github.com/mkaminski/goaim/oscar"
 	mock "github.com/stretchr/testify/mock"
+
+	state "github.com/mkaminski/goaim/state"
 )
 
 // MockSessionManager is an autogenerated mock type for the SessionManager type
@@ -133,15 +135,15 @@ func (_c *MockSessionManager_Empty_Call) RunAndReturn(run func() bool) *MockSess
 }
 
 // NewSessionWithSN provides a mock function with given fields: sessID, screenName
-func (_m *MockSessionManager) NewSessionWithSN(sessID string, screenName string) *Session {
+func (_m *MockSessionManager) NewSessionWithSN(sessID string, screenName string) *state.Session {
 	ret := _m.Called(sessID, screenName)
 
-	var r0 *Session
-	if rf, ok := ret.Get(0).(func(string, string) *Session); ok {
+	var r0 *state.Session
+	if rf, ok := ret.Get(0).(func(string, string) *state.Session); ok {
 		r0 = rf(sessID, screenName)
 	} else {
 		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(*Session)
+			r0 = ret.Get(0).(*state.Session)
 		}
 	}
 
@@ -167,18 +169,18 @@ func (_c *MockSessionManager_NewSessionWithSN_Call) Run(run func(sessID string,
 	return _c
 }
 
-func (_c *MockSessionManager_NewSessionWithSN_Call) Return(_a0 *Session) *MockSessionManager_NewSessionWithSN_Call {
+func (_c *MockSessionManager_NewSessionWithSN_Call) Return(_a0 *state.Session) *MockSessionManager_NewSessionWithSN_Call {
 	_c.Call.Return(_a0)
 	return _c
 }
 
-func (_c *MockSessionManager_NewSessionWithSN_Call) RunAndReturn(run func(string, string) *Session) *MockSessionManager_NewSessionWithSN_Call {
+func (_c *MockSessionManager_NewSessionWithSN_Call) RunAndReturn(run func(string, string) *state.Session) *MockSessionManager_NewSessionWithSN_Call {
 	_c.Call.Return(run)
 	return _c
 }
 
 // Remove provides a mock function with given fields: sess
-func (_m *MockSessionManager) Remove(sess *Session) {
+func (_m *MockSessionManager) Remove(sess *state.Session) {
 	_m.Called(sess)
 }
 
@@ -188,14 +190,14 @@ type MockSessionManager_Remove_Call struct {
 }
 
 // Remove is a helper method to define mock.On call
-//   - sess *Session
+//   - sess *state.Session
 func (_e *MockSessionManager_Expecter) Remove(sess interface{}) *MockSessionManager_Remove_Call {
 	return &MockSessionManager_Remove_Call{Call: _e.mock.On("Remove", sess)}
 }
 
-func (_c *MockSessionManager_Remove_Call) Run(run func(sess *Session)) *MockSessionManager_Remove_Call {
+func (_c *MockSessionManager_Remove_Call) Run(run func(sess *state.Session)) *MockSessionManager_Remove_Call {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(*Session))
+		run(args[0].(*state.Session))
 	})
 	return _c
 }
@@ -205,25 +207,25 @@ func (_c *MockSessionManager_Remove_Call) Return() *MockSessionManager_Remove_Ca
 	return _c
 }
 
-func (_c *MockSessionManager_Remove_Call) RunAndReturn(run func(*Session)) *MockSessionManager_Remove_Call {
+func (_c *MockSessionManager_Remove_Call) RunAndReturn(run func(*state.Session)) *MockSessionManager_Remove_Call {
 	_c.Call.Return(run)
 	return _c
 }
 
 // Retrieve provides a mock function with given fields: ID
-func (_m *MockSessionManager) Retrieve(ID string) (*Session, bool) {
+func (_m *MockSessionManager) Retrieve(ID string) (*state.Session, bool) {
 	ret := _m.Called(ID)
 
-	var r0 *Session
+	var r0 *state.Session
 	var r1 bool
-	if rf, ok := ret.Get(0).(func(string) (*Session, bool)); ok {
+	if rf, ok := ret.Get(0).(func(string) (*state.Session, bool)); ok {
 		return rf(ID)
 	}
-	if rf, ok := ret.Get(0).(func(string) *Session); ok {
+	if rf, ok := ret.Get(0).(func(string) *state.Session); ok {
 		r0 = rf(ID)
 	} else {
 		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(*Session)
+			r0 = ret.Get(0).(*state.Session)
 		}
 	}
 
@@ -254,26 +256,26 @@ func (_c *MockSessionManager_Retrieve_Call) Run(run func(ID string)) *MockSessio
 	return _c
 }
 
-func (_c *MockSessionManager_Retrieve_Call) Return(_a0 *Session, _a1 bool) *MockSessionManager_Retrieve_Call {
+func (_c *MockSessionManager_Retrieve_Call) Return(_a0 *state.Session, _a1 bool) *MockSessionManager_Retrieve_Call {
 	_c.Call.Return(_a0, _a1)
 	return _c
 }
 
-func (_c *MockSessionManager_Retrieve_Call) RunAndReturn(run func(string) (*Session, bool)) *MockSessionManager_Retrieve_Call {
+func (_c *MockSessionManager_Retrieve_Call) RunAndReturn(run func(string) (*state.Session, bool)) *MockSessionManager_Retrieve_Call {
 	_c.Call.Return(run)
 	return _c
 }
 
 // RetrieveByScreenName provides a mock function with given fields: screenName
-func (_m *MockSessionManager) RetrieveByScreenName(screenName string) *Session {
+func (_m *MockSessionManager) RetrieveByScreenName(screenName string) *state.Session {
 	ret := _m.Called(screenName)
 
-	var r0 *Session
-	if rf, ok := ret.Get(0).(func(string) *Session); ok {
+	var r0 *state.Session
+	if rf, ok := ret.Get(0).(func(string) *state.Session); ok {
 		r0 = rf(screenName)
 	} else {
 		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(*Session)
+			r0 = ret.Get(0).(*state.Session)
 		}
 	}
 
@@ -298,12 +300,12 @@ func (_c *MockSessionManager_RetrieveByScreenName_Call) Run(run func(screenName
 	return _c
 }
 
-func (_c *MockSessionManager_RetrieveByScreenName_Call) Return(_a0 *Session) *MockSessionManager_RetrieveByScreenName_Call {
+func (_c *MockSessionManager_RetrieveByScreenName_Call) Return(_a0 *state.Session) *MockSessionManager_RetrieveByScreenName_Call {
 	_c.Call.Return(_a0)
 	return _c
 }
 
-func (_c *MockSessionManager_RetrieveByScreenName_Call) RunAndReturn(run func(string) *Session) *MockSessionManager_RetrieveByScreenName_Call {
+func (_c *MockSessionManager_RetrieveByScreenName_Call) RunAndReturn(run func(string) *state.Session) *MockSessionManager_RetrieveByScreenName_Call {
 	_c.Call.Return(run)
 	return _c
 }

+ 11 - 11
handler/test_helpers.go

@@ -1,53 +1,53 @@
 package handler
 
 import (
-	"github.com/mkaminski/goaim/server"
+	"github.com/mkaminski/goaim/state"
 	"time"
 )
 
 // sessOptWarning sets a warning level on the session object
-func sessOptWarning(level uint16) func(session *server.Session) {
-	return func(session *server.Session) {
+func sessOptWarning(level uint16) func(session *state.Session) {
+	return func(session *state.Session) {
 		session.IncreaseWarning(level)
 	}
 }
 
 // sessOptCannedID sets a canned session ID ("user-sess-id") on the session
 // object
-func sessOptCannedID(session *server.Session) {
+func sessOptCannedID(session *state.Session) {
 	session.SetID("user-sess-id")
 }
 
 // sessOptCannedID sets a canned session ID ("user-sess-id") on the session
 // object
-func sessOptID(ID string) func(session *server.Session) {
-	return func(session *server.Session) {
+func sessOptID(ID string) func(session *state.Session) {
+	return func(session *state.Session) {
 		session.SetID(ID)
 	}
 }
 
 // sessOptCannedAwayMessage sets a canned away message ("this is my away
 // message!") on the session object
-func sessOptCannedAwayMessage(session *server.Session) {
+func sessOptCannedAwayMessage(session *state.Session) {
 	session.SetAwayMessage("this is my away message!")
 }
 
 // sessOptCannedSignonTime sets a canned sign-on time (1696790127565) on the
 // session object
-func sessOptCannedSignonTime(session *server.Session) {
+func sessOptCannedSignonTime(session *state.Session) {
 	session.SetSignonTime(time.UnixMilli(1696790127565))
 }
 
 // sessOptCannedSignonTime sets the invisible flag to true on the session
 // object
-func sessOptInvisible(session *server.Session) {
+func sessOptInvisible(session *state.Session) {
 	session.SetInvisible(true)
 }
 
 // newTestSession creates a session object with 0 or more functional options
 // applied
-func newTestSession(screenName string, options ...func(session *server.Session)) *server.Session {
-	s := server.NewSession()
+func newTestSession(screenName string, options ...func(session *state.Session)) *state.Session {
+	s := state.NewSession()
 	s.SetScreenName(screenName)
 	for _, op := range options {
 		op(s)

+ 14 - 13
server/types.go → handler/types.go

@@ -1,18 +1,14 @@
-package server
+package handler
 
 import (
 	"context"
 	"github.com/mkaminski/goaim/oscar"
+	"github.com/mkaminski/goaim/state"
 	"time"
 )
 
-type UserManager interface {
-	GetUser(screenName string) (*User, error)
-	UpsertUser(u User) error
-}
-
 type FeedbagManager interface {
-	Blocked(sn1, sn2 string) (BlockedState, error)
+	Blocked(sn1, sn2 string) (state.BlockedState, error)
 	Buddies(screenName string) ([]string, error)
 	Delete(screenName string, items []oscar.FeedbagItem) error
 	InterestedUsers(screenName string) ([]string, error)
@@ -21,21 +17,26 @@ type FeedbagManager interface {
 	Upsert(screenName string, items []oscar.FeedbagItem) error
 }
 
+type UserManager interface {
+	GetUser(screenName string) (*state.User, error)
+	UpsertUser(u state.User) error
+}
+
 type SessionManager interface {
 	Broadcast(ctx context.Context, msg oscar.XMessage)
 	BroadcastToScreenNames(ctx context.Context, screenNames []string, msg oscar.XMessage)
 	Empty() bool
-	NewSessionWithSN(sessID string, screenName string) *Session
-	Remove(sess *Session)
-	Retrieve(ID string) (*Session, bool)
-	RetrieveByScreenName(screenName string) *Session
+	NewSessionWithSN(sessID string, screenName string) *state.Session
+	Remove(sess *state.Session)
+	Retrieve(ID string) (*state.Session, bool)
+	RetrieveByScreenName(screenName string) *state.Session
 	SendToScreenName(ctx context.Context, screenName string, msg oscar.XMessage)
 }
 
 type ChatSessionManager interface {
 	SessionManager
-	BroadcastExcept(ctx context.Context, except *Session, msg oscar.XMessage)
-	Participants() []*Session
+	BroadcastExcept(ctx context.Context, except *state.Session, msg oscar.XMessage)
+	Participants() []*state.Session
 }
 
 type ProfileManager interface {

+ 18 - 15
server/user_manager_manager_mock.go → handler/user_manager_manager_mock.go

@@ -1,8 +1,11 @@
 // Code generated by mockery v2.34.2. DO NOT EDIT.
 
-package server
+package handler
 
-import mock "github.com/stretchr/testify/mock"
+import (
+	state "github.com/mkaminski/goaim/state"
+	mock "github.com/stretchr/testify/mock"
+)
 
 // MockUserManager is an autogenerated mock type for the UserManager type
 type MockUserManager struct {
@@ -18,19 +21,19 @@ func (_m *MockUserManager) EXPECT() *MockUserManager_Expecter {
 }
 
 // GetUser provides a mock function with given fields: screenName
-func (_m *MockUserManager) GetUser(screenName string) (*User, error) {
+func (_m *MockUserManager) GetUser(screenName string) (*state.User, error) {
 	ret := _m.Called(screenName)
 
-	var r0 *User
+	var r0 *state.User
 	var r1 error
-	if rf, ok := ret.Get(0).(func(string) (*User, error)); ok {
+	if rf, ok := ret.Get(0).(func(string) (*state.User, error)); ok {
 		return rf(screenName)
 	}
-	if rf, ok := ret.Get(0).(func(string) *User); ok {
+	if rf, ok := ret.Get(0).(func(string) *state.User); ok {
 		r0 = rf(screenName)
 	} else {
 		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(*User)
+			r0 = ret.Get(0).(*state.User)
 		}
 	}
 
@@ -61,22 +64,22 @@ func (_c *MockUserManager_GetUser_Call) Run(run func(screenName string)) *MockUs
 	return _c
 }
 
-func (_c *MockUserManager_GetUser_Call) Return(_a0 *User, _a1 error) *MockUserManager_GetUser_Call {
+func (_c *MockUserManager_GetUser_Call) Return(_a0 *state.User, _a1 error) *MockUserManager_GetUser_Call {
 	_c.Call.Return(_a0, _a1)
 	return _c
 }
 
-func (_c *MockUserManager_GetUser_Call) RunAndReturn(run func(string) (*User, error)) *MockUserManager_GetUser_Call {
+func (_c *MockUserManager_GetUser_Call) RunAndReturn(run func(string) (*state.User, error)) *MockUserManager_GetUser_Call {
 	_c.Call.Return(run)
 	return _c
 }
 
 // UpsertUser provides a mock function with given fields: u
-func (_m *MockUserManager) UpsertUser(u User) error {
+func (_m *MockUserManager) UpsertUser(u state.User) error {
 	ret := _m.Called(u)
 
 	var r0 error
-	if rf, ok := ret.Get(0).(func(User) error); ok {
+	if rf, ok := ret.Get(0).(func(state.User) error); ok {
 		r0 = rf(u)
 	} else {
 		r0 = ret.Error(0)
@@ -91,14 +94,14 @@ type MockUserManager_UpsertUser_Call struct {
 }
 
 // UpsertUser is a helper method to define mock.On call
-//   - u User
+//   - u state.User
 func (_e *MockUserManager_Expecter) UpsertUser(u interface{}) *MockUserManager_UpsertUser_Call {
 	return &MockUserManager_UpsertUser_Call{Call: _e.mock.On("UpsertUser", u)}
 }
 
-func (_c *MockUserManager_UpsertUser_Call) Run(run func(u User)) *MockUserManager_UpsertUser_Call {
+func (_c *MockUserManager_UpsertUser_Call) Run(run func(u state.User)) *MockUserManager_UpsertUser_Call {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(User))
+		run(args[0].(state.User))
 	})
 	return _c
 }
@@ -108,7 +111,7 @@ func (_c *MockUserManager_UpsertUser_Call) Return(_a0 error) *MockUserManager_Up
 	return _c
 }
 
-func (_c *MockUserManager_UpsertUser_Call) RunAndReturn(run func(User) error) *MockUserManager_UpsertUser_Call {
+func (_c *MockUserManager_UpsertUser_Call) RunAndReturn(run func(state.User) error) *MockUserManager_UpsertUser_Call {
 	_c.Call.Return(run)
 	return _c
 }

+ 5 - 73
server/chat.go

@@ -2,17 +2,14 @@ package server
 
 import (
 	"context"
-	"errors"
+	"github.com/mkaminski/goaim/oscar"
+	"github.com/mkaminski/goaim/state"
 	"io"
 	"log/slog"
-	"sync"
-	"time"
-
-	"github.com/mkaminski/goaim/oscar"
 )
 
 type ChatHandler interface {
-	ChannelMsgToHostHandler(ctx context.Context, sess *Session, room ChatSessionManager, snacPayloadIn oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost) (*oscar.XMessage, error)
+	ChannelMsgToHostHandler(ctx context.Context, sess *state.Session, chatID string, snacPayloadIn oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost) (*oscar.XMessage, error)
 }
 
 func NewChatRouter(logger *slog.Logger, chatHandler ChatHandler) ChatRouter {
@@ -29,14 +26,14 @@ type ChatRouter struct {
 	RouteLogger
 }
 
-func (rt *ChatRouter) RouteChat(ctx context.Context, sess *Session, chatSessMgr ChatSessionManager, SNACFrame oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
+func (rt *ChatRouter) RouteChat(ctx context.Context, sess *state.Session, chatID string, SNACFrame oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
 	switch SNACFrame.SubGroup {
 	case oscar.ChatChannelMsgToHost:
 		inSNAC := oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost{}
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
 			return err
 		}
-		outSNAC, err := rt.ChannelMsgToHostHandler(ctx, sess, chatSessMgr, inSNAC)
+		outSNAC, err := rt.ChannelMsgToHostHandler(ctx, sess, chatID, inSNAC)
 		if err != nil {
 			return err
 		}
@@ -50,68 +47,3 @@ func (rt *ChatRouter) RouteChat(ctx context.Context, sess *Session, chatSessMgr
 		return ErrUnsupportedSubGroup
 	}
 }
-
-type ChatRoom struct {
-	CreateTime     time.Time
-	DetailLevel    uint8
-	Exchange       uint16
-	Cookie         string
-	InstanceNumber uint16
-	Name           string
-}
-
-func (c ChatRoom) TLVList() []oscar.TLV {
-	return []oscar.TLV{
-		oscar.NewTLV(0x00c9, uint16(15)),
-		oscar.NewTLV(0x00ca, uint32(c.CreateTime.Unix())),
-		oscar.NewTLV(0x00d1, uint16(1024)),
-		oscar.NewTLV(0x00d2, uint16(100)),
-		oscar.NewTLV(0x00d5, uint8(2)),
-		oscar.NewTLV(0x006a, c.Name),
-		oscar.NewTLV(0x00d3, c.Name),
-	}
-}
-
-type ChatRegistry struct {
-	chatRoomStore map[string]ChatRoom
-	smStore       map[string]ChatSessionManager
-	mapMutex      sync.RWMutex
-}
-
-func NewChatRegistry() *ChatRegistry {
-	return &ChatRegistry{
-		chatRoomStore: make(map[string]ChatRoom),
-		smStore:       make(map[string]ChatSessionManager),
-	}
-}
-
-func (c *ChatRegistry) Register(room ChatRoom, sm ChatSessionManager) {
-	c.mapMutex.Lock()
-	defer c.mapMutex.Unlock()
-	c.chatRoomStore[room.Cookie] = room
-	c.smStore[room.Cookie] = sm
-}
-
-func (c *ChatRegistry) Retrieve(chatID string) (ChatRoom, ChatSessionManager, error) {
-	c.mapMutex.RLock()
-	defer c.mapMutex.RUnlock()
-	cr, found := c.chatRoomStore[chatID]
-	if !found {
-		return ChatRoom{}, nil, errors.New("unable to find chat room")
-	}
-	sm, found := c.smStore[chatID]
-	if !found {
-		panic("unable to find session manager for chat")
-	}
-	return cr, sm, nil
-}
-
-func (c *ChatRegistry) MaybeRemoveRoom(chatID string) {
-	c.mapMutex.Lock()
-	defer c.mapMutex.Unlock()
-	sm, found := c.smStore[chatID]
-	if found && sm.Empty() {
-		delete(c.chatRoomStore, chatID)
-		delete(c.smStore, chatID)
-	}
-}

+ 18 - 16
server/chat_mock.go

@@ -7,6 +7,8 @@ import (
 
 	oscar "github.com/mkaminski/goaim/oscar"
 	mock "github.com/stretchr/testify/mock"
+
+	state "github.com/mkaminski/goaim/state"
 )
 
 // MockChatHandler is an autogenerated mock type for the ChatHandler type
@@ -22,25 +24,25 @@ func (_m *MockChatHandler) EXPECT() *MockChatHandler_Expecter {
 	return &MockChatHandler_Expecter{mock: &_m.Mock}
 }
 
-// ChannelMsgToHostHandler provides a mock function with given fields: ctx, sess, room, snacPayloadIn
-func (_m *MockChatHandler) ChannelMsgToHostHandler(ctx context.Context, sess *Session, room ChatSessionManager, snacPayloadIn oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost) (*oscar.XMessage, error) {
-	ret := _m.Called(ctx, sess, room, snacPayloadIn)
+// ChannelMsgToHostHandler provides a mock function with given fields: ctx, sess, chatID, snacPayloadIn
+func (_m *MockChatHandler) ChannelMsgToHostHandler(ctx context.Context, sess *state.Session, chatID string, snacPayloadIn oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost) (*oscar.XMessage, error) {
+	ret := _m.Called(ctx, sess, chatID, snacPayloadIn)
 
 	var r0 *oscar.XMessage
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, ChatSessionManager, oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost) (*oscar.XMessage, error)); ok {
-		return rf(ctx, sess, room, snacPayloadIn)
+	if rf, ok := ret.Get(0).(func(context.Context, *state.Session, string, oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost) (*oscar.XMessage, error)); ok {
+		return rf(ctx, sess, chatID, snacPayloadIn)
 	}
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, ChatSessionManager, oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost) *oscar.XMessage); ok {
-		r0 = rf(ctx, sess, room, snacPayloadIn)
+	if rf, ok := ret.Get(0).(func(context.Context, *state.Session, string, oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost) *oscar.XMessage); ok {
+		r0 = rf(ctx, sess, chatID, snacPayloadIn)
 	} else {
 		if ret.Get(0) != nil {
 			r0 = ret.Get(0).(*oscar.XMessage)
 		}
 	}
 
-	if rf, ok := ret.Get(1).(func(context.Context, *Session, ChatSessionManager, oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost) error); ok {
-		r1 = rf(ctx, sess, room, snacPayloadIn)
+	if rf, ok := ret.Get(1).(func(context.Context, *state.Session, string, oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost) error); ok {
+		r1 = rf(ctx, sess, chatID, snacPayloadIn)
 	} else {
 		r1 = ret.Error(1)
 	}
@@ -55,16 +57,16 @@ type MockChatHandler_ChannelMsgToHostHandler_Call struct {
 
 // ChannelMsgToHostHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
-//   - room ChatSessionManager
+//   - sess *state.Session
+//   - chatID string
 //   - snacPayloadIn oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost
-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 (_e *MockChatHandler_Expecter) ChannelMsgToHostHandler(ctx interface{}, sess interface{}, chatID interface{}, snacPayloadIn interface{}) *MockChatHandler_ChannelMsgToHostHandler_Call {
+	return &MockChatHandler_ChannelMsgToHostHandler_Call{Call: _e.mock.On("ChannelMsgToHostHandler", ctx, sess, chatID, snacPayloadIn)}
 }
 
-func (_c *MockChatHandler_ChannelMsgToHostHandler_Call) Run(run func(ctx context.Context, sess *Session, room ChatSessionManager, snacPayloadIn oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost)) *MockChatHandler_ChannelMsgToHostHandler_Call {
+func (_c *MockChatHandler_ChannelMsgToHostHandler_Call) Run(run func(ctx context.Context, sess *state.Session, chatID string, snacPayloadIn oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost)) *MockChatHandler_ChannelMsgToHostHandler_Call {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(*Session), args[2].(ChatSessionManager), args[3].(oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost))
+		run(args[0].(context.Context), args[1].(*state.Session), args[2].(string), args[3].(oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost))
 	})
 	return _c
 }
@@ -74,7 +76,7 @@ func (_c *MockChatHandler_ChannelMsgToHostHandler_Call) Return(_a0 *oscar.XMessa
 	return _c
 }
 
-func (_c *MockChatHandler_ChannelMsgToHostHandler_Call) RunAndReturn(run func(context.Context, *Session, ChatSessionManager, oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost) (*oscar.XMessage, error)) *MockChatHandler_ChannelMsgToHostHandler_Call {
+func (_c *MockChatHandler_ChannelMsgToHostHandler_Call) RunAndReturn(run func(context.Context, *state.Session, string, oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost) (*oscar.XMessage, error)) *MockChatHandler_ChannelMsgToHostHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }

+ 4 - 11
server/chat_nav.go

@@ -2,15 +2,14 @@ package server
 
 import (
 	"context"
-	"github.com/google/uuid"
 	"github.com/mkaminski/goaim/oscar"
+	"github.com/mkaminski/goaim/state"
 	"io"
 	"log/slog"
-	"time"
 )
 
 type ChatNavHandler interface {
-	CreateRoomHandler(ctx context.Context, sess *Session, newRoom func() ChatRoom, newChatSessMgr func() ChatSessionManager, snacPayloadIn oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) (oscar.XMessage, error)
+	CreateRoomHandler(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) (oscar.XMessage, error)
 	RequestChatRightsHandler(ctx context.Context) oscar.XMessage
 	RequestRoomInfoHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x0D_0x04_ChatNavRequestRoomInfo) (oscar.XMessage, error)
 }
@@ -29,7 +28,7 @@ type ChatNavRouter struct {
 	RouteLogger
 }
 
-func (rt *ChatNavRouter) RouteChatNav(ctx context.Context, sess *Session, newChatSessMgr func() ChatSessionManager, SNACFrame oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
+func (rt *ChatNavRouter) RouteChatNav(ctx context.Context, sess *state.Session, SNACFrame oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
 	switch SNACFrame.SubGroup {
 	case oscar.ChatNavRequestChatRights:
 		outSNAC := rt.RequestChatRightsHandler(ctx)
@@ -51,13 +50,7 @@ func (rt *ChatNavRouter) RouteChatNav(ctx context.Context, sess *Session, newCha
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
 			return err
 		}
-		newChatRoom := func() ChatRoom {
-			return ChatRoom{
-				Cookie:     uuid.New().String(),
-				CreateTime: time.Now(),
-			}
-		}
-		outSNAC, err := rt.CreateRoomHandler(ctx, sess, newChatRoom, newChatSessMgr, inSNAC)
+		outSNAC, err := rt.CreateRoomHandler(ctx, sess, inSNAC)
 		if err != nil {
 			return err
 		}

+ 17 - 17
server/chat_nav_mock.go

@@ -7,6 +7,8 @@ import (
 
 	oscar "github.com/mkaminski/goaim/oscar"
 	mock "github.com/stretchr/testify/mock"
+
+	state "github.com/mkaminski/goaim/state"
 )
 
 // MockChatNavHandler is an autogenerated mock type for the ChatNavHandler type
@@ -22,23 +24,23 @@ func (_m *MockChatNavHandler) EXPECT() *MockChatNavHandler_Expecter {
 	return &MockChatNavHandler_Expecter{mock: &_m.Mock}
 }
 
-// CreateRoomHandler provides a mock function with given fields: ctx, sess, newRoom, newChatSessMgr, snacPayloadIn
-func (_m *MockChatNavHandler) CreateRoomHandler(ctx context.Context, sess *Session, newRoom func() ChatRoom, newChatSessMgr func() ChatSessionManager, snacPayloadIn oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) (oscar.XMessage, error) {
-	ret := _m.Called(ctx, sess, newRoom, newChatSessMgr, snacPayloadIn)
+// CreateRoomHandler provides a mock function with given fields: ctx, sess, snacPayloadIn
+func (_m *MockChatNavHandler) CreateRoomHandler(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) (oscar.XMessage, error) {
+	ret := _m.Called(ctx, sess, snacPayloadIn)
 
 	var r0 oscar.XMessage
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, func() ChatRoom, func() ChatSessionManager, oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) (oscar.XMessage, error)); ok {
-		return rf(ctx, sess, newRoom, newChatSessMgr, snacPayloadIn)
+	if rf, ok := ret.Get(0).(func(context.Context, *state.Session, oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) (oscar.XMessage, error)); ok {
+		return rf(ctx, sess, snacPayloadIn)
 	}
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, func() ChatRoom, func() ChatSessionManager, oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) oscar.XMessage); ok {
-		r0 = rf(ctx, sess, newRoom, newChatSessMgr, snacPayloadIn)
+	if rf, ok := ret.Get(0).(func(context.Context, *state.Session, oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) oscar.XMessage); ok {
+		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r0 = ret.Get(0).(oscar.XMessage)
 	}
 
-	if rf, ok := ret.Get(1).(func(context.Context, *Session, func() ChatRoom, func() ChatSessionManager, oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) error); ok {
-		r1 = rf(ctx, sess, newRoom, newChatSessMgr, snacPayloadIn)
+	if rf, ok := ret.Get(1).(func(context.Context, *state.Session, oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) error); ok {
+		r1 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r1 = ret.Error(1)
 	}
@@ -53,17 +55,15 @@ type MockChatNavHandler_CreateRoomHandler_Call struct {
 
 // CreateRoomHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
-//   - newRoom func() ChatRoom
-//   - newChatSessMgr func() ChatSessionManager
+//   - sess *state.Session
 //   - snacPayloadIn oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate
-func (_e *MockChatNavHandler_Expecter) CreateRoomHandler(ctx interface{}, sess interface{}, newRoom interface{}, newChatSessMgr interface{}, snacPayloadIn interface{}) *MockChatNavHandler_CreateRoomHandler_Call {
-	return &MockChatNavHandler_CreateRoomHandler_Call{Call: _e.mock.On("CreateRoomHandler", ctx, sess, newRoom, newChatSessMgr, snacPayloadIn)}
+func (_e *MockChatNavHandler_Expecter) CreateRoomHandler(ctx interface{}, sess interface{}, snacPayloadIn interface{}) *MockChatNavHandler_CreateRoomHandler_Call {
+	return &MockChatNavHandler_CreateRoomHandler_Call{Call: _e.mock.On("CreateRoomHandler", ctx, sess, snacPayloadIn)}
 }
 
-func (_c *MockChatNavHandler_CreateRoomHandler_Call) Run(run func(ctx context.Context, sess *Session, newRoom func() ChatRoom, newChatSessMgr func() ChatSessionManager, snacPayloadIn oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate)) *MockChatNavHandler_CreateRoomHandler_Call {
+func (_c *MockChatNavHandler_CreateRoomHandler_Call) Run(run func(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate)) *MockChatNavHandler_CreateRoomHandler_Call {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(*Session), args[2].(func() ChatRoom), args[3].(func() ChatSessionManager), args[4].(oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate))
+		run(args[0].(context.Context), args[1].(*state.Session), args[2].(oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate))
 	})
 	return _c
 }
@@ -73,7 +73,7 @@ func (_c *MockChatNavHandler_CreateRoomHandler_Call) Return(_a0 oscar.XMessage,
 	return _c
 }
 
-func (_c *MockChatNavHandler_CreateRoomHandler_Call) RunAndReturn(run func(context.Context, *Session, func() ChatRoom, func() ChatSessionManager, oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) (oscar.XMessage, error)) *MockChatNavHandler_CreateRoomHandler_Call {
+func (_c *MockChatNavHandler_CreateRoomHandler_Call) RunAndReturn(run func(context.Context, *state.Session, oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) (oscar.XMessage, error)) *MockChatNavHandler_CreateRoomHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }

+ 2 - 2
server/chat_nav_test.go

@@ -120,7 +120,7 @@ func TestChatNavRouter_RouteChatNavRouter(t *testing.T) {
 				Return(tc.output, tc.handlerErr).
 				Maybe()
 			svc.EXPECT().
-				CreateRoomHandler(mock.Anything, mock.Anything, mock.Anything, mock.Anything, tc.input.SnacOut).
+				CreateRoomHandler(mock.Anything, mock.Anything, tc.input.SnacOut).
 				Return(tc.output, tc.handlerErr).
 				Maybe()
 
@@ -137,7 +137,7 @@ func TestChatNavRouter_RouteChatNavRouter(t *testing.T) {
 			bufOut := &bytes.Buffer{}
 			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)
 			if tc.expectErr != nil {
 				return

+ 0 - 160
server/chat_registry_mock.go

@@ -1,160 +0,0 @@
-// Code generated by mockery v2.35.2. DO NOT EDIT.
-
-package server
-
-import mock "github.com/stretchr/testify/mock"
-
-// MockChatRegistry is an autogenerated mock type for the ChatRegistry type
-type MockChatRegistry struct {
-	mock.Mock
-}
-
-type MockChatRegistry_Expecter struct {
-	mock *mock.Mock
-}
-
-func (_m *MockChatRegistry) EXPECT() *MockChatRegistry_Expecter {
-	return &MockChatRegistry_Expecter{mock: &_m.Mock}
-}
-
-// MaybeRemoveRoom provides a mock function with given fields: chatID
-func (_m *MockChatRegistry) MaybeRemoveRoom(chatID string) {
-	_m.Called(chatID)
-}
-
-// MockChatRegistry_MaybeRemoveRoom_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'MaybeRemoveRoom'
-type MockChatRegistry_MaybeRemoveRoom_Call struct {
-	*mock.Call
-}
-
-// MaybeRemoveRoom is a helper method to define mock.On call
-//   - chatID string
-func (_e *MockChatRegistry_Expecter) MaybeRemoveRoom(chatID interface{}) *MockChatRegistry_MaybeRemoveRoom_Call {
-	return &MockChatRegistry_MaybeRemoveRoom_Call{Call: _e.mock.On("MaybeRemoveRoom", chatID)}
-}
-
-func (_c *MockChatRegistry_MaybeRemoveRoom_Call) Run(run func(chatID string)) *MockChatRegistry_MaybeRemoveRoom_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(string))
-	})
-	return _c
-}
-
-func (_c *MockChatRegistry_MaybeRemoveRoom_Call) Return() *MockChatRegistry_MaybeRemoveRoom_Call {
-	_c.Call.Return()
-	return _c
-}
-
-func (_c *MockChatRegistry_MaybeRemoveRoom_Call) RunAndReturn(run func(string)) *MockChatRegistry_MaybeRemoveRoom_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
-// Register provides a mock function with given fields: room, chatSessMsgr
-func (_m *MockChatRegistry) Register(room ChatRoom, chatSessMsgr ChatSessionManager) {
-	_m.Called(room, chatSessMsgr)
-}
-
-// MockChatRegistry_Register_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Register'
-type MockChatRegistry_Register_Call struct {
-	*mock.Call
-}
-
-// Register is a helper method to define mock.On call
-//   - room ChatRoom
-//   - chatSessMsgr ChatSessionManager
-func (_e *MockChatRegistry_Expecter) Register(room interface{}, chatSessMsgr interface{}) *MockChatRegistry_Register_Call {
-	return &MockChatRegistry_Register_Call{Call: _e.mock.On("Register", room, chatSessMsgr)}
-}
-
-func (_c *MockChatRegistry_Register_Call) Run(run func(room ChatRoom, chatSessMsgr ChatSessionManager)) *MockChatRegistry_Register_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(ChatRoom), args[1].(ChatSessionManager))
-	})
-	return _c
-}
-
-func (_c *MockChatRegistry_Register_Call) Return() *MockChatRegistry_Register_Call {
-	_c.Call.Return()
-	return _c
-}
-
-func (_c *MockChatRegistry_Register_Call) RunAndReturn(run func(ChatRoom, ChatSessionManager)) *MockChatRegistry_Register_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
-// Retrieve provides a mock function with given fields: chatID
-func (_m *MockChatRegistry) Retrieve(chatID string) (ChatRoom, ChatSessionManager, error) {
-	ret := _m.Called(chatID)
-
-	var r0 ChatRoom
-	var r1 ChatSessionManager
-	var r2 error
-	if rf, ok := ret.Get(0).(func(string) (ChatRoom, ChatSessionManager, error)); ok {
-		return rf(chatID)
-	}
-	if rf, ok := ret.Get(0).(func(string) ChatRoom); ok {
-		r0 = rf(chatID)
-	} else {
-		r0 = ret.Get(0).(ChatRoom)
-	}
-
-	if rf, ok := ret.Get(1).(func(string) ChatSessionManager); ok {
-		r1 = rf(chatID)
-	} else {
-		if ret.Get(1) != nil {
-			r1 = ret.Get(1).(ChatSessionManager)
-		}
-	}
-
-	if rf, ok := ret.Get(2).(func(string) error); ok {
-		r2 = rf(chatID)
-	} else {
-		r2 = ret.Error(2)
-	}
-
-	return r0, r1, r2
-}
-
-// MockChatRegistry_Retrieve_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Retrieve'
-type MockChatRegistry_Retrieve_Call struct {
-	*mock.Call
-}
-
-// Retrieve is a helper method to define mock.On call
-//   - chatID string
-func (_e *MockChatRegistry_Expecter) Retrieve(chatID interface{}) *MockChatRegistry_Retrieve_Call {
-	return &MockChatRegistry_Retrieve_Call{Call: _e.mock.On("Retrieve", chatID)}
-}
-
-func (_c *MockChatRegistry_Retrieve_Call) Run(run func(chatID string)) *MockChatRegistry_Retrieve_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(string))
-	})
-	return _c
-}
-
-func (_c *MockChatRegistry_Retrieve_Call) Return(_a0 ChatRoom, _a1 ChatSessionManager, _a2 error) *MockChatRegistry_Retrieve_Call {
-	_c.Call.Return(_a0, _a1, _a2)
-	return _c
-}
-
-func (_c *MockChatRegistry_Retrieve_Call) RunAndReturn(run func(string) (ChatRoom, ChatSessionManager, error)) *MockChatRegistry_Retrieve_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
-// NewMockChatRegistry creates a new instance of MockChatRegistry. 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 NewMockChatRegistry(t interface {
-	mock.TestingT
-	Cleanup(func())
-}) *MockChatRegistry {
-	mock := &MockChatRegistry{}
-	mock.Mock.Test(t)
-
-	t.Cleanup(func() { mock.AssertExpectations(t) })
-
-	return mock
-}

+ 1 - 1
server/chat_test.go

@@ -91,7 +91,7 @@ func TestChatRouter_RouteChat(t *testing.T) {
 			bufOut := &bytes.Buffer{}
 			seq := uint32(0)
 
-			err := router.RouteChat(nil, nil, nil, tc.input.SnacFrame, bufIn, bufOut, &seq)
+			err := router.RouteChat(nil, nil, "", tc.input.SnacFrame, bufIn, bufOut, &seq)
 			assert.ErrorIs(t, err, tc.expectErr)
 			if tc.expectErr != nil {
 				return

+ 19 - 12
server/connection.go

@@ -12,6 +12,7 @@ import (
 
 	"github.com/google/uuid"
 	"github.com/mkaminski/goaim/oscar"
+	"github.com/mkaminski/goaim/state"
 )
 
 var (
@@ -51,12 +52,16 @@ func consumeFLAPFrames(r io.Reader, msgCh chan incomingMessage, errCh chan error
 	}
 }
 
-func dispatchIncomingMessages(ctx context.Context, sess *Session, seq uint32, rw io.ReadWriter, logger *slog.Logger, fn clientReqHandler, alertHandler alertHandler) {
+func dispatchIncomingMessages(ctx context.Context, sess *state.Session, seq uint32, rw io.ReadWriter, logger *slog.Logger, fn clientReqHandler, alertHandler alertHandler) {
 	// buffered so that the go routine has room to exit
 	msgCh := make(chan incomingMessage, 1)
 	readErrCh := make(chan error, 1)
 	go consumeFLAPFrames(rw, msgCh, readErrCh)
 
+	defer func() {
+		logger.InfoContext(ctx, "user disconnected")
+	}()
+
 	for {
 		select {
 		case m := <-msgCh:
@@ -102,11 +107,7 @@ func dispatchIncomingMessages(ctx context.Context, sess *Session, seq uint32, rw
 			}
 			return
 		case err := <-readErrCh:
-			// handle a read error
-			switch {
-			case errors.Is(io.EOF, err):
-				fallthrough
-			default:
+			if !errors.Is(io.EOF, err) {
 				logger.ErrorContext(ctx, "client disconnected with error", "err", err)
 			}
 			return
@@ -114,29 +115,35 @@ func dispatchIncomingMessages(ctx context.Context, sess *Session, seq uint32, rw
 	}
 }
 
-func HandleChatConnection(ctx context.Context, cr *ChatRegistry, rw io.ReadWriter, router ChatServiceRouter, authHandler AuthHandler, logger *slog.Logger) {
+type sessionRetriever interface {
+	Retrieve(ID string) (*state.Session, bool)
+}
+
+func HandleChatConnection(ctx context.Context, cr *state.ChatRegistry, rw io.ReadWriter, router ChatServiceRouter, authHandler AuthHandler, logger *slog.Logger) {
 	cookie, seq, err := authHandler.VerifyChatLogin(rw)
 	if err != nil {
 		logger.ErrorContext(ctx, "user disconnected with error", "err", err.Error())
 		return
 	}
 
-	room, chatSessMgr, err := cr.Retrieve(string(cookie.Cookie))
+	_, chatSessMgr, err := cr.Retrieve(string(cookie.Cookie))
 	if err != nil {
 		logger.ErrorContext(ctx, "unable to find chat room", "err", err.Error())
 		return
 	}
 
-	chatSess, found := chatSessMgr.Retrieve(cookie.SessID)
+	chatSess, found := chatSessMgr.(sessionRetriever).Retrieve(cookie.SessID)
 	if !found {
 		logger.ErrorContext(ctx, "unable to find user for session", "sessID", cookie.SessID)
 		return
 	}
 
+	chatID := string(cookie.Cookie)
+
 	defer chatSess.Close()
 	go func() {
 		<-chatSess.Closed()
-		authHandler.SignoutChat(ctx, cr, room, chatSessMgr, chatSess)
+		authHandler.SignoutChat(ctx, chatSess, chatID)
 	}()
 
 	ctx = context.WithValue(ctx, "screenName", chatSess.ScreenName())
@@ -148,7 +155,7 @@ func HandleChatConnection(ctx context.Context, cr *ChatRegistry, rw io.ReadWrite
 	}
 
 	fnClientReqHandler := func(ctx context.Context, r io.Reader, w io.Writer, seq *uint32) error {
-		return router.Route(ctx, chatSess, r, w, seq, chatSessMgr, room)
+		return router.Route(ctx, chatSess, r, w, seq, chatID)
 	}
 	fnAlertHandler := func(ctx context.Context, msg oscar.XMessage, w io.Writer, seq *uint32) error {
 		return writeOutSNAC(oscar.SnacFrame{}, msg.SnacFrame, msg.SnacOut, seq, w)
@@ -267,7 +274,7 @@ func HandleBOSConnection(ctx context.Context, conn net.Conn, router BOSServiceRo
 	dispatchIncomingMessages(ctx, sess, seq, conn, logger, fnClientReqHandler, fnAlertHandler)
 }
 
-func ListenChat(cfg Config, router ChatServiceRouter, cr *ChatRegistry, authHandler AuthHandler, logger *slog.Logger) {
+func ListenChat(cfg Config, router ChatServiceRouter, cr *state.ChatRegistry, authHandler AuthHandler, logger *slog.Logger) {
 	addr := Address("", cfg.ChatPort)
 	listener, err := net.Listen("tcp", addr)
 	if err != nil {

+ 10 - 9
server/connection_test.go

@@ -5,6 +5,7 @@ import (
 	"bytes"
 	"context"
 	"github.com/mkaminski/goaim/oscar"
+	"github.com/mkaminski/goaim/state"
 	"github.com/stretchr/testify/assert"
 	"io"
 	"sync"
@@ -15,13 +16,13 @@ func TestHandleChatConnection_Notification(t *testing.T) {
 
 	ctx := context.Background()
 	cfg := Config{}
-	cr := NewChatRegistry()
+	cr := state.NewChatRegistry()
 	logger := NewLogger(cfg)
 
-	room := ChatRoom{
+	room := state.ChatRoom{
 		Name: "test chat room!",
 	}
-	sm := NewSessionManager(logger)
+	sm := state.NewSessionManager(logger)
 	sess := sm.NewSessionWithSN("bob-sess-id", "bob")
 	cr.Register(room, sm)
 
@@ -83,13 +84,13 @@ func TestHandleChatConnection_ClientRequestFLAP(t *testing.T) {
 
 	ctx := context.Background()
 	cfg := Config{}
-	cr := NewChatRegistry()
+	cr := state.NewChatRegistry()
 	logger := NewLogger(cfg)
 
-	room := ChatRoom{
+	room := state.ChatRoom{
 		Name: "test chat room!",
 	}
-	sm := NewSessionManager(logger)
+	sm := state.NewSessionManager(logger)
 	sess := sm.NewSessionWithSN("bob-sess-id", "bob")
 	cr.Register(room, sm)
 
@@ -143,13 +144,13 @@ func TestHandleChatConnection_SessionClosed(t *testing.T) {
 
 	ctx := context.Background()
 	cfg := Config{}
-	cr := NewChatRegistry()
+	cr := state.NewChatRegistry()
 	logger := NewLogger(cfg)
 
-	room := ChatRoom{
+	room := state.ChatRoom{
 		Name: "test chat room!",
 	}
-	sm := NewSessionManager(logger)
+	sm := state.NewSessionManager(logger)
 	sess := sm.NewSessionWithSN("bob-sess-id", "bob")
 	cr.Register(room, sm)
 

+ 7 - 6
server/feedbag.go

@@ -3,18 +3,19 @@ package server
 import (
 	"context"
 	"github.com/mkaminski/goaim/oscar"
+	"github.com/mkaminski/goaim/state"
 	"io"
 	"log/slog"
 )
 
 type FeedbagHandler interface {
-	DeleteItemHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x13_0x0A_FeedbagDeleteItem) (oscar.XMessage, error)
-	InsertItemHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x13_0x08_FeedbagInsertItem) (oscar.XMessage, error)
-	QueryHandler(ctx context.Context, sess *Session) (oscar.XMessage, error)
-	QueryIfModifiedHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x13_0x05_FeedbagQueryIfModified) (oscar.XMessage, error)
+	DeleteItemHandler(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x13_0x0A_FeedbagDeleteItem) (oscar.XMessage, error)
+	InsertItemHandler(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x13_0x08_FeedbagInsertItem) (oscar.XMessage, error)
+	QueryHandler(ctx context.Context, sess *state.Session) (oscar.XMessage, error)
+	QueryIfModifiedHandler(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x13_0x05_FeedbagQueryIfModified) (oscar.XMessage, error)
 	RightsQueryHandler(context.Context) oscar.XMessage
 	StartClusterHandler(context.Context, oscar.SNAC_0x13_0x11_FeedbagStartCluster)
-	UpdateItemHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x13_0x09_FeedbagUpdateItem) (oscar.XMessage, error)
+	UpdateItemHandler(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x13_0x09_FeedbagUpdateItem) (oscar.XMessage, error)
 }
 
 func NewFeedbagRouter(logger *slog.Logger, handler FeedbagHandler) FeedbagRouter {
@@ -31,7 +32,7 @@ type FeedbagRouter struct {
 	RouteLogger
 }
 
-func (rt FeedbagRouter) RouteFeedbag(ctx context.Context, sess *Session, SNACFrame oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
+func (rt FeedbagRouter) RouteFeedbag(ctx context.Context, sess *state.Session, SNACFrame oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
 	switch SNACFrame.SubGroup {
 	case oscar.FeedbagRightsQuery:
 		inSNAC := oscar.SNAC_0x13_0x02_FeedbagRightsQuery{}

+ 42 - 40
server/feedbag_mock.go

@@ -7,6 +7,8 @@ import (
 
 	oscar "github.com/mkaminski/goaim/oscar"
 	mock "github.com/stretchr/testify/mock"
+
+	state "github.com/mkaminski/goaim/state"
 )
 
 // MockFeedbagHandler is an autogenerated mock type for the FeedbagHandler type
@@ -23,21 +25,21 @@ func (_m *MockFeedbagHandler) EXPECT() *MockFeedbagHandler_Expecter {
 }
 
 // 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) (oscar.XMessage, error) {
+func (_m *MockFeedbagHandler) DeleteItemHandler(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x13_0x0A_FeedbagDeleteItem) (oscar.XMessage, error) {
 	ret := _m.Called(ctx, sess, snacPayloadIn)
 
 	var r0 oscar.XMessage
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x13_0x0A_FeedbagDeleteItem) (oscar.XMessage, error)); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *state.Session, oscar.SNAC_0x13_0x0A_FeedbagDeleteItem) (oscar.XMessage, error)); ok {
 		return rf(ctx, sess, snacPayloadIn)
 	}
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x13_0x0A_FeedbagDeleteItem) oscar.XMessage); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *state.Session, oscar.SNAC_0x13_0x0A_FeedbagDeleteItem) oscar.XMessage); ok {
 		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r0 = ret.Get(0).(oscar.XMessage)
 	}
 
-	if rf, ok := ret.Get(1).(func(context.Context, *Session, oscar.SNAC_0x13_0x0A_FeedbagDeleteItem) error); ok {
+	if rf, ok := ret.Get(1).(func(context.Context, *state.Session, oscar.SNAC_0x13_0x0A_FeedbagDeleteItem) error); ok {
 		r1 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r1 = ret.Error(1)
@@ -53,15 +55,15 @@ type MockFeedbagHandler_DeleteItemHandler_Call struct {
 
 // DeleteItemHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *state.Session
 //   - snacPayloadIn oscar.SNAC_0x13_0x0A_FeedbagDeleteItem
 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, sess *Session, snacPayloadIn oscar.SNAC_0x13_0x0A_FeedbagDeleteItem)) *MockFeedbagHandler_DeleteItemHandler_Call {
+func (_c *MockFeedbagHandler_DeleteItemHandler_Call) Run(run func(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x13_0x0A_FeedbagDeleteItem)) *MockFeedbagHandler_DeleteItemHandler_Call {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(*Session), args[2].(oscar.SNAC_0x13_0x0A_FeedbagDeleteItem))
+		run(args[0].(context.Context), args[1].(*state.Session), args[2].(oscar.SNAC_0x13_0x0A_FeedbagDeleteItem))
 	})
 	return _c
 }
@@ -71,27 +73,27 @@ func (_c *MockFeedbagHandler_DeleteItemHandler_Call) Return(_a0 oscar.XMessage,
 	return _c
 }
 
-func (_c *MockFeedbagHandler_DeleteItemHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x13_0x0A_FeedbagDeleteItem) (oscar.XMessage, error)) *MockFeedbagHandler_DeleteItemHandler_Call {
+func (_c *MockFeedbagHandler_DeleteItemHandler_Call) RunAndReturn(run func(context.Context, *state.Session, oscar.SNAC_0x13_0x0A_FeedbagDeleteItem) (oscar.XMessage, error)) *MockFeedbagHandler_DeleteItemHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }
 
 // 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) (oscar.XMessage, error) {
+func (_m *MockFeedbagHandler) InsertItemHandler(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x13_0x08_FeedbagInsertItem) (oscar.XMessage, error) {
 	ret := _m.Called(ctx, sess, snacPayloadIn)
 
 	var r0 oscar.XMessage
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x13_0x08_FeedbagInsertItem) (oscar.XMessage, error)); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *state.Session, oscar.SNAC_0x13_0x08_FeedbagInsertItem) (oscar.XMessage, error)); ok {
 		return rf(ctx, sess, snacPayloadIn)
 	}
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x13_0x08_FeedbagInsertItem) oscar.XMessage); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *state.Session, oscar.SNAC_0x13_0x08_FeedbagInsertItem) oscar.XMessage); ok {
 		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r0 = ret.Get(0).(oscar.XMessage)
 	}
 
-	if rf, ok := ret.Get(1).(func(context.Context, *Session, oscar.SNAC_0x13_0x08_FeedbagInsertItem) error); ok {
+	if rf, ok := ret.Get(1).(func(context.Context, *state.Session, oscar.SNAC_0x13_0x08_FeedbagInsertItem) error); ok {
 		r1 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r1 = ret.Error(1)
@@ -107,15 +109,15 @@ type MockFeedbagHandler_InsertItemHandler_Call struct {
 
 // InsertItemHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *state.Session
 //   - snacPayloadIn oscar.SNAC_0x13_0x08_FeedbagInsertItem
 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, sess *Session, snacPayloadIn oscar.SNAC_0x13_0x08_FeedbagInsertItem)) *MockFeedbagHandler_InsertItemHandler_Call {
+func (_c *MockFeedbagHandler_InsertItemHandler_Call) Run(run func(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x13_0x08_FeedbagInsertItem)) *MockFeedbagHandler_InsertItemHandler_Call {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(*Session), args[2].(oscar.SNAC_0x13_0x08_FeedbagInsertItem))
+		run(args[0].(context.Context), args[1].(*state.Session), args[2].(oscar.SNAC_0x13_0x08_FeedbagInsertItem))
 	})
 	return _c
 }
@@ -125,27 +127,27 @@ func (_c *MockFeedbagHandler_InsertItemHandler_Call) Return(_a0 oscar.XMessage,
 	return _c
 }
 
-func (_c *MockFeedbagHandler_InsertItemHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x13_0x08_FeedbagInsertItem) (oscar.XMessage, error)) *MockFeedbagHandler_InsertItemHandler_Call {
+func (_c *MockFeedbagHandler_InsertItemHandler_Call) RunAndReturn(run func(context.Context, *state.Session, oscar.SNAC_0x13_0x08_FeedbagInsertItem) (oscar.XMessage, error)) *MockFeedbagHandler_InsertItemHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }
 
 // QueryHandler provides a mock function with given fields: ctx, sess
-func (_m *MockFeedbagHandler) QueryHandler(ctx context.Context, sess *Session) (oscar.XMessage, error) {
+func (_m *MockFeedbagHandler) QueryHandler(ctx context.Context, sess *state.Session) (oscar.XMessage, error) {
 	ret := _m.Called(ctx, sess)
 
 	var r0 oscar.XMessage
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, *Session) (oscar.XMessage, error)); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *state.Session) (oscar.XMessage, error)); ok {
 		return rf(ctx, sess)
 	}
-	if rf, ok := ret.Get(0).(func(context.Context, *Session) oscar.XMessage); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *state.Session) oscar.XMessage); ok {
 		r0 = rf(ctx, sess)
 	} else {
 		r0 = ret.Get(0).(oscar.XMessage)
 	}
 
-	if rf, ok := ret.Get(1).(func(context.Context, *Session) error); ok {
+	if rf, ok := ret.Get(1).(func(context.Context, *state.Session) error); ok {
 		r1 = rf(ctx, sess)
 	} else {
 		r1 = ret.Error(1)
@@ -161,14 +163,14 @@ type MockFeedbagHandler_QueryHandler_Call struct {
 
 // QueryHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *state.Session
 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)) *MockFeedbagHandler_QueryHandler_Call {
+func (_c *MockFeedbagHandler_QueryHandler_Call) Run(run func(ctx context.Context, sess *state.Session)) *MockFeedbagHandler_QueryHandler_Call {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(*Session))
+		run(args[0].(context.Context), args[1].(*state.Session))
 	})
 	return _c
 }
@@ -178,27 +180,27 @@ func (_c *MockFeedbagHandler_QueryHandler_Call) Return(_a0 oscar.XMessage, _a1 e
 	return _c
 }
 
-func (_c *MockFeedbagHandler_QueryHandler_Call) RunAndReturn(run func(context.Context, *Session) (oscar.XMessage, error)) *MockFeedbagHandler_QueryHandler_Call {
+func (_c *MockFeedbagHandler_QueryHandler_Call) RunAndReturn(run func(context.Context, *state.Session) (oscar.XMessage, error)) *MockFeedbagHandler_QueryHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }
 
 // 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) (oscar.XMessage, error) {
+func (_m *MockFeedbagHandler) QueryIfModifiedHandler(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x13_0x05_FeedbagQueryIfModified) (oscar.XMessage, error) {
 	ret := _m.Called(ctx, sess, snacPayloadIn)
 
 	var r0 oscar.XMessage
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x13_0x05_FeedbagQueryIfModified) (oscar.XMessage, error)); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *state.Session, oscar.SNAC_0x13_0x05_FeedbagQueryIfModified) (oscar.XMessage, error)); ok {
 		return rf(ctx, sess, snacPayloadIn)
 	}
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x13_0x05_FeedbagQueryIfModified) oscar.XMessage); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *state.Session, oscar.SNAC_0x13_0x05_FeedbagQueryIfModified) oscar.XMessage); ok {
 		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r0 = ret.Get(0).(oscar.XMessage)
 	}
 
-	if rf, ok := ret.Get(1).(func(context.Context, *Session, oscar.SNAC_0x13_0x05_FeedbagQueryIfModified) error); ok {
+	if rf, ok := ret.Get(1).(func(context.Context, *state.Session, oscar.SNAC_0x13_0x05_FeedbagQueryIfModified) error); ok {
 		r1 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r1 = ret.Error(1)
@@ -214,15 +216,15 @@ type MockFeedbagHandler_QueryIfModifiedHandler_Call struct {
 
 // QueryIfModifiedHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *state.Session
 //   - snacPayloadIn oscar.SNAC_0x13_0x05_FeedbagQueryIfModified
 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, snacPayloadIn oscar.SNAC_0x13_0x05_FeedbagQueryIfModified)) *MockFeedbagHandler_QueryIfModifiedHandler_Call {
+func (_c *MockFeedbagHandler_QueryIfModifiedHandler_Call) Run(run func(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x13_0x05_FeedbagQueryIfModified)) *MockFeedbagHandler_QueryIfModifiedHandler_Call {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(*Session), args[2].(oscar.SNAC_0x13_0x05_FeedbagQueryIfModified))
+		run(args[0].(context.Context), args[1].(*state.Session), args[2].(oscar.SNAC_0x13_0x05_FeedbagQueryIfModified))
 	})
 	return _c
 }
@@ -232,7 +234,7 @@ func (_c *MockFeedbagHandler_QueryIfModifiedHandler_Call) Return(_a0 oscar.XMess
 	return _c
 }
 
-func (_c *MockFeedbagHandler_QueryIfModifiedHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x13_0x05_FeedbagQueryIfModified) (oscar.XMessage, error)) *MockFeedbagHandler_QueryIfModifiedHandler_Call {
+func (_c *MockFeedbagHandler_QueryIfModifiedHandler_Call) RunAndReturn(run func(context.Context, *state.Session, oscar.SNAC_0x13_0x05_FeedbagQueryIfModified) (oscar.XMessage, error)) *MockFeedbagHandler_QueryIfModifiedHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }
@@ -314,21 +316,21 @@ func (_c *MockFeedbagHandler_StartClusterHandler_Call) RunAndReturn(run func(con
 }
 
 // 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) (oscar.XMessage, error) {
+func (_m *MockFeedbagHandler) UpdateItemHandler(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x13_0x09_FeedbagUpdateItem) (oscar.XMessage, error) {
 	ret := _m.Called(ctx, sess, snacPayloadIn)
 
 	var r0 oscar.XMessage
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x13_0x09_FeedbagUpdateItem) (oscar.XMessage, error)); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *state.Session, oscar.SNAC_0x13_0x09_FeedbagUpdateItem) (oscar.XMessage, error)); ok {
 		return rf(ctx, sess, snacPayloadIn)
 	}
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x13_0x09_FeedbagUpdateItem) oscar.XMessage); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *state.Session, oscar.SNAC_0x13_0x09_FeedbagUpdateItem) oscar.XMessage); ok {
 		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r0 = ret.Get(0).(oscar.XMessage)
 	}
 
-	if rf, ok := ret.Get(1).(func(context.Context, *Session, oscar.SNAC_0x13_0x09_FeedbagUpdateItem) error); ok {
+	if rf, ok := ret.Get(1).(func(context.Context, *state.Session, oscar.SNAC_0x13_0x09_FeedbagUpdateItem) error); ok {
 		r1 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r1 = ret.Error(1)
@@ -344,15 +346,15 @@ type MockFeedbagHandler_UpdateItemHandler_Call struct {
 
 // UpdateItemHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *state.Session
 //   - snacPayloadIn oscar.SNAC_0x13_0x09_FeedbagUpdateItem
 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, sess *Session, snacPayloadIn oscar.SNAC_0x13_0x09_FeedbagUpdateItem)) *MockFeedbagHandler_UpdateItemHandler_Call {
+func (_c *MockFeedbagHandler_UpdateItemHandler_Call) Run(run func(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x13_0x09_FeedbagUpdateItem)) *MockFeedbagHandler_UpdateItemHandler_Call {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(*Session), args[2].(oscar.SNAC_0x13_0x09_FeedbagUpdateItem))
+		run(args[0].(context.Context), args[1].(*state.Session), args[2].(oscar.SNAC_0x13_0x09_FeedbagUpdateItem))
 	})
 	return _c
 }
@@ -362,7 +364,7 @@ func (_c *MockFeedbagHandler_UpdateItemHandler_Call) Return(_a0 oscar.XMessage,
 	return _c
 }
 
-func (_c *MockFeedbagHandler_UpdateItemHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x13_0x09_FeedbagUpdateItem) (oscar.XMessage, error)) *MockFeedbagHandler_UpdateItemHandler_Call {
+func (_c *MockFeedbagHandler_UpdateItemHandler_Call) RunAndReturn(run func(context.Context, *state.Session, oscar.SNAC_0x13_0x09_FeedbagUpdateItem) (oscar.XMessage, error)) *MockFeedbagHandler_UpdateItemHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }

+ 5 - 4
server/icbm.go

@@ -2,6 +2,7 @@ package server
 
 import (
 	"context"
+	"github.com/mkaminski/goaim/state"
 	"io"
 	"log/slog"
 
@@ -9,9 +10,9 @@ import (
 )
 
 type ICBMHandler interface {
-	ChannelMsgToHostHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost) (*oscar.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) (oscar.XMessage, error)
+	ChannelMsgToHostHandler(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost) (*oscar.XMessage, error)
+	ClientEventHandler(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x04_0x14_ICBMClientEvent) error
+	EvilRequestHandler(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x04_0x08_ICBMEvilRequest) (oscar.XMessage, error)
 	ParameterQueryHandler(context.Context) oscar.XMessage
 }
 
@@ -29,7 +30,7 @@ type ICBMRouter struct {
 	RouteLogger
 }
 
-func (rt *ICBMRouter) RouteICBM(ctx context.Context, sess *Session, SNACFrame oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
+func (rt *ICBMRouter) RouteICBM(ctx context.Context, sess *state.Session, SNACFrame oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
 	switch SNACFrame.SubGroup {
 	case oscar.ICBMAddParameters:
 		inSNAC := oscar.SNAC_0x04_0x02_ICBMAddParameters{}

+ 24 - 22
server/icbm_mock.go

@@ -7,6 +7,8 @@ import (
 
 	oscar "github.com/mkaminski/goaim/oscar"
 	mock "github.com/stretchr/testify/mock"
+
+	state "github.com/mkaminski/goaim/state"
 )
 
 // MockICBMHandler is an autogenerated mock type for the ICBMHandler type
@@ -23,15 +25,15 @@ func (_m *MockICBMHandler) EXPECT() *MockICBMHandler_Expecter {
 }
 
 // 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) (*oscar.XMessage, error) {
+func (_m *MockICBMHandler) ChannelMsgToHostHandler(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost) (*oscar.XMessage, error) {
 	ret := _m.Called(ctx, sess, snacPayloadIn)
 
 	var r0 *oscar.XMessage
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost) (*oscar.XMessage, error)); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *state.Session, oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost) (*oscar.XMessage, error)); ok {
 		return rf(ctx, sess, snacPayloadIn)
 	}
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost) *oscar.XMessage); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *state.Session, oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost) *oscar.XMessage); ok {
 		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		if ret.Get(0) != nil {
@@ -39,7 +41,7 @@ func (_m *MockICBMHandler) ChannelMsgToHostHandler(ctx context.Context, sess *Se
 		}
 	}
 
-	if rf, ok := ret.Get(1).(func(context.Context, *Session, oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost) error); ok {
+	if rf, ok := ret.Get(1).(func(context.Context, *state.Session, oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost) error); ok {
 		r1 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r1 = ret.Error(1)
@@ -55,15 +57,15 @@ type MockICBMHandler_ChannelMsgToHostHandler_Call struct {
 
 // ChannelMsgToHostHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *state.Session
 //   - snacPayloadIn oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost
 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, sess *Session, snacPayloadIn oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost)) *MockICBMHandler_ChannelMsgToHostHandler_Call {
+func (_c *MockICBMHandler_ChannelMsgToHostHandler_Call) Run(run func(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost)) *MockICBMHandler_ChannelMsgToHostHandler_Call {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(*Session), args[2].(oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost))
+		run(args[0].(context.Context), args[1].(*state.Session), args[2].(oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost))
 	})
 	return _c
 }
@@ -73,17 +75,17 @@ func (_c *MockICBMHandler_ChannelMsgToHostHandler_Call) Return(_a0 *oscar.XMessa
 	return _c
 }
 
-func (_c *MockICBMHandler_ChannelMsgToHostHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost) (*oscar.XMessage, error)) *MockICBMHandler_ChannelMsgToHostHandler_Call {
+func (_c *MockICBMHandler_ChannelMsgToHostHandler_Call) RunAndReturn(run func(context.Context, *state.Session, oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost) (*oscar.XMessage, error)) *MockICBMHandler_ChannelMsgToHostHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }
 
 // 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 {
+func (_m *MockICBMHandler) ClientEventHandler(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x04_0x14_ICBMClientEvent) error {
 	ret := _m.Called(ctx, sess, snacPayloadIn)
 
 	var r0 error
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x04_0x14_ICBMClientEvent) error); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *state.Session, oscar.SNAC_0x04_0x14_ICBMClientEvent) error); ok {
 		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r0 = ret.Error(0)
@@ -99,15 +101,15 @@ type MockICBMHandler_ClientEventHandler_Call struct {
 
 // ClientEventHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *state.Session
 //   - snacPayloadIn oscar.SNAC_0x04_0x14_ICBMClientEvent
 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, sess *Session, snacPayloadIn oscar.SNAC_0x04_0x14_ICBMClientEvent)) *MockICBMHandler_ClientEventHandler_Call {
+func (_c *MockICBMHandler_ClientEventHandler_Call) Run(run func(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x04_0x14_ICBMClientEvent)) *MockICBMHandler_ClientEventHandler_Call {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(*Session), args[2].(oscar.SNAC_0x04_0x14_ICBMClientEvent))
+		run(args[0].(context.Context), args[1].(*state.Session), args[2].(oscar.SNAC_0x04_0x14_ICBMClientEvent))
 	})
 	return _c
 }
@@ -117,27 +119,27 @@ func (_c *MockICBMHandler_ClientEventHandler_Call) Return(_a0 error) *MockICBMHa
 	return _c
 }
 
-func (_c *MockICBMHandler_ClientEventHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x04_0x14_ICBMClientEvent) error) *MockICBMHandler_ClientEventHandler_Call {
+func (_c *MockICBMHandler_ClientEventHandler_Call) RunAndReturn(run func(context.Context, *state.Session, oscar.SNAC_0x04_0x14_ICBMClientEvent) error) *MockICBMHandler_ClientEventHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }
 
 // 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) (oscar.XMessage, error) {
+func (_m *MockICBMHandler) EvilRequestHandler(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x04_0x08_ICBMEvilRequest) (oscar.XMessage, error) {
 	ret := _m.Called(ctx, sess, snacPayloadIn)
 
 	var r0 oscar.XMessage
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x04_0x08_ICBMEvilRequest) (oscar.XMessage, error)); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *state.Session, oscar.SNAC_0x04_0x08_ICBMEvilRequest) (oscar.XMessage, error)); ok {
 		return rf(ctx, sess, snacPayloadIn)
 	}
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x04_0x08_ICBMEvilRequest) oscar.XMessage); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *state.Session, oscar.SNAC_0x04_0x08_ICBMEvilRequest) oscar.XMessage); ok {
 		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r0 = ret.Get(0).(oscar.XMessage)
 	}
 
-	if rf, ok := ret.Get(1).(func(context.Context, *Session, oscar.SNAC_0x04_0x08_ICBMEvilRequest) error); ok {
+	if rf, ok := ret.Get(1).(func(context.Context, *state.Session, oscar.SNAC_0x04_0x08_ICBMEvilRequest) error); ok {
 		r1 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r1 = ret.Error(1)
@@ -153,15 +155,15 @@ type MockICBMHandler_EvilRequestHandler_Call struct {
 
 // EvilRequestHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *state.Session
 //   - snacPayloadIn oscar.SNAC_0x04_0x08_ICBMEvilRequest
 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, sess *Session, snacPayloadIn oscar.SNAC_0x04_0x08_ICBMEvilRequest)) *MockICBMHandler_EvilRequestHandler_Call {
+func (_c *MockICBMHandler_EvilRequestHandler_Call) Run(run func(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x04_0x08_ICBMEvilRequest)) *MockICBMHandler_EvilRequestHandler_Call {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(*Session), args[2].(oscar.SNAC_0x04_0x08_ICBMEvilRequest))
+		run(args[0].(context.Context), args[1].(*state.Session), args[2].(oscar.SNAC_0x04_0x08_ICBMEvilRequest))
 	})
 	return _c
 }
@@ -171,7 +173,7 @@ func (_c *MockICBMHandler_EvilRequestHandler_Call) Return(_a0 oscar.XMessage, _a
 	return _c
 }
 
-func (_c *MockICBMHandler_EvilRequestHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x04_0x08_ICBMEvilRequest) (oscar.XMessage, error)) *MockICBMHandler_EvilRequestHandler_Call {
+func (_c *MockICBMHandler_EvilRequestHandler_Call) RunAndReturn(run func(context.Context, *state.Session, oscar.SNAC_0x04_0x08_ICBMEvilRequest) (oscar.XMessage, error)) *MockICBMHandler_EvilRequestHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }

+ 4 - 3
server/locate.go

@@ -2,6 +2,7 @@ package server
 
 import (
 	"context"
+	"github.com/mkaminski/goaim/state"
 	"io"
 	"log/slog"
 
@@ -11,9 +12,9 @@ import (
 type LocateHandler interface {
 	RightsQueryHandler(ctx context.Context) oscar.XMessage
 	SetDirInfoHandler(ctx context.Context) oscar.XMessage
-	SetInfoHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x02_0x04_LocateSetInfo) error
+	SetInfoHandler(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x02_0x04_LocateSetInfo) error
 	SetKeywordInfoHandler(ctx context.Context) oscar.XMessage
-	UserInfoQuery2Handler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x02_0x15_LocateUserInfoQuery2) (oscar.XMessage, error)
+	UserInfoQuery2Handler(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x02_0x15_LocateUserInfoQuery2) (oscar.XMessage, error)
 }
 
 func NewLocateRouter(handler LocateHandler, logger *slog.Logger) LocateRouter {
@@ -30,7 +31,7 @@ type LocateRouter struct {
 	RouteLogger
 }
 
-func (rt LocateRouter) RouteLocate(ctx context.Context, sess *Session, SNACFrame oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
+func (rt LocateRouter) RouteLocate(ctx context.Context, sess *state.Session, SNACFrame oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
 	switch SNACFrame.SubGroup {
 	case oscar.LocateRightsQuery:
 		outSNAC := rt.RightsQueryHandler(ctx)

+ 16 - 14
server/locate_mock.go

@@ -7,6 +7,8 @@ import (
 
 	oscar "github.com/mkaminski/goaim/oscar"
 	mock "github.com/stretchr/testify/mock"
+
+	state "github.com/mkaminski/goaim/state"
 )
 
 // MockLocateHandler is an autogenerated mock type for the LocateHandler type
@@ -107,11 +109,11 @@ func (_c *MockLocateHandler_SetDirInfoHandler_Call) RunAndReturn(run func(contex
 }
 
 // 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 {
+func (_m *MockLocateHandler) SetInfoHandler(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x02_0x04_LocateSetInfo) error {
 	ret := _m.Called(ctx, sess, snacPayloadIn)
 
 	var r0 error
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x02_0x04_LocateSetInfo) error); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *state.Session, oscar.SNAC_0x02_0x04_LocateSetInfo) error); ok {
 		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r0 = ret.Error(0)
@@ -127,15 +129,15 @@ type MockLocateHandler_SetInfoHandler_Call struct {
 
 // SetInfoHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *state.Session
 //   - snacPayloadIn oscar.SNAC_0x02_0x04_LocateSetInfo
 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, snacPayloadIn oscar.SNAC_0x02_0x04_LocateSetInfo)) *MockLocateHandler_SetInfoHandler_Call {
+func (_c *MockLocateHandler_SetInfoHandler_Call) Run(run func(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x02_0x04_LocateSetInfo)) *MockLocateHandler_SetInfoHandler_Call {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(*Session), args[2].(oscar.SNAC_0x02_0x04_LocateSetInfo))
+		run(args[0].(context.Context), args[1].(*state.Session), args[2].(oscar.SNAC_0x02_0x04_LocateSetInfo))
 	})
 	return _c
 }
@@ -145,7 +147,7 @@ func (_c *MockLocateHandler_SetInfoHandler_Call) Return(_a0 error) *MockLocateHa
 	return _c
 }
 
-func (_c *MockLocateHandler_SetInfoHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x02_0x04_LocateSetInfo) error) *MockLocateHandler_SetInfoHandler_Call {
+func (_c *MockLocateHandler_SetInfoHandler_Call) RunAndReturn(run func(context.Context, *state.Session, oscar.SNAC_0x02_0x04_LocateSetInfo) error) *MockLocateHandler_SetInfoHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }
@@ -193,21 +195,21 @@ func (_c *MockLocateHandler_SetKeywordInfoHandler_Call) RunAndReturn(run func(co
 }
 
 // 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) (oscar.XMessage, error) {
+func (_m *MockLocateHandler) UserInfoQuery2Handler(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x02_0x15_LocateUserInfoQuery2) (oscar.XMessage, error) {
 	ret := _m.Called(ctx, sess, snacPayloadIn)
 
 	var r0 oscar.XMessage
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x02_0x15_LocateUserInfoQuery2) (oscar.XMessage, error)); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *state.Session, oscar.SNAC_0x02_0x15_LocateUserInfoQuery2) (oscar.XMessage, error)); ok {
 		return rf(ctx, sess, snacPayloadIn)
 	}
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x02_0x15_LocateUserInfoQuery2) oscar.XMessage); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *state.Session, oscar.SNAC_0x02_0x15_LocateUserInfoQuery2) oscar.XMessage); ok {
 		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r0 = ret.Get(0).(oscar.XMessage)
 	}
 
-	if rf, ok := ret.Get(1).(func(context.Context, *Session, oscar.SNAC_0x02_0x15_LocateUserInfoQuery2) error); ok {
+	if rf, ok := ret.Get(1).(func(context.Context, *state.Session, oscar.SNAC_0x02_0x15_LocateUserInfoQuery2) error); ok {
 		r1 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r1 = ret.Error(1)
@@ -223,15 +225,15 @@ type MockLocateHandler_UserInfoQuery2Handler_Call struct {
 
 // UserInfoQuery2Handler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *state.Session
 //   - snacPayloadIn oscar.SNAC_0x02_0x15_LocateUserInfoQuery2
 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, snacPayloadIn oscar.SNAC_0x02_0x15_LocateUserInfoQuery2)) *MockLocateHandler_UserInfoQuery2Handler_Call {
+func (_c *MockLocateHandler_UserInfoQuery2Handler_Call) Run(run func(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x02_0x15_LocateUserInfoQuery2)) *MockLocateHandler_UserInfoQuery2Handler_Call {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(*Session), args[2].(oscar.SNAC_0x02_0x15_LocateUserInfoQuery2))
+		run(args[0].(context.Context), args[1].(*state.Session), args[2].(oscar.SNAC_0x02_0x15_LocateUserInfoQuery2))
 	})
 	return _c
 }
@@ -241,7 +243,7 @@ func (_c *MockLocateHandler_UserInfoQuery2Handler_Call) Return(_a0 oscar.XMessag
 	return _c
 }
 
-func (_c *MockLocateHandler_UserInfoQuery2Handler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x02_0x15_LocateUserInfoQuery2) (oscar.XMessage, error)) *MockLocateHandler_UserInfoQuery2Handler_Call {
+func (_c *MockLocateHandler_UserInfoQuery2Handler_Call) RunAndReturn(run func(context.Context, *state.Session, oscar.SNAC_0x02_0x15_LocateUserInfoQuery2) (oscar.XMessage, error)) *MockLocateHandler_UserInfoQuery2Handler_Call {
 	_c.Call.Return(run)
 	return _c
 }

+ 5 - 4
server/mgmt_api.go

@@ -3,6 +3,7 @@ package server
 import (
 	"encoding/json"
 	"fmt"
+	"github.com/mkaminski/goaim/state"
 	"log/slog"
 	"net"
 	"net/http"
@@ -11,7 +12,7 @@ import (
 	"github.com/google/uuid"
 )
 
-func StartManagementAPI(fs *SQLiteFeedbagStore, logger *slog.Logger) {
+func StartManagementAPI(fs *state.SQLiteFeedbagStore, logger *slog.Logger) {
 	http.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) {
 		switch r.Method {
 		case http.MethodGet:
@@ -37,7 +38,7 @@ func StartManagementAPI(fs *SQLiteFeedbagStore, logger *slog.Logger) {
 }
 
 // getUsers handles the GET /user endpoint.
-func getUsers(fs *SQLiteFeedbagStore, w http.ResponseWriter, r *http.Request) {
+func getUsers(fs *state.SQLiteFeedbagStore, w http.ResponseWriter, r *http.Request) {
 	w.Header().Set("Content-Type", "application/json")
 	users, err := fs.Users()
 	if err != nil {
@@ -51,12 +52,12 @@ func getUsers(fs *SQLiteFeedbagStore, w http.ResponseWriter, r *http.Request) {
 }
 
 type CreateUser struct {
-	User
+	state.User
 	Password string `json:"password,omitempty"`
 }
 
 // createUser handles the POST /user endpoint.
-func createUser(fs *SQLiteFeedbagStore, w http.ResponseWriter, r *http.Request) {
+func createUser(fs *state.SQLiteFeedbagStore, w http.ResponseWriter, r *http.Request) {
 	var newUser CreateUser
 	if err := json.NewDecoder(r.Body).Decode(&newUser); err != nil {
 		http.Error(w, err.Error(), http.StatusBadRequest)

+ 12 - 11
server/oservice.go

@@ -4,31 +4,32 @@ import (
 	"context"
 	"errors"
 	"github.com/mkaminski/goaim/oscar"
+	"github.com/mkaminski/goaim/state"
 	"io"
 	"log/slog"
 )
 
 type OServiceHandler interface {
 	ClientVersionsHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x17_OServiceClientVersions) oscar.XMessage
-	IdleNotificationHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x01_0x11_OServiceIdleNotification) error
+	IdleNotificationHandler(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x01_0x11_OServiceIdleNotification) error
 	RateParamsQueryHandler(ctx context.Context) oscar.XMessage
 	RateParamsSubAddHandler(context.Context, oscar.SNAC_0x01_0x08_OServiceRateParamsSubAdd)
-	SetUserInfoFieldsHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (oscar.XMessage, error)
-	UserInfoQueryHandler(ctx context.Context, sess *Session) oscar.XMessage
+	SetUserInfoFieldsHandler(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (oscar.XMessage, error)
+	UserInfoQueryHandler(ctx context.Context, sess *state.Session) oscar.XMessage
 }
 
 type OServiceBOSHandler interface {
 	OServiceHandler
 	WriteOServiceHostOnline() oscar.XMessage
-	ServiceRequestHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x01_0x04_OServiceServiceRequest) (oscar.XMessage, error)
-	ClientOnlineHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *Session) error
+	ServiceRequestHandler(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x01_0x04_OServiceServiceRequest) (oscar.XMessage, error)
+	ClientOnlineHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *state.Session) error
 }
 
 type OServiceChatHandler interface {
 	OServiceHandler
 	WriteOServiceHostOnline() oscar.XMessage
-	ServiceRequestHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x01_0x04_OServiceServiceRequest) (oscar.XMessage, error)
-	ClientOnlineHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *Session, chatSessMgr ChatSessionManager, room ChatRoom) error
+	ServiceRequestHandler(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x01_0x04_OServiceServiceRequest) (oscar.XMessage, error)
+	ClientOnlineHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *state.Session, chatID string) error
 }
 
 type OServiceRouter struct {
@@ -36,7 +37,7 @@ type OServiceRouter struct {
 	RouteLogger
 }
 
-func (rt OServiceRouter) RouteOService(ctx context.Context, sess *Session, SNACFrame oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
+func (rt OServiceRouter) RouteOService(ctx context.Context, sess *state.Session, SNACFrame oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
 	switch SNACFrame.SubGroup {
 	case oscar.OServiceRateParamsQuery:
 		outSNAC := rt.RateParamsQueryHandler(ctx)
@@ -102,7 +103,7 @@ type OServiceBOSRouter struct {
 	OServiceBOSHandler
 }
 
-func (rt OServiceBOSRouter) RouteOService(ctx context.Context, sess *Session, SNACFrame oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
+func (rt OServiceBOSRouter) RouteOService(ctx context.Context, sess *state.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{}
@@ -148,7 +149,7 @@ type OServiceChatRouter struct {
 	OServiceChatHandler
 }
 
-func (rt OServiceChatRouter) RouteOService(ctx context.Context, sess *Session, chatSessMgr ChatSessionManager, room ChatRoom, SNACFrame oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
+func (rt OServiceChatRouter) RouteOService(ctx context.Context, sess *state.Session, chatID string, SNACFrame oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
 	switch SNACFrame.SubGroup {
 	case oscar.OServiceServiceRequest:
 		inSNAC := oscar.SNAC_0x01_0x04_OServiceServiceRequest{}
@@ -171,7 +172,7 @@ func (rt OServiceChatRouter) RouteOService(ctx context.Context, sess *Session, c
 		}
 		rt.Logger.InfoContext(ctx, "user signed on")
 		rt.logRequest(ctx, SNACFrame, inSNAC)
-		return rt.OServiceChatHandler.ClientOnlineHandler(ctx, inSNAC, sess, chatSessMgr, room)
+		return rt.OServiceChatHandler.ClientOnlineHandler(ctx, inSNAC, sess, chatID)
 	default:
 		return rt.OServiceRouter.RouteOService(ctx, sess, SNACFrame, r, w, sequence)
 	}

+ 36 - 34
server/oservice_bos_mock.go

@@ -7,6 +7,8 @@ import (
 
 	oscar "github.com/mkaminski/goaim/oscar"
 	mock "github.com/stretchr/testify/mock"
+
+	state "github.com/mkaminski/goaim/state"
 )
 
 // MockOServiceBOSHandler is an autogenerated mock type for the OServiceBOSHandler type
@@ -23,11 +25,11 @@ func (_m *MockOServiceBOSHandler) EXPECT() *MockOServiceBOSHandler_Expecter {
 }
 
 // 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 {
+func (_m *MockOServiceBOSHandler) ClientOnlineHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *state.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 {
+	if rf, ok := ret.Get(0).(func(context.Context, oscar.SNAC_0x01_0x02_OServiceClientOnline, *state.Session) error); ok {
 		r0 = rf(ctx, snacPayloadIn, sess)
 	} else {
 		r0 = ret.Error(0)
@@ -44,14 +46,14 @@ type MockOServiceBOSHandler_ClientOnlineHandler_Call struct {
 // ClientOnlineHandler is a helper method to define mock.On call
 //   - ctx context.Context
 //   - snacPayloadIn oscar.SNAC_0x01_0x02_OServiceClientOnline
-//   - sess *Session
+//   - sess *state.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 {
+func (_c *MockOServiceBOSHandler_ClientOnlineHandler_Call) Run(run func(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *state.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))
+		run(args[0].(context.Context), args[1].(oscar.SNAC_0x01_0x02_OServiceClientOnline), args[2].(*state.Session))
 	})
 	return _c
 }
@@ -61,7 +63,7 @@ func (_c *MockOServiceBOSHandler_ClientOnlineHandler_Call) Return(_a0 error) *Mo
 	return _c
 }
 
-func (_c *MockOServiceBOSHandler_ClientOnlineHandler_Call) RunAndReturn(run func(context.Context, oscar.SNAC_0x01_0x02_OServiceClientOnline, *Session) error) *MockOServiceBOSHandler_ClientOnlineHandler_Call {
+func (_c *MockOServiceBOSHandler_ClientOnlineHandler_Call) RunAndReturn(run func(context.Context, oscar.SNAC_0x01_0x02_OServiceClientOnline, *state.Session) error) *MockOServiceBOSHandler_ClientOnlineHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }
@@ -110,11 +112,11 @@ func (_c *MockOServiceBOSHandler_ClientVersionsHandler_Call) RunAndReturn(run fu
 }
 
 // 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 {
+func (_m *MockOServiceBOSHandler) IdleNotificationHandler(ctx context.Context, sess *state.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 {
+	if rf, ok := ret.Get(0).(func(context.Context, *state.Session, oscar.SNAC_0x01_0x11_OServiceIdleNotification) error); ok {
 		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r0 = ret.Error(0)
@@ -130,15 +132,15 @@ type MockOServiceBOSHandler_IdleNotificationHandler_Call struct {
 
 // IdleNotificationHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *state.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 {
+func (_c *MockOServiceBOSHandler_IdleNotificationHandler_Call) Run(run func(ctx context.Context, sess *state.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))
+		run(args[0].(context.Context), args[1].(*state.Session), args[2].(oscar.SNAC_0x01_0x11_OServiceIdleNotification))
 	})
 	return _c
 }
@@ -148,7 +150,7 @@ func (_c *MockOServiceBOSHandler_IdleNotificationHandler_Call) Return(_a0 error)
 	return _c
 }
 
-func (_c *MockOServiceBOSHandler_IdleNotificationHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x01_0x11_OServiceIdleNotification) error) *MockOServiceBOSHandler_IdleNotificationHandler_Call {
+func (_c *MockOServiceBOSHandler_IdleNotificationHandler_Call) RunAndReturn(run func(context.Context, *state.Session, oscar.SNAC_0x01_0x11_OServiceIdleNotification) error) *MockOServiceBOSHandler_IdleNotificationHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }
@@ -230,21 +232,21 @@ func (_c *MockOServiceBOSHandler_RateParamsSubAddHandler_Call) RunAndReturn(run
 }
 
 // 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) (oscar.XMessage, error) {
+func (_m *MockOServiceBOSHandler) ServiceRequestHandler(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x01_0x04_OServiceServiceRequest) (oscar.XMessage, error) {
 	ret := _m.Called(ctx, sess, snacPayloadIn)
 
 	var r0 oscar.XMessage
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x01_0x04_OServiceServiceRequest) (oscar.XMessage, error)); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *state.Session, oscar.SNAC_0x01_0x04_OServiceServiceRequest) (oscar.XMessage, error)); ok {
 		return rf(ctx, sess, snacPayloadIn)
 	}
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x01_0x04_OServiceServiceRequest) oscar.XMessage); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *state.Session, oscar.SNAC_0x01_0x04_OServiceServiceRequest) oscar.XMessage); ok {
 		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r0 = ret.Get(0).(oscar.XMessage)
 	}
 
-	if rf, ok := ret.Get(1).(func(context.Context, *Session, oscar.SNAC_0x01_0x04_OServiceServiceRequest) error); ok {
+	if rf, ok := ret.Get(1).(func(context.Context, *state.Session, oscar.SNAC_0x01_0x04_OServiceServiceRequest) error); ok {
 		r1 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r1 = ret.Error(1)
@@ -260,15 +262,15 @@ type MockOServiceBOSHandler_ServiceRequestHandler_Call struct {
 
 // ServiceRequestHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *state.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 {
+func (_c *MockOServiceBOSHandler_ServiceRequestHandler_Call) Run(run func(ctx context.Context, sess *state.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))
+		run(args[0].(context.Context), args[1].(*state.Session), args[2].(oscar.SNAC_0x01_0x04_OServiceServiceRequest))
 	})
 	return _c
 }
@@ -278,27 +280,27 @@ func (_c *MockOServiceBOSHandler_ServiceRequestHandler_Call) Return(_a0 oscar.XM
 	return _c
 }
 
-func (_c *MockOServiceBOSHandler_ServiceRequestHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x01_0x04_OServiceServiceRequest) (oscar.XMessage, error)) *MockOServiceBOSHandler_ServiceRequestHandler_Call {
+func (_c *MockOServiceBOSHandler_ServiceRequestHandler_Call) RunAndReturn(run func(context.Context, *state.Session, oscar.SNAC_0x01_0x04_OServiceServiceRequest) (oscar.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) (oscar.XMessage, error) {
+func (_m *MockOServiceBOSHandler) SetUserInfoFieldsHandler(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (oscar.XMessage, error) {
 	ret := _m.Called(ctx, sess, snacPayloadIn)
 
 	var r0 oscar.XMessage
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (oscar.XMessage, error)); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *state.Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (oscar.XMessage, error)); ok {
 		return rf(ctx, sess, snacPayloadIn)
 	}
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) oscar.XMessage); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *state.Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) oscar.XMessage); ok {
 		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r0 = ret.Get(0).(oscar.XMessage)
 	}
 
-	if rf, ok := ret.Get(1).(func(context.Context, *Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) error); ok {
+	if rf, ok := ret.Get(1).(func(context.Context, *state.Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) error); ok {
 		r1 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r1 = ret.Error(1)
@@ -314,15 +316,15 @@ type MockOServiceBOSHandler_SetUserInfoFieldsHandler_Call struct {
 
 // SetUserInfoFieldsHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *state.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 {
+func (_c *MockOServiceBOSHandler_SetUserInfoFieldsHandler_Call) Run(run func(ctx context.Context, sess *state.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))
+		run(args[0].(context.Context), args[1].(*state.Session), args[2].(oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields))
 	})
 	return _c
 }
@@ -332,17 +334,17 @@ func (_c *MockOServiceBOSHandler_SetUserInfoFieldsHandler_Call) Return(_a0 oscar
 	return _c
 }
 
-func (_c *MockOServiceBOSHandler_SetUserInfoFieldsHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (oscar.XMessage, error)) *MockOServiceBOSHandler_SetUserInfoFieldsHandler_Call {
+func (_c *MockOServiceBOSHandler_SetUserInfoFieldsHandler_Call) RunAndReturn(run func(context.Context, *state.Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (oscar.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) oscar.XMessage {
+func (_m *MockOServiceBOSHandler) UserInfoQueryHandler(ctx context.Context, sess *state.Session) oscar.XMessage {
 	ret := _m.Called(ctx, sess)
 
 	var r0 oscar.XMessage
-	if rf, ok := ret.Get(0).(func(context.Context, *Session) oscar.XMessage); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *state.Session) oscar.XMessage); ok {
 		r0 = rf(ctx, sess)
 	} else {
 		r0 = ret.Get(0).(oscar.XMessage)
@@ -358,14 +360,14 @@ type MockOServiceBOSHandler_UserInfoQueryHandler_Call struct {
 
 // UserInfoQueryHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *state.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 {
+func (_c *MockOServiceBOSHandler_UserInfoQueryHandler_Call) Run(run func(ctx context.Context, sess *state.Session)) *MockOServiceBOSHandler_UserInfoQueryHandler_Call {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(*Session))
+		run(args[0].(context.Context), args[1].(*state.Session))
 	})
 	return _c
 }
@@ -375,7 +377,7 @@ func (_c *MockOServiceBOSHandler_UserInfoQueryHandler_Call) Return(_a0 oscar.XMe
 	return _c
 }
 
-func (_c *MockOServiceBOSHandler_UserInfoQueryHandler_Call) RunAndReturn(run func(context.Context, *Session) oscar.XMessage) *MockOServiceBOSHandler_UserInfoQueryHandler_Call {
+func (_c *MockOServiceBOSHandler_UserInfoQueryHandler_Call) RunAndReturn(run func(context.Context, *state.Session) oscar.XMessage) *MockOServiceBOSHandler_UserInfoQueryHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }

+ 42 - 41
server/oservice_chat_mock.go

@@ -7,6 +7,8 @@ import (
 
 	oscar "github.com/mkaminski/goaim/oscar"
 	mock "github.com/stretchr/testify/mock"
+
+	state "github.com/mkaminski/goaim/state"
 )
 
 // MockOServiceChatHandler is an autogenerated mock type for the OServiceChatHandler type
@@ -22,13 +24,13 @@ func (_m *MockOServiceChatHandler) EXPECT() *MockOServiceChatHandler_Expecter {
 	return &MockOServiceChatHandler_Expecter{mock: &_m.Mock}
 }
 
-// ClientOnlineHandler provides a mock function with given fields: ctx, snacPayloadIn, sess, chatSessMgr, room
-func (_m *MockOServiceChatHandler) ClientOnlineHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *Session, chatSessMgr ChatSessionManager, room ChatRoom) error {
-	ret := _m.Called(ctx, snacPayloadIn, sess, chatSessMgr, room)
+// ClientOnlineHandler provides a mock function with given fields: ctx, snacPayloadIn, sess, chatID
+func (_m *MockOServiceChatHandler) ClientOnlineHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *state.Session, chatID string) error {
+	ret := _m.Called(ctx, snacPayloadIn, sess, chatID)
 
 	var r0 error
-	if rf, ok := ret.Get(0).(func(context.Context, oscar.SNAC_0x01_0x02_OServiceClientOnline, *Session, ChatSessionManager, ChatRoom) error); ok {
-		r0 = rf(ctx, snacPayloadIn, sess, chatSessMgr, room)
+	if rf, ok := ret.Get(0).(func(context.Context, oscar.SNAC_0x01_0x02_OServiceClientOnline, *state.Session, string) error); ok {
+		r0 = rf(ctx, snacPayloadIn, sess, chatID)
 	} else {
 		r0 = ret.Error(0)
 	}
@@ -44,16 +46,15 @@ type MockOServiceChatHandler_ClientOnlineHandler_Call struct {
 // ClientOnlineHandler is a helper method to define mock.On call
 //   - ctx context.Context
 //   - snacPayloadIn oscar.SNAC_0x01_0x02_OServiceClientOnline
-//   - sess *Session
-//   - chatSessMgr ChatSessionManager
-//   - room ChatRoom
-func (_e *MockOServiceChatHandler_Expecter) ClientOnlineHandler(ctx interface{}, snacPayloadIn interface{}, sess interface{}, chatSessMgr interface{}, room interface{}) *MockOServiceChatHandler_ClientOnlineHandler_Call {
-	return &MockOServiceChatHandler_ClientOnlineHandler_Call{Call: _e.mock.On("ClientOnlineHandler", ctx, snacPayloadIn, sess, chatSessMgr, room)}
+//   - sess *state.Session
+//   - chatID string
+func (_e *MockOServiceChatHandler_Expecter) ClientOnlineHandler(ctx interface{}, snacPayloadIn interface{}, sess interface{}, chatID interface{}) *MockOServiceChatHandler_ClientOnlineHandler_Call {
+	return &MockOServiceChatHandler_ClientOnlineHandler_Call{Call: _e.mock.On("ClientOnlineHandler", ctx, snacPayloadIn, sess, chatID)}
 }
 
-func (_c *MockOServiceChatHandler_ClientOnlineHandler_Call) Run(run func(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *Session, chatSessMgr ChatSessionManager, room ChatRoom)) *MockOServiceChatHandler_ClientOnlineHandler_Call {
+func (_c *MockOServiceChatHandler_ClientOnlineHandler_Call) Run(run func(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *state.Session, chatID string)) *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].(ChatSessionManager), args[4].(ChatRoom))
+		run(args[0].(context.Context), args[1].(oscar.SNAC_0x01_0x02_OServiceClientOnline), args[2].(*state.Session), args[3].(string))
 	})
 	return _c
 }
@@ -63,7 +64,7 @@ func (_c *MockOServiceChatHandler_ClientOnlineHandler_Call) Return(_a0 error) *M
 	return _c
 }
 
-func (_c *MockOServiceChatHandler_ClientOnlineHandler_Call) RunAndReturn(run func(context.Context, oscar.SNAC_0x01_0x02_OServiceClientOnline, *Session, ChatSessionManager, ChatRoom) error) *MockOServiceChatHandler_ClientOnlineHandler_Call {
+func (_c *MockOServiceChatHandler_ClientOnlineHandler_Call) RunAndReturn(run func(context.Context, oscar.SNAC_0x01_0x02_OServiceClientOnline, *state.Session, string) error) *MockOServiceChatHandler_ClientOnlineHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }
@@ -112,11 +113,11 @@ func (_c *MockOServiceChatHandler_ClientVersionsHandler_Call) RunAndReturn(run f
 }
 
 // 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 {
+func (_m *MockOServiceChatHandler) IdleNotificationHandler(ctx context.Context, sess *state.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 {
+	if rf, ok := ret.Get(0).(func(context.Context, *state.Session, oscar.SNAC_0x01_0x11_OServiceIdleNotification) error); ok {
 		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r0 = ret.Error(0)
@@ -132,15 +133,15 @@ type MockOServiceChatHandler_IdleNotificationHandler_Call struct {
 
 // IdleNotificationHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *state.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 {
+func (_c *MockOServiceChatHandler_IdleNotificationHandler_Call) Run(run func(ctx context.Context, sess *state.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))
+		run(args[0].(context.Context), args[1].(*state.Session), args[2].(oscar.SNAC_0x01_0x11_OServiceIdleNotification))
 	})
 	return _c
 }
@@ -150,7 +151,7 @@ func (_c *MockOServiceChatHandler_IdleNotificationHandler_Call) Return(_a0 error
 	return _c
 }
 
-func (_c *MockOServiceChatHandler_IdleNotificationHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x01_0x11_OServiceIdleNotification) error) *MockOServiceChatHandler_IdleNotificationHandler_Call {
+func (_c *MockOServiceChatHandler_IdleNotificationHandler_Call) RunAndReturn(run func(context.Context, *state.Session, oscar.SNAC_0x01_0x11_OServiceIdleNotification) error) *MockOServiceChatHandler_IdleNotificationHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }
@@ -232,21 +233,21 @@ func (_c *MockOServiceChatHandler_RateParamsSubAddHandler_Call) RunAndReturn(run
 }
 
 // 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) (oscar.XMessage, error) {
+func (_m *MockOServiceChatHandler) ServiceRequestHandler(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x01_0x04_OServiceServiceRequest) (oscar.XMessage, error) {
 	ret := _m.Called(ctx, sess, snacPayloadIn)
 
 	var r0 oscar.XMessage
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x01_0x04_OServiceServiceRequest) (oscar.XMessage, error)); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *state.Session, oscar.SNAC_0x01_0x04_OServiceServiceRequest) (oscar.XMessage, error)); ok {
 		return rf(ctx, sess, snacPayloadIn)
 	}
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x01_0x04_OServiceServiceRequest) oscar.XMessage); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *state.Session, oscar.SNAC_0x01_0x04_OServiceServiceRequest) oscar.XMessage); ok {
 		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r0 = ret.Get(0).(oscar.XMessage)
 	}
 
-	if rf, ok := ret.Get(1).(func(context.Context, *Session, oscar.SNAC_0x01_0x04_OServiceServiceRequest) error); ok {
+	if rf, ok := ret.Get(1).(func(context.Context, *state.Session, oscar.SNAC_0x01_0x04_OServiceServiceRequest) error); ok {
 		r1 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r1 = ret.Error(1)
@@ -262,15 +263,15 @@ type MockOServiceChatHandler_ServiceRequestHandler_Call struct {
 
 // ServiceRequestHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *state.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 {
+func (_c *MockOServiceChatHandler_ServiceRequestHandler_Call) Run(run func(ctx context.Context, sess *state.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))
+		run(args[0].(context.Context), args[1].(*state.Session), args[2].(oscar.SNAC_0x01_0x04_OServiceServiceRequest))
 	})
 	return _c
 }
@@ -280,27 +281,27 @@ func (_c *MockOServiceChatHandler_ServiceRequestHandler_Call) Return(_a0 oscar.X
 	return _c
 }
 
-func (_c *MockOServiceChatHandler_ServiceRequestHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x01_0x04_OServiceServiceRequest) (oscar.XMessage, error)) *MockOServiceChatHandler_ServiceRequestHandler_Call {
+func (_c *MockOServiceChatHandler_ServiceRequestHandler_Call) RunAndReturn(run func(context.Context, *state.Session, oscar.SNAC_0x01_0x04_OServiceServiceRequest) (oscar.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) (oscar.XMessage, error) {
+func (_m *MockOServiceChatHandler) SetUserInfoFieldsHandler(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (oscar.XMessage, error) {
 	ret := _m.Called(ctx, sess, snacPayloadIn)
 
 	var r0 oscar.XMessage
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (oscar.XMessage, error)); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *state.Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (oscar.XMessage, error)); ok {
 		return rf(ctx, sess, snacPayloadIn)
 	}
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) oscar.XMessage); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *state.Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) oscar.XMessage); ok {
 		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r0 = ret.Get(0).(oscar.XMessage)
 	}
 
-	if rf, ok := ret.Get(1).(func(context.Context, *Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) error); ok {
+	if rf, ok := ret.Get(1).(func(context.Context, *state.Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) error); ok {
 		r1 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r1 = ret.Error(1)
@@ -316,15 +317,15 @@ type MockOServiceChatHandler_SetUserInfoFieldsHandler_Call struct {
 
 // SetUserInfoFieldsHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *state.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 {
+func (_c *MockOServiceChatHandler_SetUserInfoFieldsHandler_Call) Run(run func(ctx context.Context, sess *state.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))
+		run(args[0].(context.Context), args[1].(*state.Session), args[2].(oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields))
 	})
 	return _c
 }
@@ -334,17 +335,17 @@ func (_c *MockOServiceChatHandler_SetUserInfoFieldsHandler_Call) Return(_a0 osca
 	return _c
 }
 
-func (_c *MockOServiceChatHandler_SetUserInfoFieldsHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (oscar.XMessage, error)) *MockOServiceChatHandler_SetUserInfoFieldsHandler_Call {
+func (_c *MockOServiceChatHandler_SetUserInfoFieldsHandler_Call) RunAndReturn(run func(context.Context, *state.Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (oscar.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) oscar.XMessage {
+func (_m *MockOServiceChatHandler) UserInfoQueryHandler(ctx context.Context, sess *state.Session) oscar.XMessage {
 	ret := _m.Called(ctx, sess)
 
 	var r0 oscar.XMessage
-	if rf, ok := ret.Get(0).(func(context.Context, *Session) oscar.XMessage); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *state.Session) oscar.XMessage); ok {
 		r0 = rf(ctx, sess)
 	} else {
 		r0 = ret.Get(0).(oscar.XMessage)
@@ -360,14 +361,14 @@ type MockOServiceChatHandler_UserInfoQueryHandler_Call struct {
 
 // UserInfoQueryHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *state.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 {
+func (_c *MockOServiceChatHandler_UserInfoQueryHandler_Call) Run(run func(ctx context.Context, sess *state.Session)) *MockOServiceChatHandler_UserInfoQueryHandler_Call {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(*Session))
+		run(args[0].(context.Context), args[1].(*state.Session))
 	})
 	return _c
 }
@@ -377,7 +378,7 @@ func (_c *MockOServiceChatHandler_UserInfoQueryHandler_Call) Return(_a0 oscar.XM
 	return _c
 }
 
-func (_c *MockOServiceChatHandler_UserInfoQueryHandler_Call) RunAndReturn(run func(context.Context, *Session) oscar.XMessage) *MockOServiceChatHandler_UserInfoQueryHandler_Call {
+func (_c *MockOServiceChatHandler_UserInfoQueryHandler_Call) RunAndReturn(run func(context.Context, *state.Session) oscar.XMessage) *MockOServiceChatHandler_UserInfoQueryHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }

+ 22 - 20
server/oservice_mock.go

@@ -7,6 +7,8 @@ import (
 
 	oscar "github.com/mkaminski/goaim/oscar"
 	mock "github.com/stretchr/testify/mock"
+
+	state "github.com/mkaminski/goaim/state"
 )
 
 // MockOServiceHandler is an autogenerated mock type for the OServiceHandler type
@@ -66,11 +68,11 @@ func (_c *MockOServiceHandler_ClientVersionsHandler_Call) RunAndReturn(run func(
 }
 
 // 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 {
+func (_m *MockOServiceHandler) IdleNotificationHandler(ctx context.Context, sess *state.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 {
+	if rf, ok := ret.Get(0).(func(context.Context, *state.Session, oscar.SNAC_0x01_0x11_OServiceIdleNotification) error); ok {
 		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r0 = ret.Error(0)
@@ -86,15 +88,15 @@ type MockOServiceHandler_IdleNotificationHandler_Call struct {
 
 // IdleNotificationHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *state.Session
 //   - snacPayloadIn oscar.SNAC_0x01_0x11_OServiceIdleNotification
 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, snacPayloadIn oscar.SNAC_0x01_0x11_OServiceIdleNotification)) *MockOServiceHandler_IdleNotificationHandler_Call {
+func (_c *MockOServiceHandler_IdleNotificationHandler_Call) Run(run func(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x01_0x11_OServiceIdleNotification)) *MockOServiceHandler_IdleNotificationHandler_Call {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(*Session), args[2].(oscar.SNAC_0x01_0x11_OServiceIdleNotification))
+		run(args[0].(context.Context), args[1].(*state.Session), args[2].(oscar.SNAC_0x01_0x11_OServiceIdleNotification))
 	})
 	return _c
 }
@@ -104,7 +106,7 @@ func (_c *MockOServiceHandler_IdleNotificationHandler_Call) Return(_a0 error) *M
 	return _c
 }
 
-func (_c *MockOServiceHandler_IdleNotificationHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x01_0x11_OServiceIdleNotification) error) *MockOServiceHandler_IdleNotificationHandler_Call {
+func (_c *MockOServiceHandler_IdleNotificationHandler_Call) RunAndReturn(run func(context.Context, *state.Session, oscar.SNAC_0x01_0x11_OServiceIdleNotification) error) *MockOServiceHandler_IdleNotificationHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }
@@ -186,21 +188,21 @@ func (_c *MockOServiceHandler_RateParamsSubAddHandler_Call) RunAndReturn(run fun
 }
 
 // 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) (oscar.XMessage, error) {
+func (_m *MockOServiceHandler) SetUserInfoFieldsHandler(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (oscar.XMessage, error) {
 	ret := _m.Called(ctx, sess, snacPayloadIn)
 
 	var r0 oscar.XMessage
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (oscar.XMessage, error)); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *state.Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (oscar.XMessage, error)); ok {
 		return rf(ctx, sess, snacPayloadIn)
 	}
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) oscar.XMessage); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *state.Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) oscar.XMessage); ok {
 		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r0 = ret.Get(0).(oscar.XMessage)
 	}
 
-	if rf, ok := ret.Get(1).(func(context.Context, *Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) error); ok {
+	if rf, ok := ret.Get(1).(func(context.Context, *state.Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) error); ok {
 		r1 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r1 = ret.Error(1)
@@ -216,15 +218,15 @@ type MockOServiceHandler_SetUserInfoFieldsHandler_Call struct {
 
 // SetUserInfoFieldsHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *state.Session
 //   - snacPayloadIn oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields
 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, snacPayloadIn oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields)) *MockOServiceHandler_SetUserInfoFieldsHandler_Call {
+func (_c *MockOServiceHandler_SetUserInfoFieldsHandler_Call) Run(run func(ctx context.Context, sess *state.Session, snacPayloadIn oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields)) *MockOServiceHandler_SetUserInfoFieldsHandler_Call {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(*Session), args[2].(oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields))
+		run(args[0].(context.Context), args[1].(*state.Session), args[2].(oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields))
 	})
 	return _c
 }
@@ -234,17 +236,17 @@ func (_c *MockOServiceHandler_SetUserInfoFieldsHandler_Call) Return(_a0 oscar.XM
 	return _c
 }
 
-func (_c *MockOServiceHandler_SetUserInfoFieldsHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (oscar.XMessage, error)) *MockOServiceHandler_SetUserInfoFieldsHandler_Call {
+func (_c *MockOServiceHandler_SetUserInfoFieldsHandler_Call) RunAndReturn(run func(context.Context, *state.Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (oscar.XMessage, error)) *MockOServiceHandler_SetUserInfoFieldsHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }
 
 // UserInfoQueryHandler provides a mock function with given fields: ctx, sess
-func (_m *MockOServiceHandler) UserInfoQueryHandler(ctx context.Context, sess *Session) oscar.XMessage {
+func (_m *MockOServiceHandler) UserInfoQueryHandler(ctx context.Context, sess *state.Session) oscar.XMessage {
 	ret := _m.Called(ctx, sess)
 
 	var r0 oscar.XMessage
-	if rf, ok := ret.Get(0).(func(context.Context, *Session) oscar.XMessage); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *state.Session) oscar.XMessage); ok {
 		r0 = rf(ctx, sess)
 	} else {
 		r0 = ret.Get(0).(oscar.XMessage)
@@ -260,14 +262,14 @@ type MockOServiceHandler_UserInfoQueryHandler_Call struct {
 
 // UserInfoQueryHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *state.Session
 func (_e *MockOServiceHandler_Expecter) UserInfoQueryHandler(ctx interface{}, sess interface{}) *MockOServiceHandler_UserInfoQueryHandler_Call {
 	return &MockOServiceHandler_UserInfoQueryHandler_Call{Call: _e.mock.On("UserInfoQueryHandler", ctx, sess)}
 }
 
-func (_c *MockOServiceHandler_UserInfoQueryHandler_Call) Run(run func(ctx context.Context, sess *Session)) *MockOServiceHandler_UserInfoQueryHandler_Call {
+func (_c *MockOServiceHandler_UserInfoQueryHandler_Call) Run(run func(ctx context.Context, sess *state.Session)) *MockOServiceHandler_UserInfoQueryHandler_Call {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(*Session))
+		run(args[0].(context.Context), args[1].(*state.Session))
 	})
 	return _c
 }
@@ -277,7 +279,7 @@ func (_c *MockOServiceHandler_UserInfoQueryHandler_Call) Return(_a0 oscar.XMessa
 	return _c
 }
 
-func (_c *MockOServiceHandler_UserInfoQueryHandler_Call) RunAndReturn(run func(context.Context, *Session) oscar.XMessage) *MockOServiceHandler_UserInfoQueryHandler_Call {
+func (_c *MockOServiceHandler_UserInfoQueryHandler_Call) RunAndReturn(run func(context.Context, *state.Session) oscar.XMessage) *MockOServiceHandler_UserInfoQueryHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }

+ 2 - 2
server/oservice_test.go

@@ -541,7 +541,7 @@ func TestOServiceRouter_RouteOService_ForChat(t *testing.T) {
 				Return(tc.output, tc.handlerErr).
 				Maybe()
 			svcBOS.EXPECT().
-				ClientOnlineHandler(mock.Anything, tc.input.SnacOut, mock.Anything, mock.Anything, mock.Anything).
+				ClientOnlineHandler(mock.Anything, tc.input.SnacOut, mock.Anything, mock.Anything).
 				Return(tc.handlerErr).
 				Maybe()
 
@@ -561,7 +561,7 @@ func TestOServiceRouter_RouteOService_ForChat(t *testing.T) {
 			bufOut := &bytes.Buffer{}
 			seq := uint32(1)
 
-			err := router.RouteOService(nil, nil, nil, ChatRoom{}, tc.input.SnacFrame, bufIn, bufOut, &seq)
+			err := router.RouteOService(nil, nil, "", tc.input.SnacFrame, bufIn, bufOut, &seq)
 			assert.ErrorIs(t, err, tc.expectErr)
 			if tc.expectErr != nil {
 				return

+ 9 - 9
server/router.go

@@ -6,6 +6,7 @@ import (
 	"errors"
 	"github.com/google/uuid"
 	"github.com/mkaminski/goaim/oscar"
+	"github.com/mkaminski/goaim/state"
 	"io"
 	"log/slog"
 	"net"
@@ -23,13 +24,13 @@ func NewChatServiceRouter(logger *slog.Logger, cfg Config, oserviceHandler OServ
 }
 
 type AuthHandler interface {
-	Signout(ctx context.Context, sess *Session) error
-	VerifyLogin(conn net.Conn) (*Session, uint32, error)
+	Signout(ctx context.Context, sess *state.Session) error
+	VerifyLogin(conn net.Conn) (*state.Session, uint32, error)
 	VerifyChatLogin(rw io.ReadWriter) (*ChatCookie, uint32, error)
 	SendAndReceiveSignonFrame(rw io.ReadWriter, sequence *uint32) (oscar.FlapSignonFrame, error)
 	ReceiveAndSendBUCPLoginRequest(snacPayloadIn oscar.SNAC_0x17_0x02_BUCPLoginRequest, newUUID func() uuid.UUID) (oscar.XMessage, error)
 	ReceiveAndSendAuthChallenge(snacPayloadIn oscar.SNAC_0x17_0x06_BUCPChallengeRequest, newUUID func() uuid.UUID) (oscar.XMessage, error)
-	SignoutChat(ctx context.Context, cr *ChatRegistry, chatRoom ChatRoom, chatSessManager ChatSessionManager, sess *Session)
+	SignoutChat(ctx context.Context, sess *state.Session, chatID string)
 }
 
 type BOSServiceRouter struct {
@@ -43,10 +44,9 @@ type BOSServiceRouter struct {
 	OServiceBOSRouter
 	Cfg Config
 	RouteLogger
-	NewChatSessMgr func() ChatSessionManager
 }
 
-func (rt *BOSServiceRouter) Route(ctx context.Context, sess *Session, r io.Reader, w io.Writer, sequence *uint32) error {
+func (rt *BOSServiceRouter) Route(ctx context.Context, sess *state.Session, r io.Reader, w io.Writer, sequence *uint32) error {
 	snac := oscar.SnacFrame{}
 	if err := oscar.Unmarshal(&snac, r); err != nil {
 		return err
@@ -63,7 +63,7 @@ func (rt *BOSServiceRouter) Route(ctx context.Context, sess *Session, r io.Reade
 		case oscar.ICBM:
 			return rt.RouteICBM(ctx, sess, snac, r, w, sequence)
 		case oscar.CHAT_NAV:
-			return rt.RouteChatNav(ctx, sess, rt.NewChatSessMgr, snac, r, w, sequence)
+			return rt.RouteChatNav(ctx, sess, snac, r, w, sequence)
 		case oscar.FEEDBAG:
 			return rt.RouteFeedbag(ctx, sess, snac, r, w, sequence)
 		case oscar.BUCP:
@@ -146,7 +146,7 @@ type ChatServiceRouter struct {
 	RouteLogger
 }
 
-func (rt *ChatServiceRouter) Route(ctx context.Context, sess *Session, r io.Reader, w io.Writer, sequence *uint32, chatSessMgr ChatSessionManager, room ChatRoom) error {
+func (rt *ChatServiceRouter) Route(ctx context.Context, sess *state.Session, r io.Reader, w io.Writer, sequence *uint32, chatID string) error {
 	snac := oscar.SnacFrame{}
 	if err := oscar.Unmarshal(&snac, r); err != nil {
 		return err
@@ -155,9 +155,9 @@ func (rt *ChatServiceRouter) Route(ctx context.Context, sess *Session, r io.Read
 	err := func() error {
 		switch snac.FoodGroup {
 		case oscar.OSERVICE:
-			return rt.RouteOService(ctx, sess, chatSessMgr, room, snac, r, w, sequence)
+			return rt.RouteOService(ctx, sess, chatID, snac, r, w, sequence)
 		case oscar.CHAT:
-			return rt.RouteChat(ctx, sess, chatSessMgr, snac, r, w, sequence)
+			return rt.RouteChat(ctx, sess, chatID, snac, r, w, sequence)
 		default:
 			return ErrUnsupportedSubGroup
 		}

+ 70 - 0
state/chat_registry.go

@@ -0,0 +1,70 @@
+package state
+
+import (
+	"errors"
+	"github.com/mkaminski/goaim/oscar"
+	"sync"
+	"time"
+)
+
+type ChatRegistry struct {
+	chatRoomStore map[string]ChatRoom
+	smStore       map[string]any
+	mapMutex      sync.RWMutex
+}
+
+func NewChatRegistry() *ChatRegistry {
+	return &ChatRegistry{
+		chatRoomStore: make(map[string]ChatRoom),
+		smStore:       make(map[string]any),
+	}
+}
+
+func (c *ChatRegistry) Register(room ChatRoom, sm any) {
+	c.mapMutex.Lock()
+	defer c.mapMutex.Unlock()
+	c.chatRoomStore[room.Cookie] = room
+	c.smStore[room.Cookie] = sm
+}
+
+func (c *ChatRegistry) Retrieve(chatID string) (ChatRoom, any, error) {
+	c.mapMutex.RLock()
+	defer c.mapMutex.RUnlock()
+	cr, found := c.chatRoomStore[chatID]
+	if !found {
+		return ChatRoom{}, nil, errors.New("unable to find chat room")
+	}
+	sm, found := c.smStore[chatID]
+	if !found {
+		panic("unable to find session manager for chat")
+	}
+	return cr, sm, nil
+}
+
+func (c *ChatRegistry) RemoveRoom(chatID string) {
+	c.mapMutex.Lock()
+	defer c.mapMutex.Unlock()
+	delete(c.chatRoomStore, chatID)
+	delete(c.smStore, chatID)
+}
+
+type ChatRoom struct {
+	CreateTime     time.Time
+	DetailLevel    uint8
+	Exchange       uint16
+	Cookie         string
+	InstanceNumber uint16
+	Name           string
+}
+
+func (c ChatRoom) TLVList() []oscar.TLV {
+	return []oscar.TLV{
+		oscar.NewTLV(0x00c9, uint16(15)),
+		oscar.NewTLV(0x00ca, uint32(c.CreateTime.Unix())),
+		oscar.NewTLV(0x00d1, uint16(1024)),
+		oscar.NewTLV(0x00d2, uint16(100)),
+		oscar.NewTLV(0x00d5, uint8(2)),
+		oscar.NewTLV(0x006a, c.Name),
+		oscar.NewTLV(0x00d3, c.Name),
+	}
+}

+ 1 - 1
server/feedbag_store.go → state/feedbag_store.go

@@ -1,4 +1,4 @@
-package server
+package state
 
 import (
 	"bytes"

+ 1 - 1
server/feedbag_store_test.go → state/feedbag_store_test.go

@@ -1,4 +1,4 @@
-package server
+package state
 
 import (
 	"os"

+ 1 - 1
server/session.go → state/session.go

@@ -1,4 +1,4 @@
-package server
+package state
 
 import (
 	"github.com/google/uuid"

+ 1 - 1
server/session_manager.go → state/session_manager.go

@@ -1,4 +1,4 @@
-package server
+package state
 
 import (
 	"context"

+ 1 - 1
server/session_test.go → state/session_test.go

@@ -1,4 +1,4 @@
-package server
+package state
 
 import (
 	"github.com/mkaminski/goaim/oscar"