Explorar el Código

toc: make TOC server an OSCAR client

Previously, TOC server plugged into OSCAR directly via service
types. This commit decouples TOC from the go implementation by
replacing all the direct service calls with OSCAR protocol calls.

This allows us to centralize server handling logic in the oscar
package and reduce duplicated functionality, including session
start up / tear down, rate limiting, etc.

The pattern established in this package will be applied to
webapi and legacyicq packages.
Mike hace 20 horas
padre
commit
0fc38d6655
Se han modificado 36 ficheros con 3710 adiciones y 8872 borrados
  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. 972 3683
      server/toc/cmd_client_test.go
  10. 308 100
      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. 275 0
      server/toc/oscar_conn.go
  28. 184 0
      server/toc/oscar_conn_test.go
  29. 34 66
      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. 329 0
      server/toc/toc_conn.go
  34. 116 0
      server/toc/toc_conn_test.go
  35. 11 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)
+}

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 227 - 487
server/toc/cmd_client.go


La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 972 - 3683
server/toc/cmd_client_test.go


+ 308 - 100
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"
 )
 
@@ -21,70 +20,132 @@ var (
 	// error code as this is the closest applicable code as per TiK, Tameclone, phptoclib.
 	cmdInternalSvcErr    = "ERROR:" + wire.TOCErrorAdminProcessingRequest + ":internal server error"
 	rateLimitExceededErr = "ERROR:" + wire.TOCErrorGeneralRateLimitHit
-	errDisconnect        = errors.New("got booted by another session")
+	errDisconnect        = errors.New("OSCAR server closed the connection")
 )
 
-// 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(ctx context.Context) 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 ""
 	}

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 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(ctx context.Context) error) { go func() { _ = f(context.Background()) }() }
+	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 func() { _ = 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
-}

+ 275 - 0
server/toc/oscar_conn.go

@@ -0,0 +1,275 @@
+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
+
+// flapConn is an oscarConn over a FLAP connection to the OSCAR server.
+type flapConn struct {
+	conn   net.Conn
+	flapc  *wire.FlapClient
+	logger *slog.Logger
+	// msgs holds SNACs received from the server until the TOC consumer takes
+	// them. read never waits on it, so it keeps reading from the server even
+	// while the consumer is busy; otherwise the server's writes would block,
+	// and with them its reads of the SNACs TOC sends.
+	msgs      chan wire.SNACMessage
+	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,
+		msgs:   make(chan wire.SNACMessage, snacQueueLimit),
+		closed: make(chan struct{}),
+	}
+	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
+			}
+			select {
+			case c.msgs <- msg:
+			default:
+				// 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.msgs
+}
+
+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 func() { _ = 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
+}

+ 184 - 0
server/toc/oscar_conn_test.go

@@ -0,0 +1,184 @@
+package toc
+
+import (
+	"bytes"
+	"log/slog"
+	"net"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+
+	"github.com/mk6i/open-oscar-server/wire"
+)
+
+func TestFLAPConn_readsWhileConsumerIsBusy(t *testing.T) {
+	clientEnd, serverEnd := net.Pipe()
+	server := wire.NewFlapClient(0, serverEnd, serverEnd)
+	conn := newFLAPConn(clientEnd, wire.NewFlapClient(0, clientEnd, clientEnd), slog.Default())
+	defer conn.Close()
+
+	// nobody reads conn.Messages() yet, so each send returns only because the
+	// connection keeps reading from the pipe
+	for i := uint32(1); i <= 100; i++ {
+		assert.NoError(t, server.SendSNAC(wire.SNACFrame{FoodGroup: wire.Buddy, SubGroup: wire.BuddyDeparted, RequestID: i},
+			wire.SNAC_0x03_0x0C_BuddyDeparted{}))
+	}
+
+	for i := uint32(1); i <= 100; i++ {
+		select {
+		case msg := <-conn.Messages():
+			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 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 its buffer 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 buffer 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()
+}

+ 34 - 66
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,21 +371,26 @@ func (s *Server) handleTOCRequest(
 
 	g, ctx := errgroup.WithContext(ctx)
 
+	me.toCh = msgCh
+	me.goAsync = func(f func(ctx context.Context) error) {
+		g.Go(func() error { return f(ctx) })
+	}
+
 	// process TOC client requests and enqueue TOC server responses
-	g.Go(func() error {
-		err := s.runClientCommands(ctx, g.Go, sessBOS, chatRegistry, clientFlap, msgCh)
+	me.goAsync(func(ctx context.Context) error {
+		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)
+	me.goAsync(func(ctx context.Context) error {
+		err := s.bosProxy.RecvBOS(ctx, me)
 		closeConn() // unblock runClientCommands
 		return errors.Join(err, errTOCProcessing)
 	})
 
 	// send TOC server responses to the client
-	g.Go(func() error {
+	me.goAsync(func(ctx context.Context) error {
 		err := s.sendToClient(ctx, msgCh, clientFlap)
 		closeConn() // unblock runClientCommands
 		return errors.Join(err, errServerWrite)
@@ -434,7 +399,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 +421,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 +460,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 +469,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)
+}

+ 329 - 0
server/toc/toc_conn.go

@@ -0,0 +1,329 @@
+package toc
+
+import (
+	"context"
+	"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 in the background alongside the connection's other
+	// handlers, such as the message loop of each chat room the user joins.
+	// f gets the connection's context, which is cancelled when the
+	// connection ends. The connection waits for all of them to finish when it
+	// closes, and an error from f ends the connection.
+	goAsync func(f func(ctx context.Context) 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).
+// It 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].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())
+	})
+}

+ 11 - 102
server/toc/types.go

@@ -2,89 +2,35 @@ 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
-}
-
+// TOCConfigStore loads and saves users' TOC configs.
 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 +39,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 +52,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 +60,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
 //

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio