Mike 9 часов назад
Родитель
Сommit
3ace5312e7
36 измененных файлов с 4129 добавлено и 7391 удалено
  1. 0 36
      .mockery.yaml
  2. 1 1
      AGENTS.md
  3. 11 73
      cmd/server/factory.go
  4. 6 2
      cmd/server/main.go
  5. 226 0
      cmd/server/toc_integration_test.go
  6. 32 6
      server/oscar/server.go
  7. 62 0
      server/oscar/server_test.go
  8. 227 487
      server/toc/cmd_client.go
  9. 1327 2206
      server/toc/cmd_client_test.go
  10. 307 99
      server/toc/cmd_server.go
  11. 416 121
      server/toc/cmd_server_test.go
  12. 303 239
      server/toc/helpers_test.go
  13. 0 1
      server/toc/http_test.go
  14. 0 127
      server/toc/mock_admin_service_test.go
  15. 0 642
      server/toc/mock_auth_service_test.go
  16. 0 162
      server/toc/mock_buddy_list_registry_test.go
  17. 0 369
      server/toc/mock_buddy_service_test.go
  18. 0 328
      server/toc/mock_chat_nav_service_test.go
  19. 0 129
      server/toc/mock_chat_service_test.go
  20. 0 86
      server/toc/mock_chat_session_manager_test.go
  21. 0 174
      server/toc/mock_feedbag_manager_test.go
  22. 0 663
      server/toc/mock_feedbag_service_test.go
  23. 0 402
      server/toc/mock_icbm_service_test.go
  24. 0 141
      server/toc/mock_locate_service_test.go
  25. 0 318
      server/toc/mock_oservice_service_test.go
  26. 0 358
      server/toc/mock_permit_deny_service_test.go
  27. 332 0
      server/toc/oscar_conn.go
  28. 203 0
      server/toc/oscar_conn_test.go
  29. 29 63
      server/toc/server.go
  30. 23 32
      server/toc/server_test.go
  31. 85 0
      server/toc/snac_cache.go
  32. 89 0
      server/toc/snac_cache_test.go
  33. 324 0
      server/toc/toc_conn.go
  34. 116 0
      server/toc/toc_conn_test.go
  35. 10 102
      server/toc/types.go
  36. 0 24
      state/session.go

+ 0 - 36
.mockery.yaml

@@ -171,42 +171,12 @@ packages:
           filename: "mock_user_manager_manager_test.go"
   github.com/mk6i/open-oscar-server/server/toc:
     interfaces:
-      AdminService:
-        config:
-          filename: "mock_admin_service_test.go"
-      BuddyService:
-        config:
-          filename: "mock_buddy_service_test.go"
-      ChatService:
-        config:
-          filename: "mock_chat_service_test.go"
-      ChatNavService:
-        config:
-          filename: "mock_chat_nav_service_test.go"
-      ChatSessionManager:
-        config:
-          filename: "mock_chat_session_manager_test.go"
-      ICBMService:
-        config:
-          filename: "mock_icbm_service_test.go"
-      OServiceService:
-        config:
-          filename: "mock_oservice_service_test.go"
-      AuthService:
-        config:
-          filename: "mock_auth_service_test.go"
       LocateService:
         config:
           filename: "mock_locate_service_test.go"
       DirSearchService:
         config:
           filename: "mock_dir_search_service_test.go"
-      PermitDenyService:
-        config:
-          filename: "mock_permit_deny_service_test.go"
-      BuddyListRegistry:
-        config:
-          filename: "mock_buddy_list_registry_test.go"
       TOCConfigStore:
         config:
           filename: "mock_toc_config_store_test.go"
@@ -216,12 +186,6 @@ packages:
       SessionRetriever:
         config:
           filename: "mock_session_retriever_test.go"
-      FeedbagService:
-        config:
-          filename: "mock_feedbag_service_test.go"
-      FeedbagManager:
-        config:
-          filename: "mock_feedbag_manager_test.go"
   github.com/mk6i/open-oscar-server/server/kerberos:
     interfaces:
       AuthService:

+ 1 - 1
AGENTS.md

@@ -46,7 +46,7 @@ logic.
 | `wire`            | Wire formats: OSCAR SNAC/FLAP encoding, TLV, food group codes, rate limits, frames, plus AMF3 encoding for the Web AIM client.                                                                                 |
 | `state`           | Persistence and in-memory state: `SQLiteUserStore`, `InMemorySessionManager`, `InMemoryChatSessionManager`, DB migrations.                                                                                     |
 | `server/oscar`    | OSCAR protocol server; SNAC routing and handler wiring.                                                                                                                                                        |
-| `server/toc`      | TOC protocol server (text-based).                                                                                                                                                                              |
+| `server/toc`      | TOC protocol server (text-based). Each TOC user is an OSCAR client of the OSCAR server, connected over in-memory OSCAR connections (`OSCARServer.ServeConn`).                                                  |
 | `server/kerberos` | Kerberos auth server.                                                                                                                                                                                          |
 | `server/http`     | Management HTTP API (users, sessions, chat rooms). Spec: `api.yml`.                                                                                                                                            |
 | `server/webapi`   | Web AIM-style API (AMF3). Spec: `docs/open_api/webapi.yml`.                                                                                                                                                    |

+ 11 - 73
cmd/server/factory.go

@@ -430,50 +430,17 @@ func MgmtAPI(deps Container) *http.Server {
 	)
 }
 
-// TOC creates a TOC server.
-func TOC(deps Container) *toc.Server {
+// TOC creates a TOC server whose users connect to oscarServer.
+func TOC(deps Container, oscarServer *oscar.Server) *toc.Server {
 	logger := deps.logger.With("svc", "TOC")
 
 	return toc.NewServer(
 		deps.cfg.TOCListeners,
 		logger,
 		toc.OSCARProxy{
-			AdminService: foodgroup.NewAdminService(
-				deps.sqLiteUserStore,
-				deps.sqLiteUserStore,
-				deps.sqLiteUserStore,
-				deps.inMemorySessionManager,
-				deps.inMemorySessionManager,
-				deps.logger,
-			),
-			AuthService: foodgroup.NewAuthService(
-				deps.cfg,
-				deps.inMemorySessionManager,
-				deps.inMemorySessionManager,
-				deps.chatSessionManager,
-				deps.sqLiteUserStore,
-				deps.hmacCookieBaker,
-				deps.chatSessionManager,
-				deps.sqLiteUserStore,
-				deps.sqLiteUserStore,
-				deps.sqLiteUserStore,
-				deps.rateLimitClasses,
-				state.NewAccountCreator(deps.sqLiteUserStore.InsertUser),
-				logger,
-			),
-			BuddyListRegistry: deps.sqLiteUserStore,
-			BuddyService: foodgroup.NewBuddyService(
-				deps.inMemorySessionManager,
-				deps.sqLiteUserStore,
-				deps.sqLiteUserStore,
-				deps.inMemorySessionManager,
-				deps.sqLiteUserStore,
-				deps.sqLiteUserStore,
-			),
-			ChatSessionManager: deps.chatSessionManager,
-			CookieBaker:        deps.hmacCookieBaker,
-			DirSearchService:   foodgroup.NewODirService(logger, deps.sqLiteUserStore),
-			ICBMService:        deps.icbmSvc,
+			CookieBaker:       deps.hmacCookieBaker,
+			DirSearchService:  foodgroup.NewODirService(logger, deps.sqLiteUserStore),
+			HTTPIPRateLimiter: toc.NewIPRateLimiter(rate.Every(1*time.Minute), 10, 1*time.Minute),
 			LocateService: foodgroup.NewLocateService(
 				deps.sqLiteUserStore,
 				deps.inMemorySessionManager,
@@ -482,42 +449,13 @@ func TOC(deps Container) *toc.Server {
 				deps.inMemorySessionManager,
 				deps.sqLiteUserStore,
 			),
-			Logger: logger,
-			OServiceService: foodgroup.NewOServiceService(
-				deps.cfg,
-				deps.inMemorySessionManager,
-				logger,
-				deps.hmacCookieBaker,
-				deps.sqLiteUserStore,
-				deps.sqLiteUserStore,
-				deps.inMemorySessionManager,
-				deps.sqLiteUserStore,
-				deps.snacRateLimits,
-				deps.chatSessionManager,
-				deps.sqLiteUserStore,
-				deps.sqLiteUserStore,
-				deps.sqLiteUserStore,
-			),
-			PermitDenyService: foodgroup.NewPermitDenyService(
-				deps.sqLiteUserStore,
-				deps.sqLiteUserStore,
-				deps.sqLiteUserStore,
-				deps.inMemorySessionManager,
-				deps.inMemorySessionManager,
-			),
-			TOCConfigStore:    deps.sqLiteUserStore,
-			ChatService:       foodgroup.NewChatService(deps.chatSessionManager),
-			ChatNavService:    foodgroup.NewChatNavService(logger, deps.sqLiteUserStore),
-			FeedbagManager:    deps.sqLiteUserStore,
-			FeedbagService:    deps.feedbagSvc,
-			SNACRateLimits:    deps.snacRateLimits,
-			HTTPIPRateLimiter: toc.NewIPRateLimiter(rate.Every(1*time.Minute), 10, 1*time.Minute),
-			SessionRetriever:  deps.inMemorySessionManager,
-			RandIntn:          rand.Intn,
+			Logger:           logger,
+			OSCARServer:      oscarServer,
+			OSCAREndpoint:    deps.Listeners[0].PlainEndpoint(),
+			SessionRetriever: deps.inMemorySessionManager,
+			TOCConfigStore:   deps.sqLiteUserStore,
+			RandIntn:         rand.Intn,
 		},
-		toc.NewIPRateLimiter(rate.Every(1*time.Minute), 10, 1*time.Minute),
-		deps.icbmSvc.RestoreWarningLevel,
-		deps.icbmSvc.UpdateWarnLevel,
 	)
 }
 

+ 6 - 2
cmd/server/main.go

@@ -22,7 +22,9 @@ var (
 	date    = "unknown"
 )
 
-func init() {
+// parseFlags handles the command line flags and loads the config file into the
+// environment.
+func parseFlags() {
 	cfgFile := flag.String("config", "settings.env", "Path to config file")
 	showHelp := flag.Bool("help", false, "Display help")
 	showVersion := flag.Bool("version", false, "Display build information")
@@ -49,6 +51,8 @@ func init() {
 }
 
 func main() {
+	parseFlags()
+
 	ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
 	defer stop()
 
@@ -69,7 +73,7 @@ func main() {
 	api := MgmtAPI(deps)
 	g.Go(api.ListenAndServe)
 
-	toc := TOC(deps)
+	toc := TOC(deps, oscar)
 	g.Go(toc.ListenAndServe)
 
 	var webAPI *webapi.Server

+ 226 - 0
cmd/server/toc_integration_test.go

@@ -0,0 +1,226 @@
+package main
+
+import (
+	"context"
+	"fmt"
+	"net"
+	"path/filepath"
+	"regexp"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+
+	"github.com/mk6i/open-oscar-server/wire"
+)
+
+// tocTestClient is a TOC client connected to the TOC server under test.
+type tocTestClient struct {
+	t     *testing.T
+	name  string
+	conn  net.Conn
+	flapc *wire.FlapClient
+	lines chan string
+}
+
+// dialTOC connects a TOC client to addr and sends the signon command.
+func dialTOC(t *testing.T, addr string, screenName string, signonCmd string) *tocTestClient {
+	conn, err := net.Dial("tcp", addr)
+	if !assert.NoError(t, err) {
+		t.FailNow()
+	}
+	_, err = conn.Write([]byte("FLAPON\r\n\r\n"))
+	assert.NoError(t, err)
+
+	flapc := wire.NewFlapClient(0, conn, conn)
+	_, err = flapc.ReceiveSignonFrame()
+	assert.NoError(t, err)
+	assert.NoError(t, flapc.SendSignonFrame([]wire.TLV{wire.NewTLVBE(0x01, screenName)}))
+
+	c := &tocTestClient{
+		t:     t,
+		name:  screenName,
+		conn:  conn,
+		flapc: flapc,
+		lines: make(chan string, 100),
+	}
+	go func() {
+		defer close(c.lines)
+		for {
+			frame, err := flapc.ReceiveFLAP()
+			if err != nil {
+				return
+			}
+			if frame.FrameType == wire.FLAPFrameData {
+				c.lines <- string(frame.Payload)
+			}
+		}
+	}()
+	c.send(signonCmd)
+	return c
+}
+
+func (c *tocTestClient) send(cmd string) {
+	assert.NoError(c.t, c.flapc.SendDataFrame(append([]byte(cmd), 0)))
+}
+
+// expect skips lines until one matches pattern, failing the test if none does
+// within a few seconds. It returns the match and its submatches.
+func (c *tocTestClient) expect(pattern string) []string {
+	rx := regexp.MustCompile(pattern)
+	var skipped []string
+	timeout := time.After(5 * time.Second)
+	for {
+		select {
+		case line, ok := <-c.lines:
+			if !ok {
+				c.t.Fatalf("%s: connection closed waiting for /%s/, skipped %q", c.name, pattern, skipped)
+			}
+			if m := rx.FindStringSubmatch(line); m != nil {
+				return m
+			}
+			skipped = append(skipped, line)
+		case <-timeout:
+			c.t.Fatalf("%s: timed out waiting for /%s/, skipped %q", c.name, pattern, skipped)
+		}
+	}
+}
+
+// TestTOC_OSCARIntegration drives TOC clients through the TOC server against a
+// real OSCAR server that TOC reaches over in-memory OSCAR connections.
+func TestTOC_OSCARIntegration(t *testing.T) {
+	ln, err := net.Listen("tcp", "127.0.0.1:0")
+	assert.NoError(t, err)
+	tocAddr := ln.Addr().String()
+	assert.NoError(t, ln.Close())
+
+	t.Setenv("OSCAR_LISTENERS", "LOCAL://127.0.0.1:5190")
+	t.Setenv("OSCAR_ADVERTISED_LISTENERS_PLAIN", "LOCAL://127.0.0.1:5190")
+	t.Setenv("TOC_LISTENERS", tocAddr)
+	t.Setenv("API_LISTENER", "127.0.0.1:0")
+	t.Setenv("DB_PATH", filepath.Join(t.TempDir(), "oscar.sqlite"))
+	t.Setenv("DISABLE_AUTH", "true")
+	t.Setenv("LOG_LEVEL", "error")
+	t.Setenv("WEBAPI_ENABLED", "false")
+	t.Setenv("ICQ_LEGACY_ENABLED", "false")
+
+	deps, err := MakeCommonDeps()
+	if !assert.NoError(t, err) {
+		t.FailNow()
+	}
+
+	oscarServer := OSCAR(deps)
+	tocServer := TOC(deps, oscarServer)
+	go func() {
+		_ = tocServer.ListenAndServe()
+	}()
+	defer func() {
+		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+		defer cancel()
+		_ = oscarServer.Shutdown(ctx)
+		_ = tocServer.Shutdown(ctx)
+	}()
+
+	assert.Eventually(t, func() bool {
+		conn, err := net.Dial("tcp", tocAddr)
+		if err != nil {
+			return false
+		}
+		_ = conn.Close()
+		return true
+	}, 5*time.Second, 10*time.Millisecond)
+
+	const password = "0x2408105c23001130"
+
+	alice := dialTOC(t, tocAddr, "alice", `toc2_login localhost 9898 alice `+password+` english "TIC:Test" 160 US "" "" 3 0 30303 -kentucky -utf8 103547776`)
+	alice.expect(`^SIGN_ON:TOC2\.0$`)
+	alice.expect(`^NICK:alice$`)
+	alice.expect(`^CONFIG2:done:\n$`)
+	alice.send("toc2_new_buddies {g:Buddies\nb:bob\n}")
+	alice.expect(`^NEW_BUDDY_REPLY2:bob:added$`)
+	alice.send("toc_init_done")
+
+	bob := dialTOC(t, tocAddr, "bob", `toc_signon localhost 9898 bob `+password+` english "TIC:Test"`)
+	bob.expect(`^SIGN_ON:TOC1\.0$`)
+	bob.expect(`^CONFIG:$`)
+	bob.expect(`^NICK:bob$`)
+	bob.send("toc_add_buddy alice")
+	bob.send("toc_init_done")
+
+	// presence in both directions
+	alice.expect(`^UPDATE_BUDDY2:bob:T:0:\d+:0:.{3}:$`)
+	bob.expect(`^UPDATE_BUDDY:alice:T:0:\d+:0:.{3}$`)
+
+	// instant messages in both directions
+	bob.send(`toc_send_im alice "hello alice"`)
+	alice.expect(`^IM_IN_ENC2:bob:F:F:T:...:F:L:en:hello alice$`)
+	alice.send(`toc2_send_im bob "hello bob"`)
+	bob.expect(`^IM_IN:alice:F:hello bob$`)
+
+	// a reply that only makes sense next to its request
+	bob.send("toc_get_status nobody")
+	bob.expect(`^ERROR:901:nobody$`)
+
+	// the room is announced before its first member list
+	alice.send(`toc_chat_join 4 "Integration Room"`)
+	alice.expect(`^CHAT_JOIN:0:Integration Room$`)
+	alice.expect(`^CHAT_UPDATE_BUDDY:0:T:alice$`)
+	alice.send(`toc_chat_send 0 "hello room"`)
+	alice.expect(`^CHAT_IN_ENC:0:alice:F:A:en:hello room$`)
+
+	// invitations, accepting them, and leaving rooms
+	alice.send(`toc_chat_invite 0 "join us" bob`)
+	invite := bob.expect(`^CHAT_INVITE:Integration Room:(\d+):alice:join us$`)
+	bob.send("toc_chat_accept " + invite[1])
+	bob.expect(`^CHAT_JOIN:` + invite[1] + `:Integration Room$`)
+	alice.expect(`^CHAT_UPDATE_BUDDY:0:T:bob$`)
+	bob.send("toc_chat_leave " + invite[1])
+	bob.expect(`^CHAT_LEFT:` + invite[1] + `$`)
+	alice.expect(`^CHAT_UPDATE_BUDDY:0:F:bob$`)
+
+	// a large buddy list addition is stored in full
+	var newBuddies strings.Builder
+	for i := range 60 {
+		fmt.Fprintf(&newBuddies, "g:Group %02d\nb:buddy%02d\n", i, i)
+	}
+	alice.send("toc2_new_buddies {" + newBuddies.String() + "}")
+	for i := range 60 {
+		alice.expect(fmt.Sprintf(`^NEW_BUDDY_REPLY2:buddy%02d:added$`, i))
+	}
+	// alice's commands are handled in order, so her buddy list is stored once
+	// this reply arrives
+	alice.send("toc_get_status alice")
+	alice.expect(`^UPDATE_BUDDY2:alice:T:`)
+	alice2 := dialTOC(t, tocAddr, "alice", `toc2_login localhost 9898 alice `+password+` english "TIC:Test" 160 US "" "" 3 0 30303 -kentucky -utf8 103547776`)
+	config := alice2.expect(`^CONFIG2:(?s:.*)$`)[0]
+	for i := range 60 {
+		assert.Contains(t, config, fmt.Sprintf("g:Group %02d\nb:buddy%02d\n", i, i))
+	}
+	assert.NotContains(t, config, "b:\n")
+	assert.NoError(t, alice2.conn.Close())
+
+	// a TOC1 user's deny list hides them from the denied user
+	carol := dialTOC(t, tocAddr, "carol", `toc_signon localhost 9898 carol `+password+` english "TIC:Test"`)
+	carol.expect(`^NICK:carol$`)
+	carol.send("toc_add_deny dave")
+	carol.send("toc_init_done")
+	// carol's commands are handled in order, so she's signed on once this
+	// reply arrives
+	carol.send("toc_get_status carol")
+	carol.expect(`^UPDATE_BUDDY:carol:T:`)
+	dave := dialTOC(t, tocAddr, "dave", `toc_signon localhost 9898 dave `+password+` english "TIC:Test"`)
+	dave.expect(`^NICK:dave$`)
+	dave.send("toc_init_done")
+	dave.send("toc_get_status carol")
+	dave.expect(`^ERROR:901:carol$`)
+	bob.send("toc_get_status carol")
+	bob.expect(`^UPDATE_BUDDY:carol:T:`)
+	assert.NoError(t, carol.conn.Close())
+	assert.NoError(t, dave.conn.Close())
+
+	// signing off is seen by buddies
+	assert.NoError(t, bob.conn.Close())
+	alice.expect(`^UPDATE_BUDDY2:bob:F:0:0:0:   :$`)
+	assert.NoError(t, alice.conn.Close())
+}

+ 32 - 6
server/oscar/server.go

@@ -112,7 +112,10 @@ func (s *Server) ListenAndServe() error {
 
 func (s *Server) Shutdown(ctx context.Context) error {
 	s.logger.Debug("Initiating graceful shutdown...")
+	// connections are tracked under connMu, so none can start after this
+	s.connMu.Lock()
 	s.shutdownCancel()
+	s.connMu.Unlock()
 	s.cleanupListeners()
 
 	// Wait for handlers to complete
@@ -148,16 +151,39 @@ func (s *Server) acceptLoop(ln net.Listener, endpointCfg config.Endpoint) {
 			continue
 		}
 
-		// track connection
-		s.connMu.Lock()
-		s.conns[conn] = struct{}{}
-		s.connMu.Unlock()
-
-		s.connWg.Add(1)
+		if !s.trackConn(conn) {
+			_ = conn.Close()
+			continue
+		}
 		go s.handleConnection(s.shutdownCtx, conn, endpointCfg)
 	}
 }
 
+// ServeConn serves a connection that didn't come from one of the server's
+// listeners, such as an in-memory connection opened by the TOC server, as if
+// it had arrived on the listener at endpointCfg. It returns when the
+// connection ends.
+func (s *Server) ServeConn(conn net.Conn, endpointCfg config.Endpoint) {
+	if !s.trackConn(conn) {
+		_ = conn.Close()
+		return
+	}
+	s.handleConnection(s.shutdownCtx, conn, endpointCfg)
+}
+
+// trackConn registers conn so that Shutdown waits for it. It returns false
+// if the server is shutting down.
+func (s *Server) trackConn(conn net.Conn) bool {
+	s.connMu.Lock()
+	defer s.connMu.Unlock()
+	if s.shutdownCtx.Err() != nil {
+		return false
+	}
+	s.conns[conn] = struct{}{}
+	s.connWg.Add(1)
+	return true
+}
+
 func (s *Server) handleConnection(ctx context.Context, conn net.Conn, endpointCfg config.Endpoint) {
 	defer func() {
 		// untrack connections

+ 62 - 0
server/oscar/server_test.go

@@ -1244,3 +1244,65 @@ func Test_oscarServer_receiveSessMessages_Chat_integration(t *testing.T) {
 
 	signoutWG.Wait()
 }
+
+func newServeConnTestServer() *Server {
+	return NewServer(
+		nil,
+		nil,
+		nil,
+		nil,
+		slog.Default(),
+		nil,
+		nil,
+		nil,
+		wire.DefaultSNACRateLimits(),
+		nil,
+		nil,
+		func(ctx context.Context, instance *state.SessionInstance) error { return nil },
+		func(ctx context.Context, instance *state.SessionInstance) {},
+	)
+}
+
+func TestServer_ServeConn(t *testing.T) {
+	server := newServeConnTestServer()
+
+	var gotEndpoint config.Endpoint
+	var gotRemote net.Addr
+	server.handler = func(ctx context.Context, conn net.Conn, endpointCfg config.Endpoint) error {
+		gotEndpoint = endpointCfg
+		gotRemote = conn.RemoteAddr()
+		return nil
+	}
+
+	serverEnd, clientEnd := net.Pipe()
+	clientAddr := &net.TCPAddr{IP: net.IPv4(10, 0, 0, 5), Port: 4321}
+	endpoint := config.Endpoint{ListenAddress: ":5190", Group: config.ListenerGroup{Name: "LOCAL"}}
+
+	server.ServeConn(fakeConn{Conn: serverEnd, remote: clientAddr}, endpoint)
+
+	assert.Equal(t, endpoint, gotEndpoint)
+	assert.Equal(t, clientAddr, gotRemote)
+
+	// the connection is closed once served
+	_, err := clientEnd.Read(make([]byte, 1))
+	assert.Error(t, err)
+	assert.Empty(t, server.conns)
+}
+
+func TestServer_ServeConn_afterShutdown(t *testing.T) {
+	server := newServeConnTestServer()
+
+	called := false
+	server.handler = func(ctx context.Context, conn net.Conn, endpointCfg config.Endpoint) error {
+		called = true
+		return nil
+	}
+	assert.NoError(t, server.Shutdown(context.Background()))
+
+	serverEnd, clientEnd := net.Pipe()
+	server.ServeConn(serverEnd, config.Endpoint{})
+
+	assert.False(t, called)
+	_, err := clientEnd.Read(make([]byte, 1))
+	assert.Error(t, err)
+}

Разница между файлами не показана из-за своего большого размера
+ 227 - 487
server/toc/cmd_client.go


Разница между файлами не показана из-за своего большого размера
+ 1327 - 2206
server/toc/cmd_client_test.go


+ 307 - 99
server/toc/cmd_server.go

@@ -12,7 +12,6 @@ import (
 
 	"github.com/google/uuid"
 
-	"github.com/mk6i/open-oscar-server/state"
 	"github.com/mk6i/open-oscar-server/wire"
 )
 
@@ -24,67 +23,129 @@ var (
 	errDisconnect        = errors.New("got booted by another session")
 )
 
-// RecvBOS routes incoming SNAC messages from the BOS server to their
-// corresponding TOC handlers. It ignores any SNAC messages for which there is
-// no TOC response.
-func (s OSCARProxy) RecvBOS(ctx context.Context, me *state.SessionInstance, chatRegistry *ChatRegistry, ch chan<- []string) error {
+// OServiceRateParamsChange codes the OSCAR server sends when one of the user's
+// rate classes becomes limited or clears.
+const (
+	rateParamsChangeLimited uint16 = 3
+	rateParamsChangeClear   uint16 = 4
+)
+
+// RecvBOS routes SNACs from the BOS server, replies and server-initiated
+// messages alike, to their corresponding TOC handlers. It ignores any SNAC for
+// which there is no TOC response.
+func (s OSCARProxy) RecvBOS(ctx context.Context, me *tocConn) error {
+	for _, snac := range me.backlog {
+		s.recvBOSSNAC(ctx, me, snac)
+	}
+	me.backlog = nil
+
 	for {
 		select {
 		case <-ctx.Done():
-			me.CloseInstance()
+			me.bos.Close()
 			return nil
-		case <-me.Closed():
+		case <-me.bos.Closed():
 			return errDisconnect
-		case snac := <-me.ReceiveMessage():
-			switch v := snac.Body.(type) {
-			case wire.SNAC_0x03_0x0B_BuddyArrived:
-				sendOrCancel(ctx, ch, s.UpdateBuddyArrival(v, me))
-			case wire.SNAC_0x03_0x0C_BuddyDeparted:
-				sendOrCancel(ctx, ch, s.UpdateBuddyDeparted(v, me))
-			case wire.SNAC_0x04_0x07_ICBMChannelMsgToClient:
-				sendOrCancel(ctx, ch, s.IMIn(ctx, chatRegistry, me, v))
-			case wire.SNAC_0x01_0x10_OServiceEvilNotification:
-				sendOrCancel(ctx, ch, s.Eviled(v))
-			case wire.SNAC_0x04_0x14_ICBMClientEvent:
-				if me.IsTOC2() {
-					sendOrCancel(ctx, ch, s.ClientEvent(v))
-				}
-			case wire.SNAC_0x13_0x09_FeedbagUpdateItem:
-				if me.IsTOC2() {
-					sendOrCancel(ctx, ch, s.Inserted2(ctx, me, v))
-				}
-			case wire.SNAC_0x13_0x0A_FeedbagDeleteItem:
-				if me.IsTOC2() {
-					sendOrCancel(ctx, ch, s.Deleted2(ctx, me, v))
-				}
+		case snac := <-me.bos.Messages():
+			s.recvBOSSNAC(ctx, me, snac)
+		}
+	}
+}
 
-			default:
-				s.Logger.DebugContext(ctx, fmt.Sprintf("unsupported snac. foodgroup: %s subgroup: %s",
-					wire.FoodGroupName(snac.Frame.FoodGroup),
-					wire.SubGroupName(snac.Frame.FoodGroup, snac.Frame.SubGroup)))
-			}
+func (s OSCARProxy) recvBOSSNAC(ctx context.Context, me *tocConn, snac wire.SNACMessage) {
+	var msg []string
+
+	switch v := snac.Body.(type) {
+	case wire.SNAC_0x03_0x0B_BuddyArrived:
+		msg = s.UpdateBuddyArrival(v, me)
+	case wire.SNAC_0x03_0x0C_BuddyDeparted:
+		msg = s.UpdateBuddyDeparted(v, me)
+	case wire.SNAC_0x04_0x07_ICBMChannelMsgToClient:
+		msg = s.IMIn(ctx, me, v)
+	case wire.SNAC_0x01_0x10_OServiceEvilNotification:
+		msg = s.Eviled(v)
+	case wire.SNAC_0x01_0x0A_OServiceRateParamsChange:
+		msg = s.RateParamsChange(v)
+		if v.Code == rateParamsChangeClear && me.feedbag.hasPending() {
+			// the server may have dropped feedbag changes without replying
+			// while limited
+			msg = s.reloadFeedbag(ctx, me)
+		}
+	case wire.SNAC_0x04_0x14_ICBMClientEvent:
+		if me.IsTOC2() {
+			msg = s.ClientEvent(v)
+		}
+	case wire.SNAC_0x13_0x08_FeedbagInsertItem:
+		if me.IsTOC2() {
+			me.feedbag.applyRemote(false, v.Items)
+			msg = s.Inserted2(me, v.Items)
+		}
+	case wire.SNAC_0x13_0x09_FeedbagUpdateItem:
+		if me.IsTOC2() {
+			me.feedbag.applyRemote(false, v.Items)
+			msg = s.Inserted2(me, v.Items)
+		}
+	case wire.SNAC_0x13_0x0A_FeedbagDeleteItem:
+		if me.IsTOC2() {
+			msg = s.Deleted2(me, v)
+			me.feedbag.applyRemote(true, v.Items)
 		}
+	case wire.SNACError:
+		msg = s.SNACError(ctx, me, snac.Frame, v)
+	case wire.SNAC_0x02_0x06_LocateUserInfoReply:
+		msg = s.UserInfoReply(me, snac.Frame, v)
+	case wire.SNAC_0x07_0x05_AdminChangeReply:
+		msg = s.AdminChangeReply(ctx, me, snac.Frame, v)
+	case wire.SNAC_0x0D_0x09_ChatNavNavInfo:
+		msg = s.ChatNavInfo(ctx, me, snac.Frame, v)
+	case wire.SNAC_0x01_0x05_OServiceServiceResponse:
+		msg = s.ChatServiceResponse(ctx, me, snac.Frame, v)
+	case wire.SNAC_0x13_0x0E_FeedbagStatus:
+		s.FeedbagStatus(me, snac.Frame, v)
+	case wire.SNAC_0x13_0x06_FeedbagReply:
+		s.FeedbagReply(me, snac.Frame, v)
+	case wire.SNAC_0x04_0x09_ICBMEvilReply:
+		// the warning succeeded and there's nothing to tell the client
+		me.cache.take(snac.Frame)
+	default:
+		s.Logger.DebugContext(ctx, fmt.Sprintf("unsupported snac. foodgroup: %s subgroup: %s",
+			wire.FoodGroupName(snac.Frame.FoodGroup),
+			wire.SubGroupName(snac.Frame.FoodGroup, snac.Frame.SubGroup)))
+	}
+
+	if len(msg) > 0 {
+		sendOrCancel(ctx, me.toCh, msg)
 	}
 }
 
-// RecvChat routes incoming SNAC messages from the chat server to their
-// corresponding TOC handlers. It ignores any SNAC messages for which there is
-// no TOC response.
-func (s OSCARProxy) RecvChat(ctx context.Context, me *state.SessionInstance, chatID int, ch chan<- []string) {
+// RecvChat routes SNACs from a chat room connection to their corresponding
+// TOC handlers. It ignores any SNAC for which there is no TOC response.
+func (s OSCARProxy) RecvChat(ctx context.Context, me *tocConn, conn oscarConn, chatID int) {
 	for {
 		select {
 		case <-ctx.Done():
+			conn.Close()
 			return
-		case <-me.Closed():
+		case <-conn.Closed():
 			return
-		case snac := <-me.ReceiveMessage():
+		case snac := <-conn.Messages():
 			switch v := snac.Body.(type) {
 			case wire.SNAC_0x0E_0x04_ChatUsersLeft:
-				sendOrCancel(ctx, ch, s.ChatUpdateBuddyLeft(v, chatID))
+				sendOrCancel(ctx, me.toCh, s.ChatUpdateBuddyLeft(v, chatID))
 			case wire.SNAC_0x0E_0x03_ChatUsersJoined:
-				sendOrCancel(ctx, ch, s.ChatUpdateBuddyArrived(v, chatID))
+				sendOrCancel(ctx, me.toCh, s.ChatUpdateBuddyArrived(v, chatID))
 			case wire.SNAC_0x0E_0x06_ChatChannelMsgToClient:
-				sendOrCancel(ctx, ch, s.ChatIn(ctx, me, v, chatID))
+				// a reflected message answers the user's own toc_chat_send
+				me.cache.take(snac.Frame)
+				sendOrCancel(ctx, me.toCh, s.ChatIn(ctx, me, v, chatID))
+			case wire.SNAC_0x01_0x0A_OServiceRateParamsChange:
+				if msg := s.RateParamsChange(v); len(msg) > 0 {
+					sendOrCancel(ctx, me.toCh, msg)
+				}
+			case wire.SNACError:
+				if msg := s.SNACError(ctx, me, snac.Frame, v); len(msg) > 0 {
+					sendOrCancel(ctx, me.toCh, msg)
+				}
 			default:
 				s.Logger.DebugContext(ctx, fmt.Sprintf("unsupported snac. foodgroup: %s subgroup: %s",
 					wire.FoodGroupName(snac.Frame.FoodGroup),
@@ -94,6 +155,191 @@ func (s OSCARProxy) RecvChat(ctx context.Context, me *state.SessionInstance, cha
 	}
 }
 
+// RateParamsChange tells the client that it's being rate limited when one of
+// its rate classes becomes limited, since the OSCAR server drops rate-limited
+// requests without replying.
+func (s OSCARProxy) RateParamsChange(snac wire.SNAC_0x01_0x0A_OServiceRateParamsChange) []string {
+	if snac.Code == rateParamsChangeLimited {
+		return []string{rateLimitExceededErr}
+	}
+	return nil
+}
+
+// SNACError handles an error reply from the OSCAR server according to the
+// request that caused it.
+func (s OSCARProxy) SNACError(ctx context.Context, me *tocConn, frame wire.SNACFrame, snac wire.SNACError) []string {
+	req, ok := me.cache.take(frame)
+	if !ok {
+		s.Logger.DebugContext(ctx, "received SNAC error for unknown request",
+			"foodgroup", wire.FoodGroupName(frame.FoodGroup), "code", snac.Code)
+		return nil
+	}
+
+	switch data := req.data.(type) {
+	case getStatusReqCtx:
+		if snac.Code == wire.ErrorCodeNotLoggedOn {
+			return []string{fmt.Sprintf("ERROR:%s:%s", wire.TOCErrorGeneralUserNotAvailable, data.screenName)}
+		}
+		return s.runtimeErr(ctx, fmt.Errorf("LocateUserInfoQuery error code: %d", snac.Code))
+	case evilReqCtx:
+		s.Logger.InfoContext(ctx, "unable to warn user", "code", snac.Code)
+		return []string{"ERROR:" + wire.TOCErrorGeneralWarningUserNotAvailable}
+	case feedbagChangeReqCtx:
+		s.Logger.DebugContext(ctx, "feedbag change failed", "code", snac.Code)
+		me.feedbag.discard(frame.RequestID)
+		return nil
+	case chatJoinReqCtx, chatAcceptReqCtx, chatServiceReqCtx:
+		return s.runtimeErr(ctx, fmt.Errorf("unable to join chat room, %s error code: %d",
+			wire.SubGroupName(req.frame.FoodGroup, req.frame.SubGroup), snac.Code))
+	default:
+		s.Logger.DebugContext(ctx, "received SNAC error",
+			"request", wire.SubGroupName(req.frame.FoodGroup, req.frame.SubGroup), "code", snac.Code)
+		return nil
+	}
+}
+
+// FeedbagStatus confirms the items of a feedbag change that the server
+// accepted.
+func (s OSCARProxy) FeedbagStatus(me *tocConn, frame wire.SNACFrame, snac wire.SNAC_0x13_0x0E_FeedbagStatus) {
+	me.cache.take(frame)
+	me.feedbag.confirm(frame.RequestID, snac.Results)
+}
+
+// FeedbagReply finishes reloading the feedbag mirror.
+func (s OSCARProxy) FeedbagReply(me *tocConn, frame wire.SNACFrame, snac wire.SNAC_0x13_0x06_FeedbagReply) {
+	req, ok := me.cache.take(frame)
+	if !ok {
+		return
+	}
+	if data, ok := req.data.(feedbagReloadReqCtx); ok {
+		me.feedbag.reload(data.marker, snac.Items)
+	}
+}
+
+// UserInfoReply handles the user info reply to toc_get_status, which is sent
+// to the client as UPDATE_BUDDY.
+func (s OSCARProxy) UserInfoReply(me *tocConn, frame wire.SNACFrame, snac wire.SNAC_0x02_0x06_LocateUserInfoReply) []string {
+	me.cache.take(frame)
+	return []string{userInfoToUpdateBuddy(snac.TLVUserInfo, me)}
+}
+
+// AdminChangeReply handles the reply to toc_change_passwd, sent as
+// ADMIN_PASSWD_STATUS, or to toc_format_nickname, sent as ADMIN_NICK_STATUS
+// and NICK.
+func (s OSCARProxy) AdminChangeReply(ctx context.Context, me *tocConn, frame wire.SNACFrame, snac wire.SNAC_0x07_0x05_AdminChangeReply) []string {
+	req, ok := me.cache.take(frame)
+	if !ok {
+		s.Logger.DebugContext(ctx, "received admin change reply for unknown request")
+		return nil
+	}
+
+	code, hasErr := snac.Uint16BE(wire.AdminTLVErrorCode)
+
+	switch req.data.(type) {
+	case changePasswdReqCtx:
+		if hasErr {
+			switch code {
+			case wire.AdminInfoErrorInvalidPasswordLength:
+				return []string{"ERROR:" + wire.TOCErrorAdminInvalidInput}
+			case wire.AdminInfoErrorValidatePassword:
+				return []string{"ERROR:" + wire.TOCErrorAuthIncorrectNickOrPassword}
+			default:
+				return []string{"ERROR:" + wire.TOCErrorAdminProcessingRequest}
+			}
+		}
+		return []string{"ADMIN_PASSWD_STATUS:0"}
+	case formatNickReqCtx:
+		if hasErr {
+			switch code {
+			case wire.AdminInfoErrorInvalidNickNameLength, wire.AdminInfoErrorInvalidNickName:
+				return []string{"ERROR:" + wire.TOCErrorAdminInvalidInput}
+			default:
+				return []string{"ERROR:" + wire.TOCErrorAdminProcessingRequest}
+			}
+		}
+		val, hasVal := snac.String(wire.AdminTLVScreenNameFormatted)
+		if !hasVal {
+			return s.runtimeErr(ctx, fmt.Errorf("AdminChangeReply: missing AdminTLVScreenNameFormatted %v", snac))
+		}
+		return []string{"ADMIN_NICK_STATUS:0", "NICK:" + val}
+	default:
+		return s.runtimeErr(ctx, fmt.Errorf("AdminChangeReply: unexpected request %v", req.frame))
+	}
+}
+
+// ChatNavInfo continues toc_chat_join and toc_chat_accept once the chat room
+// info arrives, by requesting a connection to the room.
+func (s OSCARProxy) ChatNavInfo(ctx context.Context, me *tocConn, frame wire.SNACFrame, snac wire.SNAC_0x0D_0x09_ChatNavNavInfo) []string {
+	req, ok := me.cache.take(frame)
+	if !ok {
+		s.Logger.DebugContext(ctx, "received chat nav info for unknown request")
+		return nil
+	}
+
+	b, hasInfo := snac.Bytes(wire.ChatNavTLVRoomInfo)
+	if !hasInfo {
+		return s.runtimeErr(ctx, errors.New("ChatNavNavInfo: missing wire.ChatNavTLVRoomInfo"))
+	}
+
+	roomInfo := wire.SNAC_0x0E_0x02_ChatRoomInfoUpdate{}
+	if err := wire.UnmarshalBE(&roomInfo, bytes.NewReader(b)); err != nil {
+		return s.runtimeErr(ctx, fmt.Errorf("wire.UnmarshalBE: %w", err))
+	}
+	roomName, hasName := roomInfo.String(wire.ChatRoomTLVRoomName)
+	if !hasName {
+		return s.runtimeErr(ctx, errors.New("roomInfo.String: missing wire.ChatRoomTLVRoomName"))
+	}
+
+	switch data := req.data.(type) {
+	case chatJoinReqCtx:
+		chatID := me.chats.Add(wire.ICBMRoomInfo{
+			Exchange: roomInfo.Exchange,
+			Cookie:   roomInfo.Cookie,
+			Instance: roomInfo.InstanceNumber,
+		})
+		return s.requestChatService(ctx, me, chatID, roomName, roomInfo.Cookie)
+	case chatAcceptReqCtx:
+		return s.requestChatService(ctx, me, data.chatID, roomName, roomInfo.Cookie)
+	default:
+		return s.runtimeErr(ctx, fmt.Errorf("ChatNavNavInfo: unexpected request %v", req.frame))
+	}
+}
+
+// ChatServiceResponse finishes toc_chat_join and toc_chat_accept by
+// connecting to the chat room. It sends CHAT_JOIN before relaying anything
+// from the room, so that the client knows the room before its first
+// CHAT_UPDATE_BUDDY.
+func (s OSCARProxy) ChatServiceResponse(ctx context.Context, me *tocConn, frame wire.SNACFrame, snac wire.SNAC_0x01_0x05_OServiceServiceResponse) []string {
+	req, ok := me.cache.take(frame)
+	if !ok {
+		s.Logger.DebugContext(ctx, "received service response for unknown request")
+		return nil
+	}
+	data, ok := req.data.(chatServiceReqCtx)
+	if !ok {
+		return s.runtimeErr(ctx, fmt.Errorf("OServiceServiceResponse: unexpected request %v", req.frame))
+	}
+
+	loginCookie, hasCookie := snac.Bytes(wire.OServiceTLVTagsLoginCookie)
+	if !hasCookie {
+		return s.runtimeErr(ctx, errors.New("OServiceServiceResponse: missing wire.OServiceTLVTagsLoginCookie"))
+	}
+
+	conn, err := s.connectChat(ctx, me, loginCookie)
+	if err != nil {
+		return s.runtimeErr(ctx, fmt.Errorf("connectChat: %w", err))
+	}
+	me.chats.RegisterConn(data.chatID, conn)
+
+	sendOrCancel(ctx, me.toCh, []string{fmt.Sprintf("CHAT_JOIN:%d:%s", data.chatID, data.roomName)})
+
+	me.goAsync(func() error {
+		s.RecvChat(ctx, me, conn, data.chatID)
+		return nil
+	})
+	return nil
+}
+
 // ChatIn handles the CHAT_IN and ENC_CHAT_IN TOC commands.
 //
 // From the TiK documentation:
@@ -107,7 +353,7 @@ func (s OSCARProxy) RecvChat(ctx context.Context, me *state.SessionInstance, cha
 //
 // Command syntax: CHAT_IN:<Chat Room Id>:<Source User>:<Whisper? T/F>:<Message>
 // Command syntax: CHAT_IN_ENC:<chatroom id>:<user>:<whisper T/F>:<???>:en:<message>
-func (s OSCARProxy) ChatIn(ctx context.Context, me *state.SessionInstance, snac wire.SNAC_0x0E_0x06_ChatChannelMsgToClient, chatID int) []string {
+func (s OSCARProxy) ChatIn(ctx context.Context, me *tocConn, snac wire.SNAC_0x0E_0x06_ChatChannelMsgToClient, chatID int) []string {
 	b, ok := snac.Bytes(wire.ChatTLVSenderInformation)
 	if !ok {
 		return s.runtimeErr(ctx, errors.New("snac.Bytes: missing wire.ChatTLVSenderInformation"))
@@ -204,12 +450,12 @@ func (s OSCARProxy) Eviled(snac wire.SNAC_0x01_0x10_OServiceEvilNotification) []
 // Command syntax: IM_IN:<Source User>:<Auto Response T/F?>:<Message>
 // Command syntax: IM_IN2:<Source User>:<Auto Response T/F?>:<Whisper?>:<Message>
 // Command syntax: IM_IN_ENC2:<User>:<Auto>:<???>:<???>:<User Class>:<???>:<???>:<Language>:<Message>
-func (s OSCARProxy) IMIn(ctx context.Context, chatRegistry *ChatRegistry, me *state.SessionInstance, snac wire.SNAC_0x04_0x07_ICBMChannelMsgToClient) []string {
+func (s OSCARProxy) IMIn(ctx context.Context, me *tocConn, snac wire.SNAC_0x04_0x07_ICBMChannelMsgToClient) []string {
 	switch snac.ChannelID {
 	case wire.ICBMChannelIM:
 		return []string{s.convertICBMInstantMsg(ctx, me, snac)}
 	case wire.ICBMChannelRendezvous:
-		return []string{s.convertICBMRendezvous(ctx, chatRegistry, snac)}
+		return []string{s.convertICBMRendezvous(ctx, me.chats, snac)}
 	default:
 		s.Logger.DebugContext(ctx, "received unsupported ICBM channel message", "channel_id", snac.ChannelID)
 		return []string{}
@@ -217,7 +463,7 @@ func (s OSCARProxy) IMIn(ctx context.Context, chatRegistry *ChatRegistry, me *st
 }
 
 // convertICBMInstantMsg converts an ICBM instant message SNAC to a TOC IM_IN or TOC2 IM_IN2, or TOC2Enhanced IM_IN_ENC2 response.
-func (s OSCARProxy) convertICBMInstantMsg(ctx context.Context, me *state.SessionInstance, snac wire.SNAC_0x04_0x07_ICBMChannelMsgToClient) string {
+func (s OSCARProxy) convertICBMInstantMsg(ctx context.Context, me *tocConn, snac wire.SNAC_0x04_0x07_ICBMChannelMsgToClient) string {
 	buf, ok := snac.Bytes(wire.ICBMTLVAOLIMData)
 	if !ok {
 		return s.runtimeErr(ctx, errors.New("TLVRestBlock.Bytes: missing wire.ICBMTLVAOLIMData"))[0]
@@ -353,7 +599,7 @@ func (s OSCARProxy) convertICBMRendezvous(ctx context.Context, chatRegistry *Cha
 //
 // For TOC2 this sends UPDATE_BUDDY2 with the same fields (plus a trailing field). When
 // the buddy has capabilities, BUDDY_CAPS2 is also sent (see userInfoToBuddyCaps).
-func (s OSCARProxy) UpdateBuddyArrival(snac wire.SNAC_0x03_0x0B_BuddyArrived, me *state.SessionInstance) []string {
+func (s OSCARProxy) UpdateBuddyArrival(snac wire.SNAC_0x03_0x0B_BuddyArrived, me *tocConn) []string {
 	msgs := []string{userInfoToUpdateBuddy(snac.TLVUserInfo, me)}
 	if caps := userInfoToBuddyCaps(snac.TLVUserInfo, me, s.Logger); caps != "" {
 		msgs = append(msgs, caps)
@@ -383,7 +629,7 @@ func (s OSCARProxy) UpdateBuddyArrival(snac wire.SNAC_0x03_0x0B_BuddyArrived, me
 //
 // Command syntax: UPDATE_BUDDY:<Buddy User>:<Online? T/F>:<Evil Amount>:<Signon Time>:<IdleTime>:<UC>
 // TOC2 uses UPDATE_BUDDY2 with the same fields.
-func (s OSCARProxy) UpdateBuddyDeparted(snac wire.SNAC_0x03_0x0C_BuddyDeparted, me *state.SessionInstance) []string {
+func (s OSCARProxy) UpdateBuddyDeparted(snac wire.SNAC_0x03_0x0C_BuddyDeparted, me *tocConn) []string {
 	if me.IsTOC2() {
 		return []string{fmt.Sprintf("UPDATE_BUDDY2:%s:F:0:0:0:   :", snac.ScreenName)}
 	}
@@ -409,32 +655,13 @@ func (s OSCARProxy) UpdateBuddyDeparted(snac wire.SNAC_0x03_0x0C_BuddyDeparted,
 //	INSERTED2:p:<username>
 //	  Somebody has been added to the permit list.
 //
-// Inserted2 is invoked when this session receives FeedbagUpdateItem (e.g. list
-// modified from another client). The feedbag is queried when adding buddies to
-// resolve GroupID to group name; buddy alias comes from TLV.
-func (s OSCARProxy) Inserted2(ctx context.Context, me *state.SessionInstance, snac wire.SNAC_0x13_0x09_FeedbagUpdateItem) []string {
+// Inserted2 is invoked when this session receives FeedbagInsertItem or
+// FeedbagUpdateItem (e.g. list modified from another client). Group names come
+// from the feedbag mirror; buddy alias comes from TLV.
+func (s OSCARProxy) Inserted2(me *tocConn, items []wire.FeedbagItem) []string {
 	var out []string
-	groupNameByID := make(map[uint16]string)
-	hasBuddy := false
-	for _, item := range snac.Items {
-		if item.ClassID == wire.FeedbagClassIdBuddy {
-			hasBuddy = true
-			break
-		}
-	}
-	if hasBuddy {
-		fb, err := s.FeedbagManager.Feedbag(ctx, me.IdentScreenName())
-		if err != nil {
-			s.Logger.DebugContext(ctx, "Inserted2: feedbag lookup failed", "err", err)
-			return nil
-		}
-		for _, item := range fb {
-			if item.ClassID == wire.FeedbagClassIdGroup {
-				groupNameByID[item.GroupID] = item.Name
-			}
-		}
-	}
-	for _, item := range snac.Items {
+	groupNameByID := me.feedbag.groupNames()
+	for _, item := range items {
 		switch item.ClassID {
 		case wire.FeedbagClassIdGroup:
 			out = append(out, fmt.Sprintf("INSERTED2:g:%s", item.Name))
@@ -477,30 +704,11 @@ func (s OSCARProxy) Inserted2(ctx context.Context, me *state.SessionInstance, sn
 //	  A user has been removed from the permit list.
 //
 // Deleted2 is invoked when this session receives FeedbagDeleteItem (e.g. list
-// modified from another client). The feedbag is queried when deleting buddies to
-// resolve GroupID to group name.
-func (s OSCARProxy) Deleted2(ctx context.Context, me *state.SessionInstance, snac wire.SNAC_0x13_0x0A_FeedbagDeleteItem) []string {
+// modified from another client), before the items leave the feedbag mirror, so
+// that deleted buddies' group names can still be found there.
+func (s OSCARProxy) Deleted2(me *tocConn, snac wire.SNAC_0x13_0x0A_FeedbagDeleteItem) []string {
 	var out []string
-	groupNameByID := make(map[uint16]string)
-	hasBuddy := false
-	for _, item := range snac.Items {
-		if item.ClassID == wire.FeedbagClassIdBuddy {
-			hasBuddy = true
-			break
-		}
-	}
-	if hasBuddy {
-		fb, err := s.FeedbagManager.Feedbag(ctx, me.IdentScreenName())
-		if err != nil {
-			s.Logger.DebugContext(ctx, "Deleted2: feedbag lookup failed", "err", err)
-			return nil
-		}
-		for _, item := range fb {
-			if item.ClassID == wire.FeedbagClassIdGroup {
-				groupNameByID[item.GroupID] = item.Name
-			}
-		}
-	}
+	groupNameByID := me.feedbag.groupNames()
 	for _, item := range snac.Items {
 		switch item.ClassID {
 		case wire.FeedbagClassIdGroup:
@@ -574,7 +782,7 @@ func sendOrCancel(ctx context.Context, ch chan<- []string, msg []string) {
 
 // userInfoToUpdateBuddy creates an UPDATE_BUDDY or UPDATE_BUDDY2 server reply from a User
 // Info TLV.
-func userInfoToUpdateBuddy(snac wire.TLVUserInfo, me *state.SessionInstance) string {
+func userInfoToUpdateBuddy(snac wire.TLVUserInfo, me *tocConn) string {
 	online, _ := snac.Uint32BE(wire.OServiceUserInfoSignonTOD)
 	idle, _ := snac.Uint16BE(wire.OServiceUserInfoIdleTime)
 
@@ -603,7 +811,7 @@ func hasFlag[T ~uint16 | ~uint8](bitmask, flag T) bool {
 //	Direct Connect, you will get sent both packets.
 //
 // Format: BUDDY_CAPS2:<User>:<Cap1>,<Cap2>,...
-func userInfoToBuddyCaps(snac wire.TLVUserInfo, me *state.SessionInstance, logger *slog.Logger) string {
+func userInfoToBuddyCaps(snac wire.TLVUserInfo, me *tocConn, logger *slog.Logger) string {
 	if !me.IsTOC2() {
 		return ""
 	}

Разница между файлами не показана из-за своего большого размера
+ 416 - 121
server/toc/cmd_server_test.go


+ 303 - 239
server/toc/helpers_test.go

@@ -1,169 +1,23 @@
 package toc
 
 import (
+	"bytes"
 	"context"
+	"errors"
+	"io"
+	"net"
+	"sync"
+	"testing"
 	"time"
 
+	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/mock"
 
+	"github.com/mk6i/open-oscar-server/config"
 	"github.com/mk6i/open-oscar-server/state"
 	"github.com/mk6i/open-oscar-server/wire"
 )
 
-type adminParams struct {
-	infoChangeRequestParams
-}
-
-type infoChangeRequestParams []struct {
-	me     state.IdentScreenName
-	inBody wire.SNAC_0x07_0x04_AdminInfoChangeRequest
-	msg    wire.SNACMessage
-	err    error
-}
-
-type addBuddiesParams []struct {
-	me     state.IdentScreenName
-	inBody wire.SNAC_0x03_0x04_BuddyAddBuddies
-	err    error
-}
-
-type delBuddiesParams []struct {
-	me     state.IdentScreenName
-	inBody wire.SNAC_0x03_0x05_BuddyDelBuddies
-	err    error
-}
-
-type buddyParams struct {
-	addBuddiesParams
-	delBuddiesParams
-}
-
-type chatParams struct {
-	channelMsgToHostParamsChat
-}
-
-type createRoomParams []struct {
-	me     state.IdentScreenName
-	inBody wire.SNAC_0x0E_0x02_ChatRoomInfoUpdate
-	msg    wire.SNACMessage
-	err    error
-}
-
-type requestRoomInfoParams []struct {
-	inBody wire.SNAC_0x0D_0x04_ChatNavRequestRoomInfo
-	msg    wire.SNACMessage
-	err    error
-}
-
-type chatNavParams struct {
-	createRoomParams
-	requestRoomInfoParams
-}
-
-type channelMsgToHostParamsChat []struct {
-	sender state.IdentScreenName
-	inBody wire.SNAC_0x0E_0x05_ChatChannelMsgToHost
-	result *wire.SNACMessage
-	err    error
-}
-
-type channelMsgToHostParamsICBM []struct {
-	sender  state.IdentScreenName
-	inFrame wire.SNACFrame
-	inBody  wire.SNAC_0x04_0x06_ICBMChannelMsgToHost
-	result  *wire.SNACMessage
-	err     error
-}
-
-type clientEventParams []struct {
-	sender state.IdentScreenName
-	inBody wire.SNAC_0x04_0x14_ICBMClientEvent
-	err    error
-}
-
-type evilRequestParams []struct {
-	me     state.IdentScreenName
-	inBody wire.SNAC_0x04_0x08_ICBMEvilRequest
-	msg    wire.SNACMessage
-	err    error
-}
-
-type icbmParams struct {
-	channelMsgToHostParamsICBM
-	clientEventParams
-	evilRequestParams
-}
-
-type clientOnlineParams []struct {
-	body    wire.SNAC_0x01_0x02_OServiceClientOnline
-	me      state.IdentScreenName
-	service uint16
-	err     error
-}
-
-type idleNotificationParams []struct {
-	me     state.IdentScreenName
-	bodyIn wire.SNAC_0x01_0x11_OServiceIdleNotification
-	err    error
-}
-
-type serviceRequestParams []struct {
-	me     state.IdentScreenName
-	bodyIn wire.SNAC_0x01_0x04_OServiceServiceRequest
-	msg    wire.SNACMessage
-	err    error
-}
-
-type oServiceParams struct {
-	clientOnlineParams
-	idleNotificationParams
-	serviceRequestParams
-}
-
-type flapLoginParams []struct {
-	frame wire.FLAPSignonFrame
-	tlv   wire.TLVRestBlock
-	err   error
-}
-
-type registerBOSSessionParams []struct {
-	authCookie state.ServerCookie
-	instance   *state.SessionInstance
-	err        error
-}
-
-type registerChatSessionParams []struct {
-	authCookie state.ServerCookie
-	instance   *state.SessionInstance
-	err        error
-}
-
-type authParams struct {
-	crackCookieParams
-	flapLoginParams
-	registerBOSSessionParams
-	registerChatSessionParams
-}
-
-type crackCookieParams []struct {
-	cookieIn  []byte
-	cookieOut state.ServerCookie
-	err       error
-}
-
-type setDirInfoParams []struct {
-	me     state.IdentScreenName
-	inBody wire.SNAC_0x02_0x09_LocateSetDirInfo
-	msg    wire.SNACMessage
-	err    error
-}
-
-type setInfoParams []struct {
-	me     state.IdentScreenName
-	inBody wire.SNAC_0x02_0x04_LocateSetInfo
-	err    error
-}
-
 type userInfoQueryParams []struct {
 	me     state.IdentScreenName
 	inBody wire.SNAC_0x02_0x05_LocateUserInfoQuery
@@ -178,8 +32,6 @@ type dirInfoParams []struct {
 }
 
 type locateParams struct {
-	setDirInfoParams
-	setInfoParams
 	userInfoQueryParams
 	dirInfoParams
 }
@@ -194,44 +46,12 @@ type dirSearchParams struct {
 	infoQueryParams
 }
 
-type addDenyListEntriesParams []struct {
-	me   state.IdentScreenName
-	body wire.SNAC_0x09_0x07_PermitDenyAddDenyListEntries
-	err  error
-}
-
-type addPermListEntriesParams []struct {
-	me   state.IdentScreenName
-	body wire.SNAC_0x09_0x05_PermitDenyAddPermListEntries
-	err  error
-}
-
-type permitDenyParams struct {
-	addDenyListEntriesParams
-	addPermListEntriesParams
-}
-
-type registerBuddyListParams []struct {
-	user state.IdentScreenName
-	err  error
-}
-
-type buddyListRegistryParams struct {
-	registerBuddyListParams
-}
-
 type setTOCConfigParams []struct {
 	user   state.IdentScreenName
 	config string
 	err    error
 }
 
-type userParams []struct {
-	screenName   state.IdentScreenName
-	returnedUser *state.User
-	err          error
-}
-
 type retrieveSessionParams []struct {
 	screenName      state.IdentScreenName
 	returnedSession *state.Session
@@ -243,81 +63,41 @@ type sessionRetrieverParams struct {
 
 type tocConfigParams struct {
 	setTOCConfigParams
-	userParams
 }
 
 type feedBagParams struct {
-	useFeedbagParams
 	feedbagParams
-	feedbagServiceUseParams
-	feedbagServiceUpsertItemParams
-	feedbagServiceDeleteItemParams
+	upsertFeedbagParams
+	deleteFeedbagParams
 }
 
-// feedbagServiceUseParams is the list of parameters for each expected
-// FeedbagService.Use call (e.g. for TOC2 Signon).
-type feedbagServiceUseParams []struct {
-	err error
-}
-
-type useFeedbagParams []struct {
-	me  state.IdentScreenName
-	err error
-}
-
-// feedbagParams is the list of parameters passed at the mock
-// FeedbagManager.Feedbag call site (e.g. for NewBuddies).
+// feedbagParams holds the user's feedbag before a command runs.
 type feedbagParams []struct {
-	screenName state.IdentScreenName
-	results    []wire.FeedbagItem
-	err        error
+	results []wire.FeedbagItem
 }
 
-// feedbagServiceUpsertItemParams is the list of parameters for each expected
-// FeedbagService.UpsertItem call. Frame is the expected SNACFrame, items is the
-// exact slice of feedbag items (order of params = order of calls).
-type feedbagServiceUpsertItemParams []struct {
+// upsertFeedbagParams is the list of feedbag insert or update SNACs a command
+// is expected to send, in order. frame holds the expected subgroup, items the
+// exact feedbag items, and err the error the connection returns for the send.
+type upsertFeedbagParams []struct {
 	frame wire.SNACFrame
 	items []wire.FeedbagItem
-	msg   *wire.SNACMessage
 	err   error
 }
 
-// feedbagServiceDeleteItemParams is the list of parameters for each expected
-// FeedbagService.DeleteItem call.
-type feedbagServiceDeleteItemParams []struct {
+// deleteFeedbagParams is the list of feedbag delete SNACs a command is
+// expected to send, in order, before any upserts.
+type deleteFeedbagParams []struct {
 	frame  wire.SNACFrame
 	inBody wire.SNAC_0x13_0x0A_FeedbagDeleteItem
-	msg    *wire.SNACMessage
 	err    error
 }
 
-// removeUserFromAllChatsParams is the list of parameters for each expected
-// ChatSessionManager.RemoveUserFromAllChats call.
-type removeUserFromAllChatsParams []struct {
-	user state.IdentScreenName
-}
-
-// chatSessionManagerParams groups mock expectations for ChatSessionManager.
-type chatSessionManagerParams struct {
-	removeUserFromAllChatsParams
-}
-
 type mockParams struct {
-	adminParams
-	authParams
-	buddyListRegistryParams
-	buddyParams
-	chatNavParams
-	chatParams
-	chatSessionManagerParams
 	cookieBakerParams
 	dirSearchParams
-	icbmParams
 	locateParams
-	oServiceParams
 	feedBagParams
-	permitDenyParams
 	sessionRetrieverParams
 	tocConfigParams
 }
@@ -334,6 +114,126 @@ type cookieBakerParams struct {
 	issueParams issueParams
 }
 
+// fakeOSCARConn is an oscarConn that records the SNACs sent to it. The nth
+// send returns sendErrs[n], if set. Tests feed server SNACs through msgs.
+type fakeOSCARConn struct {
+	mu        sync.Mutex
+	sent      []wire.SNACMessage
+	sendErrs  []error
+	msgs      chan wire.SNACMessage
+	closed    chan struct{}
+	closeOnce sync.Once
+}
+
+func newFakeOSCARConn() *fakeOSCARConn {
+	return &fakeOSCARConn{
+		msgs:   make(chan wire.SNACMessage),
+		closed: make(chan struct{}),
+	}
+}
+
+func (c *fakeOSCARConn) SendSNAC(frame wire.SNACFrame, body any) error {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+	n := len(c.sent)
+	c.sent = append(c.sent, wire.SNACMessage{Frame: frame, Body: body})
+	if n < len(c.sendErrs) {
+		return c.sendErrs[n]
+	}
+	return nil
+}
+
+func (c *fakeOSCARConn) Messages() <-chan wire.SNACMessage {
+	return c.msgs
+}
+
+func (c *fakeOSCARConn) Closed() <-chan struct{} {
+	return c.closed
+}
+
+func (c *fakeOSCARConn) Close() {
+	c.closeOnce.Do(func() {
+		close(c.closed)
+	})
+}
+
+func (c *fakeOSCARConn) isClosed() bool {
+	select {
+	case <-c.closed:
+		return true
+	default:
+		return false
+	}
+}
+
+// sentSNACs returns a copy of the SNACs sent so far.
+func (c *fakeOSCARConn) sentSNACs() []wire.SNACMessage {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+	return append([]wire.SNACMessage(nil), c.sent...)
+}
+
+// newTestTOCConn creates a TOC connection for the user of instance whose BOS
+// connection is a fakeOSCARConn.
+func newTestTOCConn(instance *state.SessionInstance) (*tocConn, *fakeOSCARConn) {
+	bos := newFakeOSCARConn()
+	me := newTOCConn(bos, instance.IdentScreenName(), &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 1234})
+	me.toCh = make(chan []string, 16)
+	me.goAsync = func(f func() error) { go f() }
+	return me, bos
+}
+
+// newSNAC returns the SNAC message a TOC connection sends as its reqIDth
+// request.
+func newSNAC(foodGroup uint16, subGroup uint16, reqID uint32, body any) wire.SNACMessage {
+	return wire.SNACMessage{
+		Frame: wire.SNACFrame{
+			FoodGroup: foodGroup,
+			SubGroup:  subGroup,
+			RequestID: reqID,
+		},
+		Body: body,
+	}
+}
+
+// feedbagSNACs returns the feedbag SNACs that params expect a command to send.
+// A command sends at most one SNAC per subgroup, in the order deletes,
+// updates, inserts, so params' items are combined by subgroup in table order.
+// It also returns the error for each send. Sends stop at the first error.
+func feedbagSNACs(params feedBagParams) ([]wire.SNACMessage, []error) {
+	var deletes, updates, inserts []wire.FeedbagItem
+	var deleteErr, updateErr, insertErr error
+	for _, p := range params.deleteFeedbagParams {
+		deletes = append(deletes, p.inBody.Items...)
+		deleteErr = errors.Join(deleteErr, p.err)
+	}
+	for _, p := range params.upsertFeedbagParams {
+		if p.frame.SubGroup == wire.FeedbagUpdateItem {
+			updates = append(updates, p.items...)
+			updateErr = errors.Join(updateErr, p.err)
+		} else {
+			inserts = append(inserts, p.items...)
+			insertErr = errors.Join(insertErr, p.err)
+		}
+	}
+
+	var want []wire.SNACMessage
+	var errs []error
+	add := func(subGroup uint16, items []wire.FeedbagItem, body any, err error) bool {
+		if len(items) == 0 {
+			return true
+		}
+		want = append(want, newSNAC(wire.Feedbag, subGroup, uint32(len(want)+1), body))
+		errs = append(errs, err)
+		return err == nil
+	}
+	if add(wire.FeedbagDeleteItem, deletes, wire.SNAC_0x13_0x0A_FeedbagDeleteItem{Items: deletes}, deleteErr) &&
+		add(wire.FeedbagUpdateItem, updates, wire.SNAC_0x13_0x09_FeedbagUpdateItem{Items: updates}, updateErr) {
+		add(wire.FeedbagInsertItem, inserts, wire.SNAC_0x13_0x08_FeedbagInsertItem{Items: inserts}, insertErr)
+	}
+	return want, errs
+}
+
 // matchSession matches a mock call based session ident screen name.
 func matchSession(mustMatch state.IdentScreenName) interface{} {
 	return mock.MatchedBy(func(s *state.SessionInstance) bool {
@@ -408,3 +308,167 @@ func matchContext() interface{} {
 		return ok
 	})
 }
+
+// receivedSNAC is a SNAC that a fake OSCAR service received from TOC, with
+// its body still marshaled.
+type receivedSNAC struct {
+	frame wire.SNACFrame
+	body  []byte
+}
+
+// newReceivedSNAC returns the receivedSNAC for a SNAC TOC sends as its
+// reqIDth request.
+func newReceivedSNAC(t *testing.T, foodGroup uint16, subGroup uint16, reqID uint32, body any) receivedSNAC {
+	buf := &bytes.Buffer{}
+	assert.NoError(t, wire.MarshalBE(body, buf))
+	return receivedSNAC{
+		frame: wire.SNACFrame{FoodGroup: foodGroup, SubGroup: subGroup, RequestID: reqID},
+		body:  buf.Bytes(),
+	}
+}
+
+// fakeOSCARServer is an OSCARServer that serves each connection TOC opens
+// with the next of its handlers, in order.
+type fakeOSCARServer struct {
+	mu          sync.Mutex
+	handlers    []func(conn net.Conn)
+	clientAddrs []net.Addr
+}
+
+func (s *fakeOSCARServer) ServeConn(conn net.Conn, _ config.Endpoint) {
+	defer conn.Close()
+
+	s.mu.Lock()
+	s.clientAddrs = append(s.clientAddrs, conn.RemoteAddr())
+	if len(s.handlers) == 0 {
+		s.mu.Unlock()
+		return
+	}
+	handler := s.handlers[0]
+	s.handlers = s.handlers[1:]
+	s.mu.Unlock()
+
+	handler(conn)
+}
+
+// fakeAuth returns an auth service handler that records the client's signon
+// frame in got and replies with replyTLVs.
+func fakeAuth(got *wire.FLAPSignonFrame, replyTLVs ...wire.TLV) func(conn net.Conn) {
+	return func(conn net.Conn) {
+		flapc := wire.NewFlapClient(100, conn, conn)
+		if err := flapc.SendSignonFrame(nil); err != nil {
+			return
+		}
+		frame, err := flapc.ReceiveSignonFrame()
+		if err != nil {
+			return
+		}
+		*got = frame
+		_ = flapc.NewSignoff(wire.TLVRestBlock{TLVList: replyTLVs})
+	}
+}
+
+// fakeAuthBUCP returns an auth service handler that records the client's
+// signon frame in got and replies with a BUCP login response holding
+// replyTLVs, as the OSCAR server does when it rate limits an address that
+// recently used BUCP login.
+func fakeAuthBUCP(got *wire.FLAPSignonFrame, replyTLVs ...wire.TLV) func(conn net.Conn) {
+	return func(conn net.Conn) {
+		flapc := wire.NewFlapClient(100, conn, conn)
+		if err := flapc.SendSignonFrame(nil); err != nil {
+			return
+		}
+		frame, err := flapc.ReceiveSignonFrame()
+		if err != nil {
+			return
+		}
+		*got = frame
+		_ = flapc.SendSNAC(wire.SNACFrame{FoodGroup: wire.BUCP, SubGroup: wire.BUCPLoginResponse},
+			wire.SNAC_0x17_0x03_BUCPLoginResponse{TLVRestBlock: wire.TLVRestBlock{TLVList: replyTLVs}})
+	}
+}
+
+// fakeService is the server side of a BOS or chat connection opened by TOC. It
+// records the login cookie and the SNACs TOC sends, and answers each SNAC with
+// reply.
+type fakeService struct {
+	// refuse makes the service sign the client off instead of going online.
+	refuse bool
+	// greeting are SNACs sent right after the service goes online.
+	greeting []wire.SNACMessage
+	// reply returns the SNACs that answer a SNAC from TOC.
+	reply func(frame wire.SNACFrame) []wire.SNACMessage
+	// ended, if set, is closed once TOC closes the connection.
+	ended chan struct{}
+
+	mu     sync.Mutex
+	cookie []byte
+	snacs  []receivedSNAC
+}
+
+func (f *fakeService) handle(conn net.Conn) {
+	if f.ended != nil {
+		defer close(f.ended)
+	}
+	flapc := wire.NewFlapClient(100, conn, conn)
+	if err := flapc.SendSignonFrame(nil); err != nil {
+		return
+	}
+	signon, err := flapc.ReceiveSignonFrame()
+	if err != nil {
+		return
+	}
+	f.mu.Lock()
+	f.cookie, _ = signon.Bytes(wire.OServiceTLVTagsLoginCookie)
+	f.mu.Unlock()
+
+	if f.refuse {
+		_ = flapc.NewSignoff(wire.TLVRestBlock{})
+		return
+	}
+	if err := flapc.SendSNAC(wire.SNACFrame{FoodGroup: wire.OService, SubGroup: wire.OServiceHostOnline},
+		wire.SNAC_0x01_0x03_OServiceHostOnline{}); err != nil {
+		return
+	}
+	for _, msg := range f.greeting {
+		if err := flapc.SendSNAC(msg.Frame, msg.Body); err != nil {
+			return
+		}
+	}
+
+	for {
+		flap, err := flapc.ReceiveFLAP()
+		if err != nil || flap.FrameType != wire.FLAPFrameData {
+			return
+		}
+		r := bytes.NewReader(flap.Payload)
+		frame := wire.SNACFrame{}
+		if err := wire.UnmarshalBE(&frame, r); err != nil {
+			return
+		}
+		body, _ := io.ReadAll(r)
+		if len(body) == 0 {
+			body = nil
+		}
+
+		f.mu.Lock()
+		f.snacs = append(f.snacs, receivedSNAC{frame: frame, body: body})
+		f.mu.Unlock()
+
+		if f.reply == nil {
+			continue
+		}
+		for _, msg := range f.reply(frame) {
+			if err := flapc.SendSNAC(msg.Frame, msg.Body); err != nil {
+				return
+			}
+		}
+	}
+}
+
+// received returns the login cookie and the SNACs received so far.
+func (f *fakeService) received() ([]byte, []receivedSNAC) {
+	f.mu.Lock()
+	defer f.mu.Unlock()
+	return f.cookie, append([]receivedSNAC(nil), f.snacs...)
+}

+ 0 - 1
server/toc/http_test.go

@@ -684,7 +684,6 @@ func TestOSCARProxy_NewServeMux(t *testing.T) {
 				Logger:            slog.Default(),
 				SessionRetriever:  sessionRetriever,
 				HTTPIPRateLimiter: NewIPRateLimiter(rate.Every(1*time.Minute), 10, 1*time.Minute),
-				SNACRateLimits:    wire.DefaultSNACRateLimits(),
 			}
 
 			req, err := http.NewRequest(http.MethodGet, tc.path, nil)

+ 0 - 127
server/toc/mock_admin_service_test.go

@@ -1,127 +0,0 @@
-// Code generated by mockery; DO NOT EDIT.
-// github.com/vektra/mockery
-// template: testify
-
-package toc
-
-import (
-	"context"
-
-	"github.com/mk6i/open-oscar-server/state"
-	"github.com/mk6i/open-oscar-server/wire"
-	mock "github.com/stretchr/testify/mock"
-)
-
-// newMockAdminService creates a new instance of mockAdminService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
-// The first argument is typically a *testing.T value.
-func newMockAdminService(t interface {
-	mock.TestingT
-	Cleanup(func())
-}) *mockAdminService {
-	if helper, ok := t.(interface{ Helper() }); ok {
-		helper.Helper()
-	}
-
-	mock := &mockAdminService{}
-	mock.Mock.Test(t)
-
-	t.Cleanup(func() {
-		if helper, ok := t.(interface{ Helper() }); ok {
-			helper.Helper()
-		}
-		mock.AssertExpectations(t)
-	})
-
-	return mock
-}
-
-// mockAdminService is an autogenerated mock type for the AdminService type
-type mockAdminService struct {
-	mock.Mock
-}
-
-type mockAdminService_Expecter struct {
-	mock *mock.Mock
-}
-
-func (_m *mockAdminService) EXPECT() *mockAdminService_Expecter {
-	return &mockAdminService_Expecter{mock: &_m.Mock}
-}
-
-// InfoChangeRequest provides a mock function for the type mockAdminService
-func (_mock *mockAdminService) InfoChangeRequest(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x07_0x04_AdminInfoChangeRequest) (wire.SNACMessage, error) {
-	ret := _mock.Called(ctx, instance, inFrame, inBody)
-
-	if len(ret) == 0 {
-		panic("no return value specified for InfoChangeRequest")
-	}
-
-	var r0 wire.SNACMessage
-	var r1 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, *state.SessionInstance, wire.SNACFrame, wire.SNAC_0x07_0x04_AdminInfoChangeRequest) (wire.SNACMessage, error)); ok {
-		return returnFunc(ctx, instance, inFrame, inBody)
-	}
-	if returnFunc, ok := ret.Get(0).(func(context.Context, *state.SessionInstance, wire.SNACFrame, wire.SNAC_0x07_0x04_AdminInfoChangeRequest) wire.SNACMessage); ok {
-		r0 = returnFunc(ctx, instance, inFrame, inBody)
-	} else {
-		r0 = ret.Get(0).(wire.SNACMessage)
-	}
-	if returnFunc, ok := ret.Get(1).(func(context.Context, *state.SessionInstance, wire.SNACFrame, wire.SNAC_0x07_0x04_AdminInfoChangeRequest) error); ok {
-		r1 = returnFunc(ctx, instance, inFrame, inBody)
-	} else {
-		r1 = ret.Error(1)
-	}
-	return r0, r1
-}
-
-// mockAdminService_InfoChangeRequest_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'InfoChangeRequest'
-type mockAdminService_InfoChangeRequest_Call struct {
-	*mock.Call
-}
-
-// InfoChangeRequest is a helper method to define mock.On call
-//   - ctx context.Context
-//   - instance *state.SessionInstance
-//   - inFrame wire.SNACFrame
-//   - inBody wire.SNAC_0x07_0x04_AdminInfoChangeRequest
-func (_e *mockAdminService_Expecter) InfoChangeRequest(ctx any, instance any, inFrame any, inBody any) *mockAdminService_InfoChangeRequest_Call {
-	return &mockAdminService_InfoChangeRequest_Call{Call: _e.mock.On("InfoChangeRequest", ctx, instance, inFrame, inBody)}
-}
-
-func (_c *mockAdminService_InfoChangeRequest_Call) Run(run func(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x07_0x04_AdminInfoChangeRequest)) *mockAdminService_InfoChangeRequest_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 *state.SessionInstance
-		if args[1] != nil {
-			arg1 = args[1].(*state.SessionInstance)
-		}
-		var arg2 wire.SNACFrame
-		if args[2] != nil {
-			arg2 = args[2].(wire.SNACFrame)
-		}
-		var arg3 wire.SNAC_0x07_0x04_AdminInfoChangeRequest
-		if args[3] != nil {
-			arg3 = args[3].(wire.SNAC_0x07_0x04_AdminInfoChangeRequest)
-		}
-		run(
-			arg0,
-			arg1,
-			arg2,
-			arg3,
-		)
-	})
-	return _c
-}
-
-func (_c *mockAdminService_InfoChangeRequest_Call) Return(sNACMessage wire.SNACMessage, err error) *mockAdminService_InfoChangeRequest_Call {
-	_c.Call.Return(sNACMessage, err)
-	return _c
-}
-
-func (_c *mockAdminService_InfoChangeRequest_Call) RunAndReturn(run func(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x07_0x04_AdminInfoChangeRequest) (wire.SNACMessage, error)) *mockAdminService_InfoChangeRequest_Call {
-	_c.Call.Return(run)
-	return _c
-}

+ 0 - 642
server/toc/mock_auth_service_test.go

@@ -1,642 +0,0 @@
-// Code generated by mockery; DO NOT EDIT.
-// github.com/vektra/mockery
-// template: testify
-
-package toc
-
-import (
-	"context"
-	"time"
-
-	"github.com/google/uuid"
-	"github.com/mk6i/open-oscar-server/config"
-	"github.com/mk6i/open-oscar-server/state"
-	"github.com/mk6i/open-oscar-server/wire"
-	mock "github.com/stretchr/testify/mock"
-)
-
-// newMockAuthService creates a new instance of mockAuthService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
-// The first argument is typically a *testing.T value.
-func newMockAuthService(t interface {
-	mock.TestingT
-	Cleanup(func())
-}) *mockAuthService {
-	if helper, ok := t.(interface{ Helper() }); ok {
-		helper.Helper()
-	}
-
-	mock := &mockAuthService{}
-	mock.Mock.Test(t)
-
-	t.Cleanup(func() {
-		if helper, ok := t.(interface{ Helper() }); ok {
-			helper.Helper()
-		}
-		mock.AssertExpectations(t)
-	})
-
-	return mock
-}
-
-// mockAuthService is an autogenerated mock type for the AuthService type
-type mockAuthService struct {
-	mock.Mock
-}
-
-type mockAuthService_Expecter struct {
-	mock *mock.Mock
-}
-
-func (_m *mockAuthService) EXPECT() *mockAuthService_Expecter {
-	return &mockAuthService_Expecter{mock: &_m.Mock}
-}
-
-// BUCPChallenge provides a mock function for the type mockAuthService
-func (_mock *mockAuthService) BUCPChallenge(ctx context.Context, inBody wire.SNAC_0x17_0x06_BUCPChallengeRequest, newUUID func() uuid.UUID) (wire.SNACMessage, error) {
-	ret := _mock.Called(ctx, inBody, newUUID)
-
-	if len(ret) == 0 {
-		panic("no return value specified for BUCPChallenge")
-	}
-
-	var r0 wire.SNACMessage
-	var r1 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, wire.SNAC_0x17_0x06_BUCPChallengeRequest, func() uuid.UUID) (wire.SNACMessage, error)); ok {
-		return returnFunc(ctx, inBody, newUUID)
-	}
-	if returnFunc, ok := ret.Get(0).(func(context.Context, wire.SNAC_0x17_0x06_BUCPChallengeRequest, func() uuid.UUID) wire.SNACMessage); ok {
-		r0 = returnFunc(ctx, inBody, newUUID)
-	} else {
-		r0 = ret.Get(0).(wire.SNACMessage)
-	}
-	if returnFunc, ok := ret.Get(1).(func(context.Context, wire.SNAC_0x17_0x06_BUCPChallengeRequest, func() uuid.UUID) error); ok {
-		r1 = returnFunc(ctx, inBody, newUUID)
-	} else {
-		r1 = ret.Error(1)
-	}
-	return r0, r1
-}
-
-// mockAuthService_BUCPChallenge_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BUCPChallenge'
-type mockAuthService_BUCPChallenge_Call struct {
-	*mock.Call
-}
-
-// BUCPChallenge is a helper method to define mock.On call
-//   - ctx context.Context
-//   - inBody wire.SNAC_0x17_0x06_BUCPChallengeRequest
-//   - newUUID func() uuid.UUID
-func (_e *mockAuthService_Expecter) BUCPChallenge(ctx any, inBody any, newUUID any) *mockAuthService_BUCPChallenge_Call {
-	return &mockAuthService_BUCPChallenge_Call{Call: _e.mock.On("BUCPChallenge", ctx, inBody, newUUID)}
-}
-
-func (_c *mockAuthService_BUCPChallenge_Call) Run(run func(ctx context.Context, inBody wire.SNAC_0x17_0x06_BUCPChallengeRequest, newUUID func() uuid.UUID)) *mockAuthService_BUCPChallenge_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 wire.SNAC_0x17_0x06_BUCPChallengeRequest
-		if args[1] != nil {
-			arg1 = args[1].(wire.SNAC_0x17_0x06_BUCPChallengeRequest)
-		}
-		var arg2 func() uuid.UUID
-		if args[2] != nil {
-			arg2 = args[2].(func() uuid.UUID)
-		}
-		run(
-			arg0,
-			arg1,
-			arg2,
-		)
-	})
-	return _c
-}
-
-func (_c *mockAuthService_BUCPChallenge_Call) Return(sNACMessage wire.SNACMessage, err error) *mockAuthService_BUCPChallenge_Call {
-	_c.Call.Return(sNACMessage, err)
-	return _c
-}
-
-func (_c *mockAuthService_BUCPChallenge_Call) RunAndReturn(run func(ctx context.Context, inBody wire.SNAC_0x17_0x06_BUCPChallengeRequest, newUUID func() uuid.UUID) (wire.SNACMessage, error)) *mockAuthService_BUCPChallenge_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
-// BUCPLogin provides a mock function for the type mockAuthService
-func (_mock *mockAuthService) BUCPLogin(ctx context.Context, inBody wire.SNAC_0x17_0x02_BUCPLoginRequest, endpointCfg config.Endpoint) (wire.SNACMessage, error) {
-	ret := _mock.Called(ctx, inBody, endpointCfg)
-
-	if len(ret) == 0 {
-		panic("no return value specified for BUCPLogin")
-	}
-
-	var r0 wire.SNACMessage
-	var r1 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, wire.SNAC_0x17_0x02_BUCPLoginRequest, config.Endpoint) (wire.SNACMessage, error)); ok {
-		return returnFunc(ctx, inBody, endpointCfg)
-	}
-	if returnFunc, ok := ret.Get(0).(func(context.Context, wire.SNAC_0x17_0x02_BUCPLoginRequest, config.Endpoint) wire.SNACMessage); ok {
-		r0 = returnFunc(ctx, inBody, endpointCfg)
-	} else {
-		r0 = ret.Get(0).(wire.SNACMessage)
-	}
-	if returnFunc, ok := ret.Get(1).(func(context.Context, wire.SNAC_0x17_0x02_BUCPLoginRequest, config.Endpoint) error); ok {
-		r1 = returnFunc(ctx, inBody, endpointCfg)
-	} else {
-		r1 = ret.Error(1)
-	}
-	return r0, r1
-}
-
-// mockAuthService_BUCPLogin_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BUCPLogin'
-type mockAuthService_BUCPLogin_Call struct {
-	*mock.Call
-}
-
-// BUCPLogin is a helper method to define mock.On call
-//   - ctx context.Context
-//   - inBody wire.SNAC_0x17_0x02_BUCPLoginRequest
-//   - endpointCfg config.Endpoint
-func (_e *mockAuthService_Expecter) BUCPLogin(ctx any, inBody any, endpointCfg any) *mockAuthService_BUCPLogin_Call {
-	return &mockAuthService_BUCPLogin_Call{Call: _e.mock.On("BUCPLogin", ctx, inBody, endpointCfg)}
-}
-
-func (_c *mockAuthService_BUCPLogin_Call) Run(run func(ctx context.Context, inBody wire.SNAC_0x17_0x02_BUCPLoginRequest, endpointCfg config.Endpoint)) *mockAuthService_BUCPLogin_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 wire.SNAC_0x17_0x02_BUCPLoginRequest
-		if args[1] != nil {
-			arg1 = args[1].(wire.SNAC_0x17_0x02_BUCPLoginRequest)
-		}
-		var arg2 config.Endpoint
-		if args[2] != nil {
-			arg2 = args[2].(config.Endpoint)
-		}
-		run(
-			arg0,
-			arg1,
-			arg2,
-		)
-	})
-	return _c
-}
-
-func (_c *mockAuthService_BUCPLogin_Call) Return(sNACMessage wire.SNACMessage, err error) *mockAuthService_BUCPLogin_Call {
-	_c.Call.Return(sNACMessage, err)
-	return _c
-}
-
-func (_c *mockAuthService_BUCPLogin_Call) RunAndReturn(run func(ctx context.Context, inBody wire.SNAC_0x17_0x02_BUCPLoginRequest, endpointCfg config.Endpoint) (wire.SNACMessage, error)) *mockAuthService_BUCPLogin_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
-// CrackCookie provides a mock function for the type mockAuthService
-func (_mock *mockAuthService) CrackCookie(authCookie []byte) (state.ServerCookie, time.Time, error) {
-	ret := _mock.Called(authCookie)
-
-	if len(ret) == 0 {
-		panic("no return value specified for CrackCookie")
-	}
-
-	var r0 state.ServerCookie
-	var r1 time.Time
-	var r2 error
-	if returnFunc, ok := ret.Get(0).(func([]byte) (state.ServerCookie, time.Time, error)); ok {
-		return returnFunc(authCookie)
-	}
-	if returnFunc, ok := ret.Get(0).(func([]byte) state.ServerCookie); ok {
-		r0 = returnFunc(authCookie)
-	} else {
-		r0 = ret.Get(0).(state.ServerCookie)
-	}
-	if returnFunc, ok := ret.Get(1).(func([]byte) time.Time); ok {
-		r1 = returnFunc(authCookie)
-	} else {
-		r1 = ret.Get(1).(time.Time)
-	}
-	if returnFunc, ok := ret.Get(2).(func([]byte) error); ok {
-		r2 = returnFunc(authCookie)
-	} else {
-		r2 = ret.Error(2)
-	}
-	return r0, r1, r2
-}
-
-// mockAuthService_CrackCookie_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CrackCookie'
-type mockAuthService_CrackCookie_Call struct {
-	*mock.Call
-}
-
-// CrackCookie is a helper method to define mock.On call
-//   - authCookie []byte
-func (_e *mockAuthService_Expecter) CrackCookie(authCookie any) *mockAuthService_CrackCookie_Call {
-	return &mockAuthService_CrackCookie_Call{Call: _e.mock.On("CrackCookie", authCookie)}
-}
-
-func (_c *mockAuthService_CrackCookie_Call) Run(run func(authCookie []byte)) *mockAuthService_CrackCookie_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 []byte
-		if args[0] != nil {
-			arg0 = args[0].([]byte)
-		}
-		run(
-			arg0,
-		)
-	})
-	return _c
-}
-
-func (_c *mockAuthService_CrackCookie_Call) Return(serverCookie state.ServerCookie, time1 time.Time, err error) *mockAuthService_CrackCookie_Call {
-	_c.Call.Return(serverCookie, time1, err)
-	return _c
-}
-
-func (_c *mockAuthService_CrackCookie_Call) RunAndReturn(run func(authCookie []byte) (state.ServerCookie, time.Time, error)) *mockAuthService_CrackCookie_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
-// FLAPLogin provides a mock function for the type mockAuthService
-func (_mock *mockAuthService) FLAPLogin(ctx context.Context, inFrame wire.FLAPSignonFrame, endpointCfg config.Endpoint) (wire.TLVRestBlock, error) {
-	ret := _mock.Called(ctx, inFrame, endpointCfg)
-
-	if len(ret) == 0 {
-		panic("no return value specified for FLAPLogin")
-	}
-
-	var r0 wire.TLVRestBlock
-	var r1 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, wire.FLAPSignonFrame, config.Endpoint) (wire.TLVRestBlock, error)); ok {
-		return returnFunc(ctx, inFrame, endpointCfg)
-	}
-	if returnFunc, ok := ret.Get(0).(func(context.Context, wire.FLAPSignonFrame, config.Endpoint) wire.TLVRestBlock); ok {
-		r0 = returnFunc(ctx, inFrame, endpointCfg)
-	} else {
-		r0 = ret.Get(0).(wire.TLVRestBlock)
-	}
-	if returnFunc, ok := ret.Get(1).(func(context.Context, wire.FLAPSignonFrame, config.Endpoint) error); ok {
-		r1 = returnFunc(ctx, inFrame, endpointCfg)
-	} else {
-		r1 = ret.Error(1)
-	}
-	return r0, r1
-}
-
-// mockAuthService_FLAPLogin_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FLAPLogin'
-type mockAuthService_FLAPLogin_Call struct {
-	*mock.Call
-}
-
-// FLAPLogin is a helper method to define mock.On call
-//   - ctx context.Context
-//   - inFrame wire.FLAPSignonFrame
-//   - endpointCfg config.Endpoint
-func (_e *mockAuthService_Expecter) FLAPLogin(ctx any, inFrame any, endpointCfg any) *mockAuthService_FLAPLogin_Call {
-	return &mockAuthService_FLAPLogin_Call{Call: _e.mock.On("FLAPLogin", ctx, inFrame, endpointCfg)}
-}
-
-func (_c *mockAuthService_FLAPLogin_Call) Run(run func(ctx context.Context, inFrame wire.FLAPSignonFrame, endpointCfg config.Endpoint)) *mockAuthService_FLAPLogin_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 wire.FLAPSignonFrame
-		if args[1] != nil {
-			arg1 = args[1].(wire.FLAPSignonFrame)
-		}
-		var arg2 config.Endpoint
-		if args[2] != nil {
-			arg2 = args[2].(config.Endpoint)
-		}
-		run(
-			arg0,
-			arg1,
-			arg2,
-		)
-	})
-	return _c
-}
-
-func (_c *mockAuthService_FLAPLogin_Call) Return(tLVRestBlock wire.TLVRestBlock, err error) *mockAuthService_FLAPLogin_Call {
-	_c.Call.Return(tLVRestBlock, err)
-	return _c
-}
-
-func (_c *mockAuthService_FLAPLogin_Call) RunAndReturn(run func(ctx context.Context, inFrame wire.FLAPSignonFrame, endpointCfg config.Endpoint) (wire.TLVRestBlock, error)) *mockAuthService_FLAPLogin_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
-// RegisterBOSSession provides a mock function for the type mockAuthService
-func (_mock *mockAuthService) RegisterBOSSession(ctx context.Context, authCookie state.ServerCookie, cfg func(*state.Session)) (*state.SessionInstance, error) {
-	ret := _mock.Called(ctx, authCookie, cfg)
-
-	if len(ret) == 0 {
-		panic("no return value specified for RegisterBOSSession")
-	}
-
-	var r0 *state.SessionInstance
-	var r1 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, state.ServerCookie, func(*state.Session)) (*state.SessionInstance, error)); ok {
-		return returnFunc(ctx, authCookie, cfg)
-	}
-	if returnFunc, ok := ret.Get(0).(func(context.Context, state.ServerCookie, func(*state.Session)) *state.SessionInstance); ok {
-		r0 = returnFunc(ctx, authCookie, cfg)
-	} else {
-		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(*state.SessionInstance)
-		}
-	}
-	if returnFunc, ok := ret.Get(1).(func(context.Context, state.ServerCookie, func(*state.Session)) error); ok {
-		r1 = returnFunc(ctx, authCookie, cfg)
-	} else {
-		r1 = ret.Error(1)
-	}
-	return r0, r1
-}
-
-// mockAuthService_RegisterBOSSession_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RegisterBOSSession'
-type mockAuthService_RegisterBOSSession_Call struct {
-	*mock.Call
-}
-
-// RegisterBOSSession is a helper method to define mock.On call
-//   - ctx context.Context
-//   - authCookie state.ServerCookie
-//   - cfg func(*state.Session)
-func (_e *mockAuthService_Expecter) RegisterBOSSession(ctx any, authCookie any, cfg any) *mockAuthService_RegisterBOSSession_Call {
-	return &mockAuthService_RegisterBOSSession_Call{Call: _e.mock.On("RegisterBOSSession", ctx, authCookie, cfg)}
-}
-
-func (_c *mockAuthService_RegisterBOSSession_Call) Run(run func(ctx context.Context, authCookie state.ServerCookie, cfg func(*state.Session))) *mockAuthService_RegisterBOSSession_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 state.ServerCookie
-		if args[1] != nil {
-			arg1 = args[1].(state.ServerCookie)
-		}
-		var arg2 func(*state.Session)
-		if args[2] != nil {
-			arg2 = args[2].(func(*state.Session))
-		}
-		run(
-			arg0,
-			arg1,
-			arg2,
-		)
-	})
-	return _c
-}
-
-func (_c *mockAuthService_RegisterBOSSession_Call) Return(sessionInstance *state.SessionInstance, err error) *mockAuthService_RegisterBOSSession_Call {
-	_c.Call.Return(sessionInstance, err)
-	return _c
-}
-
-func (_c *mockAuthService_RegisterBOSSession_Call) RunAndReturn(run func(ctx context.Context, authCookie state.ServerCookie, cfg func(*state.Session)) (*state.SessionInstance, error)) *mockAuthService_RegisterBOSSession_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
-// RegisterChatSession provides a mock function for the type mockAuthService
-func (_mock *mockAuthService) RegisterChatSession(ctx context.Context, authCookie state.ServerCookie, cfg func(sess *state.Session)) (*state.SessionInstance, error) {
-	ret := _mock.Called(ctx, authCookie, cfg)
-
-	if len(ret) == 0 {
-		panic("no return value specified for RegisterChatSession")
-	}
-
-	var r0 *state.SessionInstance
-	var r1 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, state.ServerCookie, func(sess *state.Session)) (*state.SessionInstance, error)); ok {
-		return returnFunc(ctx, authCookie, cfg)
-	}
-	if returnFunc, ok := ret.Get(0).(func(context.Context, state.ServerCookie, func(sess *state.Session)) *state.SessionInstance); ok {
-		r0 = returnFunc(ctx, authCookie, cfg)
-	} else {
-		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(*state.SessionInstance)
-		}
-	}
-	if returnFunc, ok := ret.Get(1).(func(context.Context, state.ServerCookie, func(sess *state.Session)) error); ok {
-		r1 = returnFunc(ctx, authCookie, cfg)
-	} else {
-		r1 = ret.Error(1)
-	}
-	return r0, r1
-}
-
-// mockAuthService_RegisterChatSession_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RegisterChatSession'
-type mockAuthService_RegisterChatSession_Call struct {
-	*mock.Call
-}
-
-// RegisterChatSession is a helper method to define mock.On call
-//   - ctx context.Context
-//   - authCookie state.ServerCookie
-//   - cfg func(sess *state.Session)
-func (_e *mockAuthService_Expecter) RegisterChatSession(ctx any, authCookie any, cfg any) *mockAuthService_RegisterChatSession_Call {
-	return &mockAuthService_RegisterChatSession_Call{Call: _e.mock.On("RegisterChatSession", ctx, authCookie, cfg)}
-}
-
-func (_c *mockAuthService_RegisterChatSession_Call) Run(run func(ctx context.Context, authCookie state.ServerCookie, cfg func(sess *state.Session))) *mockAuthService_RegisterChatSession_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 state.ServerCookie
-		if args[1] != nil {
-			arg1 = args[1].(state.ServerCookie)
-		}
-		var arg2 func(sess *state.Session)
-		if args[2] != nil {
-			arg2 = args[2].(func(sess *state.Session))
-		}
-		run(
-			arg0,
-			arg1,
-			arg2,
-		)
-	})
-	return _c
-}
-
-func (_c *mockAuthService_RegisterChatSession_Call) Return(sessionInstance *state.SessionInstance, err error) *mockAuthService_RegisterChatSession_Call {
-	_c.Call.Return(sessionInstance, err)
-	return _c
-}
-
-func (_c *mockAuthService_RegisterChatSession_Call) RunAndReturn(run func(ctx context.Context, authCookie state.ServerCookie, cfg func(sess *state.Session)) (*state.SessionInstance, error)) *mockAuthService_RegisterChatSession_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
-// RetrieveBOSSession provides a mock function for the type mockAuthService
-func (_mock *mockAuthService) RetrieveBOSSession(ctx context.Context, authCookie state.ServerCookie) (*state.SessionInstance, error) {
-	ret := _mock.Called(ctx, authCookie)
-
-	if len(ret) == 0 {
-		panic("no return value specified for RetrieveBOSSession")
-	}
-
-	var r0 *state.SessionInstance
-	var r1 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, state.ServerCookie) (*state.SessionInstance, error)); ok {
-		return returnFunc(ctx, authCookie)
-	}
-	if returnFunc, ok := ret.Get(0).(func(context.Context, state.ServerCookie) *state.SessionInstance); ok {
-		r0 = returnFunc(ctx, authCookie)
-	} else {
-		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(*state.SessionInstance)
-		}
-	}
-	if returnFunc, ok := ret.Get(1).(func(context.Context, state.ServerCookie) error); ok {
-		r1 = returnFunc(ctx, authCookie)
-	} else {
-		r1 = ret.Error(1)
-	}
-	return r0, r1
-}
-
-// mockAuthService_RetrieveBOSSession_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RetrieveBOSSession'
-type mockAuthService_RetrieveBOSSession_Call struct {
-	*mock.Call
-}
-
-// RetrieveBOSSession is a helper method to define mock.On call
-//   - ctx context.Context
-//   - authCookie state.ServerCookie
-func (_e *mockAuthService_Expecter) RetrieveBOSSession(ctx any, authCookie any) *mockAuthService_RetrieveBOSSession_Call {
-	return &mockAuthService_RetrieveBOSSession_Call{Call: _e.mock.On("RetrieveBOSSession", ctx, authCookie)}
-}
-
-func (_c *mockAuthService_RetrieveBOSSession_Call) Run(run func(ctx context.Context, authCookie state.ServerCookie)) *mockAuthService_RetrieveBOSSession_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 state.ServerCookie
-		if args[1] != nil {
-			arg1 = args[1].(state.ServerCookie)
-		}
-		run(
-			arg0,
-			arg1,
-		)
-	})
-	return _c
-}
-
-func (_c *mockAuthService_RetrieveBOSSession_Call) Return(sessionInstance *state.SessionInstance, err error) *mockAuthService_RetrieveBOSSession_Call {
-	_c.Call.Return(sessionInstance, err)
-	return _c
-}
-
-func (_c *mockAuthService_RetrieveBOSSession_Call) RunAndReturn(run func(ctx context.Context, authCookie state.ServerCookie) (*state.SessionInstance, error)) *mockAuthService_RetrieveBOSSession_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
-// Signout provides a mock function for the type mockAuthService
-func (_mock *mockAuthService) Signout(ctx context.Context, session *state.Session) {
-	_mock.Called(ctx, session)
-	return
-}
-
-// mockAuthService_Signout_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Signout'
-type mockAuthService_Signout_Call struct {
-	*mock.Call
-}
-
-// Signout is a helper method to define mock.On call
-//   - ctx context.Context
-//   - session *state.Session
-func (_e *mockAuthService_Expecter) Signout(ctx any, session any) *mockAuthService_Signout_Call {
-	return &mockAuthService_Signout_Call{Call: _e.mock.On("Signout", ctx, session)}
-}
-
-func (_c *mockAuthService_Signout_Call) Run(run func(ctx context.Context, session *state.Session)) *mockAuthService_Signout_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 *state.Session
-		if args[1] != nil {
-			arg1 = args[1].(*state.Session)
-		}
-		run(
-			arg0,
-			arg1,
-		)
-	})
-	return _c
-}
-
-func (_c *mockAuthService_Signout_Call) Return() *mockAuthService_Signout_Call {
-	_c.Call.Return()
-	return _c
-}
-
-func (_c *mockAuthService_Signout_Call) RunAndReturn(run func(ctx context.Context, session *state.Session)) *mockAuthService_Signout_Call {
-	_c.Run(run)
-	return _c
-}
-
-// SignoutChat provides a mock function for the type mockAuthService
-func (_mock *mockAuthService) SignoutChat(ctx context.Context, sess *state.Session) {
-	_mock.Called(ctx, sess)
-	return
-}
-
-// mockAuthService_SignoutChat_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SignoutChat'
-type mockAuthService_SignoutChat_Call struct {
-	*mock.Call
-}
-
-// SignoutChat is a helper method to define mock.On call
-//   - ctx context.Context
-//   - sess *state.Session
-func (_e *mockAuthService_Expecter) SignoutChat(ctx any, sess any) *mockAuthService_SignoutChat_Call {
-	return &mockAuthService_SignoutChat_Call{Call: _e.mock.On("SignoutChat", ctx, sess)}
-}
-
-func (_c *mockAuthService_SignoutChat_Call) Run(run func(ctx context.Context, sess *state.Session)) *mockAuthService_SignoutChat_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 *state.Session
-		if args[1] != nil {
-			arg1 = args[1].(*state.Session)
-		}
-		run(
-			arg0,
-			arg1,
-		)
-	})
-	return _c
-}
-
-func (_c *mockAuthService_SignoutChat_Call) Return() *mockAuthService_SignoutChat_Call {
-	_c.Call.Return()
-	return _c
-}
-
-func (_c *mockAuthService_SignoutChat_Call) RunAndReturn(run func(ctx context.Context, sess *state.Session)) *mockAuthService_SignoutChat_Call {
-	_c.Run(run)
-	return _c
-}

+ 0 - 162
server/toc/mock_buddy_list_registry_test.go

@@ -1,162 +0,0 @@
-// Code generated by mockery; DO NOT EDIT.
-// github.com/vektra/mockery
-// template: testify
-
-package toc
-
-import (
-	"context"
-
-	"github.com/mk6i/open-oscar-server/state"
-	mock "github.com/stretchr/testify/mock"
-)
-
-// newMockBuddyListRegistry creates a new instance of mockBuddyListRegistry. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
-// The first argument is typically a *testing.T value.
-func newMockBuddyListRegistry(t interface {
-	mock.TestingT
-	Cleanup(func())
-}) *mockBuddyListRegistry {
-	if helper, ok := t.(interface{ Helper() }); ok {
-		helper.Helper()
-	}
-
-	mock := &mockBuddyListRegistry{}
-	mock.Mock.Test(t)
-
-	t.Cleanup(func() {
-		if helper, ok := t.(interface{ Helper() }); ok {
-			helper.Helper()
-		}
-		mock.AssertExpectations(t)
-	})
-
-	return mock
-}
-
-// mockBuddyListRegistry is an autogenerated mock type for the BuddyListRegistry type
-type mockBuddyListRegistry struct {
-	mock.Mock
-}
-
-type mockBuddyListRegistry_Expecter struct {
-	mock *mock.Mock
-}
-
-func (_m *mockBuddyListRegistry) EXPECT() *mockBuddyListRegistry_Expecter {
-	return &mockBuddyListRegistry_Expecter{mock: &_m.Mock}
-}
-
-// RegisterBuddyList provides a mock function for the type mockBuddyListRegistry
-func (_mock *mockBuddyListRegistry) RegisterBuddyList(ctx context.Context, user state.IdentScreenName) error {
-	ret := _mock.Called(ctx, user)
-
-	if len(ret) == 0 {
-		panic("no return value specified for RegisterBuddyList")
-	}
-
-	var r0 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, state.IdentScreenName) error); ok {
-		r0 = returnFunc(ctx, user)
-	} else {
-		r0 = ret.Error(0)
-	}
-	return r0
-}
-
-// mockBuddyListRegistry_RegisterBuddyList_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RegisterBuddyList'
-type mockBuddyListRegistry_RegisterBuddyList_Call struct {
-	*mock.Call
-}
-
-// RegisterBuddyList is a helper method to define mock.On call
-//   - ctx context.Context
-//   - user state.IdentScreenName
-func (_e *mockBuddyListRegistry_Expecter) RegisterBuddyList(ctx any, user any) *mockBuddyListRegistry_RegisterBuddyList_Call {
-	return &mockBuddyListRegistry_RegisterBuddyList_Call{Call: _e.mock.On("RegisterBuddyList", ctx, user)}
-}
-
-func (_c *mockBuddyListRegistry_RegisterBuddyList_Call) Run(run func(ctx context.Context, user state.IdentScreenName)) *mockBuddyListRegistry_RegisterBuddyList_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 state.IdentScreenName
-		if args[1] != nil {
-			arg1 = args[1].(state.IdentScreenName)
-		}
-		run(
-			arg0,
-			arg1,
-		)
-	})
-	return _c
-}
-
-func (_c *mockBuddyListRegistry_RegisterBuddyList_Call) Return(err error) *mockBuddyListRegistry_RegisterBuddyList_Call {
-	_c.Call.Return(err)
-	return _c
-}
-
-func (_c *mockBuddyListRegistry_RegisterBuddyList_Call) RunAndReturn(run func(ctx context.Context, user state.IdentScreenName) error) *mockBuddyListRegistry_RegisterBuddyList_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
-// UnregisterBuddyList provides a mock function for the type mockBuddyListRegistry
-func (_mock *mockBuddyListRegistry) UnregisterBuddyList(ctx context.Context, user state.IdentScreenName) error {
-	ret := _mock.Called(ctx, user)
-
-	if len(ret) == 0 {
-		panic("no return value specified for UnregisterBuddyList")
-	}
-
-	var r0 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, state.IdentScreenName) error); ok {
-		r0 = returnFunc(ctx, user)
-	} else {
-		r0 = ret.Error(0)
-	}
-	return r0
-}
-
-// mockBuddyListRegistry_UnregisterBuddyList_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UnregisterBuddyList'
-type mockBuddyListRegistry_UnregisterBuddyList_Call struct {
-	*mock.Call
-}
-
-// UnregisterBuddyList is a helper method to define mock.On call
-//   - ctx context.Context
-//   - user state.IdentScreenName
-func (_e *mockBuddyListRegistry_Expecter) UnregisterBuddyList(ctx any, user any) *mockBuddyListRegistry_UnregisterBuddyList_Call {
-	return &mockBuddyListRegistry_UnregisterBuddyList_Call{Call: _e.mock.On("UnregisterBuddyList", ctx, user)}
-}
-
-func (_c *mockBuddyListRegistry_UnregisterBuddyList_Call) Run(run func(ctx context.Context, user state.IdentScreenName)) *mockBuddyListRegistry_UnregisterBuddyList_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 state.IdentScreenName
-		if args[1] != nil {
-			arg1 = args[1].(state.IdentScreenName)
-		}
-		run(
-			arg0,
-			arg1,
-		)
-	})
-	return _c
-}
-
-func (_c *mockBuddyListRegistry_UnregisterBuddyList_Call) Return(err error) *mockBuddyListRegistry_UnregisterBuddyList_Call {
-	_c.Call.Return(err)
-	return _c
-}
-
-func (_c *mockBuddyListRegistry_UnregisterBuddyList_Call) RunAndReturn(run func(ctx context.Context, user state.IdentScreenName) error) *mockBuddyListRegistry_UnregisterBuddyList_Call {
-	_c.Call.Return(run)
-	return _c
-}

+ 0 - 369
server/toc/mock_buddy_service_test.go

@@ -1,369 +0,0 @@
-// Code generated by mockery; DO NOT EDIT.
-// github.com/vektra/mockery
-// template: testify
-
-package toc
-
-import (
-	"context"
-
-	"github.com/mk6i/open-oscar-server/state"
-	"github.com/mk6i/open-oscar-server/wire"
-	mock "github.com/stretchr/testify/mock"
-)
-
-// newMockBuddyService creates a new instance of mockBuddyService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
-// The first argument is typically a *testing.T value.
-func newMockBuddyService(t interface {
-	mock.TestingT
-	Cleanup(func())
-}) *mockBuddyService {
-	if helper, ok := t.(interface{ Helper() }); ok {
-		helper.Helper()
-	}
-
-	mock := &mockBuddyService{}
-	mock.Mock.Test(t)
-
-	t.Cleanup(func() {
-		if helper, ok := t.(interface{ Helper() }); ok {
-			helper.Helper()
-		}
-		mock.AssertExpectations(t)
-	})
-
-	return mock
-}
-
-// mockBuddyService is an autogenerated mock type for the BuddyService type
-type mockBuddyService struct {
-	mock.Mock
-}
-
-type mockBuddyService_Expecter struct {
-	mock *mock.Mock
-}
-
-func (_m *mockBuddyService) EXPECT() *mockBuddyService_Expecter {
-	return &mockBuddyService_Expecter{mock: &_m.Mock}
-}
-
-// AddBuddies provides a mock function for the type mockBuddyService
-func (_mock *mockBuddyService) AddBuddies(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x03_0x04_BuddyAddBuddies) (*wire.SNACMessage, error) {
-	ret := _mock.Called(ctx, instance, inFrame, inBody)
-
-	if len(ret) == 0 {
-		panic("no return value specified for AddBuddies")
-	}
-
-	var r0 *wire.SNACMessage
-	var r1 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, *state.SessionInstance, wire.SNACFrame, wire.SNAC_0x03_0x04_BuddyAddBuddies) (*wire.SNACMessage, error)); ok {
-		return returnFunc(ctx, instance, inFrame, inBody)
-	}
-	if returnFunc, ok := ret.Get(0).(func(context.Context, *state.SessionInstance, wire.SNACFrame, wire.SNAC_0x03_0x04_BuddyAddBuddies) *wire.SNACMessage); ok {
-		r0 = returnFunc(ctx, instance, inFrame, inBody)
-	} else {
-		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(*wire.SNACMessage)
-		}
-	}
-	if returnFunc, ok := ret.Get(1).(func(context.Context, *state.SessionInstance, wire.SNACFrame, wire.SNAC_0x03_0x04_BuddyAddBuddies) error); ok {
-		r1 = returnFunc(ctx, instance, inFrame, inBody)
-	} else {
-		r1 = ret.Error(1)
-	}
-	return r0, r1
-}
-
-// mockBuddyService_AddBuddies_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddBuddies'
-type mockBuddyService_AddBuddies_Call struct {
-	*mock.Call
-}
-
-// AddBuddies is a helper method to define mock.On call
-//   - ctx context.Context
-//   - instance *state.SessionInstance
-//   - inFrame wire.SNACFrame
-//   - inBody wire.SNAC_0x03_0x04_BuddyAddBuddies
-func (_e *mockBuddyService_Expecter) AddBuddies(ctx any, instance any, inFrame any, inBody any) *mockBuddyService_AddBuddies_Call {
-	return &mockBuddyService_AddBuddies_Call{Call: _e.mock.On("AddBuddies", ctx, instance, inFrame, inBody)}
-}
-
-func (_c *mockBuddyService_AddBuddies_Call) Run(run func(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x03_0x04_BuddyAddBuddies)) *mockBuddyService_AddBuddies_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 *state.SessionInstance
-		if args[1] != nil {
-			arg1 = args[1].(*state.SessionInstance)
-		}
-		var arg2 wire.SNACFrame
-		if args[2] != nil {
-			arg2 = args[2].(wire.SNACFrame)
-		}
-		var arg3 wire.SNAC_0x03_0x04_BuddyAddBuddies
-		if args[3] != nil {
-			arg3 = args[3].(wire.SNAC_0x03_0x04_BuddyAddBuddies)
-		}
-		run(
-			arg0,
-			arg1,
-			arg2,
-			arg3,
-		)
-	})
-	return _c
-}
-
-func (_c *mockBuddyService_AddBuddies_Call) Return(sNACMessage *wire.SNACMessage, err error) *mockBuddyService_AddBuddies_Call {
-	_c.Call.Return(sNACMessage, err)
-	return _c
-}
-
-func (_c *mockBuddyService_AddBuddies_Call) RunAndReturn(run func(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x03_0x04_BuddyAddBuddies) (*wire.SNACMessage, error)) *mockBuddyService_AddBuddies_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
-// BroadcastBuddyArrived provides a mock function for the type mockBuddyService
-func (_mock *mockBuddyService) BroadcastBuddyArrived(ctx context.Context, screenName state.IdentScreenName, userInfo wire.TLVUserInfo) error {
-	ret := _mock.Called(ctx, screenName, userInfo)
-
-	if len(ret) == 0 {
-		panic("no return value specified for BroadcastBuddyArrived")
-	}
-
-	var r0 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, state.IdentScreenName, wire.TLVUserInfo) error); ok {
-		r0 = returnFunc(ctx, screenName, userInfo)
-	} else {
-		r0 = ret.Error(0)
-	}
-	return r0
-}
-
-// mockBuddyService_BroadcastBuddyArrived_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BroadcastBuddyArrived'
-type mockBuddyService_BroadcastBuddyArrived_Call struct {
-	*mock.Call
-}
-
-// BroadcastBuddyArrived is a helper method to define mock.On call
-//   - ctx context.Context
-//   - screenName state.IdentScreenName
-//   - userInfo wire.TLVUserInfo
-func (_e *mockBuddyService_Expecter) BroadcastBuddyArrived(ctx any, screenName any, userInfo any) *mockBuddyService_BroadcastBuddyArrived_Call {
-	return &mockBuddyService_BroadcastBuddyArrived_Call{Call: _e.mock.On("BroadcastBuddyArrived", ctx, screenName, userInfo)}
-}
-
-func (_c *mockBuddyService_BroadcastBuddyArrived_Call) Run(run func(ctx context.Context, screenName state.IdentScreenName, userInfo wire.TLVUserInfo)) *mockBuddyService_BroadcastBuddyArrived_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 state.IdentScreenName
-		if args[1] != nil {
-			arg1 = args[1].(state.IdentScreenName)
-		}
-		var arg2 wire.TLVUserInfo
-		if args[2] != nil {
-			arg2 = args[2].(wire.TLVUserInfo)
-		}
-		run(
-			arg0,
-			arg1,
-			arg2,
-		)
-	})
-	return _c
-}
-
-func (_c *mockBuddyService_BroadcastBuddyArrived_Call) Return(err error) *mockBuddyService_BroadcastBuddyArrived_Call {
-	_c.Call.Return(err)
-	return _c
-}
-
-func (_c *mockBuddyService_BroadcastBuddyArrived_Call) RunAndReturn(run func(ctx context.Context, screenName state.IdentScreenName, userInfo wire.TLVUserInfo) error) *mockBuddyService_BroadcastBuddyArrived_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
-// BroadcastBuddyDeparted provides a mock function for the type mockBuddyService
-func (_mock *mockBuddyService) BroadcastBuddyDeparted(ctx context.Context, screenName state.IdentScreenName) error {
-	ret := _mock.Called(ctx, screenName)
-
-	if len(ret) == 0 {
-		panic("no return value specified for BroadcastBuddyDeparted")
-	}
-
-	var r0 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, state.IdentScreenName) error); ok {
-		r0 = returnFunc(ctx, screenName)
-	} else {
-		r0 = ret.Error(0)
-	}
-	return r0
-}
-
-// mockBuddyService_BroadcastBuddyDeparted_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BroadcastBuddyDeparted'
-type mockBuddyService_BroadcastBuddyDeparted_Call struct {
-	*mock.Call
-}
-
-// BroadcastBuddyDeparted is a helper method to define mock.On call
-//   - ctx context.Context
-//   - screenName state.IdentScreenName
-func (_e *mockBuddyService_Expecter) BroadcastBuddyDeparted(ctx any, screenName any) *mockBuddyService_BroadcastBuddyDeparted_Call {
-	return &mockBuddyService_BroadcastBuddyDeparted_Call{Call: _e.mock.On("BroadcastBuddyDeparted", ctx, screenName)}
-}
-
-func (_c *mockBuddyService_BroadcastBuddyDeparted_Call) Run(run func(ctx context.Context, screenName state.IdentScreenName)) *mockBuddyService_BroadcastBuddyDeparted_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 state.IdentScreenName
-		if args[1] != nil {
-			arg1 = args[1].(state.IdentScreenName)
-		}
-		run(
-			arg0,
-			arg1,
-		)
-	})
-	return _c
-}
-
-func (_c *mockBuddyService_BroadcastBuddyDeparted_Call) Return(err error) *mockBuddyService_BroadcastBuddyDeparted_Call {
-	_c.Call.Return(err)
-	return _c
-}
-
-func (_c *mockBuddyService_BroadcastBuddyDeparted_Call) RunAndReturn(run func(ctx context.Context, screenName state.IdentScreenName) error) *mockBuddyService_BroadcastBuddyDeparted_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
-// DelBuddies provides a mock function for the type mockBuddyService
-func (_mock *mockBuddyService) DelBuddies(ctx context.Context, instance *state.SessionInstance, inBody wire.SNAC_0x03_0x05_BuddyDelBuddies) error {
-	ret := _mock.Called(ctx, instance, inBody)
-
-	if len(ret) == 0 {
-		panic("no return value specified for DelBuddies")
-	}
-
-	var r0 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, *state.SessionInstance, wire.SNAC_0x03_0x05_BuddyDelBuddies) error); ok {
-		r0 = returnFunc(ctx, instance, inBody)
-	} else {
-		r0 = ret.Error(0)
-	}
-	return r0
-}
-
-// mockBuddyService_DelBuddies_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DelBuddies'
-type mockBuddyService_DelBuddies_Call struct {
-	*mock.Call
-}
-
-// DelBuddies is a helper method to define mock.On call
-//   - ctx context.Context
-//   - instance *state.SessionInstance
-//   - inBody wire.SNAC_0x03_0x05_BuddyDelBuddies
-func (_e *mockBuddyService_Expecter) DelBuddies(ctx any, instance any, inBody any) *mockBuddyService_DelBuddies_Call {
-	return &mockBuddyService_DelBuddies_Call{Call: _e.mock.On("DelBuddies", ctx, instance, inBody)}
-}
-
-func (_c *mockBuddyService_DelBuddies_Call) Run(run func(ctx context.Context, instance *state.SessionInstance, inBody wire.SNAC_0x03_0x05_BuddyDelBuddies)) *mockBuddyService_DelBuddies_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 *state.SessionInstance
-		if args[1] != nil {
-			arg1 = args[1].(*state.SessionInstance)
-		}
-		var arg2 wire.SNAC_0x03_0x05_BuddyDelBuddies
-		if args[2] != nil {
-			arg2 = args[2].(wire.SNAC_0x03_0x05_BuddyDelBuddies)
-		}
-		run(
-			arg0,
-			arg1,
-			arg2,
-		)
-	})
-	return _c
-}
-
-func (_c *mockBuddyService_DelBuddies_Call) Return(err error) *mockBuddyService_DelBuddies_Call {
-	_c.Call.Return(err)
-	return _c
-}
-
-func (_c *mockBuddyService_DelBuddies_Call) RunAndReturn(run func(ctx context.Context, instance *state.SessionInstance, inBody wire.SNAC_0x03_0x05_BuddyDelBuddies) error) *mockBuddyService_DelBuddies_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
-// RightsQuery provides a mock function for the type mockBuddyService
-func (_mock *mockBuddyService) RightsQuery(ctx context.Context, inFrame wire.SNACFrame) wire.SNACMessage {
-	ret := _mock.Called(ctx, inFrame)
-
-	if len(ret) == 0 {
-		panic("no return value specified for RightsQuery")
-	}
-
-	var r0 wire.SNACMessage
-	if returnFunc, ok := ret.Get(0).(func(context.Context, wire.SNACFrame) wire.SNACMessage); ok {
-		r0 = returnFunc(ctx, inFrame)
-	} else {
-		r0 = ret.Get(0).(wire.SNACMessage)
-	}
-	return r0
-}
-
-// mockBuddyService_RightsQuery_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RightsQuery'
-type mockBuddyService_RightsQuery_Call struct {
-	*mock.Call
-}
-
-// RightsQuery is a helper method to define mock.On call
-//   - ctx context.Context
-//   - inFrame wire.SNACFrame
-func (_e *mockBuddyService_Expecter) RightsQuery(ctx any, inFrame any) *mockBuddyService_RightsQuery_Call {
-	return &mockBuddyService_RightsQuery_Call{Call: _e.mock.On("RightsQuery", ctx, inFrame)}
-}
-
-func (_c *mockBuddyService_RightsQuery_Call) Run(run func(ctx context.Context, inFrame wire.SNACFrame)) *mockBuddyService_RightsQuery_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 wire.SNACFrame
-		if args[1] != nil {
-			arg1 = args[1].(wire.SNACFrame)
-		}
-		run(
-			arg0,
-			arg1,
-		)
-	})
-	return _c
-}
-
-func (_c *mockBuddyService_RightsQuery_Call) Return(sNACMessage wire.SNACMessage) *mockBuddyService_RightsQuery_Call {
-	_c.Call.Return(sNACMessage)
-	return _c
-}
-
-func (_c *mockBuddyService_RightsQuery_Call) RunAndReturn(run func(ctx context.Context, inFrame wire.SNACFrame) wire.SNACMessage) *mockBuddyService_RightsQuery_Call {
-	_c.Call.Return(run)
-	return _c
-}

+ 0 - 328
server/toc/mock_chat_nav_service_test.go

@@ -1,328 +0,0 @@
-// Code generated by mockery; DO NOT EDIT.
-// github.com/vektra/mockery
-// template: testify
-
-package toc
-
-import (
-	"context"
-
-	"github.com/mk6i/open-oscar-server/state"
-	"github.com/mk6i/open-oscar-server/wire"
-	mock "github.com/stretchr/testify/mock"
-)
-
-// newMockChatNavService creates a new instance of mockChatNavService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
-// The first argument is typically a *testing.T value.
-func newMockChatNavService(t interface {
-	mock.TestingT
-	Cleanup(func())
-}) *mockChatNavService {
-	if helper, ok := t.(interface{ Helper() }); ok {
-		helper.Helper()
-	}
-
-	mock := &mockChatNavService{}
-	mock.Mock.Test(t)
-
-	t.Cleanup(func() {
-		if helper, ok := t.(interface{ Helper() }); ok {
-			helper.Helper()
-		}
-		mock.AssertExpectations(t)
-	})
-
-	return mock
-}
-
-// mockChatNavService is an autogenerated mock type for the ChatNavService type
-type mockChatNavService struct {
-	mock.Mock
-}
-
-type mockChatNavService_Expecter struct {
-	mock *mock.Mock
-}
-
-func (_m *mockChatNavService) EXPECT() *mockChatNavService_Expecter {
-	return &mockChatNavService_Expecter{mock: &_m.Mock}
-}
-
-// CreateRoom provides a mock function for the type mockChatNavService
-func (_mock *mockChatNavService) CreateRoom(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x0E_0x02_ChatRoomInfoUpdate) (wire.SNACMessage, error) {
-	ret := _mock.Called(ctx, instance, inFrame, inBody)
-
-	if len(ret) == 0 {
-		panic("no return value specified for CreateRoom")
-	}
-
-	var r0 wire.SNACMessage
-	var r1 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, *state.SessionInstance, wire.SNACFrame, wire.SNAC_0x0E_0x02_ChatRoomInfoUpdate) (wire.SNACMessage, error)); ok {
-		return returnFunc(ctx, instance, inFrame, inBody)
-	}
-	if returnFunc, ok := ret.Get(0).(func(context.Context, *state.SessionInstance, wire.SNACFrame, wire.SNAC_0x0E_0x02_ChatRoomInfoUpdate) wire.SNACMessage); ok {
-		r0 = returnFunc(ctx, instance, inFrame, inBody)
-	} else {
-		r0 = ret.Get(0).(wire.SNACMessage)
-	}
-	if returnFunc, ok := ret.Get(1).(func(context.Context, *state.SessionInstance, wire.SNACFrame, wire.SNAC_0x0E_0x02_ChatRoomInfoUpdate) error); ok {
-		r1 = returnFunc(ctx, instance, inFrame, inBody)
-	} else {
-		r1 = ret.Error(1)
-	}
-	return r0, r1
-}
-
-// mockChatNavService_CreateRoom_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateRoom'
-type mockChatNavService_CreateRoom_Call struct {
-	*mock.Call
-}
-
-// CreateRoom is a helper method to define mock.On call
-//   - ctx context.Context
-//   - instance *state.SessionInstance
-//   - inFrame wire.SNACFrame
-//   - inBody wire.SNAC_0x0E_0x02_ChatRoomInfoUpdate
-func (_e *mockChatNavService_Expecter) CreateRoom(ctx any, instance any, inFrame any, inBody any) *mockChatNavService_CreateRoom_Call {
-	return &mockChatNavService_CreateRoom_Call{Call: _e.mock.On("CreateRoom", ctx, instance, inFrame, inBody)}
-}
-
-func (_c *mockChatNavService_CreateRoom_Call) Run(run func(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x0E_0x02_ChatRoomInfoUpdate)) *mockChatNavService_CreateRoom_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 *state.SessionInstance
-		if args[1] != nil {
-			arg1 = args[1].(*state.SessionInstance)
-		}
-		var arg2 wire.SNACFrame
-		if args[2] != nil {
-			arg2 = args[2].(wire.SNACFrame)
-		}
-		var arg3 wire.SNAC_0x0E_0x02_ChatRoomInfoUpdate
-		if args[3] != nil {
-			arg3 = args[3].(wire.SNAC_0x0E_0x02_ChatRoomInfoUpdate)
-		}
-		run(
-			arg0,
-			arg1,
-			arg2,
-			arg3,
-		)
-	})
-	return _c
-}
-
-func (_c *mockChatNavService_CreateRoom_Call) Return(sNACMessage wire.SNACMessage, err error) *mockChatNavService_CreateRoom_Call {
-	_c.Call.Return(sNACMessage, err)
-	return _c
-}
-
-func (_c *mockChatNavService_CreateRoom_Call) RunAndReturn(run func(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x0E_0x02_ChatRoomInfoUpdate) (wire.SNACMessage, error)) *mockChatNavService_CreateRoom_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
-// ExchangeInfo provides a mock function for the type mockChatNavService
-func (_mock *mockChatNavService) ExchangeInfo(ctx context.Context, inFrame wire.SNACFrame, inBody wire.SNAC_0x0D_0x03_ChatNavRequestExchangeInfo) (wire.SNACMessage, error) {
-	ret := _mock.Called(ctx, inFrame, inBody)
-
-	if len(ret) == 0 {
-		panic("no return value specified for ExchangeInfo")
-	}
-
-	var r0 wire.SNACMessage
-	var r1 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, wire.SNACFrame, wire.SNAC_0x0D_0x03_ChatNavRequestExchangeInfo) (wire.SNACMessage, error)); ok {
-		return returnFunc(ctx, inFrame, inBody)
-	}
-	if returnFunc, ok := ret.Get(0).(func(context.Context, wire.SNACFrame, wire.SNAC_0x0D_0x03_ChatNavRequestExchangeInfo) wire.SNACMessage); ok {
-		r0 = returnFunc(ctx, inFrame, inBody)
-	} else {
-		r0 = ret.Get(0).(wire.SNACMessage)
-	}
-	if returnFunc, ok := ret.Get(1).(func(context.Context, wire.SNACFrame, wire.SNAC_0x0D_0x03_ChatNavRequestExchangeInfo) error); ok {
-		r1 = returnFunc(ctx, inFrame, inBody)
-	} else {
-		r1 = ret.Error(1)
-	}
-	return r0, r1
-}
-
-// mockChatNavService_ExchangeInfo_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ExchangeInfo'
-type mockChatNavService_ExchangeInfo_Call struct {
-	*mock.Call
-}
-
-// ExchangeInfo is a helper method to define mock.On call
-//   - ctx context.Context
-//   - inFrame wire.SNACFrame
-//   - inBody wire.SNAC_0x0D_0x03_ChatNavRequestExchangeInfo
-func (_e *mockChatNavService_Expecter) ExchangeInfo(ctx any, inFrame any, inBody any) *mockChatNavService_ExchangeInfo_Call {
-	return &mockChatNavService_ExchangeInfo_Call{Call: _e.mock.On("ExchangeInfo", ctx, inFrame, inBody)}
-}
-
-func (_c *mockChatNavService_ExchangeInfo_Call) Run(run func(ctx context.Context, inFrame wire.SNACFrame, inBody wire.SNAC_0x0D_0x03_ChatNavRequestExchangeInfo)) *mockChatNavService_ExchangeInfo_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 wire.SNACFrame
-		if args[1] != nil {
-			arg1 = args[1].(wire.SNACFrame)
-		}
-		var arg2 wire.SNAC_0x0D_0x03_ChatNavRequestExchangeInfo
-		if args[2] != nil {
-			arg2 = args[2].(wire.SNAC_0x0D_0x03_ChatNavRequestExchangeInfo)
-		}
-		run(
-			arg0,
-			arg1,
-			arg2,
-		)
-	})
-	return _c
-}
-
-func (_c *mockChatNavService_ExchangeInfo_Call) Return(sNACMessage wire.SNACMessage, err error) *mockChatNavService_ExchangeInfo_Call {
-	_c.Call.Return(sNACMessage, err)
-	return _c
-}
-
-func (_c *mockChatNavService_ExchangeInfo_Call) RunAndReturn(run func(ctx context.Context, inFrame wire.SNACFrame, inBody wire.SNAC_0x0D_0x03_ChatNavRequestExchangeInfo) (wire.SNACMessage, error)) *mockChatNavService_ExchangeInfo_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
-// RequestChatRights provides a mock function for the type mockChatNavService
-func (_mock *mockChatNavService) RequestChatRights(ctx context.Context, inFrame wire.SNACFrame) wire.SNACMessage {
-	ret := _mock.Called(ctx, inFrame)
-
-	if len(ret) == 0 {
-		panic("no return value specified for RequestChatRights")
-	}
-
-	var r0 wire.SNACMessage
-	if returnFunc, ok := ret.Get(0).(func(context.Context, wire.SNACFrame) wire.SNACMessage); ok {
-		r0 = returnFunc(ctx, inFrame)
-	} else {
-		r0 = ret.Get(0).(wire.SNACMessage)
-	}
-	return r0
-}
-
-// mockChatNavService_RequestChatRights_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RequestChatRights'
-type mockChatNavService_RequestChatRights_Call struct {
-	*mock.Call
-}
-
-// RequestChatRights is a helper method to define mock.On call
-//   - ctx context.Context
-//   - inFrame wire.SNACFrame
-func (_e *mockChatNavService_Expecter) RequestChatRights(ctx any, inFrame any) *mockChatNavService_RequestChatRights_Call {
-	return &mockChatNavService_RequestChatRights_Call{Call: _e.mock.On("RequestChatRights", ctx, inFrame)}
-}
-
-func (_c *mockChatNavService_RequestChatRights_Call) Run(run func(ctx context.Context, inFrame wire.SNACFrame)) *mockChatNavService_RequestChatRights_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 wire.SNACFrame
-		if args[1] != nil {
-			arg1 = args[1].(wire.SNACFrame)
-		}
-		run(
-			arg0,
-			arg1,
-		)
-	})
-	return _c
-}
-
-func (_c *mockChatNavService_RequestChatRights_Call) Return(sNACMessage wire.SNACMessage) *mockChatNavService_RequestChatRights_Call {
-	_c.Call.Return(sNACMessage)
-	return _c
-}
-
-func (_c *mockChatNavService_RequestChatRights_Call) RunAndReturn(run func(ctx context.Context, inFrame wire.SNACFrame) wire.SNACMessage) *mockChatNavService_RequestChatRights_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
-// RequestRoomInfo provides a mock function for the type mockChatNavService
-func (_mock *mockChatNavService) RequestRoomInfo(ctx context.Context, inFrame wire.SNACFrame, inBody wire.SNAC_0x0D_0x04_ChatNavRequestRoomInfo) (wire.SNACMessage, error) {
-	ret := _mock.Called(ctx, inFrame, inBody)
-
-	if len(ret) == 0 {
-		panic("no return value specified for RequestRoomInfo")
-	}
-
-	var r0 wire.SNACMessage
-	var r1 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, wire.SNACFrame, wire.SNAC_0x0D_0x04_ChatNavRequestRoomInfo) (wire.SNACMessage, error)); ok {
-		return returnFunc(ctx, inFrame, inBody)
-	}
-	if returnFunc, ok := ret.Get(0).(func(context.Context, wire.SNACFrame, wire.SNAC_0x0D_0x04_ChatNavRequestRoomInfo) wire.SNACMessage); ok {
-		r0 = returnFunc(ctx, inFrame, inBody)
-	} else {
-		r0 = ret.Get(0).(wire.SNACMessage)
-	}
-	if returnFunc, ok := ret.Get(1).(func(context.Context, wire.SNACFrame, wire.SNAC_0x0D_0x04_ChatNavRequestRoomInfo) error); ok {
-		r1 = returnFunc(ctx, inFrame, inBody)
-	} else {
-		r1 = ret.Error(1)
-	}
-	return r0, r1
-}
-
-// mockChatNavService_RequestRoomInfo_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RequestRoomInfo'
-type mockChatNavService_RequestRoomInfo_Call struct {
-	*mock.Call
-}
-
-// RequestRoomInfo is a helper method to define mock.On call
-//   - ctx context.Context
-//   - inFrame wire.SNACFrame
-//   - inBody wire.SNAC_0x0D_0x04_ChatNavRequestRoomInfo
-func (_e *mockChatNavService_Expecter) RequestRoomInfo(ctx any, inFrame any, inBody any) *mockChatNavService_RequestRoomInfo_Call {
-	return &mockChatNavService_RequestRoomInfo_Call{Call: _e.mock.On("RequestRoomInfo", ctx, inFrame, inBody)}
-}
-
-func (_c *mockChatNavService_RequestRoomInfo_Call) Run(run func(ctx context.Context, inFrame wire.SNACFrame, inBody wire.SNAC_0x0D_0x04_ChatNavRequestRoomInfo)) *mockChatNavService_RequestRoomInfo_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 wire.SNACFrame
-		if args[1] != nil {
-			arg1 = args[1].(wire.SNACFrame)
-		}
-		var arg2 wire.SNAC_0x0D_0x04_ChatNavRequestRoomInfo
-		if args[2] != nil {
-			arg2 = args[2].(wire.SNAC_0x0D_0x04_ChatNavRequestRoomInfo)
-		}
-		run(
-			arg0,
-			arg1,
-			arg2,
-		)
-	})
-	return _c
-}
-
-func (_c *mockChatNavService_RequestRoomInfo_Call) Return(sNACMessage wire.SNACMessage, err error) *mockChatNavService_RequestRoomInfo_Call {
-	_c.Call.Return(sNACMessage, err)
-	return _c
-}
-
-func (_c *mockChatNavService_RequestRoomInfo_Call) RunAndReturn(run func(ctx context.Context, inFrame wire.SNACFrame, inBody wire.SNAC_0x0D_0x04_ChatNavRequestRoomInfo) (wire.SNACMessage, error)) *mockChatNavService_RequestRoomInfo_Call {
-	_c.Call.Return(run)
-	return _c
-}

+ 0 - 129
server/toc/mock_chat_service_test.go

@@ -1,129 +0,0 @@
-// Code generated by mockery; DO NOT EDIT.
-// github.com/vektra/mockery
-// template: testify
-
-package toc
-
-import (
-	"context"
-
-	"github.com/mk6i/open-oscar-server/state"
-	"github.com/mk6i/open-oscar-server/wire"
-	mock "github.com/stretchr/testify/mock"
-)
-
-// newMockChatService creates a new instance of mockChatService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
-// The first argument is typically a *testing.T value.
-func newMockChatService(t interface {
-	mock.TestingT
-	Cleanup(func())
-}) *mockChatService {
-	if helper, ok := t.(interface{ Helper() }); ok {
-		helper.Helper()
-	}
-
-	mock := &mockChatService{}
-	mock.Mock.Test(t)
-
-	t.Cleanup(func() {
-		if helper, ok := t.(interface{ Helper() }); ok {
-			helper.Helper()
-		}
-		mock.AssertExpectations(t)
-	})
-
-	return mock
-}
-
-// mockChatService is an autogenerated mock type for the ChatService type
-type mockChatService struct {
-	mock.Mock
-}
-
-type mockChatService_Expecter struct {
-	mock *mock.Mock
-}
-
-func (_m *mockChatService) EXPECT() *mockChatService_Expecter {
-	return &mockChatService_Expecter{mock: &_m.Mock}
-}
-
-// ChannelMsgToHost provides a mock function for the type mockChatService
-func (_mock *mockChatService) ChannelMsgToHost(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x0E_0x05_ChatChannelMsgToHost) (*wire.SNACMessage, error) {
-	ret := _mock.Called(ctx, instance, inFrame, inBody)
-
-	if len(ret) == 0 {
-		panic("no return value specified for ChannelMsgToHost")
-	}
-
-	var r0 *wire.SNACMessage
-	var r1 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, *state.SessionInstance, wire.SNACFrame, wire.SNAC_0x0E_0x05_ChatChannelMsgToHost) (*wire.SNACMessage, error)); ok {
-		return returnFunc(ctx, instance, inFrame, inBody)
-	}
-	if returnFunc, ok := ret.Get(0).(func(context.Context, *state.SessionInstance, wire.SNACFrame, wire.SNAC_0x0E_0x05_ChatChannelMsgToHost) *wire.SNACMessage); ok {
-		r0 = returnFunc(ctx, instance, inFrame, inBody)
-	} else {
-		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(*wire.SNACMessage)
-		}
-	}
-	if returnFunc, ok := ret.Get(1).(func(context.Context, *state.SessionInstance, wire.SNACFrame, wire.SNAC_0x0E_0x05_ChatChannelMsgToHost) error); ok {
-		r1 = returnFunc(ctx, instance, inFrame, inBody)
-	} else {
-		r1 = ret.Error(1)
-	}
-	return r0, r1
-}
-
-// mockChatService_ChannelMsgToHost_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ChannelMsgToHost'
-type mockChatService_ChannelMsgToHost_Call struct {
-	*mock.Call
-}
-
-// ChannelMsgToHost is a helper method to define mock.On call
-//   - ctx context.Context
-//   - instance *state.SessionInstance
-//   - inFrame wire.SNACFrame
-//   - inBody wire.SNAC_0x0E_0x05_ChatChannelMsgToHost
-func (_e *mockChatService_Expecter) ChannelMsgToHost(ctx any, instance any, inFrame any, inBody any) *mockChatService_ChannelMsgToHost_Call {
-	return &mockChatService_ChannelMsgToHost_Call{Call: _e.mock.On("ChannelMsgToHost", ctx, instance, inFrame, inBody)}
-}
-
-func (_c *mockChatService_ChannelMsgToHost_Call) Run(run func(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x0E_0x05_ChatChannelMsgToHost)) *mockChatService_ChannelMsgToHost_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 *state.SessionInstance
-		if args[1] != nil {
-			arg1 = args[1].(*state.SessionInstance)
-		}
-		var arg2 wire.SNACFrame
-		if args[2] != nil {
-			arg2 = args[2].(wire.SNACFrame)
-		}
-		var arg3 wire.SNAC_0x0E_0x05_ChatChannelMsgToHost
-		if args[3] != nil {
-			arg3 = args[3].(wire.SNAC_0x0E_0x05_ChatChannelMsgToHost)
-		}
-		run(
-			arg0,
-			arg1,
-			arg2,
-			arg3,
-		)
-	})
-	return _c
-}
-
-func (_c *mockChatService_ChannelMsgToHost_Call) Return(sNACMessage *wire.SNACMessage, err error) *mockChatService_ChannelMsgToHost_Call {
-	_c.Call.Return(sNACMessage, err)
-	return _c
-}
-
-func (_c *mockChatService_ChannelMsgToHost_Call) RunAndReturn(run func(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x0E_0x05_ChatChannelMsgToHost) (*wire.SNACMessage, error)) *mockChatService_ChannelMsgToHost_Call {
-	_c.Call.Return(run)
-	return _c
-}

+ 0 - 86
server/toc/mock_chat_session_manager_test.go

@@ -1,86 +0,0 @@
-// Code generated by mockery; DO NOT EDIT.
-// github.com/vektra/mockery
-// template: testify
-
-package toc
-
-import (
-	"github.com/mk6i/open-oscar-server/state"
-	mock "github.com/stretchr/testify/mock"
-)
-
-// newMockChatSessionManager creates a new instance of mockChatSessionManager. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
-// The first argument is typically a *testing.T value.
-func newMockChatSessionManager(t interface {
-	mock.TestingT
-	Cleanup(func())
-}) *mockChatSessionManager {
-	if helper, ok := t.(interface{ Helper() }); ok {
-		helper.Helper()
-	}
-
-	mock := &mockChatSessionManager{}
-	mock.Mock.Test(t)
-
-	t.Cleanup(func() {
-		if helper, ok := t.(interface{ Helper() }); ok {
-			helper.Helper()
-		}
-		mock.AssertExpectations(t)
-	})
-
-	return mock
-}
-
-// mockChatSessionManager is an autogenerated mock type for the ChatSessionManager type
-type mockChatSessionManager struct {
-	mock.Mock
-}
-
-type mockChatSessionManager_Expecter struct {
-	mock *mock.Mock
-}
-
-func (_m *mockChatSessionManager) EXPECT() *mockChatSessionManager_Expecter {
-	return &mockChatSessionManager_Expecter{mock: &_m.Mock}
-}
-
-// RemoveUserFromAllChats provides a mock function for the type mockChatSessionManager
-func (_mock *mockChatSessionManager) RemoveUserFromAllChats(user state.IdentScreenName) {
-	_mock.Called(user)
-	return
-}
-
-// mockChatSessionManager_RemoveUserFromAllChats_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RemoveUserFromAllChats'
-type mockChatSessionManager_RemoveUserFromAllChats_Call struct {
-	*mock.Call
-}
-
-// RemoveUserFromAllChats is a helper method to define mock.On call
-//   - user state.IdentScreenName
-func (_e *mockChatSessionManager_Expecter) RemoveUserFromAllChats(user any) *mockChatSessionManager_RemoveUserFromAllChats_Call {
-	return &mockChatSessionManager_RemoveUserFromAllChats_Call{Call: _e.mock.On("RemoveUserFromAllChats", user)}
-}
-
-func (_c *mockChatSessionManager_RemoveUserFromAllChats_Call) Run(run func(user state.IdentScreenName)) *mockChatSessionManager_RemoveUserFromAllChats_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 state.IdentScreenName
-		if args[0] != nil {
-			arg0 = args[0].(state.IdentScreenName)
-		}
-		run(
-			arg0,
-		)
-	})
-	return _c
-}
-
-func (_c *mockChatSessionManager_RemoveUserFromAllChats_Call) Return() *mockChatSessionManager_RemoveUserFromAllChats_Call {
-	_c.Call.Return()
-	return _c
-}
-
-func (_c *mockChatSessionManager_RemoveUserFromAllChats_Call) RunAndReturn(run func(user state.IdentScreenName)) *mockChatSessionManager_RemoveUserFromAllChats_Call {
-	_c.Run(run)
-	return _c
-}

+ 0 - 174
server/toc/mock_feedbag_manager_test.go

@@ -1,174 +0,0 @@
-// Code generated by mockery; DO NOT EDIT.
-// github.com/vektra/mockery
-// template: testify
-
-package toc
-
-import (
-	"context"
-
-	"github.com/mk6i/open-oscar-server/state"
-	"github.com/mk6i/open-oscar-server/wire"
-	mock "github.com/stretchr/testify/mock"
-)
-
-// newMockFeedbagManager creates a new instance of mockFeedbagManager. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
-// The first argument is typically a *testing.T value.
-func newMockFeedbagManager(t interface {
-	mock.TestingT
-	Cleanup(func())
-}) *mockFeedbagManager {
-	if helper, ok := t.(interface{ Helper() }); ok {
-		helper.Helper()
-	}
-
-	mock := &mockFeedbagManager{}
-	mock.Mock.Test(t)
-
-	t.Cleanup(func() {
-		if helper, ok := t.(interface{ Helper() }); ok {
-			helper.Helper()
-		}
-		mock.AssertExpectations(t)
-	})
-
-	return mock
-}
-
-// mockFeedbagManager is an autogenerated mock type for the FeedbagManager type
-type mockFeedbagManager struct {
-	mock.Mock
-}
-
-type mockFeedbagManager_Expecter struct {
-	mock *mock.Mock
-}
-
-func (_m *mockFeedbagManager) EXPECT() *mockFeedbagManager_Expecter {
-	return &mockFeedbagManager_Expecter{mock: &_m.Mock}
-}
-
-// Feedbag provides a mock function for the type mockFeedbagManager
-func (_mock *mockFeedbagManager) Feedbag(ctx context.Context, screenName state.IdentScreenName) ([]wire.FeedbagItem, error) {
-	ret := _mock.Called(ctx, screenName)
-
-	if len(ret) == 0 {
-		panic("no return value specified for Feedbag")
-	}
-
-	var r0 []wire.FeedbagItem
-	var r1 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, state.IdentScreenName) ([]wire.FeedbagItem, error)); ok {
-		return returnFunc(ctx, screenName)
-	}
-	if returnFunc, ok := ret.Get(0).(func(context.Context, state.IdentScreenName) []wire.FeedbagItem); ok {
-		r0 = returnFunc(ctx, screenName)
-	} else {
-		if ret.Get(0) != nil {
-			r0 = ret.Get(0).([]wire.FeedbagItem)
-		}
-	}
-	if returnFunc, ok := ret.Get(1).(func(context.Context, state.IdentScreenName) error); ok {
-		r1 = returnFunc(ctx, screenName)
-	} else {
-		r1 = ret.Error(1)
-	}
-	return r0, r1
-}
-
-// mockFeedbagManager_Feedbag_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Feedbag'
-type mockFeedbagManager_Feedbag_Call struct {
-	*mock.Call
-}
-
-// Feedbag is a helper method to define mock.On call
-//   - ctx context.Context
-//   - screenName state.IdentScreenName
-func (_e *mockFeedbagManager_Expecter) Feedbag(ctx any, screenName any) *mockFeedbagManager_Feedbag_Call {
-	return &mockFeedbagManager_Feedbag_Call{Call: _e.mock.On("Feedbag", ctx, screenName)}
-}
-
-func (_c *mockFeedbagManager_Feedbag_Call) Run(run func(ctx context.Context, screenName state.IdentScreenName)) *mockFeedbagManager_Feedbag_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 state.IdentScreenName
-		if args[1] != nil {
-			arg1 = args[1].(state.IdentScreenName)
-		}
-		run(
-			arg0,
-			arg1,
-		)
-	})
-	return _c
-}
-
-func (_c *mockFeedbagManager_Feedbag_Call) Return(feedbagItems []wire.FeedbagItem, err error) *mockFeedbagManager_Feedbag_Call {
-	_c.Call.Return(feedbagItems, err)
-	return _c
-}
-
-func (_c *mockFeedbagManager_Feedbag_Call) RunAndReturn(run func(ctx context.Context, screenName state.IdentScreenName) ([]wire.FeedbagItem, error)) *mockFeedbagManager_Feedbag_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
-// UseFeedbag provides a mock function for the type mockFeedbagManager
-func (_mock *mockFeedbagManager) UseFeedbag(ctx context.Context, screenName state.IdentScreenName) error {
-	ret := _mock.Called(ctx, screenName)
-
-	if len(ret) == 0 {
-		panic("no return value specified for UseFeedbag")
-	}
-
-	var r0 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, state.IdentScreenName) error); ok {
-		r0 = returnFunc(ctx, screenName)
-	} else {
-		r0 = ret.Error(0)
-	}
-	return r0
-}
-
-// mockFeedbagManager_UseFeedbag_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UseFeedbag'
-type mockFeedbagManager_UseFeedbag_Call struct {
-	*mock.Call
-}
-
-// UseFeedbag is a helper method to define mock.On call
-//   - ctx context.Context
-//   - screenName state.IdentScreenName
-func (_e *mockFeedbagManager_Expecter) UseFeedbag(ctx any, screenName any) *mockFeedbagManager_UseFeedbag_Call {
-	return &mockFeedbagManager_UseFeedbag_Call{Call: _e.mock.On("UseFeedbag", ctx, screenName)}
-}
-
-func (_c *mockFeedbagManager_UseFeedbag_Call) Run(run func(ctx context.Context, screenName state.IdentScreenName)) *mockFeedbagManager_UseFeedbag_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 state.IdentScreenName
-		if args[1] != nil {
-			arg1 = args[1].(state.IdentScreenName)
-		}
-		run(
-			arg0,
-			arg1,
-		)
-	})
-	return _c
-}
-
-func (_c *mockFeedbagManager_UseFeedbag_Call) Return(err error) *mockFeedbagManager_UseFeedbag_Call {
-	_c.Call.Return(err)
-	return _c
-}
-
-func (_c *mockFeedbagManager_UseFeedbag_Call) RunAndReturn(run func(ctx context.Context, screenName state.IdentScreenName) error) *mockFeedbagManager_UseFeedbag_Call {
-	_c.Call.Return(run)
-	return _c
-}

+ 0 - 663
server/toc/mock_feedbag_service_test.go

@@ -1,663 +0,0 @@
-// Code generated by mockery; DO NOT EDIT.
-// github.com/vektra/mockery
-// template: testify
-
-package toc
-
-import (
-	"context"
-
-	"github.com/mk6i/open-oscar-server/state"
-	"github.com/mk6i/open-oscar-server/wire"
-	mock "github.com/stretchr/testify/mock"
-)
-
-// newMockFeedbagService creates a new instance of mockFeedbagService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
-// The first argument is typically a *testing.T value.
-func newMockFeedbagService(t interface {
-	mock.TestingT
-	Cleanup(func())
-}) *mockFeedbagService {
-	if helper, ok := t.(interface{ Helper() }); ok {
-		helper.Helper()
-	}
-
-	mock := &mockFeedbagService{}
-	mock.Mock.Test(t)
-
-	t.Cleanup(func() {
-		if helper, ok := t.(interface{ Helper() }); ok {
-			helper.Helper()
-		}
-		mock.AssertExpectations(t)
-	})
-
-	return mock
-}
-
-// mockFeedbagService is an autogenerated mock type for the FeedbagService type
-type mockFeedbagService struct {
-	mock.Mock
-}
-
-type mockFeedbagService_Expecter struct {
-	mock *mock.Mock
-}
-
-func (_m *mockFeedbagService) EXPECT() *mockFeedbagService_Expecter {
-	return &mockFeedbagService_Expecter{mock: &_m.Mock}
-}
-
-// DeleteItem provides a mock function for the type mockFeedbagService
-func (_mock *mockFeedbagService) DeleteItem(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x13_0x0A_FeedbagDeleteItem) (*wire.SNACMessage, error) {
-	ret := _mock.Called(ctx, instance, inFrame, inBody)
-
-	if len(ret) == 0 {
-		panic("no return value specified for DeleteItem")
-	}
-
-	var r0 *wire.SNACMessage
-	var r1 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, *state.SessionInstance, wire.SNACFrame, wire.SNAC_0x13_0x0A_FeedbagDeleteItem) (*wire.SNACMessage, error)); ok {
-		return returnFunc(ctx, instance, inFrame, inBody)
-	}
-	if returnFunc, ok := ret.Get(0).(func(context.Context, *state.SessionInstance, wire.SNACFrame, wire.SNAC_0x13_0x0A_FeedbagDeleteItem) *wire.SNACMessage); ok {
-		r0 = returnFunc(ctx, instance, inFrame, inBody)
-	} else {
-		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(*wire.SNACMessage)
-		}
-	}
-	if returnFunc, ok := ret.Get(1).(func(context.Context, *state.SessionInstance, wire.SNACFrame, wire.SNAC_0x13_0x0A_FeedbagDeleteItem) error); ok {
-		r1 = returnFunc(ctx, instance, inFrame, inBody)
-	} else {
-		r1 = ret.Error(1)
-	}
-	return r0, r1
-}
-
-// mockFeedbagService_DeleteItem_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteItem'
-type mockFeedbagService_DeleteItem_Call struct {
-	*mock.Call
-}
-
-// DeleteItem is a helper method to define mock.On call
-//   - ctx context.Context
-//   - instance *state.SessionInstance
-//   - inFrame wire.SNACFrame
-//   - inBody wire.SNAC_0x13_0x0A_FeedbagDeleteItem
-func (_e *mockFeedbagService_Expecter) DeleteItem(ctx any, instance any, inFrame any, inBody any) *mockFeedbagService_DeleteItem_Call {
-	return &mockFeedbagService_DeleteItem_Call{Call: _e.mock.On("DeleteItem", ctx, instance, inFrame, inBody)}
-}
-
-func (_c *mockFeedbagService_DeleteItem_Call) Run(run func(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x13_0x0A_FeedbagDeleteItem)) *mockFeedbagService_DeleteItem_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 *state.SessionInstance
-		if args[1] != nil {
-			arg1 = args[1].(*state.SessionInstance)
-		}
-		var arg2 wire.SNACFrame
-		if args[2] != nil {
-			arg2 = args[2].(wire.SNACFrame)
-		}
-		var arg3 wire.SNAC_0x13_0x0A_FeedbagDeleteItem
-		if args[3] != nil {
-			arg3 = args[3].(wire.SNAC_0x13_0x0A_FeedbagDeleteItem)
-		}
-		run(
-			arg0,
-			arg1,
-			arg2,
-			arg3,
-		)
-	})
-	return _c
-}
-
-func (_c *mockFeedbagService_DeleteItem_Call) Return(sNACMessage *wire.SNACMessage, err error) *mockFeedbagService_DeleteItem_Call {
-	_c.Call.Return(sNACMessage, err)
-	return _c
-}
-
-func (_c *mockFeedbagService_DeleteItem_Call) RunAndReturn(run func(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x13_0x0A_FeedbagDeleteItem) (*wire.SNACMessage, error)) *mockFeedbagService_DeleteItem_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
-// EndCluster provides a mock function for the type mockFeedbagService
-func (_mock *mockFeedbagService) EndCluster(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame) error {
-	ret := _mock.Called(ctx, instance, inFrame)
-
-	if len(ret) == 0 {
-		panic("no return value specified for EndCluster")
-	}
-
-	var r0 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, *state.SessionInstance, wire.SNACFrame) error); ok {
-		r0 = returnFunc(ctx, instance, inFrame)
-	} else {
-		r0 = ret.Error(0)
-	}
-	return r0
-}
-
-// mockFeedbagService_EndCluster_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EndCluster'
-type mockFeedbagService_EndCluster_Call struct {
-	*mock.Call
-}
-
-// EndCluster is a helper method to define mock.On call
-//   - ctx context.Context
-//   - instance *state.SessionInstance
-//   - inFrame wire.SNACFrame
-func (_e *mockFeedbagService_Expecter) EndCluster(ctx any, instance any, inFrame any) *mockFeedbagService_EndCluster_Call {
-	return &mockFeedbagService_EndCluster_Call{Call: _e.mock.On("EndCluster", ctx, instance, inFrame)}
-}
-
-func (_c *mockFeedbagService_EndCluster_Call) Run(run func(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame)) *mockFeedbagService_EndCluster_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 *state.SessionInstance
-		if args[1] != nil {
-			arg1 = args[1].(*state.SessionInstance)
-		}
-		var arg2 wire.SNACFrame
-		if args[2] != nil {
-			arg2 = args[2].(wire.SNACFrame)
-		}
-		run(
-			arg0,
-			arg1,
-			arg2,
-		)
-	})
-	return _c
-}
-
-func (_c *mockFeedbagService_EndCluster_Call) Return(err error) *mockFeedbagService_EndCluster_Call {
-	_c.Call.Return(err)
-	return _c
-}
-
-func (_c *mockFeedbagService_EndCluster_Call) RunAndReturn(run func(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame) error) *mockFeedbagService_EndCluster_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
-// Query provides a mock function for the type mockFeedbagService
-func (_mock *mockFeedbagService) Query(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame) (wire.SNACMessage, error) {
-	ret := _mock.Called(ctx, instance, inFrame)
-
-	if len(ret) == 0 {
-		panic("no return value specified for Query")
-	}
-
-	var r0 wire.SNACMessage
-	var r1 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, *state.SessionInstance, wire.SNACFrame) (wire.SNACMessage, error)); ok {
-		return returnFunc(ctx, instance, inFrame)
-	}
-	if returnFunc, ok := ret.Get(0).(func(context.Context, *state.SessionInstance, wire.SNACFrame) wire.SNACMessage); ok {
-		r0 = returnFunc(ctx, instance, inFrame)
-	} else {
-		r0 = ret.Get(0).(wire.SNACMessage)
-	}
-	if returnFunc, ok := ret.Get(1).(func(context.Context, *state.SessionInstance, wire.SNACFrame) error); ok {
-		r1 = returnFunc(ctx, instance, inFrame)
-	} else {
-		r1 = ret.Error(1)
-	}
-	return r0, r1
-}
-
-// mockFeedbagService_Query_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Query'
-type mockFeedbagService_Query_Call struct {
-	*mock.Call
-}
-
-// Query is a helper method to define mock.On call
-//   - ctx context.Context
-//   - instance *state.SessionInstance
-//   - inFrame wire.SNACFrame
-func (_e *mockFeedbagService_Expecter) Query(ctx any, instance any, inFrame any) *mockFeedbagService_Query_Call {
-	return &mockFeedbagService_Query_Call{Call: _e.mock.On("Query", ctx, instance, inFrame)}
-}
-
-func (_c *mockFeedbagService_Query_Call) Run(run func(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame)) *mockFeedbagService_Query_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 *state.SessionInstance
-		if args[1] != nil {
-			arg1 = args[1].(*state.SessionInstance)
-		}
-		var arg2 wire.SNACFrame
-		if args[2] != nil {
-			arg2 = args[2].(wire.SNACFrame)
-		}
-		run(
-			arg0,
-			arg1,
-			arg2,
-		)
-	})
-	return _c
-}
-
-func (_c *mockFeedbagService_Query_Call) Return(sNACMessage wire.SNACMessage, err error) *mockFeedbagService_Query_Call {
-	_c.Call.Return(sNACMessage, err)
-	return _c
-}
-
-func (_c *mockFeedbagService_Query_Call) RunAndReturn(run func(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame) (wire.SNACMessage, error)) *mockFeedbagService_Query_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
-// QueryIfModified provides a mock function for the type mockFeedbagService
-func (_mock *mockFeedbagService) QueryIfModified(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x13_0x05_FeedbagQueryIfModified) (wire.SNACMessage, error) {
-	ret := _mock.Called(ctx, instance, inFrame, inBody)
-
-	if len(ret) == 0 {
-		panic("no return value specified for QueryIfModified")
-	}
-
-	var r0 wire.SNACMessage
-	var r1 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, *state.SessionInstance, wire.SNACFrame, wire.SNAC_0x13_0x05_FeedbagQueryIfModified) (wire.SNACMessage, error)); ok {
-		return returnFunc(ctx, instance, inFrame, inBody)
-	}
-	if returnFunc, ok := ret.Get(0).(func(context.Context, *state.SessionInstance, wire.SNACFrame, wire.SNAC_0x13_0x05_FeedbagQueryIfModified) wire.SNACMessage); ok {
-		r0 = returnFunc(ctx, instance, inFrame, inBody)
-	} else {
-		r0 = ret.Get(0).(wire.SNACMessage)
-	}
-	if returnFunc, ok := ret.Get(1).(func(context.Context, *state.SessionInstance, wire.SNACFrame, wire.SNAC_0x13_0x05_FeedbagQueryIfModified) error); ok {
-		r1 = returnFunc(ctx, instance, inFrame, inBody)
-	} else {
-		r1 = ret.Error(1)
-	}
-	return r0, r1
-}
-
-// mockFeedbagService_QueryIfModified_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'QueryIfModified'
-type mockFeedbagService_QueryIfModified_Call struct {
-	*mock.Call
-}
-
-// QueryIfModified is a helper method to define mock.On call
-//   - ctx context.Context
-//   - instance *state.SessionInstance
-//   - inFrame wire.SNACFrame
-//   - inBody wire.SNAC_0x13_0x05_FeedbagQueryIfModified
-func (_e *mockFeedbagService_Expecter) QueryIfModified(ctx any, instance any, inFrame any, inBody any) *mockFeedbagService_QueryIfModified_Call {
-	return &mockFeedbagService_QueryIfModified_Call{Call: _e.mock.On("QueryIfModified", ctx, instance, inFrame, inBody)}
-}
-
-func (_c *mockFeedbagService_QueryIfModified_Call) Run(run func(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x13_0x05_FeedbagQueryIfModified)) *mockFeedbagService_QueryIfModified_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 *state.SessionInstance
-		if args[1] != nil {
-			arg1 = args[1].(*state.SessionInstance)
-		}
-		var arg2 wire.SNACFrame
-		if args[2] != nil {
-			arg2 = args[2].(wire.SNACFrame)
-		}
-		var arg3 wire.SNAC_0x13_0x05_FeedbagQueryIfModified
-		if args[3] != nil {
-			arg3 = args[3].(wire.SNAC_0x13_0x05_FeedbagQueryIfModified)
-		}
-		run(
-			arg0,
-			arg1,
-			arg2,
-			arg3,
-		)
-	})
-	return _c
-}
-
-func (_c *mockFeedbagService_QueryIfModified_Call) Return(sNACMessage wire.SNACMessage, err error) *mockFeedbagService_QueryIfModified_Call {
-	_c.Call.Return(sNACMessage, err)
-	return _c
-}
-
-func (_c *mockFeedbagService_QueryIfModified_Call) RunAndReturn(run func(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x13_0x05_FeedbagQueryIfModified) (wire.SNACMessage, error)) *mockFeedbagService_QueryIfModified_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
-// RespondAuthorizeToHost provides a mock function for the type mockFeedbagService
-func (_mock *mockFeedbagService) RespondAuthorizeToHost(ctx context.Context, instance state.IdentScreenName, inFrame wire.SNACFrame, inBody wire.SNAC_0x13_0x1A_FeedbagRespondAuthorizeToHost) error {
-	ret := _mock.Called(ctx, instance, inFrame, inBody)
-
-	if len(ret) == 0 {
-		panic("no return value specified for RespondAuthorizeToHost")
-	}
-
-	var r0 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, state.IdentScreenName, wire.SNACFrame, wire.SNAC_0x13_0x1A_FeedbagRespondAuthorizeToHost) error); ok {
-		r0 = returnFunc(ctx, instance, inFrame, inBody)
-	} else {
-		r0 = ret.Error(0)
-	}
-	return r0
-}
-
-// mockFeedbagService_RespondAuthorizeToHost_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RespondAuthorizeToHost'
-type mockFeedbagService_RespondAuthorizeToHost_Call struct {
-	*mock.Call
-}
-
-// RespondAuthorizeToHost is a helper method to define mock.On call
-//   - ctx context.Context
-//   - instance state.IdentScreenName
-//   - inFrame wire.SNACFrame
-//   - inBody wire.SNAC_0x13_0x1A_FeedbagRespondAuthorizeToHost
-func (_e *mockFeedbagService_Expecter) RespondAuthorizeToHost(ctx any, instance any, inFrame any, inBody any) *mockFeedbagService_RespondAuthorizeToHost_Call {
-	return &mockFeedbagService_RespondAuthorizeToHost_Call{Call: _e.mock.On("RespondAuthorizeToHost", ctx, instance, inFrame, inBody)}
-}
-
-func (_c *mockFeedbagService_RespondAuthorizeToHost_Call) Run(run func(ctx context.Context, instance state.IdentScreenName, inFrame wire.SNACFrame, inBody wire.SNAC_0x13_0x1A_FeedbagRespondAuthorizeToHost)) *mockFeedbagService_RespondAuthorizeToHost_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 state.IdentScreenName
-		if args[1] != nil {
-			arg1 = args[1].(state.IdentScreenName)
-		}
-		var arg2 wire.SNACFrame
-		if args[2] != nil {
-			arg2 = args[2].(wire.SNACFrame)
-		}
-		var arg3 wire.SNAC_0x13_0x1A_FeedbagRespondAuthorizeToHost
-		if args[3] != nil {
-			arg3 = args[3].(wire.SNAC_0x13_0x1A_FeedbagRespondAuthorizeToHost)
-		}
-		run(
-			arg0,
-			arg1,
-			arg2,
-			arg3,
-		)
-	})
-	return _c
-}
-
-func (_c *mockFeedbagService_RespondAuthorizeToHost_Call) Return(err error) *mockFeedbagService_RespondAuthorizeToHost_Call {
-	_c.Call.Return(err)
-	return _c
-}
-
-func (_c *mockFeedbagService_RespondAuthorizeToHost_Call) RunAndReturn(run func(ctx context.Context, instance state.IdentScreenName, inFrame wire.SNACFrame, inBody wire.SNAC_0x13_0x1A_FeedbagRespondAuthorizeToHost) error) *mockFeedbagService_RespondAuthorizeToHost_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
-// RightsQuery provides a mock function for the type mockFeedbagService
-func (_mock *mockFeedbagService) RightsQuery(ctx context.Context, inFrame wire.SNACFrame) wire.SNACMessage {
-	ret := _mock.Called(ctx, inFrame)
-
-	if len(ret) == 0 {
-		panic("no return value specified for RightsQuery")
-	}
-
-	var r0 wire.SNACMessage
-	if returnFunc, ok := ret.Get(0).(func(context.Context, wire.SNACFrame) wire.SNACMessage); ok {
-		r0 = returnFunc(ctx, inFrame)
-	} else {
-		r0 = ret.Get(0).(wire.SNACMessage)
-	}
-	return r0
-}
-
-// mockFeedbagService_RightsQuery_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RightsQuery'
-type mockFeedbagService_RightsQuery_Call struct {
-	*mock.Call
-}
-
-// RightsQuery is a helper method to define mock.On call
-//   - ctx context.Context
-//   - inFrame wire.SNACFrame
-func (_e *mockFeedbagService_Expecter) RightsQuery(ctx any, inFrame any) *mockFeedbagService_RightsQuery_Call {
-	return &mockFeedbagService_RightsQuery_Call{Call: _e.mock.On("RightsQuery", ctx, inFrame)}
-}
-
-func (_c *mockFeedbagService_RightsQuery_Call) Run(run func(ctx context.Context, inFrame wire.SNACFrame)) *mockFeedbagService_RightsQuery_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 wire.SNACFrame
-		if args[1] != nil {
-			arg1 = args[1].(wire.SNACFrame)
-		}
-		run(
-			arg0,
-			arg1,
-		)
-	})
-	return _c
-}
-
-func (_c *mockFeedbagService_RightsQuery_Call) Return(sNACMessage wire.SNACMessage) *mockFeedbagService_RightsQuery_Call {
-	_c.Call.Return(sNACMessage)
-	return _c
-}
-
-func (_c *mockFeedbagService_RightsQuery_Call) RunAndReturn(run func(ctx context.Context, inFrame wire.SNACFrame) wire.SNACMessage) *mockFeedbagService_RightsQuery_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
-// StartCluster provides a mock function for the type mockFeedbagService
-func (_mock *mockFeedbagService) StartCluster(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x13_0x11_FeedbagStartCluster) {
-	_mock.Called(ctx, instance, inFrame, inBody)
-	return
-}
-
-// mockFeedbagService_StartCluster_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'StartCluster'
-type mockFeedbagService_StartCluster_Call struct {
-	*mock.Call
-}
-
-// StartCluster is a helper method to define mock.On call
-//   - ctx context.Context
-//   - instance *state.SessionInstance
-//   - inFrame wire.SNACFrame
-//   - inBody wire.SNAC_0x13_0x11_FeedbagStartCluster
-func (_e *mockFeedbagService_Expecter) StartCluster(ctx any, instance any, inFrame any, inBody any) *mockFeedbagService_StartCluster_Call {
-	return &mockFeedbagService_StartCluster_Call{Call: _e.mock.On("StartCluster", ctx, instance, inFrame, inBody)}
-}
-
-func (_c *mockFeedbagService_StartCluster_Call) Run(run func(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x13_0x11_FeedbagStartCluster)) *mockFeedbagService_StartCluster_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 *state.SessionInstance
-		if args[1] != nil {
-			arg1 = args[1].(*state.SessionInstance)
-		}
-		var arg2 wire.SNACFrame
-		if args[2] != nil {
-			arg2 = args[2].(wire.SNACFrame)
-		}
-		var arg3 wire.SNAC_0x13_0x11_FeedbagStartCluster
-		if args[3] != nil {
-			arg3 = args[3].(wire.SNAC_0x13_0x11_FeedbagStartCluster)
-		}
-		run(
-			arg0,
-			arg1,
-			arg2,
-			arg3,
-		)
-	})
-	return _c
-}
-
-func (_c *mockFeedbagService_StartCluster_Call) Return() *mockFeedbagService_StartCluster_Call {
-	_c.Call.Return()
-	return _c
-}
-
-func (_c *mockFeedbagService_StartCluster_Call) RunAndReturn(run func(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x13_0x11_FeedbagStartCluster)) *mockFeedbagService_StartCluster_Call {
-	_c.Run(run)
-	return _c
-}
-
-// UpsertItem provides a mock function for the type mockFeedbagService
-func (_mock *mockFeedbagService) UpsertItem(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, items []wire.FeedbagItem) (*wire.SNACMessage, error) {
-	ret := _mock.Called(ctx, instance, inFrame, items)
-
-	if len(ret) == 0 {
-		panic("no return value specified for UpsertItem")
-	}
-
-	var r0 *wire.SNACMessage
-	var r1 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, *state.SessionInstance, wire.SNACFrame, []wire.FeedbagItem) (*wire.SNACMessage, error)); ok {
-		return returnFunc(ctx, instance, inFrame, items)
-	}
-	if returnFunc, ok := ret.Get(0).(func(context.Context, *state.SessionInstance, wire.SNACFrame, []wire.FeedbagItem) *wire.SNACMessage); ok {
-		r0 = returnFunc(ctx, instance, inFrame, items)
-	} else {
-		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(*wire.SNACMessage)
-		}
-	}
-	if returnFunc, ok := ret.Get(1).(func(context.Context, *state.SessionInstance, wire.SNACFrame, []wire.FeedbagItem) error); ok {
-		r1 = returnFunc(ctx, instance, inFrame, items)
-	} else {
-		r1 = ret.Error(1)
-	}
-	return r0, r1
-}
-
-// mockFeedbagService_UpsertItem_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpsertItem'
-type mockFeedbagService_UpsertItem_Call struct {
-	*mock.Call
-}
-
-// UpsertItem is a helper method to define mock.On call
-//   - ctx context.Context
-//   - instance *state.SessionInstance
-//   - inFrame wire.SNACFrame
-//   - items []wire.FeedbagItem
-func (_e *mockFeedbagService_Expecter) UpsertItem(ctx any, instance any, inFrame any, items any) *mockFeedbagService_UpsertItem_Call {
-	return &mockFeedbagService_UpsertItem_Call{Call: _e.mock.On("UpsertItem", ctx, instance, inFrame, items)}
-}
-
-func (_c *mockFeedbagService_UpsertItem_Call) Run(run func(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, items []wire.FeedbagItem)) *mockFeedbagService_UpsertItem_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 *state.SessionInstance
-		if args[1] != nil {
-			arg1 = args[1].(*state.SessionInstance)
-		}
-		var arg2 wire.SNACFrame
-		if args[2] != nil {
-			arg2 = args[2].(wire.SNACFrame)
-		}
-		var arg3 []wire.FeedbagItem
-		if args[3] != nil {
-			arg3 = args[3].([]wire.FeedbagItem)
-		}
-		run(
-			arg0,
-			arg1,
-			arg2,
-			arg3,
-		)
-	})
-	return _c
-}
-
-func (_c *mockFeedbagService_UpsertItem_Call) Return(sNACMessage *wire.SNACMessage, err error) *mockFeedbagService_UpsertItem_Call {
-	_c.Call.Return(sNACMessage, err)
-	return _c
-}
-
-func (_c *mockFeedbagService_UpsertItem_Call) RunAndReturn(run func(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, items []wire.FeedbagItem) (*wire.SNACMessage, error)) *mockFeedbagService_UpsertItem_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
-// Use provides a mock function for the type mockFeedbagService
-func (_mock *mockFeedbagService) Use(ctx context.Context, instance *state.SessionInstance) error {
-	ret := _mock.Called(ctx, instance)
-
-	if len(ret) == 0 {
-		panic("no return value specified for Use")
-	}
-
-	var r0 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, *state.SessionInstance) error); ok {
-		r0 = returnFunc(ctx, instance)
-	} else {
-		r0 = ret.Error(0)
-	}
-	return r0
-}
-
-// mockFeedbagService_Use_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Use'
-type mockFeedbagService_Use_Call struct {
-	*mock.Call
-}
-
-// Use is a helper method to define mock.On call
-//   - ctx context.Context
-//   - instance *state.SessionInstance
-func (_e *mockFeedbagService_Expecter) Use(ctx any, instance any) *mockFeedbagService_Use_Call {
-	return &mockFeedbagService_Use_Call{Call: _e.mock.On("Use", ctx, instance)}
-}
-
-func (_c *mockFeedbagService_Use_Call) Run(run func(ctx context.Context, instance *state.SessionInstance)) *mockFeedbagService_Use_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 *state.SessionInstance
-		if args[1] != nil {
-			arg1 = args[1].(*state.SessionInstance)
-		}
-		run(
-			arg0,
-			arg1,
-		)
-	})
-	return _c
-}
-
-func (_c *mockFeedbagService_Use_Call) Return(err error) *mockFeedbagService_Use_Call {
-	_c.Call.Return(err)
-	return _c
-}
-
-func (_c *mockFeedbagService_Use_Call) RunAndReturn(run func(ctx context.Context, instance *state.SessionInstance) error) *mockFeedbagService_Use_Call {
-	_c.Call.Return(run)
-	return _c
-}

+ 0 - 402
server/toc/mock_icbm_service_test.go

@@ -1,402 +0,0 @@
-// Code generated by mockery; DO NOT EDIT.
-// github.com/vektra/mockery
-// template: testify
-
-package toc
-
-import (
-	"context"
-
-	"github.com/mk6i/open-oscar-server/state"
-	"github.com/mk6i/open-oscar-server/wire"
-	mock "github.com/stretchr/testify/mock"
-)
-
-// newMockICBMService creates a new instance of mockICBMService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
-// The first argument is typically a *testing.T value.
-func newMockICBMService(t interface {
-	mock.TestingT
-	Cleanup(func())
-}) *mockICBMService {
-	if helper, ok := t.(interface{ Helper() }); ok {
-		helper.Helper()
-	}
-
-	mock := &mockICBMService{}
-	mock.Mock.Test(t)
-
-	t.Cleanup(func() {
-		if helper, ok := t.(interface{ Helper() }); ok {
-			helper.Helper()
-		}
-		mock.AssertExpectations(t)
-	})
-
-	return mock
-}
-
-// mockICBMService is an autogenerated mock type for the ICBMService type
-type mockICBMService struct {
-	mock.Mock
-}
-
-type mockICBMService_Expecter struct {
-	mock *mock.Mock
-}
-
-func (_m *mockICBMService) EXPECT() *mockICBMService_Expecter {
-	return &mockICBMService_Expecter{mock: &_m.Mock}
-}
-
-// ChannelMsgToHost provides a mock function for the type mockICBMService
-func (_mock *mockICBMService) ChannelMsgToHost(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x04_0x06_ICBMChannelMsgToHost) (*wire.SNACMessage, error) {
-	ret := _mock.Called(ctx, instance, inFrame, inBody)
-
-	if len(ret) == 0 {
-		panic("no return value specified for ChannelMsgToHost")
-	}
-
-	var r0 *wire.SNACMessage
-	var r1 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, *state.SessionInstance, wire.SNACFrame, wire.SNAC_0x04_0x06_ICBMChannelMsgToHost) (*wire.SNACMessage, error)); ok {
-		return returnFunc(ctx, instance, inFrame, inBody)
-	}
-	if returnFunc, ok := ret.Get(0).(func(context.Context, *state.SessionInstance, wire.SNACFrame, wire.SNAC_0x04_0x06_ICBMChannelMsgToHost) *wire.SNACMessage); ok {
-		r0 = returnFunc(ctx, instance, inFrame, inBody)
-	} else {
-		if ret.Get(0) != nil {
-			r0 = ret.Get(0).(*wire.SNACMessage)
-		}
-	}
-	if returnFunc, ok := ret.Get(1).(func(context.Context, *state.SessionInstance, wire.SNACFrame, wire.SNAC_0x04_0x06_ICBMChannelMsgToHost) error); ok {
-		r1 = returnFunc(ctx, instance, inFrame, inBody)
-	} else {
-		r1 = ret.Error(1)
-	}
-	return r0, r1
-}
-
-// mockICBMService_ChannelMsgToHost_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ChannelMsgToHost'
-type mockICBMService_ChannelMsgToHost_Call struct {
-	*mock.Call
-}
-
-// ChannelMsgToHost is a helper method to define mock.On call
-//   - ctx context.Context
-//   - instance *state.SessionInstance
-//   - inFrame wire.SNACFrame
-//   - inBody wire.SNAC_0x04_0x06_ICBMChannelMsgToHost
-func (_e *mockICBMService_Expecter) ChannelMsgToHost(ctx any, instance any, inFrame any, inBody any) *mockICBMService_ChannelMsgToHost_Call {
-	return &mockICBMService_ChannelMsgToHost_Call{Call: _e.mock.On("ChannelMsgToHost", ctx, instance, inFrame, inBody)}
-}
-
-func (_c *mockICBMService_ChannelMsgToHost_Call) Run(run func(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x04_0x06_ICBMChannelMsgToHost)) *mockICBMService_ChannelMsgToHost_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 *state.SessionInstance
-		if args[1] != nil {
-			arg1 = args[1].(*state.SessionInstance)
-		}
-		var arg2 wire.SNACFrame
-		if args[2] != nil {
-			arg2 = args[2].(wire.SNACFrame)
-		}
-		var arg3 wire.SNAC_0x04_0x06_ICBMChannelMsgToHost
-		if args[3] != nil {
-			arg3 = args[3].(wire.SNAC_0x04_0x06_ICBMChannelMsgToHost)
-		}
-		run(
-			arg0,
-			arg1,
-			arg2,
-			arg3,
-		)
-	})
-	return _c
-}
-
-func (_c *mockICBMService_ChannelMsgToHost_Call) Return(sNACMessage *wire.SNACMessage, err error) *mockICBMService_ChannelMsgToHost_Call {
-	_c.Call.Return(sNACMessage, err)
-	return _c
-}
-
-func (_c *mockICBMService_ChannelMsgToHost_Call) RunAndReturn(run func(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x04_0x06_ICBMChannelMsgToHost) (*wire.SNACMessage, error)) *mockICBMService_ChannelMsgToHost_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
-// ClientErr provides a mock function for the type mockICBMService
-func (_mock *mockICBMService) ClientErr(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x04_0x0B_ICBMClientErr) error {
-	ret := _mock.Called(ctx, instance, inFrame, inBody)
-
-	if len(ret) == 0 {
-		panic("no return value specified for ClientErr")
-	}
-
-	var r0 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, *state.SessionInstance, wire.SNACFrame, wire.SNAC_0x04_0x0B_ICBMClientErr) error); ok {
-		r0 = returnFunc(ctx, instance, inFrame, inBody)
-	} else {
-		r0 = ret.Error(0)
-	}
-	return r0
-}
-
-// mockICBMService_ClientErr_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ClientErr'
-type mockICBMService_ClientErr_Call struct {
-	*mock.Call
-}
-
-// ClientErr is a helper method to define mock.On call
-//   - ctx context.Context
-//   - instance *state.SessionInstance
-//   - inFrame wire.SNACFrame
-//   - inBody wire.SNAC_0x04_0x0B_ICBMClientErr
-func (_e *mockICBMService_Expecter) ClientErr(ctx any, instance any, inFrame any, inBody any) *mockICBMService_ClientErr_Call {
-	return &mockICBMService_ClientErr_Call{Call: _e.mock.On("ClientErr", ctx, instance, inFrame, inBody)}
-}
-
-func (_c *mockICBMService_ClientErr_Call) Run(run func(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x04_0x0B_ICBMClientErr)) *mockICBMService_ClientErr_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 *state.SessionInstance
-		if args[1] != nil {
-			arg1 = args[1].(*state.SessionInstance)
-		}
-		var arg2 wire.SNACFrame
-		if args[2] != nil {
-			arg2 = args[2].(wire.SNACFrame)
-		}
-		var arg3 wire.SNAC_0x04_0x0B_ICBMClientErr
-		if args[3] != nil {
-			arg3 = args[3].(wire.SNAC_0x04_0x0B_ICBMClientErr)
-		}
-		run(
-			arg0,
-			arg1,
-			arg2,
-			arg3,
-		)
-	})
-	return _c
-}
-
-func (_c *mockICBMService_ClientErr_Call) Return(err error) *mockICBMService_ClientErr_Call {
-	_c.Call.Return(err)
-	return _c
-}
-
-func (_c *mockICBMService_ClientErr_Call) RunAndReturn(run func(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x04_0x0B_ICBMClientErr) error) *mockICBMService_ClientErr_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
-// ClientEvent provides a mock function for the type mockICBMService
-func (_mock *mockICBMService) ClientEvent(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x04_0x14_ICBMClientEvent) error {
-	ret := _mock.Called(ctx, instance, inFrame, inBody)
-
-	if len(ret) == 0 {
-		panic("no return value specified for ClientEvent")
-	}
-
-	var r0 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, *state.SessionInstance, wire.SNACFrame, wire.SNAC_0x04_0x14_ICBMClientEvent) error); ok {
-		r0 = returnFunc(ctx, instance, inFrame, inBody)
-	} else {
-		r0 = ret.Error(0)
-	}
-	return r0
-}
-
-// mockICBMService_ClientEvent_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ClientEvent'
-type mockICBMService_ClientEvent_Call struct {
-	*mock.Call
-}
-
-// ClientEvent is a helper method to define mock.On call
-//   - ctx context.Context
-//   - instance *state.SessionInstance
-//   - inFrame wire.SNACFrame
-//   - inBody wire.SNAC_0x04_0x14_ICBMClientEvent
-func (_e *mockICBMService_Expecter) ClientEvent(ctx any, instance any, inFrame any, inBody any) *mockICBMService_ClientEvent_Call {
-	return &mockICBMService_ClientEvent_Call{Call: _e.mock.On("ClientEvent", ctx, instance, inFrame, inBody)}
-}
-
-func (_c *mockICBMService_ClientEvent_Call) Run(run func(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x04_0x14_ICBMClientEvent)) *mockICBMService_ClientEvent_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 *state.SessionInstance
-		if args[1] != nil {
-			arg1 = args[1].(*state.SessionInstance)
-		}
-		var arg2 wire.SNACFrame
-		if args[2] != nil {
-			arg2 = args[2].(wire.SNACFrame)
-		}
-		var arg3 wire.SNAC_0x04_0x14_ICBMClientEvent
-		if args[3] != nil {
-			arg3 = args[3].(wire.SNAC_0x04_0x14_ICBMClientEvent)
-		}
-		run(
-			arg0,
-			arg1,
-			arg2,
-			arg3,
-		)
-	})
-	return _c
-}
-
-func (_c *mockICBMService_ClientEvent_Call) Return(err error) *mockICBMService_ClientEvent_Call {
-	_c.Call.Return(err)
-	return _c
-}
-
-func (_c *mockICBMService_ClientEvent_Call) RunAndReturn(run func(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x04_0x14_ICBMClientEvent) error) *mockICBMService_ClientEvent_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
-// EvilRequest provides a mock function for the type mockICBMService
-func (_mock *mockICBMService) EvilRequest(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x04_0x08_ICBMEvilRequest) (wire.SNACMessage, error) {
-	ret := _mock.Called(ctx, instance, inFrame, inBody)
-
-	if len(ret) == 0 {
-		panic("no return value specified for EvilRequest")
-	}
-
-	var r0 wire.SNACMessage
-	var r1 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, *state.SessionInstance, wire.SNACFrame, wire.SNAC_0x04_0x08_ICBMEvilRequest) (wire.SNACMessage, error)); ok {
-		return returnFunc(ctx, instance, inFrame, inBody)
-	}
-	if returnFunc, ok := ret.Get(0).(func(context.Context, *state.SessionInstance, wire.SNACFrame, wire.SNAC_0x04_0x08_ICBMEvilRequest) wire.SNACMessage); ok {
-		r0 = returnFunc(ctx, instance, inFrame, inBody)
-	} else {
-		r0 = ret.Get(0).(wire.SNACMessage)
-	}
-	if returnFunc, ok := ret.Get(1).(func(context.Context, *state.SessionInstance, wire.SNACFrame, wire.SNAC_0x04_0x08_ICBMEvilRequest) error); ok {
-		r1 = returnFunc(ctx, instance, inFrame, inBody)
-	} else {
-		r1 = ret.Error(1)
-	}
-	return r0, r1
-}
-
-// mockICBMService_EvilRequest_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EvilRequest'
-type mockICBMService_EvilRequest_Call struct {
-	*mock.Call
-}
-
-// EvilRequest is a helper method to define mock.On call
-//   - ctx context.Context
-//   - instance *state.SessionInstance
-//   - inFrame wire.SNACFrame
-//   - inBody wire.SNAC_0x04_0x08_ICBMEvilRequest
-func (_e *mockICBMService_Expecter) EvilRequest(ctx any, instance any, inFrame any, inBody any) *mockICBMService_EvilRequest_Call {
-	return &mockICBMService_EvilRequest_Call{Call: _e.mock.On("EvilRequest", ctx, instance, inFrame, inBody)}
-}
-
-func (_c *mockICBMService_EvilRequest_Call) Run(run func(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x04_0x08_ICBMEvilRequest)) *mockICBMService_EvilRequest_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 *state.SessionInstance
-		if args[1] != nil {
-			arg1 = args[1].(*state.SessionInstance)
-		}
-		var arg2 wire.SNACFrame
-		if args[2] != nil {
-			arg2 = args[2].(wire.SNACFrame)
-		}
-		var arg3 wire.SNAC_0x04_0x08_ICBMEvilRequest
-		if args[3] != nil {
-			arg3 = args[3].(wire.SNAC_0x04_0x08_ICBMEvilRequest)
-		}
-		run(
-			arg0,
-			arg1,
-			arg2,
-			arg3,
-		)
-	})
-	return _c
-}
-
-func (_c *mockICBMService_EvilRequest_Call) Return(sNACMessage wire.SNACMessage, err error) *mockICBMService_EvilRequest_Call {
-	_c.Call.Return(sNACMessage, err)
-	return _c
-}
-
-func (_c *mockICBMService_EvilRequest_Call) RunAndReturn(run func(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x04_0x08_ICBMEvilRequest) (wire.SNACMessage, error)) *mockICBMService_EvilRequest_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
-// ParameterQuery provides a mock function for the type mockICBMService
-func (_mock *mockICBMService) ParameterQuery(ctx context.Context, inFrame wire.SNACFrame) wire.SNACMessage {
-	ret := _mock.Called(ctx, inFrame)
-
-	if len(ret) == 0 {
-		panic("no return value specified for ParameterQuery")
-	}
-
-	var r0 wire.SNACMessage
-	if returnFunc, ok := ret.Get(0).(func(context.Context, wire.SNACFrame) wire.SNACMessage); ok {
-		r0 = returnFunc(ctx, inFrame)
-	} else {
-		r0 = ret.Get(0).(wire.SNACMessage)
-	}
-	return r0
-}
-
-// mockICBMService_ParameterQuery_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ParameterQuery'
-type mockICBMService_ParameterQuery_Call struct {
-	*mock.Call
-}
-
-// ParameterQuery is a helper method to define mock.On call
-//   - ctx context.Context
-//   - inFrame wire.SNACFrame
-func (_e *mockICBMService_Expecter) ParameterQuery(ctx any, inFrame any) *mockICBMService_ParameterQuery_Call {
-	return &mockICBMService_ParameterQuery_Call{Call: _e.mock.On("ParameterQuery", ctx, inFrame)}
-}
-
-func (_c *mockICBMService_ParameterQuery_Call) Run(run func(ctx context.Context, inFrame wire.SNACFrame)) *mockICBMService_ParameterQuery_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 wire.SNACFrame
-		if args[1] != nil {
-			arg1 = args[1].(wire.SNACFrame)
-		}
-		run(
-			arg0,
-			arg1,
-		)
-	})
-	return _c
-}
-
-func (_c *mockICBMService_ParameterQuery_Call) Return(sNACMessage wire.SNACMessage) *mockICBMService_ParameterQuery_Call {
-	_c.Call.Return(sNACMessage)
-	return _c
-}
-
-func (_c *mockICBMService_ParameterQuery_Call) RunAndReturn(run func(ctx context.Context, inFrame wire.SNACFrame) wire.SNACMessage) *mockICBMService_ParameterQuery_Call {
-	_c.Call.Return(run)
-	return _c
-}

+ 0 - 141
server/toc/mock_locate_service_test.go

@@ -120,147 +120,6 @@ func (_c *mockLocateService_DirInfo_Call) RunAndReturn(run func(ctx context.Cont
 	return _c
 }
 
-// SetDirInfo provides a mock function for the type mockLocateService
-func (_mock *mockLocateService) SetDirInfo(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x02_0x09_LocateSetDirInfo) (wire.SNACMessage, error) {
-	ret := _mock.Called(ctx, instance, inFrame, inBody)
-
-	if len(ret) == 0 {
-		panic("no return value specified for SetDirInfo")
-	}
-
-	var r0 wire.SNACMessage
-	var r1 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, *state.SessionInstance, wire.SNACFrame, wire.SNAC_0x02_0x09_LocateSetDirInfo) (wire.SNACMessage, error)); ok {
-		return returnFunc(ctx, instance, inFrame, inBody)
-	}
-	if returnFunc, ok := ret.Get(0).(func(context.Context, *state.SessionInstance, wire.SNACFrame, wire.SNAC_0x02_0x09_LocateSetDirInfo) wire.SNACMessage); ok {
-		r0 = returnFunc(ctx, instance, inFrame, inBody)
-	} else {
-		r0 = ret.Get(0).(wire.SNACMessage)
-	}
-	if returnFunc, ok := ret.Get(1).(func(context.Context, *state.SessionInstance, wire.SNACFrame, wire.SNAC_0x02_0x09_LocateSetDirInfo) error); ok {
-		r1 = returnFunc(ctx, instance, inFrame, inBody)
-	} else {
-		r1 = ret.Error(1)
-	}
-	return r0, r1
-}
-
-// mockLocateService_SetDirInfo_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetDirInfo'
-type mockLocateService_SetDirInfo_Call struct {
-	*mock.Call
-}
-
-// SetDirInfo is a helper method to define mock.On call
-//   - ctx context.Context
-//   - instance *state.SessionInstance
-//   - inFrame wire.SNACFrame
-//   - inBody wire.SNAC_0x02_0x09_LocateSetDirInfo
-func (_e *mockLocateService_Expecter) SetDirInfo(ctx any, instance any, inFrame any, inBody any) *mockLocateService_SetDirInfo_Call {
-	return &mockLocateService_SetDirInfo_Call{Call: _e.mock.On("SetDirInfo", ctx, instance, inFrame, inBody)}
-}
-
-func (_c *mockLocateService_SetDirInfo_Call) Run(run func(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x02_0x09_LocateSetDirInfo)) *mockLocateService_SetDirInfo_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 *state.SessionInstance
-		if args[1] != nil {
-			arg1 = args[1].(*state.SessionInstance)
-		}
-		var arg2 wire.SNACFrame
-		if args[2] != nil {
-			arg2 = args[2].(wire.SNACFrame)
-		}
-		var arg3 wire.SNAC_0x02_0x09_LocateSetDirInfo
-		if args[3] != nil {
-			arg3 = args[3].(wire.SNAC_0x02_0x09_LocateSetDirInfo)
-		}
-		run(
-			arg0,
-			arg1,
-			arg2,
-			arg3,
-		)
-	})
-	return _c
-}
-
-func (_c *mockLocateService_SetDirInfo_Call) Return(sNACMessage wire.SNACMessage, err error) *mockLocateService_SetDirInfo_Call {
-	_c.Call.Return(sNACMessage, err)
-	return _c
-}
-
-func (_c *mockLocateService_SetDirInfo_Call) RunAndReturn(run func(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x02_0x09_LocateSetDirInfo) (wire.SNACMessage, error)) *mockLocateService_SetDirInfo_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
-// SetInfo provides a mock function for the type mockLocateService
-func (_mock *mockLocateService) SetInfo(ctx context.Context, instance *state.SessionInstance, inBody wire.SNAC_0x02_0x04_LocateSetInfo) error {
-	ret := _mock.Called(ctx, instance, inBody)
-
-	if len(ret) == 0 {
-		panic("no return value specified for SetInfo")
-	}
-
-	var r0 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, *state.SessionInstance, wire.SNAC_0x02_0x04_LocateSetInfo) error); ok {
-		r0 = returnFunc(ctx, instance, inBody)
-	} else {
-		r0 = ret.Error(0)
-	}
-	return r0
-}
-
-// mockLocateService_SetInfo_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetInfo'
-type mockLocateService_SetInfo_Call struct {
-	*mock.Call
-}
-
-// SetInfo is a helper method to define mock.On call
-//   - ctx context.Context
-//   - instance *state.SessionInstance
-//   - inBody wire.SNAC_0x02_0x04_LocateSetInfo
-func (_e *mockLocateService_Expecter) SetInfo(ctx any, instance any, inBody any) *mockLocateService_SetInfo_Call {
-	return &mockLocateService_SetInfo_Call{Call: _e.mock.On("SetInfo", ctx, instance, inBody)}
-}
-
-func (_c *mockLocateService_SetInfo_Call) Run(run func(ctx context.Context, instance *state.SessionInstance, inBody wire.SNAC_0x02_0x04_LocateSetInfo)) *mockLocateService_SetInfo_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 *state.SessionInstance
-		if args[1] != nil {
-			arg1 = args[1].(*state.SessionInstance)
-		}
-		var arg2 wire.SNAC_0x02_0x04_LocateSetInfo
-		if args[2] != nil {
-			arg2 = args[2].(wire.SNAC_0x02_0x04_LocateSetInfo)
-		}
-		run(
-			arg0,
-			arg1,
-			arg2,
-		)
-	})
-	return _c
-}
-
-func (_c *mockLocateService_SetInfo_Call) Return(err error) *mockLocateService_SetInfo_Call {
-	_c.Call.Return(err)
-	return _c
-}
-
-func (_c *mockLocateService_SetInfo_Call) RunAndReturn(run func(ctx context.Context, instance *state.SessionInstance, inBody wire.SNAC_0x02_0x04_LocateSetInfo) error) *mockLocateService_SetInfo_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
 // UserInfoQuery provides a mock function for the type mockLocateService
 func (_mock *mockLocateService) UserInfoQuery(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x02_0x05_LocateUserInfoQuery) (wire.SNACMessage, error) {
 	ret := _mock.Called(ctx, instance, inFrame, inBody)

+ 0 - 318
server/toc/mock_oservice_service_test.go

@@ -1,318 +0,0 @@
-// Code generated by mockery; DO NOT EDIT.
-// github.com/vektra/mockery
-// template: testify
-
-package toc
-
-import (
-	"context"
-
-	"github.com/mk6i/open-oscar-server/config"
-	"github.com/mk6i/open-oscar-server/state"
-	"github.com/mk6i/open-oscar-server/wire"
-	mock "github.com/stretchr/testify/mock"
-)
-
-// newMockOServiceService creates a new instance of mockOServiceService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
-// The first argument is typically a *testing.T value.
-func newMockOServiceService(t interface {
-	mock.TestingT
-	Cleanup(func())
-}) *mockOServiceService {
-	if helper, ok := t.(interface{ Helper() }); ok {
-		helper.Helper()
-	}
-
-	mock := &mockOServiceService{}
-	mock.Mock.Test(t)
-
-	t.Cleanup(func() {
-		if helper, ok := t.(interface{ Helper() }); ok {
-			helper.Helper()
-		}
-		mock.AssertExpectations(t)
-	})
-
-	return mock
-}
-
-// mockOServiceService is an autogenerated mock type for the OServiceService type
-type mockOServiceService struct {
-	mock.Mock
-}
-
-type mockOServiceService_Expecter struct {
-	mock *mock.Mock
-}
-
-func (_m *mockOServiceService) EXPECT() *mockOServiceService_Expecter {
-	return &mockOServiceService_Expecter{mock: &_m.Mock}
-}
-
-// ClientOnline provides a mock function for the type mockOServiceService
-func (_mock *mockOServiceService) ClientOnline(ctx context.Context, service uint16, inBody wire.SNAC_0x01_0x02_OServiceClientOnline, instance *state.SessionInstance) error {
-	ret := _mock.Called(ctx, service, inBody, instance)
-
-	if len(ret) == 0 {
-		panic("no return value specified for ClientOnline")
-	}
-
-	var r0 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, uint16, wire.SNAC_0x01_0x02_OServiceClientOnline, *state.SessionInstance) error); ok {
-		r0 = returnFunc(ctx, service, inBody, instance)
-	} else {
-		r0 = ret.Error(0)
-	}
-	return r0
-}
-
-// mockOServiceService_ClientOnline_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ClientOnline'
-type mockOServiceService_ClientOnline_Call struct {
-	*mock.Call
-}
-
-// ClientOnline is a helper method to define mock.On call
-//   - ctx context.Context
-//   - service uint16
-//   - inBody wire.SNAC_0x01_0x02_OServiceClientOnline
-//   - instance *state.SessionInstance
-func (_e *mockOServiceService_Expecter) ClientOnline(ctx any, service any, inBody any, instance any) *mockOServiceService_ClientOnline_Call {
-	return &mockOServiceService_ClientOnline_Call{Call: _e.mock.On("ClientOnline", ctx, service, inBody, instance)}
-}
-
-func (_c *mockOServiceService_ClientOnline_Call) Run(run func(ctx context.Context, service uint16, inBody wire.SNAC_0x01_0x02_OServiceClientOnline, instance *state.SessionInstance)) *mockOServiceService_ClientOnline_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 uint16
-		if args[1] != nil {
-			arg1 = args[1].(uint16)
-		}
-		var arg2 wire.SNAC_0x01_0x02_OServiceClientOnline
-		if args[2] != nil {
-			arg2 = args[2].(wire.SNAC_0x01_0x02_OServiceClientOnline)
-		}
-		var arg3 *state.SessionInstance
-		if args[3] != nil {
-			arg3 = args[3].(*state.SessionInstance)
-		}
-		run(
-			arg0,
-			arg1,
-			arg2,
-			arg3,
-		)
-	})
-	return _c
-}
-
-func (_c *mockOServiceService_ClientOnline_Call) Return(err error) *mockOServiceService_ClientOnline_Call {
-	_c.Call.Return(err)
-	return _c
-}
-
-func (_c *mockOServiceService_ClientOnline_Call) RunAndReturn(run func(ctx context.Context, service uint16, inBody wire.SNAC_0x01_0x02_OServiceClientOnline, instance *state.SessionInstance) error) *mockOServiceService_ClientOnline_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
-// IdleNotification provides a mock function for the type mockOServiceService
-func (_mock *mockOServiceService) IdleNotification(ctx context.Context, instance *state.SessionInstance, inBody wire.SNAC_0x01_0x11_OServiceIdleNotification) error {
-	ret := _mock.Called(ctx, instance, inBody)
-
-	if len(ret) == 0 {
-		panic("no return value specified for IdleNotification")
-	}
-
-	var r0 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, *state.SessionInstance, wire.SNAC_0x01_0x11_OServiceIdleNotification) error); ok {
-		r0 = returnFunc(ctx, instance, inBody)
-	} else {
-		r0 = ret.Error(0)
-	}
-	return r0
-}
-
-// mockOServiceService_IdleNotification_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IdleNotification'
-type mockOServiceService_IdleNotification_Call struct {
-	*mock.Call
-}
-
-// IdleNotification is a helper method to define mock.On call
-//   - ctx context.Context
-//   - instance *state.SessionInstance
-//   - inBody wire.SNAC_0x01_0x11_OServiceIdleNotification
-func (_e *mockOServiceService_Expecter) IdleNotification(ctx any, instance any, inBody any) *mockOServiceService_IdleNotification_Call {
-	return &mockOServiceService_IdleNotification_Call{Call: _e.mock.On("IdleNotification", ctx, instance, inBody)}
-}
-
-func (_c *mockOServiceService_IdleNotification_Call) Run(run func(ctx context.Context, instance *state.SessionInstance, inBody wire.SNAC_0x01_0x11_OServiceIdleNotification)) *mockOServiceService_IdleNotification_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 *state.SessionInstance
-		if args[1] != nil {
-			arg1 = args[1].(*state.SessionInstance)
-		}
-		var arg2 wire.SNAC_0x01_0x11_OServiceIdleNotification
-		if args[2] != nil {
-			arg2 = args[2].(wire.SNAC_0x01_0x11_OServiceIdleNotification)
-		}
-		run(
-			arg0,
-			arg1,
-			arg2,
-		)
-	})
-	return _c
-}
-
-func (_c *mockOServiceService_IdleNotification_Call) Return(err error) *mockOServiceService_IdleNotification_Call {
-	_c.Call.Return(err)
-	return _c
-}
-
-func (_c *mockOServiceService_IdleNotification_Call) RunAndReturn(run func(ctx context.Context, instance *state.SessionInstance, inBody wire.SNAC_0x01_0x11_OServiceIdleNotification) error) *mockOServiceService_IdleNotification_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
-// MonitorRateLimits provides a mock function for the type mockOServiceService
-func (_mock *mockOServiceService) MonitorRateLimits(ctx context.Context, session *state.Session) {
-	_mock.Called(ctx, session)
-	return
-}
-
-// mockOServiceService_MonitorRateLimits_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'MonitorRateLimits'
-type mockOServiceService_MonitorRateLimits_Call struct {
-	*mock.Call
-}
-
-// MonitorRateLimits is a helper method to define mock.On call
-//   - ctx context.Context
-//   - session *state.Session
-func (_e *mockOServiceService_Expecter) MonitorRateLimits(ctx any, session any) *mockOServiceService_MonitorRateLimits_Call {
-	return &mockOServiceService_MonitorRateLimits_Call{Call: _e.mock.On("MonitorRateLimits", ctx, session)}
-}
-
-func (_c *mockOServiceService_MonitorRateLimits_Call) Run(run func(ctx context.Context, session *state.Session)) *mockOServiceService_MonitorRateLimits_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 *state.Session
-		if args[1] != nil {
-			arg1 = args[1].(*state.Session)
-		}
-		run(
-			arg0,
-			arg1,
-		)
-	})
-	return _c
-}
-
-func (_c *mockOServiceService_MonitorRateLimits_Call) Return() *mockOServiceService_MonitorRateLimits_Call {
-	_c.Call.Return()
-	return _c
-}
-
-func (_c *mockOServiceService_MonitorRateLimits_Call) RunAndReturn(run func(ctx context.Context, session *state.Session)) *mockOServiceService_MonitorRateLimits_Call {
-	_c.Run(run)
-	return _c
-}
-
-// ServiceRequest provides a mock function for the type mockOServiceService
-func (_mock *mockOServiceService) ServiceRequest(ctx context.Context, service uint16, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x01_0x04_OServiceServiceRequest, listenerGroup config.ListenerGroup) (wire.SNACMessage, error) {
-	ret := _mock.Called(ctx, service, instance, inFrame, inBody, listenerGroup)
-
-	if len(ret) == 0 {
-		panic("no return value specified for ServiceRequest")
-	}
-
-	var r0 wire.SNACMessage
-	var r1 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, uint16, *state.SessionInstance, wire.SNACFrame, wire.SNAC_0x01_0x04_OServiceServiceRequest, config.ListenerGroup) (wire.SNACMessage, error)); ok {
-		return returnFunc(ctx, service, instance, inFrame, inBody, listenerGroup)
-	}
-	if returnFunc, ok := ret.Get(0).(func(context.Context, uint16, *state.SessionInstance, wire.SNACFrame, wire.SNAC_0x01_0x04_OServiceServiceRequest, config.ListenerGroup) wire.SNACMessage); ok {
-		r0 = returnFunc(ctx, service, instance, inFrame, inBody, listenerGroup)
-	} else {
-		r0 = ret.Get(0).(wire.SNACMessage)
-	}
-	if returnFunc, ok := ret.Get(1).(func(context.Context, uint16, *state.SessionInstance, wire.SNACFrame, wire.SNAC_0x01_0x04_OServiceServiceRequest, config.ListenerGroup) error); ok {
-		r1 = returnFunc(ctx, service, instance, inFrame, inBody, listenerGroup)
-	} else {
-		r1 = ret.Error(1)
-	}
-	return r0, r1
-}
-
-// mockOServiceService_ServiceRequest_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ServiceRequest'
-type mockOServiceService_ServiceRequest_Call struct {
-	*mock.Call
-}
-
-// ServiceRequest is a helper method to define mock.On call
-//   - ctx context.Context
-//   - service uint16
-//   - instance *state.SessionInstance
-//   - inFrame wire.SNACFrame
-//   - inBody wire.SNAC_0x01_0x04_OServiceServiceRequest
-//   - listenerGroup config.ListenerGroup
-func (_e *mockOServiceService_Expecter) ServiceRequest(ctx any, service any, instance any, inFrame any, inBody any, listenerGroup any) *mockOServiceService_ServiceRequest_Call {
-	return &mockOServiceService_ServiceRequest_Call{Call: _e.mock.On("ServiceRequest", ctx, service, instance, inFrame, inBody, listenerGroup)}
-}
-
-func (_c *mockOServiceService_ServiceRequest_Call) Run(run func(ctx context.Context, service uint16, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x01_0x04_OServiceServiceRequest, listenerGroup config.ListenerGroup)) *mockOServiceService_ServiceRequest_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 uint16
-		if args[1] != nil {
-			arg1 = args[1].(uint16)
-		}
-		var arg2 *state.SessionInstance
-		if args[2] != nil {
-			arg2 = args[2].(*state.SessionInstance)
-		}
-		var arg3 wire.SNACFrame
-		if args[3] != nil {
-			arg3 = args[3].(wire.SNACFrame)
-		}
-		var arg4 wire.SNAC_0x01_0x04_OServiceServiceRequest
-		if args[4] != nil {
-			arg4 = args[4].(wire.SNAC_0x01_0x04_OServiceServiceRequest)
-		}
-		var arg5 config.ListenerGroup
-		if args[5] != nil {
-			arg5 = args[5].(config.ListenerGroup)
-		}
-		run(
-			arg0,
-			arg1,
-			arg2,
-			arg3,
-			arg4,
-			arg5,
-		)
-	})
-	return _c
-}
-
-func (_c *mockOServiceService_ServiceRequest_Call) Return(sNACMessage wire.SNACMessage, err error) *mockOServiceService_ServiceRequest_Call {
-	_c.Call.Return(sNACMessage, err)
-	return _c
-}
-
-func (_c *mockOServiceService_ServiceRequest_Call) RunAndReturn(run func(ctx context.Context, service uint16, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x01_0x04_OServiceServiceRequest, listenerGroup config.ListenerGroup) (wire.SNACMessage, error)) *mockOServiceService_ServiceRequest_Call {
-	_c.Call.Return(run)
-	return _c
-}

+ 0 - 358
server/toc/mock_permit_deny_service_test.go

@@ -1,358 +0,0 @@
-// Code generated by mockery; DO NOT EDIT.
-// github.com/vektra/mockery
-// template: testify
-
-package toc
-
-import (
-	"context"
-
-	"github.com/mk6i/open-oscar-server/state"
-	"github.com/mk6i/open-oscar-server/wire"
-	mock "github.com/stretchr/testify/mock"
-)
-
-// newMockPermitDenyService creates a new instance of mockPermitDenyService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
-// The first argument is typically a *testing.T value.
-func newMockPermitDenyService(t interface {
-	mock.TestingT
-	Cleanup(func())
-}) *mockPermitDenyService {
-	if helper, ok := t.(interface{ Helper() }); ok {
-		helper.Helper()
-	}
-
-	mock := &mockPermitDenyService{}
-	mock.Mock.Test(t)
-
-	t.Cleanup(func() {
-		if helper, ok := t.(interface{ Helper() }); ok {
-			helper.Helper()
-		}
-		mock.AssertExpectations(t)
-	})
-
-	return mock
-}
-
-// mockPermitDenyService is an autogenerated mock type for the PermitDenyService type
-type mockPermitDenyService struct {
-	mock.Mock
-}
-
-type mockPermitDenyService_Expecter struct {
-	mock *mock.Mock
-}
-
-func (_m *mockPermitDenyService) EXPECT() *mockPermitDenyService_Expecter {
-	return &mockPermitDenyService_Expecter{mock: &_m.Mock}
-}
-
-// AddDenyListEntries provides a mock function for the type mockPermitDenyService
-func (_mock *mockPermitDenyService) AddDenyListEntries(ctx context.Context, instance *state.SessionInstance, inBody wire.SNAC_0x09_0x07_PermitDenyAddDenyListEntries) error {
-	ret := _mock.Called(ctx, instance, inBody)
-
-	if len(ret) == 0 {
-		panic("no return value specified for AddDenyListEntries")
-	}
-
-	var r0 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, *state.SessionInstance, wire.SNAC_0x09_0x07_PermitDenyAddDenyListEntries) error); ok {
-		r0 = returnFunc(ctx, instance, inBody)
-	} else {
-		r0 = ret.Error(0)
-	}
-	return r0
-}
-
-// mockPermitDenyService_AddDenyListEntries_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddDenyListEntries'
-type mockPermitDenyService_AddDenyListEntries_Call struct {
-	*mock.Call
-}
-
-// AddDenyListEntries is a helper method to define mock.On call
-//   - ctx context.Context
-//   - instance *state.SessionInstance
-//   - inBody wire.SNAC_0x09_0x07_PermitDenyAddDenyListEntries
-func (_e *mockPermitDenyService_Expecter) AddDenyListEntries(ctx any, instance any, inBody any) *mockPermitDenyService_AddDenyListEntries_Call {
-	return &mockPermitDenyService_AddDenyListEntries_Call{Call: _e.mock.On("AddDenyListEntries", ctx, instance, inBody)}
-}
-
-func (_c *mockPermitDenyService_AddDenyListEntries_Call) Run(run func(ctx context.Context, instance *state.SessionInstance, inBody wire.SNAC_0x09_0x07_PermitDenyAddDenyListEntries)) *mockPermitDenyService_AddDenyListEntries_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 *state.SessionInstance
-		if args[1] != nil {
-			arg1 = args[1].(*state.SessionInstance)
-		}
-		var arg2 wire.SNAC_0x09_0x07_PermitDenyAddDenyListEntries
-		if args[2] != nil {
-			arg2 = args[2].(wire.SNAC_0x09_0x07_PermitDenyAddDenyListEntries)
-		}
-		run(
-			arg0,
-			arg1,
-			arg2,
-		)
-	})
-	return _c
-}
-
-func (_c *mockPermitDenyService_AddDenyListEntries_Call) Return(err error) *mockPermitDenyService_AddDenyListEntries_Call {
-	_c.Call.Return(err)
-	return _c
-}
-
-func (_c *mockPermitDenyService_AddDenyListEntries_Call) RunAndReturn(run func(ctx context.Context, instance *state.SessionInstance, inBody wire.SNAC_0x09_0x07_PermitDenyAddDenyListEntries) error) *mockPermitDenyService_AddDenyListEntries_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
-// AddPermListEntries provides a mock function for the type mockPermitDenyService
-func (_mock *mockPermitDenyService) AddPermListEntries(ctx context.Context, instance *state.SessionInstance, inBody wire.SNAC_0x09_0x05_PermitDenyAddPermListEntries) error {
-	ret := _mock.Called(ctx, instance, inBody)
-
-	if len(ret) == 0 {
-		panic("no return value specified for AddPermListEntries")
-	}
-
-	var r0 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, *state.SessionInstance, wire.SNAC_0x09_0x05_PermitDenyAddPermListEntries) error); ok {
-		r0 = returnFunc(ctx, instance, inBody)
-	} else {
-		r0 = ret.Error(0)
-	}
-	return r0
-}
-
-// mockPermitDenyService_AddPermListEntries_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddPermListEntries'
-type mockPermitDenyService_AddPermListEntries_Call struct {
-	*mock.Call
-}
-
-// AddPermListEntries is a helper method to define mock.On call
-//   - ctx context.Context
-//   - instance *state.SessionInstance
-//   - inBody wire.SNAC_0x09_0x05_PermitDenyAddPermListEntries
-func (_e *mockPermitDenyService_Expecter) AddPermListEntries(ctx any, instance any, inBody any) *mockPermitDenyService_AddPermListEntries_Call {
-	return &mockPermitDenyService_AddPermListEntries_Call{Call: _e.mock.On("AddPermListEntries", ctx, instance, inBody)}
-}
-
-func (_c *mockPermitDenyService_AddPermListEntries_Call) Run(run func(ctx context.Context, instance *state.SessionInstance, inBody wire.SNAC_0x09_0x05_PermitDenyAddPermListEntries)) *mockPermitDenyService_AddPermListEntries_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 *state.SessionInstance
-		if args[1] != nil {
-			arg1 = args[1].(*state.SessionInstance)
-		}
-		var arg2 wire.SNAC_0x09_0x05_PermitDenyAddPermListEntries
-		if args[2] != nil {
-			arg2 = args[2].(wire.SNAC_0x09_0x05_PermitDenyAddPermListEntries)
-		}
-		run(
-			arg0,
-			arg1,
-			arg2,
-		)
-	})
-	return _c
-}
-
-func (_c *mockPermitDenyService_AddPermListEntries_Call) Return(err error) *mockPermitDenyService_AddPermListEntries_Call {
-	_c.Call.Return(err)
-	return _c
-}
-
-func (_c *mockPermitDenyService_AddPermListEntries_Call) RunAndReturn(run func(ctx context.Context, instance *state.SessionInstance, inBody wire.SNAC_0x09_0x05_PermitDenyAddPermListEntries) error) *mockPermitDenyService_AddPermListEntries_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
-// DelDenyListEntries provides a mock function for the type mockPermitDenyService
-func (_mock *mockPermitDenyService) DelDenyListEntries(ctx context.Context, instance *state.SessionInstance, inBody wire.SNAC_0x09_0x08_PermitDenyDelDenyListEntries) error {
-	ret := _mock.Called(ctx, instance, inBody)
-
-	if len(ret) == 0 {
-		panic("no return value specified for DelDenyListEntries")
-	}
-
-	var r0 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, *state.SessionInstance, wire.SNAC_0x09_0x08_PermitDenyDelDenyListEntries) error); ok {
-		r0 = returnFunc(ctx, instance, inBody)
-	} else {
-		r0 = ret.Error(0)
-	}
-	return r0
-}
-
-// mockPermitDenyService_DelDenyListEntries_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DelDenyListEntries'
-type mockPermitDenyService_DelDenyListEntries_Call struct {
-	*mock.Call
-}
-
-// DelDenyListEntries is a helper method to define mock.On call
-//   - ctx context.Context
-//   - instance *state.SessionInstance
-//   - inBody wire.SNAC_0x09_0x08_PermitDenyDelDenyListEntries
-func (_e *mockPermitDenyService_Expecter) DelDenyListEntries(ctx any, instance any, inBody any) *mockPermitDenyService_DelDenyListEntries_Call {
-	return &mockPermitDenyService_DelDenyListEntries_Call{Call: _e.mock.On("DelDenyListEntries", ctx, instance, inBody)}
-}
-
-func (_c *mockPermitDenyService_DelDenyListEntries_Call) Run(run func(ctx context.Context, instance *state.SessionInstance, inBody wire.SNAC_0x09_0x08_PermitDenyDelDenyListEntries)) *mockPermitDenyService_DelDenyListEntries_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 *state.SessionInstance
-		if args[1] != nil {
-			arg1 = args[1].(*state.SessionInstance)
-		}
-		var arg2 wire.SNAC_0x09_0x08_PermitDenyDelDenyListEntries
-		if args[2] != nil {
-			arg2 = args[2].(wire.SNAC_0x09_0x08_PermitDenyDelDenyListEntries)
-		}
-		run(
-			arg0,
-			arg1,
-			arg2,
-		)
-	})
-	return _c
-}
-
-func (_c *mockPermitDenyService_DelDenyListEntries_Call) Return(err error) *mockPermitDenyService_DelDenyListEntries_Call {
-	_c.Call.Return(err)
-	return _c
-}
-
-func (_c *mockPermitDenyService_DelDenyListEntries_Call) RunAndReturn(run func(ctx context.Context, instance *state.SessionInstance, inBody wire.SNAC_0x09_0x08_PermitDenyDelDenyListEntries) error) *mockPermitDenyService_DelDenyListEntries_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
-// DelPermListEntries provides a mock function for the type mockPermitDenyService
-func (_mock *mockPermitDenyService) DelPermListEntries(ctx context.Context, instance *state.SessionInstance, inBody wire.SNAC_0x09_0x06_PermitDenyDelPermListEntries) error {
-	ret := _mock.Called(ctx, instance, inBody)
-
-	if len(ret) == 0 {
-		panic("no return value specified for DelPermListEntries")
-	}
-
-	var r0 error
-	if returnFunc, ok := ret.Get(0).(func(context.Context, *state.SessionInstance, wire.SNAC_0x09_0x06_PermitDenyDelPermListEntries) error); ok {
-		r0 = returnFunc(ctx, instance, inBody)
-	} else {
-		r0 = ret.Error(0)
-	}
-	return r0
-}
-
-// mockPermitDenyService_DelPermListEntries_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DelPermListEntries'
-type mockPermitDenyService_DelPermListEntries_Call struct {
-	*mock.Call
-}
-
-// DelPermListEntries is a helper method to define mock.On call
-//   - ctx context.Context
-//   - instance *state.SessionInstance
-//   - inBody wire.SNAC_0x09_0x06_PermitDenyDelPermListEntries
-func (_e *mockPermitDenyService_Expecter) DelPermListEntries(ctx any, instance any, inBody any) *mockPermitDenyService_DelPermListEntries_Call {
-	return &mockPermitDenyService_DelPermListEntries_Call{Call: _e.mock.On("DelPermListEntries", ctx, instance, inBody)}
-}
-
-func (_c *mockPermitDenyService_DelPermListEntries_Call) Run(run func(ctx context.Context, instance *state.SessionInstance, inBody wire.SNAC_0x09_0x06_PermitDenyDelPermListEntries)) *mockPermitDenyService_DelPermListEntries_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 *state.SessionInstance
-		if args[1] != nil {
-			arg1 = args[1].(*state.SessionInstance)
-		}
-		var arg2 wire.SNAC_0x09_0x06_PermitDenyDelPermListEntries
-		if args[2] != nil {
-			arg2 = args[2].(wire.SNAC_0x09_0x06_PermitDenyDelPermListEntries)
-		}
-		run(
-			arg0,
-			arg1,
-			arg2,
-		)
-	})
-	return _c
-}
-
-func (_c *mockPermitDenyService_DelPermListEntries_Call) Return(err error) *mockPermitDenyService_DelPermListEntries_Call {
-	_c.Call.Return(err)
-	return _c
-}
-
-func (_c *mockPermitDenyService_DelPermListEntries_Call) RunAndReturn(run func(ctx context.Context, instance *state.SessionInstance, inBody wire.SNAC_0x09_0x06_PermitDenyDelPermListEntries) error) *mockPermitDenyService_DelPermListEntries_Call {
-	_c.Call.Return(run)
-	return _c
-}
-
-// RightsQuery provides a mock function for the type mockPermitDenyService
-func (_mock *mockPermitDenyService) RightsQuery(ctx context.Context, inFrame wire.SNACFrame) wire.SNACMessage {
-	ret := _mock.Called(ctx, inFrame)
-
-	if len(ret) == 0 {
-		panic("no return value specified for RightsQuery")
-	}
-
-	var r0 wire.SNACMessage
-	if returnFunc, ok := ret.Get(0).(func(context.Context, wire.SNACFrame) wire.SNACMessage); ok {
-		r0 = returnFunc(ctx, inFrame)
-	} else {
-		r0 = ret.Get(0).(wire.SNACMessage)
-	}
-	return r0
-}
-
-// mockPermitDenyService_RightsQuery_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RightsQuery'
-type mockPermitDenyService_RightsQuery_Call struct {
-	*mock.Call
-}
-
-// RightsQuery is a helper method to define mock.On call
-//   - ctx context.Context
-//   - inFrame wire.SNACFrame
-func (_e *mockPermitDenyService_Expecter) RightsQuery(ctx any, inFrame any) *mockPermitDenyService_RightsQuery_Call {
-	return &mockPermitDenyService_RightsQuery_Call{Call: _e.mock.On("RightsQuery", ctx, inFrame)}
-}
-
-func (_c *mockPermitDenyService_RightsQuery_Call) Run(run func(ctx context.Context, inFrame wire.SNACFrame)) *mockPermitDenyService_RightsQuery_Call {
-	_c.Call.Run(func(args mock.Arguments) {
-		var arg0 context.Context
-		if args[0] != nil {
-			arg0 = args[0].(context.Context)
-		}
-		var arg1 wire.SNACFrame
-		if args[1] != nil {
-			arg1 = args[1].(wire.SNACFrame)
-		}
-		run(
-			arg0,
-			arg1,
-		)
-	})
-	return _c
-}
-
-func (_c *mockPermitDenyService_RightsQuery_Call) Return(sNACMessage wire.SNACMessage) *mockPermitDenyService_RightsQuery_Call {
-	_c.Call.Return(sNACMessage)
-	return _c
-}
-
-func (_c *mockPermitDenyService_RightsQuery_Call) RunAndReturn(run func(ctx context.Context, inFrame wire.SNACFrame) wire.SNACMessage) *mockPermitDenyService_RightsQuery_Call {
-	_c.Call.Return(run)
-	return _c
-}

+ 332 - 0
server/toc/oscar_conn.go

@@ -0,0 +1,332 @@
+package toc
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"io"
+	"log/slog"
+	"net"
+	"sync"
+
+	"github.com/mk6i/open-oscar-server/wire"
+)
+
+// errSignedOff indicates that the OSCAR server refused or ended a connection
+// with a signoff frame.
+var errSignedOff = errors.New("signed off by OSCAR server")
+
+// oscarConn is a TOC user's connection to an OSCAR service, either BOS or a
+// chat room.
+type oscarConn interface {
+	// SendSNAC sends a SNAC to the OSCAR server.
+	SendSNAC(frame wire.SNACFrame, body any) error
+	// Messages returns the SNACs received from the server, replies and
+	// server-initiated messages alike, in arrival order.
+	Messages() <-chan wire.SNACMessage
+	// Closed returns a channel that is closed when the connection closes.
+	Closed() <-chan struct{}
+	// Close closes the connection.
+	Close()
+}
+
+// snacQueueLimit is how many received SNACs a connection holds for the TOC
+// consumer, the same as an OSCAR session's message queue.
+const snacQueueLimit = 1000
+
+// snacQueue is a FIFO of SNACs received from the server. push never blocks,
+// so the connection reader keeps draining the server while the TOC consumer is
+// busy.
+type snacQueue struct {
+	mu    sync.Mutex
+	items []wire.SNACMessage
+	limit int
+	ready chan struct{}
+	out   chan wire.SNACMessage
+}
+
+// newSNACQueue creates a snacQueue that holds up to limit SNACs and delivers
+// until done is closed.
+func newSNACQueue(limit int, done <-chan struct{}) *snacQueue {
+	q := &snacQueue{
+		limit: limit,
+		ready: make(chan struct{}, 1),
+		out:   make(chan wire.SNACMessage),
+	}
+	go q.pump(done)
+	return q
+}
+
+// push queues msg. It returns false without queuing msg if the queue is full.
+func (q *snacQueue) push(msg wire.SNACMessage) bool {
+	q.mu.Lock()
+	if len(q.items) >= q.limit {
+		q.mu.Unlock()
+		return false
+	}
+	q.items = append(q.items, msg)
+	q.mu.Unlock()
+	select {
+	case q.ready <- struct{}{}:
+	default:
+	}
+	return true
+}
+
+func (q *snacQueue) pump(done <-chan struct{}) {
+	for {
+		q.mu.Lock()
+		if len(q.items) == 0 {
+			q.mu.Unlock()
+			select {
+			case <-q.ready:
+				continue
+			case <-done:
+				return
+			}
+		}
+		msg := q.items[0]
+		q.items = q.items[1:]
+		q.mu.Unlock()
+
+		select {
+		case q.out <- msg:
+		case <-done:
+			return
+		}
+	}
+}
+
+// flapConn is an oscarConn over a FLAP connection to the OSCAR server.
+type flapConn struct {
+	conn      net.Conn
+	flapc     *wire.FlapClient
+	logger    *slog.Logger
+	queue     *snacQueue
+	closed    chan struct{}
+	closeOnce sync.Once
+}
+
+// newFLAPConn wraps a FLAP connection that has finished signing on to an
+// OSCAR service and starts reading SNACs from it.
+func newFLAPConn(conn net.Conn, flapc *wire.FlapClient, logger *slog.Logger) *flapConn {
+	c := &flapConn{
+		conn:   conn,
+		flapc:  flapc,
+		logger: logger,
+		closed: make(chan struct{}),
+	}
+	c.queue = newSNACQueue(snacQueueLimit, c.closed)
+	go c.read()
+	return c
+}
+
+func (c *flapConn) read() {
+	defer c.Close()
+	for {
+		frame, err := c.flapc.ReceiveFLAP()
+		if err != nil {
+			return
+		}
+		switch frame.FrameType {
+		case wire.FLAPFrameData:
+			msg, err := decodeSNAC(frame.Payload)
+			if err != nil {
+				c.logger.Debug("unable to decode SNAC from OSCAR server", "err", err.Error())
+				continue
+			}
+			if !c.queue.push(msg) {
+				// like the OSCAR server, disconnect a client that falls too far behind
+				c.logger.Warn("TOC client isn't keeping up with the OSCAR server, disconnecting")
+				return
+			}
+		case wire.FLAPFrameSignoff:
+			return
+		}
+	}
+}
+
+func (c *flapConn) SendSNAC(frame wire.SNACFrame, body any) error {
+	return c.flapc.SendSNAC(frame, body)
+}
+
+func (c *flapConn) Messages() <-chan wire.SNACMessage {
+	return c.queue.out
+}
+
+func (c *flapConn) Closed() <-chan struct{} {
+	return c.closed
+}
+
+func (c *flapConn) Close() {
+	c.closeOnce.Do(func() {
+		close(c.closed)
+		_ = c.conn.Close()
+	})
+}
+
+// clientAddrConn is the server end of an in-memory OSCAR connection. It
+// reports the TOC client's address as its remote address, so that the OSCAR
+// server sees where the user really connects from.
+type clientAddrConn struct {
+	net.Conn
+	clientAddr net.Addr
+}
+
+func (c clientAddrConn) RemoteAddr() net.Addr {
+	return c.clientAddr
+}
+
+// dialOSCAR opens an in-memory connection to the OSCAR server on behalf of
+// the TOC client at clientAddr.
+func (s OSCARProxy) dialOSCAR(clientAddr net.Addr) net.Conn {
+	clientEnd, serverEnd := net.Pipe()
+	go s.OSCARServer.ServeConn(clientAddrConn{Conn: serverEnd, clientAddr: clientAddr}, s.OSCAREndpoint)
+	return clientEnd
+}
+
+// authenticate signs on to the OSCAR auth service with the given signon TLVs
+// and returns the server's reply TLVs, which hold either a login cookie or an
+// error code.
+func (s OSCARProxy) authenticate(clientAddr net.Addr, signonTLVs []wire.TLV) (wire.TLVRestBlock, error) {
+	conn := s.dialOSCAR(clientAddr)
+	defer conn.Close()
+
+	flapc := wire.NewFlapClient(0, conn, conn)
+	if _, err := flapc.ReceiveSignonFrame(); err != nil {
+		return wire.TLVRestBlock{}, fmt.Errorf("ReceiveSignonFrame: %w", err)
+	}
+	if err := flapc.SendSignonFrame(signonTLVs); err != nil {
+		return wire.TLVRestBlock{}, fmt.Errorf("SendSignonFrame: %w", err)
+	}
+
+	frame, err := flapc.ReceiveFLAP()
+	if err != nil {
+		return wire.TLVRestBlock{}, fmt.Errorf("ReceiveFLAP: %w", err)
+	}
+
+	switch frame.FrameType {
+	case wire.FLAPFrameSignoff:
+		block := wire.TLVRestBlock{}
+		if err := wire.UnmarshalBE(&block, bytes.NewReader(frame.Payload)); err != nil {
+			return wire.TLVRestBlock{}, fmt.Errorf("UnmarshalBE: %w", err)
+		}
+		return block, nil
+	case wire.FLAPFrameData:
+		// the server answers with a BUCP login response when it rate limits
+		// an address that recently signed on with BUCP
+		msg, err := decodeSNAC(frame.Payload)
+		if err != nil {
+			return wire.TLVRestBlock{}, fmt.Errorf("decodeSNAC: %w", err)
+		}
+		reply, ok := msg.Body.(wire.SNAC_0x17_0x03_BUCPLoginResponse)
+		if !ok {
+			return wire.TLVRestBlock{}, fmt.Errorf("unexpected login reply %s",
+				wire.SubGroupName(msg.Frame.FoodGroup, msg.Frame.SubGroup))
+		}
+		return reply.TLVRestBlock, nil
+	default:
+		return wire.TLVRestBlock{}, fmt.Errorf("unexpected login reply frame type %d", frame.FrameType)
+	}
+}
+
+// connectService connects to the OSCAR service that loginCookie grants access
+// to, either BOS or a chat room, and waits for the server to report the
+// service online.
+func (s OSCARProxy) connectService(clientAddr net.Addr, loginCookie []byte) (*flapConn, error) {
+	conn := s.dialOSCAR(clientAddr)
+
+	flapc := wire.NewFlapClient(0, conn, conn)
+	if err := func() error {
+		if _, err := flapc.ReceiveSignonFrame(); err != nil {
+			return fmt.Errorf("ReceiveSignonFrame: %w", err)
+		}
+		if err := flapc.SendSignonFrame([]wire.TLV{wire.NewTLVBE(wire.OServiceTLVTagsLoginCookie, loginCookie)}); err != nil {
+			return fmt.Errorf("SendSignonFrame: %w", err)
+		}
+
+		frame, err := flapc.ReceiveFLAP()
+		if err != nil {
+			return fmt.Errorf("ReceiveFLAP: %w", err)
+		}
+		if frame.FrameType == wire.FLAPFrameSignoff {
+			return errSignedOff
+		}
+		msg, err := decodeSNAC(frame.Payload)
+		if err != nil {
+			return fmt.Errorf("decodeSNAC: %w", err)
+		}
+		if _, ok := msg.Body.(wire.SNAC_0x01_0x03_OServiceHostOnline); !ok {
+			return fmt.Errorf("expected host online, got %s",
+				wire.SubGroupName(msg.Frame.FoodGroup, msg.Frame.SubGroup))
+		}
+		return nil
+	}(); err != nil {
+		_ = conn.Close()
+		return nil, err
+	}
+
+	return newFLAPConn(conn, flapc, s.Logger), nil
+}
+
+// snacDecoders unmarshal the bodies of the SNACs a TOC connection handles,
+// keyed by food group and subgroup.
+var snacDecoders = map[[2]uint16]func(r io.Reader) (any, error){
+	{wire.OService, wire.OServiceHostOnline}:       decodeBody[wire.SNAC_0x01_0x03_OServiceHostOnline],
+	{wire.OService, wire.OServiceServiceResponse}:  decodeBody[wire.SNAC_0x01_0x05_OServiceServiceResponse],
+	{wire.OService, wire.OServiceRateParamChange}:  decodeBody[wire.SNAC_0x01_0x0A_OServiceRateParamsChange],
+	{wire.OService, wire.OServiceUserInfoUpdate}:   decodeBody[wire.SNAC_0x01_0x0F_OServiceUserInfoUpdate],
+	{wire.OService, wire.OServiceEvilNotification}: decodeBody[wire.SNAC_0x01_0x10_OServiceEvilNotification],
+	{wire.Locate, wire.LocateUserInfoReply}:        decodeBody[wire.SNAC_0x02_0x06_LocateUserInfoReply],
+	{wire.Buddy, wire.BuddyArrived}:                decodeBody[wire.SNAC_0x03_0x0B_BuddyArrived],
+	{wire.Buddy, wire.BuddyDeparted}:               decodeBody[wire.SNAC_0x03_0x0C_BuddyDeparted],
+	{wire.ICBM, wire.ICBMChannelMsgToClient}:       decodeBody[wire.SNAC_0x04_0x07_ICBMChannelMsgToClient],
+	{wire.ICBM, wire.ICBMEvilReply}:                decodeBody[wire.SNAC_0x04_0x09_ICBMEvilReply],
+	{wire.ICBM, wire.ICBMClientEvent}:              decodeBody[wire.SNAC_0x04_0x14_ICBMClientEvent],
+	{wire.Admin, wire.AdminInfoChangeReply}:        decodeBody[wire.SNAC_0x07_0x05_AdminChangeReply],
+	{wire.ChatNav, wire.ChatNavNavInfo}:            decodeBody[wire.SNAC_0x0D_0x09_ChatNavNavInfo],
+	{wire.Chat, wire.ChatUsersJoined}:              decodeBody[wire.SNAC_0x0E_0x03_ChatUsersJoined],
+	{wire.Chat, wire.ChatUsersLeft}:                decodeBody[wire.SNAC_0x0E_0x04_ChatUsersLeft],
+	{wire.Chat, wire.ChatChannelMsgToClient}:       decodeBody[wire.SNAC_0x0E_0x06_ChatChannelMsgToClient],
+	{wire.Feedbag, wire.FeedbagReply}:              decodeBody[wire.SNAC_0x13_0x06_FeedbagReply],
+	{wire.Feedbag, wire.FeedbagInsertItem}:         decodeBody[wire.SNAC_0x13_0x08_FeedbagInsertItem],
+	{wire.Feedbag, wire.FeedbagUpdateItem}:         decodeBody[wire.SNAC_0x13_0x09_FeedbagUpdateItem],
+	{wire.Feedbag, wire.FeedbagDeleteItem}:         decodeBody[wire.SNAC_0x13_0x0A_FeedbagDeleteItem],
+	{wire.Feedbag, wire.FeedbagStatus}:             decodeBody[wire.SNAC_0x13_0x0E_FeedbagStatus],
+	{wire.BUCP, wire.BUCPLoginResponse}:            decodeBody[wire.SNAC_0x17_0x03_BUCPLoginResponse],
+}
+
+// snacErrorSubGroup is the subgroup of the error SNAC in every food group.
+const snacErrorSubGroup = 0x0001
+
+// decodeSNAC unmarshals a SNAC received from the OSCAR server. A SNAC that
+// TOC doesn't handle is returned with a nil body.
+func decodeSNAC(payload []byte) (wire.SNACMessage, error) {
+	r := bytes.NewReader(payload)
+	msg := wire.SNACMessage{}
+	if err := wire.UnmarshalBE(&msg.Frame, r); err != nil {
+		return msg, fmt.Errorf("unmarshal SNAC frame: %w", err)
+	}
+
+	decode := snacDecoders[[2]uint16{msg.Frame.FoodGroup, msg.Frame.SubGroup}]
+	if msg.Frame.SubGroup == snacErrorSubGroup {
+		decode = decodeBody[wire.SNACError]
+	}
+	if decode == nil {
+		return msg, nil
+	}
+
+	body, err := decode(r)
+	if err != nil {
+		return msg, fmt.Errorf("unmarshal %s: %w",
+			wire.SubGroupName(msg.Frame.FoodGroup, msg.Frame.SubGroup), err)
+	}
+	msg.Body = body
+	return msg, nil
+}
+
+func decodeBody[T any](r io.Reader) (any, error) {
+	var body T
+	err := wire.UnmarshalBE(&body, r)
+	return body, err
+}

+ 203 - 0
server/toc/oscar_conn_test.go

@@ -0,0 +1,203 @@
+package toc
+
+import (
+	"bytes"
+	"log/slog"
+	"net"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+
+	"github.com/mk6i/open-oscar-server/wire"
+)
+
+func TestSNACQueue_deliversInOrderWithoutBlockingProducer(t *testing.T) {
+	done := make(chan struct{})
+	defer close(done)
+	q := newSNACQueue(100, done)
+
+	// nobody is reading yet, so push must not block
+	for i := uint32(1); i <= 100; i++ {
+		assert.True(t, q.push(wire.SNACMessage{Frame: wire.SNACFrame{RequestID: i}}))
+	}
+
+	for i := uint32(1); i <= 100; i++ {
+		select {
+		case msg := <-q.out:
+			assert.Equal(t, i, msg.Frame.RequestID)
+		case <-time.After(time.Second):
+			t.Fatalf("timed out waiting for SNAC %d", i)
+		}
+	}
+}
+
+// marshalSNAC returns the FLAP payload of a SNAC.
+func marshalSNAC(t *testing.T, frame wire.SNACFrame, body any) []byte {
+	buf := &bytes.Buffer{}
+	assert.NoError(t, wire.MarshalBE(frame, buf))
+	assert.NoError(t, wire.MarshalBE(body, buf))
+	return buf.Bytes()
+}
+
+func TestDecodeSNAC(t *testing.T) {
+	cases := []struct {
+		name    string
+		payload func(t *testing.T) []byte
+		want    wire.SNACMessage
+		wantErr bool
+	}{
+		{
+			name: "SNAC TOC handles",
+			payload: func(t *testing.T) []byte {
+				return marshalSNAC(t, wire.SNACFrame{FoodGroup: wire.Buddy, SubGroup: wire.BuddyArrived, RequestID: 7},
+					wire.SNAC_0x03_0x0B_BuddyArrived{TLVUserInfo: wire.TLVUserInfo{ScreenName: "them", WarningLevel: 10}})
+			},
+			want: newSNAC(wire.Buddy, wire.BuddyArrived, 7,
+				wire.SNAC_0x03_0x0B_BuddyArrived{TLVUserInfo: wire.TLVUserInfo{ScreenName: "them", WarningLevel: 10}}),
+		},
+		{
+			name: "relayed feedbag insert",
+			payload: func(t *testing.T) []byte {
+				return marshalSNAC(t, wire.SNACFrame{FoodGroup: wire.Feedbag, SubGroup: wire.FeedbagInsertItem},
+					wire.SNAC_0x13_0x09_FeedbagUpdateItem{Items: []wire.FeedbagItem{{Name: "them"}}})
+			},
+			want: newSNAC(wire.Feedbag, wire.FeedbagInsertItem, 0,
+				wire.SNAC_0x13_0x08_FeedbagInsertItem{Items: []wire.FeedbagItem{{Name: "them"}}}),
+		},
+		{
+			name: "error in any food group",
+			payload: func(t *testing.T) []byte {
+				return marshalSNAC(t, wire.SNACFrame{FoodGroup: wire.ICBM, SubGroup: wire.ICBMErr, RequestID: 3},
+					wire.SNACError{Code: wire.ErrorCodeNotLoggedOn})
+			},
+			want: newSNAC(wire.ICBM, wire.ICBMErr, 3, wire.SNACError{Code: wire.ErrorCodeNotLoggedOn}),
+		},
+		{
+			name: "SNAC TOC doesn't handle has no body",
+			payload: func(t *testing.T) []byte {
+				return marshalSNAC(t, wire.SNACFrame{FoodGroup: wire.Stats, SubGroup: wire.StatsSetMinReportInterval},
+					wire.SNAC_0x0B_0x02_StatsSetMinReportInterval{MinReportInterval: 1})
+			},
+			want: wire.SNACMessage{Frame: wire.SNACFrame{FoodGroup: wire.Stats, SubGroup: wire.StatsSetMinReportInterval}},
+		},
+		{
+			name: "truncated SNAC frame",
+			payload: func(t *testing.T) []byte {
+				return []byte{0x00, 0x03}
+			},
+			wantErr: true,
+		},
+		{
+			name: "truncated SNAC body",
+			payload: func(t *testing.T) []byte {
+				return marshalSNAC(t, wire.SNACFrame{FoodGroup: wire.Buddy, SubGroup: wire.BuddyArrived}, []byte{0x05, 't'})
+			},
+			wantErr: true,
+		},
+	}
+
+	for _, tc := range cases {
+		t.Run(tc.name, func(t *testing.T) {
+			got, err := decodeSNAC(tc.payload(t))
+			if tc.wantErr {
+				assert.Error(t, err)
+				return
+			}
+			assert.NoError(t, err)
+			assert.Equal(t, tc.want, got)
+		})
+	}
+}
+
+func TestFLAPConn(t *testing.T) {
+	clientEnd, serverEnd := net.Pipe()
+	server := wire.NewFlapClient(0, serverEnd, serverEnd)
+	conn := newFLAPConn(clientEnd, wire.NewFlapClient(0, clientEnd, clientEnd), slog.Default())
+
+	// SNACs sent to the server arrive intact
+	go func() {
+		assert.NoError(t, conn.SendSNAC(wire.SNACFrame{FoodGroup: wire.Buddy, SubGroup: wire.BuddyAddBuddies, RequestID: 1},
+			wire.SNAC_0x03_0x04_BuddyAddBuddies{}))
+	}()
+	frame := wire.SNACFrame{}
+	assert.NoError(t, server.ReceiveSNAC(&frame, &wire.SNAC_0x03_0x04_BuddyAddBuddies{}))
+	assert.Equal(t, wire.SNACFrame{FoodGroup: wire.Buddy, SubGroup: wire.BuddyAddBuddies, RequestID: 1}, frame)
+
+	// SNACs from the server are decoded and delivered in order
+	assert.NoError(t, server.SendSNAC(wire.SNACFrame{FoodGroup: wire.Buddy, SubGroup: wire.BuddyDeparted},
+		wire.SNAC_0x03_0x0C_BuddyDeparted{TLVUserInfo: wire.TLVUserInfo{ScreenName: "first"}}))
+	assert.NoError(t, server.SendSNAC(wire.SNACFrame{FoodGroup: wire.Buddy, SubGroup: wire.BuddyDeparted},
+		wire.SNAC_0x03_0x0C_BuddyDeparted{TLVUserInfo: wire.TLVUserInfo{ScreenName: "second"}}))
+	for _, want := range []string{"first", "second"} {
+		msg := <-conn.Messages()
+		assert.Equal(t, want, msg.Body.(wire.SNAC_0x03_0x0C_BuddyDeparted).ScreenName)
+	}
+
+	// a signoff from the server closes the connection
+	assert.NoError(t, server.NewSignoff(wire.TLVRestBlock{}))
+	select {
+	case <-conn.Closed():
+	case <-time.After(time.Second):
+		t.Fatal("connection didn't close after signoff")
+	}
+	_, err := server.ReceiveFLAP()
+	assert.Error(t, err)
+}
+
+func TestSNACQueue_full(t *testing.T) {
+	done := make(chan struct{})
+	defer close(done)
+	q := newSNACQueue(2, done)
+
+	// the pump holds one SNAC while it waits for a reader
+	assert.True(t, q.push(wire.SNACMessage{Frame: wire.SNACFrame{RequestID: 1}}))
+	assert.Eventually(t, func() bool {
+		q.mu.Lock()
+		defer q.mu.Unlock()
+		return len(q.items) == 0
+	}, time.Second, time.Millisecond)
+
+	assert.True(t, q.push(wire.SNACMessage{Frame: wire.SNACFrame{RequestID: 2}}))
+	assert.True(t, q.push(wire.SNACMessage{Frame: wire.SNACFrame{RequestID: 3}}))
+	assert.False(t, q.push(wire.SNACMessage{Frame: wire.SNACFrame{RequestID: 4}}))
+
+	for i := uint32(1); i <= 3; i++ {
+		assert.Equal(t, i, (<-q.out).Frame.RequestID)
+	}
+}
+
+func TestFLAPConn_closesWhenConsumerFallsBehind(t *testing.T) {
+	clientEnd, serverEnd := net.Pipe()
+	server := wire.NewFlapClient(0, serverEnd, serverEnd)
+	conn := newFLAPConn(clientEnd, wire.NewFlapClient(0, clientEnd, clientEnd), slog.Default())
+
+	// nobody reads conn.Messages(), so the queue fills up
+	go func() {
+		for {
+			err := server.SendSNAC(wire.SNACFrame{FoodGroup: wire.Buddy, SubGroup: wire.BuddyDeparted},
+				wire.SNAC_0x03_0x0C_BuddyDeparted{TLVUserInfo: wire.TLVUserInfo{ScreenName: "them"}})
+			if err != nil {
+				return
+			}
+		}
+	}()
+
+	select {
+	case <-conn.Closed():
+	case <-time.After(5 * time.Second):
+		t.Fatal("connection didn't close after its queue filled up")
+	}
+}
+
+func TestFLAPConn_Close(t *testing.T) {
+	clientEnd, serverEnd := net.Pipe()
+	conn := newFLAPConn(clientEnd, wire.NewFlapClient(0, clientEnd, clientEnd), slog.Default())
+
+	conn.Close()
+	conn.Close()
+
+	_, err := wire.NewFlapClient(0, serverEnd, serverEnd).ReceiveFLAP()
+	assert.Error(t, err)
+	<-conn.Closed()
+}

+ 29 - 63
server/toc/server.go

@@ -10,7 +10,6 @@ import (
 	"log/slog"
 	"net"
 	"net/http"
-	"net/netip"
 	"sync"
 	"syscall"
 	"time"
@@ -19,7 +18,6 @@ import (
 	"golang.org/x/sync/errgroup"
 	"golang.org/x/time/rate"
 
-	"github.com/mk6i/open-oscar-server/state"
 	"github.com/mk6i/open-oscar-server/wire"
 )
 
@@ -37,7 +35,6 @@ var (
 type contextKey string
 
 const (
-	ipContextKey         contextKey = "ip"
 	screenNameContextKey contextKey = "screenName"
 )
 
@@ -133,24 +130,18 @@ func NewServer(
 	endpointCfg []string,
 	logger *slog.Logger,
 	BOSProxy OSCARProxy,
-	ipRateLimiter *IPRateLimiter,
-	recalcWarning func(ctx context.Context, instance *state.SessionInstance) error,
-	lowerWarnLevel func(ctx context.Context, instance *state.SessionInstance),
 ) *Server {
 
 	ctx, cancel := context.WithCancel(context.Background())
 
 	s := &Server{
-		bosProxy:           BOSProxy,
-		conns:              make(map[net.Conn]struct{}),
-		endpointCfg:        endpointCfg,
-		logger:             logger,
-		loginIPRateLimiter: ipRateLimiter,
-		recalcWarning:      recalcWarning,
-		lowerWarnLevel:     lowerWarnLevel,
-		servers:            make([]*http.Server, 0, len(endpointCfg)),
-		shutdownCancel:     cancel,
-		shutdownCtx:        ctx,
+		bosProxy:       BOSProxy,
+		conns:          make(map[net.Conn]struct{}),
+		endpointCfg:    endpointCfg,
+		logger:         logger,
+		servers:        make([]*http.Server, 0, len(endpointCfg)),
+		shutdownCancel: cancel,
+		shutdownCtx:    ctx,
 	}
 
 	for range endpointCfg {
@@ -169,11 +160,8 @@ func NewServer(
 // TOC/FLAP requests. It acts as a gateway, forwarding all TOC requests
 // to the OSCAR server for processing.
 type Server struct {
-	bosProxy           OSCARProxy
-	logger             *slog.Logger
-	loginIPRateLimiter *IPRateLimiter
-	recalcWarning      func(ctx context.Context, instance *state.SessionInstance) error
-	lowerWarnLevel     func(ctx context.Context, instance *state.SessionInstance)
+	bosProxy OSCARProxy
+	logger   *slog.Logger
 
 	endpointCfg []string
 	listeners   []net.Listener
@@ -346,49 +334,22 @@ func (s *Server) dispatchFLAP(ctx context.Context, conn net.Conn) error {
 	}
 	defer closeConn()
 
-	ctx = context.WithValue(ctx, ipContextKey, conn.RemoteAddr().String())
-
 	clientFlap, err := s.initFLAP(ctx, conn)
 	if err != nil {
 		return err
 	}
 
-	ip, _, err := net.SplitHostPort(conn.RemoteAddr().String())
-	if err != nil {
-		s.logger.Error("failed to parse remote address", "err", err.Error())
-		return err
-	}
-
-	if ok := s.loginIPRateLimiter.Allow(ip); !ok {
-		s.logger.InfoContext(ctx, "user rate limited at login, dropping connection")
-		if err := clientFlap.SendDataFrame([]byte("ERROR:983")); err != nil {
-			return fmt.Errorf("clientFlap.SendDataFrame: %w", err)
-		}
-		return nil
-	}
-
-	chatRegistry := NewChatRegistry()
-
-	sessBOS, err := s.login(ctx, clientFlap, chatRegistry)
+	me, err := s.login(ctx, clientFlap, conn.RemoteAddr())
 	if err != nil {
 		return fmt.Errorf("s.login: %w", err)
 	}
-	if sessBOS == nil {
-		return nil // user not found
+	if me == nil {
+		return nil // signon failed
 	}
 
-	ctx = context.WithValue(ctx, screenNameContextKey, sessBOS.IdentScreenName())
-
-	remoteAddr, ok := ctx.Value(ipContextKey).(string)
-	if ok {
-		ip, err := netip.ParseAddrPort(remoteAddr)
-		if err != nil {
-			return errors.New("unable to parse ip addr")
-		}
-		sessBOS.SetRemoteAddr(&ip)
-	}
+	ctx = context.WithValue(ctx, screenNameContextKey, me.IdentScreenName())
 
-	return s.handleTOCRequest(ctx, closeConn, sessBOS, chatRegistry, clientFlap)
+	return s.handleTOCRequest(ctx, closeConn, me, clientFlap)
 }
 
 // handleTOCRequest processes incoming TOC requests and coordinates their handling.
@@ -402,8 +363,7 @@ func (s *Server) dispatchFLAP(ctx context.Context, conn net.Conn) error {
 func (s *Server) handleTOCRequest(
 	ctx context.Context,
 	closeConn func(),
-	sessBOS *state.SessionInstance,
-	chatRegistry *ChatRegistry,
+	me *tocConn,
 	clientFlap *wire.FlapClient,
 ) error {
 	// TOC response queue
@@ -411,15 +371,18 @@ func (s *Server) handleTOCRequest(
 
 	g, ctx := errgroup.WithContext(ctx)
 
+	me.toCh = msgCh
+	me.goAsync = g.Go
+
 	// process TOC client requests and enqueue TOC server responses
 	g.Go(func() error {
-		err := s.runClientCommands(ctx, g.Go, sessBOS, chatRegistry, clientFlap, msgCh)
+		err := s.runClientCommands(ctx, me, clientFlap)
 		return errors.Join(err, errClientReq)
 	})
 
 	// translate OSCAR server responses to TOC responses and enqueue them
 	g.Go(func() error {
-		err := s.bosProxy.RecvBOS(ctx, sessBOS, chatRegistry, msgCh)
+		err := s.bosProxy.RecvBOS(ctx, me)
 		closeConn() // unblock runClientCommands
 		return errors.Join(err, errTOCProcessing)
 	})
@@ -434,7 +397,7 @@ func (s *Server) handleTOCRequest(
 	return g.Wait()
 }
 
-func (s *Server) runClientCommands(ctx context.Context, doAsync func(f func() error), sessBOS *state.SessionInstance, chatRegistry *ChatRegistry, clientFlap *wire.FlapClient, toCh chan<- []string) error {
+func (s *Server) runClientCommands(ctx context.Context, me *tocConn, clientFlap *wire.FlapClient) error {
 	for {
 		clientFrame, err := clientFlap.ReceiveFLAP()
 		if err != nil {
@@ -456,10 +419,10 @@ func (s *Server) runClientCommands(ctx context.Context, doAsync func(f func() er
 				return errors.New("TOC command exceeds maximum length (2048)")
 			}
 
-			msg := s.bosProxy.RecvClientCmd(ctx, sessBOS, chatRegistry, clientFrame.Payload, toCh, doAsync)
+			msg := s.bosProxy.RecvClientCmd(ctx, me, clientFrame.Payload)
 			if len(msg) > 0 {
 				select {
-				case toCh <- msg:
+				case me.toCh <- msg:
 				case <-ctx.Done():
 					return nil
 				}
@@ -495,7 +458,7 @@ func (s *Server) sendToClient(ctx context.Context, toClient <-chan []string, cli
 	}
 }
 
-func (s *Server) login(ctx context.Context, clientFlap *wire.FlapClient, chatRegistry *ChatRegistry) (*state.SessionInstance, error) {
+func (s *Server) login(ctx context.Context, clientFlap *wire.FlapClient, clientAddr net.Addr) (*tocConn, error) {
 	clientFrame, err := clientFlap.ReceiveFLAP()
 	if err != nil {
 		if errors.Is(err, io.EOF) {
@@ -504,15 +467,18 @@ func (s *Server) login(ctx context.Context, clientFlap *wire.FlapClient, chatReg
 		return nil, fmt.Errorf("clientFlap.ReceiveFLAP: %w", err)
 	}
 
-	sessBOS, reply := s.bosProxy.Signon(ctx, clientFrame.Payload, s.recalcWarning, s.lowerWarnLevel, chatRegistry)
+	me, reply := s.bosProxy.Signon(ctx, clientFrame.Payload, clientAddr)
 
 	for _, m := range reply {
 		if err := clientFlap.SendDataFrame([]byte(m)); err != nil {
+			if me != nil {
+				me.bos.Close()
+			}
 			return nil, fmt.Errorf("clientFlap.SendDataFrame: %w", err)
 		}
 	}
 
-	return sessBOS, nil
+	return me, nil
 }
 
 // initFLAP sets up a new FLAP connection. It returns a flap client if the

+ 23 - 32
server/toc/server_test.go

@@ -8,7 +8,6 @@ import (
 	"sync"
 	"testing"
 
-	"github.com/mk6i/open-oscar-server/state"
 	"github.com/mk6i/open-oscar-server/wire"
 
 	"github.com/stretchr/testify/assert"
@@ -24,10 +23,8 @@ func TestServer_handleTOCRequest_serverShutdown(t *testing.T) {
 	go func() {
 		defer wg.Done()
 		sv := Server{
-			bosProxy:       testOSCARProxy(t),
-			logger:         slog.Default(),
-			recalcWarning:  func(ctx context.Context, instance *state.SessionInstance) error { return nil },
-			lowerWarnLevel: func(ctx context.Context, instance *state.SessionInstance) {},
+			bosProxy: testOSCARProxy(t),
+			logger:   slog.Default(),
 		}
 
 		serverReader, _ := io.Pipe()
@@ -36,8 +33,8 @@ func TestServer_handleTOCRequest_serverShutdown(t *testing.T) {
 		closeConn := func() {
 			_ = serverReader.Close()
 		}
-		sess := newTestSession("me")
-		err := sv.handleTOCRequest(ctx, closeConn, sess, NewChatRegistry(), fc)
+		me, _ := newTestTOCConn(newTestSession("me"))
+		err := sv.handleTOCRequest(ctx, closeConn, me, fc)
 		assert.True(t, errors.Is(err, errTOCProcessing) || errors.Is(err, errServerWrite))
 	}()
 
@@ -60,16 +57,14 @@ func TestServer_handleTOCRequest_clientReadDisconnect(t *testing.T) {
 		closeConn := func() {
 			_ = serverReader.Close()
 		}
-		sess := newTestSession("me")
+		me, _ := newTestTOCConn(newTestSession("me"))
 		fc := wire.NewFlapClient(0, serverReader, nil)
 
 		sv := Server{
-			bosProxy:       testOSCARProxy(t),
-			logger:         slog.Default(),
-			recalcWarning:  func(ctx context.Context, instance *state.SessionInstance) error { return nil },
-			lowerWarnLevel: func(ctx context.Context, instance *state.SessionInstance) {},
+			bosProxy: testOSCARProxy(t),
+			logger:   slog.Default(),
 		}
-		err := sv.handleTOCRequest(context.Background(), closeConn, sess, NewChatRegistry(), fc)
+		err := sv.handleTOCRequest(context.Background(), closeConn, me, fc)
 		assert.ErrorIs(t, err, errClientReq)
 		assert.ErrorIs(t, err, io.ErrClosedPipe)
 	}()
@@ -86,7 +81,7 @@ func TestServer_handleTOCRequest_sessClose(t *testing.T) {
 	wg := sync.WaitGroup{}
 	wg.Add(1)
 
-	sess := newTestSession("me")
+	me, bos := newTestTOCConn(newTestSession("me"))
 
 	go func() {
 		defer wg.Done()
@@ -99,18 +94,17 @@ func TestServer_handleTOCRequest_sessClose(t *testing.T) {
 		}
 
 		sv := Server{
-			bosProxy:       testOSCARProxy(t),
-			logger:         slog.Default(),
-			recalcWarning:  func(ctx context.Context, instance *state.SessionInstance) error { return nil },
-			lowerWarnLevel: func(ctx context.Context, instance *state.SessionInstance) {},
+			bosProxy: testOSCARProxy(t),
+			logger:   slog.Default(),
 		}
-		err := sv.handleTOCRequest(context.Background(), closeConn, sess, NewChatRegistry(), fc)
+		err := sv.handleTOCRequest(context.Background(), closeConn, me, fc)
 		assert.ErrorIs(t, err, errTOCProcessing)
 		assert.ErrorIs(t, err, errDisconnect)
 	}()
 
-	// close the session, simulating another client login kicking this session
-	sess.CloseInstance()
+	// close the BOS connection, simulating another client login kicking this
+	// session
+	bos.Close()
 
 	// wait for handleTOCRequest to return
 	wg.Wait()
@@ -129,16 +123,14 @@ func TestServer_handleTOCRequest_replyFailure(t *testing.T) {
 		closeConn := func() {
 			_ = serverReader.Close()
 		}
-		sess := newTestSession("me")
+		me, _ := newTestTOCConn(newTestSession("me"))
 		fc := wire.NewFlapClient(0, serverReader, serverWriter)
 
 		sv := Server{
-			bosProxy:       testOSCARProxy(t),
-			logger:         slog.Default(),
-			recalcWarning:  func(ctx context.Context, instance *state.SessionInstance) error { return nil },
-			lowerWarnLevel: func(ctx context.Context, instance *state.SessionInstance) {},
+			bosProxy: testOSCARProxy(t),
+			logger:   slog.Default(),
 		}
-		err := sv.handleTOCRequest(context.Background(), closeConn, sess, NewChatRegistry(), fc)
+		err := sv.handleTOCRequest(context.Background(), closeConn, me, fc)
 		assert.ErrorIs(t, err, errServerWrite)
 		assert.ErrorIs(t, err, io.ErrClosedPipe)
 	}()
@@ -172,12 +164,11 @@ func TestServer_handleTOCRequest_happyPath(t *testing.T) {
 		}
 		fc := wire.NewFlapClient(0, serverReader, serverWriter)
 		sv := Server{
-			bosProxy:       testOSCARProxy(t),
-			logger:         slog.Default(),
-			recalcWarning:  func(ctx context.Context, instance *state.SessionInstance) error { return nil },
-			lowerWarnLevel: func(ctx context.Context, instance *state.SessionInstance) {},
+			bosProxy: testOSCARProxy(t),
+			logger:   slog.Default(),
 		}
-		err := sv.handleTOCRequest(context.Background(), closeConn, newTestSession("me"), NewChatRegistry(), fc)
+		me, _ := newTestTOCConn(newTestSession("me"))
+		err := sv.handleTOCRequest(context.Background(), closeConn, me, fc)
 		assert.ErrorIs(t, err, errClientReq)
 		assert.ErrorIs(t, err, io.ErrClosedPipe)
 	}()

+ 85 - 0
server/toc/snac_cache.go

@@ -0,0 +1,85 @@
+package toc
+
+import (
+	"sync"
+	"time"
+
+	"github.com/mk6i/open-oscar-server/wire"
+)
+
+// snacCacheTTL is how long a sent SNAC waits for a reply before it is dropped
+// from the cache. Requests the server drops, such as rate-limited ones, never
+// get a reply.
+const snacCacheTTL = 60 * time.Second
+
+// snacCache tracks the SNACs a TOC connection sends to the OSCAR server by
+// request ID, so that a reply can be matched to the request that produced it.
+type snacCache struct {
+	mu      sync.Mutex
+	lastID  uint32
+	entries map[uint32]sentSNAC
+	timeNow func() time.Time
+}
+
+// sentSNAC is a SNAC request awaiting a reply.
+type sentSNAC struct {
+	// frame is the request's SNAC frame.
+	frame wire.SNACFrame
+	// data holds whatever the reply handler needs to know about the request.
+	data   any
+	sentAt time.Time
+}
+
+func newSNACCache() *snacCache {
+	return &snacCache{
+		entries: make(map[uint32]sentSNAC),
+		timeNow: time.Now,
+	}
+}
+
+// add assigns the next request ID to frame and records the request along with
+// data. Entries older than snacCacheTTL are swept out.
+func (c *snacCache) add(frame *wire.SNACFrame, data any) {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
+	now := c.timeNow()
+	for id, entry := range c.entries {
+		if now.Sub(entry.sentAt) > snacCacheTTL {
+			delete(c.entries, id)
+		}
+	}
+
+	c.lastID++
+	if c.lastID >= wire.ReqIDFromServer {
+		c.lastID = 1
+	}
+	frame.RequestID = c.lastID
+	c.entries[frame.RequestID] = sentSNAC{
+		frame:  *frame,
+		data:   data,
+		sentAt: now,
+	}
+}
+
+// take removes and returns the request that reply answers. A reply only
+// matches a request in the same food group, so that a relayed SNAC carrying
+// another user's request ID is not mistaken for a reply.
+func (c *snacCache) take(reply wire.SNACFrame) (sentSNAC, bool) {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+
+	entry, ok := c.entries[reply.RequestID]
+	if !ok || entry.frame.FoodGroup != reply.FoodGroup {
+		return sentSNAC{}, false
+	}
+	delete(c.entries, reply.RequestID)
+	return entry, true
+}
+
+// remove drops the request with the given ID.
+func (c *snacCache) remove(requestID uint32) {
+	c.mu.Lock()
+	defer c.mu.Unlock()
+	delete(c.entries, requestID)
+}

+ 89 - 0
server/toc/snac_cache_test.go

@@ -0,0 +1,89 @@
+package toc
+
+import (
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+
+	"github.com/mk6i/open-oscar-server/wire"
+)
+
+func TestSNACCache_addAndTake(t *testing.T) {
+	c := newSNACCache()
+
+	first := wire.SNACFrame{FoodGroup: wire.Locate, SubGroup: wire.LocateUserInfoQuery}
+	c.add(&first, getStatusReqCtx{screenName: "them"})
+	second := wire.SNACFrame{FoodGroup: wire.ICBM, SubGroup: wire.ICBMEvilRequest}
+	c.add(&second, evilReqCtx{})
+
+	assert.Equal(t, uint32(1), first.RequestID)
+	assert.Equal(t, uint32(2), second.RequestID)
+
+	req, ok := c.take(wire.SNACFrame{FoodGroup: wire.Locate, SubGroup: wire.LocateUserInfoReply, RequestID: 1})
+	assert.True(t, ok)
+	assert.Equal(t, first, req.frame)
+	assert.Equal(t, getStatusReqCtx{screenName: "them"}, req.data)
+
+	// an entry is removed once taken
+	_, ok = c.take(wire.SNACFrame{FoodGroup: wire.Locate, RequestID: 1})
+	assert.False(t, ok)
+
+	req, ok = c.take(wire.SNACFrame{FoodGroup: wire.ICBM, SubGroup: wire.ICBMErr, RequestID: 2})
+	assert.True(t, ok)
+	assert.Equal(t, evilReqCtx{}, req.data)
+}
+
+func TestSNACCache_takeRequiresMatchingFoodGroup(t *testing.T) {
+	c := newSNACCache()
+
+	frame := wire.SNACFrame{FoodGroup: wire.Locate, SubGroup: wire.LocateUserInfoQuery}
+	c.add(&frame, getStatusReqCtx{screenName: "them"})
+
+	// a relayed SNAC that happens to carry the same request ID is not a reply
+	_, ok := c.take(wire.SNACFrame{FoodGroup: wire.ICBM, SubGroup: wire.ICBMClientEvent, RequestID: 1})
+	assert.False(t, ok)
+
+	// the real reply still finds the request
+	_, ok = c.take(wire.SNACFrame{FoodGroup: wire.Locate, SubGroup: wire.LocateUserInfoReply, RequestID: 1})
+	assert.True(t, ok)
+}
+
+func TestSNACCache_remove(t *testing.T) {
+	c := newSNACCache()
+
+	frame := wire.SNACFrame{FoodGroup: wire.Locate, SubGroup: wire.LocateUserInfoQuery}
+	c.add(&frame, nil)
+	c.remove(frame.RequestID)
+
+	_, ok := c.take(wire.SNACFrame{FoodGroup: wire.Locate, RequestID: frame.RequestID})
+	assert.False(t, ok)
+}
+
+func TestSNACCache_sweepsExpiredEntries(t *testing.T) {
+	now := time.Now()
+	c := newSNACCache()
+	c.timeNow = func() time.Time { return now }
+
+	old := wire.SNACFrame{FoodGroup: wire.ICBM, SubGroup: wire.ICBMChannelMsgToHost}
+	c.add(&old, nil)
+
+	now = now.Add(snacCacheTTL + time.Second)
+	fresh := wire.SNACFrame{FoodGroup: wire.ICBM, SubGroup: wire.ICBMChannelMsgToHost}
+	c.add(&fresh, nil)
+
+	_, ok := c.take(wire.SNACFrame{FoodGroup: wire.ICBM, RequestID: old.RequestID})
+	assert.False(t, ok)
+	_, ok = c.take(wire.SNACFrame{FoodGroup: wire.ICBM, RequestID: fresh.RequestID})
+	assert.True(t, ok)
+}
+
+func TestSNACCache_requestIDWrapsBeforeServerRange(t *testing.T) {
+	c := newSNACCache()
+	c.lastID = wire.ReqIDFromServer - 1
+
+	frame := wire.SNACFrame{FoodGroup: wire.ICBM}
+	c.add(&frame, nil)
+
+	assert.Equal(t, uint32(1), frame.RequestID)
+}

+ 324 - 0
server/toc/toc_conn.go

@@ -0,0 +1,324 @@
+package toc
+
+import (
+	"net"
+	"slices"
+	"sync"
+	"time"
+
+	"golang.org/x/time/rate"
+
+	"github.com/mk6i/open-oscar-server/state"
+	"github.com/mk6i/open-oscar-server/wire"
+)
+
+// tocConn holds the state of one TOC client connection: its connections to
+// OSCAR services, the chat rooms it's in, its in-flight SNAC requests, and the
+// TOC protocol version it speaks.
+type tocConn struct {
+	// bos is the connection to the BOS service.
+	bos oscarConn
+	// chats tracks the chat rooms the user has been invited to or joined.
+	chats *ChatRegistry
+	// cache matches SNAC replies to the requests that produced them.
+	cache *snacCache
+	// ident is the user's screen name.
+	ident state.IdentScreenName
+	// isTOC2 is true for TOC2 clients.
+	isTOC2 bool
+	// supportsMsgEnc is true for TOC2 clients that signed on with toc2_login.
+	supportsMsgEnc bool
+	// toCh queues TOC responses for the client.
+	toCh chan<- []string
+	// goAsync runs f for the lifetime of the TOC connection.
+	goAsync func(f func() error)
+	// clientAddr is the TOC client's address, which the OSCAR server sees as
+	// the user's address.
+	clientAddr net.Addr
+	// limiter rate limits the commands that TOC handles without the OSCAR
+	// server. The OSCAR server rate limits everything else.
+	limiter *rate.Limiter
+	// backlog holds BOS SNACs that arrived during signon, before RecvBOS
+	// started.
+	backlog []wire.SNACMessage
+	// feedbag is the TOC2 user's buddy list.
+	feedbag *feedbagMirror
+}
+
+// newTOCConn creates a TOC connection for the signed-on user ident whose BOS
+// connection is bos.
+func newTOCConn(bos oscarConn, ident state.IdentScreenName, clientAddr net.Addr) *tocConn {
+	return &tocConn{
+		bos:        bos,
+		chats:      NewChatRegistry(),
+		cache:      newSNACCache(),
+		ident:      ident,
+		clientAddr: clientAddr,
+		limiter:    rate.NewLimiter(rate.Every(1500*time.Millisecond), 5),
+		feedbag:    &feedbagMirror{},
+	}
+}
+
+// IdentScreenName returns the user's screen name.
+func (c *tocConn) IdentScreenName() state.IdentScreenName {
+	return c.ident
+}
+
+// IsTOC2 returns true when the client speaks TOC2, with or without encoded
+// messaging.
+func (c *tocConn) IsTOC2() bool {
+	return c.isTOC2
+}
+
+// SupportsTOC2MsgEnc returns true when the client signed on with toc2_login,
+// which supports encoded messaging.
+func (c *tocConn) SupportsTOC2MsgEnc() bool {
+	return c.supportsMsgEnc
+}
+
+// NewChatRegistry creates a new ChatRegistry instance.
+func NewChatRegistry() *ChatRegistry {
+	return &ChatRegistry{
+		lookup: make(map[int]wire.ICBMRoomInfo),
+		conns:  make(map[int]oscarConn),
+	}
+}
+
+// ChatRegistry manages the chat rooms that a user is connected to during a TOC
+// session. It maps chat room identifiers to room metadata and to the
+// connection for each joined room.
+//
+// This struct provides thread-safe operations for adding, retrieving, and
+// managing chat room metadata and associated connections.
+type ChatRegistry struct {
+	lookup map[int]wire.ICBMRoomInfo // Maps chat room IDs to their metadata.
+	conns  map[int]oscarConn         // Tracks chat room connections by chat room ID.
+	nextID int                       // Incremental identifier for newly added chat rooms.
+	m      sync.RWMutex              // Synchronization primitive for concurrent access.
+}
+
+// Add registers metadata for a newly joined chat room and returns a unique
+// identifier for it. If the room is already registered, it returns the existing ID.
+func (c *ChatRegistry) Add(room wire.ICBMRoomInfo) int {
+	c.m.Lock()
+	defer c.m.Unlock()
+	for chatID, r := range c.lookup {
+		if r == room {
+			return chatID
+		}
+	}
+	id := c.nextID
+	c.lookup[id] = room
+	c.nextID++
+	return id
+}
+
+// LookupRoom retrieves metadata for the chat room registered with chatID.
+// It returns the room metadata and a boolean indicating whether the chat ID
+// was found.
+func (c *ChatRegistry) LookupRoom(chatID int) (wire.ICBMRoomInfo, bool) {
+	c.m.RLock()
+	defer c.m.RUnlock()
+	room, found := c.lookup[chatID]
+	return room, found
+}
+
+// RegisterConn associates a chat room connection with a chat ID. If a
+// connection is already registered for the given chat ID, it will be
+// overwritten.
+func (c *ChatRegistry) RegisterConn(chatID int, conn oscarConn) {
+	c.m.Lock()
+	defer c.m.Unlock()
+	c.conns[chatID] = conn
+}
+
+// RetrieveConn retrieves the chat room connection associated with the given
+// chat ID. If no connection is registered for the chat ID, it returns nil.
+func (c *ChatRegistry) RetrieveConn(chatID int) oscarConn {
+	c.m.RLock()
+	defer c.m.RUnlock()
+	return c.conns[chatID]
+}
+
+// RemoveConn removes a chat room connection.
+func (c *ChatRegistry) RemoveConn(chatID int) {
+	c.m.Lock()
+	defer c.m.Unlock()
+	delete(c.conns, chatID)
+}
+
+// feedbagMirror is TOC's copy of the user's feedbag (server-side buddy list).
+// Iit holds the feedbag as the server last confirmed it plus the changes TOC
+// sent that the server hasn't confirmed yet. Commands work from the
+// feedbag as it will be once those changes land, and only confirmed changes
+// become part of the confirmed feedbag.
+type feedbagMirror struct {
+	// sendMu keeps changes and reloads in the order they're sent.
+	sendMu sync.Mutex
+
+	mu        sync.Mutex
+	confirmed []wire.FeedbagItem
+	pending   []feedbagChange
+	lastSeq   uint64
+}
+
+// feedbagChange is a feedbag change sent to the server and not yet
+// confirmed.
+type feedbagChange struct {
+	seq       uint64
+	requestID uint32
+	del       bool
+	items     []wire.FeedbagItem
+}
+
+// snapshot returns the feedbag with all pending changes applied.
+func (m *feedbagMirror) snapshot() []wire.FeedbagItem {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+	items := append([]wire.FeedbagItem(nil), m.confirmed...)
+	for _, change := range m.pending {
+		items = applyFeedbagChange(items, change.del, change.items)
+	}
+	return cloneFeedbagItems(items)
+}
+
+// reset replaces the confirmed feedbag with items and forgets pending
+// changes.
+func (m *feedbagMirror) reset(items []wire.FeedbagItem) {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+	m.confirmed = cloneFeedbagItems(items)
+	m.pending = nil
+}
+
+// sent records a change TOC is sending as request requestID. del is true
+// when the change deletes items.
+func (m *feedbagMirror) sent(requestID uint32, del bool, items []wire.FeedbagItem) {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+	m.lastSeq++
+	m.pending = append(m.pending, feedbagChange{
+		seq:       m.lastSeq,
+		requestID: requestID,
+		del:       del,
+		items:     cloneFeedbagItems(items),
+	})
+}
+
+// confirm applies the items of change requestID that the server accepted,
+// according to results, and forgets the change.
+func (m *feedbagMirror) confirm(requestID uint32, results []uint16) {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+	i := m.indexOf(requestID)
+	if i < 0 {
+		return
+	}
+	change := m.pending[i]
+	var accepted []wire.FeedbagItem
+	for j, item := range change.items {
+		if j < len(results) && results[j] == 0 {
+			accepted = append(accepted, item)
+		}
+	}
+	m.confirmed = applyFeedbagChange(m.confirmed, change.del, accepted)
+	m.pending = append(m.pending[:i], m.pending[i+1:]...)
+}
+
+// discard forgets change requestID, which the server rejected.
+func (m *feedbagMirror) discard(requestID uint32) {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+	if i := m.indexOf(requestID); i >= 0 {
+		m.pending = append(m.pending[:i], m.pending[i+1:]...)
+	}
+}
+
+// hasPending returns true if any change is awaiting confirmation.
+func (m *feedbagMirror) hasPending() bool {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+	return len(m.pending) > 0
+}
+
+// mark returns a marker for the changes sent so far, for reload.
+func (m *feedbagMirror) mark() uint64 {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+	return m.lastSeq
+}
+
+// reload replaces the confirmed feedbag with items, the server's feedbag as of
+// a query sent after the changes up to marker. Those changes are forgotten,
+// since the server either applied them or dropped them. Changes sent after the
+// query stay pending.
+func (m *feedbagMirror) reload(marker uint64, items []wire.FeedbagItem) {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+	m.confirmed = cloneFeedbagItems(items)
+	var pending []feedbagChange
+	for _, change := range m.pending {
+		if change.seq > marker {
+			pending = append(pending, change)
+		}
+	}
+	m.pending = pending
+}
+
+// applyRemote applies a change made at another of the user's locations. del
+// is true when the change deletes items.
+func (m *feedbagMirror) applyRemote(del bool, items []wire.FeedbagItem) {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+	m.confirmed = applyFeedbagChange(m.confirmed, del, cloneFeedbagItems(items))
+}
+
+// groupNames returns the name of each group by group ID, with all pending
+// changes applied.
+func (m *feedbagMirror) groupNames() map[uint16]string {
+	names := make(map[uint16]string)
+	for _, item := range m.snapshot() {
+		if item.ClassID == wire.FeedbagClassIdGroup {
+			names[item.GroupID] = item.Name
+		}
+	}
+	return names
+}
+
+func (m *feedbagMirror) indexOf(requestID uint32) int {
+	for i, change := range m.pending {
+		if change.requestID == requestID {
+			return i
+		}
+	}
+	return -1
+}
+
+// cloneFeedbagItems returns a copy of items that shares no TLV lists with
+// them, since state.FeedbagList edits TLV lists in place.
+func cloneFeedbagItems(items []wire.FeedbagItem) []wire.FeedbagItem {
+	items = slices.Clone(items)
+	for i := range items {
+		items[i].TLVList = slices.Clone(items[i].TLVList)
+	}
+	return items
+}
+
+// applyFeedbagChange returns items with changed upserted, or deleted when del
+// is true. Items match by group and item ID.
+func applyFeedbagChange(items []wire.FeedbagItem, del bool, changed []wire.FeedbagItem) []wire.FeedbagItem {
+	for _, item := range changed {
+		i := slices.IndexFunc(items, func(existing wire.FeedbagItem) bool {
+			return existing.GroupID == item.GroupID && existing.ItemID == item.ItemID
+		})
+		switch {
+		case del && i >= 0:
+			items = slices.Delete(items, i, i+1)
+		case !del && i >= 0:
+			items[i] = item
+		case !del:
+			items = append(items, item)
+		}
+	}
+	return items
+}

+ 116 - 0
server/toc/toc_conn_test.go

@@ -0,0 +1,116 @@
+package toc
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+
+	"github.com/mk6i/open-oscar-server/wire"
+)
+
+func TestFeedbagMirror(t *testing.T) {
+	root := wire.FeedbagItem{ClassID: wire.FeedbagClassIdGroup}
+	friends := wire.FeedbagItem{ClassID: wire.FeedbagClassIdGroup, GroupID: 1, Name: "Friends"}
+	alice := wire.FeedbagItem{ClassID: wire.FeedbagClassIdBuddy, GroupID: 1, ItemID: 2, Name: "alice"}
+	aliceRenamed := wire.FeedbagItem{ClassID: wire.FeedbagClassIdBuddy, GroupID: 1, ItemID: 2, Name: "Alice"}
+	bob := wire.FeedbagItem{ClassID: wire.FeedbagClassIdBuddy, GroupID: 1, ItemID: 3, Name: "bob"}
+	carol := wire.FeedbagItem{ClassID: wire.FeedbagClassIdBuddy, GroupID: 1, ItemID: 4, Name: "carol"}
+
+	t.Run("pending changes show in the snapshot until confirmed", func(t *testing.T) {
+		m := &feedbagMirror{}
+		m.reset([]wire.FeedbagItem{root, friends, alice})
+
+		m.sent(1, false, []wire.FeedbagItem{aliceRenamed, bob})
+		m.sent(2, true, []wire.FeedbagItem{aliceRenamed})
+		assert.True(t, m.hasPending())
+		assert.Equal(t, []wire.FeedbagItem{root, friends, bob}, m.snapshot())
+
+		// only the accepted items are confirmed
+		m.confirm(1, []uint16{0, 0x0E})
+		m.confirm(2, []uint16{0})
+		assert.False(t, m.hasPending())
+		assert.Equal(t, []wire.FeedbagItem{root, friends}, m.snapshot())
+	})
+
+	t.Run("a rejected change is discarded", func(t *testing.T) {
+		m := &feedbagMirror{}
+		m.reset([]wire.FeedbagItem{root, friends})
+
+		m.sent(1, false, []wire.FeedbagItem{bob})
+		m.discard(1)
+		assert.False(t, m.hasPending())
+		assert.Equal(t, []wire.FeedbagItem{root, friends}, m.snapshot())
+	})
+
+	t.Run("a reload keeps changes sent after its query", func(t *testing.T) {
+		m := &feedbagMirror{}
+		m.reset([]wire.FeedbagItem{root, friends})
+
+		m.sent(1, false, []wire.FeedbagItem{bob}) // dropped by the server
+		marker := m.mark()
+		m.sent(2, false, []wire.FeedbagItem{carol})
+
+		m.reload(marker, []wire.FeedbagItem{root, friends, alice})
+		assert.Equal(t, []wire.FeedbagItem{root, friends, alice, carol}, m.snapshot())
+
+		// the confirmation of the dropped change never comes; a late one for
+		// a change the reload covered changes nothing
+		m.confirm(1, []uint16{0})
+		m.confirm(2, []uint16{0})
+		assert.False(t, m.hasPending())
+		assert.Equal(t, []wire.FeedbagItem{root, friends, alice, carol}, m.snapshot())
+	})
+
+	t.Run("changes from other locations apply right away", func(t *testing.T) {
+		m := &feedbagMirror{}
+		m.reset([]wire.FeedbagItem{root, friends, alice})
+
+		m.applyRemote(false, []wire.FeedbagItem{bob})
+		m.applyRemote(true, []wire.FeedbagItem{alice})
+		assert.False(t, m.hasPending())
+		assert.Equal(t, []wire.FeedbagItem{root, friends, bob}, m.snapshot())
+		assert.Equal(t, map[uint16]string{0: "", 1: "Friends"}, m.groupNames())
+	})
+
+	t.Run("a snapshot doesn't change with the mirror", func(t *testing.T) {
+		m := &feedbagMirror{}
+		m.reset([]wire.FeedbagItem{root, friends, alice})
+		snapshot := m.snapshot()
+		m.applyRemote(true, []wire.FeedbagItem{alice})
+		assert.Equal(t, []wire.FeedbagItem{root, friends, alice}, snapshot)
+	})
+
+	t.Run("the mirror shares no TLVs with items going in or out", func(t *testing.T) {
+		order := func(ids ...uint16) wire.FeedbagItem {
+			return wire.FeedbagItem{
+				ClassID:   wire.FeedbagClassIdGroup,
+				TLVLBlock: wire.TLVLBlock{TLVList: wire.TLVList{wire.NewTLVBE(wire.FeedbagAttributesOrder, ids)}},
+			}
+		}
+		edit := func(items []wire.FeedbagItem) {
+			items[0].TLVList.Set(wire.NewTLVBE(wire.FeedbagAttributesOrder, []uint16{9}))
+		}
+
+		m := &feedbagMirror{}
+		items := []wire.FeedbagItem{order(1)}
+		m.reset(items)
+		edit(items)
+		edit(m.snapshot())
+		assert.Equal(t, []wire.FeedbagItem{order(1)}, m.snapshot())
+
+		items = []wire.FeedbagItem{order(1, 2)}
+		m.sent(1, false, items)
+		edit(items)
+		assert.Equal(t, []wire.FeedbagItem{order(1, 2)}, m.snapshot())
+
+		items = []wire.FeedbagItem{order(1, 2, 3)}
+		m.reload(m.mark(), items)
+		edit(items)
+		assert.Equal(t, []wire.FeedbagItem{order(1, 2, 3)}, m.snapshot())
+
+		items = []wire.FeedbagItem{order(1, 2, 3, 4)}
+		m.applyRemote(false, items)
+		edit(items)
+		assert.Equal(t, []wire.FeedbagItem{order(1, 2, 3, 4)}, m.snapshot())
+	})
+}

+ 10 - 102
server/toc/types.go

@@ -2,89 +2,34 @@ package toc
 
 import (
 	"context"
-	"net/http"
+	"net"
 	"time"
 
-	"github.com/google/uuid"
-
 	"github.com/mk6i/open-oscar-server/config"
 	"github.com/mk6i/open-oscar-server/state"
 	"github.com/mk6i/open-oscar-server/wire"
 )
 
-type BuddyService interface {
-	AddBuddies(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x03_0x04_BuddyAddBuddies) (*wire.SNACMessage, error)
-	BroadcastBuddyArrived(ctx context.Context, screenName state.IdentScreenName, userInfo wire.TLVUserInfo) error
-	BroadcastBuddyDeparted(ctx context.Context, screenName state.IdentScreenName) error
-	DelBuddies(ctx context.Context, instance *state.SessionInstance, inBody wire.SNAC_0x03_0x05_BuddyDelBuddies) error
-	RightsQuery(ctx context.Context, inFrame wire.SNACFrame) wire.SNACMessage
-}
-
-type ChatService interface {
-	ChannelMsgToHost(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x0E_0x05_ChatChannelMsgToHost) (*wire.SNACMessage, error)
-}
-
-type ChatNavService interface {
-	CreateRoom(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x0E_0x02_ChatRoomInfoUpdate) (wire.SNACMessage, error)
-	ExchangeInfo(ctx context.Context, inFrame wire.SNACFrame, inBody wire.SNAC_0x0D_0x03_ChatNavRequestExchangeInfo) (wire.SNACMessage, error)
-	RequestChatRights(ctx context.Context, inFrame wire.SNACFrame) wire.SNACMessage
-	RequestRoomInfo(ctx context.Context, inFrame wire.SNACFrame, inBody wire.SNAC_0x0D_0x04_ChatNavRequestRoomInfo) (wire.SNACMessage, error)
-}
-
-type ICBMService interface {
-	ChannelMsgToHost(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x04_0x06_ICBMChannelMsgToHost) (*wire.SNACMessage, error)
-	ClientEvent(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x04_0x14_ICBMClientEvent) error
-	EvilRequest(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x04_0x08_ICBMEvilRequest) (wire.SNACMessage, error)
-	ParameterQuery(ctx context.Context, inFrame wire.SNACFrame) wire.SNACMessage
-	ClientErr(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x04_0x0B_ICBMClientErr) error
-}
-
-type OServiceService interface {
-	ClientOnline(ctx context.Context, service uint16, inBody wire.SNAC_0x01_0x02_OServiceClientOnline, instance *state.SessionInstance) error
-	IdleNotification(ctx context.Context, instance *state.SessionInstance, inBody wire.SNAC_0x01_0x11_OServiceIdleNotification) error
-	MonitorRateLimits(ctx context.Context, session *state.Session)
-	ServiceRequest(ctx context.Context, service uint16, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x01_0x04_OServiceServiceRequest, listenerGroup config.ListenerGroup) (wire.SNACMessage, error)
-}
-
-type AuthService interface {
-	BUCPChallenge(ctx context.Context, inBody wire.SNAC_0x17_0x06_BUCPChallengeRequest, newUUID func() uuid.UUID) (wire.SNACMessage, error)
-	BUCPLogin(ctx context.Context, inBody wire.SNAC_0x17_0x02_BUCPLoginRequest, endpointCfg config.Endpoint) (wire.SNACMessage, error)
-	CrackCookie(authCookie []byte) (state.ServerCookie, time.Time, error)
-	FLAPLogin(ctx context.Context, inFrame wire.FLAPSignonFrame, endpointCfg config.Endpoint) (wire.TLVRestBlock, error)
-	RegisterBOSSession(ctx context.Context, authCookie state.ServerCookie, cfg func(*state.Session)) (*state.SessionInstance, error)
-	RegisterChatSession(ctx context.Context, authCookie state.ServerCookie, cfg func(sess *state.Session)) (*state.SessionInstance, error)
-	RetrieveBOSSession(ctx context.Context, authCookie state.ServerCookie) (*state.SessionInstance, error)
-	Signout(ctx context.Context, session *state.Session)
-	SignoutChat(ctx context.Context, sess *state.Session)
+// OSCARServer serves OSCAR connections that the TOC server opens on behalf of
+// its users.
+type OSCARServer interface {
+	// ServeConn serves conn as if it had been accepted on the listener at
+	// endpointCfg, and returns when the connection ends.
+	ServeConn(conn net.Conn, endpointCfg config.Endpoint)
 }
 
+// LocateService looks up user profiles and directory info for the TOC HTTP
+// pages.
 type LocateService interface {
-	SetDirInfo(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x02_0x09_LocateSetDirInfo) (wire.SNACMessage, error)
-	SetInfo(ctx context.Context, instance *state.SessionInstance, inBody wire.SNAC_0x02_0x04_LocateSetInfo) error
 	UserInfoQuery(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x02_0x05_LocateUserInfoQuery) (wire.SNACMessage, error)
 	DirInfo(ctx context.Context, inFrame wire.SNACFrame, inBody wire.SNAC_0x02_0x0B_LocateGetDirInfo) (wire.SNACMessage, error)
 }
 
+// DirSearchService searches the user directory for the TOC HTTP pages.
 type DirSearchService interface {
 	InfoQuery(ctx context.Context, inFrame wire.SNACFrame, inBody wire.SNAC_0x0F_0x02_InfoQuery) (wire.SNACMessage, error)
 }
 
-type PermitDenyService interface {
-	AddDenyListEntries(ctx context.Context, instance *state.SessionInstance, inBody wire.SNAC_0x09_0x07_PermitDenyAddDenyListEntries) error
-	AddPermListEntries(ctx context.Context, instance *state.SessionInstance, inBody wire.SNAC_0x09_0x05_PermitDenyAddPermListEntries) error
-	DelDenyListEntries(ctx context.Context, instance *state.SessionInstance, inBody wire.SNAC_0x09_0x08_PermitDenyDelDenyListEntries) error
-	DelPermListEntries(ctx context.Context, instance *state.SessionInstance, inBody wire.SNAC_0x09_0x06_PermitDenyDelPermListEntries) error
-	RightsQuery(ctx context.Context, inFrame wire.SNACFrame) wire.SNACMessage
-}
-
-// BuddyListRegistry is the interface for keeping track of users with active
-// buddy lists. Once registered, a user becomes visible to other users' buddy
-// lists and vice versa.
-type BuddyListRegistry interface {
-	RegisterBuddyList(ctx context.Context, user state.IdentScreenName) error
-	UnregisterBuddyList(ctx context.Context, user state.IdentScreenName) error
-}
-
 type TOCConfigStore interface {
 	// SetTOCConfig sets the user's TOC config. The TOC config is the server-side
 	// buddy list functionality for TOC. This configuration is not available to
@@ -93,26 +38,6 @@ type TOCConfigStore interface {
 	User(ctx context.Context, screenName state.IdentScreenName) (*state.User, error)
 }
 
-type FeedbagManager interface {
-	// Feedbag fetches the contents of a user's feedbag for CONFIG2
-	Feedbag(ctx context.Context, screenName state.IdentScreenName) ([]wire.FeedbagItem, error)
-	UseFeedbag(ctx context.Context, screenName state.IdentScreenName) error
-}
-
-// FeedbagService defines the interface for managing server-side buddy lists
-// (feedbag) and related operations.
-type FeedbagService interface {
-	DeleteItem(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x13_0x0A_FeedbagDeleteItem) (*wire.SNACMessage, error)
-	Query(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame) (wire.SNACMessage, error)
-	QueryIfModified(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x13_0x05_FeedbagQueryIfModified) (wire.SNACMessage, error)
-	RespondAuthorizeToHost(ctx context.Context, instance state.IdentScreenName, inFrame wire.SNACFrame, inBody wire.SNAC_0x13_0x1A_FeedbagRespondAuthorizeToHost) error
-	RightsQuery(ctx context.Context, inFrame wire.SNACFrame) wire.SNACMessage
-	StartCluster(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x13_0x11_FeedbagStartCluster)
-	EndCluster(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame) error
-	UpsertItem(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, items []wire.FeedbagItem) (*wire.SNACMessage, error)
-	Use(ctx context.Context, instance *state.SessionInstance) error
-}
-
 // CookieBaker defines methods for issuing and verifying AIM authentication tokens ("cookies").
 // These tokens are used for authenticating client sessions with AIM services.
 type CookieBaker interface {
@@ -126,10 +51,6 @@ type CookieBaker interface {
 	Issue(data []byte, ttl time.Duration) ([]byte, error)
 }
 
-type AdminService interface {
-	InfoChangeRequest(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, inBody wire.SNAC_0x07_0x04_AdminInfoChangeRequest) (wire.SNACMessage, error)
-}
-
 // SessionRetriever defines a method for retrieving an active session
 // associated with a given screen name.
 type SessionRetriever interface {
@@ -138,16 +59,3 @@ type SessionRetriever interface {
 	// are active instances with complete signon.
 	RetrieveSession(screenName state.IdentScreenName) *state.Session
 }
-
-type OSCARProxyer interface {
-	Signon(ctx context.Context, args []byte, isTOC1 bool) (*state.Session, []string)
-	RecvBOS(ctx context.Context, sessBOS *state.Session, chatRegistry *ChatRegistry, msgCh chan<- []string) error
-	RecvClientCmd(ctx context.Context, sessBOS *state.Session, chatRegistry *ChatRegistry, payload []byte, toCh chan<- []string, doAsync func(f func() error)) []string
-	NewServeMux() http.Handler
-}
-
-// ChatSessionManager is the interface for closing chat sessions
-// when a client disconnects.
-type ChatSessionManager interface {
-	RemoveUserFromAllChats(user state.IdentScreenName)
-}

+ 0 - 24
state/session.go

@@ -1011,8 +1011,6 @@ type SessionInstance struct {
 	capabilities      [][16]byte
 	foodGroupVersions [wire.MDir + 1]uint16
 	multiConnFlag     wire.MultiConnFlag
-	toc2              bool
-	toc2MsgEnc        bool
 	icqDCInfo         wire.ICQDCInfo
 
 	// Per-session state
@@ -1081,28 +1079,6 @@ func (s *SessionInstance) SetClientID(clientID string) {
 	s.clientID = clientID
 }
 
-// SetTOC2 sets this instance to TOC2. supportsTOC2MsgEnc is true for toc2_login (encoded messaging), false for toc2_signon.
-func (s *SessionInstance) SetTOC2(supportsTOC2MsgEnc bool) {
-	s.mutex.Lock()
-	defer s.mutex.Unlock()
-	s.toc2 = true
-	s.toc2MsgEnc = supportsTOC2MsgEnc
-}
-
-// IsTOC2 returns true when the client is TOC2 (with or without encoded messaging).
-func (s *SessionInstance) IsTOC2() bool {
-	s.mutex.RLock()
-	defer s.mutex.RUnlock()
-	return s.toc2
-}
-
-// SupportsTOC2MsgEnc returns true only when TOC2 with encoded messaging (toc2_login).
-func (s *SessionInstance) SupportsTOC2MsgEnc() bool {
-	s.mutex.RLock()
-	defer s.mutex.RUnlock()
-	return s.toc2MsgEnc
-}
-
 //
 // Status / Availability
 //

Некоторые файлы не были показаны из-за большого количества измененных файлов