Ver código fonte

move Session to user package

Mike 2 anos atrás
pai
commit
14bc609803

+ 5 - 0
oscar/snacs.go

@@ -796,3 +796,8 @@ type FeedbagItem struct {
 	ClassID uint16
 	TLVLBlock
 }
+
+type XMessage struct {
+	SnacFrame SnacFrame
+	SnacOut   any
+}

+ 19 - 19
server/alert_test.go

@@ -13,9 +13,9 @@ func TestAlertRouter_RouteAlert(t *testing.T) {
 		// name is the unit test name
 		name string
 		// input is the request payload
-		input XMessage
+		input oscar.XMessage
 		// output is the response payload
-		output XMessage
+		output oscar.XMessage
 		// handlerErr is the mocked handler error response
 		handlerErr error
 		// expectErr is the expected error returned by the router
@@ -23,36 +23,36 @@ func TestAlertRouter_RouteAlert(t *testing.T) {
 	}{
 		{
 			name: "receive AlertNotifyCapabilities, return no response",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.ALERT,
 					SubGroup:  oscar.AlertNotifyCapabilities,
 				},
-				snacOut: oscar.SnacFrame{},
+				SnacOut: oscar.SnacFrame{},
 			},
-			output: XMessage{},
+			output: oscar.XMessage{},
 		},
 		{
 			name: "receive AlertNotifyDisplayCapabilities, return no response",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.ALERT,
 					SubGroup:  oscar.AlertNotifyDisplayCapabilities,
 				},
-				snacOut: oscar.SnacFrame{},
+				SnacOut: oscar.SnacFrame{},
 			},
-			output: XMessage{},
+			output: oscar.XMessage{},
 		},
 		{
 			name: "receive AlertGetSubsRequest, expect ErrUnsupportedSubGroup",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.ALERT,
 					SubGroup:  oscar.AlertGetSubsRequest,
 				},
-				snacOut: struct{}{}, // empty SNAC
+				SnacOut: struct{}{}, // empty SNAC
 			},
-			output:    XMessage{}, // empty SNAC
+			output:    oscar.XMessage{}, // empty SNAC
 			expectErr: ErrUnsupportedSubGroup,
 		},
 	}
@@ -66,18 +66,18 @@ func TestAlertRouter_RouteAlert(t *testing.T) {
 			}
 
 			bufIn := &bytes.Buffer{}
-			assert.NoError(t, oscar.Marshal(tc.input.snacOut, bufIn))
+			assert.NoError(t, oscar.Marshal(tc.input.SnacOut, bufIn))
 
 			bufOut := &bytes.Buffer{}
 			seq := uint32(1)
 
-			err := router.RouteAlert(context.Background(), tc.input.snacFrame)
+			err := router.RouteAlert(context.Background(), tc.input.SnacFrame)
 			assert.ErrorIs(t, err, tc.expectErr)
 			if tc.expectErr != nil {
 				return
 			}
 
-			if tc.output == (XMessage{}) {
+			if tc.output == (oscar.XMessage{}) {
 				// make sure no response was sent
 				assert.Empty(t, bufOut.Bytes())
 				return
@@ -96,11 +96,11 @@ func TestAlertRouter_RouteAlert(t *testing.T) {
 			// verify the SNAC frame
 			snacFrame := oscar.SnacFrame{}
 			assert.NoError(t, oscar.Unmarshal(&snacFrame, flapBuf))
-			assert.Equal(t, tc.output.snacFrame, snacFrame)
+			assert.Equal(t, tc.output.SnacFrame, snacFrame)
 
 			// verify the SNAC message
 			snacBuf := &bytes.Buffer{}
-			assert.NoError(t, oscar.Marshal(tc.output.snacOut, snacBuf))
+			assert.NoError(t, oscar.Marshal(tc.output.SnacOut, snacBuf))
 			assert.Equal(t, snacBuf.Bytes(), flapBuf.Bytes())
 		})
 	}

+ 6 - 5
server/bucp.go

@@ -5,6 +5,7 @@ import (
 	"context"
 	"errors"
 	"fmt"
+	"github.com/mkaminski/goaim/user"
 	"io"
 
 	"github.com/google/uuid"
@@ -140,7 +141,7 @@ func ReceiveAndSendBUCPLoginRequest(cfg Config, sm SessionManager, fm *FeedbagSt
 		sess := sm.NewSessionWithSN(newUUID().String(), screenName)
 		snacPayloadOut.AddTLVList([]oscar.TLV{
 			oscar.NewTLV(oscar.TLVReconnectHere, Address(cfg.OSCARHost, cfg.BOSPort)),
-			oscar.NewTLV(oscar.TLVAuthorizationCookie, sess.ID),
+			oscar.NewTLV(oscar.TLVAuthorizationCookie, sess.ID()),
 		})
 	} else {
 		snacPayloadOut.AddTLVList([]oscar.TLV{
@@ -191,7 +192,7 @@ func SendAndReceiveSignonFrame(rw io.ReadWriter, sequence *uint32) (oscar.FlapSi
 	return flapSignonFrameIn, nil
 }
 
-func VerifyLogin(sm SessionManager, rw io.ReadWriter) (*Session, uint32, error) {
+func VerifyLogin(sm SessionManager, rw io.ReadWriter) (*user.Session, uint32, error) {
 	seq := uint32(100)
 
 	flap, err := SendAndReceiveSignonFrame(rw, &seq)
@@ -202,12 +203,12 @@ func VerifyLogin(sm SessionManager, rw io.ReadWriter) (*Session, uint32, error)
 	var ok bool
 	ID, ok := flap.GetSlice(oscar.OServiceTLVTagsLoginCookie)
 	if !ok {
-		return nil, 0, errors.New("unable to get session ID from payload")
+		return nil, 0, errors.New("unable to get session id from payload")
 	}
 
 	sess, ok := sm.Retrieve(string(ID))
 	if !ok {
-		return nil, 0, fmt.Errorf("unable to find session by ID %s", ID)
+		return nil, 0, fmt.Errorf("unable to find session by id %s", ID)
 	}
 
 	return sess, seq, nil
@@ -224,7 +225,7 @@ func VerifyChatLogin(rw io.ReadWriter) (*ChatCookie, uint32, error) {
 	var ok bool
 	buf, ok := flap.GetSlice(oscar.OServiceTLVTagsLoginCookie)
 	if !ok {
-		return nil, 0, errors.New("unable to get session ID from payload")
+		return nil, 0, errors.New("unable to get session id from payload")
 	}
 
 	cookie := ChatCookie{}

+ 32 - 31
server/buddy.go

@@ -2,6 +2,7 @@ package server
 
 import (
 	"context"
+	"github.com/mkaminski/goaim/user"
 	"io"
 	"log/slog"
 
@@ -9,7 +10,7 @@ import (
 )
 
 type BuddyHandler interface {
-	RightsQueryHandler(ctx context.Context) XMessage
+	RightsQueryHandler(ctx context.Context) oscar.XMessage
 }
 
 func NewBuddyRouter(logger *slog.Logger) BuddyRouter {
@@ -34,8 +35,8 @@ func (rt *BuddyRouter) RouteBuddy(ctx context.Context, SNACFrame oscar.SnacFrame
 			return err
 		}
 		outSNAC := rt.RightsQueryHandler(ctx)
-		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.snacFrame, outSNAC.snacOut)
-		return writeOutSNAC(SNACFrame, outSNAC.snacFrame, outSNAC.snacOut, sequence, w)
+		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.SnacFrame, outSNAC.SnacOut)
+		return writeOutSNAC(SNACFrame, outSNAC.SnacFrame, outSNAC.SnacOut, sequence, w)
 	default:
 		return ErrUnsupportedSubGroup
 	}
@@ -44,13 +45,13 @@ func (rt *BuddyRouter) RouteBuddy(ctx context.Context, SNACFrame oscar.SnacFrame
 type BuddyService struct {
 }
 
-func (s BuddyService) RightsQueryHandler(context.Context) XMessage {
-	return XMessage{
-		snacFrame: oscar.SnacFrame{
+func (s BuddyService) RightsQueryHandler(context.Context) oscar.XMessage {
+	return oscar.XMessage{
+		SnacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.BUDDY,
 			SubGroup:  oscar.BuddyRightsReply,
 		},
-		snacOut: oscar.SNAC_0x03_0x03_BuddyRightsReply{
+		SnacOut: oscar.SNAC_0x03_0x03_BuddyRightsReply{
 			TLVRestBlock: oscar.TLVRestBlock{
 				TLVList: oscar.TLVList{
 					oscar.NewTLV(0x01, uint16(100)),
@@ -63,23 +64,23 @@ func (s BuddyService) RightsQueryHandler(context.Context) XMessage {
 	}
 }
 
-func BroadcastArrival(ctx context.Context, sess *Session, sm SessionManager, fm FeedbagManager) error {
-	screenNames, err := fm.InterestedUsers(sess.ScreenName)
+func BroadcastArrival(ctx context.Context, sess *user.Session, sm SessionManager, fm FeedbagManager) error {
+	screenNames, err := fm.InterestedUsers(sess.ScreenName())
 	if err != nil {
 		return err
 	}
 
-	sm.BroadcastToScreenNames(ctx, screenNames, XMessage{
-		snacFrame: oscar.SnacFrame{
+	sm.BroadcastToScreenNames(ctx, screenNames, oscar.XMessage{
+		SnacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.BUDDY,
 			SubGroup:  oscar.BuddyArrived,
 		},
-		snacOut: oscar.SNAC_0x03_0x0A_BuddyArrived{
+		SnacOut: oscar.SNAC_0x03_0x0A_BuddyArrived{
 			TLVUserInfo: oscar.TLVUserInfo{
-				ScreenName:   sess.ScreenName,
-				WarningLevel: sess.GetWarning(),
+				ScreenName:   sess.ScreenName(),
+				WarningLevel: sess.Warning(),
 				TLVBlock: oscar.TLVBlock{
-					TLVList: sess.GetUserInfo(),
+					TLVList: sess.UserInfo(),
 				},
 			},
 		},
@@ -88,21 +89,21 @@ func BroadcastArrival(ctx context.Context, sess *Session, sm SessionManager, fm
 	return nil
 }
 
-func BroadcastDeparture(ctx context.Context, sess *Session, sm SessionManager, fm FeedbagManager) error {
-	screenNames, err := fm.InterestedUsers(sess.ScreenName)
+func BroadcastDeparture(ctx context.Context, sess *user.Session, sm SessionManager, fm FeedbagManager) error {
+	screenNames, err := fm.InterestedUsers(sess.ScreenName())
 	if err != nil {
 		return err
 	}
 
-	sm.BroadcastToScreenNames(ctx, screenNames, XMessage{
-		snacFrame: oscar.SnacFrame{
+	sm.BroadcastToScreenNames(ctx, screenNames, oscar.XMessage{
+		SnacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.BUDDY,
 			SubGroup:  oscar.BuddyDeparted,
 		},
-		snacOut: oscar.SNAC_0x03_0x0B_BuddyDeparted{
+		SnacOut: oscar.SNAC_0x03_0x0B_BuddyDeparted{
 			TLVUserInfo: oscar.TLVUserInfo{
-				ScreenName:   sess.ScreenName,
-				WarningLevel: sess.GetWarning(),
+				ScreenName:   sess.ScreenName(),
+				WarningLevel: sess.Warning(),
 			},
 		},
 	})
@@ -118,13 +119,13 @@ func UnicastArrival(ctx context.Context, srcScreenName, destScreenName string, s
 	case sess.Invisible(): // don't tell user this buddy is online
 		return nil
 	}
-	sm.SendToScreenName(ctx, destScreenName, XMessage{
-		snacFrame: oscar.SnacFrame{
+	sm.SendToScreenName(ctx, destScreenName, oscar.XMessage{
+		SnacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.BUDDY,
 			SubGroup:  oscar.BuddyArrived,
 		},
-		snacOut: oscar.SNAC_0x03_0x0A_BuddyArrived{
-			TLVUserInfo: sess.GetTLVUserInfo(),
+		SnacOut: oscar.SNAC_0x03_0x0A_BuddyArrived{
+			TLVUserInfo: sess.TLVUserInfo(),
 		},
 	})
 
@@ -140,17 +141,17 @@ func UnicastDeparture(ctx context.Context, srcScreenName, destScreenName string,
 		return nil
 	}
 
-	sm.SendToScreenName(ctx, destScreenName, XMessage{
-		snacFrame: oscar.SnacFrame{
+	sm.SendToScreenName(ctx, destScreenName, oscar.XMessage{
+		SnacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.BUDDY,
 			SubGroup:  oscar.BuddyDeparted,
 		},
-		snacOut: oscar.SNAC_0x03_0x0B_BuddyDeparted{
+		SnacOut: oscar.SNAC_0x03_0x0B_BuddyDeparted{
 			TLVUserInfo: oscar.TLVUserInfo{
 				// don't include the TLV block, otherwise the AIM client fails
 				// to process the block event
-				ScreenName:   sess.ScreenName,
-				WarningLevel: sess.GetWarning(),
+				ScreenName:   sess.ScreenName(),
+				WarningLevel: sess.Warning(),
 			},
 		},
 	})

+ 7 - 6
server/buddy_mock.go

@@ -5,6 +5,7 @@ package server
 import (
 	context "context"
 
+	oscar "github.com/mkaminski/goaim/oscar"
 	mock "github.com/stretchr/testify/mock"
 )
 
@@ -22,14 +23,14 @@ func (_m *MockBuddyHandler) EXPECT() *MockBuddyHandler_Expecter {
 }
 
 // RightsQueryHandler provides a mock function with given fields: ctx
-func (_m *MockBuddyHandler) RightsQueryHandler(ctx context.Context) XMessage {
+func (_m *MockBuddyHandler) RightsQueryHandler(ctx context.Context) oscar.XMessage {
 	ret := _m.Called(ctx)
 
-	var r0 XMessage
-	if rf, ok := ret.Get(0).(func(context.Context) XMessage); ok {
+	var r0 oscar.XMessage
+	if rf, ok := ret.Get(0).(func(context.Context) oscar.XMessage); ok {
 		r0 = rf(ctx)
 	} else {
-		r0 = ret.Get(0).(XMessage)
+		r0 = ret.Get(0).(oscar.XMessage)
 	}
 
 	return r0
@@ -53,12 +54,12 @@ func (_c *MockBuddyHandler_RightsQueryHandler_Call) Run(run func(ctx context.Con
 	return _c
 }
 
-func (_c *MockBuddyHandler_RightsQueryHandler_Call) Return(_a0 XMessage) *MockBuddyHandler_RightsQueryHandler_Call {
+func (_c *MockBuddyHandler_RightsQueryHandler_Call) Return(_a0 oscar.XMessage) *MockBuddyHandler_RightsQueryHandler_Call {
 	_c.Call.Return(_a0)
 	return _c
 }
 
-func (_c *MockBuddyHandler_RightsQueryHandler_Call) RunAndReturn(run func(context.Context) XMessage) *MockBuddyHandler_RightsQueryHandler_Call {
+func (_c *MockBuddyHandler_RightsQueryHandler_Call) RunAndReturn(run func(context.Context) oscar.XMessage) *MockBuddyHandler_RightsQueryHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }

+ 17 - 17
server/buddy_test.go

@@ -13,9 +13,9 @@ func TestBuddyRouter_RouteBuddy(t *testing.T) {
 		// name is the unit test name
 		name string
 		// input is the request payload
-		input XMessage
+		input oscar.XMessage
 		// output is the response payload
-		output XMessage
+		output oscar.XMessage
 		// handlerErr is the mocked handler error response
 		handlerErr error
 		// expectErr is the expected error returned by the router
@@ -23,12 +23,12 @@ func TestBuddyRouter_RouteBuddy(t *testing.T) {
 	}{
 		{
 			name: "receive BuddyRightsQuery, return BuddyRightsReply",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.BUDDY,
 					SubGroup:  oscar.BuddyRightsQuery,
 				},
-				snacOut: oscar.SNAC_0x03_0x02_BuddyRightsQuery{
+				SnacOut: oscar.SNAC_0x03_0x02_BuddyRightsQuery{
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							{
@@ -39,12 +39,12 @@ func TestBuddyRouter_RouteBuddy(t *testing.T) {
 					},
 				},
 			},
-			output: XMessage{
-				snacFrame: oscar.SnacFrame{
+			output: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.BUDDY,
 					SubGroup:  oscar.BuddyRightsReply,
 				},
-				snacOut: oscar.SNAC_0x03_0x03_BuddyRightsReply{
+				SnacOut: oscar.SNAC_0x03_0x03_BuddyRightsReply{
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							{
@@ -58,14 +58,14 @@ func TestBuddyRouter_RouteBuddy(t *testing.T) {
 		},
 		{
 			name: "receive ErrorCodeReplyTooBig, expect ErrUnsupportedSubGroup",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.BUDDY,
 					SubGroup:  oscar.ErrorCodeReplyTooBig,
 				},
-				snacOut: struct{}{}, // empty SNAC
+				SnacOut: struct{}{}, // empty SNAC
 			},
-			output:    XMessage{},
+			output:    oscar.XMessage{},
 			expectErr: ErrUnsupportedSubGroup,
 		},
 	}
@@ -86,18 +86,18 @@ func TestBuddyRouter_RouteBuddy(t *testing.T) {
 			}
 
 			bufIn := &bytes.Buffer{}
-			assert.NoError(t, oscar.Marshal(tc.input.snacOut, bufIn))
+			assert.NoError(t, oscar.Marshal(tc.input.SnacOut, bufIn))
 
 			bufOut := &bytes.Buffer{}
 			seq := uint32(1)
 
-			err := router.RouteBuddy(nil, tc.input.snacFrame, bufIn, bufOut, &seq)
+			err := router.RouteBuddy(nil, tc.input.SnacFrame, bufIn, bufOut, &seq)
 			assert.ErrorIs(t, err, tc.expectErr)
 			if tc.expectErr != nil {
 				return
 			}
 
-			if tc.output == (XMessage{}) {
+			if tc.output == (oscar.XMessage{}) {
 				// make sure no response was sent
 				assert.Empty(t, bufOut.Bytes())
 				return
@@ -116,11 +116,11 @@ func TestBuddyRouter_RouteBuddy(t *testing.T) {
 			// verify the SNAC frame
 			snacFrame := oscar.SnacFrame{}
 			assert.NoError(t, oscar.Unmarshal(&snacFrame, flapBuf))
-			assert.Equal(t, tc.output.snacFrame, snacFrame)
+			assert.Equal(t, tc.output.SnacFrame, snacFrame)
 
 			// verify the SNAC message
 			snacBuf := &bytes.Buffer{}
-			assert.NoError(t, oscar.Marshal(tc.output.snacOut, snacBuf))
+			assert.NoError(t, oscar.Marshal(tc.output.SnacOut, snacBuf))
 			assert.Equal(t, snacBuf.Bytes(), flapBuf.Bytes())
 		})
 	}

+ 41 - 40
server/chat.go

@@ -2,6 +2,7 @@ package server
 
 import (
 	"context"
+	"github.com/mkaminski/goaim/user"
 	"io"
 	"log/slog"
 
@@ -9,7 +10,7 @@ import (
 )
 
 type ChatHandler interface {
-	ChannelMsgToHostHandler(ctx context.Context, sess *Session, room ChatRoom, snacPayloadIn oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost) (*XMessage, error)
+	ChannelMsgToHostHandler(ctx context.Context, sess *user.Session, room ChatRoom, snacPayloadIn oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost) (*oscar.XMessage, error)
 }
 
 func NewChatRouter(logger *slog.Logger) ChatRouter {
@@ -26,7 +27,7 @@ type ChatRouter struct {
 	RouteLogger
 }
 
-func (rt *ChatRouter) RouteChat(ctx context.Context, sess *Session, room ChatRoom, SNACFrame oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
+func (rt *ChatRouter) RouteChat(ctx context.Context, sess *user.Session, room ChatRoom, SNACFrame oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
 	switch SNACFrame.SubGroup {
 	case oscar.ChatChannelMsgToHost:
 		inSNAC := oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost{}
@@ -41,8 +42,8 @@ func (rt *ChatRouter) RouteChat(ctx context.Context, sess *Session, room ChatRoo
 			return nil
 		}
 		rt.Logger.InfoContext(ctx, "user sent a chat message")
-		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.snacFrame, outSNAC.snacOut)
-		return writeOutSNAC(SNACFrame, outSNAC.snacFrame, outSNAC.snacOut, sequence, w)
+		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.SnacFrame, outSNAC.SnacOut)
+		return writeOutSNAC(SNACFrame, outSNAC.SnacFrame, outSNAC.SnacOut, sequence, w)
 	default:
 		return ErrUnsupportedSubGroup
 	}
@@ -51,7 +52,7 @@ func (rt *ChatRouter) RouteChat(ctx context.Context, sess *Session, room ChatRoo
 type ChatService struct {
 }
 
-func (s ChatService) ChannelMsgToHostHandler(ctx context.Context, sess *Session, room ChatRoom, snacPayloadIn oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost) (*XMessage, error) {
+func (s ChatService) ChannelMsgToHostHandler(ctx context.Context, sess *user.Session, room ChatRoom, snacPayloadIn oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost) (*oscar.XMessage, error) {
 	snacFrameOut := oscar.SnacFrame{
 		FoodGroup: oscar.CHAT,
 		SubGroup:  oscar.ChatChannelMsgToClient,
@@ -65,68 +66,68 @@ func (s ChatService) ChannelMsgToHostHandler(ctx context.Context, sess *Session,
 	}
 	snacPayloadOut.AddTLV(
 		oscar.NewTLV(oscar.ChatTLVSenderInformation, oscar.TLVUserInfo{
-			ScreenName:   sess.ScreenName,
-			WarningLevel: sess.GetWarning(),
+			ScreenName:   sess.ScreenName(),
+			WarningLevel: sess.Warning(),
 			TLVBlock: oscar.TLVBlock{
-				TLVList: sess.GetUserInfo(),
+				TLVList: sess.UserInfo(),
 			},
 		}),
 	)
 
 	// send message to all the participants except sender
-	room.BroadcastExcept(ctx, sess, XMessage{
-		snacFrame: snacFrameOut,
-		snacOut:   snacPayloadOut,
+	room.BroadcastExcept(ctx, sess, oscar.XMessage{
+		SnacFrame: snacFrameOut,
+		SnacOut:   snacPayloadOut,
 	})
 
-	var ret *XMessage
+	var ret *oscar.XMessage
 	if _, ackMsg := snacPayloadIn.GetTLV(oscar.ChatTLVEnableReflectionFlag); ackMsg {
 		// reflect the message back to the sender
-		ret = &XMessage{
-			snacFrame: snacFrameOut,
-			snacOut:   snacPayloadOut,
+		ret = &oscar.XMessage{
+			SnacFrame: snacFrameOut,
+			SnacOut:   snacPayloadOut,
 		}
 	}
 
 	return ret, nil
 }
 
-func SetOnlineChatUsers(ctx context.Context, sess *Session, sm ChatRoom) {
+func SetOnlineChatUsers(ctx context.Context, sess *user.Session, sm ChatRoom) {
 	snacPayloadOut := oscar.SNAC_0x0E_0x03_ChatUsersJoined{}
 	sessions := sm.Participants()
 
 	for _, uSess := range sessions {
 		snacPayloadOut.Users = append(snacPayloadOut.Users, oscar.TLVUserInfo{
-			ScreenName:   uSess.ScreenName,
-			WarningLevel: uSess.GetWarning(),
+			ScreenName:   uSess.ScreenName(),
+			WarningLevel: uSess.Warning(),
 			TLVBlock: oscar.TLVBlock{
-				TLVList: uSess.GetUserInfo(),
+				TLVList: uSess.UserInfo(),
 			},
 		})
 	}
 
-	sm.SendToScreenName(ctx, sess.ScreenName, XMessage{
-		snacFrame: oscar.SnacFrame{
+	sm.SendToScreenName(ctx, sess.ScreenName(), oscar.XMessage{
+		SnacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.CHAT,
 			SubGroup:  oscar.ChatUsersJoined,
 		},
-		snacOut: snacPayloadOut,
+		SnacOut: snacPayloadOut,
 	})
 }
 
-func AlertUserJoined(ctx context.Context, sess *Session, sm SessionManager) {
-	sm.BroadcastExcept(ctx, sess, XMessage{
-		snacFrame: oscar.SnacFrame{
+func AlertUserJoined(ctx context.Context, sess *user.Session, sm SessionManager) {
+	sm.BroadcastExcept(ctx, sess, oscar.XMessage{
+		SnacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.CHAT,
 			SubGroup:  oscar.ChatUsersJoined,
 		},
-		snacOut: oscar.SNAC_0x0E_0x03_ChatUsersJoined{
+		SnacOut: oscar.SNAC_0x0E_0x03_ChatUsersJoined{
 			Users: []oscar.TLVUserInfo{
 				{
-					ScreenName:   sess.ScreenName,
-					WarningLevel: sess.GetWarning(),
+					ScreenName:   sess.ScreenName(),
+					WarningLevel: sess.Warning(),
 					TLVBlock: oscar.TLVBlock{
-						TLVList: sess.GetUserInfo(),
+						TLVList: sess.UserInfo(),
 					},
 				},
 			},
@@ -134,19 +135,19 @@ func AlertUserJoined(ctx context.Context, sess *Session, sm SessionManager) {
 	})
 }
 
-func AlertUserLeft(ctx context.Context, sess *Session, sm SessionManager) {
-	sm.BroadcastExcept(ctx, sess, XMessage{
-		snacFrame: oscar.SnacFrame{
+func AlertUserLeft(ctx context.Context, sess *user.Session, sm SessionManager) {
+	sm.BroadcastExcept(ctx, sess, oscar.XMessage{
+		SnacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.CHAT,
 			SubGroup:  oscar.ChatUsersLeft,
 		},
-		snacOut: oscar.SNAC_0x0E_0x04_ChatUsersLeft{
+		SnacOut: oscar.SNAC_0x0E_0x04_ChatUsersLeft{
 			Users: []oscar.TLVUserInfo{
 				{
-					ScreenName:   sess.ScreenName,
-					WarningLevel: sess.GetWarning(),
+					ScreenName:   sess.ScreenName(),
+					WarningLevel: sess.Warning(),
 					TLVBlock: oscar.TLVBlock{
-						TLVList: sess.GetUserInfo(),
+						TLVList: sess.UserInfo(),
 					},
 				},
 			},
@@ -154,13 +155,13 @@ func AlertUserLeft(ctx context.Context, sess *Session, sm SessionManager) {
 	})
 }
 
-func SendChatRoomInfoUpdate(ctx context.Context, sess *Session, room ChatRoom) {
-	room.SendToScreenName(ctx, sess.ScreenName, XMessage{
-		snacFrame: oscar.SnacFrame{
+func SendChatRoomInfoUpdate(ctx context.Context, sess *user.Session, room ChatRoom) {
+	room.SendToScreenName(ctx, sess.ScreenName(), oscar.XMessage{
+		SnacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.CHAT,
 			SubGroup:  oscar.ChatRoomInfoUpdate,
 		},
-		snacOut: oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate{
+		SnacOut: oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate{
 			Exchange:       4,
 			Cookie:         room.Cookie,
 			InstanceNumber: 100,

+ 13 - 11
server/chat_mock.go

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

+ 26 - 25
server/chat_nav.go

@@ -5,15 +5,16 @@ import (
 	"errors"
 	"github.com/google/uuid"
 	"github.com/mkaminski/goaim/oscar"
+	"github.com/mkaminski/goaim/user"
 	"io"
 	"log/slog"
 	"time"
 )
 
 type ChatNavHandler interface {
-	CreateRoomHandler(ctx context.Context, sess *Session, newChatRoom ChatRoomFactory, snacPayloadIn oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) (XMessage, error)
-	RequestChatRightsHandler(ctx context.Context) XMessage
-	RequestRoomInfoHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x0D_0x04_ChatNavRequestRoomInfo) (XMessage, error)
+	CreateRoomHandler(ctx context.Context, sess *user.Session, newChatRoom ChatRoomFactory, 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)
 }
 
 func NewChatNavRouter(logger *slog.Logger, cr *ChatRegistry) ChatNavRouter {
@@ -33,12 +34,12 @@ type ChatNavRouter struct {
 	RouteLogger
 }
 
-func (rt *ChatNavRouter) RouteChatNav(ctx context.Context, sess *Session, SNACFrame oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
+func (rt *ChatNavRouter) RouteChatNav(ctx context.Context, sess *user.Session, SNACFrame oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
 	switch SNACFrame.SubGroup {
 	case oscar.ChatNavRequestChatRights:
 		outSNAC := rt.RequestChatRightsHandler(ctx)
-		rt.logRequestAndResponse(ctx, SNACFrame, nil, outSNAC.snacFrame, outSNAC.snacOut)
-		return writeOutSNAC(SNACFrame, outSNAC.snacFrame, outSNAC.snacOut, sequence, w)
+		rt.logRequestAndResponse(ctx, SNACFrame, nil, outSNAC.SnacFrame, outSNAC.SnacOut)
+		return writeOutSNAC(SNACFrame, outSNAC.SnacFrame, outSNAC.SnacOut, sequence, w)
 	case oscar.ChatNavRequestRoomInfo:
 		inSNAC := oscar.SNAC_0x0D_0x04_ChatNavRequestRoomInfo{}
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
@@ -48,8 +49,8 @@ func (rt *ChatNavRouter) RouteChatNav(ctx context.Context, sess *Session, SNACFr
 		if err != nil {
 			return err
 		}
-		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.snacFrame, outSNAC.snacOut)
-		return writeOutSNAC(SNACFrame, outSNAC.snacFrame, outSNAC.snacOut, sequence, w)
+		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.SnacFrame, outSNAC.SnacOut)
+		return writeOutSNAC(SNACFrame, outSNAC.SnacFrame, outSNAC.SnacOut, sequence, w)
 	case oscar.ChatNavCreateRoom:
 		inSNAC := oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate{}
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
@@ -61,8 +62,8 @@ func (rt *ChatNavRouter) RouteChatNav(ctx context.Context, sess *Session, SNACFr
 		}
 		roomName, _ := inSNAC.GetString(oscar.ChatTLVRoomName)
 		rt.Logger.InfoContext(ctx, "user started a chat room", slog.String("roomName", roomName))
-		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.snacFrame, outSNAC.snacOut)
-		return writeOutSNAC(SNACFrame, outSNAC.snacFrame, outSNAC.snacOut, sequence, w)
+		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.SnacFrame, outSNAC.SnacOut)
+		return writeOutSNAC(SNACFrame, outSNAC.SnacFrame, outSNAC.SnacOut, sequence, w)
 	default:
 		return ErrUnsupportedSubGroup
 	}
@@ -78,13 +79,13 @@ type ChatCookie struct {
 	SessID string `len_prefix:"uint16"`
 }
 
-func (s ChatNavService) RequestChatRightsHandler(context.Context) XMessage {
-	return XMessage{
-		snacFrame: oscar.SnacFrame{
+func (s ChatNavService) RequestChatRightsHandler(context.Context) oscar.XMessage {
+	return oscar.XMessage{
+		SnacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.CHAT_NAV,
 			SubGroup:  oscar.ChatNavNavInfo,
 		},
-		snacOut: oscar.SNAC_0x0D_0x09_ChatNavNavInfo{
+		SnacOut: oscar.SNAC_0x0D_0x09_ChatNavNavInfo{
 			TLVRestBlock: oscar.TLVRestBlock{
 				TLVList: oscar.TLVList{
 					oscar.NewTLV(0x02, uint8(10)),
@@ -119,10 +120,10 @@ func NewChatRoom(logger *slog.Logger) ChatRoom {
 
 type ChatRoomFactory func(logger *slog.Logger) ChatRoom
 
-func (s ChatNavService) CreateRoomHandler(_ context.Context, sess *Session, newChatRoom ChatRoomFactory, snacPayloadIn oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) (XMessage, error) {
+func (s ChatNavService) CreateRoomHandler(_ context.Context, sess *user.Session, newChatRoom ChatRoomFactory, snacPayloadIn oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) (oscar.XMessage, error) {
 	name, hasName := snacPayloadIn.GetString(oscar.ChatTLVRoomName)
 	if !hasName {
-		return XMessage{}, errors.New("unable to find chat name")
+		return oscar.XMessage{}, errors.New("unable to find chat name")
 	}
 
 	room := newChatRoom(s.Logger)
@@ -133,14 +134,14 @@ func (s ChatNavService) CreateRoomHandler(_ context.Context, sess *Session, newC
 	s.cr.Register(room)
 
 	// add user to chat room
-	room.NewSessionWithSN(sess.ID, sess.ScreenName)
+	room.NewSessionWithSN(sess.ID(), sess.ScreenName())
 
-	return XMessage{
-		snacFrame: oscar.SnacFrame{
+	return oscar.XMessage{
+		SnacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.CHAT_NAV,
 			SubGroup:  oscar.ChatNavNavInfo,
 		},
-		snacOut: oscar.SNAC_0x0D_0x09_ChatNavNavInfo{
+		SnacOut: oscar.SNAC_0x0D_0x09_ChatNavNavInfo{
 			TLVRestBlock: oscar.TLVRestBlock{
 				TLVList: oscar.TLVList{
 					oscar.NewTLV(oscar.ChatNavTLVRoomInfo, oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate{
@@ -158,18 +159,18 @@ func (s ChatNavService) CreateRoomHandler(_ context.Context, sess *Session, newC
 	}, nil
 }
 
-func (s ChatNavService) RequestRoomInfoHandler(_ context.Context, snacPayloadIn oscar.SNAC_0x0D_0x04_ChatNavRequestRoomInfo) (XMessage, error) {
+func (s ChatNavService) RequestRoomInfoHandler(_ context.Context, snacPayloadIn oscar.SNAC_0x0D_0x04_ChatNavRequestRoomInfo) (oscar.XMessage, error) {
 	room, err := s.cr.Retrieve(string(snacPayloadIn.Cookie))
 	if err != nil {
-		return XMessage{}, err
+		return oscar.XMessage{}, err
 	}
 
-	return XMessage{
-		snacFrame: oscar.SnacFrame{
+	return oscar.XMessage{
+		SnacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.CHAT_NAV,
 			SubGroup:  oscar.ChatNavNavInfo,
 		},
-		snacOut: oscar.SNAC_0x0D_0x09_ChatNavNavInfo{
+		SnacOut: oscar.SNAC_0x0D_0x09_ChatNavNavInfo{
 			TLVRestBlock: oscar.TLVRestBlock{
 				TLVList: oscar.TLVList{
 					oscar.NewTLV(0x04, oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate{

+ 26 - 24
server/chat_nav_mock.go

@@ -7,6 +7,8 @@ import (
 
 	oscar "github.com/mkaminski/goaim/oscar"
 	mock "github.com/stretchr/testify/mock"
+
+	user "github.com/mkaminski/goaim/user"
 )
 
 // MockChatNavHandler is an autogenerated mock type for the ChatNavHandler type
@@ -23,21 +25,21 @@ func (_m *MockChatNavHandler) EXPECT() *MockChatNavHandler_Expecter {
 }
 
 // CreateRoomHandler provides a mock function with given fields: ctx, sess, newChatRoom, snacPayloadIn
-func (_m *MockChatNavHandler) CreateRoomHandler(ctx context.Context, sess *Session, newChatRoom ChatRoomFactory, snacPayloadIn oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) (XMessage, error) {
+func (_m *MockChatNavHandler) CreateRoomHandler(ctx context.Context, sess *user.Session, newChatRoom ChatRoomFactory, snacPayloadIn oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) (oscar.XMessage, error) {
 	ret := _m.Called(ctx, sess, newChatRoom, snacPayloadIn)
 
-	var r0 XMessage
+	var r0 oscar.XMessage
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, ChatRoomFactory, oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) (XMessage, error)); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *user.Session, ChatRoomFactory, oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) (oscar.XMessage, error)); ok {
 		return rf(ctx, sess, newChatRoom, snacPayloadIn)
 	}
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, ChatRoomFactory, oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) XMessage); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *user.Session, ChatRoomFactory, oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) oscar.XMessage); ok {
 		r0 = rf(ctx, sess, newChatRoom, snacPayloadIn)
 	} else {
-		r0 = ret.Get(0).(XMessage)
+		r0 = ret.Get(0).(oscar.XMessage)
 	}
 
-	if rf, ok := ret.Get(1).(func(context.Context, *Session, ChatRoomFactory, oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) error); ok {
+	if rf, ok := ret.Get(1).(func(context.Context, *user.Session, ChatRoomFactory, oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) error); ok {
 		r1 = rf(ctx, sess, newChatRoom, snacPayloadIn)
 	} else {
 		r1 = ret.Error(1)
@@ -53,39 +55,39 @@ type MockChatNavHandler_CreateRoomHandler_Call struct {
 
 // CreateRoomHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *user.Session
 //   - newChatRoom ChatRoomFactory
 //   - snacPayloadIn oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate
 func (_e *MockChatNavHandler_Expecter) CreateRoomHandler(ctx interface{}, sess interface{}, newChatRoom interface{}, snacPayloadIn interface{}) *MockChatNavHandler_CreateRoomHandler_Call {
 	return &MockChatNavHandler_CreateRoomHandler_Call{Call: _e.mock.On("CreateRoomHandler", ctx, sess, newChatRoom, snacPayloadIn)}
 }
 
-func (_c *MockChatNavHandler_CreateRoomHandler_Call) Run(run func(ctx context.Context, sess *Session, newChatRoom ChatRoomFactory, snacPayloadIn oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate)) *MockChatNavHandler_CreateRoomHandler_Call {
+func (_c *MockChatNavHandler_CreateRoomHandler_Call) Run(run func(ctx context.Context, sess *user.Session, newChatRoom ChatRoomFactory, 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].(ChatRoomFactory), args[3].(oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate))
+		run(args[0].(context.Context), args[1].(*user.Session), args[2].(ChatRoomFactory), args[3].(oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate))
 	})
 	return _c
 }
 
-func (_c *MockChatNavHandler_CreateRoomHandler_Call) Return(_a0 XMessage, _a1 error) *MockChatNavHandler_CreateRoomHandler_Call {
+func (_c *MockChatNavHandler_CreateRoomHandler_Call) Return(_a0 oscar.XMessage, _a1 error) *MockChatNavHandler_CreateRoomHandler_Call {
 	_c.Call.Return(_a0, _a1)
 	return _c
 }
 
-func (_c *MockChatNavHandler_CreateRoomHandler_Call) RunAndReturn(run func(context.Context, *Session, ChatRoomFactory, oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) (XMessage, error)) *MockChatNavHandler_CreateRoomHandler_Call {
+func (_c *MockChatNavHandler_CreateRoomHandler_Call) RunAndReturn(run func(context.Context, *user.Session, ChatRoomFactory, oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate) (oscar.XMessage, error)) *MockChatNavHandler_CreateRoomHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }
 
 // RequestChatRightsHandler provides a mock function with given fields: ctx
-func (_m *MockChatNavHandler) RequestChatRightsHandler(ctx context.Context) XMessage {
+func (_m *MockChatNavHandler) RequestChatRightsHandler(ctx context.Context) oscar.XMessage {
 	ret := _m.Called(ctx)
 
-	var r0 XMessage
-	if rf, ok := ret.Get(0).(func(context.Context) XMessage); ok {
+	var r0 oscar.XMessage
+	if rf, ok := ret.Get(0).(func(context.Context) oscar.XMessage); ok {
 		r0 = rf(ctx)
 	} else {
-		r0 = ret.Get(0).(XMessage)
+		r0 = ret.Get(0).(oscar.XMessage)
 	}
 
 	return r0
@@ -109,29 +111,29 @@ func (_c *MockChatNavHandler_RequestChatRightsHandler_Call) Run(run func(ctx con
 	return _c
 }
 
-func (_c *MockChatNavHandler_RequestChatRightsHandler_Call) Return(_a0 XMessage) *MockChatNavHandler_RequestChatRightsHandler_Call {
+func (_c *MockChatNavHandler_RequestChatRightsHandler_Call) Return(_a0 oscar.XMessage) *MockChatNavHandler_RequestChatRightsHandler_Call {
 	_c.Call.Return(_a0)
 	return _c
 }
 
-func (_c *MockChatNavHandler_RequestChatRightsHandler_Call) RunAndReturn(run func(context.Context) XMessage) *MockChatNavHandler_RequestChatRightsHandler_Call {
+func (_c *MockChatNavHandler_RequestChatRightsHandler_Call) RunAndReturn(run func(context.Context) oscar.XMessage) *MockChatNavHandler_RequestChatRightsHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }
 
 // RequestRoomInfoHandler provides a mock function with given fields: ctx, snacPayloadIn
-func (_m *MockChatNavHandler) RequestRoomInfoHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x0D_0x04_ChatNavRequestRoomInfo) (XMessage, error) {
+func (_m *MockChatNavHandler) RequestRoomInfoHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x0D_0x04_ChatNavRequestRoomInfo) (oscar.XMessage, error) {
 	ret := _m.Called(ctx, snacPayloadIn)
 
-	var r0 XMessage
+	var r0 oscar.XMessage
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, oscar.SNAC_0x0D_0x04_ChatNavRequestRoomInfo) (XMessage, error)); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, oscar.SNAC_0x0D_0x04_ChatNavRequestRoomInfo) (oscar.XMessage, error)); ok {
 		return rf(ctx, snacPayloadIn)
 	}
-	if rf, ok := ret.Get(0).(func(context.Context, oscar.SNAC_0x0D_0x04_ChatNavRequestRoomInfo) XMessage); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, oscar.SNAC_0x0D_0x04_ChatNavRequestRoomInfo) oscar.XMessage); ok {
 		r0 = rf(ctx, snacPayloadIn)
 	} else {
-		r0 = ret.Get(0).(XMessage)
+		r0 = ret.Get(0).(oscar.XMessage)
 	}
 
 	if rf, ok := ret.Get(1).(func(context.Context, oscar.SNAC_0x0D_0x04_ChatNavRequestRoomInfo) error); ok {
@@ -162,12 +164,12 @@ func (_c *MockChatNavHandler_RequestRoomInfoHandler_Call) Run(run func(ctx conte
 	return _c
 }
 
-func (_c *MockChatNavHandler_RequestRoomInfoHandler_Call) Return(_a0 XMessage, _a1 error) *MockChatNavHandler_RequestRoomInfoHandler_Call {
+func (_c *MockChatNavHandler_RequestRoomInfoHandler_Call) Return(_a0 oscar.XMessage, _a1 error) *MockChatNavHandler_RequestRoomInfoHandler_Call {
 	_c.Call.Return(_a0, _a1)
 	return _c
 }
 
-func (_c *MockChatNavHandler_RequestRoomInfoHandler_Call) RunAndReturn(run func(context.Context, oscar.SNAC_0x0D_0x04_ChatNavRequestRoomInfo) (XMessage, error)) *MockChatNavHandler_RequestRoomInfoHandler_Call {
+func (_c *MockChatNavHandler_RequestRoomInfoHandler_Call) RunAndReturn(run func(context.Context, oscar.SNAC_0x0D_0x04_ChatNavRequestRoomInfo) (oscar.XMessage, error)) *MockChatNavHandler_RequestRoomInfoHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }

+ 38 - 40
server/chat_nav_test.go

@@ -3,6 +3,7 @@ package server
 import (
 	"bytes"
 	"context"
+	"github.com/mkaminski/goaim/user"
 	"log/slog"
 	"testing"
 	"time"
@@ -16,16 +17,13 @@ func TestSendAndReceiveCreateRoom(t *testing.T) {
 	//
 	// build dependencies
 	//
-	userSess := newTestSession(Session{
-		ID:         "sess-id",
-		ScreenName: "user-screen-name",
-	})
+	userSess := newTestSession("user-screen-name", sessOptCannedID)
 
 	cr := NewChatRegistry()
 
 	sm := NewMockSessionManager(t)
-	sm.EXPECT().NewSessionWithSN(userSess.ID, userSess.ScreenName).
-		Return(&Session{})
+	sm.EXPECT().NewSessionWithSN(userSess.ID(), userSess.ScreenName()).
+		Return(&user.Session{})
 
 	crf := func(logger *slog.Logger) ChatRoom {
 		return ChatRoom{
@@ -74,12 +72,12 @@ func TestSendAndReceiveCreateRoom(t *testing.T) {
 	//
 	// send input SNAC
 	//
-	expectSNAC := XMessage{
-		snacFrame: oscar.SnacFrame{
+	expectSNAC := oscar.XMessage{
+		SnacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.CHAT_NAV,
 			SubGroup:  oscar.ChatNavNavInfo,
 		},
-		snacOut: oscar.SNAC_0x0D_0x09_ChatNavNavInfo{
+		SnacOut: oscar.SNAC_0x0D_0x09_ChatNavNavInfo{
 			TLVRestBlock: oscar.TLVRestBlock{
 				TLVList: oscar.TLVList{
 					oscar.NewTLV(
@@ -107,9 +105,9 @@ func TestChatNavRouter_RouteChatNavRouter(t *testing.T) {
 		// name is the unit test name
 		name string
 		// input is the request payload
-		input XMessage
+		input oscar.XMessage
 		// output is the response payload
-		output XMessage
+		output oscar.XMessage
 		// handlerErr is the mocked handler error response
 		handlerErr error
 		// expectErr is the expected error returned by the router
@@ -117,19 +115,19 @@ func TestChatNavRouter_RouteChatNavRouter(t *testing.T) {
 	}{
 		{
 			name: "receive ChatNavRequestChatRights, return ChatNavNavInfo",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.CHAT_NAV,
 					SubGroup:  oscar.ChatNavRequestChatRights,
 				},
-				snacOut: struct{}{},
+				SnacOut: struct{}{},
 			},
-			output: XMessage{
-				snacFrame: oscar.SnacFrame{
+			output: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.CHAT_NAV,
 					SubGroup:  oscar.ChatNavNavInfo,
 				},
-				snacOut: oscar.SNAC_0x0D_0x09_ChatNavNavInfo{
+				SnacOut: oscar.SNAC_0x0D_0x09_ChatNavNavInfo{
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							oscar.NewTLV(0x02, uint8(10)),
@@ -140,21 +138,21 @@ func TestChatNavRouter_RouteChatNavRouter(t *testing.T) {
 		},
 		{
 			name: "receive ChatNavRequestRoomInfo, return ChatNavNavInfo",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.CHAT_NAV,
 					SubGroup:  oscar.ChatNavRequestRoomInfo,
 				},
-				snacOut: oscar.SNAC_0x0D_0x04_ChatNavRequestRoomInfo{
+				SnacOut: oscar.SNAC_0x0D_0x04_ChatNavRequestRoomInfo{
 					Exchange: 1,
 				},
 			},
-			output: XMessage{
-				snacFrame: oscar.SnacFrame{
+			output: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.CHAT_NAV,
 					SubGroup:  oscar.ChatNavNavInfo,
 				},
-				snacOut: oscar.SNAC_0x0D_0x09_ChatNavNavInfo{
+				SnacOut: oscar.SNAC_0x0D_0x09_ChatNavNavInfo{
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							oscar.NewTLV(0x02, uint8(10)),
@@ -165,21 +163,21 @@ func TestChatNavRouter_RouteChatNavRouter(t *testing.T) {
 		},
 		{
 			name: "receive ChatNavCreateRoom, return ChatNavNavInfo",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.CHAT_NAV,
 					SubGroup:  oscar.ChatNavCreateRoom,
 				},
-				snacOut: oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate{
+				SnacOut: oscar.SNAC_0x0E_0x02_ChatRoomInfoUpdate{
 					Exchange: 1,
 				},
 			},
-			output: XMessage{
-				snacFrame: oscar.SnacFrame{
+			output: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.CHAT_NAV,
 					SubGroup:  oscar.ChatNavNavInfo,
 				},
-				snacOut: oscar.SNAC_0x0D_0x09_ChatNavNavInfo{
+				SnacOut: oscar.SNAC_0x0D_0x09_ChatNavNavInfo{
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							oscar.NewTLV(0x02, uint8(10)),
@@ -190,14 +188,14 @@ func TestChatNavRouter_RouteChatNavRouter(t *testing.T) {
 		},
 		{
 			name: "receive ChatNavRequestOccupantList, return ErrUnsupportedSubGroup",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.CHAT_NAV,
 					SubGroup:  oscar.ChatNavRequestOccupantList,
 				},
-				snacOut: struct{}{},
+				SnacOut: struct{}{},
 			},
-			output:    XMessage{},
+			output:    oscar.XMessage{},
 			expectErr: ErrUnsupportedSubGroup,
 		},
 	}
@@ -210,11 +208,11 @@ func TestChatNavRouter_RouteChatNavRouter(t *testing.T) {
 				Return(tc.output).
 				Maybe()
 			svc.EXPECT().
-				RequestRoomInfoHandler(mock.Anything, tc.input.snacOut).
+				RequestRoomInfoHandler(mock.Anything, tc.input.SnacOut).
 				Return(tc.output, tc.handlerErr).
 				Maybe()
 			svc.EXPECT().
-				CreateRoomHandler(mock.Anything, mock.Anything, mock.Anything, tc.input.snacOut).
+				CreateRoomHandler(mock.Anything, mock.Anything, mock.Anything, tc.input.SnacOut).
 				Return(tc.output, tc.handlerErr).
 				Maybe()
 
@@ -226,18 +224,18 @@ func TestChatNavRouter_RouteChatNavRouter(t *testing.T) {
 			}
 
 			bufIn := &bytes.Buffer{}
-			assert.NoError(t, oscar.Marshal(tc.input.snacOut, bufIn))
+			assert.NoError(t, oscar.Marshal(tc.input.SnacOut, bufIn))
 
 			bufOut := &bytes.Buffer{}
 			seq := uint32(0)
 
-			err := router.RouteChatNav(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
 			}
 
-			if tc.output.snacFrame == (oscar.SnacFrame{}) {
+			if tc.output.SnacFrame == (oscar.SnacFrame{}) {
 				return
 			}
 
@@ -255,11 +253,11 @@ func TestChatNavRouter_RouteChatNavRouter(t *testing.T) {
 			// verify the SNAC frame
 			snacFrame := oscar.SnacFrame{}
 			assert.NoError(t, oscar.Unmarshal(&snacFrame, flapBuf))
-			assert.Equal(t, tc.output.snacFrame, snacFrame)
+			assert.Equal(t, tc.output.SnacFrame, snacFrame)
 
 			// verify the SNAC message
 			snacBuf := &bytes.Buffer{}
-			assert.NoError(t, oscar.Marshal(tc.output.snacOut, snacBuf))
+			assert.NoError(t, oscar.Marshal(tc.output.SnacOut, snacBuf))
 			assert.Equal(t, snacBuf.Bytes(), flapBuf.Bytes())
 		})
 	}

+ 41 - 48
server/chat_test.go

@@ -3,6 +3,7 @@ package server
 import (
 	"bytes"
 	"context"
+	"github.com/mkaminski/goaim/user"
 	"github.com/stretchr/testify/mock"
 	"testing"
 
@@ -15,19 +16,17 @@ 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 *Session
+		userSession *user.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
 		// room participants (except the sender)
-		expectSNACToParticipants XMessage
-		expectOutput             *XMessage
+		expectSNACToParticipants oscar.XMessage
+		expectOutput             *oscar.XMessage
 	}{
 		{
-			name: "send chat room message, expect acknowledgement to sender client",
-			userSession: newTestSession(Session{
-				ScreenName: "user_sending_chat_msg",
-			}),
+			name:        "send chat room message, expect acknowledgement to sender client",
+			userSession: newTestSession("user_sending_chat_msg", sessOptCannedSignonTime),
 			inputSNAC: oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost{
 				Cookie:  1234,
 				Channel: 14,
@@ -44,12 +43,12 @@ func TestSendAndReceiveChatChannelMsgToHost(t *testing.T) {
 					},
 				},
 			},
-			expectSNACToParticipants: XMessage{
-				snacFrame: oscar.SnacFrame{
+			expectSNACToParticipants: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.CHAT,
 					SubGroup:  oscar.ChatChannelMsgToClient,
 				},
-				snacOut: oscar.SNAC_0x0E_0x06_ChatChannelMsgToClient{
+				SnacOut: oscar.SNAC_0x0E_0x06_ChatChannelMsgToClient{
 					Cookie:  1234,
 					Channel: 14,
 					TLVRestBlock: oscar.TLVRestBlock{
@@ -57,36 +56,32 @@ func TestSendAndReceiveChatChannelMsgToHost(t *testing.T) {
 							oscar.NewTLV(oscar.ChatTLVPublicWhisperFlag, []byte{}),
 							oscar.NewTLV(oscar.ChatTLVEnableReflectionFlag, []byte{}),
 							oscar.NewTLV(oscar.ChatTLVSenderInformation,
-								newTestSession(Session{ScreenName: "user_sending_chat_msg"}).GetTLVUserInfo()),
+								newTestSession("user_sending_chat_msg", sessOptCannedSignonTime).TLVUserInfo()),
 						},
 					},
 				},
 			},
-			expectOutput: &XMessage{
-				snacFrame: oscar.SnacFrame{
+			expectOutput: &oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.CHAT,
 					SubGroup:  oscar.ChatChannelMsgToClient,
 				},
-				snacOut: oscar.SNAC_0x0E_0x06_ChatChannelMsgToClient{
+				SnacOut: oscar.SNAC_0x0E_0x06_ChatChannelMsgToClient{
 					Cookie:  1234,
 					Channel: 14,
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							oscar.NewTLV(oscar.ChatTLVPublicWhisperFlag, []byte{}),
 							oscar.NewTLV(oscar.ChatTLVEnableReflectionFlag, []byte{}),
-							oscar.NewTLV(oscar.ChatTLVSenderInformation, newTestSession(Session{
-								ScreenName: "user_sending_chat_msg",
-							}).GetTLVUserInfo()),
+							oscar.NewTLV(oscar.ChatTLVSenderInformation, newTestSession("user_sending_chat_msg", sessOptCannedSignonTime).TLVUserInfo()),
 						},
 					},
 				},
 			},
 		},
 		{
-			name: "send chat room message, don't expect acknowledgement to sender client",
-			userSession: newTestSession(Session{
-				ScreenName: "user_sending_chat_msg",
-			}),
+			name:        "send chat room message, don't expect acknowledgement to sender client",
+			userSession: newTestSession("user_sending_chat_msg", sessOptCannedSignonTime),
 			inputSNAC: oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost{
 				Cookie:  1234,
 				Channel: 14,
@@ -99,26 +94,24 @@ func TestSendAndReceiveChatChannelMsgToHost(t *testing.T) {
 					},
 				},
 			},
-			expectSNACToParticipants: XMessage{
-				snacFrame: oscar.SnacFrame{
+			expectSNACToParticipants: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.CHAT,
 					SubGroup:  oscar.ChatChannelMsgToClient,
 				},
-				snacOut: oscar.SNAC_0x0E_0x06_ChatChannelMsgToClient{
+				SnacOut: oscar.SNAC_0x0E_0x06_ChatChannelMsgToClient{
 					Cookie:  1234,
 					Channel: 14,
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							oscar.NewTLV(oscar.ChatTLVPublicWhisperFlag, []byte{}),
 							oscar.NewTLV(oscar.ChatTLVSenderInformation,
-								newTestSession(Session{
-									ScreenName: "user_sending_chat_msg",
-								}).GetTLVUserInfo()),
+								newTestSession("user_sending_chat_msg", sessOptCannedSignonTime).TLVUserInfo()),
 						},
 					},
 				},
 			},
-			expectOutput: &XMessage{},
+			expectOutput: &oscar.XMessage{},
 		},
 	}
 
@@ -138,7 +131,7 @@ func TestSendAndReceiveChatChannelMsgToHost(t *testing.T) {
 			outputSNAC, err := svc.ChannelMsgToHostHandler(context.Background(), tc.userSession, room, tc.inputSNAC)
 			assert.NoError(t, err)
 
-			if tc.expectOutput.snacFrame == (oscar.SnacFrame{}) {
+			if tc.expectOutput.SnacFrame == (oscar.SnacFrame{}) {
 				return // handler doesn't return response
 			}
 
@@ -152,9 +145,9 @@ func TestChatRouter_RouteChat(t *testing.T) {
 		// name is the unit test name
 		name string
 		// input is the request payload
-		input XMessage
+		input oscar.XMessage
 		// output is the response payload
-		output *XMessage
+		output *oscar.XMessage
 		// handlerErr is the mocked handler error response
 		handlerErr error
 		// expectErr is the expected error returned by the router
@@ -162,33 +155,33 @@ func TestChatRouter_RouteChat(t *testing.T) {
 	}{
 		{
 			name: "receive ChatChannelMsgToHost, return ChatChannelMsgToClient",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.CHAT,
 					SubGroup:  oscar.ChatChannelMsgToHost,
 				},
-				snacOut: oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost{
+				SnacOut: oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost{
 					Channel: 4,
 				},
 			},
-			output: &XMessage{
-				snacFrame: oscar.SnacFrame{
+			output: &oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.CHAT,
 					SubGroup:  oscar.ChatChannelMsgToClient,
 				},
-				snacOut: oscar.SNAC_0x0E_0x06_ChatChannelMsgToClient{
+				SnacOut: oscar.SNAC_0x0E_0x06_ChatChannelMsgToClient{
 					Channel: 4,
 				},
 			},
 		},
 		{
 			name: "receive ChatChannelMsgToHost, return no response",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.CHAT,
 					SubGroup:  oscar.ChatChannelMsgToHost,
 				},
-				snacOut: oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost{
+				SnacOut: oscar.SNAC_0x0E_0x05_ChatChannelMsgToHost{
 					Channel: 4,
 				},
 			},
@@ -196,12 +189,12 @@ func TestChatRouter_RouteChat(t *testing.T) {
 		},
 		{
 			name: "receive ChatRowListInfo, return ErrUnsupportedSubGroup",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.CHAT,
 					SubGroup:  oscar.ChatRowListInfo,
 				},
-				snacOut: struct{}{},
+				SnacOut: struct{}{},
 			},
 			output:    nil,
 			expectErr: ErrUnsupportedSubGroup,
@@ -212,7 +205,7 @@ func TestChatRouter_RouteChat(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			svc := NewMockChatHandler(t)
 			svc.EXPECT().
-				ChannelMsgToHostHandler(mock.Anything, mock.Anything, mock.Anything, tc.input.snacOut).
+				ChannelMsgToHostHandler(mock.Anything, mock.Anything, mock.Anything, tc.input.SnacOut).
 				Return(tc.output, tc.handlerErr).
 				Maybe()
 
@@ -224,12 +217,12 @@ func TestChatRouter_RouteChat(t *testing.T) {
 			}
 
 			bufIn := &bytes.Buffer{}
-			assert.NoError(t, oscar.Marshal(tc.input.snacOut, bufIn))
+			assert.NoError(t, oscar.Marshal(tc.input.SnacOut, bufIn))
 
 			bufOut := &bytes.Buffer{}
 			seq := uint32(0)
 
-			err := router.RouteChat(nil, nil, ChatRoom{}, tc.input.snacFrame, bufIn, bufOut, &seq)
+			err := router.RouteChat(nil, nil, ChatRoom{}, tc.input.SnacFrame, bufIn, bufOut, &seq)
 			assert.ErrorIs(t, err, tc.expectErr)
 			if tc.expectErr != nil {
 				return
@@ -255,11 +248,11 @@ func TestChatRouter_RouteChat(t *testing.T) {
 			// verify the SNAC frame
 			snacFrame := oscar.SnacFrame{}
 			assert.NoError(t, oscar.Unmarshal(&snacFrame, flapBuf))
-			assert.Equal(t, tc.output.snacFrame, snacFrame)
+			assert.Equal(t, tc.output.SnacFrame, snacFrame)
 
 			// verify the SNAC message
 			snacBuf := &bytes.Buffer{}
-			assert.NoError(t, oscar.Marshal(tc.output.snacOut, snacBuf))
+			assert.NoError(t, oscar.Marshal(tc.output.SnacOut, snacBuf))
 			assert.Equal(t, snacBuf.Bytes(), flapBuf.Bytes())
 		})
 	}

+ 11 - 15
server/connection.go

@@ -4,6 +4,7 @@ import (
 	"bytes"
 	"context"
 	"errors"
+	"github.com/mkaminski/goaim/user"
 	"io"
 	"log"
 	"log/slog"
@@ -15,20 +16,15 @@ import (
 )
 
 var (
-	CapChat, _             = uuid.MustParse("748F2420-6287-11D1-8222-444553540000").MarshalBinary()
 	ErrUnsupportedSubGroup = errors.New("unimplemented subgroup, your client version may be unsupported")
 )
 
 type (
-	XMessage struct {
-		snacFrame oscar.SnacFrame
-		snacOut   any
-	}
 	incomingMessage struct {
 		flap    oscar.FlapFrame
 		payload *bytes.Buffer
 	}
-	alertHandler     func(ctx context.Context, msg XMessage, w io.Writer, u *uint32) error
+	alertHandler     func(ctx context.Context, msg oscar.XMessage, w io.Writer, u *uint32) error
 	clientReqHandler func(ctx context.Context, r io.Reader, w io.Writer, u *uint32) error
 )
 
@@ -56,7 +52,7 @@ 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 *user.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)
@@ -89,10 +85,10 @@ func dispatchIncomingMessages(ctx context.Context, sess *Session, seq uint32, rw
 		case m := <-sess.RecvMessage():
 			// forward a notification sent from another client to this client
 			if err := alertHandler(ctx, m, rw, &seq); err != nil {
-				logRequestError(ctx, logger, m.snacFrame, err)
+				logRequestError(ctx, logger, m.SnacFrame, err)
 				return
 			}
-			logRequest(ctx, logger, m.snacFrame, m.snacOut)
+			logRequest(ctx, logger, m.SnacFrame, m.SnacOut)
 		case <-sess.Closed():
 			// gracefully disconnect so that the client does not try to
 			// reconnect when the connection closes.
@@ -148,7 +144,7 @@ func HandleChatConnection(ctx context.Context, cr *ChatRegistry, rw io.ReadWrite
 		cr.MaybeRemoveRoom(room.Cookie)
 	}()
 
-	ctx = context.WithValue(ctx, "screenName", chatSess.ScreenName)
+	ctx = context.WithValue(ctx, "screenName", chatSess.ScreenName())
 
 	if err := router.WriteOServiceHostOnline(rw, &seq); err != nil {
 		logger.ErrorContext(ctx, "error WriteOServiceHostOnline")
@@ -157,8 +153,8 @@ 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, room)
 	}
-	fnAlertHandler := func(ctx context.Context, msg XMessage, w io.Writer, seq *uint32) error {
-		return writeOutSNAC(oscar.SnacFrame{}, msg.snacFrame, msg.snacOut, seq, w)
+	fnAlertHandler := func(ctx context.Context, msg oscar.XMessage, w io.Writer, seq *uint32) error {
+		return writeOutSNAC(oscar.SnacFrame{}, msg.SnacFrame, msg.SnacOut, seq, w)
 	}
 	dispatchIncomingMessages(ctx, chatSess, seq, rw, logger, fnClientReqHandler, fnAlertHandler)
 }
@@ -200,7 +196,7 @@ func HandleBOSConnection(ctx context.Context, conn net.Conn, router BOSServiceRo
 		router.Signout(ctx, logger, sess)
 	}()
 
-	ctx = context.WithValue(ctx, "screenName", sess.ScreenName)
+	ctx = context.WithValue(ctx, "screenName", sess.ScreenName())
 
 	if err := router.WriteOServiceHostOnline(conn, &seq); err != nil {
 		logger.ErrorContext(ctx, "error WriteOServiceHostOnline")
@@ -209,8 +205,8 @@ func HandleBOSConnection(ctx context.Context, conn net.Conn, router BOSServiceRo
 	fnClientReqHandler := func(ctx context.Context, r io.Reader, w io.Writer, seq *uint32) error {
 		return router.Route(ctx, sess, r, w, seq)
 	}
-	fnAlertHandler := func(ctx context.Context, msg XMessage, w io.Writer, seq *uint32) error {
-		return writeOutSNAC(oscar.SnacFrame{}, msg.snacFrame, msg.snacOut, seq, w)
+	fnAlertHandler := func(ctx context.Context, msg oscar.XMessage, w io.Writer, seq *uint32) error {
+		return writeOutSNAC(oscar.SnacFrame{}, msg.SnacFrame, msg.SnacOut, seq, w)
 	}
 	dispatchIncomingMessages(ctx, sess, seq, conn, logger, fnClientReqHandler, fnAlertHandler)
 }

+ 10 - 10
server/connection_test.go

@@ -25,24 +25,24 @@ func TestHandleChatConnection_Notification(t *testing.T) {
 	bobSess := room.NewSessionWithSN("bob-sess-id", "bob")
 	cr.Register(room)
 
-	msgIn := []XMessage{
+	msgIn := []oscar.XMessage{
 		{
-			snacFrame: oscar.SnacFrame{
+			SnacFrame: oscar.SnacFrame{
 				FoodGroup: oscar.CHAT,
 				SubGroup:  oscar.ChatUsersJoined,
 			},
-			snacOut: oscar.SNAC_0x0E_0x03_ChatUsersJoined{
+			SnacOut: oscar.SNAC_0x0E_0x03_ChatUsersJoined{
 				Users: []oscar.TLVUserInfo{
-					bobSess.GetTLVUserInfo(),
+					bobSess.TLVUserInfo(),
 				},
 			},
 		},
 		{
-			snacFrame: oscar.SnacFrame{
+			SnacFrame: oscar.SnacFrame{
 				FoodGroup: oscar.CHAT,
 				SubGroup:  oscar.ChatUsersLeft,
 			},
-			snacOut: oscar.SNAC_0x0E_0x03_ChatUsersJoined{
+			SnacOut: oscar.SNAC_0x0E_0x03_ChatUsersJoined{
 				Users: []oscar.TLVUserInfo{},
 			},
 		},
@@ -55,8 +55,8 @@ func TestHandleChatConnection_Notification(t *testing.T) {
 	wg := sync.WaitGroup{}
 	wg.Add(len(msgIn))
 
-	var msgOut []XMessage
-	alertHandler := func(ctx context.Context, msg XMessage, w io.Writer, u *uint32) error {
+	var msgOut []oscar.XMessage
+	alertHandler := func(ctx context.Context, msg oscar.XMessage, w io.Writer, u *uint32) error {
 		msgOut = append(msgOut, msg)
 		wg.Done()
 		return nil
@@ -123,7 +123,7 @@ func TestHandleChatConnection_ClientRequestFLAP(t *testing.T) {
 		wg.Done()
 		return err
 	}
-	alertHandler := func(ctx context.Context, msg XMessage, w io.Writer, u *uint32) error {
+	alertHandler := func(ctx context.Context, msg oscar.XMessage, w io.Writer, u *uint32) error {
 		return nil
 	}
 
@@ -157,7 +157,7 @@ func TestHandleChatConnection_SessionClosed(t *testing.T) {
 		t.Fatal("not expecting any output")
 		return nil
 	}
-	alertHandler := func(ctx context.Context, msg XMessage, w io.Writer, u *uint32) error {
+	alertHandler := func(ctx context.Context, msg oscar.XMessage, w io.Writer, u *uint32) error {
 		t.Fatal("not expecting any alerts")
 		return nil
 	}

+ 76 - 75
server/feedbag.go

@@ -3,6 +3,7 @@ package server
 import (
 	"context"
 	"errors"
+	"github.com/mkaminski/goaim/user"
 	"io"
 	"log/slog"
 	"time"
@@ -11,13 +12,13 @@ import (
 )
 
 type FeedbagHandler interface {
-	DeleteItemHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x13_0x0A_FeedbagDeleteItem) (XMessage, error)
-	InsertItemHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x13_0x08_FeedbagInsertItem) (XMessage, error)
-	QueryHandler(ctx context.Context, sess *Session) (XMessage, error)
-	QueryIfModifiedHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x13_0x05_FeedbagQueryIfModified) (XMessage, error)
-	RightsQueryHandler(context.Context) XMessage
+	DeleteItemHandler(ctx context.Context, sess *user.Session, snacPayloadIn oscar.SNAC_0x13_0x0A_FeedbagDeleteItem) (oscar.XMessage, error)
+	InsertItemHandler(ctx context.Context, sess *user.Session, snacPayloadIn oscar.SNAC_0x13_0x08_FeedbagInsertItem) (oscar.XMessage, error)
+	QueryHandler(ctx context.Context, sess *user.Session) (oscar.XMessage, error)
+	QueryIfModifiedHandler(ctx context.Context, sess *user.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) (XMessage, error)
+	UpdateItemHandler(ctx context.Context, sess *user.Session, snacPayloadIn oscar.SNAC_0x13_0x09_FeedbagUpdateItem) (oscar.XMessage, error)
 }
 
 func NewFeedbagRouter(logger *slog.Logger, sm SessionManager, fm FeedbagManager) FeedbagRouter {
@@ -37,7 +38,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 *user.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{}
@@ -45,15 +46,15 @@ func (rt FeedbagRouter) RouteFeedbag(ctx context.Context, sess *Session, SNACFra
 			return err
 		}
 		outSNAC := rt.RightsQueryHandler(ctx)
-		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.snacFrame, outSNAC.snacOut)
-		return writeOutSNAC(SNACFrame, outSNAC.snacFrame, outSNAC.snacOut, sequence, w)
+		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.SnacFrame, outSNAC.SnacOut)
+		return writeOutSNAC(SNACFrame, outSNAC.SnacFrame, outSNAC.SnacOut, sequence, w)
 	case oscar.FeedbagQuery:
 		inSNAC, err := rt.QueryHandler(ctx, sess)
 		if err != nil {
 			return err
 		}
 		rt.logRequest(ctx, SNACFrame, inSNAC)
-		return writeOutSNAC(SNACFrame, inSNAC.snacFrame, inSNAC.snacOut, sequence, w)
+		return writeOutSNAC(SNACFrame, inSNAC.SnacFrame, inSNAC.SnacOut, sequence, w)
 	case oscar.FeedbagQueryIfModified:
 		inSNAC := oscar.SNAC_0x13_0x05_FeedbagQueryIfModified{}
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
@@ -63,8 +64,8 @@ func (rt FeedbagRouter) RouteFeedbag(ctx context.Context, sess *Session, SNACFra
 		if err != nil {
 			return err
 		}
-		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.snacFrame, outSNAC.snacOut)
-		return writeOutSNAC(SNACFrame, outSNAC.snacFrame, outSNAC.snacOut, sequence, w)
+		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.SnacFrame, outSNAC.SnacOut)
+		return writeOutSNAC(SNACFrame, outSNAC.SnacFrame, outSNAC.SnacOut, sequence, w)
 	case oscar.FeedbagUse:
 		rt.logRequest(ctx, SNACFrame, nil)
 		return nil
@@ -77,8 +78,8 @@ func (rt FeedbagRouter) RouteFeedbag(ctx context.Context, sess *Session, SNACFra
 		if err != nil {
 			return err
 		}
-		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.snacFrame, outSNAC.snacOut)
-		return writeOutSNAC(SNACFrame, outSNAC.snacFrame, outSNAC.snacOut, sequence, w)
+		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.SnacFrame, outSNAC.SnacOut)
+		return writeOutSNAC(SNACFrame, outSNAC.SnacFrame, outSNAC.SnacOut, sequence, w)
 	case oscar.FeedbagUpdateItem:
 		inSNAC := oscar.SNAC_0x13_0x09_FeedbagUpdateItem{}
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
@@ -88,8 +89,8 @@ func (rt FeedbagRouter) RouteFeedbag(ctx context.Context, sess *Session, SNACFra
 		if err != nil {
 			return err
 		}
-		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.snacFrame, outSNAC.snacOut)
-		return writeOutSNAC(SNACFrame, outSNAC.snacFrame, outSNAC.snacOut, sequence, w)
+		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.SnacFrame, outSNAC.SnacOut)
+		return writeOutSNAC(SNACFrame, outSNAC.SnacFrame, outSNAC.SnacOut, sequence, w)
 	case oscar.FeedbagDeleteItem:
 		inSNAC := oscar.SNAC_0x13_0x0A_FeedbagDeleteItem{}
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
@@ -99,8 +100,8 @@ func (rt FeedbagRouter) RouteFeedbag(ctx context.Context, sess *Session, SNACFra
 		if err != nil {
 			return err
 		}
-		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.snacFrame, outSNAC.snacOut)
-		return writeOutSNAC(SNACFrame, outSNAC.snacFrame, outSNAC.snacOut, sequence, w)
+		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.SnacFrame, outSNAC.SnacOut)
+		return writeOutSNAC(SNACFrame, outSNAC.SnacFrame, outSNAC.SnacOut, sequence, w)
 	case oscar.FeedbagStartCluster:
 		inSNAC := oscar.SNAC_0x13_0x11_FeedbagStartCluster{}
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
@@ -122,13 +123,13 @@ type FeedbagService struct {
 	fm FeedbagManager
 }
 
-func (s FeedbagService) RightsQueryHandler(context.Context) XMessage {
-	return XMessage{
-		snacFrame: oscar.SnacFrame{
+func (s FeedbagService) RightsQueryHandler(context.Context) oscar.XMessage {
+	return oscar.XMessage{
+		SnacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.FEEDBAG,
 			SubGroup:  oscar.FeedbagRightsReply,
 		},
-		snacOut: oscar.SNAC_0x13_0x03_FeedbagRightsReply{
+		SnacOut: oscar.SNAC_0x13_0x03_FeedbagRightsReply{
 			TLVRestBlock: oscar.TLVRestBlock{
 				TLVList: oscar.TLVList{
 					oscar.NewTLV(0x03, uint16(200)),
@@ -170,27 +171,27 @@ func (s FeedbagService) RightsQueryHandler(context.Context) XMessage {
 	}
 }
 
-func (s FeedbagService) QueryHandler(_ context.Context, sess *Session) (XMessage, error) {
-	fb, err := s.fm.Retrieve(sess.ScreenName)
+func (s FeedbagService) QueryHandler(_ context.Context, sess *user.Session) (oscar.XMessage, error) {
+	fb, err := s.fm.Retrieve(sess.ScreenName())
 	if err != nil {
-		return XMessage{}, err
+		return oscar.XMessage{}, err
 	}
 
 	lm := time.UnixMilli(0)
 
 	if len(fb) > 0 {
-		lm, err = s.fm.LastModified(sess.ScreenName)
+		lm, err = s.fm.LastModified(sess.ScreenName())
 		if err != nil {
-			return XMessage{}, err
+			return oscar.XMessage{}, err
 		}
 	}
 
-	return XMessage{
-		snacFrame: oscar.SnacFrame{
+	return oscar.XMessage{
+		SnacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.FEEDBAG,
 			SubGroup:  oscar.FeedbagReply,
 		},
-		snacOut: oscar.SNAC_0x13_0x06_FeedbagReply{
+		SnacOut: oscar.SNAC_0x13_0x06_FeedbagReply{
 			Version:    0,
 			Items:      fb,
 			LastUpdate: uint32(lm.Unix()),
@@ -198,26 +199,26 @@ func (s FeedbagService) QueryHandler(_ context.Context, sess *Session) (XMessage
 	}, nil
 }
 
-func (s FeedbagService) QueryIfModifiedHandler(_ context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x13_0x05_FeedbagQueryIfModified) (XMessage, error) {
-	fb, err := s.fm.Retrieve(sess.ScreenName)
+func (s FeedbagService) QueryIfModifiedHandler(_ context.Context, sess *user.Session, snacPayloadIn oscar.SNAC_0x13_0x05_FeedbagQueryIfModified) (oscar.XMessage, error) {
+	fb, err := s.fm.Retrieve(sess.ScreenName())
 	if err != nil {
-		return XMessage{}, err
+		return oscar.XMessage{}, err
 	}
 
 	lm := time.UnixMilli(0)
 
 	if len(fb) > 0 {
-		lm, err = s.fm.LastModified(sess.ScreenName)
+		lm, err = s.fm.LastModified(sess.ScreenName())
 		if err != nil {
-			return XMessage{}, err
+			return oscar.XMessage{}, err
 		}
 		if lm.Before(time.Unix(int64(snacPayloadIn.LastUpdate), 0)) {
-			return XMessage{
-				snacFrame: oscar.SnacFrame{
+			return oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.FEEDBAG,
 					SubGroup:  oscar.FeedbagReplyNotModified,
 				},
-				snacOut: oscar.SNAC_0x13_0x05_FeedbagQueryIfModified{
+				SnacOut: oscar.SNAC_0x13_0x05_FeedbagQueryIfModified{
 					LastUpdate: uint32(lm.Unix()),
 					Count:      uint8(len(fb)),
 				},
@@ -225,12 +226,12 @@ func (s FeedbagService) QueryIfModifiedHandler(_ context.Context, sess *Session,
 		}
 	}
 
-	return XMessage{
-		snacFrame: oscar.SnacFrame{
+	return oscar.XMessage{
+		SnacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.FEEDBAG,
 			SubGroup:  oscar.FeedbagReply,
 		},
-		snacOut: oscar.SNAC_0x13_0x06_FeedbagReply{
+		SnacOut: oscar.SNAC_0x13_0x06_FeedbagReply{
 			Version:    0,
 			Items:      fb,
 			LastUpdate: uint32(lm.Unix()),
@@ -238,49 +239,49 @@ func (s FeedbagService) QueryIfModifiedHandler(_ context.Context, sess *Session,
 	}, nil
 }
 
-func (s FeedbagService) InsertItemHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x13_0x08_FeedbagInsertItem) (XMessage, error) {
+func (s FeedbagService) InsertItemHandler(ctx context.Context, sess *user.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.
-		if item.ClassID == 3 && item.Name == sess.ScreenName {
-			return XMessage{
-				snacFrame: oscar.SnacFrame{
+		if item.ClassID == 3 && item.Name == sess.ScreenName() {
+			return oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.FEEDBAG,
 					SubGroup:  oscar.FeedbagErr,
 				},
-				snacOut: oscar.SnacError{
+				SnacOut: oscar.SnacError{
 					Code: oscar.ErrorCodeNotSupportedByHost,
 				},
 			}, nil
 		}
 	}
 
-	if err := s.fm.Upsert(sess.ScreenName, snacPayloadIn.Items); err != nil {
-		return XMessage{}, nil
+	if err := s.fm.Upsert(sess.ScreenName(), snacPayloadIn.Items); err != nil {
+		return oscar.XMessage{}, nil
 	}
 
 	for _, item := range snacPayloadIn.Items {
 		switch item.ClassID {
 		case oscar.FeedbagClassIdBuddy, oscar.FeedbagClassIDPermit: // add new buddy
-			err := UnicastArrival(ctx, item.Name, sess.ScreenName, s.sm)
+			err := UnicastArrival(ctx, item.Name, sess.ScreenName(), s.sm)
 			switch {
 			case errors.Is(err, ErrSessNotFound):
 				continue
 			case err != nil:
-				return XMessage{}, err
+				return oscar.XMessage{}, err
 			}
 		case oscar.FeedbagClassIDDeny: // block buddy
 			// notify this user that buddy is offline
-			err := UnicastDeparture(ctx, item.Name, sess.ScreenName, s.sm)
+			err := UnicastDeparture(ctx, item.Name, sess.ScreenName(), s.sm)
 			switch {
 			case errors.Is(err, ErrSessNotFound):
 				continue
 			case err != nil:
-				return XMessage{}, err
+				return oscar.XMessage{}, err
 			}
 			// notify former buddy that this user is offline
-			if err := UnicastDeparture(ctx, sess.ScreenName, item.Name, s.sm); err != nil {
-				return XMessage{}, err
+			if err := UnicastDeparture(ctx, sess.ScreenName(), item.Name, s.sm); err != nil {
+				return oscar.XMessage{}, err
 			}
 		}
 	}
@@ -290,29 +291,29 @@ func (s FeedbagService) InsertItemHandler(ctx context.Context, sess *Session, sn
 		snacPayloadOut.Results = append(snacPayloadOut.Results, 0x0000)
 	}
 
-	return XMessage{
-		snacFrame: oscar.SnacFrame{
+	return oscar.XMessage{
+		SnacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.FEEDBAG,
 			SubGroup:  oscar.FeedbagStatus,
 		},
-		snacOut: snacPayloadOut,
+		SnacOut: snacPayloadOut,
 	}, nil
 }
 
-func (s FeedbagService) UpdateItemHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x13_0x09_FeedbagUpdateItem) (XMessage, error) {
-	if err := s.fm.Upsert(sess.ScreenName, snacPayloadIn.Items); err != nil {
-		return XMessage{}, nil
+func (s FeedbagService) UpdateItemHandler(ctx context.Context, sess *user.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
 	}
 
 	for _, item := range snacPayloadIn.Items {
 		switch item.ClassID {
 		case oscar.FeedbagClassIdBuddy, oscar.FeedbagClassIDPermit:
-			err := UnicastArrival(ctx, item.Name, sess.ScreenName, s.sm)
+			err := UnicastArrival(ctx, item.Name, sess.ScreenName(), s.sm)
 			switch {
 			case errors.Is(err, ErrSessNotFound):
 				continue
 			case err != nil:
-				return XMessage{}, err
+				return oscar.XMessage{}, err
 			}
 		}
 	}
@@ -322,35 +323,35 @@ func (s FeedbagService) UpdateItemHandler(ctx context.Context, sess *Session, sn
 		snacPayloadOut.Results = append(snacPayloadOut.Results, 0x0000)
 	}
 
-	return XMessage{
-		snacFrame: oscar.SnacFrame{
+	return oscar.XMessage{
+		SnacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.FEEDBAG,
 			SubGroup:  oscar.FeedbagStatus,
 		},
-		snacOut: snacPayloadOut,
+		SnacOut: snacPayloadOut,
 	}, nil
 }
 
-func (s FeedbagService) DeleteItemHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x13_0x0A_FeedbagDeleteItem) (XMessage, error) {
-	if err := s.fm.Delete(sess.ScreenName, snacPayloadIn.Items); err != nil {
-		return XMessage{}, err
+func (s FeedbagService) DeleteItemHandler(ctx context.Context, sess *user.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
 	}
 
 	for _, item := range snacPayloadIn.Items {
 		if item.ClassID == oscar.FeedbagClassIDDeny {
-			err := UnicastArrival(ctx, item.Name, sess.ScreenName, s.sm)
+			err := UnicastArrival(ctx, item.Name, sess.ScreenName(), s.sm)
 			switch {
 			case errors.Is(err, ErrSessNotFound):
 				continue
 			case err != nil:
-				return XMessage{}, err
+				return oscar.XMessage{}, err
 			}
-			err = UnicastArrival(ctx, sess.ScreenName, item.Name, s.sm)
+			err = UnicastArrival(ctx, sess.ScreenName(), item.Name, s.sm)
 			switch {
 			case errors.Is(err, ErrSessNotFound):
 				continue
 			case err != nil:
-				return XMessage{}, err
+				return oscar.XMessage{}, err
 			}
 		}
 	}
@@ -360,12 +361,12 @@ func (s FeedbagService) DeleteItemHandler(ctx context.Context, sess *Session, sn
 		snacPayloadOut.Results = append(snacPayloadOut.Results, 0x0000) // success by default
 	}
 
-	return XMessage{
-		snacFrame: oscar.SnacFrame{
+	return oscar.XMessage{
+		SnacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.FEEDBAG,
 			SubGroup:  oscar.FeedbagStatus,
 		},
-		snacOut: snacPayloadOut,
+		SnacOut: snacPayloadOut,
 	}, nil
 }
 

+ 63 - 61
server/feedbag_mock.go

@@ -7,6 +7,8 @@ import (
 
 	oscar "github.com/mkaminski/goaim/oscar"
 	mock "github.com/stretchr/testify/mock"
+
+	user "github.com/mkaminski/goaim/user"
 )
 
 // 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) (XMessage, error) {
+func (_m *MockFeedbagHandler) DeleteItemHandler(ctx context.Context, sess *user.Session, snacPayloadIn oscar.SNAC_0x13_0x0A_FeedbagDeleteItem) (oscar.XMessage, error) {
 	ret := _m.Called(ctx, sess, snacPayloadIn)
 
-	var r0 XMessage
+	var r0 oscar.XMessage
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x13_0x0A_FeedbagDeleteItem) (XMessage, error)); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *user.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) XMessage); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *user.Session, oscar.SNAC_0x13_0x0A_FeedbagDeleteItem) oscar.XMessage); ok {
 		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
-		r0 = ret.Get(0).(XMessage)
+		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, *user.Session, oscar.SNAC_0x13_0x0A_FeedbagDeleteItem) error); ok {
 		r1 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r1 = ret.Error(1)
@@ -53,45 +55,45 @@ type MockFeedbagHandler_DeleteItemHandler_Call struct {
 
 // DeleteItemHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *user.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 *user.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].(*user.Session), args[2].(oscar.SNAC_0x13_0x0A_FeedbagDeleteItem))
 	})
 	return _c
 }
 
-func (_c *MockFeedbagHandler_DeleteItemHandler_Call) Return(_a0 XMessage, _a1 error) *MockFeedbagHandler_DeleteItemHandler_Call {
+func (_c *MockFeedbagHandler_DeleteItemHandler_Call) Return(_a0 oscar.XMessage, _a1 error) *MockFeedbagHandler_DeleteItemHandler_Call {
 	_c.Call.Return(_a0, _a1)
 	return _c
 }
 
-func (_c *MockFeedbagHandler_DeleteItemHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x13_0x0A_FeedbagDeleteItem) (XMessage, error)) *MockFeedbagHandler_DeleteItemHandler_Call {
+func (_c *MockFeedbagHandler_DeleteItemHandler_Call) RunAndReturn(run func(context.Context, *user.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) (XMessage, error) {
+func (_m *MockFeedbagHandler) InsertItemHandler(ctx context.Context, sess *user.Session, snacPayloadIn oscar.SNAC_0x13_0x08_FeedbagInsertItem) (oscar.XMessage, error) {
 	ret := _m.Called(ctx, sess, snacPayloadIn)
 
-	var r0 XMessage
+	var r0 oscar.XMessage
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x13_0x08_FeedbagInsertItem) (XMessage, error)); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *user.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) XMessage); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *user.Session, oscar.SNAC_0x13_0x08_FeedbagInsertItem) oscar.XMessage); ok {
 		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
-		r0 = ret.Get(0).(XMessage)
+		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, *user.Session, oscar.SNAC_0x13_0x08_FeedbagInsertItem) error); ok {
 		r1 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r1 = ret.Error(1)
@@ -107,45 +109,45 @@ type MockFeedbagHandler_InsertItemHandler_Call struct {
 
 // InsertItemHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *user.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 *user.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].(*user.Session), args[2].(oscar.SNAC_0x13_0x08_FeedbagInsertItem))
 	})
 	return _c
 }
 
-func (_c *MockFeedbagHandler_InsertItemHandler_Call) Return(_a0 XMessage, _a1 error) *MockFeedbagHandler_InsertItemHandler_Call {
+func (_c *MockFeedbagHandler_InsertItemHandler_Call) Return(_a0 oscar.XMessage, _a1 error) *MockFeedbagHandler_InsertItemHandler_Call {
 	_c.Call.Return(_a0, _a1)
 	return _c
 }
 
-func (_c *MockFeedbagHandler_InsertItemHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x13_0x08_FeedbagInsertItem) (XMessage, error)) *MockFeedbagHandler_InsertItemHandler_Call {
+func (_c *MockFeedbagHandler_InsertItemHandler_Call) RunAndReturn(run func(context.Context, *user.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) (XMessage, error) {
+func (_m *MockFeedbagHandler) QueryHandler(ctx context.Context, sess *user.Session) (oscar.XMessage, error) {
 	ret := _m.Called(ctx, sess)
 
-	var r0 XMessage
+	var r0 oscar.XMessage
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, *Session) (XMessage, error)); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *user.Session) (oscar.XMessage, error)); ok {
 		return rf(ctx, sess)
 	}
-	if rf, ok := ret.Get(0).(func(context.Context, *Session) XMessage); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *user.Session) oscar.XMessage); ok {
 		r0 = rf(ctx, sess)
 	} else {
-		r0 = ret.Get(0).(XMessage)
+		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, *user.Session) error); ok {
 		r1 = rf(ctx, sess)
 	} else {
 		r1 = ret.Error(1)
@@ -161,44 +163,44 @@ type MockFeedbagHandler_QueryHandler_Call struct {
 
 // QueryHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *user.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 *user.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].(*user.Session))
 	})
 	return _c
 }
 
-func (_c *MockFeedbagHandler_QueryHandler_Call) Return(_a0 XMessage, _a1 error) *MockFeedbagHandler_QueryHandler_Call {
+func (_c *MockFeedbagHandler_QueryHandler_Call) Return(_a0 oscar.XMessage, _a1 error) *MockFeedbagHandler_QueryHandler_Call {
 	_c.Call.Return(_a0, _a1)
 	return _c
 }
 
-func (_c *MockFeedbagHandler_QueryHandler_Call) RunAndReturn(run func(context.Context, *Session) (XMessage, error)) *MockFeedbagHandler_QueryHandler_Call {
+func (_c *MockFeedbagHandler_QueryHandler_Call) RunAndReturn(run func(context.Context, *user.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) (XMessage, error) {
+func (_m *MockFeedbagHandler) QueryIfModifiedHandler(ctx context.Context, sess *user.Session, snacPayloadIn oscar.SNAC_0x13_0x05_FeedbagQueryIfModified) (oscar.XMessage, error) {
 	ret := _m.Called(ctx, sess, snacPayloadIn)
 
-	var r0 XMessage
+	var r0 oscar.XMessage
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x13_0x05_FeedbagQueryIfModified) (XMessage, error)); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *user.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) XMessage); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *user.Session, oscar.SNAC_0x13_0x05_FeedbagQueryIfModified) oscar.XMessage); ok {
 		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
-		r0 = ret.Get(0).(XMessage)
+		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, *user.Session, oscar.SNAC_0x13_0x05_FeedbagQueryIfModified) error); ok {
 		r1 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r1 = ret.Error(1)
@@ -214,38 +216,38 @@ type MockFeedbagHandler_QueryIfModifiedHandler_Call struct {
 
 // QueryIfModifiedHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *user.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 *user.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].(*user.Session), args[2].(oscar.SNAC_0x13_0x05_FeedbagQueryIfModified))
 	})
 	return _c
 }
 
-func (_c *MockFeedbagHandler_QueryIfModifiedHandler_Call) Return(_a0 XMessage, _a1 error) *MockFeedbagHandler_QueryIfModifiedHandler_Call {
+func (_c *MockFeedbagHandler_QueryIfModifiedHandler_Call) Return(_a0 oscar.XMessage, _a1 error) *MockFeedbagHandler_QueryIfModifiedHandler_Call {
 	_c.Call.Return(_a0, _a1)
 	return _c
 }
 
-func (_c *MockFeedbagHandler_QueryIfModifiedHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x13_0x05_FeedbagQueryIfModified) (XMessage, error)) *MockFeedbagHandler_QueryIfModifiedHandler_Call {
+func (_c *MockFeedbagHandler_QueryIfModifiedHandler_Call) RunAndReturn(run func(context.Context, *user.Session, oscar.SNAC_0x13_0x05_FeedbagQueryIfModified) (oscar.XMessage, error)) *MockFeedbagHandler_QueryIfModifiedHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }
 
 // RightsQueryHandler provides a mock function with given fields: _a0
-func (_m *MockFeedbagHandler) RightsQueryHandler(_a0 context.Context) XMessage {
+func (_m *MockFeedbagHandler) RightsQueryHandler(_a0 context.Context) oscar.XMessage {
 	ret := _m.Called(_a0)
 
-	var r0 XMessage
-	if rf, ok := ret.Get(0).(func(context.Context) XMessage); ok {
+	var r0 oscar.XMessage
+	if rf, ok := ret.Get(0).(func(context.Context) oscar.XMessage); ok {
 		r0 = rf(_a0)
 	} else {
-		r0 = ret.Get(0).(XMessage)
+		r0 = ret.Get(0).(oscar.XMessage)
 	}
 
 	return r0
@@ -269,12 +271,12 @@ func (_c *MockFeedbagHandler_RightsQueryHandler_Call) Run(run func(_a0 context.C
 	return _c
 }
 
-func (_c *MockFeedbagHandler_RightsQueryHandler_Call) Return(_a0 XMessage) *MockFeedbagHandler_RightsQueryHandler_Call {
+func (_c *MockFeedbagHandler_RightsQueryHandler_Call) Return(_a0 oscar.XMessage) *MockFeedbagHandler_RightsQueryHandler_Call {
 	_c.Call.Return(_a0)
 	return _c
 }
 
-func (_c *MockFeedbagHandler_RightsQueryHandler_Call) RunAndReturn(run func(context.Context) XMessage) *MockFeedbagHandler_RightsQueryHandler_Call {
+func (_c *MockFeedbagHandler_RightsQueryHandler_Call) RunAndReturn(run func(context.Context) oscar.XMessage) *MockFeedbagHandler_RightsQueryHandler_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) (XMessage, error) {
+func (_m *MockFeedbagHandler) UpdateItemHandler(ctx context.Context, sess *user.Session, snacPayloadIn oscar.SNAC_0x13_0x09_FeedbagUpdateItem) (oscar.XMessage, error) {
 	ret := _m.Called(ctx, sess, snacPayloadIn)
 
-	var r0 XMessage
+	var r0 oscar.XMessage
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x13_0x09_FeedbagUpdateItem) (XMessage, error)); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *user.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) XMessage); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *user.Session, oscar.SNAC_0x13_0x09_FeedbagUpdateItem) oscar.XMessage); ok {
 		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
-		r0 = ret.Get(0).(XMessage)
+		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, *user.Session, oscar.SNAC_0x13_0x09_FeedbagUpdateItem) error); ok {
 		r1 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r1 = ret.Error(1)
@@ -344,25 +346,25 @@ type MockFeedbagHandler_UpdateItemHandler_Call struct {
 
 // UpdateItemHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *user.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 *user.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].(*user.Session), args[2].(oscar.SNAC_0x13_0x09_FeedbagUpdateItem))
 	})
 	return _c
 }
 
-func (_c *MockFeedbagHandler_UpdateItemHandler_Call) Return(_a0 XMessage, _a1 error) *MockFeedbagHandler_UpdateItemHandler_Call {
+func (_c *MockFeedbagHandler_UpdateItemHandler_Call) Return(_a0 oscar.XMessage, _a1 error) *MockFeedbagHandler_UpdateItemHandler_Call {
 	_c.Call.Return(_a0, _a1)
 	return _c
 }
 
-func (_c *MockFeedbagHandler_UpdateItemHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x13_0x09_FeedbagUpdateItem) (XMessage, error)) *MockFeedbagHandler_UpdateItemHandler_Call {
+func (_c *MockFeedbagHandler_UpdateItemHandler_Call) RunAndReturn(run func(context.Context, *user.Session, oscar.SNAC_0x13_0x09_FeedbagUpdateItem) (oscar.XMessage, error)) *MockFeedbagHandler_UpdateItemHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }

+ 10 - 9
server/feedbag_store.go

@@ -12,6 +12,7 @@ import (
 	"github.com/google/uuid"
 	_ "github.com/mattn/go-sqlite3"
 	"github.com/mkaminski/goaim/oscar"
+	"github.com/mkaminski/goaim/user"
 )
 
 var feedbagDDL = `
@@ -400,16 +401,16 @@ type FeedbagManager interface {
 }
 
 type SessionManager interface {
-	BroadcastToScreenNames(ctx context.Context, screenNames []string, msg 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, error)
-	SendToScreenName(ctx context.Context, screenName string, msg XMessage)
-	Broadcast(ctx context.Context, msg XMessage)
-	BroadcastExcept(ctx context.Context, except *Session, msg XMessage)
-	Participants() []*Session
+	NewSessionWithSN(sessID string, screenName string) *user.Session
+	Remove(sess *user.Session)
+	Retrieve(ID string) (*user.Session, bool)
+	RetrieveByScreenName(screenName string) (*user.Session, error)
+	SendToScreenName(ctx context.Context, screenName string, msg oscar.XMessage)
+	Broadcast(ctx context.Context, msg oscar.XMessage)
+	BroadcastExcept(ctx context.Context, except *user.Session, msg oscar.XMessage)
+	Participants() []*user.Session
 }
 
 type ProfileManager interface {

+ 147 - 166
server/feedbag_test.go

@@ -3,6 +3,7 @@ package server
 import (
 	"bytes"
 	"github.com/mkaminski/goaim/oscar"
+	"github.com/mkaminski/goaim/user"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/mock"
 	"testing"
@@ -21,19 +22,19 @@ func TestQueryHandler(t *testing.T) {
 		lastModified time.Time
 		// expectOutput is the SNAC payload sent from the server to the
 		// recipient client
-		expectOutput XMessage
+		expectOutput oscar.XMessage
 	}{
 		{
 			name:         "retrieve empty feedbag",
 			screenName:   "user_screen_name",
 			feedbagItems: []oscar.FeedbagItem{},
 			lastModified: time.UnixMilli(0),
-			expectOutput: XMessage{
-				snacFrame: oscar.SnacFrame{
+			expectOutput: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.FEEDBAG,
 					SubGroup:  oscar.FeedbagReply,
 				},
-				snacOut: oscar.SNAC_0x13_0x06_FeedbagReply{
+				SnacOut: oscar.SNAC_0x13_0x06_FeedbagReply{
 					Items: []oscar.FeedbagItem{},
 				},
 			},
@@ -50,12 +51,12 @@ func TestQueryHandler(t *testing.T) {
 				},
 			},
 			lastModified: time.UnixMilli(1696472198082),
-			expectOutput: XMessage{
-				snacFrame: oscar.SnacFrame{
+			expectOutput: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.FEEDBAG,
 					SubGroup:  oscar.FeedbagReply,
 				},
-				snacOut: oscar.SNAC_0x13_0x06_FeedbagReply{
+				SnacOut: oscar.SNAC_0x13_0x06_FeedbagReply{
 					Version: 0,
 					Items: []oscar.FeedbagItem{
 						{
@@ -88,9 +89,7 @@ func TestQueryHandler(t *testing.T) {
 			//
 			// send input SNAC
 			//
-			senderSession := &Session{
-				ScreenName: tc.screenName,
-			}
+			senderSession := newTestSession(tc.screenName)
 			svc := FeedbagService{
 				fm: fm,
 			}
@@ -115,7 +114,7 @@ func TestQueryIfModifiedHandler(t *testing.T) {
 		inputSNAC oscar.SNAC_0x13_0x05_FeedbagQueryIfModified
 		// expectOutput is the SNAC payload sent from the server to the
 		// recipient client
-		expectOutput XMessage
+		expectOutput oscar.XMessage
 	}{
 		{
 			name:         "retrieve empty feedbag",
@@ -125,12 +124,12 @@ func TestQueryIfModifiedHandler(t *testing.T) {
 			inputSNAC: oscar.SNAC_0x13_0x05_FeedbagQueryIfModified{
 				LastUpdate: uint32(time.UnixMilli(100000).Unix()),
 			},
-			expectOutput: XMessage{
-				snacFrame: oscar.SnacFrame{
+			expectOutput: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.FEEDBAG,
 					SubGroup:  oscar.FeedbagReply,
 				},
-				snacOut: oscar.SNAC_0x13_0x06_FeedbagReply{
+				SnacOut: oscar.SNAC_0x13_0x06_FeedbagReply{
 					Items: []oscar.FeedbagItem{},
 				},
 			},
@@ -150,12 +149,12 @@ func TestQueryIfModifiedHandler(t *testing.T) {
 			inputSNAC: oscar.SNAC_0x13_0x05_FeedbagQueryIfModified{
 				LastUpdate: uint32(time.UnixMilli(100000).Unix()),
 			},
-			expectOutput: XMessage{
-				snacFrame: oscar.SnacFrame{
+			expectOutput: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.FEEDBAG,
 					SubGroup:  oscar.FeedbagReply,
 				},
-				snacOut: oscar.SNAC_0x13_0x06_FeedbagReply{
+				SnacOut: oscar.SNAC_0x13_0x06_FeedbagReply{
 					Version: 0,
 					Items: []oscar.FeedbagItem{
 						{
@@ -184,12 +183,12 @@ func TestQueryIfModifiedHandler(t *testing.T) {
 			inputSNAC: oscar.SNAC_0x13_0x05_FeedbagQueryIfModified{
 				LastUpdate: uint32(time.UnixMilli(200000).Unix()),
 			},
-			expectOutput: XMessage{
-				snacFrame: oscar.SnacFrame{
+			expectOutput: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.FEEDBAG,
 					SubGroup:  oscar.FeedbagReplyNotModified,
 				},
-				snacOut: oscar.SNAC_0x13_0x05_FeedbagQueryIfModified{
+				SnacOut: oscar.SNAC_0x13_0x05_FeedbagQueryIfModified{
 					LastUpdate: uint32(time.UnixMilli(100000).Unix()),
 					Count:      2,
 				},
@@ -214,9 +213,7 @@ func TestQueryIfModifiedHandler(t *testing.T) {
 			//
 			// send input SNAC
 			//
-			senderSession := &Session{
-				ScreenName: tc.screenName,
-			}
+			senderSession := newTestSession(tc.screenName)
 			svc := FeedbagService{
 				fm: fm,
 			}
@@ -231,34 +228,31 @@ func TestQueryIfModifiedHandler(t *testing.T) {
 }
 
 func TestInsertItemHandler(t *testing.T) {
-	defaultSess := &Session{}
 	cases := []struct {
 		// name is the unit test name
 		name string
 		// userSession is the session of the user managing buddy list
-		userSession *Session
+		userSession *user.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 *Session
+			sess *user.Session
 			err  error
 		}
 		// clientResponse is the message returned to the client
-		clientResponse XMessage
+		clientResponse oscar.XMessage
 		// buddyMessages are events forwarded to buddy clients
 		buddyMessages []struct {
 			user string
-			msg  XMessage
+			msg  oscar.XMessage
 		}
 	}{
 		{
-			name: "user adds 2 online buddies, expect OK response",
-			userSession: &Session{
-				ScreenName: "user_screen_name",
-			},
+			name:        "user adds 2 online buddies, expect OK response",
+			userSession: newTestSession("user_screen_name"),
 			inputSNAC: oscar.SNAC_0x13_0x08_FeedbagInsertItem{
 				Items: []oscar.FeedbagItem{
 					{
@@ -272,44 +266,44 @@ func TestInsertItemHandler(t *testing.T) {
 				},
 			},
 			screenNameLookups: map[string]struct {
-				sess *Session
+				sess *user.Session
 				err  error
 			}{
 				"user_screen_name": {
-					sess: &Session{ScreenName: "user_screen_name"},
+					sess: newTestSession("user_screen_name", sessOptCannedSignonTime),
 				},
 				"buddy_1_online": {
-					sess: &Session{ScreenName: "buddy_1_online"},
+					sess: newTestSession("buddy_1_online", sessOptCannedSignonTime),
 				},
 				"buddy_2_online": {
-					sess: &Session{ScreenName: "buddy_2_online"},
+					sess: newTestSession("buddy_2_online", sessOptCannedSignonTime),
 				},
 			},
-			clientResponse: XMessage{
-				snacFrame: oscar.SnacFrame{
+			clientResponse: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.FEEDBAG,
 					SubGroup:  oscar.FeedbagStatus,
 				},
-				snacOut: oscar.SNAC_0x13_0x0E_FeedbagStatus{
+				SnacOut: oscar.SNAC_0x13_0x0E_FeedbagStatus{
 					Results: []uint16{0x0000, 0x0000},
 				},
 			},
 			buddyMessages: []struct {
 				user string
-				msg  XMessage
+				msg  oscar.XMessage
 			}{
 				{
 					user: "user_screen_name",
-					msg: XMessage{
-						snacFrame: oscar.SnacFrame{
+					msg: oscar.XMessage{
+						SnacFrame: oscar.SnacFrame{
 							FoodGroup: oscar.BUDDY,
 							SubGroup:  oscar.BuddyArrived,
 						},
-						snacOut: oscar.SNAC_0x03_0x0A_BuddyArrived{
+						SnacOut: oscar.SNAC_0x03_0x0A_BuddyArrived{
 							TLVUserInfo: oscar.TLVUserInfo{
 								ScreenName: "buddy_1_online",
 								TLVBlock: oscar.TLVBlock{
-									TLVList: defaultSess.GetUserInfo(),
+									TLVList: newTestSession("", sessOptCannedSignonTime).UserInfo(),
 								},
 							},
 						},
@@ -317,16 +311,16 @@ func TestInsertItemHandler(t *testing.T) {
 				},
 				{
 					user: "user_screen_name",
-					msg: XMessage{
-						snacFrame: oscar.SnacFrame{
+					msg: oscar.XMessage{
+						SnacFrame: oscar.SnacFrame{
 							FoodGroup: oscar.BUDDY,
 							SubGroup:  oscar.BuddyArrived,
 						},
-						snacOut: oscar.SNAC_0x03_0x0A_BuddyArrived{
+						SnacOut: oscar.SNAC_0x03_0x0A_BuddyArrived{
 							TLVUserInfo: oscar.TLVUserInfo{
 								ScreenName: "buddy_2_online",
 								TLVBlock: oscar.TLVBlock{
-									TLVList: defaultSess.GetUserInfo(),
+									TLVList: newTestSession("", sessOptCannedSignonTime).UserInfo(),
 								},
 							},
 						},
@@ -335,10 +329,8 @@ func TestInsertItemHandler(t *testing.T) {
 			},
 		},
 		{
-			name: "user adds an offline buddy, expect OK response and 0 buddy arrived events",
-			userSession: &Session{
-				ScreenName: "user_screen_name",
-			},
+			name:        "user adds an offline buddy, expect OK response and 0 buddy arrived events",
+			userSession: newTestSession("user_screen_name"),
 			inputSNAC: oscar.SNAC_0x13_0x08_FeedbagInsertItem{
 				Items: []oscar.FeedbagItem{
 					{
@@ -348,28 +340,26 @@ func TestInsertItemHandler(t *testing.T) {
 				},
 			},
 			screenNameLookups: map[string]struct {
-				sess *Session
+				sess *user.Session
 				err  error
 			}{
 				"buddy_offline": {
 					err: ErrSessNotFound,
 				},
 			},
-			clientResponse: XMessage{
-				snacFrame: oscar.SnacFrame{
+			clientResponse: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.FEEDBAG,
 					SubGroup:  oscar.FeedbagStatus,
 				},
-				snacOut: oscar.SNAC_0x13_0x0E_FeedbagStatus{
+				SnacOut: oscar.SNAC_0x13_0x0E_FeedbagStatus{
 					Results: []uint16{0x0000},
 				},
 			},
 		},
 		{
-			name: "users adds an invisible buddy, expect OK response and 0 buddy arrived events",
-			userSession: &Session{
-				ScreenName: "user_screen_name",
-			},
+			name:        "users adds an invisible buddy, expect OK response and 0 buddy arrived events",
+			userSession: newTestSession("user_screen_name"),
 			inputSNAC: oscar.SNAC_0x13_0x08_FeedbagInsertItem{
 				Items: []oscar.FeedbagItem{
 					{
@@ -379,22 +369,19 @@ func TestInsertItemHandler(t *testing.T) {
 				},
 			},
 			screenNameLookups: map[string]struct {
-				sess *Session
+				sess *user.Session
 				err  error
 			}{
 				"invisible_buddy_online": {
-					sess: &Session{
-						ScreenName: "invisible_buddy_online",
-						invisible:  true,
-					},
+					sess: newTestSession("invisible_buddy_online", sessOptInvisible),
 				},
 			},
-			clientResponse: XMessage{
-				snacFrame: oscar.SnacFrame{
+			clientResponse: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.FEEDBAG,
 					SubGroup:  oscar.FeedbagStatus,
 				},
-				snacOut: oscar.SNAC_0x13_0x0E_FeedbagStatus{
+				SnacOut: oscar.SNAC_0x13_0x0E_FeedbagStatus{
 					Results: []uint16{0x0000},
 				},
 			},
@@ -402,9 +389,7 @@ func TestInsertItemHandler(t *testing.T) {
 		{
 			name: "user blocks buddy currently online, expect OK response, buddy departed event client, 1 buddy " +
 				"departed event sent to buddy",
-			userSession: &Session{
-				ScreenName: "user_screen_name",
-			},
+			userSession: newTestSession("user_screen_name"),
 			inputSNAC: oscar.SNAC_0x13_0x08_FeedbagInsertItem{
 				Items: []oscar.FeedbagItem{
 					{
@@ -414,28 +399,28 @@ func TestInsertItemHandler(t *testing.T) {
 				},
 			},
 			screenNameLookups: map[string]struct {
-				sess *Session
+				sess *user.Session
 				err  error
 			}{
 				"user_screen_name": {
-					sess: &Session{ScreenName: "user_screen_name"},
+					sess: newTestSession("user_screen_name"),
 				},
 				"buddy_1": {
-					sess: &Session{ScreenName: "buddy_1"},
+					sess: newTestSession("buddy_1"),
 				},
 			},
 			buddyMessages: []struct {
 				user string
-				msg  XMessage
+				msg  oscar.XMessage
 			}{
 				{
 					user: "buddy_1",
-					msg: XMessage{
-						snacFrame: oscar.SnacFrame{
+					msg: oscar.XMessage{
+						SnacFrame: oscar.SnacFrame{
 							FoodGroup: oscar.BUDDY,
 							SubGroup:  oscar.BuddyDeparted,
 						},
-						snacOut: oscar.SNAC_0x03_0x0B_BuddyDeparted{
+						SnacOut: oscar.SNAC_0x03_0x0B_BuddyDeparted{
 							TLVUserInfo: oscar.TLVUserInfo{
 								ScreenName:   "user_screen_name",
 								WarningLevel: 0,
@@ -445,12 +430,12 @@ func TestInsertItemHandler(t *testing.T) {
 				},
 				{
 					user: "user_screen_name",
-					msg: XMessage{
-						snacFrame: oscar.SnacFrame{
+					msg: oscar.XMessage{
+						SnacFrame: oscar.SnacFrame{
 							FoodGroup: oscar.BUDDY,
 							SubGroup:  oscar.BuddyDeparted,
 						},
-						snacOut: oscar.SNAC_0x03_0x0B_BuddyDeparted{
+						SnacOut: oscar.SNAC_0x03_0x0B_BuddyDeparted{
 							TLVUserInfo: oscar.TLVUserInfo{
 								ScreenName:   "buddy_1",
 								WarningLevel: 0,
@@ -459,21 +444,19 @@ func TestInsertItemHandler(t *testing.T) {
 					},
 				},
 			},
-			clientResponse: XMessage{
-				snacFrame: oscar.SnacFrame{
+			clientResponse: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.FEEDBAG,
 					SubGroup:  oscar.FeedbagStatus,
 				},
-				snacOut: oscar.SNAC_0x13_0x0E_FeedbagStatus{
+				SnacOut: oscar.SNAC_0x13_0x0E_FeedbagStatus{
 					Results: []uint16{0x0000},
 				},
 			},
 		},
 		{
-			name: "user blocks buddy currently offline, expect OK response and no buddy departed events",
-			userSession: &Session{
-				ScreenName: "user_screen_name",
-			},
+			name:        "user blocks buddy currently offline, expect OK response and no buddy departed events",
+			userSession: newTestSession("user_screen_name"),
 			inputSNAC: oscar.SNAC_0x13_0x08_FeedbagInsertItem{
 				Items: []oscar.FeedbagItem{
 					{
@@ -483,31 +466,29 @@ func TestInsertItemHandler(t *testing.T) {
 				},
 			},
 			screenNameLookups: map[string]struct {
-				sess *Session
+				sess *user.Session
 				err  error
 			}{
 				"user_screen_name": {
-					sess: &Session{ScreenName: "user_screen_name"},
+					sess: newTestSession("user_screen_name"),
 				},
 				"buddy_1": {
 					err: ErrSessNotFound,
 				},
 			},
-			clientResponse: XMessage{
-				snacFrame: oscar.SnacFrame{
+			clientResponse: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.FEEDBAG,
 					SubGroup:  oscar.FeedbagStatus,
 				},
-				snacOut: oscar.SNAC_0x13_0x0E_FeedbagStatus{
+				SnacOut: oscar.SNAC_0x13_0x0E_FeedbagStatus{
 					Results: []uint16{0x0000},
 				},
 			},
 		},
 		{
-			name: "user tries to block themselves, expect feedback error",
-			userSession: &Session{
-				ScreenName: "user_screen_name",
-			},
+			name:        "user tries to block themselves, expect feedback error",
+			userSession: newTestSession("user_screen_name"),
 			inputSNAC: oscar.SNAC_0x13_0x08_FeedbagInsertItem{
 				Items: []oscar.FeedbagItem{
 					{
@@ -516,12 +497,12 @@ func TestInsertItemHandler(t *testing.T) {
 					},
 				},
 			},
-			clientResponse: XMessage{
-				snacFrame: oscar.SnacFrame{
+			clientResponse: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.FEEDBAG,
 					SubGroup:  oscar.FeedbagErr,
 				},
-				snacOut: oscar.SnacError{
+				SnacOut: oscar.SnacError{
 					Code: oscar.ErrorCodeNotSupportedByHost,
 				},
 			},
@@ -535,11 +516,11 @@ func TestInsertItemHandler(t *testing.T) {
 			//
 			fm := NewMockFeedbagManager(t)
 			fm.EXPECT().
-				Upsert(tc.userSession.ScreenName, tc.inputSNAC.Items).
+				Upsert(tc.userSession.ScreenName(), tc.inputSNAC.Items).
 				Return(nil).
 				Maybe()
 			fm.EXPECT().
-				Buddies(tc.userSession.ScreenName).
+				Buddies(tc.userSession.ScreenName()).
 				Return([]string{}, nil).
 				Maybe()
 			sm := NewMockSessionManager(t)
@@ -576,9 +557,9 @@ func TestFeedbagRouter_RouteFeedbag(t *testing.T) {
 		// name is the unit test name
 		name string
 		// input is the request payload
-		input XMessage
+		input oscar.XMessage
 		// output is the response payload
-		output XMessage
+		output oscar.XMessage
 		// handlerErr is the mocked handler error response
 		handlerErr error
 		// expectErr is the expected error returned by the router
@@ -586,12 +567,12 @@ func TestFeedbagRouter_RouteFeedbag(t *testing.T) {
 	}{
 		{
 			name: "receive FeedbagRightsQuery, return FeedbagRightsReply",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.FEEDBAG,
 					SubGroup:  oscar.FeedbagRightsQuery,
 				},
-				snacOut: oscar.SNAC_0x13_0x02_FeedbagRightsQuery{
+				SnacOut: oscar.SNAC_0x13_0x02_FeedbagRightsQuery{
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							{
@@ -602,12 +583,12 @@ func TestFeedbagRouter_RouteFeedbag(t *testing.T) {
 					},
 				},
 			},
-			output: XMessage{
-				snacFrame: oscar.SnacFrame{
+			output: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.FEEDBAG,
 					SubGroup:  oscar.FeedbagRightsReply,
 				},
-				snacOut: oscar.SNAC_0x13_0x03_FeedbagRightsReply{
+				SnacOut: oscar.SNAC_0x13_0x03_FeedbagRightsReply{
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							{
@@ -621,12 +602,12 @@ func TestFeedbagRouter_RouteFeedbag(t *testing.T) {
 		},
 		{
 			name: "receive FeedbagQuery, return FeedbagReply",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.FEEDBAG,
 					SubGroup:  oscar.FeedbagQuery,
 				},
-				snacOut: oscar.SNAC_0x13_0x02_FeedbagRightsQuery{
+				SnacOut: oscar.SNAC_0x13_0x02_FeedbagRightsQuery{
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							{
@@ -637,56 +618,56 @@ func TestFeedbagRouter_RouteFeedbag(t *testing.T) {
 					},
 				},
 			},
-			output: XMessage{
-				snacFrame: oscar.SnacFrame{
+			output: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.FEEDBAG,
 					SubGroup:  oscar.FeedbagReply,
 				},
-				snacOut: oscar.SNAC_0x13_0x06_FeedbagReply{
+				SnacOut: oscar.SNAC_0x13_0x06_FeedbagReply{
 					Version: 4,
 				},
 			},
 		},
 		{
 			name: "receive FeedbagQueryIfModified, return FeedbagRightsReply",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.FEEDBAG,
 					SubGroup:  oscar.FeedbagQueryIfModified,
 				},
-				snacOut: oscar.SNAC_0x13_0x05_FeedbagQueryIfModified{
+				SnacOut: oscar.SNAC_0x13_0x05_FeedbagQueryIfModified{
 					LastUpdate: 1234,
 				},
 			},
-			output: XMessage{
-				snacFrame: oscar.SnacFrame{
+			output: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.FEEDBAG,
 					SubGroup:  oscar.FeedbagReply,
 				},
-				snacOut: oscar.SNAC_0x13_0x06_FeedbagReply{
+				SnacOut: oscar.SNAC_0x13_0x06_FeedbagReply{
 					LastUpdate: 1234,
 				},
 			},
 		},
 		{
 			name: "receive FeedbagUse, return no response",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.FEEDBAG,
 					SubGroup:  oscar.FeedbagUse,
 				},
-				snacOut: struct{}{},
+				SnacOut: struct{}{},
 			},
-			output: XMessage{},
+			output: oscar.XMessage{},
 		},
 		{
 			name: "receive FeedbagInsertItem, return BuddyArrived and FeedbagStatus",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.FEEDBAG,
 					SubGroup:  oscar.FeedbagInsertItem,
 				},
-				snacOut: oscar.SNAC_0x13_0x08_FeedbagInsertItem{
+				SnacOut: oscar.SNAC_0x13_0x08_FeedbagInsertItem{
 					Items: []oscar.FeedbagItem{
 						{
 							Name: "my-item",
@@ -694,24 +675,24 @@ func TestFeedbagRouter_RouteFeedbag(t *testing.T) {
 					},
 				},
 			},
-			output: XMessage{
-				snacFrame: oscar.SnacFrame{
+			output: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.FEEDBAG,
 					SubGroup:  oscar.FeedbagStatus,
 				},
-				snacOut: oscar.SNAC_0x13_0x0E_FeedbagStatus{
+				SnacOut: oscar.SNAC_0x13_0x0E_FeedbagStatus{
 					Results: []uint16{1234},
 				},
 			},
 		},
 		{
 			name: "receive FeedbagUpdateItem, return BuddyArrived and FeedbagStatus",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.FEEDBAG,
 					SubGroup:  oscar.FeedbagUpdateItem,
 				},
-				snacOut: oscar.SNAC_0x13_0x09_FeedbagUpdateItem{
+				SnacOut: oscar.SNAC_0x13_0x09_FeedbagUpdateItem{
 					Items: []oscar.FeedbagItem{
 						{
 							Name: "my-item",
@@ -719,24 +700,24 @@ func TestFeedbagRouter_RouteFeedbag(t *testing.T) {
 					},
 				},
 			},
-			output: XMessage{
-				snacFrame: oscar.SnacFrame{
+			output: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.FEEDBAG,
 					SubGroup:  oscar.FeedbagStatus,
 				},
-				snacOut: oscar.SNAC_0x13_0x0E_FeedbagStatus{
+				SnacOut: oscar.SNAC_0x13_0x0E_FeedbagStatus{
 					Results: []uint16{1234},
 				},
 			},
 		},
 		{
 			name: "receive FeedbagDeleteItem, return FeedbagStatus",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.FEEDBAG,
 					SubGroup:  oscar.FeedbagDeleteItem,
 				},
-				snacOut: oscar.SNAC_0x13_0x0A_FeedbagDeleteItem{
+				SnacOut: oscar.SNAC_0x13_0x0A_FeedbagDeleteItem{
 					Items: []oscar.FeedbagItem{
 						{
 							Name: "my-item",
@@ -744,24 +725,24 @@ func TestFeedbagRouter_RouteFeedbag(t *testing.T) {
 					},
 				},
 			},
-			output: XMessage{
-				snacFrame: oscar.SnacFrame{
+			output: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.FEEDBAG,
 					SubGroup:  oscar.FeedbagStatus,
 				},
-				snacOut: oscar.SNAC_0x13_0x0E_FeedbagStatus{
+				SnacOut: oscar.SNAC_0x13_0x0E_FeedbagStatus{
 					Results: []uint16{1234},
 				},
 			},
 		},
 		{
 			name: "receive FeedbagStartCluster, return no response",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.FEEDBAG,
 					SubGroup:  oscar.FeedbagStartCluster,
 				},
-				snacOut: oscar.SNAC_0x13_0x11_FeedbagStartCluster{
+				SnacOut: oscar.SNAC_0x13_0x11_FeedbagStartCluster{
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							{
@@ -772,29 +753,29 @@ func TestFeedbagRouter_RouteFeedbag(t *testing.T) {
 					},
 				},
 			},
-			output: XMessage{},
+			output: oscar.XMessage{},
 		},
 		{
 			name: "receive FeedbagEndCluster, return no response",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.FEEDBAG,
 					SubGroup:  oscar.FeedbagEndCluster,
 				},
-				snacOut: struct{}{},
+				SnacOut: struct{}{},
 			},
-			output: XMessage{},
+			output: oscar.XMessage{},
 		},
 		{
 			name: "receive FeedbagDeleteUser, return ErrUnsupportedSubGroup",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.FEEDBAG,
 					SubGroup:  oscar.FeedbagDeleteUser,
 				},
-				snacOut: struct{}{},
+				SnacOut: struct{}{},
 			},
-			output:    XMessage{},
+			output:    oscar.XMessage{},
 			expectErr: ErrUnsupportedSubGroup,
 		},
 	}
@@ -803,7 +784,7 @@ func TestFeedbagRouter_RouteFeedbag(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			svc := NewMockFeedbagHandler(t)
 			svc.EXPECT().
-				DeleteItemHandler(mock.Anything, mock.Anything, tc.input.snacOut).
+				DeleteItemHandler(mock.Anything, mock.Anything, tc.input.SnacOut).
 				Return(tc.output, tc.handlerErr).
 				Maybe()
 			svc.EXPECT().
@@ -811,7 +792,7 @@ func TestFeedbagRouter_RouteFeedbag(t *testing.T) {
 				Return(tc.output, tc.handlerErr).
 				Maybe()
 			svc.EXPECT().
-				QueryIfModifiedHandler(mock.Anything, mock.Anything, tc.input.snacOut).
+				QueryIfModifiedHandler(mock.Anything, mock.Anything, tc.input.SnacOut).
 				Return(tc.output, tc.handlerErr).
 				Maybe()
 			svc.EXPECT().
@@ -819,15 +800,15 @@ func TestFeedbagRouter_RouteFeedbag(t *testing.T) {
 				Return(tc.output).
 				Maybe()
 			svc.EXPECT().
-				InsertItemHandler(mock.Anything, mock.Anything, tc.input.snacOut).
+				InsertItemHandler(mock.Anything, mock.Anything, tc.input.SnacOut).
 				Return(tc.output, tc.handlerErr).
 				Maybe()
 			svc.EXPECT().
-				UpdateItemHandler(mock.Anything, mock.Anything, tc.input.snacOut).
+				UpdateItemHandler(mock.Anything, mock.Anything, tc.input.SnacOut).
 				Return(tc.output, tc.handlerErr).
 				Maybe()
 			svc.EXPECT().
-				StartClusterHandler(mock.Anything, tc.input.snacOut).
+				StartClusterHandler(mock.Anything, tc.input.SnacOut).
 				Maybe()
 
 			router := FeedbagRouter{
@@ -838,18 +819,18 @@ func TestFeedbagRouter_RouteFeedbag(t *testing.T) {
 			}
 
 			bufIn := &bytes.Buffer{}
-			assert.NoError(t, oscar.Marshal(tc.input.snacOut, bufIn))
+			assert.NoError(t, oscar.Marshal(tc.input.SnacOut, bufIn))
 
 			bufOut := &bytes.Buffer{}
 			seq := uint32(0)
 
-			err := router.RouteFeedbag(nil, nil, tc.input.snacFrame, bufIn, bufOut, &seq)
+			err := router.RouteFeedbag(nil, nil, tc.input.SnacFrame, bufIn, bufOut, &seq)
 			assert.ErrorIs(t, err, tc.expectErr)
 			if tc.expectErr != nil {
 				return
 			}
 
-			if tc.output.snacFrame == (oscar.SnacFrame{}) {
+			if tc.output.SnacFrame == (oscar.SnacFrame{}) {
 				return
 			}
 
@@ -867,11 +848,11 @@ func TestFeedbagRouter_RouteFeedbag(t *testing.T) {
 			// verify the SNAC frame
 			snacFrame := oscar.SnacFrame{}
 			assert.NoError(t, oscar.Unmarshal(&snacFrame, flapBuf))
-			assert.Equal(t, tc.output.snacFrame, snacFrame)
+			assert.Equal(t, tc.output.SnacFrame, snacFrame)
 
 			// verify the SNAC message
 			snacBuf := &bytes.Buffer{}
-			assert.NoError(t, oscar.Marshal(tc.output.snacOut, snacBuf))
+			assert.NoError(t, oscar.Marshal(tc.output.SnacOut, snacBuf))
 			assert.Equal(t, snacBuf.Bytes(), flapBuf.Bytes())
 		})
 	}

+ 61 - 60
server/icbm.go

@@ -3,6 +3,7 @@ package server
 import (
 	"context"
 	"errors"
+	"github.com/mkaminski/goaim/user"
 	"io"
 	"log/slog"
 
@@ -15,10 +16,10 @@ const (
 )
 
 type ICBMHandler interface {
-	ChannelMsgToHostHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost) (*XMessage, error)
-	ClientEventHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x04_0x14_ICBMClientEvent) error
-	EvilRequestHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x04_0x08_ICBMEvilRequest) (XMessage, error)
-	ParameterQueryHandler(context.Context) XMessage
+	ChannelMsgToHostHandler(ctx context.Context, sess *user.Session, snacPayloadIn oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost) (*oscar.XMessage, error)
+	ClientEventHandler(ctx context.Context, sess *user.Session, snacPayloadIn oscar.SNAC_0x04_0x14_ICBMClientEvent) error
+	EvilRequestHandler(ctx context.Context, sess *user.Session, snacPayloadIn oscar.SNAC_0x04_0x08_ICBMEvilRequest) (oscar.XMessage, error)
+	ParameterQueryHandler(context.Context) oscar.XMessage
 }
 
 func NewICBMRouter(logger *slog.Logger, sm SessionManager, fm FeedbagManager) ICBMRouter {
@@ -38,7 +39,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 *user.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{}
@@ -46,8 +47,8 @@ func (rt *ICBMRouter) RouteICBM(ctx context.Context, sess *Session, SNACFrame os
 		return oscar.Unmarshal(&inSNAC, r)
 	case oscar.ICBMParameterQuery:
 		outSNAC := rt.ParameterQueryHandler(ctx)
-		rt.logRequestAndResponse(ctx, SNACFrame, outSNAC, outSNAC.snacFrame, outSNAC.snacOut)
-		return writeOutSNAC(SNACFrame, outSNAC.snacFrame, outSNAC.snacOut, sequence, w)
+		rt.logRequestAndResponse(ctx, SNACFrame, outSNAC, outSNAC.SnacFrame, outSNAC.SnacOut)
+		return writeOutSNAC(SNACFrame, outSNAC.SnacFrame, outSNAC.SnacOut, sequence, w)
 	case oscar.ICBMChannelMsgToHost:
 		inSNAC := oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost{}
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
@@ -58,8 +59,8 @@ func (rt *ICBMRouter) RouteICBM(ctx context.Context, sess *Session, SNACFrame os
 			return err
 		}
 		rt.Logger.InfoContext(ctx, "user sent an IM", slog.String("recipient", inSNAC.ScreenName))
-		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.snacFrame, outSNAC.snacOut)
-		return writeOutSNAC(SNACFrame, outSNAC.snacFrame, outSNAC.snacOut, sequence, w)
+		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.SnacFrame, outSNAC.SnacOut)
+		return writeOutSNAC(SNACFrame, outSNAC.SnacFrame, outSNAC.SnacOut, sequence, w)
 	case oscar.ICBMEvilRequest:
 		inSNAC := oscar.SNAC_0x04_0x08_ICBMEvilRequest{}
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
@@ -69,8 +70,8 @@ func (rt *ICBMRouter) RouteICBM(ctx context.Context, sess *Session, SNACFrame os
 		if err != nil {
 			return err
 		}
-		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.snacFrame, outSNAC.snacOut)
-		return writeOutSNAC(SNACFrame, outSNAC.snacFrame, outSNAC.snacOut, sequence, w)
+		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.SnacFrame, outSNAC.SnacOut)
+		return writeOutSNAC(SNACFrame, outSNAC.SnacFrame, outSNAC.SnacOut, sequence, w)
 	case oscar.ICBMClientErr:
 		inSNAC := oscar.SNAC_0x04_0x0B_ICBMClientErr{}
 		rt.logRequest(ctx, SNACFrame, inSNAC)
@@ -92,13 +93,13 @@ type ICBMService struct {
 	fm FeedbagManager
 }
 
-func (s ICBMService) ParameterQueryHandler(context.Context) XMessage {
-	return XMessage{
-		snacFrame: oscar.SnacFrame{
+func (s ICBMService) ParameterQueryHandler(context.Context) oscar.XMessage {
+	return oscar.XMessage{
+		SnacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.ICBM,
 			SubGroup:  oscar.ICBMParameterReply,
 		},
-		snacOut: oscar.SNAC_0x04_0x05_ICBMParameterReply{
+		SnacOut: oscar.SNAC_0x04_0x05_ICBMParameterReply{
 			MaxSlots:             100,
 			ICBMFlags:            3,
 			MaxIncomingICBMLen:   512,
@@ -109,8 +110,8 @@ func (s ICBMService) ParameterQueryHandler(context.Context) XMessage {
 	}
 }
 
-func (s ICBMService) ChannelMsgToHostHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost) (*XMessage, error) {
-	blocked, err := s.fm.Blocked(sess.ScreenName, snacPayloadIn.ScreenName)
+func (s ICBMService) ChannelMsgToHostHandler(ctx context.Context, sess *user.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
 	}
@@ -120,12 +121,12 @@ func (s ICBMService) ChannelMsgToHostHandler(ctx context.Context, sess *Session,
 		if blocked == BlockedA {
 			code = oscar.ErrorCodeInLocalPermitDeny
 		}
-		return &XMessage{
-			snacFrame: oscar.SnacFrame{
+		return &oscar.XMessage{
+			SnacFrame: oscar.SnacFrame{
 				FoodGroup: oscar.ICBM,
 				SubGroup:  oscar.ICBMErr,
 			},
-			snacOut: oscar.SnacError{
+			SnacOut: oscar.SnacError{
 				Code: code,
 			},
 		}, nil
@@ -134,12 +135,12 @@ func (s ICBMService) ChannelMsgToHostHandler(ctx context.Context, sess *Session,
 	recipSess, err := s.sm.RetrieveByScreenName(snacPayloadIn.ScreenName)
 	switch {
 	case errors.Is(err, ErrSessNotFound):
-		return &XMessage{
-			snacFrame: oscar.SnacFrame{
+		return &oscar.XMessage{
+			SnacFrame: oscar.SnacFrame{
 				FoodGroup: oscar.ICBM,
 				SubGroup:  oscar.ICBMErr,
 			},
-			snacOut: oscar.SnacError{
+			SnacOut: oscar.SnacError{
 				Code: oscar.ErrorCodeNotLoggedOn,
 			},
 		}, nil
@@ -151,8 +152,8 @@ func (s ICBMService) ChannelMsgToHostHandler(ctx context.Context, sess *Session,
 		Cookie:    snacPayloadIn.Cookie,
 		ChannelID: snacPayloadIn.ChannelID,
 		TLVUserInfo: oscar.TLVUserInfo{
-			ScreenName:   sess.ScreenName,
-			WarningLevel: sess.GetWarning(),
+			ScreenName:   sess.ScreenName(),
+			WarningLevel: sess.Warning(),
 		},
 		TLVRestBlock: oscar.TLVRestBlock{
 			TLVList: oscar.TLVList{
@@ -168,12 +169,12 @@ func (s ICBMService) ChannelMsgToHostHandler(ctx context.Context, sess *Session,
 	// far as I can tell.
 	clientIM.AddTLVList(snacPayloadIn.TLVRestBlock.TLVList)
 
-	s.sm.SendToScreenName(ctx, recipSess.ScreenName, XMessage{
-		snacFrame: oscar.SnacFrame{
+	s.sm.SendToScreenName(ctx, recipSess.ScreenName(), oscar.XMessage{
+		SnacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.ICBM,
 			SubGroup:  oscar.ICBMChannelMsgToclient,
 		},
-		snacOut: clientIM,
+		SnacOut: clientIM,
 	})
 
 	if _, requestedConfirmation := snacPayloadIn.TLVRestBlock.GetSlice(oscar.ICBMTLVTagRequestHostAck); !requestedConfirmation {
@@ -182,12 +183,12 @@ func (s ICBMService) ChannelMsgToHostHandler(ctx context.Context, sess *Session,
 	}
 
 	// ack message back to sender
-	return &XMessage{
-		snacFrame: oscar.SnacFrame{
+	return &oscar.XMessage{
+		SnacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.ICBM,
 			SubGroup:  oscar.ICBMHostAck,
 		},
-		snacOut: oscar.SNAC_0x04_0x0C_ICBMHostAck{
+		SnacOut: oscar.SNAC_0x04_0x0C_ICBMHostAck{
 			Cookie:     snacPayloadIn.Cookie,
 			ChannelID:  snacPayloadIn.ChannelID,
 			ScreenName: snacPayloadIn.ScreenName,
@@ -195,8 +196,8 @@ func (s ICBMService) ChannelMsgToHostHandler(ctx context.Context, sess *Session,
 	}, nil
 }
 
-func (s ICBMService) ClientEventHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x04_0x14_ICBMClientEvent) error {
-	blocked, err := s.fm.Blocked(sess.ScreenName, snacPayloadIn.ScreenName)
+func (s ICBMService) ClientEventHandler(ctx context.Context, sess *user.Session, snacPayloadIn oscar.SNAC_0x04_0x14_ICBMClientEvent) error {
+	blocked, err := s.fm.Blocked(sess.ScreenName(), snacPayloadIn.ScreenName)
 
 	switch {
 	case err != nil:
@@ -204,15 +205,15 @@ func (s ICBMService) ClientEventHandler(ctx context.Context, sess *Session, snac
 	case blocked != BlockedNo:
 		return nil
 	default:
-		s.sm.SendToScreenName(ctx, snacPayloadIn.ScreenName, XMessage{
-			snacFrame: oscar.SnacFrame{
+		s.sm.SendToScreenName(ctx, snacPayloadIn.ScreenName, oscar.XMessage{
+			SnacFrame: oscar.SnacFrame{
 				FoodGroup: oscar.ICBM,
 				SubGroup:  oscar.ICBMClientEvent,
 			},
-			snacOut: oscar.SNAC_0x04_0x14_ICBMClientEvent{
+			SnacOut: oscar.SNAC_0x04_0x14_ICBMClientEvent{
 				Cookie:     snacPayloadIn.Cookie,
 				ChannelID:  snacPayloadIn.ChannelID,
-				ScreenName: sess.ScreenName,
+				ScreenName: sess.ScreenName(),
 				Event:      snacPayloadIn.Event,
 			},
 		})
@@ -220,32 +221,32 @@ func (s ICBMService) ClientEventHandler(ctx context.Context, sess *Session, snac
 	}
 }
 
-func (s ICBMService) EvilRequestHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x04_0x08_ICBMEvilRequest) (XMessage, error) {
+func (s ICBMService) EvilRequestHandler(ctx context.Context, sess *user.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 {
-		return XMessage{
-			snacFrame: oscar.SnacFrame{
+	if snacPayloadIn.ScreenName == sess.ScreenName() {
+		return oscar.XMessage{
+			SnacFrame: oscar.SnacFrame{
 				FoodGroup: oscar.ICBM,
 				SubGroup:  oscar.ICBMErr,
 			},
-			snacOut: oscar.SnacError{
+			SnacOut: oscar.SnacError{
 				Code: oscar.ErrorCodeNotSupportedByHost,
 			},
 		}, nil
 	}
 
-	blocked, err := s.fm.Blocked(sess.ScreenName, snacPayloadIn.ScreenName)
+	blocked, err := s.fm.Blocked(sess.ScreenName(), snacPayloadIn.ScreenName)
 	if err != nil {
-		return XMessage{}, nil
+		return oscar.XMessage{}, nil
 	}
 	if blocked != BlockedNo {
-		return XMessage{
-			snacFrame: oscar.SnacFrame{
+		return oscar.XMessage{
+			SnacFrame: oscar.SnacFrame{
 				FoodGroup: oscar.ICBM,
 				SubGroup:  oscar.ICBMErr,
 			},
-			snacOut: oscar.SnacError{
+			SnacOut: oscar.SnacError{
 				Code: oscar.ErrorCodeNotLoggedOn,
 			},
 		}, nil
@@ -253,7 +254,7 @@ func (s ICBMService) EvilRequestHandler(ctx context.Context, sess *Session, snac
 
 	recipSess, err := s.sm.RetrieveByScreenName(snacPayloadIn.ScreenName)
 	if err != nil {
-		return XMessage{}, nil
+		return oscar.XMessage{}, nil
 	}
 
 	increase := evilDelta
@@ -265,38 +266,38 @@ func (s ICBMService) EvilRequestHandler(ctx context.Context, sess *Session, snac
 	var notif any
 	if snacPayloadIn.SendAs == 0 {
 		notif = oscar.SNAC_0x01_0x10_OServiceEvilNotification{
-			NewEvil: recipSess.GetWarning(),
+			NewEvil: recipSess.Warning(),
 			TLVUserInfo: oscar.TLVUserInfo{
-				ScreenName:   sess.ScreenName,
-				WarningLevel: recipSess.GetWarning(),
+				ScreenName:   sess.ScreenName(),
+				WarningLevel: recipSess.Warning(),
 			},
 		}
 	} else {
 		notif = oscar.SNAC_0x01_0x10_OServiceEvilNotificationAnon{
-			NewEvil: recipSess.GetWarning(),
+			NewEvil: recipSess.Warning(),
 		}
 	}
 
-	s.sm.SendToScreenName(ctx, recipSess.ScreenName, XMessage{
-		snacFrame: oscar.SnacFrame{
+	s.sm.SendToScreenName(ctx, recipSess.ScreenName(), oscar.XMessage{
+		SnacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.OSERVICE,
 			SubGroup:  oscar.OServiceEvilNotification,
 		},
-		snacOut: notif,
+		SnacOut: notif,
 	})
 
 	if err := BroadcastArrival(ctx, recipSess, s.sm, s.fm); err != nil {
-		return XMessage{}, nil
+		return oscar.XMessage{}, nil
 	}
 
-	return XMessage{
-		snacFrame: oscar.SnacFrame{
+	return oscar.XMessage{
+		SnacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.ICBM,
 			SubGroup:  oscar.ICBMEvilReply,
 		},
-		snacOut: oscar.SNAC_0x04_0x09_ICBMEvilReply{
+		SnacOut: oscar.SNAC_0x04_0x09_ICBMEvilReply{
 			EvilDeltaApplied: increase,
-			UpdatedEvilValue: recipSess.GetWarning(),
+			UpdatedEvilValue: recipSess.Warning(),
 		},
 	}, nil
 }

+ 36 - 34
server/icbm_mock.go

@@ -7,6 +7,8 @@ import (
 
 	oscar "github.com/mkaminski/goaim/oscar"
 	mock "github.com/stretchr/testify/mock"
+
+	user "github.com/mkaminski/goaim/user"
 )
 
 // MockICBMHandler is an autogenerated mock type for the ICBMHandler type
@@ -23,23 +25,23 @@ 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) (*XMessage, error) {
+func (_m *MockICBMHandler) ChannelMsgToHostHandler(ctx context.Context, sess *user.Session, snacPayloadIn oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost) (*oscar.XMessage, error) {
 	ret := _m.Called(ctx, sess, snacPayloadIn)
 
-	var r0 *XMessage
+	var r0 *oscar.XMessage
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost) (*XMessage, error)); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *user.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) *XMessage); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *user.Session, oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost) *oscar.XMessage); ok {
 		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(*XMessage)
+			r0 = ret.Get(0).(*oscar.XMessage)
 		}
 	}
 
-	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, *user.Session, oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost) error); ok {
 		r1 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r1 = ret.Error(1)
@@ -55,35 +57,35 @@ type MockICBMHandler_ChannelMsgToHostHandler_Call struct {
 
 // ChannelMsgToHostHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *user.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 *user.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].(*user.Session), args[2].(oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost))
 	})
 	return _c
 }
 
-func (_c *MockICBMHandler_ChannelMsgToHostHandler_Call) Return(_a0 *XMessage, _a1 error) *MockICBMHandler_ChannelMsgToHostHandler_Call {
+func (_c *MockICBMHandler_ChannelMsgToHostHandler_Call) Return(_a0 *oscar.XMessage, _a1 error) *MockICBMHandler_ChannelMsgToHostHandler_Call {
 	_c.Call.Return(_a0, _a1)
 	return _c
 }
 
-func (_c *MockICBMHandler_ChannelMsgToHostHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost) (*XMessage, error)) *MockICBMHandler_ChannelMsgToHostHandler_Call {
+func (_c *MockICBMHandler_ChannelMsgToHostHandler_Call) RunAndReturn(run func(context.Context, *user.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 *user.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, *user.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 *user.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 *user.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].(*user.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, *user.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) (XMessage, error) {
+func (_m *MockICBMHandler) EvilRequestHandler(ctx context.Context, sess *user.Session, snacPayloadIn oscar.SNAC_0x04_0x08_ICBMEvilRequest) (oscar.XMessage, error) {
 	ret := _m.Called(ctx, sess, snacPayloadIn)
 
-	var r0 XMessage
+	var r0 oscar.XMessage
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x04_0x08_ICBMEvilRequest) (XMessage, error)); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *user.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) XMessage); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *user.Session, oscar.SNAC_0x04_0x08_ICBMEvilRequest) oscar.XMessage); ok {
 		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
-		r0 = ret.Get(0).(XMessage)
+		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, *user.Session, oscar.SNAC_0x04_0x08_ICBMEvilRequest) error); ok {
 		r1 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r1 = ret.Error(1)
@@ -153,38 +155,38 @@ type MockICBMHandler_EvilRequestHandler_Call struct {
 
 // EvilRequestHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *user.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 *user.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].(*user.Session), args[2].(oscar.SNAC_0x04_0x08_ICBMEvilRequest))
 	})
 	return _c
 }
 
-func (_c *MockICBMHandler_EvilRequestHandler_Call) Return(_a0 XMessage, _a1 error) *MockICBMHandler_EvilRequestHandler_Call {
+func (_c *MockICBMHandler_EvilRequestHandler_Call) Return(_a0 oscar.XMessage, _a1 error) *MockICBMHandler_EvilRequestHandler_Call {
 	_c.Call.Return(_a0, _a1)
 	return _c
 }
 
-func (_c *MockICBMHandler_EvilRequestHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x04_0x08_ICBMEvilRequest) (XMessage, error)) *MockICBMHandler_EvilRequestHandler_Call {
+func (_c *MockICBMHandler_EvilRequestHandler_Call) RunAndReturn(run func(context.Context, *user.Session, oscar.SNAC_0x04_0x08_ICBMEvilRequest) (oscar.XMessage, error)) *MockICBMHandler_EvilRequestHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }
 
 // ParameterQueryHandler provides a mock function with given fields: _a0
-func (_m *MockICBMHandler) ParameterQueryHandler(_a0 context.Context) XMessage {
+func (_m *MockICBMHandler) ParameterQueryHandler(_a0 context.Context) oscar.XMessage {
 	ret := _m.Called(_a0)
 
-	var r0 XMessage
-	if rf, ok := ret.Get(0).(func(context.Context) XMessage); ok {
+	var r0 oscar.XMessage
+	if rf, ok := ret.Get(0).(func(context.Context) oscar.XMessage); ok {
 		r0 = rf(_a0)
 	} else {
-		r0 = ret.Get(0).(XMessage)
+		r0 = ret.Get(0).(oscar.XMessage)
 	}
 
 	return r0
@@ -208,12 +210,12 @@ func (_c *MockICBMHandler_ParameterQueryHandler_Call) Run(run func(_a0 context.C
 	return _c
 }
 
-func (_c *MockICBMHandler_ParameterQueryHandler_Call) Return(_a0 XMessage) *MockICBMHandler_ParameterQueryHandler_Call {
+func (_c *MockICBMHandler_ParameterQueryHandler_Call) Return(_a0 oscar.XMessage) *MockICBMHandler_ParameterQueryHandler_Call {
 	_c.Call.Return(_a0)
 	return _c
 }
 
-func (_c *MockICBMHandler_ParameterQueryHandler_Call) RunAndReturn(run func(context.Context) XMessage) *MockICBMHandler_ParameterQueryHandler_Call {
+func (_c *MockICBMHandler_ParameterQueryHandler_Call) RunAndReturn(run func(context.Context) oscar.XMessage) *MockICBMHandler_ParameterQueryHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }

+ 143 - 190
server/icbm_test.go

@@ -2,10 +2,12 @@ package server
 
 import (
 	"bytes"
+	"testing"
+
 	"github.com/mkaminski/goaim/oscar"
+	"github.com/mkaminski/goaim/user"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/mock"
-	"testing"
 )
 
 func TestSendAndReceiveChannelMsgTohost(t *testing.T) {
@@ -17,28 +19,22 @@ func TestSendAndReceiveChannelMsgTohost(t *testing.T) {
 		// recipRetrieveErr is the error returned by the recipient session
 		// lookup
 		recipRetrieveErr error
-		senderSession    *Session
-		recipientSession *Session
+		senderSession    *user.Session
+		recipientSession *user.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
 		// recipient client
-		expectSNACToClient XMessage
+		expectSNACToClient oscar.XMessage
 		// inputSNAC is the SNAC frame sent from the server to the recipient
 		// client
-		expectOutput *XMessage
+		expectOutput *oscar.XMessage
 	}{
 		{
-			name:         "transmit message from sender to recipient, ack message back to sender",
-			blockedState: BlockedNo,
-			senderSession: &Session{
-				ScreenName: "sender-screen-name",
-				Warning:    10,
-			},
-			recipientSession: &Session{
-				ScreenName: "recipient-screen-name",
-				Warning:    20,
-			},
+			name:             "transmit message from sender to recipient, ack message back to sender",
+			blockedState:     BlockedNo,
+			senderSession:    newTestSession("sender-screen-name", sessOptWarning(10)),
+			recipientSession: newTestSession("recipient-screen-name", sessOptWarning(20)),
 			inputSNAC: oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost{
 				ScreenName: "recipient-screen-name",
 				TLVRestBlock: oscar.TLVRestBlock{
@@ -50,12 +46,12 @@ func TestSendAndReceiveChannelMsgTohost(t *testing.T) {
 					},
 				},
 			},
-			expectSNACToClient: XMessage{
-				snacFrame: oscar.SnacFrame{
+			expectSNACToClient: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.ICBM,
 					SubGroup:  oscar.ICBMChannelMsgToclient,
 				},
-				snacOut: oscar.SNAC_0x04_0x07_ICBMChannelMsgToClient{
+				SnacOut: oscar.SNAC_0x04_0x07_ICBMChannelMsgToClient{
 					TLVUserInfo: oscar.TLVUserInfo{
 						ScreenName:   "sender-screen-name",
 						WarningLevel: 10,
@@ -74,39 +70,33 @@ func TestSendAndReceiveChannelMsgTohost(t *testing.T) {
 					},
 				},
 			},
-			expectOutput: &XMessage{
-				snacFrame: oscar.SnacFrame{
+			expectOutput: &oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.ICBM,
 					SubGroup:  oscar.ICBMHostAck,
 				},
-				snacOut: oscar.SNAC_0x04_0x0C_ICBMHostAck{
+				SnacOut: oscar.SNAC_0x04_0x0C_ICBMHostAck{
 					ScreenName: "recipient-screen-name",
 				},
 			},
 		},
 		{
-			name:         "transmit message from sender to recipient, don't ack message back to sender",
-			blockedState: BlockedNo,
-			senderSession: &Session{
-				ScreenName: "sender-screen-name",
-				Warning:    10,
-			},
-			recipientSession: &Session{
-				ScreenName: "recipient-screen-name",
-				Warning:    20,
-			},
+			name:             "transmit message from sender to recipient, don't ack message back to sender",
+			blockedState:     BlockedNo,
+			senderSession:    newTestSession("sender-screen-name", sessOptWarning(10)),
+			recipientSession: newTestSession("recipient-screen-name", sessOptWarning(20)),
 			inputSNAC: oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost{
 				ScreenName: "recipient-screen-name",
 				TLVRestBlock: oscar.TLVRestBlock{
 					TLVList: oscar.TLVList{},
 				},
 			},
-			expectSNACToClient: XMessage{
-				snacFrame: oscar.SnacFrame{
+			expectSNACToClient: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.ICBM,
 					SubGroup:  oscar.ICBMChannelMsgToclient,
 				},
-				snacOut: oscar.SNAC_0x04_0x07_ICBMChannelMsgToClient{
+				SnacOut: oscar.SNAC_0x04_0x07_ICBMChannelMsgToClient{
 					TLVUserInfo: oscar.TLVUserInfo{
 						ScreenName:   "sender-screen-name",
 						WarningLevel: 10,
@@ -124,16 +114,10 @@ func TestSendAndReceiveChannelMsgTohost(t *testing.T) {
 			expectOutput: nil,
 		},
 		{
-			name:         "don't transmit message from sender to recipient because sender has blocked recipient",
-			blockedState: BlockedA,
-			senderSession: &Session{
-				ScreenName: "sender-screen-name",
-				Warning:    10,
-			},
-			recipientSession: &Session{
-				ScreenName: "recipient-screen-name",
-				Warning:    20,
-			},
+			name:             "don't transmit message from sender to recipient because sender has blocked recipient",
+			blockedState:     BlockedA,
+			senderSession:    newTestSession("sender-screen-name", sessOptWarning(10)),
+			recipientSession: newTestSession("recipient-screen-name", sessOptWarning(20)),
 			inputSNAC: oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost{
 				ScreenName: "recipient-screen-name",
 				TLVRestBlock: oscar.TLVRestBlock{
@@ -145,27 +129,21 @@ func TestSendAndReceiveChannelMsgTohost(t *testing.T) {
 					},
 				},
 			},
-			expectOutput: &XMessage{
-				snacFrame: oscar.SnacFrame{
+			expectOutput: &oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.ICBM,
 					SubGroup:  oscar.ICBMErr,
 				},
-				snacOut: oscar.SnacError{
+				SnacOut: oscar.SnacError{
 					Code: oscar.ErrorCodeInLocalPermitDeny,
 				},
 			},
 		},
 		{
-			name:         "don't transmit message from sender to recipient because recipient has blocked sender",
-			blockedState: BlockedB,
-			senderSession: &Session{
-				ScreenName: "sender-screen-name",
-				Warning:    10,
-			},
-			recipientSession: &Session{
-				ScreenName: "recipient-screen-name",
-				Warning:    20,
-			},
+			name:             "don't transmit message from sender to recipient because recipient has blocked sender",
+			blockedState:     BlockedB,
+			senderSession:    newTestSession("sender-screen-name", sessOptWarning(10)),
+			recipientSession: newTestSession("recipient-screen-name", sessOptWarning(20)),
 			inputSNAC: oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost{
 				ScreenName: "recipient-screen-name",
 				TLVRestBlock: oscar.TLVRestBlock{
@@ -177,12 +155,12 @@ func TestSendAndReceiveChannelMsgTohost(t *testing.T) {
 					},
 				},
 			},
-			expectOutput: &XMessage{
-				snacFrame: oscar.SnacFrame{
+			expectOutput: &oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.ICBM,
 					SubGroup:  oscar.ICBMErr,
 				},
-				snacOut: oscar.SnacError{
+				SnacOut: oscar.SnacError{
 					Code: oscar.ErrorCodeNotLoggedOn,
 				},
 			},
@@ -191,14 +169,8 @@ func TestSendAndReceiveChannelMsgTohost(t *testing.T) {
 			name:             "don't transmit message from sender to recipient because recipient doesn't exist",
 			blockedState:     BlockedNo,
 			recipRetrieveErr: ErrSessNotFound,
-			senderSession: &Session{
-				ScreenName: "sender-screen-name",
-				Warning:    10,
-			},
-			recipientSession: &Session{
-				ScreenName: "recipient-screen-name",
-				Warning:    20,
-			},
+			senderSession:    newTestSession("sender-screen-name", sessOptWarning(10)),
+			recipientSession: newTestSession("recipient-screen-name", sessOptWarning(20)),
 			inputSNAC: oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost{
 				ScreenName: "recipient-screen-name",
 				TLVRestBlock: oscar.TLVRestBlock{
@@ -210,12 +182,12 @@ func TestSendAndReceiveChannelMsgTohost(t *testing.T) {
 					},
 				},
 			},
-			expectOutput: &XMessage{
-				snacFrame: oscar.SnacFrame{
+			expectOutput: &oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.ICBM,
 					SubGroup:  oscar.ICBMErr,
 				},
-				snacOut: oscar.SnacError{
+				SnacOut: oscar.SnacError{
 					Code: oscar.ErrorCodeNotLoggedOn,
 				},
 			},
@@ -229,16 +201,16 @@ func TestSendAndReceiveChannelMsgTohost(t *testing.T) {
 			//
 			fm := NewMockFeedbagManager(t)
 			fm.EXPECT().
-				Blocked(tc.senderSession.ScreenName, tc.recipientSession.ScreenName).
+				Blocked(tc.senderSession.ScreenName(), tc.recipientSession.ScreenName()).
 				Return(tc.blockedState, nil).
 				Maybe()
 			sm := NewMockSessionManager(t)
 			sm.EXPECT().
-				RetrieveByScreenName(tc.recipientSession.ScreenName).
+				RetrieveByScreenName(tc.recipientSession.ScreenName()).
 				Return(tc.recipientSession, tc.recipRetrieveErr).
 				Maybe()
 			sm.EXPECT().
-				SendToScreenName(mock.Anything, tc.recipientSession.ScreenName, tc.expectSNACToClient).
+				SendToScreenName(mock.Anything, tc.recipientSession.ScreenName(), tc.expectSNACToClient).
 				Maybe()
 			//
 			// send input SNAC
@@ -269,7 +241,7 @@ func TestSendAndReceiveClientEvent(t *testing.T) {
 		inputSNAC oscar.SNAC_0x04_0x14_ICBMClientEvent
 		// expectSNACToClient is the SNAC sent from the server to the
 		// recipient client
-		expectSNACToClient XMessage
+		expectSNACToClient oscar.XMessage
 	}{
 		{
 			name:             "transmit message from sender to recipient",
@@ -281,12 +253,12 @@ func TestSendAndReceiveClientEvent(t *testing.T) {
 				ScreenName: "recipient-screen-name",
 				Event:      12,
 			},
-			expectSNACToClient: XMessage{
-				snacFrame: oscar.SnacFrame{
+			expectSNACToClient: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.ICBM,
 					SubGroup:  oscar.ICBMClientEvent,
 				},
-				snacOut: oscar.SNAC_0x04_0x14_ICBMClientEvent{
+				SnacOut: oscar.SNAC_0x04_0x14_ICBMClientEvent{
 					Cookie:     [8]byte{1, 2, 3, 4, 5, 6, 7, 8},
 					ChannelID:  42,
 					ScreenName: "sender-screen-name",
@@ -322,9 +294,7 @@ func TestSendAndReceiveClientEvent(t *testing.T) {
 			//
 			// send input SNAC
 			//
-			senderSession := &Session{
-				ScreenName: tc.senderScreenName,
-			}
+			senderSession := newTestSession(tc.senderScreenName)
 			svc := ICBMService{
 				sm: sm,
 				fm: fm,
@@ -335,8 +305,6 @@ func TestSendAndReceiveClientEvent(t *testing.T) {
 }
 
 func TestSendAndReceiveEvilRequest(t *testing.T) {
-	defaultSess := &Session{}
-
 	cases := []struct {
 		// name is the unit test name
 		name string
@@ -346,39 +314,37 @@ func TestSendAndReceiveEvilRequest(t *testing.T) {
 		// lookup
 		recipRetrieveErr error
 		// senderScreenName is the session name of the user sending the IM
-		senderSession *Session
+		senderSession *user.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
 		// updated warning level
 		recipientBuddies []string
-		broadcastMessage XMessage
+		broadcastMessage oscar.XMessage
 		// inputSNAC is the SNAC sent by the sender client
 		inputSNAC oscar.SNAC_0x04_0x08_ICBMEvilRequest
 		// expectSNACToClient is the SNAC sent from the server to the
 		// recipient client
-		expectSNACToClient XMessage
+		expectSNACToClient oscar.XMessage
 
-		expectOutput XMessage
+		expectOutput oscar.XMessage
 	}{
 		{
-			name:         "transmit anonymous warning from sender to recipient",
-			blockedState: BlockedNo,
-			senderSession: &Session{
-				ScreenName: "sender-screen-name",
-			},
+			name:                "transmit anonymous warning from sender to recipient",
+			blockedState:        BlockedNo,
+			senderSession:       newTestSession("sender-screen-name"),
 			recipientScreenName: "recipient-screen-name",
-			broadcastMessage: XMessage{
-				snacFrame: oscar.SnacFrame{
+			broadcastMessage: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.BUDDY,
 					SubGroup:  oscar.BuddyArrived,
 				},
-				snacOut: oscar.SNAC_0x03_0x0A_BuddyArrived{
+				SnacOut: oscar.SNAC_0x03_0x0A_BuddyArrived{
 					TLVUserInfo: oscar.TLVUserInfo{
 						ScreenName:   "recipient-screen-name",
 						WarningLevel: evilDeltaAnon,
 						TLVBlock: oscar.TLVBlock{
-							TLVList: defaultSess.GetUserInfo(),
+							TLVList: newTestSession("", sessOptCannedSignonTime).UserInfo(),
 						},
 					},
 				},
@@ -388,45 +354,43 @@ func TestSendAndReceiveEvilRequest(t *testing.T) {
 				SendAs:     1, // make it anonymous
 				ScreenName: "recipient-screen-name",
 			},
-			expectSNACToClient: XMessage{
-				snacFrame: oscar.SnacFrame{
+			expectSNACToClient: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.OSERVICE,
 					SubGroup:  oscar.OServiceEvilNotification,
 				},
-				snacOut: oscar.SNAC_0x01_0x10_OServiceEvilNotificationAnon{
+				SnacOut: oscar.SNAC_0x01_0x10_OServiceEvilNotificationAnon{
 					NewEvil: evilDeltaAnon,
 				},
 			},
-			expectOutput: XMessage{
-				snacFrame: oscar.SnacFrame{
+			expectOutput: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.ICBM,
 					SubGroup:  oscar.ICBMEvilReply,
 				},
-				snacOut: oscar.SNAC_0x04_0x09_ICBMEvilReply{
+				SnacOut: oscar.SNAC_0x04_0x09_ICBMEvilReply{
 					EvilDeltaApplied: 30,
 					UpdatedEvilValue: 30,
 				},
 			},
 		},
 		{
-			name:         "transmit non-anonymous warning from sender to recipient",
-			blockedState: BlockedNo,
-			senderSession: &Session{
-				ScreenName: "sender-screen-name",
-			},
+			name:                "transmit non-anonymous warning from sender to recipient",
+			blockedState:        BlockedNo,
+			senderSession:       newTestSession("sender-screen-name"),
 			recipientScreenName: "recipient-screen-name",
 			recipientBuddies:    []string{"buddy1", "buddy2"},
-			broadcastMessage: XMessage{
-				snacFrame: oscar.SnacFrame{
+			broadcastMessage: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.BUDDY,
 					SubGroup:  oscar.BuddyArrived,
 				},
-				snacOut: oscar.SNAC_0x03_0x0A_BuddyArrived{
+				SnacOut: oscar.SNAC_0x03_0x0A_BuddyArrived{
 					TLVUserInfo: oscar.TLVUserInfo{
 						ScreenName:   "recipient-screen-name",
 						WarningLevel: evilDelta,
 						TLVBlock: oscar.TLVBlock{
-							TLVList: defaultSess.GetUserInfo(),
+							TLVList: newTestSession("", sessOptCannedSignonTime).UserInfo(),
 						},
 					},
 				},
@@ -435,12 +399,12 @@ func TestSendAndReceiveEvilRequest(t *testing.T) {
 				SendAs:     0, // make it identified
 				ScreenName: "recipient-screen-name",
 			},
-			expectSNACToClient: XMessage{
-				snacFrame: oscar.SnacFrame{
+			expectSNACToClient: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.OSERVICE,
 					SubGroup:  oscar.OServiceEvilNotification,
 				},
-				snacOut: oscar.SNAC_0x01_0x10_OServiceEvilNotification{
+				SnacOut: oscar.SNAC_0x01_0x10_OServiceEvilNotification{
 					NewEvil: evilDelta,
 					TLVUserInfo: oscar.TLVUserInfo{
 						ScreenName:   "sender-screen-name",
@@ -448,77 +412,71 @@ func TestSendAndReceiveEvilRequest(t *testing.T) {
 					},
 				},
 			},
-			expectOutput: XMessage{
-				snacFrame: oscar.SnacFrame{
+			expectOutput: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.ICBM,
 					SubGroup:  oscar.ICBMEvilReply,
 				},
-				snacOut: oscar.SNAC_0x04_0x09_ICBMEvilReply{
+				SnacOut: oscar.SNAC_0x04_0x09_ICBMEvilReply{
 					EvilDeltaApplied: 100,
 					UpdatedEvilValue: 100,
 				},
 			},
 		},
 		{
-			name:         "don't transmit non-anonymous warning from sender to recipient because sender has blocked recipient",
-			blockedState: BlockedA,
-			senderSession: &Session{
-				ScreenName: "sender-screen-name",
-			},
+			name:                "don't transmit non-anonymous warning from sender to recipient because sender has blocked recipient",
+			blockedState:        BlockedA,
+			senderSession:       newTestSession("sender-screen-name"),
 			recipientScreenName: "recipient-screen-name",
 			recipientBuddies:    []string{"buddy1", "buddy2"},
 			inputSNAC: oscar.SNAC_0x04_0x08_ICBMEvilRequest{
 				SendAs:     0, // make it identified
 				ScreenName: "recipient-screen-name",
 			},
-			expectOutput: XMessage{
-				snacFrame: oscar.SnacFrame{
+			expectOutput: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.ICBM,
 					SubGroup:  oscar.ICBMErr,
 				},
-				snacOut: oscar.SnacError{
+				SnacOut: oscar.SnacError{
 					Code: oscar.ErrorCodeNotLoggedOn,
 				},
 			},
 		},
 		{
-			name:         "don't transmit non-anonymous warning from sender to recipient because recipient has blocked sender",
-			blockedState: BlockedB,
-			senderSession: &Session{
-				ScreenName: "sender-screen-name",
-			},
+			name:                "don't transmit non-anonymous warning from sender to recipient because recipient has blocked sender",
+			blockedState:        BlockedB,
+			senderSession:       newTestSession("sender-screen-name"),
 			recipientScreenName: "recipient-screen-name",
 			recipientBuddies:    []string{"buddy1", "buddy2"},
 			inputSNAC: oscar.SNAC_0x04_0x08_ICBMEvilRequest{
 				SendAs:     0, // make it identified
 				ScreenName: "recipient-screen-name",
 			},
-			expectOutput: XMessage{
-				snacFrame: oscar.SnacFrame{
+			expectOutput: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.ICBM,
 					SubGroup:  oscar.ICBMErr,
 				},
-				snacOut: oscar.SnacError{
+				SnacOut: oscar.SnacError{
 					Code: oscar.ErrorCodeNotLoggedOn,
 				},
 			},
 		},
 		{
-			name: "don't let users block themselves",
-			senderSession: &Session{
-				ScreenName: "sender-screen-name",
-			},
+			name:                "don't let users block themselves",
+			senderSession:       newTestSession("sender-screen-name"),
 			recipientScreenName: "sender-screen-name",
 			inputSNAC: oscar.SNAC_0x04_0x08_ICBMEvilRequest{
 				SendAs:     0, // make it identified
 				ScreenName: "sender-screen-name",
 			},
-			expectOutput: XMessage{
-				snacFrame: oscar.SnacFrame{
+			expectOutput: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.ICBM,
 					SubGroup:  oscar.ICBMErr,
 				},
-				snacOut: oscar.SnacError{
+				SnacOut: oscar.SnacError{
 					Code: oscar.ErrorCodeNotSupportedByHost,
 				},
 			},
@@ -532,17 +490,14 @@ func TestSendAndReceiveEvilRequest(t *testing.T) {
 			//
 			fm := NewMockFeedbagManager(t)
 			fm.EXPECT().
-				Blocked(tc.senderSession.ScreenName, tc.recipientScreenName).
+				Blocked(tc.senderSession.ScreenName(), tc.recipientScreenName).
 				Return(tc.blockedState, nil).
 				Maybe()
 			fm.EXPECT().
 				InterestedUsers(tc.recipientScreenName).
 				Return(tc.recipientBuddies, nil).
 				Maybe()
-			recipSess := &Session{
-				ScreenName: tc.recipientScreenName,
-				Warning:    0,
-			}
+			recipSess := newTestSession(tc.recipientScreenName, sessOptCannedSignonTime)
 			sm := NewMockSessionManager(t)
 			sm.EXPECT().
 				RetrieveByScreenName(tc.recipientScreenName).
@@ -557,9 +512,7 @@ func TestSendAndReceiveEvilRequest(t *testing.T) {
 			//
 			// send input SNAC
 			//
-			senderSession := &Session{
-				ScreenName: tc.senderSession.ScreenName,
-			}
+			senderSession := newTestSession(tc.senderSession.ScreenName())
 			svc := ICBMService{
 				sm: sm,
 				fm: fm,
@@ -576,9 +529,9 @@ func TestICBMRouter_RouteICBM(t *testing.T) {
 		// name is the unit test name
 		name string
 		// input is the request payload
-		input XMessage
+		input oscar.XMessage
 		// output is the response payload
-		output *XMessage
+		output *oscar.XMessage
 		// handlerErr is the mocked handler error response
 		handlerErr error
 		// expectErr is the expected error returned by the router
@@ -586,12 +539,12 @@ func TestICBMRouter_RouteICBM(t *testing.T) {
 	}{
 		{
 			name: "receive ICBMAddParameters SNAC, return no response",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.ICBM,
 					SubGroup:  oscar.ICBMAddParameters,
 				},
-				snacOut: oscar.SNAC_0x04_0x02_ICBMAddParameters{
+				SnacOut: oscar.SNAC_0x04_0x02_ICBMAddParameters{
 					Channel: 1,
 				},
 			},
@@ -599,52 +552,52 @@ func TestICBMRouter_RouteICBM(t *testing.T) {
 		},
 		{
 			name: "receive ICBMParameterQuery, return ICBMParameterReply",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.ICBM,
 					SubGroup:  oscar.ICBMParameterQuery,
 				},
-				snacOut: struct{}{}, // empty SNAC
+				SnacOut: struct{}{}, // empty SNAC
 			},
-			output: &XMessage{
-				snacFrame: oscar.SnacFrame{
+			output: &oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.ICBM,
 					SubGroup:  oscar.ICBMParameterReply,
 				},
-				snacOut: oscar.SNAC_0x04_0x05_ICBMParameterReply{
+				SnacOut: oscar.SNAC_0x04_0x05_ICBMParameterReply{
 					MaxSlots: 100,
 				},
 			},
 		},
 		{
 			name: "receive ICBMChannelMsgToHost, return ICBMHostAck",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.ICBM,
 					SubGroup:  oscar.ICBMChannelMsgToHost,
 				},
-				snacOut: oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost{
+				SnacOut: oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost{
 					ScreenName: "recipient-screen-name",
 				},
 			},
-			output: &XMessage{
-				snacFrame: oscar.SnacFrame{
+			output: &oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.ICBM,
 					SubGroup:  oscar.ICBMHostAck,
 				},
-				snacOut: oscar.SNAC_0x04_0x0C_ICBMHostAck{
+				SnacOut: oscar.SNAC_0x04_0x0C_ICBMHostAck{
 					ChannelID: 4,
 				},
 			},
 		},
 		{
 			name: "receive ICBMChannelMsgToHost, return no reply",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.ICBM,
 					SubGroup:  oscar.ICBMChannelMsgToHost,
 				},
-				snacOut: oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost{
+				SnacOut: oscar.SNAC_0x04_0x06_ICBMChannelMsgToHost{
 					ScreenName: "recipient-screen-name",
 				},
 			},
@@ -652,33 +605,33 @@ func TestICBMRouter_RouteICBM(t *testing.T) {
 		},
 		{
 			name: "receive ICBMEvilRequest, return ICBMEvilReply",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.ICBM,
 					SubGroup:  oscar.ICBMEvilRequest,
 				},
-				snacOut: oscar.SNAC_0x04_0x08_ICBMEvilRequest{
+				SnacOut: oscar.SNAC_0x04_0x08_ICBMEvilRequest{
 					ScreenName: "recipient-screen-name",
 				},
 			},
-			output: &XMessage{
-				snacFrame: oscar.SnacFrame{
+			output: &oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.ICBM,
 					SubGroup:  oscar.ICBMEvilReply,
 				},
-				snacOut: oscar.SNAC_0x04_0x09_ICBMEvilReply{
+				SnacOut: oscar.SNAC_0x04_0x09_ICBMEvilReply{
 					EvilDeltaApplied: 100,
 				},
 			},
 		},
 		{
 			name: "receive ICBMClientErr, return no response",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.ICBM,
 					SubGroup:  oscar.ICBMClientErr,
 				},
-				snacOut: oscar.SNAC_0x04_0x0B_ICBMClientErr{
+				SnacOut: oscar.SNAC_0x04_0x0B_ICBMClientErr{
 					Code: 4,
 				},
 			},
@@ -686,12 +639,12 @@ func TestICBMRouter_RouteICBM(t *testing.T) {
 		},
 		{
 			name: "receive ICBMClientEvent, return no response",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.ICBM,
 					SubGroup:  oscar.ICBMClientEvent,
 				},
-				snacOut: oscar.SNAC_0x04_0x14_ICBMClientEvent{
+				SnacOut: oscar.SNAC_0x04_0x14_ICBMClientEvent{
 					ScreenName: "recipient-screen-name",
 				},
 			},
@@ -699,12 +652,12 @@ func TestICBMRouter_RouteICBM(t *testing.T) {
 		},
 		{
 			name: "receive ICBMMissedCalls, expect ErrUnsupportedSubGroup",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.ICBM,
 					SubGroup:  oscar.ICBMMissedCalls,
 				},
-				snacOut: struct{}{}, // empty SNAC
+				SnacOut: struct{}{}, // empty SNAC
 			},
 			output:    nil,
 			expectErr: ErrUnsupportedSubGroup,
@@ -715,16 +668,16 @@ func TestICBMRouter_RouteICBM(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			svc := NewMockICBMHandler(t)
 			svc.EXPECT().
-				ChannelMsgToHostHandler(mock.Anything, mock.Anything, tc.input.snacOut).
+				ChannelMsgToHostHandler(mock.Anything, mock.Anything, tc.input.SnacOut).
 				Return(tc.output, tc.handlerErr).
 				Maybe()
 			svc.EXPECT().
-				ClientEventHandler(mock.Anything, mock.Anything, tc.input.snacOut).
+				ClientEventHandler(mock.Anything, mock.Anything, tc.input.SnacOut).
 				Return(tc.handlerErr).
 				Maybe()
 			if tc.output != nil {
 				svc.EXPECT().
-					EvilRequestHandler(mock.Anything, mock.Anything, tc.input.snacOut).
+					EvilRequestHandler(mock.Anything, mock.Anything, tc.input.SnacOut).
 					Return(*tc.output, tc.handlerErr).
 					Maybe()
 				svc.EXPECT().
@@ -741,12 +694,12 @@ func TestICBMRouter_RouteICBM(t *testing.T) {
 			}
 
 			bufIn := &bytes.Buffer{}
-			assert.NoError(t, oscar.Marshal(tc.input.snacOut, bufIn))
+			assert.NoError(t, oscar.Marshal(tc.input.SnacOut, bufIn))
 
 			bufOut := &bytes.Buffer{}
 			seq := uint32(1)
 
-			err := router.RouteICBM(nil, nil, tc.input.snacFrame, bufIn, bufOut, &seq)
+			err := router.RouteICBM(nil, nil, tc.input.SnacFrame, bufIn, bufOut, &seq)
 			assert.ErrorIs(t, err, tc.expectErr)
 			if tc.expectErr != nil {
 				return
@@ -769,11 +722,11 @@ func TestICBMRouter_RouteICBM(t *testing.T) {
 			// verify the SNAC frame
 			snacFrame := oscar.SnacFrame{}
 			assert.NoError(t, oscar.Unmarshal(&snacFrame, bufOut))
-			assert.Equal(t, tc.output.snacFrame, snacFrame)
+			assert.Equal(t, tc.output.SnacFrame, snacFrame)
 
 			// verify the SNAC message
 			snacBuf := &bytes.Buffer{}
-			assert.NoError(t, oscar.Marshal(tc.output.snacOut, snacBuf))
+			assert.NoError(t, oscar.Marshal(tc.output.SnacOut, snacBuf))
 			assert.Equal(t, snacBuf.Bytes(), bufOut.Bytes())
 		})
 	}

+ 45 - 44
server/locate.go

@@ -3,6 +3,7 @@ package server
 import (
 	"context"
 	"errors"
+	"github.com/mkaminski/goaim/user"
 	"io"
 	"log/slog"
 
@@ -10,11 +11,11 @@ import (
 )
 
 type LocateHandler interface {
-	RightsQueryHandler(ctx context.Context) XMessage
-	SetDirInfoHandler(ctx context.Context) XMessage
-	SetInfoHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x02_0x04_LocateSetInfo) error
-	SetKeywordInfoHandler(ctx context.Context) XMessage
-	UserInfoQuery2Handler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x02_0x15_LocateUserInfoQuery2) (XMessage, error)
+	RightsQueryHandler(ctx context.Context) oscar.XMessage
+	SetDirInfoHandler(ctx context.Context) oscar.XMessage
+	SetInfoHandler(ctx context.Context, sess *user.Session, snacPayloadIn oscar.SNAC_0x02_0x04_LocateSetInfo) error
+	SetKeywordInfoHandler(ctx context.Context) oscar.XMessage
+	UserInfoQuery2Handler(ctx context.Context, sess *user.Session, snacPayloadIn oscar.SNAC_0x02_0x15_LocateUserInfoQuery2) (oscar.XMessage, error)
 }
 
 func NewLocateRouter(logger *slog.Logger, sm SessionManager, fm FeedbagManager, pm ProfileManager) LocateRouter {
@@ -35,12 +36,12 @@ 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 *user.Session, SNACFrame oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
 	switch SNACFrame.SubGroup {
 	case oscar.LocateRightsQuery:
 		outSNAC := rt.RightsQueryHandler(ctx)
-		rt.logRequestAndResponse(ctx, SNACFrame, nil, outSNAC.snacFrame, outSNAC.snacOut)
-		return writeOutSNAC(SNACFrame, outSNAC.snacFrame, outSNAC.snacOut, sequence, w)
+		rt.logRequestAndResponse(ctx, SNACFrame, nil, outSNAC.SnacFrame, outSNAC.SnacOut)
+		return writeOutSNAC(SNACFrame, outSNAC.SnacFrame, outSNAC.SnacOut, sequence, w)
 	case oscar.LocateSetInfo:
 		inSNAC := oscar.SNAC_0x02_0x04_LocateSetInfo{}
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
@@ -54,8 +55,8 @@ func (rt LocateRouter) RouteLocate(ctx context.Context, sess *Session, SNACFrame
 			return err
 		}
 		outSNAC := rt.SetDirInfoHandler(ctx)
-		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.snacFrame, outSNAC.snacOut)
-		return writeOutSNAC(SNACFrame, outSNAC.snacFrame, outSNAC.snacOut, sequence, w)
+		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.SnacFrame, outSNAC.SnacOut)
+		return writeOutSNAC(SNACFrame, outSNAC.SnacFrame, outSNAC.SnacOut, sequence, w)
 	case oscar.LocateGetDirInfo:
 		inSNAC := oscar.SNAC_0x02_0x0B_LocateGetDirInfo{}
 		rt.logRequest(ctx, SNACFrame, inSNAC)
@@ -66,8 +67,8 @@ func (rt LocateRouter) RouteLocate(ctx context.Context, sess *Session, SNACFrame
 			return err
 		}
 		outSNAC := rt.SetKeywordInfoHandler(ctx)
-		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.snacFrame, outSNAC.snacOut)
-		return writeOutSNAC(SNACFrame, outSNAC.snacFrame, outSNAC.snacOut, sequence, w)
+		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.SnacFrame, outSNAC.SnacOut)
+		return writeOutSNAC(SNACFrame, outSNAC.SnacFrame, outSNAC.SnacOut, sequence, w)
 	case oscar.LocateUserInfoQuery2:
 		inSNAC := oscar.SNAC_0x02_0x15_LocateUserInfoQuery2{}
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
@@ -77,8 +78,8 @@ func (rt LocateRouter) RouteLocate(ctx context.Context, sess *Session, SNACFrame
 		if err != nil {
 			return err
 		}
-		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.snacFrame, outSNAC.snacOut)
-		return writeOutSNAC(SNACFrame, outSNAC.snacFrame, outSNAC.snacOut, sequence, w)
+		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.SnacFrame, outSNAC.SnacOut)
+		return writeOutSNAC(SNACFrame, outSNAC.SnacFrame, outSNAC.SnacOut, sequence, w)
 	default:
 		return ErrUnsupportedSubGroup
 	}
@@ -90,13 +91,13 @@ type LocateService struct {
 	pm ProfileManager
 }
 
-func (s LocateService) RightsQueryHandler(context.Context) XMessage {
-	return XMessage{
-		snacFrame: oscar.SnacFrame{
+func (s LocateService) RightsQueryHandler(context.Context) oscar.XMessage {
+	return oscar.XMessage{
+		SnacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.LOCATE,
 			SubGroup:  oscar.LocateRightsReply,
 		},
-		snacOut: oscar.SNAC_0x02_0x03_LocateRightsReply{
+		SnacOut: oscar.SNAC_0x02_0x03_LocateRightsReply{
 			TLVRestBlock: oscar.TLVRestBlock{
 				TLVList: oscar.TLVList{
 					oscar.NewTLV(0x01, uint16(1000)),
@@ -110,10 +111,10 @@ func (s LocateService) RightsQueryHandler(context.Context) XMessage {
 	}
 }
 
-func (s LocateService) SetInfoHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x02_0x04_LocateSetInfo) error {
+func (s LocateService) SetInfoHandler(ctx context.Context, sess *user.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 {
+		if err := s.pm.UpsertProfile(sess.ScreenName(), profile); err != nil {
 			return err
 		}
 	}
@@ -128,18 +129,18 @@ func (s LocateService) SetInfoHandler(ctx context.Context, sess *Session, snacPa
 	return nil
 }
 
-func (s LocateService) UserInfoQuery2Handler(_ context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x02_0x15_LocateUserInfoQuery2) (XMessage, error) {
-	blocked, err := s.fm.Blocked(sess.ScreenName, snacPayloadIn.ScreenName)
+func (s LocateService) UserInfoQuery2Handler(_ context.Context, sess *user.Session, snacPayloadIn oscar.SNAC_0x02_0x15_LocateUserInfoQuery2) (oscar.XMessage, error) {
+	blocked, err := s.fm.Blocked(sess.ScreenName(), snacPayloadIn.ScreenName)
 	switch {
 	case err != nil:
-		return XMessage{}, err
+		return oscar.XMessage{}, err
 	case blocked != BlockedNo:
-		return XMessage{
-			snacFrame: oscar.SnacFrame{
+		return oscar.XMessage{
+			SnacFrame: oscar.SnacFrame{
 				FoodGroup: oscar.LOCATE,
 				SubGroup:  oscar.LocateErr,
 			},
-			snacOut: oscar.SnacError{
+			SnacOut: oscar.SnacError{
 				Code: oscar.ErrorCodeNotLoggedOn,
 			},
 		}, nil
@@ -148,17 +149,17 @@ func (s LocateService) UserInfoQuery2Handler(_ context.Context, sess *Session, s
 	buddySess, err := s.sm.RetrieveByScreenName(snacPayloadIn.ScreenName)
 	switch {
 	case errors.Is(err, ErrSessNotFound):
-		return XMessage{
-			snacFrame: oscar.SnacFrame{
+		return oscar.XMessage{
+			SnacFrame: oscar.SnacFrame{
 				FoodGroup: oscar.LOCATE,
 				SubGroup:  oscar.LocateErr,
 			},
-			snacOut: oscar.SnacError{
+			SnacOut: oscar.SnacError{
 				Code: oscar.ErrorCodeNotLoggedOn,
 			},
 		}, nil
 	case err != nil:
-		return XMessage{}, err
+		return oscar.XMessage{}, err
 	}
 
 	var list oscar.TLVList
@@ -166,7 +167,7 @@ func (s LocateService) UserInfoQuery2Handler(_ context.Context, sess *Session, s
 	if snacPayloadIn.RequestProfile() {
 		profile, err := s.pm.RetrieveProfile(snacPayloadIn.ScreenName)
 		if err != nil {
-			return XMessage{}, err
+			return oscar.XMessage{}, err
 		}
 		list.AddTLVList([]oscar.TLV{
 			oscar.NewTLV(oscar.LocateTLVTagsInfoSigMime, `text/aolrtf; charset="us-ascii"`),
@@ -177,17 +178,17 @@ func (s LocateService) UserInfoQuery2Handler(_ context.Context, sess *Session, s
 	if snacPayloadIn.RequestAwayMessage() {
 		list.AddTLVList([]oscar.TLV{
 			oscar.NewTLV(oscar.LocateTLVTagsInfoUnavailableMime, `text/aolrtf; charset="us-ascii"`),
-			oscar.NewTLV(oscar.LocateTLVTagsInfoUnavailableData, buddySess.GetAwayMessage()),
+			oscar.NewTLV(oscar.LocateTLVTagsInfoUnavailableData, buddySess.AwayMessage()),
 		})
 	}
 
-	return XMessage{
-		snacFrame: oscar.SnacFrame{
+	return oscar.XMessage{
+		SnacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.LOCATE,
 			SubGroup:  oscar.LocateUserInfoReply,
 		},
-		snacOut: oscar.SNAC_0x02_0x06_LocateUserInfoReply{
-			TLVUserInfo: buddySess.GetTLVUserInfo(),
+		SnacOut: oscar.SNAC_0x02_0x06_LocateUserInfoReply{
+			TLVUserInfo: buddySess.TLVUserInfo(),
 			LocateInfo: oscar.TLVRestBlock{
 				TLVList: list,
 			},
@@ -195,25 +196,25 @@ func (s LocateService) UserInfoQuery2Handler(_ context.Context, sess *Session, s
 	}, nil
 }
 
-func (s LocateService) SetDirInfoHandler(_ context.Context) XMessage {
-	return XMessage{
-		snacFrame: oscar.SnacFrame{
+func (s LocateService) SetDirInfoHandler(_ context.Context) oscar.XMessage {
+	return oscar.XMessage{
+		SnacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.LOCATE,
 			SubGroup:  oscar.LocateSetDirReply,
 		},
-		snacOut: oscar.SNAC_0x02_0x0A_LocateSetDirReply{
+		SnacOut: oscar.SNAC_0x02_0x0A_LocateSetDirReply{
 			Result: 1,
 		},
 	}
 }
 
-func (s LocateService) SetKeywordInfoHandler(_ context.Context) XMessage {
-	return XMessage{
-		snacFrame: oscar.SnacFrame{
+func (s LocateService) SetKeywordInfoHandler(_ context.Context) oscar.XMessage {
+	return oscar.XMessage{
+		SnacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.LOCATE,
 			SubGroup:  oscar.LocateSetKeywordReply,
 		},
-		snacOut: oscar.SNAC_0x02_0x10_LocateSetKeywordReply{
+		SnacOut: oscar.SNAC_0x02_0x10_LocateSetKeywordReply{
 			Unknown: 1,
 		},
 	}

+ 37 - 35
server/locate_mock.go

@@ -7,6 +7,8 @@ import (
 
 	oscar "github.com/mkaminski/goaim/oscar"
 	mock "github.com/stretchr/testify/mock"
+
+	user "github.com/mkaminski/goaim/user"
 )
 
 // MockLocateHandler is an autogenerated mock type for the LocateHandler type
@@ -23,14 +25,14 @@ func (_m *MockLocateHandler) EXPECT() *MockLocateHandler_Expecter {
 }
 
 // RightsQueryHandler provides a mock function with given fields: ctx
-func (_m *MockLocateHandler) RightsQueryHandler(ctx context.Context) XMessage {
+func (_m *MockLocateHandler) RightsQueryHandler(ctx context.Context) oscar.XMessage {
 	ret := _m.Called(ctx)
 
-	var r0 XMessage
-	if rf, ok := ret.Get(0).(func(context.Context) XMessage); ok {
+	var r0 oscar.XMessage
+	if rf, ok := ret.Get(0).(func(context.Context) oscar.XMessage); ok {
 		r0 = rf(ctx)
 	} else {
-		r0 = ret.Get(0).(XMessage)
+		r0 = ret.Get(0).(oscar.XMessage)
 	}
 
 	return r0
@@ -54,25 +56,25 @@ func (_c *MockLocateHandler_RightsQueryHandler_Call) Run(run func(ctx context.Co
 	return _c
 }
 
-func (_c *MockLocateHandler_RightsQueryHandler_Call) Return(_a0 XMessage) *MockLocateHandler_RightsQueryHandler_Call {
+func (_c *MockLocateHandler_RightsQueryHandler_Call) Return(_a0 oscar.XMessage) *MockLocateHandler_RightsQueryHandler_Call {
 	_c.Call.Return(_a0)
 	return _c
 }
 
-func (_c *MockLocateHandler_RightsQueryHandler_Call) RunAndReturn(run func(context.Context) XMessage) *MockLocateHandler_RightsQueryHandler_Call {
+func (_c *MockLocateHandler_RightsQueryHandler_Call) RunAndReturn(run func(context.Context) oscar.XMessage) *MockLocateHandler_RightsQueryHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }
 
 // SetDirInfoHandler provides a mock function with given fields: ctx
-func (_m *MockLocateHandler) SetDirInfoHandler(ctx context.Context) XMessage {
+func (_m *MockLocateHandler) SetDirInfoHandler(ctx context.Context) oscar.XMessage {
 	ret := _m.Called(ctx)
 
-	var r0 XMessage
-	if rf, ok := ret.Get(0).(func(context.Context) XMessage); ok {
+	var r0 oscar.XMessage
+	if rf, ok := ret.Get(0).(func(context.Context) oscar.XMessage); ok {
 		r0 = rf(ctx)
 	} else {
-		r0 = ret.Get(0).(XMessage)
+		r0 = ret.Get(0).(oscar.XMessage)
 	}
 
 	return r0
@@ -96,22 +98,22 @@ func (_c *MockLocateHandler_SetDirInfoHandler_Call) Run(run func(ctx context.Con
 	return _c
 }
 
-func (_c *MockLocateHandler_SetDirInfoHandler_Call) Return(_a0 XMessage) *MockLocateHandler_SetDirInfoHandler_Call {
+func (_c *MockLocateHandler_SetDirInfoHandler_Call) Return(_a0 oscar.XMessage) *MockLocateHandler_SetDirInfoHandler_Call {
 	_c.Call.Return(_a0)
 	return _c
 }
 
-func (_c *MockLocateHandler_SetDirInfoHandler_Call) RunAndReturn(run func(context.Context) XMessage) *MockLocateHandler_SetDirInfoHandler_Call {
+func (_c *MockLocateHandler_SetDirInfoHandler_Call) RunAndReturn(run func(context.Context) oscar.XMessage) *MockLocateHandler_SetDirInfoHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }
 
 // 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 *user.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, *user.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 *user.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 *user.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].(*user.Session), args[2].(oscar.SNAC_0x02_0x04_LocateSetInfo))
 	})
 	return _c
 }
@@ -145,20 +147,20 @@ 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, *user.Session, oscar.SNAC_0x02_0x04_LocateSetInfo) error) *MockLocateHandler_SetInfoHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }
 
 // SetKeywordInfoHandler provides a mock function with given fields: ctx
-func (_m *MockLocateHandler) SetKeywordInfoHandler(ctx context.Context) XMessage {
+func (_m *MockLocateHandler) SetKeywordInfoHandler(ctx context.Context) oscar.XMessage {
 	ret := _m.Called(ctx)
 
-	var r0 XMessage
-	if rf, ok := ret.Get(0).(func(context.Context) XMessage); ok {
+	var r0 oscar.XMessage
+	if rf, ok := ret.Get(0).(func(context.Context) oscar.XMessage); ok {
 		r0 = rf(ctx)
 	} else {
-		r0 = ret.Get(0).(XMessage)
+		r0 = ret.Get(0).(oscar.XMessage)
 	}
 
 	return r0
@@ -182,32 +184,32 @@ func (_c *MockLocateHandler_SetKeywordInfoHandler_Call) Run(run func(ctx context
 	return _c
 }
 
-func (_c *MockLocateHandler_SetKeywordInfoHandler_Call) Return(_a0 XMessage) *MockLocateHandler_SetKeywordInfoHandler_Call {
+func (_c *MockLocateHandler_SetKeywordInfoHandler_Call) Return(_a0 oscar.XMessage) *MockLocateHandler_SetKeywordInfoHandler_Call {
 	_c.Call.Return(_a0)
 	return _c
 }
 
-func (_c *MockLocateHandler_SetKeywordInfoHandler_Call) RunAndReturn(run func(context.Context) XMessage) *MockLocateHandler_SetKeywordInfoHandler_Call {
+func (_c *MockLocateHandler_SetKeywordInfoHandler_Call) RunAndReturn(run func(context.Context) oscar.XMessage) *MockLocateHandler_SetKeywordInfoHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }
 
 // UserInfoQuery2Handler provides a mock function with given fields: ctx, sess, snacPayloadIn
-func (_m *MockLocateHandler) UserInfoQuery2Handler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x02_0x15_LocateUserInfoQuery2) (XMessage, error) {
+func (_m *MockLocateHandler) UserInfoQuery2Handler(ctx context.Context, sess *user.Session, snacPayloadIn oscar.SNAC_0x02_0x15_LocateUserInfoQuery2) (oscar.XMessage, error) {
 	ret := _m.Called(ctx, sess, snacPayloadIn)
 
-	var r0 XMessage
+	var r0 oscar.XMessage
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x02_0x15_LocateUserInfoQuery2) (XMessage, error)); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *user.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) XMessage); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *user.Session, oscar.SNAC_0x02_0x15_LocateUserInfoQuery2) oscar.XMessage); ok {
 		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
-		r0 = ret.Get(0).(XMessage)
+		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, *user.Session, oscar.SNAC_0x02_0x15_LocateUserInfoQuery2) error); ok {
 		r1 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r1 = ret.Error(1)
@@ -223,25 +225,25 @@ type MockLocateHandler_UserInfoQuery2Handler_Call struct {
 
 // UserInfoQuery2Handler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *user.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 *user.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].(*user.Session), args[2].(oscar.SNAC_0x02_0x15_LocateUserInfoQuery2))
 	})
 	return _c
 }
 
-func (_c *MockLocateHandler_UserInfoQuery2Handler_Call) Return(_a0 XMessage, _a1 error) *MockLocateHandler_UserInfoQuery2Handler_Call {
+func (_c *MockLocateHandler_UserInfoQuery2Handler_Call) Return(_a0 oscar.XMessage, _a1 error) *MockLocateHandler_UserInfoQuery2Handler_Call {
 	_c.Call.Return(_a0, _a1)
 	return _c
 }
 
-func (_c *MockLocateHandler_UserInfoQuery2Handler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x02_0x15_LocateUserInfoQuery2) (XMessage, error)) *MockLocateHandler_UserInfoQuery2Handler_Call {
+func (_c *MockLocateHandler_UserInfoQuery2Handler_Call) RunAndReturn(run func(context.Context, *user.Session, oscar.SNAC_0x02_0x15_LocateUserInfoQuery2) (oscar.XMessage, error)) *MockLocateHandler_UserInfoQuery2Handler_Call {
 	_c.Call.Return(run)
 	return _c
 }

+ 109 - 133
server/locate_test.go

@@ -4,20 +4,12 @@ import (
 	"bytes"
 	"context"
 	"github.com/mkaminski/goaim/oscar"
+	"github.com/mkaminski/goaim/user"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/mock"
 	"testing"
-	"time"
 )
 
-// newTestSession returns a copy of the prototype session with fixed time
-//
-//goland:noinspection GoVetCopyLock
-func newTestSession(prototype Session) *Session {
-	prototype.SignonTime = time.UnixMilli(1696790127565)
-	return &prototype
-}
-
 func TestSendAndReceiveUserInfoQuery2(t *testing.T) {
 	cases := []struct {
 		// name is the unit test name
@@ -26,7 +18,7 @@ func TestSendAndReceiveUserInfoQuery2(t *testing.T) {
 		blockedState BlockedState
 		// screenNameLookups is the list of user session lookups
 		screenNameLookups map[string]struct {
-			sess *Session
+			sess *user.Session
 			err  error
 		}
 		// screenNameLookups is the list of user session lookups
@@ -35,42 +27,39 @@ func TestSendAndReceiveUserInfoQuery2(t *testing.T) {
 			err     error
 		}
 		// userSession is the session of the user requesting the user info
-		userSession *Session
+		userSession *user.Session
 		// inputSNAC is the SNAC sent by the sender client
 		inputSNAC    oscar.SNAC_0x02_0x15_LocateUserInfoQuery2
-		expectOutput XMessage
+		expectOutput oscar.XMessage
 	}{
 		{
 			name:         "request user info, expect user info response",
 			blockedState: BlockedNo,
 			screenNameLookups: map[string]struct {
-				sess *Session
+				sess *user.Session
 				err  error
 			}{
 				"requested-user": {
-					sess: newTestSession(Session{
-						ScreenName:  "requested-user",
-						AwayMessage: "this is my away message!",
-					}),
+					sess: newTestSession("requested-user",
+						sessOptCannedSignonTime,
+						sessOptCannedAwayMessage),
 				},
 			},
-			userSession: &Session{
-				ScreenName: "user_screen_name",
-			},
+			userSession: newTestSession("user_screen_name"),
 			inputSNAC: oscar.SNAC_0x02_0x15_LocateUserInfoQuery2{
 				Type2:      0,
 				ScreenName: "requested-user",
 			},
-			expectOutput: XMessage{
-				snacFrame: oscar.SnacFrame{
+			expectOutput: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.LOCATE,
 					SubGroup:  oscar.LocateUserInfoReply,
 				},
-				snacOut: oscar.SNAC_0x02_0x06_LocateUserInfoReply{
-					TLVUserInfo: newTestSession(Session{
-						ScreenName:  "requested-user",
-						AwayMessage: "this is my away message!",
-					}).GetTLVUserInfo(),
+				SnacOut: oscar.SNAC_0x02_0x06_LocateUserInfoReply{
+					TLVUserInfo: newTestSession("requested-user",
+						sessOptCannedSignonTime,
+						sessOptCannedAwayMessage).
+						TLVUserInfo(),
 					LocateInfo: oscar.TLVRestBlock{},
 				},
 			},
@@ -79,14 +68,13 @@ func TestSendAndReceiveUserInfoQuery2(t *testing.T) {
 			name:         "request user info + profile, expect user info response + profile",
 			blockedState: BlockedNo,
 			screenNameLookups: map[string]struct {
-				sess *Session
+				sess *user.Session
 				err  error
 			}{
 				"requested-user": {
-					sess: newTestSession(Session{
-						ScreenName:  "requested-user",
-						AwayMessage: "this is my away message!",
-					}),
+					sess: newTestSession("requested-user",
+						sessOptCannedSignonTime,
+						sessOptCannedAwayMessage),
 				},
 			},
 			profileLookups: map[string]struct {
@@ -97,24 +85,22 @@ func TestSendAndReceiveUserInfoQuery2(t *testing.T) {
 					payload: "this is my profile!",
 				},
 			},
-			userSession: &Session{
-				ScreenName: "user_screen_name",
-			},
+			userSession: newTestSession("user_screen_name"),
 			inputSNAC: oscar.SNAC_0x02_0x15_LocateUserInfoQuery2{
 				// 2048 is a dummy to make sure bitmask check works
 				Type2:      oscar.LocateType2Sig | 2048,
 				ScreenName: "requested-user",
 			},
-			expectOutput: XMessage{
-				snacFrame: oscar.SnacFrame{
+			expectOutput: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.LOCATE,
 					SubGroup:  oscar.LocateUserInfoReply,
 				},
-				snacOut: oscar.SNAC_0x02_0x06_LocateUserInfoReply{
-					TLVUserInfo: newTestSession(Session{
-						ScreenName:  "requested-user",
-						AwayMessage: "this is my away message!",
-					}).GetTLVUserInfo(),
+				SnacOut: oscar.SNAC_0x02_0x06_LocateUserInfoReply{
+					TLVUserInfo: newTestSession("requested-user",
+						sessOptCannedSignonTime,
+						sessOptCannedAwayMessage).
+						TLVUserInfo(),
 					LocateInfo: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							oscar.NewTLV(oscar.LocateTLVTagsInfoSigMime, `text/aolrtf; charset="us-ascii"`),
@@ -128,14 +114,13 @@ func TestSendAndReceiveUserInfoQuery2(t *testing.T) {
 			name:         "request user info + profile, expect user info response + profile",
 			blockedState: BlockedNo,
 			screenNameLookups: map[string]struct {
-				sess *Session
+				sess *user.Session
 				err  error
 			}{
 				"requested-user": {
-					sess: newTestSession(Session{
-						ScreenName:  "requested-user",
-						AwayMessage: "this is my away message!",
-					}),
+					sess: newTestSession("requested-user",
+						sessOptCannedSignonTime,
+						sessOptCannedAwayMessage),
 				},
 			},
 			profileLookups: map[string]struct {
@@ -146,24 +131,22 @@ func TestSendAndReceiveUserInfoQuery2(t *testing.T) {
 					payload: "this is my profile!",
 				},
 			},
-			userSession: &Session{
-				ScreenName: "user_screen_name",
-			},
+			userSession: newTestSession("user_screen_name"),
 			inputSNAC: oscar.SNAC_0x02_0x15_LocateUserInfoQuery2{
 				// 2048 is a dummy to make sure bitmask check works
 				Type2:      oscar.LocateType2Sig | 2048,
 				ScreenName: "requested-user",
 			},
-			expectOutput: XMessage{
-				snacFrame: oscar.SnacFrame{
+			expectOutput: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.LOCATE,
 					SubGroup:  oscar.LocateUserInfoReply,
 				},
-				snacOut: oscar.SNAC_0x02_0x06_LocateUserInfoReply{
-					TLVUserInfo: newTestSession(Session{
-						ScreenName:  "requested-user",
-						AwayMessage: "this is my away message!",
-					}).GetTLVUserInfo(),
+				SnacOut: oscar.SNAC_0x02_0x06_LocateUserInfoReply{
+					TLVUserInfo: newTestSession("requested-user",
+						sessOptCannedSignonTime,
+						sessOptCannedAwayMessage).
+						TLVUserInfo(),
 					LocateInfo: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							oscar.NewTLV(oscar.LocateTLVTagsInfoSigMime, `text/aolrtf; charset="us-ascii"`),
@@ -177,34 +160,31 @@ func TestSendAndReceiveUserInfoQuery2(t *testing.T) {
 			name:         "request user info + away message, expect user info response + away message",
 			blockedState: BlockedNo,
 			screenNameLookups: map[string]struct {
-				sess *Session
+				sess *user.Session
 				err  error
 			}{
 				"requested-user": {
-					sess: newTestSession(Session{
-						ScreenName:  "requested-user",
-						AwayMessage: "this is my away message!",
-					}),
+					sess: newTestSession("requested-user",
+						sessOptCannedSignonTime,
+						sessOptCannedAwayMessage),
 				},
 			},
-			userSession: &Session{
-				ScreenName: "user_screen_name",
-			},
+			userSession: newTestSession("user_screen_name"),
 			inputSNAC: oscar.SNAC_0x02_0x15_LocateUserInfoQuery2{
 				// 2048 is a dummy to make sure bitmask check works
 				Type2:      oscar.LocateType2Unavailable | 2048,
 				ScreenName: "requested-user",
 			},
-			expectOutput: XMessage{
-				snacFrame: oscar.SnacFrame{
+			expectOutput: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.LOCATE,
 					SubGroup:  oscar.LocateUserInfoReply,
 				},
-				snacOut: oscar.SNAC_0x02_0x06_LocateUserInfoReply{
-					TLVUserInfo: newTestSession(Session{
-						ScreenName:  "requested-user",
-						AwayMessage: "this is my away message!",
-					}).GetTLVUserInfo(),
+				SnacOut: oscar.SNAC_0x02_0x06_LocateUserInfoReply{
+					TLVUserInfo: newTestSession("requested-user",
+						sessOptCannedSignonTime,
+						sessOptCannedAwayMessage).
+						TLVUserInfo(),
 					LocateInfo: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							oscar.NewTLV(oscar.LocateTLVTagsInfoUnavailableMime, `text/aolrtf; charset="us-ascii"`),
@@ -217,18 +197,16 @@ func TestSendAndReceiveUserInfoQuery2(t *testing.T) {
 		{
 			name:         "request user info of user who blocked requester, expect not logged in error",
 			blockedState: BlockedB,
-			userSession: &Session{
-				ScreenName: "user_screen_name",
-			},
+			userSession:  newTestSession("user_screen_name"),
 			inputSNAC: oscar.SNAC_0x02_0x15_LocateUserInfoQuery2{
 				ScreenName: "requested-user",
 			},
-			expectOutput: XMessage{
-				snacFrame: oscar.SnacFrame{
+			expectOutput: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.LOCATE,
 					SubGroup:  oscar.LocateErr,
 				},
-				snacOut: oscar.SnacError{
+				SnacOut: oscar.SnacError{
 					Code: oscar.ErrorCodeNotLoggedOn,
 				},
 			},
@@ -237,25 +215,23 @@ func TestSendAndReceiveUserInfoQuery2(t *testing.T) {
 			name:         "request user info of user who does not exist, expect not logged in error",
 			blockedState: BlockedNo,
 			screenNameLookups: map[string]struct {
-				sess *Session
+				sess *user.Session
 				err  error
 			}{
 				"non_existent_requested_user": {
 					err: ErrSessNotFound,
 				},
 			},
-			userSession: &Session{
-				ScreenName: "user_screen_name",
-			},
+			userSession: newTestSession("user_screen_name"),
 			inputSNAC: oscar.SNAC_0x02_0x15_LocateUserInfoQuery2{
 				ScreenName: "non_existent_requested_user",
 			},
-			expectOutput: XMessage{
-				snacFrame: oscar.SnacFrame{
+			expectOutput: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.LOCATE,
 					SubGroup:  oscar.LocateErr,
 				},
-				snacOut: oscar.SnacError{
+				SnacOut: oscar.SnacError{
 					Code: oscar.ErrorCodeNotLoggedOn,
 				},
 			},
@@ -266,7 +242,7 @@ func TestSendAndReceiveUserInfoQuery2(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			fm := NewMockFeedbagManager(t)
 			fm.EXPECT().
-				Blocked(tc.userSession.ScreenName, tc.inputSNAC.ScreenName).
+				Blocked(tc.userSession.ScreenName(), tc.inputSNAC.ScreenName).
 				Return(tc.blockedState, nil).
 				Maybe()
 			sm := NewMockSessionManager(t)
@@ -301,9 +277,9 @@ func TestLocateRouter_RouteLocate(t *testing.T) {
 		// name is the unit test name
 		name string
 		// input is the request payload
-		input XMessage
+		input oscar.XMessage
 		// output is the response payload
-		output XMessage
+		output oscar.XMessage
 		// handlerErr is the mocked handler error response
 		handlerErr error
 		// expectErr is the expected error returned by the router
@@ -311,19 +287,19 @@ func TestLocateRouter_RouteLocate(t *testing.T) {
 	}{
 		{
 			name: "receive LocateRightsQuery, return LocateRightsReply",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.LOCATE,
 					SubGroup:  oscar.LocateRightsQuery,
 				},
-				snacOut: struct{}{},
+				SnacOut: struct{}{},
 			},
-			output: XMessage{
-				snacFrame: oscar.SnacFrame{
+			output: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.LOCATE,
 					SubGroup:  oscar.LocateRightsReply,
 				},
-				snacOut: oscar.SNAC_0x02_0x03_LocateRightsReply{
+				SnacOut: oscar.SNAC_0x02_0x03_LocateRightsReply{
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							oscar.NewTLV(0x01, uint16(1000)),
@@ -334,12 +310,12 @@ func TestLocateRouter_RouteLocate(t *testing.T) {
 		},
 		{
 			name: "receive LocateSetInfo, return no response",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.LOCATE,
 					SubGroup:  oscar.LocateSetInfo,
 				},
-				snacOut: oscar.SNAC_0x02_0x04_LocateSetInfo{
+				SnacOut: oscar.SNAC_0x02_0x04_LocateSetInfo{
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							{
@@ -350,18 +326,18 @@ func TestLocateRouter_RouteLocate(t *testing.T) {
 					},
 				},
 			},
-			output: XMessage{
-				snacFrame: oscar.SnacFrame{},
+			output: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{},
 			},
 		},
 		{
 			name: "receive LocateSetDirInfo, return LocateSetDirReply",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.LOCATE,
 					SubGroup:  oscar.LocateSetDirInfo,
 				},
-				snacOut: oscar.SNAC_0x02_0x09_LocateSetDirInfo{
+				SnacOut: oscar.SNAC_0x02_0x09_LocateSetDirInfo{
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							{
@@ -372,39 +348,39 @@ func TestLocateRouter_RouteLocate(t *testing.T) {
 					},
 				},
 			},
-			output: XMessage{
-				snacFrame: oscar.SnacFrame{
+			output: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.LOCATE,
 					SubGroup:  oscar.LocateSetDirReply,
 				},
-				snacOut: oscar.SNAC_0x02_0x0A_LocateSetDirReply{
+				SnacOut: oscar.SNAC_0x02_0x0A_LocateSetDirReply{
 					Result: 1,
 				},
 			},
 		},
 		{
 			name: "receive LocateGetDirInfo, return no response",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.LOCATE,
 					SubGroup:  oscar.LocateGetDirInfo,
 				},
-				snacOut: oscar.SNAC_0x02_0x0B_LocateGetDirInfo{
+				SnacOut: oscar.SNAC_0x02_0x0B_LocateGetDirInfo{
 					WatcherScreenNames: "screen-name",
 				},
 			},
-			output: XMessage{
-				snacFrame: oscar.SnacFrame{},
+			output: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{},
 			},
 		},
 		{
 			name: "receive LocateSetKeywordInfo, return LocateSetKeywordReply",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.LOCATE,
 					SubGroup:  oscar.LocateSetKeywordInfo,
 				},
-				snacOut: oscar.SNAC_0x02_0x0F_LocateSetKeywordInfo{
+				SnacOut: oscar.SNAC_0x02_0x0F_LocateSetKeywordInfo{
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							{
@@ -415,33 +391,33 @@ func TestLocateRouter_RouteLocate(t *testing.T) {
 					},
 				},
 			},
-			output: XMessage{
-				snacFrame: oscar.SnacFrame{
+			output: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.LOCATE,
 					SubGroup:  oscar.LocateSetKeywordReply,
 				},
-				snacOut: oscar.SNAC_0x02_0x10_LocateSetKeywordReply{
+				SnacOut: oscar.SNAC_0x02_0x10_LocateSetKeywordReply{
 					Unknown: 1,
 				},
 			},
 		},
 		{
 			name: "receive LocateUserInfoQuery2, return LocateUserInfoReply",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.LOCATE,
 					SubGroup:  oscar.LocateUserInfoQuery2,
 				},
-				snacOut: oscar.SNAC_0x02_0x15_LocateUserInfoQuery2{
+				SnacOut: oscar.SNAC_0x02_0x15_LocateUserInfoQuery2{
 					Type2: 1,
 				},
 			},
-			output: XMessage{
-				snacFrame: oscar.SnacFrame{
+			output: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.LOCATE,
 					SubGroup:  oscar.LocateUserInfoReply,
 				},
-				snacOut: oscar.SNAC_0x02_0x06_LocateUserInfoReply{
+				SnacOut: oscar.SNAC_0x02_0x06_LocateUserInfoReply{
 					TLVUserInfo: oscar.TLVUserInfo{
 						ScreenName: "screen-name",
 					},
@@ -458,14 +434,14 @@ func TestLocateRouter_RouteLocate(t *testing.T) {
 		},
 		{
 			name: "receive LocateGetKeywordInfo, expect ErrUnsupportedSubGroup",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.LOCATE,
 					SubGroup:  oscar.LocateGetKeywordInfo,
 				},
-				snacOut: struct{}{}, // empty SNAC
+				SnacOut: struct{}{}, // empty SNAC
 			},
-			output:    XMessage{}, // empty SNAC
+			output:    oscar.XMessage{}, // empty SNAC
 			expectErr: ErrUnsupportedSubGroup,
 		},
 	}
@@ -482,7 +458,7 @@ func TestLocateRouter_RouteLocate(t *testing.T) {
 				Return(tc.output).
 				Maybe()
 			svc.EXPECT().
-				SetInfoHandler(mock.Anything, mock.Anything, tc.input.snacOut).
+				SetInfoHandler(mock.Anything, mock.Anything, tc.input.SnacOut).
 				Return(tc.handlerErr).
 				Maybe()
 			svc.EXPECT().
@@ -490,7 +466,7 @@ func TestLocateRouter_RouteLocate(t *testing.T) {
 				Return(tc.output).
 				Maybe()
 			svc.EXPECT().
-				UserInfoQuery2Handler(mock.Anything, mock.Anything, tc.input.snacOut).
+				UserInfoQuery2Handler(mock.Anything, mock.Anything, tc.input.SnacOut).
 				Return(tc.output, tc.handlerErr).
 				Maybe()
 
@@ -502,18 +478,18 @@ func TestLocateRouter_RouteLocate(t *testing.T) {
 			}
 
 			bufIn := &bytes.Buffer{}
-			assert.NoError(t, oscar.Marshal(tc.input.snacOut, bufIn))
+			assert.NoError(t, oscar.Marshal(tc.input.SnacOut, bufIn))
 
 			bufOut := &bytes.Buffer{}
 			seq := uint32(1)
 
-			err := router.RouteLocate(nil, nil, tc.input.snacFrame, bufIn, bufOut, &seq)
+			err := router.RouteLocate(nil, nil, tc.input.SnacFrame, bufIn, bufOut, &seq)
 			assert.ErrorIs(t, err, tc.expectErr)
 			if tc.expectErr != nil {
 				return
 			}
 
-			if tc.output.snacFrame == (oscar.SnacFrame{}) {
+			if tc.output.SnacFrame == (oscar.SnacFrame{}) {
 				return // handler doesn't return response
 			}
 
@@ -528,11 +504,11 @@ func TestLocateRouter_RouteLocate(t *testing.T) {
 			// verify the SNAC frame
 			snacFrame := oscar.SnacFrame{}
 			assert.NoError(t, oscar.Unmarshal(&snacFrame, bufOut))
-			assert.Equal(t, tc.output.snacFrame, snacFrame)
+			assert.Equal(t, tc.output.SnacFrame, snacFrame)
 
 			// verify the SNAC message
 			snacBuf := &bytes.Buffer{}
-			assert.NoError(t, oscar.Marshal(tc.output.snacOut, snacBuf))
+			assert.NoError(t, oscar.Marshal(tc.output.SnacOut, snacBuf))
 			assert.Equal(t, snacBuf.Bytes(), bufOut.Bytes())
 		})
 	}

+ 63 - 62
server/oservice.go

@@ -6,32 +6,33 @@ import (
 	"errors"
 	"fmt"
 	"github.com/mkaminski/goaim/oscar"
+	"github.com/mkaminski/goaim/user"
 	"io"
 	"log/slog"
 	"time"
 )
 
 type OServiceHandler interface {
-	ClientVersionsHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x17_OServiceClientVersions) XMessage
-	IdleNotificationHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x01_0x11_OServiceIdleNotification) error
-	RateParamsQueryHandler(ctx context.Context) XMessage
+	ClientVersionsHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x17_OServiceClientVersions) oscar.XMessage
+	IdleNotificationHandler(ctx context.Context, sess *user.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) (XMessage, error)
-	UserInfoQueryHandler(ctx context.Context, sess *Session) XMessage
+	SetUserInfoFieldsHandler(ctx context.Context, sess *user.Session, snacPayloadIn oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (oscar.XMessage, error)
+	UserInfoQueryHandler(ctx context.Context, sess *user.Session) oscar.XMessage
 }
 
 type OServiceBOSHandler interface {
 	OServiceHandler
 	WriteOServiceHostOnline(w io.Writer, sequence *uint32) error
-	ServiceRequestHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x01_0x04_OServiceServiceRequest) (XMessage, error)
-	ClientOnlineHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *Session) error
+	ServiceRequestHandler(ctx context.Context, sess *user.Session, snacPayloadIn oscar.SNAC_0x01_0x04_OServiceServiceRequest) (oscar.XMessage, error)
+	ClientOnlineHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *user.Session) error
 }
 
 type OServiceChatHandler interface {
 	OServiceHandler
 	WriteOServiceHostOnline(w io.Writer, sequence *uint32) error
-	ServiceRequestHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x01_0x04_OServiceServiceRequest) (XMessage, error)
-	ClientOnlineHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *Session, room ChatRoom) error
+	ServiceRequestHandler(ctx context.Context, sess *user.Session, snacPayloadIn oscar.SNAC_0x01_0x04_OServiceServiceRequest) (oscar.XMessage, error)
+	ClientOnlineHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *user.Session, room ChatRoom) error
 }
 
 type OServiceRouter struct {
@@ -39,12 +40,12 @@ 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 *user.Session, SNACFrame oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
 	switch SNACFrame.SubGroup {
 	case oscar.OServiceRateParamsQuery:
 		outSNAC := rt.RateParamsQueryHandler(ctx)
-		rt.logRequestAndResponse(ctx, SNACFrame, nil, outSNAC.snacFrame, outSNAC.snacOut)
-		return writeOutSNAC(SNACFrame, outSNAC.snacFrame, outSNAC.snacOut, sequence, w)
+		rt.logRequestAndResponse(ctx, SNACFrame, nil, outSNAC.SnacFrame, outSNAC.SnacOut)
+		return writeOutSNAC(SNACFrame, outSNAC.SnacFrame, outSNAC.SnacOut, sequence, w)
 	case oscar.OServiceRateParamsSubAdd:
 		inSNAC := oscar.SNAC_0x01_0x08_OServiceRateParamsSubAdd{}
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
@@ -55,8 +56,8 @@ func (rt OServiceRouter) RouteOService(ctx context.Context, sess *Session, SNACF
 		return oscar.Unmarshal(&inSNAC, r)
 	case oscar.OServiceUserInfoQuery:
 		outSNAC := rt.UserInfoQueryHandler(ctx, sess)
-		rt.logRequestAndResponse(ctx, SNACFrame, nil, outSNAC.snacFrame, outSNAC.snacOut)
-		return writeOutSNAC(SNACFrame, outSNAC.snacFrame, outSNAC.snacOut, sequence, w)
+		rt.logRequestAndResponse(ctx, SNACFrame, nil, outSNAC.SnacFrame, outSNAC.SnacOut)
+		return writeOutSNAC(SNACFrame, outSNAC.SnacFrame, outSNAC.SnacOut, sequence, w)
 	case oscar.OServiceIdleNotification:
 		inSNAC := oscar.SNAC_0x01_0x11_OServiceIdleNotification{}
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
@@ -70,8 +71,8 @@ func (rt OServiceRouter) RouteOService(ctx context.Context, sess *Session, SNACF
 			return err
 		}
 		outSNAC := rt.ClientVersionsHandler(ctx, inSNAC)
-		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.snacFrame, outSNAC.snacOut)
-		return writeOutSNAC(SNACFrame, outSNAC.snacFrame, outSNAC.snacOut, sequence, w)
+		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.SnacFrame, outSNAC.SnacOut)
+		return writeOutSNAC(SNACFrame, outSNAC.SnacFrame, outSNAC.SnacOut, sequence, w)
 	case oscar.OServiceSetUserInfoFields:
 		inSNAC := oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields{}
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
@@ -81,8 +82,8 @@ func (rt OServiceRouter) RouteOService(ctx context.Context, sess *Session, SNACF
 		if err != nil {
 			return err
 		}
-		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.snacFrame, outSNAC.snacOut)
-		return writeOutSNAC(SNACFrame, outSNAC.snacFrame, outSNAC.snacOut, sequence, w)
+		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.SnacFrame, outSNAC.SnacOut)
+		return writeOutSNAC(SNACFrame, outSNAC.SnacFrame, outSNAC.SnacOut, sequence, w)
 	default:
 		return ErrUnsupportedSubGroup
 	}
@@ -113,7 +114,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 *user.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{}
@@ -127,8 +128,8 @@ func (rt OServiceBOSRouter) RouteOService(ctx context.Context, sess *Session, SN
 		case err != nil:
 			return err
 		}
-		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.snacFrame, outSNAC.snacOut)
-		return writeOutSNAC(SNACFrame, outSNAC.snacFrame, outSNAC.snacOut, sequence, w)
+		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.SnacFrame, outSNAC.SnacOut)
+		return writeOutSNAC(SNACFrame, outSNAC.SnacFrame, outSNAC.SnacOut, sequence, w)
 	case oscar.OServiceClientOnline:
 		inSNAC := oscar.SNAC_0x01_0x02_OServiceClientOnline{}
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
@@ -147,7 +148,7 @@ type OServiceChatRouter struct {
 	OServiceChatHandler
 }
 
-func (rt OServiceChatRouter) RouteOService(ctx context.Context, sess *Session, room ChatRoom, SNACFrame oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
+func (rt OServiceChatRouter) RouteOService(ctx context.Context, sess *user.Session, room ChatRoom, SNACFrame oscar.SnacFrame, r io.Reader, w io.Writer, sequence *uint32) error {
 	switch SNACFrame.SubGroup {
 	case oscar.OServiceServiceRequest:
 		inSNAC := oscar.SNAC_0x01_0x04_OServiceServiceRequest{}
@@ -161,8 +162,8 @@ func (rt OServiceChatRouter) RouteOService(ctx context.Context, sess *Session, r
 		case err != nil:
 			return err
 		}
-		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.snacFrame, outSNAC.snacOut)
-		return writeOutSNAC(SNACFrame, outSNAC.snacFrame, outSNAC.snacOut, sequence, w)
+		rt.logRequestAndResponse(ctx, SNACFrame, inSNAC, outSNAC.SnacFrame, outSNAC.SnacOut)
+		return writeOutSNAC(SNACFrame, outSNAC.SnacFrame, outSNAC.SnacOut, sequence, w)
 	case oscar.OServiceClientOnline:
 		inSNAC := oscar.SNAC_0x01_0x02_OServiceClientOnline{}
 		if err := oscar.Unmarshal(&inSNAC, r); err != nil {
@@ -182,19 +183,19 @@ type OServiceService struct {
 	sm  SessionManager
 }
 
-func (s OServiceService) ClientVersionsHandler(_ context.Context, snacPayloadIn oscar.SNAC_0x01_0x17_OServiceClientVersions) XMessage {
-	return XMessage{
-		snacFrame: oscar.SnacFrame{
+func (s OServiceService) ClientVersionsHandler(_ context.Context, snacPayloadIn oscar.SNAC_0x01_0x17_OServiceClientVersions) oscar.XMessage {
+	return oscar.XMessage{
+		SnacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.OSERVICE,
 			SubGroup:  oscar.OServiceHostVersions,
 		},
-		snacOut: oscar.SNAC_0x01_0x18_OServiceHostVersions{
+		SnacOut: oscar.SNAC_0x01_0x18_OServiceHostVersions{
 			Versions: snacPayloadIn.Versions,
 		},
 	}
 }
 
-func (s OServiceService) RateParamsQueryHandler(_ context.Context) XMessage {
+func (s OServiceService) RateParamsQueryHandler(_ context.Context) oscar.XMessage {
 	snacFrameOut := oscar.SnacFrame{
 		FoodGroup: oscar.OSERVICE,
 		SubGroup:  oscar.OServiceRateParamsReply,
@@ -255,53 +256,53 @@ func (s OServiceService) RateParamsQueryHandler(_ context.Context) XMessage {
 		}
 	}
 
-	return XMessage{
-		snacFrame: snacFrameOut,
-		snacOut:   snacPayloadOut,
+	return oscar.XMessage{
+		SnacFrame: snacFrameOut,
+		SnacOut:   snacPayloadOut,
 	}
 }
 
-func (s OServiceService) UserInfoQueryHandler(_ context.Context, sess *Session) XMessage {
-	return XMessage{
-		snacFrame: oscar.SnacFrame{
+func (s OServiceService) UserInfoQueryHandler(_ context.Context, sess *user.Session) oscar.XMessage {
+	return oscar.XMessage{
+		SnacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.OSERVICE,
 			SubGroup:  oscar.OServiceUserInfoUpdate,
 		},
-		snacOut: oscar.SNAC_0x01_0x0F_OServiceUserInfoUpdate{
-			TLVUserInfo: sess.GetTLVUserInfo(),
+		SnacOut: oscar.SNAC_0x01_0x0F_OServiceUserInfoUpdate{
+			TLVUserInfo: sess.TLVUserInfo(),
 		},
 	}
 }
 
-func (s OServiceService) SetUserInfoFieldsHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (XMessage, error) {
+func (s OServiceService) SetUserInfoFieldsHandler(ctx context.Context, sess *user.Session, snacPayloadIn oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (oscar.XMessage, error) {
 	if status, hasStatus := snacPayloadIn.GetUint32(0x06); hasStatus {
 		switch status {
 		case 0x000:
 			sess.SetInvisible(false)
 			if err := BroadcastArrival(ctx, sess, s.sm, s.fm); err != nil {
-				return XMessage{}, err
+				return oscar.XMessage{}, err
 			}
 		case 0x100:
 			sess.SetInvisible(true)
 			if err := BroadcastDeparture(ctx, sess, s.sm, s.fm); err != nil {
-				return XMessage{}, err
+				return oscar.XMessage{}, err
 			}
 		default:
-			return XMessage{}, fmt.Errorf("don't know what to do with status %d", status)
+			return oscar.XMessage{}, fmt.Errorf("don't know what to do with status %d", status)
 		}
 	}
-	return XMessage{
-		snacFrame: oscar.SnacFrame{
+	return oscar.XMessage{
+		SnacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.OSERVICE,
 			SubGroup:  oscar.OServiceUserInfoUpdate,
 		},
-		snacOut: oscar.SNAC_0x01_0x0F_OServiceUserInfoUpdate{
-			TLVUserInfo: sess.GetTLVUserInfo(),
+		SnacOut: oscar.SNAC_0x01_0x0F_OServiceUserInfoUpdate{
+			TLVUserInfo: sess.TLVUserInfo(),
 		},
 	}, nil
 }
 
-func (s OServiceService) IdleNotificationHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x01_0x11_OServiceIdleNotification) error {
+func (s OServiceService) IdleNotificationHandler(ctx context.Context, sess *user.Session, snacPayloadIn oscar.SNAC_0x01_0x11_OServiceIdleNotification) error {
 	if snacPayloadIn.IdleTime == 0 {
 		sess.SetActive()
 	} else {
@@ -320,39 +321,39 @@ type OServiceServiceForBOS struct {
 	cr *ChatRegistry
 }
 
-func (s OServiceServiceForBOS) ServiceRequestHandler(_ context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x01_0x04_OServiceServiceRequest) (XMessage, error) {
+func (s OServiceServiceForBOS) ServiceRequestHandler(_ context.Context, sess *user.Session, snacPayloadIn oscar.SNAC_0x01_0x04_OServiceServiceRequest) (oscar.XMessage, error) {
 	if snacPayloadIn.FoodGroup != oscar.CHAT {
-		return XMessage{}, ErrUnsupportedSubGroup
+		return oscar.XMessage{}, ErrUnsupportedSubGroup
 	}
 
 	roomMeta, ok := snacPayloadIn.GetSlice(0x01)
 	if !ok {
-		return XMessage{}, errors.New("missing room info")
+		return oscar.XMessage{}, errors.New("missing room info")
 	}
 
 	roomSnac := oscar.SNAC_0x01_0x04_TLVRoomInfo{}
 	if err := oscar.Unmarshal(&roomSnac, bytes.NewBuffer(roomMeta)); err != nil {
-		return XMessage{}, err
+		return oscar.XMessage{}, err
 	}
 
 	room, err := s.cr.Retrieve(string(roomSnac.Cookie))
 	if err != nil {
-		return XMessage{}, ErrUnsupportedSubGroup
+		return oscar.XMessage{}, ErrUnsupportedSubGroup
 	}
-	room.NewSessionWithSN(sess.ID, sess.ScreenName)
+	room.NewSessionWithSN(sess.ID(), sess.ScreenName())
 
-	return XMessage{
-		snacFrame: oscar.SnacFrame{
+	return oscar.XMessage{
+		SnacFrame: oscar.SnacFrame{
 			FoodGroup: oscar.OSERVICE,
 			SubGroup:  oscar.OServiceServiceResponse,
 		},
-		snacOut: oscar.SNAC_0x01_0x05_OServiceServiceResponse{
+		SnacOut: oscar.SNAC_0x01_0x05_OServiceServiceResponse{
 			TLVRestBlock: oscar.TLVRestBlock{
 				TLVList: oscar.TLVList{
 					oscar.NewTLV(oscar.OServiceTLVTagsReconnectHere, Address(s.cfg.OSCARHost, s.cfg.ChatPort)),
 					oscar.NewTLV(oscar.OServiceTLVTagsLoginCookie, ChatCookie{
 						Cookie: []byte(room.Cookie),
-						SessID: sess.ID,
+						SessID: sess.ID(),
 					}),
 					oscar.NewTLV(oscar.OServiceTLVTagsGroupID, oscar.CHAT),
 					oscar.NewTLV(oscar.OServiceTLVTagsSSLCertName, ""),
@@ -382,16 +383,16 @@ func (s OServiceServiceForBOS) WriteOServiceHostOnline(w io.Writer, sequence *ui
 	return writeOutSNAC(oscar.SnacFrame{}, snacFrameOut, snacPayloadOut, sequence, w)
 }
 
-func (s OServiceServiceForBOS) ClientOnlineHandler(ctx context.Context, _ oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *Session) error {
+func (s OServiceServiceForBOS) ClientOnlineHandler(ctx context.Context, _ oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *user.Session) error {
 	if err := BroadcastArrival(ctx, sess, s.sm, s.fm); err != nil {
 		return err
 	}
-	buddies, err := s.fm.Buddies(sess.ScreenName)
+	buddies, err := s.fm.Buddies(sess.ScreenName())
 	if err != nil {
 		return err
 	}
 	for _, buddy := range buddies {
-		err := UnicastArrival(ctx, buddy, sess.ScreenName, s.sm)
+		err := UnicastArrival(ctx, buddy, sess.ScreenName(), s.sm)
 		switch {
 		case errors.Is(err, ErrSessNotFound):
 			continue
@@ -426,8 +427,8 @@ type OServiceServiceForChat struct {
 	RouteLogger
 }
 
-func (s OServiceServiceForChat) ServiceRequestHandler(_ context.Context, _ *Session, _ oscar.SNAC_0x01_0x04_OServiceServiceRequest) (XMessage, error) {
-	return XMessage{}, ErrUnsupportedSubGroup
+func (s OServiceServiceForChat) ServiceRequestHandler(_ context.Context, _ *user.Session, _ oscar.SNAC_0x01_0x04_OServiceServiceRequest) (oscar.XMessage, error) {
+	return oscar.XMessage{}, ErrUnsupportedSubGroup
 }
 
 func (s OServiceServiceForChat) WriteOServiceHostOnline(w io.Writer, sequence *uint32) error {
@@ -441,7 +442,7 @@ func (s OServiceServiceForChat) WriteOServiceHostOnline(w io.Writer, sequence *u
 	return writeOutSNAC(oscar.SnacFrame{}, snacFrameOut, snacPayloadOut, sequence, w)
 }
 
-func (s OServiceServiceForChat) ClientOnlineHandler(ctx context.Context, _ oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *Session, room ChatRoom) error {
+func (s OServiceServiceForChat) ClientOnlineHandler(ctx context.Context, _ oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *user.Session, room ChatRoom) error {
 	SendChatRoomInfoUpdate(ctx, sess, room)
 	AlertUserJoined(ctx, sess, room)
 	SetOnlineChatUsers(ctx, sess, room)

+ 57 - 55
server/oservice_bos_mock.go

@@ -9,6 +9,8 @@ import (
 	mock "github.com/stretchr/testify/mock"
 
 	oscar "github.com/mkaminski/goaim/oscar"
+
+	user "github.com/mkaminski/goaim/user"
 )
 
 // MockOServiceBOSHandler is an autogenerated mock type for the OServiceBOSHandler type
@@ -25,11 +27,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 *user.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, *user.Session) error); ok {
 		r0 = rf(ctx, snacPayloadIn, sess)
 	} else {
 		r0 = ret.Error(0)
@@ -46,14 +48,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 *user.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 *user.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].(*user.Session))
 	})
 	return _c
 }
@@ -63,20 +65,20 @@ 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, *user.Session) error) *MockOServiceBOSHandler_ClientOnlineHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }
 
 // ClientVersionsHandler provides a mock function with given fields: ctx, snacPayloadIn
-func (_m *MockOServiceBOSHandler) ClientVersionsHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x17_OServiceClientVersions) XMessage {
+func (_m *MockOServiceBOSHandler) ClientVersionsHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x17_OServiceClientVersions) oscar.XMessage {
 	ret := _m.Called(ctx, snacPayloadIn)
 
-	var r0 XMessage
-	if rf, ok := ret.Get(0).(func(context.Context, oscar.SNAC_0x01_0x17_OServiceClientVersions) XMessage); ok {
+	var r0 oscar.XMessage
+	if rf, ok := ret.Get(0).(func(context.Context, oscar.SNAC_0x01_0x17_OServiceClientVersions) oscar.XMessage); ok {
 		r0 = rf(ctx, snacPayloadIn)
 	} else {
-		r0 = ret.Get(0).(XMessage)
+		r0 = ret.Get(0).(oscar.XMessage)
 	}
 
 	return r0
@@ -101,22 +103,22 @@ func (_c *MockOServiceBOSHandler_ClientVersionsHandler_Call) Run(run func(ctx co
 	return _c
 }
 
-func (_c *MockOServiceBOSHandler_ClientVersionsHandler_Call) Return(_a0 XMessage) *MockOServiceBOSHandler_ClientVersionsHandler_Call {
+func (_c *MockOServiceBOSHandler_ClientVersionsHandler_Call) Return(_a0 oscar.XMessage) *MockOServiceBOSHandler_ClientVersionsHandler_Call {
 	_c.Call.Return(_a0)
 	return _c
 }
 
-func (_c *MockOServiceBOSHandler_ClientVersionsHandler_Call) RunAndReturn(run func(context.Context, oscar.SNAC_0x01_0x17_OServiceClientVersions) XMessage) *MockOServiceBOSHandler_ClientVersionsHandler_Call {
+func (_c *MockOServiceBOSHandler_ClientVersionsHandler_Call) RunAndReturn(run func(context.Context, oscar.SNAC_0x01_0x17_OServiceClientVersions) oscar.XMessage) *MockOServiceBOSHandler_ClientVersionsHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }
 
 // IdleNotificationHandler provides a mock function with given fields: ctx, sess, snacPayloadIn
-func (_m *MockOServiceBOSHandler) IdleNotificationHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x01_0x11_OServiceIdleNotification) error {
+func (_m *MockOServiceBOSHandler) IdleNotificationHandler(ctx context.Context, sess *user.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, *user.Session, oscar.SNAC_0x01_0x11_OServiceIdleNotification) error); ok {
 		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r0 = ret.Error(0)
@@ -132,15 +134,15 @@ type MockOServiceBOSHandler_IdleNotificationHandler_Call struct {
 
 // IdleNotificationHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *user.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 *user.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].(*user.Session), args[2].(oscar.SNAC_0x01_0x11_OServiceIdleNotification))
 	})
 	return _c
 }
@@ -150,20 +152,20 @@ 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, *user.Session, oscar.SNAC_0x01_0x11_OServiceIdleNotification) error) *MockOServiceBOSHandler_IdleNotificationHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }
 
 // RateParamsQueryHandler provides a mock function with given fields: ctx
-func (_m *MockOServiceBOSHandler) RateParamsQueryHandler(ctx context.Context) XMessage {
+func (_m *MockOServiceBOSHandler) RateParamsQueryHandler(ctx context.Context) oscar.XMessage {
 	ret := _m.Called(ctx)
 
-	var r0 XMessage
-	if rf, ok := ret.Get(0).(func(context.Context) XMessage); ok {
+	var r0 oscar.XMessage
+	if rf, ok := ret.Get(0).(func(context.Context) oscar.XMessage); ok {
 		r0 = rf(ctx)
 	} else {
-		r0 = ret.Get(0).(XMessage)
+		r0 = ret.Get(0).(oscar.XMessage)
 	}
 
 	return r0
@@ -187,12 +189,12 @@ func (_c *MockOServiceBOSHandler_RateParamsQueryHandler_Call) Run(run func(ctx c
 	return _c
 }
 
-func (_c *MockOServiceBOSHandler_RateParamsQueryHandler_Call) Return(_a0 XMessage) *MockOServiceBOSHandler_RateParamsQueryHandler_Call {
+func (_c *MockOServiceBOSHandler_RateParamsQueryHandler_Call) Return(_a0 oscar.XMessage) *MockOServiceBOSHandler_RateParamsQueryHandler_Call {
 	_c.Call.Return(_a0)
 	return _c
 }
 
-func (_c *MockOServiceBOSHandler_RateParamsQueryHandler_Call) RunAndReturn(run func(context.Context) XMessage) *MockOServiceBOSHandler_RateParamsQueryHandler_Call {
+func (_c *MockOServiceBOSHandler_RateParamsQueryHandler_Call) RunAndReturn(run func(context.Context) oscar.XMessage) *MockOServiceBOSHandler_RateParamsQueryHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }
@@ -232,21 +234,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) (XMessage, error) {
+func (_m *MockOServiceBOSHandler) ServiceRequestHandler(ctx context.Context, sess *user.Session, snacPayloadIn oscar.SNAC_0x01_0x04_OServiceServiceRequest) (oscar.XMessage, error) {
 	ret := _m.Called(ctx, sess, snacPayloadIn)
 
-	var r0 XMessage
+	var r0 oscar.XMessage
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x01_0x04_OServiceServiceRequest) (XMessage, error)); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *user.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) XMessage); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *user.Session, oscar.SNAC_0x01_0x04_OServiceServiceRequest) oscar.XMessage); ok {
 		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
-		r0 = ret.Get(0).(XMessage)
+		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, *user.Session, oscar.SNAC_0x01_0x04_OServiceServiceRequest) error); ok {
 		r1 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r1 = ret.Error(1)
@@ -262,45 +264,45 @@ type MockOServiceBOSHandler_ServiceRequestHandler_Call struct {
 
 // ServiceRequestHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *user.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 *user.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].(*user.Session), args[2].(oscar.SNAC_0x01_0x04_OServiceServiceRequest))
 	})
 	return _c
 }
 
-func (_c *MockOServiceBOSHandler_ServiceRequestHandler_Call) Return(_a0 XMessage, _a1 error) *MockOServiceBOSHandler_ServiceRequestHandler_Call {
+func (_c *MockOServiceBOSHandler_ServiceRequestHandler_Call) Return(_a0 oscar.XMessage, _a1 error) *MockOServiceBOSHandler_ServiceRequestHandler_Call {
 	_c.Call.Return(_a0, _a1)
 	return _c
 }
 
-func (_c *MockOServiceBOSHandler_ServiceRequestHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x01_0x04_OServiceServiceRequest) (XMessage, error)) *MockOServiceBOSHandler_ServiceRequestHandler_Call {
+func (_c *MockOServiceBOSHandler_ServiceRequestHandler_Call) RunAndReturn(run func(context.Context, *user.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) (XMessage, error) {
+func (_m *MockOServiceBOSHandler) SetUserInfoFieldsHandler(ctx context.Context, sess *user.Session, snacPayloadIn oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (oscar.XMessage, error) {
 	ret := _m.Called(ctx, sess, snacPayloadIn)
 
-	var r0 XMessage
+	var r0 oscar.XMessage
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (XMessage, error)); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *user.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) XMessage); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *user.Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) oscar.XMessage); ok {
 		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
-		r0 = ret.Get(0).(XMessage)
+		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, *user.Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) error); ok {
 		r1 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r1 = ret.Error(1)
@@ -316,38 +318,38 @@ type MockOServiceBOSHandler_SetUserInfoFieldsHandler_Call struct {
 
 // SetUserInfoFieldsHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *user.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 *user.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].(*user.Session), args[2].(oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields))
 	})
 	return _c
 }
 
-func (_c *MockOServiceBOSHandler_SetUserInfoFieldsHandler_Call) Return(_a0 XMessage, _a1 error) *MockOServiceBOSHandler_SetUserInfoFieldsHandler_Call {
+func (_c *MockOServiceBOSHandler_SetUserInfoFieldsHandler_Call) Return(_a0 oscar.XMessage, _a1 error) *MockOServiceBOSHandler_SetUserInfoFieldsHandler_Call {
 	_c.Call.Return(_a0, _a1)
 	return _c
 }
 
-func (_c *MockOServiceBOSHandler_SetUserInfoFieldsHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (XMessage, error)) *MockOServiceBOSHandler_SetUserInfoFieldsHandler_Call {
+func (_c *MockOServiceBOSHandler_SetUserInfoFieldsHandler_Call) RunAndReturn(run func(context.Context, *user.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) XMessage {
+func (_m *MockOServiceBOSHandler) UserInfoQueryHandler(ctx context.Context, sess *user.Session) oscar.XMessage {
 	ret := _m.Called(ctx, sess)
 
-	var r0 XMessage
-	if rf, ok := ret.Get(0).(func(context.Context, *Session) XMessage); ok {
+	var r0 oscar.XMessage
+	if rf, ok := ret.Get(0).(func(context.Context, *user.Session) oscar.XMessage); ok {
 		r0 = rf(ctx, sess)
 	} else {
-		r0 = ret.Get(0).(XMessage)
+		r0 = ret.Get(0).(oscar.XMessage)
 	}
 
 	return r0
@@ -360,24 +362,24 @@ type MockOServiceBOSHandler_UserInfoQueryHandler_Call struct {
 
 // UserInfoQueryHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *user.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 *user.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].(*user.Session))
 	})
 	return _c
 }
 
-func (_c *MockOServiceBOSHandler_UserInfoQueryHandler_Call) Return(_a0 XMessage) *MockOServiceBOSHandler_UserInfoQueryHandler_Call {
+func (_c *MockOServiceBOSHandler_UserInfoQueryHandler_Call) Return(_a0 oscar.XMessage) *MockOServiceBOSHandler_UserInfoQueryHandler_Call {
 	_c.Call.Return(_a0)
 	return _c
 }
 
-func (_c *MockOServiceBOSHandler_UserInfoQueryHandler_Call) RunAndReturn(run func(context.Context, *Session) XMessage) *MockOServiceBOSHandler_UserInfoQueryHandler_Call {
+func (_c *MockOServiceBOSHandler_UserInfoQueryHandler_Call) RunAndReturn(run func(context.Context, *user.Session) oscar.XMessage) *MockOServiceBOSHandler_UserInfoQueryHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }

+ 57 - 55
server/oservice_chat_mock.go

@@ -9,6 +9,8 @@ import (
 	mock "github.com/stretchr/testify/mock"
 
 	oscar "github.com/mkaminski/goaim/oscar"
+
+	user "github.com/mkaminski/goaim/user"
 )
 
 // MockOServiceChatHandler is an autogenerated mock type for the OServiceChatHandler type
@@ -25,11 +27,11 @@ func (_m *MockOServiceChatHandler) EXPECT() *MockOServiceChatHandler_Expecter {
 }
 
 // ClientOnlineHandler provides a mock function with given fields: ctx, snacPayloadIn, sess, room
-func (_m *MockOServiceChatHandler) ClientOnlineHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *Session, room ChatRoom) error {
+func (_m *MockOServiceChatHandler) ClientOnlineHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *user.Session, room ChatRoom) error {
 	ret := _m.Called(ctx, snacPayloadIn, sess, room)
 
 	var r0 error
-	if rf, ok := ret.Get(0).(func(context.Context, oscar.SNAC_0x01_0x02_OServiceClientOnline, *Session, ChatRoom) error); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, oscar.SNAC_0x01_0x02_OServiceClientOnline, *user.Session, ChatRoom) error); ok {
 		r0 = rf(ctx, snacPayloadIn, sess, room)
 	} else {
 		r0 = ret.Error(0)
@@ -46,15 +48,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
+//   - sess *user.Session
 //   - room ChatRoom
 func (_e *MockOServiceChatHandler_Expecter) ClientOnlineHandler(ctx interface{}, snacPayloadIn interface{}, sess interface{}, room interface{}) *MockOServiceChatHandler_ClientOnlineHandler_Call {
 	return &MockOServiceChatHandler_ClientOnlineHandler_Call{Call: _e.mock.On("ClientOnlineHandler", ctx, snacPayloadIn, sess, room)}
 }
 
-func (_c *MockOServiceChatHandler_ClientOnlineHandler_Call) Run(run func(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *Session, room ChatRoom)) *MockOServiceChatHandler_ClientOnlineHandler_Call {
+func (_c *MockOServiceChatHandler_ClientOnlineHandler_Call) Run(run func(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x02_OServiceClientOnline, sess *user.Session, room ChatRoom)) *MockOServiceChatHandler_ClientOnlineHandler_Call {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(oscar.SNAC_0x01_0x02_OServiceClientOnline), args[2].(*Session), args[3].(ChatRoom))
+		run(args[0].(context.Context), args[1].(oscar.SNAC_0x01_0x02_OServiceClientOnline), args[2].(*user.Session), args[3].(ChatRoom))
 	})
 	return _c
 }
@@ -64,20 +66,20 @@ 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, ChatRoom) error) *MockOServiceChatHandler_ClientOnlineHandler_Call {
+func (_c *MockOServiceChatHandler_ClientOnlineHandler_Call) RunAndReturn(run func(context.Context, oscar.SNAC_0x01_0x02_OServiceClientOnline, *user.Session, ChatRoom) error) *MockOServiceChatHandler_ClientOnlineHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }
 
 // ClientVersionsHandler provides a mock function with given fields: ctx, snacPayloadIn
-func (_m *MockOServiceChatHandler) ClientVersionsHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x17_OServiceClientVersions) XMessage {
+func (_m *MockOServiceChatHandler) ClientVersionsHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x17_OServiceClientVersions) oscar.XMessage {
 	ret := _m.Called(ctx, snacPayloadIn)
 
-	var r0 XMessage
-	if rf, ok := ret.Get(0).(func(context.Context, oscar.SNAC_0x01_0x17_OServiceClientVersions) XMessage); ok {
+	var r0 oscar.XMessage
+	if rf, ok := ret.Get(0).(func(context.Context, oscar.SNAC_0x01_0x17_OServiceClientVersions) oscar.XMessage); ok {
 		r0 = rf(ctx, snacPayloadIn)
 	} else {
-		r0 = ret.Get(0).(XMessage)
+		r0 = ret.Get(0).(oscar.XMessage)
 	}
 
 	return r0
@@ -102,22 +104,22 @@ func (_c *MockOServiceChatHandler_ClientVersionsHandler_Call) Run(run func(ctx c
 	return _c
 }
 
-func (_c *MockOServiceChatHandler_ClientVersionsHandler_Call) Return(_a0 XMessage) *MockOServiceChatHandler_ClientVersionsHandler_Call {
+func (_c *MockOServiceChatHandler_ClientVersionsHandler_Call) Return(_a0 oscar.XMessage) *MockOServiceChatHandler_ClientVersionsHandler_Call {
 	_c.Call.Return(_a0)
 	return _c
 }
 
-func (_c *MockOServiceChatHandler_ClientVersionsHandler_Call) RunAndReturn(run func(context.Context, oscar.SNAC_0x01_0x17_OServiceClientVersions) XMessage) *MockOServiceChatHandler_ClientVersionsHandler_Call {
+func (_c *MockOServiceChatHandler_ClientVersionsHandler_Call) RunAndReturn(run func(context.Context, oscar.SNAC_0x01_0x17_OServiceClientVersions) oscar.XMessage) *MockOServiceChatHandler_ClientVersionsHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }
 
 // IdleNotificationHandler provides a mock function with given fields: ctx, sess, snacPayloadIn
-func (_m *MockOServiceChatHandler) IdleNotificationHandler(ctx context.Context, sess *Session, snacPayloadIn oscar.SNAC_0x01_0x11_OServiceIdleNotification) error {
+func (_m *MockOServiceChatHandler) IdleNotificationHandler(ctx context.Context, sess *user.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, *user.Session, oscar.SNAC_0x01_0x11_OServiceIdleNotification) error); ok {
 		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r0 = ret.Error(0)
@@ -133,15 +135,15 @@ type MockOServiceChatHandler_IdleNotificationHandler_Call struct {
 
 // IdleNotificationHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *user.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 *user.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].(*user.Session), args[2].(oscar.SNAC_0x01_0x11_OServiceIdleNotification))
 	})
 	return _c
 }
@@ -151,20 +153,20 @@ 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, *user.Session, oscar.SNAC_0x01_0x11_OServiceIdleNotification) error) *MockOServiceChatHandler_IdleNotificationHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }
 
 // RateParamsQueryHandler provides a mock function with given fields: ctx
-func (_m *MockOServiceChatHandler) RateParamsQueryHandler(ctx context.Context) XMessage {
+func (_m *MockOServiceChatHandler) RateParamsQueryHandler(ctx context.Context) oscar.XMessage {
 	ret := _m.Called(ctx)
 
-	var r0 XMessage
-	if rf, ok := ret.Get(0).(func(context.Context) XMessage); ok {
+	var r0 oscar.XMessage
+	if rf, ok := ret.Get(0).(func(context.Context) oscar.XMessage); ok {
 		r0 = rf(ctx)
 	} else {
-		r0 = ret.Get(0).(XMessage)
+		r0 = ret.Get(0).(oscar.XMessage)
 	}
 
 	return r0
@@ -188,12 +190,12 @@ func (_c *MockOServiceChatHandler_RateParamsQueryHandler_Call) Run(run func(ctx
 	return _c
 }
 
-func (_c *MockOServiceChatHandler_RateParamsQueryHandler_Call) Return(_a0 XMessage) *MockOServiceChatHandler_RateParamsQueryHandler_Call {
+func (_c *MockOServiceChatHandler_RateParamsQueryHandler_Call) Return(_a0 oscar.XMessage) *MockOServiceChatHandler_RateParamsQueryHandler_Call {
 	_c.Call.Return(_a0)
 	return _c
 }
 
-func (_c *MockOServiceChatHandler_RateParamsQueryHandler_Call) RunAndReturn(run func(context.Context) XMessage) *MockOServiceChatHandler_RateParamsQueryHandler_Call {
+func (_c *MockOServiceChatHandler_RateParamsQueryHandler_Call) RunAndReturn(run func(context.Context) oscar.XMessage) *MockOServiceChatHandler_RateParamsQueryHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }
@@ -233,21 +235,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) (XMessage, error) {
+func (_m *MockOServiceChatHandler) ServiceRequestHandler(ctx context.Context, sess *user.Session, snacPayloadIn oscar.SNAC_0x01_0x04_OServiceServiceRequest) (oscar.XMessage, error) {
 	ret := _m.Called(ctx, sess, snacPayloadIn)
 
-	var r0 XMessage
+	var r0 oscar.XMessage
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x01_0x04_OServiceServiceRequest) (XMessage, error)); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *user.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) XMessage); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *user.Session, oscar.SNAC_0x01_0x04_OServiceServiceRequest) oscar.XMessage); ok {
 		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
-		r0 = ret.Get(0).(XMessage)
+		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, *user.Session, oscar.SNAC_0x01_0x04_OServiceServiceRequest) error); ok {
 		r1 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r1 = ret.Error(1)
@@ -263,45 +265,45 @@ type MockOServiceChatHandler_ServiceRequestHandler_Call struct {
 
 // ServiceRequestHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *user.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 *user.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].(*user.Session), args[2].(oscar.SNAC_0x01_0x04_OServiceServiceRequest))
 	})
 	return _c
 }
 
-func (_c *MockOServiceChatHandler_ServiceRequestHandler_Call) Return(_a0 XMessage, _a1 error) *MockOServiceChatHandler_ServiceRequestHandler_Call {
+func (_c *MockOServiceChatHandler_ServiceRequestHandler_Call) Return(_a0 oscar.XMessage, _a1 error) *MockOServiceChatHandler_ServiceRequestHandler_Call {
 	_c.Call.Return(_a0, _a1)
 	return _c
 }
 
-func (_c *MockOServiceChatHandler_ServiceRequestHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x01_0x04_OServiceServiceRequest) (XMessage, error)) *MockOServiceChatHandler_ServiceRequestHandler_Call {
+func (_c *MockOServiceChatHandler_ServiceRequestHandler_Call) RunAndReturn(run func(context.Context, *user.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) (XMessage, error) {
+func (_m *MockOServiceChatHandler) SetUserInfoFieldsHandler(ctx context.Context, sess *user.Session, snacPayloadIn oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (oscar.XMessage, error) {
 	ret := _m.Called(ctx, sess, snacPayloadIn)
 
-	var r0 XMessage
+	var r0 oscar.XMessage
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (XMessage, error)); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *user.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) XMessage); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *user.Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) oscar.XMessage); ok {
 		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
-		r0 = ret.Get(0).(XMessage)
+		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, *user.Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) error); ok {
 		r1 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r1 = ret.Error(1)
@@ -317,38 +319,38 @@ type MockOServiceChatHandler_SetUserInfoFieldsHandler_Call struct {
 
 // SetUserInfoFieldsHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *user.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 *user.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].(*user.Session), args[2].(oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields))
 	})
 	return _c
 }
 
-func (_c *MockOServiceChatHandler_SetUserInfoFieldsHandler_Call) Return(_a0 XMessage, _a1 error) *MockOServiceChatHandler_SetUserInfoFieldsHandler_Call {
+func (_c *MockOServiceChatHandler_SetUserInfoFieldsHandler_Call) Return(_a0 oscar.XMessage, _a1 error) *MockOServiceChatHandler_SetUserInfoFieldsHandler_Call {
 	_c.Call.Return(_a0, _a1)
 	return _c
 }
 
-func (_c *MockOServiceChatHandler_SetUserInfoFieldsHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (XMessage, error)) *MockOServiceChatHandler_SetUserInfoFieldsHandler_Call {
+func (_c *MockOServiceChatHandler_SetUserInfoFieldsHandler_Call) RunAndReturn(run func(context.Context, *user.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) XMessage {
+func (_m *MockOServiceChatHandler) UserInfoQueryHandler(ctx context.Context, sess *user.Session) oscar.XMessage {
 	ret := _m.Called(ctx, sess)
 
-	var r0 XMessage
-	if rf, ok := ret.Get(0).(func(context.Context, *Session) XMessage); ok {
+	var r0 oscar.XMessage
+	if rf, ok := ret.Get(0).(func(context.Context, *user.Session) oscar.XMessage); ok {
 		r0 = rf(ctx, sess)
 	} else {
-		r0 = ret.Get(0).(XMessage)
+		r0 = ret.Get(0).(oscar.XMessage)
 	}
 
 	return r0
@@ -361,24 +363,24 @@ type MockOServiceChatHandler_UserInfoQueryHandler_Call struct {
 
 // UserInfoQueryHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *user.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 *user.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].(*user.Session))
 	})
 	return _c
 }
 
-func (_c *MockOServiceChatHandler_UserInfoQueryHandler_Call) Return(_a0 XMessage) *MockOServiceChatHandler_UserInfoQueryHandler_Call {
+func (_c *MockOServiceChatHandler_UserInfoQueryHandler_Call) Return(_a0 oscar.XMessage) *MockOServiceChatHandler_UserInfoQueryHandler_Call {
 	_c.Call.Return(_a0)
 	return _c
 }
 
-func (_c *MockOServiceChatHandler_UserInfoQueryHandler_Call) RunAndReturn(run func(context.Context, *Session) XMessage) *MockOServiceChatHandler_UserInfoQueryHandler_Call {
+func (_c *MockOServiceChatHandler_UserInfoQueryHandler_Call) RunAndReturn(run func(context.Context, *user.Session) oscar.XMessage) *MockOServiceChatHandler_UserInfoQueryHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }

+ 40 - 38
server/oservice_mock.go

@@ -7,6 +7,8 @@ import (
 
 	oscar "github.com/mkaminski/goaim/oscar"
 	mock "github.com/stretchr/testify/mock"
+
+	user "github.com/mkaminski/goaim/user"
 )
 
 // MockOServiceHandler is an autogenerated mock type for the OServiceHandler type
@@ -23,14 +25,14 @@ func (_m *MockOServiceHandler) EXPECT() *MockOServiceHandler_Expecter {
 }
 
 // ClientVersionsHandler provides a mock function with given fields: ctx, snacPayloadIn
-func (_m *MockOServiceHandler) ClientVersionsHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x17_OServiceClientVersions) XMessage {
+func (_m *MockOServiceHandler) ClientVersionsHandler(ctx context.Context, snacPayloadIn oscar.SNAC_0x01_0x17_OServiceClientVersions) oscar.XMessage {
 	ret := _m.Called(ctx, snacPayloadIn)
 
-	var r0 XMessage
-	if rf, ok := ret.Get(0).(func(context.Context, oscar.SNAC_0x01_0x17_OServiceClientVersions) XMessage); ok {
+	var r0 oscar.XMessage
+	if rf, ok := ret.Get(0).(func(context.Context, oscar.SNAC_0x01_0x17_OServiceClientVersions) oscar.XMessage); ok {
 		r0 = rf(ctx, snacPayloadIn)
 	} else {
-		r0 = ret.Get(0).(XMessage)
+		r0 = ret.Get(0).(oscar.XMessage)
 	}
 
 	return r0
@@ -55,22 +57,22 @@ func (_c *MockOServiceHandler_ClientVersionsHandler_Call) Run(run func(ctx conte
 	return _c
 }
 
-func (_c *MockOServiceHandler_ClientVersionsHandler_Call) Return(_a0 XMessage) *MockOServiceHandler_ClientVersionsHandler_Call {
+func (_c *MockOServiceHandler_ClientVersionsHandler_Call) Return(_a0 oscar.XMessage) *MockOServiceHandler_ClientVersionsHandler_Call {
 	_c.Call.Return(_a0)
 	return _c
 }
 
-func (_c *MockOServiceHandler_ClientVersionsHandler_Call) RunAndReturn(run func(context.Context, oscar.SNAC_0x01_0x17_OServiceClientVersions) XMessage) *MockOServiceHandler_ClientVersionsHandler_Call {
+func (_c *MockOServiceHandler_ClientVersionsHandler_Call) RunAndReturn(run func(context.Context, oscar.SNAC_0x01_0x17_OServiceClientVersions) oscar.XMessage) *MockOServiceHandler_ClientVersionsHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }
 
 // 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 *user.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, *user.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 *user.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 *user.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].(*user.Session), args[2].(oscar.SNAC_0x01_0x11_OServiceIdleNotification))
 	})
 	return _c
 }
@@ -104,20 +106,20 @@ 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, *user.Session, oscar.SNAC_0x01_0x11_OServiceIdleNotification) error) *MockOServiceHandler_IdleNotificationHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }
 
 // RateParamsQueryHandler provides a mock function with given fields: ctx
-func (_m *MockOServiceHandler) RateParamsQueryHandler(ctx context.Context) XMessage {
+func (_m *MockOServiceHandler) RateParamsQueryHandler(ctx context.Context) oscar.XMessage {
 	ret := _m.Called(ctx)
 
-	var r0 XMessage
-	if rf, ok := ret.Get(0).(func(context.Context) XMessage); ok {
+	var r0 oscar.XMessage
+	if rf, ok := ret.Get(0).(func(context.Context) oscar.XMessage); ok {
 		r0 = rf(ctx)
 	} else {
-		r0 = ret.Get(0).(XMessage)
+		r0 = ret.Get(0).(oscar.XMessage)
 	}
 
 	return r0
@@ -141,12 +143,12 @@ func (_c *MockOServiceHandler_RateParamsQueryHandler_Call) Run(run func(ctx cont
 	return _c
 }
 
-func (_c *MockOServiceHandler_RateParamsQueryHandler_Call) Return(_a0 XMessage) *MockOServiceHandler_RateParamsQueryHandler_Call {
+func (_c *MockOServiceHandler_RateParamsQueryHandler_Call) Return(_a0 oscar.XMessage) *MockOServiceHandler_RateParamsQueryHandler_Call {
 	_c.Call.Return(_a0)
 	return _c
 }
 
-func (_c *MockOServiceHandler_RateParamsQueryHandler_Call) RunAndReturn(run func(context.Context) XMessage) *MockOServiceHandler_RateParamsQueryHandler_Call {
+func (_c *MockOServiceHandler_RateParamsQueryHandler_Call) RunAndReturn(run func(context.Context) oscar.XMessage) *MockOServiceHandler_RateParamsQueryHandler_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) (XMessage, error) {
+func (_m *MockOServiceHandler) SetUserInfoFieldsHandler(ctx context.Context, sess *user.Session, snacPayloadIn oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (oscar.XMessage, error) {
 	ret := _m.Called(ctx, sess, snacPayloadIn)
 
-	var r0 XMessage
+	var r0 oscar.XMessage
 	var r1 error
-	if rf, ok := ret.Get(0).(func(context.Context, *Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (XMessage, error)); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *user.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) XMessage); ok {
+	if rf, ok := ret.Get(0).(func(context.Context, *user.Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) oscar.XMessage); ok {
 		r0 = rf(ctx, sess, snacPayloadIn)
 	} else {
-		r0 = ret.Get(0).(XMessage)
+		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, *user.Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) error); ok {
 		r1 = rf(ctx, sess, snacPayloadIn)
 	} else {
 		r1 = ret.Error(1)
@@ -216,38 +218,38 @@ type MockOServiceHandler_SetUserInfoFieldsHandler_Call struct {
 
 // SetUserInfoFieldsHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *user.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 *user.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].(*user.Session), args[2].(oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields))
 	})
 	return _c
 }
 
-func (_c *MockOServiceHandler_SetUserInfoFieldsHandler_Call) Return(_a0 XMessage, _a1 error) *MockOServiceHandler_SetUserInfoFieldsHandler_Call {
+func (_c *MockOServiceHandler_SetUserInfoFieldsHandler_Call) Return(_a0 oscar.XMessage, _a1 error) *MockOServiceHandler_SetUserInfoFieldsHandler_Call {
 	_c.Call.Return(_a0, _a1)
 	return _c
 }
 
-func (_c *MockOServiceHandler_SetUserInfoFieldsHandler_Call) RunAndReturn(run func(context.Context, *Session, oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields) (XMessage, error)) *MockOServiceHandler_SetUserInfoFieldsHandler_Call {
+func (_c *MockOServiceHandler_SetUserInfoFieldsHandler_Call) RunAndReturn(run func(context.Context, *user.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) XMessage {
+func (_m *MockOServiceHandler) UserInfoQueryHandler(ctx context.Context, sess *user.Session) oscar.XMessage {
 	ret := _m.Called(ctx, sess)
 
-	var r0 XMessage
-	if rf, ok := ret.Get(0).(func(context.Context, *Session) XMessage); ok {
+	var r0 oscar.XMessage
+	if rf, ok := ret.Get(0).(func(context.Context, *user.Session) oscar.XMessage); ok {
 		r0 = rf(ctx, sess)
 	} else {
-		r0 = ret.Get(0).(XMessage)
+		r0 = ret.Get(0).(oscar.XMessage)
 	}
 
 	return r0
@@ -260,24 +262,24 @@ type MockOServiceHandler_UserInfoQueryHandler_Call struct {
 
 // UserInfoQueryHandler is a helper method to define mock.On call
 //   - ctx context.Context
-//   - sess *Session
+//   - sess *user.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 *user.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].(*user.Session))
 	})
 	return _c
 }
 
-func (_c *MockOServiceHandler_UserInfoQueryHandler_Call) Return(_a0 XMessage) *MockOServiceHandler_UserInfoQueryHandler_Call {
+func (_c *MockOServiceHandler_UserInfoQueryHandler_Call) Return(_a0 oscar.XMessage) *MockOServiceHandler_UserInfoQueryHandler_Call {
 	_c.Call.Return(_a0)
 	return _c
 }
 
-func (_c *MockOServiceHandler_UserInfoQueryHandler_Call) RunAndReturn(run func(context.Context, *Session) XMessage) *MockOServiceHandler_UserInfoQueryHandler_Call {
+func (_c *MockOServiceHandler_UserInfoQueryHandler_Call) RunAndReturn(run func(context.Context, *user.Session) oscar.XMessage) *MockOServiceHandler_UserInfoQueryHandler_Call {
 	_c.Call.Return(run)
 	return _c
 }

+ 131 - 138
server/oservice_test.go

@@ -2,6 +2,7 @@ package server
 
 import (
 	"bytes"
+	"github.com/mkaminski/goaim/user"
 	"github.com/stretchr/testify/mock"
 	"testing"
 	"time"
@@ -20,20 +21,18 @@ func TestReceiveAndSendServiceRequest(t *testing.T) {
 		chatRoom *ChatRoom
 		// userSession is the session of the user requesting the chat service
 		// info
-		userSession *Session
+		userSession *user.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
 		// client
-		expectOutput XMessage
+		expectOutput oscar.XMessage
 		// expectErr is the expected error returned by the router
 		expectErr error
 	}{
 		{
-			name: "request info for ICBM service, return invalid SNAC err",
-			userSession: &Session{
-				ScreenName: "user_screen_name",
-			},
+			name:        "request info for ICBM service, return invalid SNAC err",
+			userSession: newTestSession("user_screen_name"),
 			inputSNAC: oscar.SNAC_0x01_0x04_OServiceServiceRequest{
 				FoodGroup: oscar.ICBM,
 			},
@@ -53,10 +52,7 @@ func TestReceiveAndSendServiceRequest(t *testing.T) {
 				InstanceNumber: 16,
 				Name:           "my new chat",
 			},
-			userSession: &Session{
-				ID:         "user-sess-id",
-				ScreenName: "user_screen_name",
-			},
+			userSession: newTestSession("user_screen_name", sessOptCannedID),
 			inputSNAC: oscar.SNAC_0x01_0x04_OServiceServiceRequest{
 				FoodGroup: oscar.CHAT,
 				TLVRestBlock: oscar.TLVRestBlock{
@@ -69,12 +65,12 @@ func TestReceiveAndSendServiceRequest(t *testing.T) {
 					},
 				},
 			},
-			expectOutput: XMessage{
-				snacFrame: oscar.SnacFrame{
+			expectOutput: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.OSERVICE,
 					SubGroup:  oscar.OServiceServiceResponse,
 				},
-				snacOut: oscar.SNAC_0x01_0x05_OServiceServiceResponse{
+				SnacOut: oscar.SNAC_0x01_0x05_OServiceServiceResponse{
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							oscar.NewTLV(oscar.OServiceTLVTagsReconnectHere, "127.0.0.1:1234"),
@@ -96,11 +92,8 @@ func TestReceiveAndSendServiceRequest(t *testing.T) {
 				OSCARHost: "127.0.0.1",
 				ChatPort:  1234,
 			},
-			chatRoom: nil,
-			userSession: &Session{
-				ID:         "user-sess-id",
-				ScreenName: "user_screen_name",
-			},
+			chatRoom:    nil,
+			userSession: newTestSession("user_screen_name", sessOptCannedID),
 			inputSNAC: oscar.SNAC_0x01_0x04_OServiceServiceRequest{
 				FoodGroup: oscar.CHAT,
 				TLVRestBlock: oscar.TLVRestBlock{
@@ -126,8 +119,8 @@ func TestReceiveAndSendServiceRequest(t *testing.T) {
 			cr := NewChatRegistry()
 			if tc.chatRoom != nil {
 				sm.EXPECT().
-					NewSessionWithSN(tc.userSession.ID, tc.userSession.ScreenName).
-					Return(&Session{}).
+					NewSessionWithSN(tc.userSession.ID(), tc.userSession.ScreenName()).
+					Return(&user.Session{}).
 					Maybe()
 				tc.chatRoom.SessionManager = sm
 				cr.Register(*tc.chatRoom)
@@ -161,9 +154,9 @@ func TestOServiceRouter_RouteOService_ForBOS(t *testing.T) {
 		// name is the unit test name
 		name string
 		// input is the request payload
-		input XMessage
+		input oscar.XMessage
 		// output is the response payload
-		output XMessage
+		output oscar.XMessage
 		// handlerErr is the mocked handler error response
 		handlerErr error
 		// expectErr is the expected error returned by the router
@@ -171,12 +164,12 @@ func TestOServiceRouter_RouteOService_ForBOS(t *testing.T) {
 	}{
 		{
 			name: "receive OServiceClientOnline, return no response",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.OSERVICE,
 					SubGroup:  oscar.OServiceClientOnline,
 				},
-				snacOut: oscar.SNAC_0x01_0x02_OServiceClientOnline{
+				SnacOut: oscar.SNAC_0x01_0x02_OServiceClientOnline{
 					GroupVersions: []struct {
 						FoodGroup   uint16
 						Version     uint16
@@ -189,25 +182,25 @@ func TestOServiceRouter_RouteOService_ForBOS(t *testing.T) {
 					},
 				},
 			},
-			output: XMessage{},
+			output: oscar.XMessage{},
 		},
 		{
 			name: "receive OServiceServiceRequest, return OServiceServiceResponse",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.OSERVICE,
 					SubGroup:  oscar.OServiceServiceRequest,
 				},
-				snacOut: oscar.SNAC_0x01_0x04_OServiceServiceRequest{
+				SnacOut: oscar.SNAC_0x01_0x04_OServiceServiceRequest{
 					FoodGroup: 10,
 				},
 			},
-			output: XMessage{
-				snacFrame: oscar.SnacFrame{
+			output: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.OSERVICE,
 					SubGroup:  oscar.OServiceServiceResponse,
 				},
-				snacOut: oscar.SNAC_0x01_0x05_OServiceServiceResponse{
+				SnacOut: oscar.SNAC_0x01_0x05_OServiceServiceResponse{
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							oscar.NewTLV(0x01, uint16(1000)),
@@ -218,19 +211,19 @@ func TestOServiceRouter_RouteOService_ForBOS(t *testing.T) {
 		},
 		{
 			name: "receive OServiceRateParamsQuery, return OServiceRateParamsReply",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.OSERVICE,
 					SubGroup:  oscar.OServiceRateParamsQuery,
 				},
-				snacOut: struct{}{},
+				SnacOut: struct{}{},
 			},
-			output: XMessage{
-				snacFrame: oscar.SnacFrame{
+			output: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.OSERVICE,
 					SubGroup:  oscar.OServiceRateParamsReply,
 				},
-				snacOut: oscar.SNAC_0x01_0x07_OServiceRateParamsReply{
+				SnacOut: oscar.SNAC_0x01_0x07_OServiceRateParamsReply{
 					RateGroups: []struct {
 						ID    uint16
 						Pairs []struct {
@@ -247,12 +240,12 @@ func TestOServiceRouter_RouteOService_ForBOS(t *testing.T) {
 		},
 		{
 			name: "receive OServiceRateParamsSubAdd, return no response",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.OSERVICE,
 					SubGroup:  oscar.OServiceRateParamsSubAdd,
 				},
-				snacOut: oscar.SNAC_0x01_0x08_OServiceRateParamsSubAdd{
+				SnacOut: oscar.SNAC_0x01_0x08_OServiceRateParamsSubAdd{
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							oscar.NewTLV(0x01, []byte{1, 2, 3, 4}),
@@ -260,23 +253,23 @@ func TestOServiceRouter_RouteOService_ForBOS(t *testing.T) {
 					},
 				},
 			},
-			output: XMessage{},
+			output: oscar.XMessage{},
 		},
 		{
 			name: "receive OServiceUserInfoQuery, return OServiceUserInfoUpdate",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.OSERVICE,
 					SubGroup:  oscar.OServiceUserInfoQuery,
 				},
-				snacOut: struct{}{},
+				SnacOut: struct{}{},
 			},
-			output: XMessage{
-				snacFrame: oscar.SnacFrame{
+			output: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.OSERVICE,
 					SubGroup:  oscar.OServiceUserInfoUpdate,
 				},
-				snacOut: oscar.SNAC_0x01_0x0F_OServiceUserInfoUpdate{
+				SnacOut: oscar.SNAC_0x01_0x0F_OServiceUserInfoUpdate{
 					TLVUserInfo: oscar.TLVUserInfo{
 						ScreenName: "screen-name",
 					},
@@ -285,36 +278,36 @@ func TestOServiceRouter_RouteOService_ForBOS(t *testing.T) {
 		},
 		{
 			name: "receive OServiceIdleNotification, return no response",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.OSERVICE,
 					SubGroup:  oscar.OServiceIdleNotification,
 				},
-				snacOut: oscar.SNAC_0x01_0x11_OServiceIdleNotification{
+				SnacOut: oscar.SNAC_0x01_0x11_OServiceIdleNotification{
 					IdleTime: 10,
 				},
 			},
-			output: XMessage{},
+			output: oscar.XMessage{},
 		},
 		{
 			name: "receive OServiceClientVersions, return OServiceHostVersions",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.OSERVICE,
 					SubGroup:  oscar.OServiceClientVersions,
 				},
-				snacOut: oscar.SNAC_0x01_0x17_OServiceClientVersions{
+				SnacOut: oscar.SNAC_0x01_0x17_OServiceClientVersions{
 					Versions: []uint16{
 						10,
 					},
 				},
 			},
-			output: XMessage{
-				snacFrame: oscar.SnacFrame{
+			output: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.OSERVICE,
 					SubGroup:  oscar.OServiceHostVersions,
 				},
-				snacOut: oscar.SNAC_0x01_0x18_OServiceHostVersions{
+				SnacOut: oscar.SNAC_0x01_0x18_OServiceHostVersions{
 					Versions: []uint16{
 						10,
 					},
@@ -323,12 +316,12 @@ func TestOServiceRouter_RouteOService_ForBOS(t *testing.T) {
 		},
 		{
 			name: "receive OServiceSetUserInfoFields, return OServiceUserInfoUpdate",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.OSERVICE,
 					SubGroup:  oscar.OServiceSetUserInfoFields,
 				},
-				snacOut: oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields{
+				SnacOut: oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields{
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							oscar.NewTLV(0x01, []byte{1, 2, 3, 4}),
@@ -336,12 +329,12 @@ func TestOServiceRouter_RouteOService_ForBOS(t *testing.T) {
 					},
 				},
 			},
-			output: XMessage{
-				snacFrame: oscar.SnacFrame{
+			output: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.OSERVICE,
 					SubGroup:  oscar.OServiceUserInfoUpdate,
 				},
-				snacOut: oscar.SNAC_0x01_0x0F_OServiceUserInfoUpdate{
+				SnacOut: oscar.SNAC_0x01_0x0F_OServiceUserInfoUpdate{
 					TLVUserInfo: oscar.TLVUserInfo{
 						ScreenName: "screen-name",
 					},
@@ -350,14 +343,14 @@ func TestOServiceRouter_RouteOService_ForBOS(t *testing.T) {
 		},
 		{
 			name: "receive OServicePauseReq, expect ErrUnsupportedSubGroup",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.OSERVICE,
 					SubGroup:  oscar.OServicePauseReq,
 				},
-				snacOut: struct{}{}, // empty SNAC
+				SnacOut: struct{}{}, // empty SNAC
 			},
-			output:    XMessage{}, // empty SNAC
+			output:    oscar.XMessage{}, // empty SNAC
 			expectErr: ErrUnsupportedSubGroup,
 		},
 	}
@@ -374,28 +367,28 @@ func TestOServiceRouter_RouteOService_ForBOS(t *testing.T) {
 				Return(tc.output).
 				Maybe()
 			svc.EXPECT().
-				ClientVersionsHandler(mock.Anything, tc.input.snacOut).
+				ClientVersionsHandler(mock.Anything, tc.input.SnacOut).
 				Return(tc.output).
 				Maybe()
 			svc.EXPECT().
-				SetUserInfoFieldsHandler(mock.Anything, mock.Anything, tc.input.snacOut).
+				SetUserInfoFieldsHandler(mock.Anything, mock.Anything, tc.input.SnacOut).
 				Return(tc.output, tc.handlerErr).
 				Maybe()
 			svc.EXPECT().
-				RateParamsSubAddHandler(mock.Anything, tc.input.snacOut).
+				RateParamsSubAddHandler(mock.Anything, tc.input.SnacOut).
 				Maybe()
 			svc.EXPECT().
-				IdleNotificationHandler(mock.Anything, mock.Anything, tc.input.snacOut).
+				IdleNotificationHandler(mock.Anything, mock.Anything, tc.input.SnacOut).
 				Return(tc.handlerErr).
 				Maybe()
 
 			svcBOS := NewMockOServiceBOSHandler(t)
 			svcBOS.EXPECT().
-				ServiceRequestHandler(mock.Anything, mock.Anything, tc.input.snacOut).
+				ServiceRequestHandler(mock.Anything, mock.Anything, tc.input.SnacOut).
 				Return(tc.output, tc.handlerErr).
 				Maybe()
 			svcBOS.EXPECT().
-				ClientOnlineHandler(mock.Anything, tc.input.snacOut, mock.Anything).
+				ClientOnlineHandler(mock.Anything, tc.input.SnacOut, mock.Anything).
 				Return(tc.handlerErr).
 				Maybe()
 
@@ -410,18 +403,18 @@ func TestOServiceRouter_RouteOService_ForBOS(t *testing.T) {
 			}
 
 			bufIn := &bytes.Buffer{}
-			assert.NoError(t, oscar.Marshal(tc.input.snacOut, bufIn))
+			assert.NoError(t, oscar.Marshal(tc.input.SnacOut, bufIn))
 
 			bufOut := &bytes.Buffer{}
 			seq := uint32(1)
 
-			err := router.RouteOService(nil, nil, tc.input.snacFrame, bufIn, bufOut, &seq)
+			err := router.RouteOService(nil, nil, tc.input.SnacFrame, bufIn, bufOut, &seq)
 			assert.ErrorIs(t, err, tc.expectErr)
 			if tc.expectErr != nil {
 				return
 			}
 
-			if tc.output == (XMessage{}) {
+			if tc.output == (oscar.XMessage{}) {
 				// make sure no response was sent
 				assert.Empty(t, bufOut.Bytes())
 				return
@@ -440,11 +433,11 @@ func TestOServiceRouter_RouteOService_ForBOS(t *testing.T) {
 			// verify the SNAC frame
 			snacFrame := oscar.SnacFrame{}
 			assert.NoError(t, oscar.Unmarshal(&snacFrame, flapBuf))
-			assert.Equal(t, tc.output.snacFrame, snacFrame)
+			assert.Equal(t, tc.output.SnacFrame, snacFrame)
 
 			// verify the SNAC message
 			snacBuf := &bytes.Buffer{}
-			assert.NoError(t, oscar.Marshal(tc.output.snacOut, snacBuf))
+			assert.NoError(t, oscar.Marshal(tc.output.SnacOut, snacBuf))
 			assert.Equal(t, snacBuf.Bytes(), flapBuf.Bytes())
 		})
 	}
@@ -455,9 +448,9 @@ func TestOServiceRouter_RouteOService_ForChat(t *testing.T) {
 		// name is the unit test name
 		name string
 		// input is the request payload
-		input XMessage
+		input oscar.XMessage
 		// output is the response payload
-		output XMessage
+		output oscar.XMessage
 		// handlerErr is the mocked handler error response
 		handlerErr error
 		// expectErr is the expected error returned by the router
@@ -465,12 +458,12 @@ func TestOServiceRouter_RouteOService_ForChat(t *testing.T) {
 	}{
 		{
 			name: "receive OServiceClientOnline, return no response",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.OSERVICE,
 					SubGroup:  oscar.OServiceClientOnline,
 				},
-				snacOut: oscar.SNAC_0x01_0x02_OServiceClientOnline{
+				SnacOut: oscar.SNAC_0x01_0x02_OServiceClientOnline{
 					GroupVersions: []struct {
 						FoodGroup   uint16
 						Version     uint16
@@ -483,25 +476,25 @@ func TestOServiceRouter_RouteOService_ForChat(t *testing.T) {
 					},
 				},
 			},
-			output: XMessage{},
+			output: oscar.XMessage{},
 		},
 		{
 			name: "receive OServiceServiceRequest, return OServiceServiceResponse",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.OSERVICE,
 					SubGroup:  oscar.OServiceServiceRequest,
 				},
-				snacOut: oscar.SNAC_0x01_0x04_OServiceServiceRequest{
+				SnacOut: oscar.SNAC_0x01_0x04_OServiceServiceRequest{
 					FoodGroup: 10,
 				},
 			},
-			output: XMessage{
-				snacFrame: oscar.SnacFrame{
+			output: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.OSERVICE,
 					SubGroup:  oscar.OServiceServiceResponse,
 				},
-				snacOut: oscar.SNAC_0x01_0x05_OServiceServiceResponse{
+				SnacOut: oscar.SNAC_0x01_0x05_OServiceServiceResponse{
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							oscar.NewTLV(0x01, uint16(1000)),
@@ -512,19 +505,19 @@ func TestOServiceRouter_RouteOService_ForChat(t *testing.T) {
 		},
 		{
 			name: "receive OServiceRateParamsQuery, return OServiceRateParamsReply",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.OSERVICE,
 					SubGroup:  oscar.OServiceRateParamsQuery,
 				},
-				snacOut: struct{}{},
+				SnacOut: struct{}{},
 			},
-			output: XMessage{
-				snacFrame: oscar.SnacFrame{
+			output: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.OSERVICE,
 					SubGroup:  oscar.OServiceRateParamsReply,
 				},
-				snacOut: oscar.SNAC_0x01_0x07_OServiceRateParamsReply{
+				SnacOut: oscar.SNAC_0x01_0x07_OServiceRateParamsReply{
 					RateGroups: []struct {
 						ID    uint16
 						Pairs []struct {
@@ -541,12 +534,12 @@ func TestOServiceRouter_RouteOService_ForChat(t *testing.T) {
 		},
 		{
 			name: "receive OServiceRateParamsSubAdd, return no response",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.OSERVICE,
 					SubGroup:  oscar.OServiceRateParamsSubAdd,
 				},
-				snacOut: oscar.SNAC_0x01_0x08_OServiceRateParamsSubAdd{
+				SnacOut: oscar.SNAC_0x01_0x08_OServiceRateParamsSubAdd{
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							oscar.NewTLV(0x01, []byte{1, 2, 3, 4}),
@@ -554,23 +547,23 @@ func TestOServiceRouter_RouteOService_ForChat(t *testing.T) {
 					},
 				},
 			},
-			output: XMessage{},
+			output: oscar.XMessage{},
 		},
 		{
 			name: "receive OServiceUserInfoQuery, return OServiceUserInfoUpdate",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.OSERVICE,
 					SubGroup:  oscar.OServiceUserInfoQuery,
 				},
-				snacOut: struct{}{},
+				SnacOut: struct{}{},
 			},
-			output: XMessage{
-				snacFrame: oscar.SnacFrame{
+			output: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.OSERVICE,
 					SubGroup:  oscar.OServiceUserInfoUpdate,
 				},
-				snacOut: oscar.SNAC_0x01_0x0F_OServiceUserInfoUpdate{
+				SnacOut: oscar.SNAC_0x01_0x0F_OServiceUserInfoUpdate{
 					TLVUserInfo: oscar.TLVUserInfo{
 						ScreenName: "screen-name",
 					},
@@ -579,36 +572,36 @@ func TestOServiceRouter_RouteOService_ForChat(t *testing.T) {
 		},
 		{
 			name: "receive OServiceIdleNotification, return no response",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.OSERVICE,
 					SubGroup:  oscar.OServiceIdleNotification,
 				},
-				snacOut: oscar.SNAC_0x01_0x11_OServiceIdleNotification{
+				SnacOut: oscar.SNAC_0x01_0x11_OServiceIdleNotification{
 					IdleTime: 10,
 				},
 			},
-			output: XMessage{},
+			output: oscar.XMessage{},
 		},
 		{
 			name: "receive OServiceClientVersions, return OServiceHostVersions",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.OSERVICE,
 					SubGroup:  oscar.OServiceClientVersions,
 				},
-				snacOut: oscar.SNAC_0x01_0x17_OServiceClientVersions{
+				SnacOut: oscar.SNAC_0x01_0x17_OServiceClientVersions{
 					Versions: []uint16{
 						10,
 					},
 				},
 			},
-			output: XMessage{
-				snacFrame: oscar.SnacFrame{
+			output: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.OSERVICE,
 					SubGroup:  oscar.OServiceHostVersions,
 				},
-				snacOut: oscar.SNAC_0x01_0x18_OServiceHostVersions{
+				SnacOut: oscar.SNAC_0x01_0x18_OServiceHostVersions{
 					Versions: []uint16{
 						10,
 					},
@@ -617,12 +610,12 @@ func TestOServiceRouter_RouteOService_ForChat(t *testing.T) {
 		},
 		{
 			name: "receive OServiceSetUserInfoFields, return OServiceUserInfoUpdate",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.OSERVICE,
 					SubGroup:  oscar.OServiceSetUserInfoFields,
 				},
-				snacOut: oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields{
+				SnacOut: oscar.SNAC_0x01_0x1E_OServiceSetUserInfoFields{
 					TLVRestBlock: oscar.TLVRestBlock{
 						TLVList: oscar.TLVList{
 							oscar.NewTLV(0x01, []byte{1, 2, 3, 4}),
@@ -630,12 +623,12 @@ func TestOServiceRouter_RouteOService_ForChat(t *testing.T) {
 					},
 				},
 			},
-			output: XMessage{
-				snacFrame: oscar.SnacFrame{
+			output: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.OSERVICE,
 					SubGroup:  oscar.OServiceUserInfoUpdate,
 				},
-				snacOut: oscar.SNAC_0x01_0x0F_OServiceUserInfoUpdate{
+				SnacOut: oscar.SNAC_0x01_0x0F_OServiceUserInfoUpdate{
 					TLVUserInfo: oscar.TLVUserInfo{
 						ScreenName: "screen-name",
 					},
@@ -644,14 +637,14 @@ func TestOServiceRouter_RouteOService_ForChat(t *testing.T) {
 		},
 		{
 			name: "receive OServicePauseReq, expect ErrUnsupportedSubGroup",
-			input: XMessage{
-				snacFrame: oscar.SnacFrame{
+			input: oscar.XMessage{
+				SnacFrame: oscar.SnacFrame{
 					FoodGroup: oscar.OSERVICE,
 					SubGroup:  oscar.OServicePauseReq,
 				},
-				snacOut: struct{}{}, // empty SNAC
+				SnacOut: struct{}{}, // empty SNAC
 			},
-			output:    XMessage{}, // empty SNAC
+			output:    oscar.XMessage{}, // empty SNAC
 			expectErr: ErrUnsupportedSubGroup,
 		},
 	}
@@ -668,28 +661,28 @@ func TestOServiceRouter_RouteOService_ForChat(t *testing.T) {
 				Return(tc.output).
 				Maybe()
 			svc.EXPECT().
-				ClientVersionsHandler(mock.Anything, tc.input.snacOut).
+				ClientVersionsHandler(mock.Anything, tc.input.SnacOut).
 				Return(tc.output).
 				Maybe()
 			svc.EXPECT().
-				SetUserInfoFieldsHandler(mock.Anything, mock.Anything, tc.input.snacOut).
+				SetUserInfoFieldsHandler(mock.Anything, mock.Anything, tc.input.SnacOut).
 				Return(tc.output, tc.handlerErr).
 				Maybe()
 			svc.EXPECT().
-				RateParamsSubAddHandler(mock.Anything, tc.input.snacOut).
+				RateParamsSubAddHandler(mock.Anything, tc.input.SnacOut).
 				Maybe()
 			svc.EXPECT().
-				IdleNotificationHandler(mock.Anything, mock.Anything, tc.input.snacOut).
+				IdleNotificationHandler(mock.Anything, mock.Anything, tc.input.SnacOut).
 				Return(tc.handlerErr).
 				Maybe()
 
 			svcBOS := NewMockOServiceChatHandler(t)
 			svcBOS.EXPECT().
-				ServiceRequestHandler(mock.Anything, mock.Anything, tc.input.snacOut).
+				ServiceRequestHandler(mock.Anything, mock.Anything, tc.input.SnacOut).
 				Return(tc.output, tc.handlerErr).
 				Maybe()
 			svcBOS.EXPECT().
-				ClientOnlineHandler(mock.Anything, tc.input.snacOut, mock.Anything, mock.Anything).
+				ClientOnlineHandler(mock.Anything, tc.input.SnacOut, mock.Anything, mock.Anything).
 				Return(tc.handlerErr).
 				Maybe()
 
@@ -704,18 +697,18 @@ func TestOServiceRouter_RouteOService_ForChat(t *testing.T) {
 			}
 
 			bufIn := &bytes.Buffer{}
-			assert.NoError(t, oscar.Marshal(tc.input.snacOut, bufIn))
+			assert.NoError(t, oscar.Marshal(tc.input.SnacOut, bufIn))
 
 			bufOut := &bytes.Buffer{}
 			seq := uint32(1)
 
-			err := router.RouteOService(nil, nil, ChatRoom{}, tc.input.snacFrame, bufIn, bufOut, &seq)
+			err := router.RouteOService(nil, nil, ChatRoom{}, tc.input.SnacFrame, bufIn, bufOut, &seq)
 			assert.ErrorIs(t, err, tc.expectErr)
 			if tc.expectErr != nil {
 				return
 			}
 
-			if tc.output == (XMessage{}) {
+			if tc.output == (oscar.XMessage{}) {
 				// make sure no response was sent
 				assert.Empty(t, bufOut.Bytes())
 				return
@@ -734,11 +727,11 @@ func TestOServiceRouter_RouteOService_ForChat(t *testing.T) {
 			// verify the SNAC frame
 			snacFrame := oscar.SnacFrame{}
 			assert.NoError(t, oscar.Unmarshal(&snacFrame, flapBuf))
-			assert.Equal(t, tc.output.snacFrame, snacFrame)
+			assert.Equal(t, tc.output.SnacFrame, snacFrame)
 
 			// verify the SNAC message
 			snacBuf := &bytes.Buffer{}
-			assert.NoError(t, oscar.Marshal(tc.output.snacOut, snacBuf))
+			assert.NoError(t, oscar.Marshal(tc.output.SnacOut, snacBuf))
 			assert.Equal(t, snacBuf.Bytes(), flapBuf.Bytes())
 		})
 	}

+ 7 - 6
server/router.go

@@ -5,6 +5,7 @@ import (
 	"context"
 	"errors"
 	"fmt"
+	"github.com/mkaminski/goaim/user"
 	"io"
 	"log/slog"
 	"net"
@@ -55,7 +56,7 @@ type BOSServiceRouter struct {
 	RouteLogger
 }
 
-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 *user.Session, r io.Reader, w io.Writer, sequence *uint32) error {
 	snac := oscar.SnacFrame{}
 	if err := oscar.Unmarshal(&snac, r); err != nil {
 		return err
@@ -100,14 +101,14 @@ func (rt *BOSServiceRouter) Route(ctx context.Context, sess *Session, r io.Reade
 	return err
 }
 
-func (rt *BOSServiceRouter) Signout(ctx context.Context, logger *slog.Logger, sess *Session) {
+func (rt *BOSServiceRouter) Signout(ctx context.Context, logger *slog.Logger, sess *user.Session) {
 	if err := BroadcastDeparture(ctx, sess, rt.sm, rt.fm); err != nil {
 		logger.ErrorContext(ctx, "error notifying departure", "err", err.Error())
 	}
 	rt.sm.Remove(sess)
 }
 
-func (rt *BOSServiceRouter) VerifyLogin(conn net.Conn) (*Session, uint32, error) {
+func (rt *BOSServiceRouter) VerifyLogin(conn net.Conn) (*user.Session, uint32, error) {
 	seq := uint32(100)
 
 	flap, err := SendAndReceiveSignonFrame(conn, &seq)
@@ -118,12 +119,12 @@ func (rt *BOSServiceRouter) VerifyLogin(conn net.Conn) (*Session, uint32, error)
 	var ok bool
 	ID, ok := flap.GetSlice(oscar.OServiceTLVTagsLoginCookie)
 	if !ok {
-		return nil, 0, errors.New("unable to get session ID from payload")
+		return nil, 0, errors.New("unable to get session id from payload")
 	}
 
 	sess, ok := rt.sm.Retrieve(string(ID))
 	if !ok {
-		return nil, 0, fmt.Errorf("unable to find session by ID %s", ID)
+		return nil, 0, fmt.Errorf("unable to find session by id %s", ID)
 	}
 
 	return sess, seq, nil
@@ -184,7 +185,7 @@ type ChatServiceRouter struct {
 	RouteLogger
 }
 
-func (rt *ChatServiceRouter) Route(ctx context.Context, sess *Session, r io.Reader, w io.Writer, sequence *uint32, room ChatRoom) error {
+func (rt *ChatServiceRouter) Route(ctx context.Context, sess *user.Session, r io.Reader, w io.Writer, sequence *uint32, room ChatRoom) error {
 	snac := oscar.SnacFrame{}
 	if err := oscar.Unmarshal(&snac, r); err != nil {
 		return err

+ 29 - 204
server/session.go

@@ -4,6 +4,7 @@ import (
 	"context"
 	"errors"
 	"fmt"
+	"github.com/mkaminski/goaim/user"
 	"log/slog"
 	"sync"
 	"time"
@@ -16,176 +17,8 @@ var (
 	ErrSignedOff    = errors.New("user signed off")
 )
 
-type SessSendStatus int
-
-const (
-	// SessSendOK indicates message was sent to recipient
-	SessSendOK SessSendStatus = iota
-	// SessSendClosed indicates send did not complete because session is closed
-	SessSendClosed
-	// SessQueueFull indicates send failed due to full queue -- client is likely
-	// dead
-	SessQueueFull
-)
-
-type Session struct {
-	ID          string
-	ScreenName  string
-	msgCh       chan XMessage
-	stopCh      chan struct{}
-	Mutex       sync.RWMutex
-	Warning     uint16
-	AwayMessage string
-	SignonTime  time.Time
-	invisible   bool
-	idle        bool
-	idleTime    time.Time
-	closed      bool
-}
-
-func (s *Session) IncreaseWarning(incr uint16) {
-	s.Mutex.RLock()
-	defer s.Mutex.RUnlock()
-	s.Warning += incr
-}
-
-func (s *Session) SetInvisible(invisible bool) {
-	s.Mutex.RLock()
-	defer s.Mutex.RUnlock()
-	s.invisible = invisible
-}
-
-func (s *Session) Invisible() bool {
-	s.Mutex.RLock()
-	defer s.Mutex.RUnlock()
-	return s.invisible
-}
-
-func (s *Session) SetIdle(dur time.Duration) {
-	s.Mutex.RLock()
-	defer s.Mutex.RUnlock()
-	s.idle = true
-	// set the time the user became idle
-	s.idleTime = time.Now().Add(-dur)
-}
-
-func (s *Session) SetActive() {
-	s.Mutex.RLock()
-	defer s.Mutex.RUnlock()
-	s.idle = false
-}
-
-func (s *Session) Idle() bool {
-	s.Mutex.RLock()
-	defer s.Mutex.RUnlock()
-	return s.idle
-}
-
-func (s *Session) SetAwayMessage(awayMessage string) {
-	s.Mutex.RLock()
-	defer s.Mutex.RUnlock()
-	s.AwayMessage = awayMessage
-}
-
-func (s *Session) GetAwayMessage() string {
-	s.Mutex.RLock()
-	defer s.Mutex.RUnlock()
-	return s.AwayMessage
-}
-
-func (s *Session) GetTLVUserInfo() oscar.TLVUserInfo {
-	return oscar.TLVUserInfo{
-		ScreenName:   s.ScreenName,
-		WarningLevel: s.GetWarning(),
-		TLVBlock: oscar.TLVBlock{
-			TLVList: s.GetUserInfo(),
-		},
-	}
-}
-
-func (s *Session) GetUserInfo() oscar.TLVList {
-	s.Mutex.RLock()
-	defer s.Mutex.RUnlock()
-
-	// sign-in timestamp
-	tlvs := oscar.TLVList{}
-
-	tlvs.AddTLV(oscar.NewTLV(0x03, uint32(s.SignonTime.Unix())))
-
-	// away message status
-	if s.AwayMessage != "" {
-		tlvs.AddTLV(oscar.NewTLV(0x01, uint16(0x0010)|uint16(0x0020)))
-	} else {
-		tlvs.AddTLV(oscar.NewTLV(0x01, uint16(0x0010)))
-	}
-
-	// invisibility status
-	if s.invisible {
-		tlvs.AddTLV(oscar.NewTLV(0x06, uint16(0x0100)))
-	} else {
-		tlvs.AddTLV(oscar.NewTLV(0x06, uint16(0x0000)))
-	}
-
-	// idle status
-	if s.idle {
-		tlvs.AddTLV(oscar.NewTLV(0x04, uint16(time.Now().Sub(s.idleTime).Seconds())))
-	} else {
-		tlvs.AddTLV(oscar.NewTLV(0x04, uint16(0)))
-	}
-
-	// capabilities
-	var caps []byte
-	// chat capability
-	caps = append(caps, CapChat...)
-	tlvs.AddTLV(oscar.NewTLV(0x0D, caps))
-
-	return tlvs
-}
-
-func (s *Session) GetWarning() uint16 {
-	var w uint16
-	s.Mutex.RLock()
-	w = s.Warning
-	s.Mutex.RUnlock()
-	return w
-}
-
-func (s *Session) RecvMessage() chan XMessage {
-	return s.msgCh
-}
-
-func (s *Session) SendMessage(msg XMessage) SessSendStatus {
-	s.Mutex.Lock()
-	if s.closed {
-		return SessSendClosed
-	}
-	s.Mutex.Unlock()
-	select {
-	case s.msgCh <- msg:
-		return SessSendOK
-	case <-s.stopCh:
-		return SessSendClosed
-	default:
-		return SessQueueFull
-	}
-}
-
-func (s *Session) Close() {
-	s.Mutex.Lock()
-	defer s.Mutex.Unlock()
-	if s.closed {
-		return
-	}
-	close(s.stopCh)
-	s.closed = true
-}
-
-func (s *Session) Closed() <-chan struct{} {
-	return s.stopCh
-}
-
 type InMemorySessionManager struct {
-	store    map[string]*Session
+	store    map[string]*user.Session
 	mapMutex sync.RWMutex
 	logger   *slog.Logger
 }
@@ -193,11 +26,11 @@ type InMemorySessionManager struct {
 func NewSessionManager(logger *slog.Logger) *InMemorySessionManager {
 	return &InMemorySessionManager{
 		logger: logger,
-		store:  make(map[string]*Session),
+		store:  make(map[string]*user.Session),
 	}
 }
 
-func (s *InMemorySessionManager) Broadcast(ctx context.Context, msg XMessage) {
+func (s *InMemorySessionManager) Broadcast(ctx context.Context, msg oscar.XMessage) {
 	s.mapMutex.RLock()
 	defer s.mapMutex.RUnlock()
 	for _, sess := range s.store {
@@ -205,12 +38,12 @@ func (s *InMemorySessionManager) Broadcast(ctx context.Context, msg XMessage) {
 	}
 }
 
-func (s *InMemorySessionManager) maybeSendMessage(ctx context.Context, msg XMessage, sess *Session) {
+func (s *InMemorySessionManager) maybeSendMessage(ctx context.Context, msg oscar.XMessage, sess *user.Session) {
 	switch sess.SendMessage(msg) {
-	case SessSendClosed:
-		s.logger.WarnContext(ctx, "can't send notification because the user's session is closed", "recipient", sess.ScreenName, "message", msg)
-	case SessQueueFull:
-		s.logger.WarnContext(ctx, "can't send notification because queue is full", "recipient", sess.ScreenName, "message", msg)
+	case user.SessSendClosed:
+		s.logger.WarnContext(ctx, "can't send notification because the user's session is closed", "recipient", sess.ScreenName(), "message", msg)
+	case user.SessQueueFull:
+		s.logger.WarnContext(ctx, "can't send notification because queue is full", "recipient", sess.ScreenName(), "message", msg)
 		sess.Close()
 	}
 }
@@ -221,17 +54,17 @@ func (s *InMemorySessionManager) Empty() bool {
 	return len(s.store) == 0
 }
 
-func (s *InMemorySessionManager) Participants() []*Session {
+func (s *InMemorySessionManager) Participants() []*user.Session {
 	s.mapMutex.RLock()
 	defer s.mapMutex.RUnlock()
-	var sessions []*Session
+	var sessions []*user.Session
 	for _, sess := range s.store {
 		sessions = append(sessions, sess)
 	}
 	return sessions
 }
 
-func (s *InMemorySessionManager) BroadcastExcept(ctx context.Context, except *Session, msg XMessage) {
+func (s *InMemorySessionManager) BroadcastExcept(ctx context.Context, except *user.Session, msg oscar.XMessage) {
 	s.mapMutex.RLock()
 	defer s.mapMutex.RUnlock()
 	for _, sess := range s.store {
@@ -242,31 +75,31 @@ func (s *InMemorySessionManager) BroadcastExcept(ctx context.Context, except *Se
 	}
 }
 
-func (s *InMemorySessionManager) Retrieve(ID string) (*Session, bool) {
+func (s *InMemorySessionManager) Retrieve(ID string) (*user.Session, bool) {
 	s.mapMutex.RLock()
 	defer s.mapMutex.RUnlock()
 	sess, found := s.store[ID]
 	return sess, found
 }
 
-func (s *InMemorySessionManager) RetrieveByScreenName(screenName string) (*Session, error) {
+func (s *InMemorySessionManager) RetrieveByScreenName(screenName string) (*user.Session, error) {
 	s.mapMutex.RLock()
 	defer s.mapMutex.RUnlock()
 	for _, sess := range s.store {
-		if screenName == sess.ScreenName {
+		if screenName == sess.ScreenName() {
 			return sess, nil
 		}
 	}
 	return nil, fmt.Errorf("%w: %s", ErrSessNotFound, screenName)
 }
 
-func (s *InMemorySessionManager) retrieveByScreenNames(screenNames []string) []*Session {
+func (s *InMemorySessionManager) retrieveByScreenNames(screenNames []string) []*user.Session {
 	s.mapMutex.RLock()
 	defer s.mapMutex.RUnlock()
-	var ret []*Session
+	var ret []*user.Session
 	for _, sn := range screenNames {
 		for _, sess := range s.store {
-			if sn == sess.ScreenName {
+			if sn == sess.ScreenName() {
 				ret = append(ret, sess)
 			}
 		}
@@ -274,7 +107,7 @@ func (s *InMemorySessionManager) retrieveByScreenNames(screenNames []string) []*
 	return ret
 }
 
-func (s *InMemorySessionManager) SendToScreenName(ctx context.Context, screenName string, msg XMessage) {
+func (s *InMemorySessionManager) SendToScreenName(ctx context.Context, screenName string, msg oscar.XMessage) {
 	sess, err := s.RetrieveByScreenName(screenName)
 	if err != nil {
 		s.logger.WarnContext(ctx, "can't send notification because user is not online", "recipient", screenName, "message", msg)
@@ -283,21 +116,13 @@ func (s *InMemorySessionManager) SendToScreenName(ctx context.Context, screenNam
 	s.maybeSendMessage(ctx, msg, sess)
 }
 
-func (s *InMemorySessionManager) BroadcastToScreenNames(ctx context.Context, screenNames []string, msg XMessage) {
+func (s *InMemorySessionManager) BroadcastToScreenNames(ctx context.Context, screenNames []string, msg oscar.XMessage) {
 	for _, sess := range s.retrieveByScreenNames(screenNames) {
 		s.maybeSendMessage(ctx, msg, sess)
 	}
 }
 
-func makeSession() *Session {
-	return &Session{
-		msgCh:      make(chan XMessage, 1000),
-		stopCh:     make(chan struct{}),
-		SignonTime: time.Now(),
-	}
-}
-
-func (s *InMemorySessionManager) NewSessionWithSN(sessID string, screenName string) *Session {
+func (s *InMemorySessionManager) NewSessionWithSN(sessID string, screenName string) *user.Session {
 	s.mapMutex.Lock()
 	defer s.mapMutex.Unlock()
 
@@ -307,24 +132,24 @@ func (s *InMemorySessionManager) NewSessionWithSN(sessID string, screenName stri
 	// 2) the session might be orphaned due to an undetected client
 	// disconnection.
 	for _, sess := range s.store {
-		if screenName == sess.ScreenName {
+		if screenName == sess.ScreenName() {
 			sess.Close()
-			delete(s.store, sess.ID)
+			delete(s.store, sess.ID())
 			break
 		}
 	}
 
-	sess := makeSession()
-	sess.ID = sessID
-	sess.ScreenName = screenName
-	s.store[sess.ID] = sess
+	sess := user.NewSession()
+	sess.SetID(sessID)
+	sess.SetScreenName(screenName)
+	s.store[sess.ID()] = sess
 	return sess
 }
 
-func (s *InMemorySessionManager) Remove(sess *Session) {
+func (s *InMemorySessionManager) Remove(sess *user.Session) {
 	s.mapMutex.Lock()
 	defer s.mapMutex.Unlock()
-	delete(s.store, sess.ID)
+	delete(s.store, sess.ID())
 }
 
 type ChatRoom struct {

+ 55 - 52
server/session_manager_mock.go

@@ -5,7 +5,10 @@ package server
 import (
 	context "context"
 
+	oscar "github.com/mkaminski/goaim/oscar"
 	mock "github.com/stretchr/testify/mock"
+
+	user "github.com/mkaminski/goaim/user"
 )
 
 // MockSessionManager is an autogenerated mock type for the SessionManager type
@@ -22,7 +25,7 @@ func (_m *MockSessionManager) EXPECT() *MockSessionManager_Expecter {
 }
 
 // Broadcast provides a mock function with given fields: ctx, msg
-func (_m *MockSessionManager) Broadcast(ctx context.Context, msg XMessage) {
+func (_m *MockSessionManager) Broadcast(ctx context.Context, msg oscar.XMessage) {
 	_m.Called(ctx, msg)
 }
 
@@ -33,14 +36,14 @@ type MockSessionManager_Broadcast_Call struct {
 
 // Broadcast is a helper method to define mock.On call
 //   - ctx context.Context
-//   - msg XMessage
+//   - msg oscar.XMessage
 func (_e *MockSessionManager_Expecter) Broadcast(ctx interface{}, msg interface{}) *MockSessionManager_Broadcast_Call {
 	return &MockSessionManager_Broadcast_Call{Call: _e.mock.On("Broadcast", ctx, msg)}
 }
 
-func (_c *MockSessionManager_Broadcast_Call) Run(run func(ctx context.Context, msg XMessage)) *MockSessionManager_Broadcast_Call {
+func (_c *MockSessionManager_Broadcast_Call) Run(run func(ctx context.Context, msg oscar.XMessage)) *MockSessionManager_Broadcast_Call {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(XMessage))
+		run(args[0].(context.Context), args[1].(oscar.XMessage))
 	})
 	return _c
 }
@@ -50,13 +53,13 @@ func (_c *MockSessionManager_Broadcast_Call) Return() *MockSessionManager_Broadc
 	return _c
 }
 
-func (_c *MockSessionManager_Broadcast_Call) RunAndReturn(run func(context.Context, XMessage)) *MockSessionManager_Broadcast_Call {
+func (_c *MockSessionManager_Broadcast_Call) RunAndReturn(run func(context.Context, oscar.XMessage)) *MockSessionManager_Broadcast_Call {
 	_c.Call.Return(run)
 	return _c
 }
 
 // BroadcastExcept provides a mock function with given fields: ctx, except, msg
-func (_m *MockSessionManager) BroadcastExcept(ctx context.Context, except *Session, msg XMessage) {
+func (_m *MockSessionManager) BroadcastExcept(ctx context.Context, except *user.Session, msg oscar.XMessage) {
 	_m.Called(ctx, except, msg)
 }
 
@@ -67,15 +70,15 @@ type MockSessionManager_BroadcastExcept_Call struct {
 
 // BroadcastExcept is a helper method to define mock.On call
 //   - ctx context.Context
-//   - except *Session
-//   - msg XMessage
+//   - except *user.Session
+//   - msg oscar.XMessage
 func (_e *MockSessionManager_Expecter) BroadcastExcept(ctx interface{}, except interface{}, msg interface{}) *MockSessionManager_BroadcastExcept_Call {
 	return &MockSessionManager_BroadcastExcept_Call{Call: _e.mock.On("BroadcastExcept", ctx, except, msg)}
 }
 
-func (_c *MockSessionManager_BroadcastExcept_Call) Run(run func(ctx context.Context, except *Session, msg XMessage)) *MockSessionManager_BroadcastExcept_Call {
+func (_c *MockSessionManager_BroadcastExcept_Call) Run(run func(ctx context.Context, except *user.Session, msg oscar.XMessage)) *MockSessionManager_BroadcastExcept_Call {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(*Session), args[2].(XMessage))
+		run(args[0].(context.Context), args[1].(*user.Session), args[2].(oscar.XMessage))
 	})
 	return _c
 }
@@ -85,13 +88,13 @@ func (_c *MockSessionManager_BroadcastExcept_Call) Return() *MockSessionManager_
 	return _c
 }
 
-func (_c *MockSessionManager_BroadcastExcept_Call) RunAndReturn(run func(context.Context, *Session, XMessage)) *MockSessionManager_BroadcastExcept_Call {
+func (_c *MockSessionManager_BroadcastExcept_Call) RunAndReturn(run func(context.Context, *user.Session, oscar.XMessage)) *MockSessionManager_BroadcastExcept_Call {
 	_c.Call.Return(run)
 	return _c
 }
 
 // BroadcastToScreenNames provides a mock function with given fields: ctx, screenNames, msg
-func (_m *MockSessionManager) BroadcastToScreenNames(ctx context.Context, screenNames []string, msg XMessage) {
+func (_m *MockSessionManager) BroadcastToScreenNames(ctx context.Context, screenNames []string, msg oscar.XMessage) {
 	_m.Called(ctx, screenNames, msg)
 }
 
@@ -103,14 +106,14 @@ type MockSessionManager_BroadcastToScreenNames_Call struct {
 // BroadcastToScreenNames is a helper method to define mock.On call
 //   - ctx context.Context
 //   - screenNames []string
-//   - msg XMessage
+//   - msg oscar.XMessage
 func (_e *MockSessionManager_Expecter) BroadcastToScreenNames(ctx interface{}, screenNames interface{}, msg interface{}) *MockSessionManager_BroadcastToScreenNames_Call {
 	return &MockSessionManager_BroadcastToScreenNames_Call{Call: _e.mock.On("BroadcastToScreenNames", ctx, screenNames, msg)}
 }
 
-func (_c *MockSessionManager_BroadcastToScreenNames_Call) Run(run func(ctx context.Context, screenNames []string, msg XMessage)) *MockSessionManager_BroadcastToScreenNames_Call {
+func (_c *MockSessionManager_BroadcastToScreenNames_Call) Run(run func(ctx context.Context, screenNames []string, msg oscar.XMessage)) *MockSessionManager_BroadcastToScreenNames_Call {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].([]string), args[2].(XMessage))
+		run(args[0].(context.Context), args[1].([]string), args[2].(oscar.XMessage))
 	})
 	return _c
 }
@@ -120,7 +123,7 @@ func (_c *MockSessionManager_BroadcastToScreenNames_Call) Return() *MockSessionM
 	return _c
 }
 
-func (_c *MockSessionManager_BroadcastToScreenNames_Call) RunAndReturn(run func(context.Context, []string, XMessage)) *MockSessionManager_BroadcastToScreenNames_Call {
+func (_c *MockSessionManager_BroadcastToScreenNames_Call) RunAndReturn(run func(context.Context, []string, oscar.XMessage)) *MockSessionManager_BroadcastToScreenNames_Call {
 	_c.Call.Return(run)
 	return _c
 }
@@ -167,15 +170,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) *user.Session {
 	ret := _m.Called(sessID, screenName)
 
-	var r0 *Session
-	if rf, ok := ret.Get(0).(func(string, string) *Session); ok {
+	var r0 *user.Session
+	if rf, ok := ret.Get(0).(func(string, string) *user.Session); ok {
 		r0 = rf(sessID, screenName)
 	} else {
 		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(*Session)
+			r0 = ret.Get(0).(*user.Session)
 		}
 	}
 
@@ -201,26 +204,26 @@ 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 *user.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) *user.Session) *MockSessionManager_NewSessionWithSN_Call {
 	_c.Call.Return(run)
 	return _c
 }
 
 // Participants provides a mock function with given fields:
-func (_m *MockSessionManager) Participants() []*Session {
+func (_m *MockSessionManager) Participants() []*user.Session {
 	ret := _m.Called()
 
-	var r0 []*Session
-	if rf, ok := ret.Get(0).(func() []*Session); ok {
+	var r0 []*user.Session
+	if rf, ok := ret.Get(0).(func() []*user.Session); ok {
 		r0 = rf()
 	} else {
 		if ret.Get(0) != nil {
-			r0 = ret.Get(0).([]*Session)
+			r0 = ret.Get(0).([]*user.Session)
 		}
 	}
 
@@ -244,18 +247,18 @@ func (_c *MockSessionManager_Participants_Call) Run(run func()) *MockSessionMana
 	return _c
 }
 
-func (_c *MockSessionManager_Participants_Call) Return(_a0 []*Session) *MockSessionManager_Participants_Call {
+func (_c *MockSessionManager_Participants_Call) Return(_a0 []*user.Session) *MockSessionManager_Participants_Call {
 	_c.Call.Return(_a0)
 	return _c
 }
 
-func (_c *MockSessionManager_Participants_Call) RunAndReturn(run func() []*Session) *MockSessionManager_Participants_Call {
+func (_c *MockSessionManager_Participants_Call) RunAndReturn(run func() []*user.Session) *MockSessionManager_Participants_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 *user.Session) {
 	_m.Called(sess)
 }
 
@@ -265,14 +268,14 @@ type MockSessionManager_Remove_Call struct {
 }
 
 // Remove is a helper method to define mock.On call
-//   - sess *Session
+//   - sess *user.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 *user.Session)) *MockSessionManager_Remove_Call {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(*Session))
+		run(args[0].(*user.Session))
 	})
 	return _c
 }
@@ -282,25 +285,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(*user.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) (*user.Session, bool) {
 	ret := _m.Called(ID)
 
-	var r0 *Session
+	var r0 *user.Session
 	var r1 bool
-	if rf, ok := ret.Get(0).(func(string) (*Session, bool)); ok {
+	if rf, ok := ret.Get(0).(func(string) (*user.Session, bool)); ok {
 		return rf(ID)
 	}
-	if rf, ok := ret.Get(0).(func(string) *Session); ok {
+	if rf, ok := ret.Get(0).(func(string) *user.Session); ok {
 		r0 = rf(ID)
 	} else {
 		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(*Session)
+			r0 = ret.Get(0).(*user.Session)
 		}
 	}
 
@@ -331,30 +334,30 @@ 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 *user.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) (*user.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, error) {
+func (_m *MockSessionManager) RetrieveByScreenName(screenName string) (*user.Session, error) {
 	ret := _m.Called(screenName)
 
-	var r0 *Session
+	var r0 *user.Session
 	var r1 error
-	if rf, ok := ret.Get(0).(func(string) (*Session, error)); ok {
+	if rf, ok := ret.Get(0).(func(string) (*user.Session, error)); ok {
 		return rf(screenName)
 	}
-	if rf, ok := ret.Get(0).(func(string) *Session); ok {
+	if rf, ok := ret.Get(0).(func(string) *user.Session); ok {
 		r0 = rf(screenName)
 	} else {
 		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(*Session)
+			r0 = ret.Get(0).(*user.Session)
 		}
 	}
 
@@ -385,18 +388,18 @@ func (_c *MockSessionManager_RetrieveByScreenName_Call) Run(run func(screenName
 	return _c
 }
 
-func (_c *MockSessionManager_RetrieveByScreenName_Call) Return(_a0 *Session, _a1 error) *MockSessionManager_RetrieveByScreenName_Call {
+func (_c *MockSessionManager_RetrieveByScreenName_Call) Return(_a0 *user.Session, _a1 error) *MockSessionManager_RetrieveByScreenName_Call {
 	_c.Call.Return(_a0, _a1)
 	return _c
 }
 
-func (_c *MockSessionManager_RetrieveByScreenName_Call) RunAndReturn(run func(string) (*Session, error)) *MockSessionManager_RetrieveByScreenName_Call {
+func (_c *MockSessionManager_RetrieveByScreenName_Call) RunAndReturn(run func(string) (*user.Session, error)) *MockSessionManager_RetrieveByScreenName_Call {
 	_c.Call.Return(run)
 	return _c
 }
 
 // SendToScreenName provides a mock function with given fields: ctx, screenName, msg
-func (_m *MockSessionManager) SendToScreenName(ctx context.Context, screenName string, msg XMessage) {
+func (_m *MockSessionManager) SendToScreenName(ctx context.Context, screenName string, msg oscar.XMessage) {
 	_m.Called(ctx, screenName, msg)
 }
 
@@ -408,14 +411,14 @@ type MockSessionManager_SendToScreenName_Call struct {
 // SendToScreenName is a helper method to define mock.On call
 //   - ctx context.Context
 //   - screenName string
-//   - msg XMessage
+//   - msg oscar.XMessage
 func (_e *MockSessionManager_Expecter) SendToScreenName(ctx interface{}, screenName interface{}, msg interface{}) *MockSessionManager_SendToScreenName_Call {
 	return &MockSessionManager_SendToScreenName_Call{Call: _e.mock.On("SendToScreenName", ctx, screenName, msg)}
 }
 
-func (_c *MockSessionManager_SendToScreenName_Call) Run(run func(ctx context.Context, screenName string, msg XMessage)) *MockSessionManager_SendToScreenName_Call {
+func (_c *MockSessionManager_SendToScreenName_Call) Run(run func(ctx context.Context, screenName string, msg oscar.XMessage)) *MockSessionManager_SendToScreenName_Call {
 	_c.Call.Run(func(args mock.Arguments) {
-		run(args[0].(context.Context), args[1].(string), args[2].(XMessage))
+		run(args[0].(context.Context), args[1].(string), args[2].(oscar.XMessage))
 	})
 	return _c
 }
@@ -425,7 +428,7 @@ func (_c *MockSessionManager_SendToScreenName_Call) Return() *MockSessionManager
 	return _c
 }
 
-func (_c *MockSessionManager_SendToScreenName_Call) RunAndReturn(run func(context.Context, string, XMessage)) *MockSessionManager_SendToScreenName_Call {
+func (_c *MockSessionManager_SendToScreenName_Call) RunAndReturn(run func(context.Context, string, oscar.XMessage)) *MockSessionManager_SendToScreenName_Call {
 	_c.Call.Return(run)
 	return _c
 }

+ 48 - 0
server/test_helpers.go

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

+ 218 - 0
user/session.go

@@ -0,0 +1,218 @@
+package user
+
+import (
+	"github.com/google/uuid"
+	"github.com/mkaminski/goaim/oscar"
+	"sync"
+	"time"
+)
+
+var CapChat, _ = uuid.MustParse("748F2420-6287-11D1-8222-444553540000").MarshalBinary()
+
+type SessSendStatus int
+
+const (
+	// SessSendOK indicates message was sent to recipient
+	SessSendOK SessSendStatus = iota
+	// SessSendClosed indicates send did not complete because session is closed
+	SessSendClosed
+	// SessQueueFull indicates send failed due to full queue -- client is likely
+	// dead
+	SessQueueFull
+)
+
+type Session struct {
+	awayMessage string
+	closed      bool
+	id          string
+	idle        bool
+	idleTime    time.Time
+	invisible   bool
+	msgCh       chan oscar.XMessage
+	mutex       sync.RWMutex
+	screenName  string
+	signonTime  time.Time
+	stopCh      chan struct{}
+	warning     uint16
+}
+
+func (s *Session) IncreaseWarning(incr uint16) {
+	s.mutex.Lock()
+	defer s.mutex.Unlock()
+	s.warning += incr
+}
+
+func (s *Session) SetInvisible(invisible bool) {
+	s.mutex.Lock()
+	defer s.mutex.Unlock()
+	s.invisible = invisible
+}
+
+func (s *Session) SetScreenName(screenName string) {
+	s.mutex.Lock()
+	defer s.mutex.Unlock()
+	s.screenName = screenName
+}
+
+func (s *Session) ScreenName() string {
+	s.mutex.RLock()
+	defer s.mutex.RUnlock()
+	return s.screenName
+}
+
+func (s *Session) SetID(ID string) {
+	s.mutex.Lock()
+	defer s.mutex.Unlock()
+	s.id = ID
+}
+
+func (s *Session) SetSignonTime(t time.Time) {
+	s.mutex.Lock()
+	defer s.mutex.Unlock()
+	s.signonTime = t
+}
+
+func (s *Session) ID() string {
+	s.mutex.RLock()
+	defer s.mutex.RUnlock()
+	return s.id
+}
+
+func (s *Session) Invisible() bool {
+	s.mutex.RLock()
+	defer s.mutex.RUnlock()
+	return s.invisible
+}
+
+func (s *Session) SetIdle(dur time.Duration) {
+	s.mutex.Lock()
+	defer s.mutex.Unlock()
+	s.idle = true
+	// set the time the user became idle
+	s.idleTime = time.Now().Add(-dur)
+}
+
+func (s *Session) SetActive() {
+	s.mutex.Lock()
+	defer s.mutex.Unlock()
+	s.idle = false
+}
+
+func (s *Session) Idle() bool {
+	s.mutex.RLock()
+	defer s.mutex.RUnlock()
+	return s.idle
+}
+
+func (s *Session) SetAwayMessage(awayMessage string) {
+	s.mutex.Lock()
+	defer s.mutex.Unlock()
+	s.awayMessage = awayMessage
+}
+
+func (s *Session) AwayMessage() string {
+	s.mutex.RLock()
+	defer s.mutex.RUnlock()
+	return s.awayMessage
+}
+
+func (s *Session) TLVUserInfo() oscar.TLVUserInfo {
+	s.mutex.RLock()
+	defer s.mutex.RUnlock()
+	return oscar.TLVUserInfo{
+		ScreenName:   s.screenName,
+		WarningLevel: s.warning,
+		TLVBlock: oscar.TLVBlock{
+			TLVList: s.UserInfo(),
+		},
+	}
+}
+
+func (s *Session) UserInfo() oscar.TLVList {
+	s.mutex.RLock()
+	defer s.mutex.RUnlock()
+
+	// sign-in timestamp
+	tlvs := oscar.TLVList{}
+
+	tlvs.AddTLV(oscar.NewTLV(0x03, uint32(s.signonTime.Unix())))
+
+	// away message status
+	if s.awayMessage != "" {
+		tlvs.AddTLV(oscar.NewTLV(0x01, uint16(0x0010)|uint16(0x0020)))
+	} else {
+		tlvs.AddTLV(oscar.NewTLV(0x01, uint16(0x0010)))
+	}
+
+	// invisibility status
+	if s.invisible {
+		tlvs.AddTLV(oscar.NewTLV(0x06, uint16(0x0100)))
+	} else {
+		tlvs.AddTLV(oscar.NewTLV(0x06, uint16(0x0000)))
+	}
+
+	// idle status
+	if s.idle {
+		tlvs.AddTLV(oscar.NewTLV(0x04, uint16(time.Now().Sub(s.idleTime).Seconds())))
+	} else {
+		tlvs.AddTLV(oscar.NewTLV(0x04, uint16(0)))
+	}
+
+	// capabilities
+	var caps []byte
+	// chat capability
+	caps = append(caps, CapChat...)
+	tlvs.AddTLV(oscar.NewTLV(0x0D, caps))
+
+	return tlvs
+}
+
+func (s *Session) Warning() uint16 {
+	var w uint16
+	s.mutex.RLock()
+	w = s.warning
+	s.mutex.RUnlock()
+	return w
+}
+
+func (s *Session) RecvMessage() chan oscar.XMessage {
+	return s.msgCh
+}
+
+func (s *Session) SendMessage(msg oscar.XMessage) SessSendStatus {
+	s.mutex.Lock()
+	if s.closed {
+		return SessSendClosed
+	}
+	s.mutex.Unlock()
+	select {
+	case s.msgCh <- msg:
+		return SessSendOK
+	case <-s.stopCh:
+		return SessSendClosed
+	default:
+		return SessQueueFull
+	}
+}
+
+func (s *Session) Close() {
+	s.mutex.Lock()
+	defer s.mutex.Unlock()
+	if s.closed {
+		return
+	}
+	close(s.stopCh)
+	s.closed = true
+}
+
+func (s *Session) Closed() <-chan struct{} {
+	return s.stopCh
+}
+
+func NewSession() *Session {
+	return &Session{
+		msgCh:      make(chan oscar.XMessage, 1000),
+		stopCh:     make(chan struct{}),
+		signonTime: time.Now(),
+	}
+}

+ 9 - 8
server/session_test.go → user/session_test.go

@@ -1,6 +1,7 @@
-package server
+package user
 
 import (
+	"github.com/mkaminski/goaim/oscar"
 	"github.com/stretchr/testify/assert"
 	"testing"
 	"time"
@@ -8,21 +9,21 @@ import (
 
 func TestSession_SendMessage_SessSendOK(t *testing.T) {
 	s := Session{
-		msgCh:  make(chan XMessage, 1),
+		msgCh:  make(chan oscar.XMessage, 1),
 		stopCh: make(chan struct{}),
 	}
-	if res := s.SendMessage(XMessage{}); res != SessSendOK {
+	if res := s.SendMessage(oscar.XMessage{}); res != SessSendOK {
 		t.Fatalf("expected SessSendOK, got %+v", res)
 	}
 }
 
 func TestSession_SendMessage_SessSendClosed(t *testing.T) {
 	s := Session{
-		msgCh:  make(chan XMessage, 1),
+		msgCh:  make(chan oscar.XMessage, 1),
 		stopCh: make(chan struct{}),
 	}
 	s.Close()
-	if res := s.SendMessage(XMessage{}); res != SessSendClosed {
+	if res := s.SendMessage(oscar.XMessage{}); res != SessSendClosed {
 		t.Fatalf("expected SessSendClosed, got %+v", res)
 	}
 }
@@ -30,13 +31,13 @@ func TestSession_SendMessage_SessSendClosed(t *testing.T) {
 func TestSession_SendMessage_SessQueueFull(t *testing.T) {
 	bufSize := 10
 	s := Session{
-		msgCh:  make(chan XMessage, bufSize),
+		msgCh:  make(chan oscar.XMessage, bufSize),
 		stopCh: make(chan struct{}),
 	}
 	for i := 0; i < bufSize; i++ {
-		assert.Equal(t, SessSendOK, s.SendMessage(XMessage{}))
+		assert.Equal(t, SessSendOK, s.SendMessage(oscar.XMessage{}))
 	}
-	assert.Equal(t, SessQueueFull, s.SendMessage(XMessage{}))
+	assert.Equal(t, SessQueueFull, s.SendMessage(oscar.XMessage{}))
 }
 
 func TestSession_Close_Twice(t *testing.T) {