Explorar o código

fix all golangci-lint reports

Mike hai 2 meses
pai
achega
bd3a8adc2a
Modificáronse 76 ficheiros con 919 adicións e 1014 borrados
  1. 5 0
      .github/workflows/go.yml
  2. 9 0
      .golangci.yml
  3. 12 0
      Makefile
  4. 1 1
      cmd/config_generator/main.go
  5. 9 9
      cmd/server/factory.go
  6. 12 14
      cmd/server/main.go
  7. 2 2
      cmd/snac_buster/main.go
  8. 4 4
      cmd/webapi_keygen/main.go
  9. 8 0
      docs/BUILD.md
  10. 8 8
      foodgroup/admin.go
  11. 9 9
      foodgroup/admin_test.go
  12. 1 4
      foodgroup/auth.go
  13. 2 2
      foodgroup/auth_test.go
  14. 2 2
      foodgroup/bart_test.go
  15. 2 2
      foodgroup/buddy_test.go
  16. 2 2
      foodgroup/chat.go
  17. 1 2
      foodgroup/chat_nav.go
  18. 26 26
      foodgroup/feedbag_test.go
  19. 0 41
      foodgroup/helpers_test.go
  20. 4 4
      foodgroup/icbm.go
  21. 7 7
      foodgroup/icbm_test.go
  22. 1 1
      foodgroup/locate_test.go
  23. 1 3
      foodgroup/oservice.go
  24. 4 4
      foodgroup/oservice_test.go
  25. 1 1
      foodgroup/permit_deny_test.go
  26. 0 7
      server/http/helpers_test.go
  27. 4 4
      server/http/mgmt_api.go
  28. 37 37
      server/http/mgmt_api_test.go
  29. 5 5
      server/icq_legacy/legacy_message_bridge.go
  30. 2 2
      server/icq_legacy/server.go
  31. 15 15
      server/icq_legacy/v2_handler.go
  32. 65 65
      server/icq_legacy/v3_handler.go
  33. 0 13
      server/icq_legacy/v3_packet_builder.go
  34. 39 39
      server/icq_legacy/v4_handler.go
  35. 180 182
      server/icq_legacy/v5_handler.go
  36. 30 30
      server/icq_legacy/wire_legacy.go
  37. 1 1
      server/kerberos/kerberos.go
  38. 2 2
      server/kerberos/kerberos_test.go
  39. 68 68
      server/oscar/handler.go
  40. 1 4
      server/oscar/handler_test.go
  41. 22 2
      server/oscar/middleware/logger.go
  42. 70 70
      server/oscar/server.go
  43. 53 53
      server/oscar/server_test.go
  44. 1 1
      server/toc/cmd_client.go
  45. 33 33
      server/toc/cmd_client_test.go
  46. 7 7
      server/toc/cmd_server.go
  47. 2 2
      server/toc/cmd_server_test.go
  48. 0 22
      server/toc/helpers_test.go
  49. 10 3
      server/toc/server.go
  50. 2 2
      server/webapi/adapters.go
  51. 2 19
      server/webapi/handlers/amf_encoder.go
  52. 0 1
      server/webapi/handlers/auth.go
  53. 3 3
      server/webapi/handlers/buddyfeed.go
  54. 10 8
      server/webapi/handlers/buddylist.go
  55. 0 37
      server/webapi/handlers/chat.go
  56. 13 13
      server/webapi/handlers/common.go
  57. 11 9
      server/webapi/handlers/events.go
  58. 5 4
      server/webapi/handlers/oscar_bridge.go
  59. 4 3
      server/webapi/handlers/preference.go
  60. 5 4
      server/webapi/handlers/presence.go
  61. 5 4
      server/webapi/handlers/session.go
  62. 1 1
      server/webapi/server.go
  63. 2 1
      server/webapi/types/events.go
  64. 25 52
      state/session_manager_test.go
  65. 3 3
      state/user_store.go
  66. 2 2
      state/user_store_test.go
  67. 1 1
      state/webapi_analytics.go
  68. 1 1
      state/webapi_auth.go
  69. 9 3
      state/webapi_buddyfeed.go
  70. 17 7
      state/webapi_chat.go
  71. 1 1
      state/webapi_oscar_bridge.go
  72. 1 1
      state/webapi_session.go
  73. 3 6
      wire/decode.go
  74. 3 3
      wire/encode.go
  75. 3 3
      wire/snacs.go
  76. 7 7
      wire/user.go

+ 5 - 0
.github/workflows/go.yml

@@ -26,6 +26,11 @@ jobs:
           gofmt -s -l .
           if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then exit 1; fi
 
+      - name: GolangCI-Lint
+        uses: golangci/golangci-lint-action@v8
+        with:
+          version: v2.12.2
+
       - name: Go Vet
         run: go vet ./...
 

+ 9 - 0
.golangci.yml

@@ -0,0 +1,9 @@
+version: "2"
+
+linters:
+  settings:
+    errcheck:
+      exclude-functions:
+        # Closing query rows and rolling back already-committed transactions are cleanup paths.
+        - "(*database/sql.Rows).Close"
+        - "(*database/sql.Tx).Rollback"

+ 12 - 0
Makefile

@@ -12,6 +12,7 @@ SKIP_CODE_SIGN ?= 1
 SIGN_HTTP_URL ?=
 SIGN_SERVER_TOKEN ?=
 GORELEASER ?= goreleaser
+GOLANGCI_LINT ?= golangci-lint
 
 DOCKER_RUN_GO_RELEASER := @docker run \
 	--env CGO_ENABLED=0 \
@@ -34,6 +35,17 @@ config-ssl: ## Generate SSL config file template
 
 config: config-basic config-ssl ## Generate all config file templates from Config struct
 
+.PHONY: lint
+lint: ## Run formatting and static analysis checks
+	@fmt_output="$$(gofmt -s -l .)"; \
+	if [ -n "$$fmt_output" ]; then \
+		echo "The following files need formatting:"; \
+		echo "$$fmt_output"; \
+		exit 1; \
+	fi
+	$(GOLANGCI_LINT) run ./...
+	go vet ./...
+
 .PHONY: release
 release: ## Run a clean, full GoReleaser run (publish + validate)
 	$(DOCKER_RUN_GO_RELEASER) --clean

+ 1 - 1
cmd/config_generator/main.go

@@ -52,7 +52,7 @@ func main() {
 		fmt.Fprintf(os.Stderr, "error creating file: %s\n", err.Error())
 		os.Exit(1)
 	}
-	defer f.Close()
+	defer func() { _ = f.Close() }()
 
 	configType := reflect.TypeOf(config.Config{})
 	writeFields(f, configType, valueTag, keywords)

+ 9 - 9
cmd/server/factory.go

@@ -175,14 +175,14 @@ func validateConfigMigration() error {
 		if len(oldEnvVarsFound) > 0 {
 			errorMsg.WriteString("\nOld environment variables that must be removed:\n\n")
 			for _, envVar := range oldEnvVarsFound {
-				errorMsg.WriteString(fmt.Sprintf("  - %s\n", envVar))
+				fmt.Fprintf(&errorMsg, "  - %s\n", envVar)
 			}
 		}
 
 		if len(newEnvVarsMissing) > 0 {
 			errorMsg.WriteString("\nNew environment variables that must be provided:\n\n")
 			for _, envVar := range newEnvVarsMissing {
-				errorMsg.WriteString(fmt.Sprintf("  - %s\n", envVar))
+				fmt.Fprintf(&errorMsg, "  - %s\n", envVar)
 			}
 
 			// Generate export commands based on old environment variables
@@ -191,29 +191,29 @@ func validateConfigMigration() error {
 			if contains(newEnvVarsMissing, "API_LISTENER") {
 				apiHost := getEnvOrDefault("API_HOST", "127.0.0.1")
 				apiPort := getEnvOrDefault("API_PORT", "8080")
-				errorMsg.WriteString(fmt.Sprintf("export API_LISTENER=%s:%s\n", apiHost, apiPort))
+				fmt.Fprintf(&errorMsg, "export API_LISTENER=%s:%s\n", apiHost, apiPort)
 			}
 
 			if contains(newEnvVarsMissing, "OSCAR_ADVERTISED_LISTENERS_PLAIN") {
 				oscarHost := getEnvOrDefault("OSCAR_HOST", "127.0.0.1")
 				authPort := getEnvOrDefault("AUTH_PORT", "5190")
-				errorMsg.WriteString(fmt.Sprintf("export OSCAR_ADVERTISED_LISTENERS_PLAIN=LOCAL://%s:%s\n", oscarHost, authPort))
+				fmt.Fprintf(&errorMsg, "export OSCAR_ADVERTISED_LISTENERS_PLAIN=LOCAL://%s:%s\n", oscarHost, authPort)
 			}
 
 			if contains(newEnvVarsMissing, "OSCAR_LISTENERS") {
 				authPort := getEnvOrDefault("AUTH_PORT", "5190")
-				errorMsg.WriteString(fmt.Sprintf("export OSCAR_LISTENERS=LOCAL://0.0.0.0:%s\n", authPort))
+				fmt.Fprintf(&errorMsg, "export OSCAR_LISTENERS=LOCAL://0.0.0.0:%s\n", authPort)
 			}
 
 			if contains(newEnvVarsMissing, "KERBEROS_LISTENERS") {
 				kerberosPort := getEnvOrDefault("KERBEROS_PORT", "1088")
-				errorMsg.WriteString(fmt.Sprintf("export KERBEROS_LISTENERS=LOCAL://0.0.0.0:%s\n", kerberosPort))
+				fmt.Fprintf(&errorMsg, "export KERBEROS_LISTENERS=LOCAL://0.0.0.0:%s\n", kerberosPort)
 			}
 
 			if contains(newEnvVarsMissing, "TOC_LISTENERS") {
 				tocHost := getEnvOrDefault("TOC_HOST", "0.0.0.0")
 				tocPort := getEnvOrDefault("TOC_PORT", "9898")
-				errorMsg.WriteString(fmt.Sprintf("export TOC_LISTENERS=%s:%s\n", tocHost, tocPort))
+				fmt.Fprintf(&errorMsg, "export TOC_LISTENERS=%s:%s\n", tocHost, tocPort)
 			}
 		}
 
@@ -744,11 +744,11 @@ func ICQLegacy(deps Container) *icq_legacy.LegacyServer {
 		)
 		// Notify legacy contacts
 		sessionManager.BroadcastToContacts(session, func(contact *icq_legacy.LegacySession) {
-			dispatcher.SendUserOffline(contact, session.UIN)
+			_ = dispatcher.SendUserOffline(contact, session.UIN)
 		})
 		// Notify OSCAR clients
 		ctx := context.Background()
-		icqLegacyService.NotifyUserOffline(ctx, session.UIN)
+		_ = icqLegacyService.NotifyUserOffline(ctx, session.UIN)
 	})
 
 	return server

+ 12 - 14
cmd/server/main.go

@@ -84,20 +84,18 @@ func main() {
 		g.Go(icqLegacy.ListenAndServe)
 	}
 
-	select {
-	case <-ctx.Done():
-		shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
-		defer cancel()
-		_ = oscar.Shutdown(shutdownCtx)
-		_ = kerb.Shutdown(shutdownCtx)
-		_ = api.Shutdown(shutdownCtx)
-		_ = toc.Shutdown(shutdownCtx)
-		if os.Getenv("ENABLE_WEBAPI") == "1" {
-			_ = webAPI.Shutdown(shutdownCtx)
-		}
-		if deps.cfg.ICQLegacy.Enabled {
-			_ = icqLegacy.Shutdown(shutdownCtx)
-		}
+	<-ctx.Done()
+	shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+	defer cancel()
+	_ = oscar.Shutdown(shutdownCtx)
+	_ = kerb.Shutdown(shutdownCtx)
+	_ = api.Shutdown(shutdownCtx)
+	_ = toc.Shutdown(shutdownCtx)
+	if os.Getenv("ENABLE_WEBAPI") == "1" {
+		_ = webAPI.Shutdown(shutdownCtx)
+	}
+	if deps.cfg.ICQLegacy.Enabled {
+		_ = icqLegacy.Shutdown(shutdownCtx)
 	}
 
 	if err = g.Wait(); err != nil {

+ 2 - 2
cmd/snac_buster/main.go

@@ -14,12 +14,12 @@ func main() {
 	flap := wire.FLAPFrame{}
 	err := wire.UnmarshalBE(&flap, bytes.NewReader(b))
 	if err != nil {
-		err = fmt.Errorf("unable to unmarshal FLAP frame: %w", err)
+		panic(fmt.Errorf("unable to unmarshal FLAP frame: %w", err))
 	}
 
 	rd := bytes.NewBuffer(flap.Payload)
 	snac := wire.SNACFrame{}
-	wire.UnmarshalBE(&snac, rd)
+	_ = wire.UnmarshalBE(&snac, rd)
 
 	printByteSlice(rd.Bytes())
 	//snacBody := wire.SNAC_0x01_0x0F_OServiceUserInfoUpdate{}

+ 4 - 4
cmd/webapi_keygen/main.go

@@ -192,8 +192,8 @@ func handleList(args []string) {
 
 	// Create a tabwriter for formatted output
 	w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
-	fmt.Fprintln(w, "DEV ID\tAPP NAME\tACTIVE\tRATE LIMIT\tCREATED\tLAST USED")
-	fmt.Fprintln(w, "------\t--------\t------\t----------\t-------\t---------")
+	_, _ = fmt.Fprintln(w, "DEV ID\tAPP NAME\tACTIVE\tRATE LIMIT\tCREATED\tLAST USED")
+	_, _ = fmt.Fprintln(w, "------\t--------\t------\t----------\t-------\t---------")
 
 	for _, key := range keys {
 		lastUsed := "Never"
@@ -201,7 +201,7 @@ func handleList(args []string) {
 			lastUsed = key.LastUsed.Format("2006-01-02 15:04")
 		}
 
-		fmt.Fprintf(w, "%s\t%s\t%v\t%d/min\t%s\t%s\n",
+		_, _ = fmt.Fprintf(w, "%s\t%s\t%v\t%d/min\t%s\t%s\n",
 			truncateString(key.DevID, 20),
 			truncateString(key.AppName, 20),
 			key.IsActive,
@@ -210,7 +210,7 @@ func handleList(args []string) {
 			lastUsed,
 		)
 	}
-	w.Flush()
+	_ = w.Flush()
 }
 
 func handleShow(args []string) {

+ 8 - 0
docs/BUILD.md

@@ -153,6 +153,14 @@ command from the root of the repository in a terminal:
 go test -race ./...
 ```
 
+Before opening a PR, run the same lint checks used by CI:
+
+```shell
+make lint
+```
+
+Install `golangci-lint` locally if the command is not already available on your `PATH`.
+
 ## Config File Generation
 
 The config file `config/settings.env` is generated programmatically from the [Config](../config/config.go) struct using

+ 8 - 8
foodgroup/admin.go

@@ -98,7 +98,7 @@ func (s AdminService) InfoQuery(ctx context.Context, instance *state.SessionInst
 
 	tlvList := wire.TLVList{}
 
-	if _, hasRegStatus := inBody.TLVRestBlock.Bytes(wire.AdminTLVRegistrationStatus); hasRegStatus {
+	if _, hasRegStatus := inBody.Bytes(wire.AdminTLVRegistrationStatus); hasRegStatus {
 		regStatus, err := s.accountManager.RegStatus(ctx, instance.IdentScreenName())
 		if err != nil {
 			return wire.SNACMessage{}, err
@@ -107,7 +107,7 @@ func (s AdminService) InfoQuery(ctx context.Context, instance *state.SessionInst
 		return getAdminInfoReply(tlvList), nil
 	}
 
-	if _, hasEmail := inBody.TLVRestBlock.Bytes(wire.AdminTLVEmailAddress); hasEmail {
+	if _, hasEmail := inBody.Bytes(wire.AdminTLVEmailAddress); hasEmail {
 		e, err := s.accountManager.EmailAddress(ctx, instance.IdentScreenName())
 		if errors.Is(err, state.ErrNoEmailAddress) {
 			tlvList.Append(wire.NewTLVBE(wire.AdminTLVEmailAddress, ""))
@@ -119,7 +119,7 @@ func (s AdminService) InfoQuery(ctx context.Context, instance *state.SessionInst
 		return getAdminInfoReply(tlvList), nil
 	}
 
-	if _, hasNickName := inBody.TLVRestBlock.Bytes(wire.AdminTLVScreenNameFormatted); hasNickName {
+	if _, hasNickName := inBody.Bytes(wire.AdminTLVScreenNameFormatted); hasNickName {
 		tlvList.Append(wire.NewTLVBE(wire.AdminTLVScreenNameFormatted, instance.DisplayScreenName().String()))
 		return getAdminInfoReply(tlvList), nil
 	}
@@ -199,7 +199,7 @@ func (s AdminService) InfoChangeRequest(ctx context.Context, instance *state.Ses
 
 	tlvList := wire.TLVList{}
 
-	if sn, hasScreenNameFormatted := inBody.TLVRestBlock.Bytes(wire.AdminTLVScreenNameFormatted); hasScreenNameFormatted {
+	if sn, hasScreenNameFormatted := inBody.Bytes(wire.AdminTLVScreenNameFormatted); hasScreenNameFormatted {
 		proposedName := state.DisplayScreenName(sn)
 		if ok, errorCode := validateProposedName(proposedName); !ok {
 			tlvList.Append(wire.NewTLVBE(wire.AdminTLVErrorCode, errorCode))
@@ -224,7 +224,7 @@ func (s AdminService) InfoChangeRequest(ctx context.Context, instance *state.Ses
 		return getAdminChangeReply(tlvList), nil
 	}
 
-	if emailAddress, hasEmailAddress := inBody.TLVRestBlock.Bytes(wire.AdminTLVEmailAddress); hasEmailAddress {
+	if emailAddress, hasEmailAddress := inBody.Bytes(wire.AdminTLVEmailAddress); hasEmailAddress {
 		e, errorCode := validateProposedEmailAddress(emailAddress)
 		if errorCode != 0 {
 			tlvList.Append(wire.NewTLVBE(wire.AdminTLVErrorCode, errorCode))
@@ -239,7 +239,7 @@ func (s AdminService) InfoChangeRequest(ctx context.Context, instance *state.Ses
 		return getAdminChangeReply(tlvList), nil
 	}
 
-	if regStatus, hasRegStatus := inBody.TLVRestBlock.Uint16BE(wire.AdminTLVRegistrationStatus); hasRegStatus {
+	if regStatus, hasRegStatus := inBody.Uint16BE(wire.AdminTLVRegistrationStatus); hasRegStatus {
 		switch regStatus {
 		case
 			wire.AdminInfoRegStatusFullDisclosure,
@@ -257,10 +257,10 @@ func (s AdminService) InfoChangeRequest(ctx context.Context, instance *state.Ses
 	}
 
 	// change password
-	if newPass, hasPassStatus := inBody.TLVRestBlock.String(wire.AdminTLVNewPassword); hasPassStatus {
+	if newPass, hasPassStatus := inBody.String(wire.AdminTLVNewPassword); hasPassStatus {
 		tlvList.Append(wire.NewTLVBE(wire.AdminTLVNewPassword, []byte{}))
 
-		oldPass, ok := inBody.TLVRestBlock.String(wire.AdminTLVOldPassword)
+		oldPass, ok := inBody.String(wire.AdminTLVOldPassword)
 		if !ok {
 			tlvList.Append(wire.NewTLVBE(wire.AdminTLVErrorCode, wire.AdminInfoErrorNeedOldPassword))
 			return getAdminChangeReply(tlvList), nil

+ 9 - 9
foodgroup/admin_test.go

@@ -175,17 +175,17 @@ func TestAdminService_ConfirmRequest(t *testing.T) {
 			accountManager := newMockAccountManager(t)
 			buddyBroadcaster := newMockbuddyBroadcaster(t)
 
-			for _, params := range tc.mockParams.accountManagerParams.accountManagerEmailAddressParams {
+			for _, params := range tc.mockParams.accountManagerEmailAddressParams {
 				accountManager.EXPECT().
 					EmailAddress(matchContext(), params.screenName).
 					Return(params.emailAddress, params.err)
 			}
-			for _, params := range tc.mockParams.accountManagerParams.accountManagerConfirmStatusParams {
+			for _, params := range tc.mockParams.accountManagerConfirmStatusParams {
 				accountManager.EXPECT().
 					ConfirmStatus(matchContext(), params.screenName).
 					Return(params.confirmStatus, params.err)
 			}
-			for _, params := range tc.mockParams.accountManagerParams.accountManagerUpdateConfirmStatusParams {
+			for _, params := range tc.mockParams.accountManagerUpdateConfirmStatusParams {
 				accountManager.EXPECT().
 					UpdateConfirmStatus(matchContext(), params.screenName, params.confirmStatus).
 					Return(params.err)
@@ -426,13 +426,13 @@ func TestAdminService_InfoQuery(t *testing.T) {
 			accountManager := newMockAccountManager(t)
 			buddyBroadcaster := newMockbuddyBroadcaster(t)
 
-			for _, params := range tc.mockParams.accountManagerParams.accountManagerRegStatusParams {
+			for _, params := range tc.mockParams.accountManagerRegStatusParams {
 				accountManager.EXPECT().
 					RegStatus(matchContext(), params.screenName).
 					Return(params.regStatus, params.err)
 			}
 
-			for _, params := range tc.mockParams.accountManagerParams.accountManagerEmailAddressParams {
+			for _, params := range tc.mockParams.accountManagerEmailAddressParams {
 				accountManager.EXPECT().
 					EmailAddress(matchContext(), params.screenName).
 					Return(params.emailAddress, params.err)
@@ -704,7 +704,7 @@ func TestAdminService_InfoChangeRequest_ScreenName(t *testing.T) {
 			accountManager := newMockAccountManager(t)
 			mockBuddyBroadcaster := newMockbuddyBroadcaster(t)
 
-			for _, params := range tc.mockParams.accountManagerParams.accountManagerUpdateDisplayScreenNameParams {
+			for _, params := range tc.mockParams.accountManagerUpdateDisplayScreenNameParams {
 				accountManager.EXPECT().
 					UpdateDisplayScreenName(matchContext(), params.displayScreenName).
 					Return(params.err)
@@ -718,7 +718,7 @@ func TestAdminService_InfoChangeRequest_ScreenName(t *testing.T) {
 					Return(params.err)
 			}
 
-			for _, params := range tc.mockParams.messageRelayerParams.relayToScreenNameParams {
+			for _, params := range tc.mockParams.relayToScreenNameParams {
 				p := params
 				messageRelayer.EXPECT().
 					RelayToScreenName(mock.Anything, p.screenName, p.message)
@@ -884,7 +884,7 @@ func TestAdminService_InfoChangeRequest_EmailAddress(t *testing.T) {
 			accountManager := newMockAccountManager(t)
 			buddyBroadcaster := newMockbuddyBroadcaster(t)
 
-			for _, params := range tc.mockParams.accountManagerParams.accountManagerUpdateEmailAddressParams {
+			for _, params := range tc.mockParams.accountManagerUpdateEmailAddressParams {
 				accountManager.EXPECT().
 					UpdateEmailAddress(matchContext(), params.screenName, params.emailAddress).
 					Return(params.err)
@@ -1008,7 +1008,7 @@ func TestAdminService_InfoChangeRequest_RegStatus(t *testing.T) {
 			accountManager := newMockAccountManager(t)
 			buddyBroadcaster := newMockbuddyBroadcaster(t)
 
-			for _, params := range tc.mockParams.accountManagerParams.accountManagerUpdateRegStatusParams {
+			for _, params := range tc.mockParams.accountManagerUpdateRegStatusParams {
 				accountManager.EXPECT().
 					UpdateRegStatus(context.Background(), params.screenName, params.regStatus).
 					Return(params.err)

+ 1 - 4
foodgroup/auth.go

@@ -123,10 +123,7 @@ func (s AuthService) RegisterBOSSession(ctx context.Context, authCookie state.Se
 
 	flag := wire.MultiConnFlag(authCookie.MultiConnFlag)
 
-	doMultiSess := false
-	if flag == wire.MultiConnFlagsRecentClient {
-		doMultiSess = true
-	}
+	doMultiSess := flag == wire.MultiConnFlagsRecentClient
 
 	cfg := func(sess *state.Session) {
 		sess.SetSignonTime(time.Now())

+ 2 - 2
foodgroup/auth_test.go

@@ -773,7 +773,7 @@ func TestAuthService_BUCPLoginRequest(t *testing.T) {
 			}
 
 			sessionRetriever := newMockSessionRetriever(t)
-			for _, params := range tc.mockParams.sessionRetrieverParams.retrieveSessionParams {
+			for _, params := range tc.mockParams.retrieveSessionParams {
 				sessionRetriever.EXPECT().
 					RetrieveSession(params.screenName).
 					Return(params.result)
@@ -1548,7 +1548,7 @@ func TestAuthService_KerberosLogin(t *testing.T) {
 					Return(params.cookieOut, params.err)
 			}
 			sessionRetriever := newMockSessionRetriever(t)
-			for _, params := range tc.mockParams.sessionRetrieverParams.retrieveSessionParams {
+			for _, params := range tc.mockParams.retrieveSessionParams {
 				sessionRetriever.EXPECT().
 					RetrieveSession(params.screenName).
 					Return(params.result)

+ 2 - 2
foodgroup/bart_test.go

@@ -496,7 +496,7 @@ func TestBARTService_RetrieveItem(t *testing.T) {
 	for _, tc := range cases {
 		t.Run(tc.name, func(t *testing.T) {
 			bartItemManager := newMockBARTItemManager(t)
-			for _, params := range tc.mockParams.bartItemManagerParams.bartItemManagerRetrieveParams {
+			for _, params := range tc.mockParams.bartItemManagerRetrieveParams {
 				bartItemManager.EXPECT().
 					BARTItem(matchContext(), params.itemHash).
 					Return(params.result, params.err)
@@ -870,7 +870,7 @@ func TestBARTService_RetrieveItemV2(t *testing.T) {
 	for _, tc := range cases {
 		t.Run(tc.name, func(t *testing.T) {
 			bartItemManager := newMockBARTItemManager(t)
-			for _, params := range tc.mockParams.bartItemManagerParams.bartItemManagerRetrieveParams {
+			for _, params := range tc.mockParams.bartItemManagerRetrieveParams {
 				bartItemManager.EXPECT().
 					BARTItem(matchContext(), params.itemHash).
 					Return(params.result, params.err)

+ 2 - 2
foodgroup/buddy_test.go

@@ -31,7 +31,7 @@ func TestBuddyService_RightsQuery(t *testing.T) {
 			},
 		},
 	}
-	have := svc.RightsQuery(nil, wire.SNACFrame{RequestID: 1234})
+	have := svc.RightsQuery(context.TODO(), wire.SNACFrame{RequestID: 1234})
 
 	assert.Equal(t, want, have)
 }
@@ -1313,7 +1313,7 @@ func newBuddyArrivedNotif(screenName state.DisplayScreenName) wire.SNACMessage {
 			if !ok {
 				return false
 			}
-			return snac.ScreenName == screenName.String() && len(snac.TLVUserInfo.TLVList) > 0
+			return snac.ScreenName == screenName.String() && len(snac.TLVList) > 0
 		},
 	}
 }

+ 2 - 2
foodgroup/chat.go

@@ -177,9 +177,9 @@ func newChatTLVBlock(body wire.SNAC_0x0E_0x05_ChatChannelMsgToHost, instance *st
 func (s ChatService) rollDice(instance *state.SessionInstance, dice int, sides int) wire.TLVRestBlock {
 	sb := strings.Builder{}
 	sb.WriteString("<HTML><BODY BGCOLOR=\"#ffffff\"><FONT LANG=\"0\">")
-	sb.WriteString(fmt.Sprintf("%s rolled %d %d-sided dice:", instance.DisplayScreenName().String(), dice, sides))
+	fmt.Fprintf(&sb, "%s rolled %d %d-sided dice:", instance.DisplayScreenName().String(), dice, sides)
 	for i := 0; i < dice; i++ {
-		sb.WriteString(fmt.Sprintf(" %d", s.randRollDie(sides)))
+		fmt.Fprintf(&sb, " %d", s.randRollDie(sides))
 	}
 	sb.WriteString("</FONT></BODY></HTML>")
 

+ 1 - 2
foodgroup/chat_nav.go

@@ -105,7 +105,6 @@ func (s ChatNavService) CreateRoom(ctx context.Context, instance *state.SessionI
 		if err := s.chatRoomManager.CreateChatRoom(ctx, &room); err != nil {
 			return wire.SNACMessage{}, fmt.Errorf("%w: %w", errChatNavRoomCreateFailed, err)
 		}
-		break
 	case err != nil:
 		return wire.SNACMessage{}, fmt.Errorf("%w: %w", errChatNavRetrieveFailed, err)
 	}
@@ -214,7 +213,7 @@ func sendChatNavErrorSNAC(inFrame wire.SNACFrame, errorCode uint16) (wire.SNACMe
 }
 
 func validateExchange(exchange uint16) error {
-	if !(exchange == state.PrivateExchange || exchange == state.PublicExchange) {
+	if exchange != state.PrivateExchange && exchange != state.PublicExchange {
 		return fmt.Errorf("only exchanges %d and %d are supported", state.PrivateExchange, state.PublicExchange)
 	}
 	return nil

+ 26 - 26
foodgroup/feedbag_test.go

@@ -2218,13 +2218,13 @@ func TestFeedbagService_UpsertItem(t *testing.T) {
 	for _, tc := range cases {
 		t.Run(tc.name, func(t *testing.T) {
 			feedbagManager := newMockFeedbagManager(t)
-			for _, params := range tc.mockParams.feedbagManagerParams.feedbagUpsertParams {
+			for _, params := range tc.mockParams.feedbagUpsertParams {
 				feedbagManager.EXPECT().
 					FeedbagUpsert(matchContext(), params.screenName, params.items).
 					Return(nil)
 			}
 			messageRelayer := newMockMessageRelayer(t)
-			for _, params := range tc.mockParams.messageRelayerParams.relayToScreenNameParams {
+			for _, params := range tc.mockParams.relayToScreenNameParams {
 				if matcherFn, ok := params.message.Body.(func(val any) bool); ok {
 					messageRelayer.EXPECT().
 						RelayToScreenName(matchContext(), params.screenName, mock.MatchedBy(func(message wire.SNACMessage) bool {
@@ -2236,16 +2236,16 @@ func TestFeedbagService_UpsertItem(t *testing.T) {
 						RelayToScreenName(matchContext(), params.screenName, params.message)
 				}
 			}
-			for _, params := range tc.mockParams.messageRelayerParams.relayToOtherInstancesParams {
+			for _, params := range tc.mockParams.relayToOtherInstancesParams {
 				messageRelayer.EXPECT().
 					RelayToOtherInstances(mock.Anything, mock.Anything, params.message)
 			}
-			for _, params := range tc.mockParams.messageRelayerParams.relayToSelfParams {
+			for _, params := range tc.mockParams.relayToSelfParams {
 				messageRelayer.EXPECT().
 					RelayToSelf(mock.Anything, mock.Anything, params.message)
 			}
 			bartItemManager := newMockBARTItemManager(t)
-			for _, params := range tc.mockParams.bartItemManagerParams.bartItemManagerRetrieveParams {
+			for _, params := range tc.mockParams.bartItemManagerRetrieveParams {
 				bartItemManager.EXPECT().
 					BARTItem(matchContext(), params.itemHash).
 					Return(params.result, nil)
@@ -2271,7 +2271,7 @@ func TestFeedbagService_UpsertItem(t *testing.T) {
 				contactPreAuth.EXPECT().RequiresAuthorization(matchContext(), params.owner, params.requester).Return(params.result, params.err)
 			}
 			sessionRetriever := newMockSessionRetriever(t)
-			for _, params := range tc.mockParams.sessionRetrieverParams.retrieveSessionParams {
+			for _, params := range tc.mockParams.retrieveSessionParams {
 				sessionRetriever.EXPECT().
 					RetrieveSession(params.screenName).
 					Return(params.result)
@@ -2428,7 +2428,7 @@ func TestFeedbagService_DeleteItem(t *testing.T) {
 	for _, tc := range cases {
 		t.Run(tc.name, func(t *testing.T) {
 			feedbagManager := newMockFeedbagManager(t)
-			for _, params := range tc.mockParams.feedbagManagerParams.feedbagDeleteParams {
+			for _, params := range tc.mockParams.feedbagDeleteParams {
 				feedbagManager.EXPECT().
 					FeedbagDelete(matchContext(), params.screenName, params.items).
 					Return(nil)
@@ -2440,11 +2440,11 @@ func TestFeedbagService_DeleteItem(t *testing.T) {
 					Return(params.err)
 			}
 			messageRelayer := newMockMessageRelayer(t)
-			for _, params := range tc.mockParams.messageRelayerParams.relayToOtherInstancesParams {
+			for _, params := range tc.mockParams.relayToOtherInstancesParams {
 				messageRelayer.EXPECT().
 					RelayToOtherInstances(mock.Anything, mock.Anything, params.message)
 			}
-			for _, params := range tc.mockParams.messageRelayerParams.relayToSelfParams {
+			for _, params := range tc.mockParams.relayToSelfParams {
 				messageRelayer.EXPECT().
 					RelayToSelf(mock.Anything, mock.Anything, params.message)
 			}
@@ -2922,11 +2922,11 @@ func TestFeedbagService_RequestAuthorizeToHost(t *testing.T) {
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			sessionRetriever := newMockSessionRetriever(t)
-			for _, params := range tt.mockParams.sessionRetrieverParams.retrieveSessionParams {
+			for _, params := range tt.mockParams.retrieveSessionParams {
 				sessionRetriever.EXPECT().RetrieveSession(params.screenName).Return(tt.buddySess)
 			}
 			messageRelayer := newMockMessageRelayer(t)
-			for _, params := range tt.mockParams.messageRelayerParams.relayToScreenNameParams {
+			for _, params := range tt.mockParams.relayToScreenNameParams {
 				messageRelayer.EXPECT().RelayToScreenName(matchContext(), params.screenName, params.message)
 			}
 
@@ -3359,7 +3359,7 @@ func TestFeedbagService_RespondAuthorizeToHost(t *testing.T) {
 			sessionRetriever.EXPECT().
 				RetrieveSession(state.NewIdentScreenName(tt.bodyIn.ScreenName)).
 				Return(tt.buddySess)
-			for _, params := range tt.mockParams.sessionRetrieverParams.retrieveSessionParams {
+			for _, params := range tt.mockParams.retrieveSessionParams {
 				sessionRetriever.EXPECT().RetrieveSession(params.screenName).Return(params.result)
 			}
 
@@ -3369,20 +3369,20 @@ func TestFeedbagService_RespondAuthorizeToHost(t *testing.T) {
 			}
 
 			messageRelayer := newMockMessageRelayer(t)
-			for _, params := range tt.mockParams.messageRelayerParams.relayToScreenNameParams {
+			for _, params := range tt.mockParams.relayToScreenNameParams {
 				messageRelayer.EXPECT().RelayToScreenName(matchContext(), params.screenName, params.message)
 			}
 
 			feedbagManager := newMockFeedbagManager(t)
-			for _, params := range tt.mockParams.feedbagManagerParams.feedbagParams {
+			for _, params := range tt.mockParams.feedbagParams {
 				feedbagManager.EXPECT().Feedbag(matchContext(), params.screenName).Return(params.results, params.err)
 			}
-			for _, params := range tt.mockParams.feedbagManagerParams.feedbagUpsertParams {
+			for _, params := range tt.mockParams.feedbagUpsertParams {
 				feedbagManager.EXPECT().FeedbagUpsert(matchContext(), params.screenName, params.items).Return(nil)
 			}
 
 			relationshipFetcher := newMockRelationshipFetcher(t)
-			for _, params := range tt.mockParams.relationshipFetcherParams.relationshipParams {
+			for _, params := range tt.mockParams.relationshipParams {
 				relationshipFetcher.EXPECT().Relationship(matchContext(), params.me, params.them).Return(params.result, params.err)
 			}
 
@@ -3701,25 +3701,25 @@ func TestFeedbagService_PreAuthorizeBuddy(t *testing.T) {
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			contactPreAuth := newMockContactPreAuthorizer(t)
-			for _, params := range tt.mockParams.contactPreAuthorizerParams.recordPreAuthParams {
+			for _, params := range tt.mockParams.recordPreAuthParams {
 				contactPreAuth.EXPECT().RecordPreAuth(matchContext(), params.owner, params.buddy).Return(params.err)
 			}
 			sessionRetriever := newMockSessionRetriever(t)
-			for _, params := range tt.mockParams.sessionRetrieverParams.retrieveSessionParams {
+			for _, params := range tt.mockParams.retrieveSessionParams {
 				sessionRetriever.EXPECT().RetrieveSession(params.screenName).Return(tt.buddySess)
 			}
 			feedbagManager := newMockFeedbagManager(t)
-			for _, params := range tt.mockParams.feedbagManagerParams.feedbagParams {
+			for _, params := range tt.mockParams.feedbagParams {
 				feedbagManager.EXPECT().
 					Feedbag(matchContext(), params.screenName).
 					Return(params.results, params.err)
 			}
 			relationshipFetcher := newMockRelationshipFetcher(t)
-			for _, params := range tt.mockParams.relationshipFetcherParams.relationshipParams {
+			for _, params := range tt.mockParams.relationshipParams {
 				relationshipFetcher.EXPECT().Relationship(matchContext(), params.me, params.them).Return(params.result, params.err)
 			}
 			messageRelayer := newMockMessageRelayer(t)
-			for _, params := range tt.mockParams.messageRelayerParams.relayToScreenNameParams {
+			for _, params := range tt.mockParams.relayToScreenNameParams {
 				messageRelayer.EXPECT().RelayToScreenName(matchContext(), params.screenName, params.message)
 			}
 
@@ -4225,23 +4225,23 @@ func TestFeedbagService_ForwardICQAuthEvents(t *testing.T) {
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			sessionRetriever := newMockSessionRetriever(t)
-			for _, params := range tt.mockParams.sessionRetrieverParams.retrieveSessionParams {
+			for _, params := range tt.mockParams.retrieveSessionParams {
 				sessionRetriever.EXPECT().RetrieveSession(params.screenName).Return(tt.buddySess)
 			}
 			contactPreAuth := newMockContactPreAuthorizer(t)
-			for _, params := range tt.mockParams.contactPreAuthorizerParams.recordPreAuthParams {
+			for _, params := range tt.mockParams.recordPreAuthParams {
 				contactPreAuth.EXPECT().RecordPreAuth(matchContext(), params.owner, params.buddy).Return(params.err)
 			}
 			feedbagManager := newMockFeedbagManager(t)
-			for _, params := range tt.mockParams.feedbagManagerParams.feedbagParams {
+			for _, params := range tt.mockParams.feedbagParams {
 				feedbagManager.EXPECT().Feedbag(matchContext(), params.screenName).Return(params.results, params.err)
 			}
 			relationshipFetcher := newMockRelationshipFetcher(t)
-			for _, params := range tt.mockParams.relationshipFetcherParams.relationshipParams {
+			for _, params := range tt.mockParams.relationshipParams {
 				relationshipFetcher.EXPECT().Relationship(matchContext(), params.me, params.them).Return(params.result, params.err)
 			}
 			messageRelayer := newMockMessageRelayer(t)
-			for _, params := range tt.mockParams.messageRelayerParams.relayToScreenNameParams {
+			for _, params := range tt.mockParams.relayToScreenNameParams {
 				messageRelayer.EXPECT().RelayToScreenName(matchContext(), params.screenName, params.message)
 			}
 

+ 0 - 41
foodgroup/helpers_test.go

@@ -143,19 +143,10 @@ type icqUserFinderParams struct {
 	findByDetailsParams
 	findByEmailParams
 	findByInterestsParams
-	findByKeywordParams
 	findByUINParams
 	searchICQUsersParams
 }
 
-// findByKeywordParams is the list of parameters passed at the mock
-// ICQUserFinder.FindByKeyword call site
-type findByKeywordParams []struct {
-	keyword string
-	result  []state.User
-	err     error
-}
-
 // findByUINParams is the list of parameters passed at the mock
 // ICQUserFinder.FindByUIN call site
 type findByUINParams []struct {
@@ -205,7 +196,6 @@ type icqUserUpdaterParams struct {
 	setAffiliationsParams
 	setBasicInfoParams
 	setFullInfoParams
-	setHomepageCategoryParams
 	setInterestsParams
 	setMoreInfoParams
 	setPermissionsParams
@@ -277,14 +267,6 @@ type setPermissionsParams []struct {
 	err  error
 }
 
-// setHomepageCategoryParams is the list of parameters passed at the mock
-// ICQUserUpdater.SetHomepageCategory call site
-type setHomepageCategoryParams []struct {
-	name state.IdentScreenName
-	data state.ICQHomepageCategory
-	err  error
-}
-
 // bartItemManagerParams is a helper struct that contains mock parameters for
 // BARTItemManager methods
 type bartItemManagerParams struct {
@@ -359,7 +341,6 @@ type removeSessionParams []struct {
 type feedbagManagerParams struct {
 	adjacentUsersParams
 	feedbagUpsertParams
-	buddiesParams
 	feedbagParams
 	feedbagLastModifiedParams
 	feedbagDeleteParams
@@ -381,13 +362,6 @@ type feedbagUpsertParams []struct {
 	items      []wire.FeedbagItem
 }
 
-// buddiesParams is the list of parameters passed at the mock
-// FeedbagManager.Buddies call site
-type buddiesParams []struct {
-	screenName state.IdentScreenName
-	results    []state.IdentScreenName
-}
-
 // useParams is the list of parameters passed at the mock
 // FeedbagManager.Use call site
 type useParams []struct {
@@ -642,18 +616,9 @@ type setPDModeParams []struct {
 // cookieBakerParams is a helper struct that contains mock parameters for
 // CookieBaker methods
 type cookieBakerParams struct {
-	cookieCrackParams
 	cookieIssueParams
 }
 
-// cookieCrackParams is the list of parameters passed at the mock
-// CookieBaker.Crack call site
-type cookieCrackParams []struct {
-	cookieIn []byte
-	dataOut  []byte
-	err      error
-}
-
 // cookieIssueParams is the list of parameters passed at the mock
 // CookieBaker.Issue call site
 type cookieIssueParams []struct {
@@ -997,12 +962,6 @@ func newTestInstance(screenName state.DisplayScreenName, options ...func(instanc
 	return instance
 }
 
-func userInfoWithBARTIcon(instance *state.SessionInstance, bid wire.BARTID) wire.TLVUserInfo {
-	info := instance.Session().TLVUserInfo()
-	info.Append(wire.NewTLVBE(wire.OServiceUserInfoBARTInfo, bid))
-	return info
-}
-
 // matchSession matches a mock call based session ident screen name.
 func matchSession(mustMatch state.IdentScreenName) interface{} {
 	return mock.MatchedBy(func(s *state.SessionInstance) bool {

+ 4 - 4
foodgroup/icbm.go

@@ -163,7 +163,7 @@ func (s *ICBMService) ChannelMsgToHost(ctx context.Context, instance *state.Sess
 		TLVRestBlock: wire.TLVRestBlock{},
 	}
 
-	for _, tlv := range inBody.TLVRestBlock.TLVList {
+	for _, tlv := range inBody.TLVList {
 		if tlv.Tag == wire.ICBMTLVRequestHostAck {
 			// Exclude this TLV, because its presence breaks chat invitations
 			// on macOS client v4.0.9.
@@ -215,7 +215,7 @@ func (s *ICBMService) ChannelMsgToHost(ctx context.Context, instance *state.Sess
 
 	s.convoTracker.trackConvo(time.Now(), instance.IdentScreenName(), recipSess.IdentScreenName())
 
-	if _, requestedConfirmation := inBody.TLVRestBlock.Bytes(wire.ICBMTLVRequestHostAck); !requestedConfirmation {
+	if _, requestedConfirmation := inBody.Bytes(wire.ICBMTLVRequestHostAck); !requestedConfirmation {
 		// don't ack message
 		return nil, nil
 	}
@@ -289,7 +289,7 @@ func (s *ICBMService) sendOfflineMessage(ctx context.Context, instance *state.Se
 		return newICBMErr(inFrame.RequestID, wire.ErrorCodeNotLoggedOn), nil
 	}
 
-	if _, requestedConfirmation := inBody.TLVRestBlock.Bytes(wire.ICBMTLVRequestHostAck); requestedConfirmation {
+	if _, requestedConfirmation := inBody.Bytes(wire.ICBMTLVRequestHostAck); requestedConfirmation {
 		// ack message back to sender
 		return &wire.SNACMessage{
 			Frame: wire.SNACFrame{
@@ -570,7 +570,7 @@ func (s *ICBMService) OfflineRetrieve(ctx context.Context, instance *state.Sessi
 			TLVRestBlock: wire.TLVRestBlock{},
 		}
 
-		for _, tlv := range event.Message.TLVRestBlock.TLVList {
+		for _, tlv := range event.Message.TLVList {
 			clientIM.Append(tlv)
 		}
 		clientIM.Append(wire.NewTLVBE(wire.ICBMTLVSendTime, uint32(event.Sent.Unix())))

+ 7 - 7
foodgroup/icbm_test.go

@@ -2061,13 +2061,13 @@ func TestICBMService_ChannelMsgToHost(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			discardLogger := slog.New(slog.NewTextHandler(io.Discard, nil))
 			relationshipFetcher := newMockRelationshipFetcher(t)
-			for _, item := range tc.mockParams.relationshipFetcherParams.relationshipParams {
+			for _, item := range tc.mockParams.relationshipParams {
 				relationshipFetcher.EXPECT().
 					Relationship(matchContext(), item.me, item.them).
 					Return(item.result, item.err)
 			}
 			sessionRetriever := newMockSessionRetriever(t)
-			for _, item := range tc.mockParams.sessionRetrieverParams.retrieveSessionParams {
+			for _, item := range tc.mockParams.retrieveSessionParams {
 				sessionRetriever.EXPECT().
 					RetrieveSession(item.screenName).
 					Return(item.result)
@@ -2422,7 +2422,7 @@ func TestICBMService_ClientEvent(t *testing.T) {
 	for _, tc := range cases {
 		t.Run(tc.name, func(t *testing.T) {
 			relationshipFetcher := newMockRelationshipFetcher(t)
-			for _, item := range tc.mockParams.relationshipFetcherParams.relationshipParams {
+			for _, item := range tc.mockParams.relationshipParams {
 				relationshipFetcher.EXPECT().
 					Relationship(matchContext(), item.me, item.them).
 					Return(item.result, item.err)
@@ -2868,13 +2868,13 @@ func TestICBMService_EvilRequest(t *testing.T) {
 	for _, tc := range cases {
 		t.Run(tc.name, func(t *testing.T) {
 			relationshipFetcher := newMockRelationshipFetcher(t)
-			for _, item := range tc.mockParams.relationshipFetcherParams.relationshipParams {
+			for _, item := range tc.mockParams.relationshipParams {
 				relationshipFetcher.EXPECT().
 					Relationship(matchContext(), item.me, item.them).
 					Return(item.result, item.err)
 			}
 			sessionRetriever := newMockSessionRetriever(t)
-			for _, item := range tc.mockParams.sessionRetrieverParams.retrieveSessionParams {
+			for _, item := range tc.mockParams.retrieveSessionParams {
 				sessionRetriever.EXPECT().
 					RetrieveSession(item.screenName).
 					Return(item.result)
@@ -2915,7 +2915,7 @@ func TestICBMService_EvilRequest(t *testing.T) {
 				wg.Add(1)
 				go func() {
 					defer wg.Done()
-					for _, sess := range tc.mockParams.sessionRetrieverParams.retrieveSessionParams {
+					for _, sess := range tc.mockParams.retrieveSessionParams {
 						<-sess.result.WarningCh()
 					}
 				}()
@@ -2933,7 +2933,7 @@ func TestICBMService_EvilRequest(t *testing.T) {
 func TestICBMService_ParameterQuery(t *testing.T) {
 	svc := NewICBMService(nil, nil, nil, nil, nil, nil, nil, nil, wire.DefaultSNACRateLimits(), slog.Default())
 
-	have := svc.ParameterQuery(nil, wire.SNACFrame{RequestID: 1234})
+	have := svc.ParameterQuery(context.TODO(), wire.SNACFrame{RequestID: 1234})
 	want := wire.SNACMessage{
 		Frame: wire.SNACFrame{
 			FoodGroup: wire.ICBM,

+ 1 - 1
foodgroup/locate_test.go

@@ -302,7 +302,7 @@ func TestLocateService_UserInfoQuery(t *testing.T) {
 	for _, tc := range cases {
 		t.Run(tc.name, func(t *testing.T) {
 			relationshipFetcher := newMockRelationshipFetcher(t)
-			for _, params := range tc.mockParams.relationshipFetcherParams.relationshipParams {
+			for _, params := range tc.mockParams.relationshipParams {
 				relationshipFetcher.EXPECT().
 					Relationship(matchContext(), params.me, params.them).
 					Return(params.result, params.err)

+ 1 - 3
foodgroup/oservice.go

@@ -102,9 +102,7 @@ func (s OServiceService) ClientVersions(ctx context.Context, instance *state.Ses
 				SubGroup:  wire.OServiceHostVersions,
 				RequestID: inFrame.RequestID,
 			},
-			Body: wire.SNAC_0x01_0x18_OServiceHostVersions{
-				Versions: inBody.Versions,
-			},
+			Body: wire.SNAC_0x01_0x18_OServiceHostVersions(inBody),
 		},
 		{
 			Frame: wire.SNACFrame{

+ 4 - 4
foodgroup/oservice_test.go

@@ -1075,7 +1075,7 @@ func TestOServiceService_SetUserInfoFields(t *testing.T) {
 				logger:           slog.Default(),
 				buddyBroadcaster: buddyUpdateBroadcaster,
 			}
-			outputSNAC, err := svc.SetUserInfoFields(nil, tc.instance, tc.inputSNAC.Frame,
+			outputSNAC, err := svc.SetUserInfoFields(context.TODO(), tc.instance, tc.inputSNAC.Frame,
 				tc.inputSNAC.Body.(wire.SNAC_0x01_0x1E_OServiceSetUserInfoFields))
 			assert.ErrorIs(t, err, tc.expectErr)
 			if tc.expectErr != nil {
@@ -2163,7 +2163,7 @@ func TestOServiceService_IdleNotification(t *testing.T) {
 				logger:           slog.Default(),
 				buddyBroadcaster: buddyUpdateBroadcaster,
 			}
-			haveErr := svc.IdleNotification(nil, tt.instance, tt.bodyIn)
+			haveErr := svc.IdleNotification(context.TODO(), tt.instance, tt.bodyIn)
 			assert.ErrorIs(t, tt.wantErr, haveErr)
 		})
 	}
@@ -2726,13 +2726,13 @@ func TestOServiceService_ClientOnline(t *testing.T) {
 					RelayToScreenName(matchContext(), params.cookie, params.screenName, params.message)
 			}
 			profileManager := newMockProfileManager(t)
-			for _, params := range tt.mockParams.profileManagerParams.retrieveProfileParams {
+			for _, params := range tt.mockParams.retrieveProfileParams {
 				profileManager.EXPECT().
 					Profile(matchContext(), params.screenName).
 					Return(params.result, params.err)
 			}
 			offlineMessageManager := newMockOfflineMessageManager(t)
-			for _, params := range tt.mockParams.offlineMessageManagerParams.setOfflineMsgCountParams {
+			for _, params := range tt.mockParams.setOfflineMsgCountParams {
 				offlineMessageManager.EXPECT().
 					SetOfflineMsgCount(matchContext(), params.screenName, params.count).
 					Return(params.err)

+ 1 - 1
foodgroup/permit_deny_test.go

@@ -13,7 +13,7 @@ import (
 func TestPermitDenyService_RightsQuery(t *testing.T) {
 	svc := NewPermitDenyService(nil, nil, nil, nil, nil)
 
-	have := svc.RightsQuery(nil, wire.SNACFrame{RequestID: 1234})
+	have := svc.RightsQuery(context.TODO(), wire.SNACFrame{RequestID: 1234})
 	want := wire.SNACMessage{
 		Frame: wire.SNACFrame{
 			FoodGroup: wire.PermitDeny,

+ 0 - 7
server/http/helpers_test.go

@@ -304,16 +304,9 @@ type retrieveProfileParams []struct {
 // sessionRetrieverParams is a helper struct that contains mock parameters for
 // SessionRetriever methods
 type sessionRetrieverParams struct {
-	sessionRetrieverAllSessionsParams
 	retrieveSessionByNameParams
 }
 
-// sessionRetrieverAllSessionsParams is the list of parameters passed at the mock
-// SessionRetriever.AllSessions call site
-type sessionRetrieverAllSessionsParams []struct {
-	result []*state.Session
-}
-
 // retrieveSessionByNameParams is the list of parameters passed at the mock
 // SessionRetriever.RetrieveSession call site
 type retrieveSessionByNameParams []struct {

+ 4 - 4
server/http/mgmt_api.go

@@ -695,7 +695,7 @@ func getUserBuddyIconHandler(w http.ResponseWriter, r *http.Request, u UserManag
 		return
 	}
 	w.Header().Set("Content-Type", http.DetectContentType(icon))
-	w.Write(icon)
+	_, _ = w.Write(icon)
 }
 
 // getUserAccountHandler handles the GET /user/{screenname}/account endpoint.
@@ -1480,7 +1480,7 @@ func getBARTHandler(w http.ResponseWriter, r *http.Request, bartAssetManager BAR
 	}
 
 	w.Header().Set("Content-Type", "application/octet-stream")
-	w.Write(body)
+	_, _ = w.Write(body)
 }
 
 // postBARTHandler handles the POST /bart endpoint.
@@ -1533,7 +1533,7 @@ func postBARTHandler(w http.ResponseWriter, r *http.Request, bartAssetManager BA
 		Hash: hex.EncodeToString(hashBytes),
 		Type: bartType,
 	}
-	json.NewEncoder(w).Encode(response)
+	_ = json.NewEncoder(w).Encode(response)
 }
 
 // deleteBARTHandler handles the DELETE /bart/{hash} endpoint.
@@ -1564,7 +1564,7 @@ func deleteBARTHandler(w http.ResponseWriter, r *http.Request, bartAssetManager
 	}
 
 	msg := messageBody{Message: "BART asset deleted successfully."}
-	json.NewEncoder(w).Encode(msg)
+	_ = json.NewEncoder(w).Encode(msg)
 }
 
 // getFeedbagBuddyHandler handles the GET /feedbag/{screen_name}/group endpoint.

+ 37 - 37
server/http/mgmt_api_test.go

@@ -294,7 +294,7 @@ func TestSessionHandlerScreenname_DELETE(t *testing.T) {
 			responseRecorder := httptest.NewRecorder()
 
 			sessionRetriever := newMockSessionRetriever(t)
-			for _, params := range tc.mockParams.sessionRetrieverParams.retrieveSessionByNameParams {
+			for _, params := range tc.mockParams.retrieveSessionByNameParams {
 				sessionRetriever.EXPECT().
 					RetrieveSession(params.screenName).
 					Return(params.result)
@@ -493,31 +493,31 @@ func TestUserAccountHandler_GET(t *testing.T) {
 			responseRecorder := httptest.NewRecorder()
 
 			userManager := newMockUserManager(t)
-			for _, params := range tc.mockParams.userManagerParams.getUserParams {
+			for _, params := range tc.mockParams.getUserParams {
 				userManager.EXPECT().
 					User(matchContext(), params.screenName).
 					Return(params.result, params.err)
 			}
 
 			accountManager := newMockAccountManager(t)
-			for _, params := range tc.mockParams.accountManagerParams.EmailAddressParams {
+			for _, params := range tc.mockParams.EmailAddressParams {
 				accountManager.EXPECT().
 					EmailAddress(matchContext(), params.screenName).
 					Return(params.result, params.err)
 			}
-			for _, params := range tc.mockParams.accountManagerParams.RegStatusParams {
+			for _, params := range tc.mockParams.RegStatusParams {
 				accountManager.EXPECT().
 					RegStatus(matchContext(), params.screenName).
 					Return(params.result, params.err)
 			}
-			for _, params := range tc.mockParams.accountManagerParams.ConfirmStatusParams {
+			for _, params := range tc.mockParams.ConfirmStatusParams {
 				accountManager.EXPECT().
 					ConfirmStatus(matchContext(), params.screenName).
 					Return(params.result, params.err)
 			}
 
 			profileRetriever := newMockProfileRetriever(t)
-			for _, params := range tc.mockParams.profileRetrieverParams.retrieveProfileParams {
+			for _, params := range tc.mockParams.retrieveProfileParams {
 				profileRetriever.EXPECT().
 					Profile(matchContext(), params.screenName).
 					Return(params.result, params.err)
@@ -747,19 +747,19 @@ func TestUserAccountHandler_PATCH(t *testing.T) {
 			responseRecorder := httptest.NewRecorder()
 
 			userManager := newMockUserManager(t)
-			for _, params := range tc.mockParams.userManagerParams.getUserParams {
+			for _, params := range tc.mockParams.getUserParams {
 				userManager.EXPECT().
 					User(matchContext(), params.screenName).
 					Return(params.result, params.err)
 			}
 
 			accountManager := newMockAccountManager(t)
-			for _, params := range tc.mockParams.accountManagerParams.updateSuspendedStatusParams {
+			for _, params := range tc.mockParams.updateSuspendedStatusParams {
 				accountManager.EXPECT().
 					UpdateSuspendedStatus(matchContext(), params.suspendedStatus, params.screenName).
 					Return(params.err)
 			}
-			for _, params := range tc.mockParams.accountManagerParams.setBotStatusParams {
+			for _, params := range tc.mockParams.setBotStatusParams {
 				accountManager.EXPECT().
 					SetBotStatus(matchContext(), params.isBot, params.screenName).
 					Return(params.err)
@@ -1007,21 +1007,21 @@ func TestUserBuddyIconHandler_GET(t *testing.T) {
 			responseRecorder := httptest.NewRecorder()
 
 			userManager := newMockUserManager(t)
-			for _, params := range tc.mockParams.userManagerParams.getUserParams {
+			for _, params := range tc.mockParams.getUserParams {
 				userManager.EXPECT().
 					User(matchContext(), params.screenName).
 					Return(params.result, params.err)
 			}
 
 			feedbagRetriever := newMockFeedBagRetriever(t)
-			for _, params := range tc.mockParams.feedBagRetrieverParams.buddyIconMetadataParams {
+			for _, params := range tc.mockParams.buddyIconMetadataParams {
 				feedbagRetriever.EXPECT().
 					BuddyIconMetadata(matchContext(), params.screenName).
 					Return(params.result, params.err)
 			}
 
 			bartRetriever := newMockBARTAssetManager(t)
-			for _, params := range tc.mockParams.bartAssetManagerParams.bartItemParams {
+			for _, params := range tc.mockParams.bartItemParams {
 				bartRetriever.EXPECT().
 					BARTItem(matchContext(), params.hash).
 					Return(params.result, params.err)
@@ -1118,7 +1118,7 @@ func TestUserHandler_GET(t *testing.T) {
 			responseRecorder := httptest.NewRecorder()
 
 			userManager := newMockUserManager(t)
-			for _, params := range tc.mockParams.userManagerParams.allUsersParams {
+			for _, params := range tc.mockParams.allUsersParams {
 				userManager.EXPECT().
 					AllUsers(matchContext()).
 					Return(params.result, params.err)
@@ -1328,7 +1328,7 @@ func TestUserHandler_DELETE(t *testing.T) {
 			responseRecorder := httptest.NewRecorder()
 
 			userManager := newMockUserManager(t)
-			for _, params := range tc.mockParams.userManagerParams.deleteUserParams {
+			for _, params := range tc.mockParams.deleteUserParams {
 				userManager.EXPECT().
 					DeleteUser(matchContext(), params.screenName).
 					Return(params.err)
@@ -1436,7 +1436,7 @@ func TestUserPasswordHandler_PUT(t *testing.T) {
 			responseRecorder := httptest.NewRecorder()
 
 			userManager := newMockUserManager(t)
-			for _, params := range tc.mockParams.userManagerParams.setUserPasswordParams {
+			for _, params := range tc.mockParams.setUserPasswordParams {
 				userManager.EXPECT().
 					SetUserPassword(matchContext(), params.screenName, params.newPassword).
 					Return(params.err)
@@ -1558,14 +1558,14 @@ func TestPublicChatHandler_GET(t *testing.T) {
 			responseRecorder := httptest.NewRecorder()
 
 			chatRoomRetriever := newMockChatRoomRetriever(t)
-			for _, params := range tc.mockParams.chatRoomRetrieverParams.allChatRoomsParams {
+			for _, params := range tc.mockParams.allChatRoomsParams {
 				chatRoomRetriever.EXPECT().
 					AllChatRooms(matchContext(), params.exchange).
 					Return(params.result, params.err)
 			}
 
 			chatSessionRetriever := newMockChatSessionRetriever(t)
-			for _, params := range tc.mockParams.chatSessionRetrieverParams.chatSessionRetrieverAllSessionsParams {
+			for _, params := range tc.mockParams.chatSessionRetrieverAllSessionsParams {
 				chatSessionRetriever.EXPECT().
 					AllSessions(params.cookie).
 					Return(params.result)
@@ -1661,7 +1661,7 @@ func TestDeletePublicChatHandler(t *testing.T) {
 			responseRecorder := httptest.NewRecorder()
 
 			chatRoomDeleter := newMockChatRoomDeleter(t)
-			for _, params := range tc.mockParams.chatRoomDeleterParams.deleteChatRoomsParams {
+			for _, params := range tc.mockParams.deleteChatRoomsParams {
 				chatRoomDeleter.EXPECT().
 					DeleteChatRooms(matchContext(), params.exchange, params.names).
 					Return(params.err)
@@ -1783,14 +1783,14 @@ func TestPrivateChatHandler_GET(t *testing.T) {
 			responseRecorder := httptest.NewRecorder()
 
 			chatRoomRetriever := newMockChatRoomRetriever(t)
-			for _, params := range tc.mockParams.chatRoomRetrieverParams.allChatRoomsParams {
+			for _, params := range tc.mockParams.allChatRoomsParams {
 				chatRoomRetriever.EXPECT().
 					AllChatRooms(matchContext(), params.exchange).
 					Return(params.result, params.err)
 			}
 
 			chatSessionRetriever := newMockChatSessionRetriever(t)
-			for _, params := range tc.mockParams.chatSessionRetrieverParams.chatSessionRetrieverAllSessionsParams {
+			for _, params := range tc.mockParams.chatSessionRetrieverAllSessionsParams {
 				chatSessionRetriever.EXPECT().
 					AllSessions(params.cookie).
 					Return(params.result)
@@ -1854,7 +1854,7 @@ func TestInstantMessageHandler_POST(t *testing.T) {
 			for _, params := range tc.relayToScreenNameInputs {
 				validateSNAC := func(msg wire.SNACMessage) bool {
 					body := msg.Body.(wire.SNAC_0x04_0x07_ICBMChannelMsgToClient)
-					assert.Equal(t, params.sender.String(), body.TLVUserInfo.ScreenName)
+					assert.Equal(t, params.sender.String(), body.ScreenName)
 
 					b, ok := body.Bytes(wire.ICBMTLVAOLIMData)
 					assert.True(t, ok)
@@ -2641,7 +2641,7 @@ func TestBARTByTypeHandler_GET(t *testing.T) {
 			responseRecorder := httptest.NewRecorder()
 
 			mockBARTManager := newMockBARTAssetManager(t)
-			for _, params := range tc.mockParams.bartAssetManagerParams.listBARTItemsParams {
+			for _, params := range tc.mockParams.listBARTItemsParams {
 				mockBARTManager.EXPECT().
 					ListBARTItems(matchContext(), params.itemType).
 					Return(params.result, params.err)
@@ -2740,7 +2740,7 @@ func TestBARTHandler_GET(t *testing.T) {
 			responseRecorder := httptest.NewRecorder()
 
 			mockBARTManager := newMockBARTAssetManager(t)
-			for _, params := range tc.mockParams.bartAssetManagerParams.bartItemParams {
+			for _, params := range tc.mockParams.bartItemParams {
 				mockBARTManager.EXPECT().
 					BARTItem(matchContext(), params.hash).
 					Return(params.result, params.err)
@@ -2894,7 +2894,7 @@ func TestBARTHandler_POST(t *testing.T) {
 			responseRecorder := httptest.NewRecorder()
 
 			mockBARTManager := newMockBARTAssetManager(t)
-			for _, params := range tc.mockParams.bartAssetManagerParams.insertBARTItemParams {
+			for _, params := range tc.mockParams.insertBARTItemParams {
 				mockBARTManager.EXPECT().
 					InsertBARTItem(matchContext(), params.hash, params.blob, params.itemType).
 					Return(params.err)
@@ -2988,7 +2988,7 @@ func TestBARTHandler_DELETE(t *testing.T) {
 			responseRecorder := httptest.NewRecorder()
 
 			mockBARTManager := newMockBARTAssetManager(t)
-			for _, params := range tc.mockParams.bartAssetManagerParams.deleteBARTItemParams {
+			for _, params := range tc.mockParams.deleteBARTItemParams {
 				mockBARTManager.EXPECT().
 					DeleteBARTItem(matchContext(), params.hash).
 					Return(params.err)
@@ -3158,7 +3158,7 @@ func TestFeedbagBuddyHandler_GET(t *testing.T) {
 			responseRecorder := httptest.NewRecorder()
 
 			feedbagManager := newMockFeedbagManager(t)
-			for _, params := range tc.mockParams.feedbagManagerParams.feedbagParams {
+			for _, params := range tc.mockParams.feedbagParams {
 				feedbagManager.EXPECT().
 					Feedbag(matchContext(), params.screenName).
 					Return(params.result, params.err)
@@ -3819,26 +3819,26 @@ func TestFeedbagBuddyHandler_PUT(t *testing.T) {
 			messageRelayer := newMockMessageRelayer(t)
 			buddyBroadcaster := newMockBuddyBroadcaster(t)
 
-			for _, params := range tc.mockParams.feedbagManagerParams.feedbagParams {
+			for _, params := range tc.mockParams.feedbagParams {
 				feedbagManager.EXPECT().
 					Feedbag(matchContext(), params.screenName).
 					Return(params.result, params.err)
 			}
-			for _, params := range tc.mockParams.feedbagManagerParams.feedbagUpsertParams {
+			for _, params := range tc.mockParams.feedbagUpsertParams {
 				feedbagManager.EXPECT().
 					FeedbagUpsert(matchContext(), params.screenName, params.items).
 					Return(params.err)
 			}
-			for _, params := range tc.mockParams.sessionRetrieverParams.retrieveSessionByNameParams {
+			for _, params := range tc.mockParams.retrieveSessionByNameParams {
 				sessionRetriever.EXPECT().
 					RetrieveSession(params.screenName).
 					Return(params.result)
 			}
-			for _, params := range tc.mockParams.messageRelayerParams.relayToScreenNameParams {
+			for _, params := range tc.mockParams.relayToScreenNameParams {
 				messageRelayer.EXPECT().
 					RelayToScreenName(matchContext(), params.screenName, params.msg)
 			}
-			for _, params := range tc.mockParams.buddyBroadcasterParams.broadcastVisibilityParams {
+			for _, params := range tc.mockParams.broadcastVisibilityParams {
 				// Use mock.MatchedBy to match any SessionInstance, since we're mainly verifying filter and sendDepartures
 				buddyBroadcaster.EXPECT().
 					BroadcastVisibility(
@@ -4547,31 +4547,31 @@ func TestFeedbagBuddyHandler_DELETE(t *testing.T) {
 			messageRelayer := newMockMessageRelayer(t)
 			buddyBroadcaster := newMockBuddyBroadcaster(t)
 
-			for _, params := range tc.mockParams.feedbagManagerParams.feedbagParams {
+			for _, params := range tc.mockParams.feedbagParams {
 				feedbagManager.EXPECT().
 					Feedbag(matchContext(), params.screenName).
 					Return(params.result, params.err)
 			}
-			for _, params := range tc.mockParams.feedbagManagerParams.feedbagUpsertParams {
+			for _, params := range tc.mockParams.feedbagUpsertParams {
 				feedbagManager.EXPECT().
 					FeedbagUpsert(matchContext(), params.screenName, params.items).
 					Return(params.err)
 			}
-			for _, params := range tc.mockParams.feedbagManagerParams.feedbagDeleteParams {
+			for _, params := range tc.mockParams.feedbagDeleteParams {
 				feedbagManager.EXPECT().
 					FeedbagDelete(matchContext(), params.screenName, params.items).
 					Return(params.err)
 			}
-			for _, params := range tc.mockParams.sessionRetrieverParams.retrieveSessionByNameParams {
+			for _, params := range tc.mockParams.retrieveSessionByNameParams {
 				sessionRetriever.EXPECT().
 					RetrieveSession(params.screenName).
 					Return(params.result)
 			}
-			for _, params := range tc.mockParams.messageRelayerParams.relayToScreenNameParams {
+			for _, params := range tc.mockParams.relayToScreenNameParams {
 				messageRelayer.EXPECT().
 					RelayToScreenName(matchContext(), params.screenName, params.msg)
 			}
-			for _, params := range tc.mockParams.buddyBroadcasterParams.broadcastVisibilityParams {
+			for _, params := range tc.mockParams.broadcastVisibilityParams {
 				buddyBroadcaster.EXPECT().
 					BroadcastVisibility(
 						matchContext(),

+ 5 - 5
server/icq_legacy/legacy_message_bridge.go

@@ -171,7 +171,7 @@ func (b *LegacyMessageBridge) handleBuddyArrived(session *LegacySession, msg wir
 		return
 	}
 
-	oscarStatus, _ := arrived.TLVList.Uint32BE(wire.OServiceUserInfoStatus)
+	oscarStatus, _ := arrived.Uint32BE(wire.OServiceUserInfoStatus)
 	legacyStatus := oscarStatusToLegacy(oscarStatus)
 
 	b.logger.Debug("OSCAR->legacy buddy arrived",
@@ -355,8 +355,8 @@ func (b *LegacyMessageBridge) handleICBMMessage(session *LegacySession, msg wire
 	b.logger.Debug("handleICBMMessage: received",
 		"uin", session.UIN,
 		"channel_id", clientMsg.ChannelID,
-		"from_screen_name", clientMsg.TLVUserInfo.ScreenName,
-		"tlv_count", len(clientMsg.TLVRestBlock.TLVList),
+		"from_screen_name", clientMsg.ScreenName,
+		"tlv_count", len(clientMsg.TLVList),
 	)
 
 	// Handle channel 1 (IM) and channel 4 (ICQ) messages
@@ -369,11 +369,11 @@ func (b *LegacyMessageBridge) handleICBMMessage(session *LegacySession, msg wire
 		return
 	}
 
-	fromUIN, ok := parseUIN(clientMsg.TLVUserInfo.ScreenName)
+	fromUIN, ok := parseUIN(clientMsg.ScreenName)
 	if !ok {
 		b.logger.Debug("handleICBMMessage: parseUIN failed",
 			"uin", session.UIN,
-			"screen_name", clientMsg.TLVUserInfo.ScreenName,
+			"screen_name", clientMsg.ScreenName,
 		)
 		return // AIM screen name - can't represent as legacy UIN
 	}

+ 2 - 2
server/icq_legacy/server.go

@@ -126,7 +126,7 @@ func (s *LegacyServer) Stop() error {
 
 	// Close the UDP socket to unblock the receive loop
 	if s.conn != nil {
-		s.conn.Close()
+		_ = s.conn.Close()
 	}
 
 	// Wait for all goroutines to finish
@@ -150,7 +150,7 @@ func (s *LegacyServer) receiveLoop(ctx context.Context) {
 		}
 
 		// Set read deadline to allow periodic checking of stop signal
-		s.conn.SetReadDeadline(time.Now().Add(1 * time.Second))
+		_ = s.conn.SetReadDeadline(time.Now().Add(1 * time.Second))
 
 		n, addr, err := s.conn.ReadFromUDP(buf)
 		if err != nil {

+ 15 - 15
server/icq_legacy/v2_handler.go

@@ -261,7 +261,7 @@ func (h *V2Handler) handleLogoff(session *LegacySession, pkt *V2ClientPacket) er
 
 	// Notify contacts that user is offline
 	h.sessions.BroadcastToContacts(session, func(contact *LegacySession) {
-		h.sendUserOffline(contact, session.UIN)
+		_ = h.sendUserOffline(contact, session.UIN)
 	})
 
 	// Notify OSCAR clients that this user went offline
@@ -358,7 +358,7 @@ func (h *V2Handler) handleContactList(session *LegacySession, pkt *V2ClientPacke
 				nil, // IP not available from service layer
 				0,   // Port not available from service layer
 			)
-			h.sender.SendToSession(session, onlinePkt)
+			_ = h.sender.SendToSession(session, onlinePkt)
 		}
 	}
 
@@ -430,9 +430,9 @@ func (h *V2Handler) handleSendMessage(session *LegacySession, pkt *V2ClientPacke
 			targetSession := h.sessions.GetSession(msg.ToUIN)
 			if targetSession != nil {
 				if h.dispatcher != nil {
-					h.dispatcher.SendOnlineMessage(targetSession, msg.FromUIN, msg.MsgType, msg.Message)
+					_ = h.dispatcher.SendOnlineMessage(targetSession, msg.FromUIN, msg.MsgType, msg.Message)
 				} else {
-					h.sendMessage(targetSession, msg.FromUIN, msg.MsgType, msg.Message)
+					_ = h.sendMessage(targetSession, msg.FromUIN, msg.MsgType, msg.Message)
 				}
 			}
 		}
@@ -500,14 +500,14 @@ func (h *V2Handler) handleSetStatus(session *LegacySession, pkt *V2ClientPacket)
 			targetSession := h.sessions.GetSession(target.UIN)
 			if targetSession != nil {
 				if h.dispatcher != nil {
-					h.dispatcher.SendStatusChange(targetSession, session.UIN, newStatus)
+					_ = h.dispatcher.SendStatusChange(targetSession, session.UIN, newStatus)
 				} else {
 					statusPkt := h.packetBuilder.BuildStatusUpdate(
 						targetSession.NextServerSeqNum(),
 						session.UIN,
 						newStatus,
 					)
-					h.sender.SendToSession(targetSession, statusPkt)
+					_ = h.sender.SendToSession(targetSession, statusPkt)
 				}
 			}
 		}
@@ -918,24 +918,24 @@ func (h *V2Handler) handleUserAdd(session *LegacySession, pkt *V2ClientPacket) e
 	targetSession := h.sessions.GetSession(targetUIN)
 	if targetSession != nil {
 		if h.dispatcher != nil {
-			h.dispatcher.SendUserOnline(session, targetUIN, targetSession.GetStatus())
+			_ = h.dispatcher.SendUserOnline(session, targetUIN, targetSession.GetStatus())
 		} else {
-			h.sendUserOnline(session, targetUIN, targetSession.GetStatus(), nil, 0)
+			_ = h.sendUserOnline(session, targetUIN, targetSession.GetStatus(), nil, 0)
 		}
 
 		// Also send the adder's online status to the target.
 		// The target won't see the adder as online unless we tell them.
 		if h.dispatcher != nil {
-			h.dispatcher.SendUserOnline(targetSession, session.UIN, session.GetStatus())
+			_ = h.dispatcher.SendUserOnline(targetSession, session.UIN, session.GetStatus())
 		} else {
-			h.sendUserOnline(targetSession, session.UIN, session.GetStatus(), nil, 0)
+			_ = h.sendUserOnline(targetSession, session.UIN, session.GetStatus(), nil, 0)
 		}
 	} else {
 		// Check if target is online via OSCAR session
 		info, err := h.service.GetUserInfoForProtocol(ctx, targetUIN)
 		if err == nil && info != nil && info.Online {
 			status := downgradeStatusForV2(info.Status)
-			h.sendUserOnline(session, targetUIN, status, nil, 0)
+			_ = h.sendUserOnline(session, targetUIN, status, nil, 0)
 		}
 	}
 
@@ -1207,14 +1207,14 @@ func (h *V2Handler) handleUpdateDetail(session *LegacySession, pkt *V2ClientPack
 
 	city, _ := ParseLegacyString(r, true)
 	var country uint16
-	binary.Read(r, binary.LittleEndian, &country)
+	_ = binary.Read(r, binary.LittleEndian, &country)
 	var countryStat uint8
-	binary.Read(r, binary.LittleEndian, &countryStat)
+	_ = binary.Read(r, binary.LittleEndian, &countryStat)
 	st, _ := ParseLegacyString(r, true)
 	var age uint16
-	binary.Read(r, binary.LittleEndian, &age)
+	_ = binary.Read(r, binary.LittleEndian, &age)
 	var sex uint8
-	binary.Read(r, binary.LittleEndian, &sex)
+	_ = binary.Read(r, binary.LittleEndian, &sex)
 	phone, _ := ParseLegacyString(r, true)
 	homepage, _ := ParseLegacyString(r, true)
 	about, _ := ParseLegacyString(r, true)

+ 65 - 65
server/icq_legacy/v3_handler.go

@@ -266,7 +266,7 @@ func (h *V3Handler) handleGetDeps(addr *net.UDPAddr, seq1, seq2 uint16, data []b
 
 	// 3. Build and send responses using packet builder
 	// Send ACK
-	h.sender.SendPacket(addr, h.packetBuilder.BuildAck(seq1, seq2, uin))
+	_ = h.sender.SendPacket(addr, h.packetBuilder.BuildAck(seq1, seq2, uin))
 
 	// Send pre-auth response using packet builder
 	return h.sender.SendPacket(addr, h.packetBuilder.BuildDeptsList(seq2, uin))
@@ -282,7 +282,7 @@ func (h *V3Handler) handleLogin(session *LegacySession, addr *net.UDPAddr, seq1,
 	ctx := context.Background()
 
 	// First send ACK (iserverd does this immediately)
-	h.sender.SendPacket(addr, h.packetBuilder.BuildAck(seq1, seq2, uin))
+	_ = h.sender.SendPacket(addr, h.packetBuilder.BuildAck(seq1, seq2, uin))
 
 	// 1. Unmarshal packet to typed struct
 	// V3 login data format (from iserverd):
@@ -425,7 +425,7 @@ func (h *V3Handler) notifyContactsUserOffline(session *LegacySession) {
 		contactSession := h.sessions.GetSession(contactUIN)
 		if contactSession != nil {
 			// Use dispatcher to send offline notification in correct protocol format
-			h.dispatcher.SendUserOffline(contactSession, session.UIN)
+			_ = h.dispatcher.SendUserOffline(contactSession, session.UIN)
 		}
 	}
 }
@@ -441,7 +441,7 @@ func (h *V3Handler) handleContactList(session *LegacySession, seq1, seq2 uint16,
 	}
 
 	// 1. Send ACK using packet builder
-	h.sender.SendPacket(session.Addr, h.packetBuilder.BuildAck(seq1, seq2, uin))
+	_ = h.sender.SendPacket(session.Addr, h.packetBuilder.BuildAck(seq1, seq2, uin))
 
 	// 2. Unmarshal packet to typed request struct
 	req, err := h.parseContactListPacket(data, uin)
@@ -473,10 +473,10 @@ func (h *V3Handler) handleContactList(session *LegacySession, seq1, seq2 uint16,
 		if contact.Online {
 			if h.dispatcher != nil {
 				// Use central dispatcher - routes to correct protocol based on session's version
-				h.dispatcher.SendUserOnline(session, contact.UIN, contact.Status)
+				_ = h.dispatcher.SendUserOnline(session, contact.UIN, contact.Status)
 			} else {
 				// Fallback to V3 format if dispatcher not set
-				h.sendUserOnline(session, contact.UIN, contact.Status)
+				_ = h.sendUserOnline(session, contact.UIN, contact.Status)
 			}
 		}
 	}
@@ -542,7 +542,7 @@ func (h *V3Handler) notifyContactsUserOnline(session *LegacySession) {
 		contactSession := h.sessions.GetSession(contactUIN)
 		if contactSession != nil {
 			// Use dispatcher to send in correct protocol format for each contact
-			h.dispatcher.SendUserOnline(contactSession, session.UIN, session.GetStatus())
+			_ = h.dispatcher.SendUserOnline(contactSession, session.UIN, session.GetStatus())
 		}
 	}
 }
@@ -562,7 +562,7 @@ func (h *V3Handler) handleSetStatus(session *LegacySession, seq1, seq2 uint16, u
 	ctx := context.Background()
 
 	// 1. Send ACK using packet builder
-	h.sender.SendPacket(session.Addr, h.packetBuilder.BuildAck(seq1, seq2, uin))
+	_ = h.sender.SendPacket(session.Addr, h.packetBuilder.BuildAck(seq1, seq2, uin))
 
 	// 2. Unmarshal packet to typed request struct
 	// Parse status - format: TIMESTAMP(4) + STATUS(4)
@@ -607,7 +607,7 @@ func (h *V3Handler) handleSetStatus(session *LegacySession, seq1, seq2 uint16, u
 		if targetSession != nil {
 			if h.dispatcher != nil {
 				// Use central dispatcher - routes to correct protocol based on target's version
-				h.dispatcher.SendStatusChange(targetSession, uin, newStatus)
+				_ = h.dispatcher.SendStatusChange(targetSession, uin, newStatus)
 			} else {
 				// Fallback to V3 format if dispatcher not set
 				statusPkt := h.packetBuilder.BuildUserStatus(
@@ -615,7 +615,7 @@ func (h *V3Handler) handleSetStatus(session *LegacySession, seq1, seq2 uint16, u
 					uin,
 					newStatus,
 				)
-				h.sender.SendToSession(targetSession, statusPkt)
+				_ = h.sender.SendToSession(targetSession, statusPkt)
 			}
 		}
 	}
@@ -635,7 +635,7 @@ func (h *V3Handler) handleMessage(session *LegacySession, seq1, seq2 uint16, uin
 	}
 
 	// 1. Send ACK using packet builder
-	h.sender.SendPacket(session.Addr, h.packetBuilder.BuildAck(seq1, seq2, uin))
+	_ = h.sender.SendPacket(session.Addr, h.packetBuilder.BuildAck(seq1, seq2, uin))
 
 	// 2. Unmarshal packet to typed request struct
 	req, err := h.parseMessagePacket(data, uin)
@@ -670,10 +670,10 @@ func (h *V3Handler) handleMessage(session *LegacySession, seq1, seq2 uint16, uin
 		if targetSession != nil {
 			if h.dispatcher != nil {
 				// Use central dispatcher - routes to correct protocol based on target's version
-				h.dispatcher.SendOnlineMessage(targetSession, req.FromUIN, req.MsgType, req.Message)
+				_ = h.dispatcher.SendOnlineMessage(targetSession, req.FromUIN, req.MsgType, req.Message)
 			} else {
 				// Fallback to V3 format if dispatcher not set
-				h.sendOnlineMessage(targetSession, req.FromUIN, req.MsgType, req.Message, seq2)
+				_ = h.sendOnlineMessage(targetSession, req.FromUIN, req.MsgType, req.Message, seq2)
 			}
 			h.logger.Debug("V3 message forwarded",
 				"from", req.FromUIN,
@@ -744,7 +744,7 @@ func (h *V3Handler) handleUserAdd(session *LegacySession, seq1, seq2 uint16, uin
 	}
 
 	// 1. Send ACK using packet builder
-	h.sender.SendPacket(session.Addr, h.packetBuilder.BuildAck(seq1, seq2, uin))
+	_ = h.sender.SendPacket(session.Addr, h.packetBuilder.BuildAck(seq1, seq2, uin))
 
 	// 2. Unmarshal packet to typed request struct
 	req, err := h.parseUserAddPacket(data, uin)
@@ -774,7 +774,7 @@ func (h *V3Handler) handleUserAdd(session *LegacySession, seq1, seq2 uint16, uin
 	// 4. Send notifications based on service result
 	if result.TargetOnline {
 		// Send target's online status to the user who added them
-		h.sendUserOnline(session, req.TargetUIN, result.TargetStatus)
+		_ = h.sendUserOnline(session, req.TargetUIN, result.TargetStatus)
 
 		// Send "you were added" notification to target user if service says to
 		if result.SendYouWereAdded {
@@ -786,19 +786,19 @@ func (h *V3Handler) handleUserAdd(session *LegacySession, seq1, seq2 uint16, uin
 
 				if h.dispatcher != nil {
 					// Use dispatcher for cross-protocol support
-					h.dispatcher.SendOnlineMessage(targetSession, req.FromUIN, ICQLegacyMsgAdded, youWereAddedMsg)
+					_ = h.dispatcher.SendOnlineMessage(targetSession, req.FromUIN, ICQLegacyMsgAdded, youWereAddedMsg)
 				} else {
 					// Fallback to V3 format
-					h.sendOnlineMessage(targetSession, req.FromUIN, ICQLegacyMsgAdded, youWereAddedMsg, 0)
+					_ = h.sendOnlineMessage(targetSession, req.FromUIN, ICQLegacyMsgAdded, youWereAddedMsg, 0)
 				}
 
 				// Also send the adder's online status to the target.
 				// The target receives "you were added" but won't see the
 				// adder as online unless we explicitly tell them.
 				if h.dispatcher != nil {
-					h.dispatcher.SendUserOnline(targetSession, req.FromUIN, session.GetStatus())
+					_ = h.dispatcher.SendUserOnline(targetSession, req.FromUIN, session.GetStatus())
 				} else {
-					h.sendUserOnline(targetSession, req.FromUIN, session.GetStatus())
+					_ = h.sendUserOnline(targetSession, req.FromUIN, session.GetStatus())
 				}
 
 				h.logger.Debug("V3 sent 'you were added' notification",
@@ -841,7 +841,7 @@ func (h *V3Handler) handleGetInfo(session *LegacySession, seq1, seq2 uint16, uin
 	}
 
 	// 1. Send ACK using packet builder
-	h.sender.SendPacket(session.Addr, h.packetBuilder.BuildAck(seq1, seq2, uin))
+	_ = h.sender.SendPacket(session.Addr, h.packetBuilder.BuildAck(seq1, seq2, uin))
 
 	// 2. Unmarshal packet to extract target UIN
 	// Format: TIMESTAMP(4) + TARGET_UIN(4)
@@ -870,11 +870,11 @@ func (h *V3Handler) handleGetInfo(session *LegacySession, seq1, seq2 uint16, uin
 
 	// 4. Build and send all user info packets using packet builder
 	// (matching iserverd v3_process_getinfo which sends 5 packets)
-	h.sender.SendToSession(session, h.packetBuilder.BuildBasicInfo(session.NextServerSeqNum(), seq2, session.UIN, info))
-	h.sender.SendToSession(session, h.packetBuilder.BuildHomeInfo(session.NextServerSeqNum(), seq2, session.UIN, info))
-	h.sender.SendToSession(session, h.packetBuilder.BuildHomeWeb(session.NextServerSeqNum(), seq2, session.UIN, info))
-	h.sender.SendToSession(session, h.packetBuilder.BuildWorkInfo(session.NextServerSeqNum(), seq2, session.UIN, info))
-	h.sender.SendToSession(session, h.packetBuilder.BuildWorkWeb(session.NextServerSeqNum(), seq2, session.UIN, info))
+	_ = h.sender.SendToSession(session, h.packetBuilder.BuildBasicInfo(session.NextServerSeqNum(), seq2, session.UIN, info))
+	_ = h.sender.SendToSession(session, h.packetBuilder.BuildHomeInfo(session.NextServerSeqNum(), seq2, session.UIN, info))
+	_ = h.sender.SendToSession(session, h.packetBuilder.BuildHomeWeb(session.NextServerSeqNum(), seq2, session.UIN, info))
+	_ = h.sender.SendToSession(session, h.packetBuilder.BuildWorkInfo(session.NextServerSeqNum(), seq2, session.UIN, info))
+	_ = h.sender.SendToSession(session, h.packetBuilder.BuildWorkWeb(session.NextServerSeqNum(), seq2, session.UIN, info))
 
 	return nil
 }
@@ -984,7 +984,7 @@ func (h *V3Handler) handleOfflineMsgReq(session *LegacySession, seq1, seq2 uint1
 		return nil
 	}
 
-	h.sendAck(session.Addr, seq1, seq2, uin)
+	_ = h.sendAck(session.Addr, seq1, seq2, uin)
 
 	ctx := context.Background()
 
@@ -1022,7 +1022,7 @@ func (h *V3Handler) handleSetBasicInfo(session *LegacySession, seq1, seq2 uint16
 		return nil
 	}
 
-	h.sendAck(session.Addr, seq1, seq2, uin)
+	_ = h.sendAck(session.Addr, seq1, seq2, uin)
 
 	if len(data) < 6 {
 		return h.sendReplyOK(session, seq2, 0x01E0)
@@ -1055,7 +1055,7 @@ func (h *V3Handler) handleSetHomeInfo(session *LegacySession, seq1, seq2 uint16,
 		return nil
 	}
 
-	h.sendAck(session.Addr, seq1, seq2, uin)
+	_ = h.sendAck(session.Addr, seq1, seq2, uin)
 
 	if len(data) < 6 {
 		return h.sendReplyOK(session, seq2, 0x0280)
@@ -1066,17 +1066,17 @@ func (h *V3Handler) handleSetHomeInfo(session *LegacySession, seq1, seq2 uint16,
 	city, _ := ParseLegacyString(r, true)
 	st, _ := ParseLegacyString(r, true)
 	var country uint16
-	binary.Read(r, binary.LittleEndian, &country)
+	_ = binary.Read(r, binary.LittleEndian, &country)
 	phone, _ := ParseLegacyString(r, true)
 	fax, _ := ParseLegacyString(r, true)
 	cell, _ := ParseLegacyString(r, true)
 	var zipCode uint32
-	binary.Read(r, binary.LittleEndian, &zipCode)
+	_ = binary.Read(r, binary.LittleEndian, &zipCode)
 	var gmt uint16
-	binary.Read(r, binary.LittleEndian, &gmt)
+	_ = binary.Read(r, binary.LittleEndian, &gmt)
 	var auth, webaware uint8
-	binary.Read(r, binary.LittleEndian, &auth)
-	binary.Read(r, binary.LittleEndian, &webaware)
+	_ = binary.Read(r, binary.LittleEndian, &auth)
+	_ = binary.Read(r, binary.LittleEndian, &webaware)
 
 	// Read existing basic info to avoid overwriting nick/first/last/email
 	ctx := context.Background()
@@ -1109,7 +1109,7 @@ func (h *V3Handler) handleSetHomeWeb(session *LegacySession, seq1, seq2 uint16,
 		return nil
 	}
 
-	h.sendAck(session.Addr, seq1, seq2, uin)
+	_ = h.sendAck(session.Addr, seq1, seq2, uin)
 
 	if len(data) < 7 {
 		return h.sendReplyOK(session, seq2, 0x0294)
@@ -1117,18 +1117,18 @@ func (h *V3Handler) handleSetHomeWeb(session *LegacySession, seq1, seq2 uint16,
 
 	r := bytes.NewReader(data[4:]) // skip timestamp
 	var age uint16
-	binary.Read(r, binary.LittleEndian, &age)
+	_ = binary.Read(r, binary.LittleEndian, &age)
 	var sex uint8
-	binary.Read(r, binary.LittleEndian, &sex)
+	_ = binary.Read(r, binary.LittleEndian, &sex)
 	hp, _ := ParseLegacyString(r, true)
 	var birthYear uint16
-	binary.Read(r, binary.LittleEndian, &birthYear)
+	_ = binary.Read(r, binary.LittleEndian, &birthYear)
 	var birthMonth, birthDay, lang1, lang2, lang3 uint8
-	binary.Read(r, binary.LittleEndian, &birthMonth)
-	binary.Read(r, binary.LittleEndian, &birthDay)
-	binary.Read(r, binary.LittleEndian, &lang1)
-	binary.Read(r, binary.LittleEndian, &lang2)
-	binary.Read(r, binary.LittleEndian, &lang3)
+	_ = binary.Read(r, binary.LittleEndian, &birthMonth)
+	_ = binary.Read(r, binary.LittleEndian, &birthDay)
+	_ = binary.Read(r, binary.LittleEndian, &lang1)
+	_ = binary.Read(r, binary.LittleEndian, &lang2)
+	_ = binary.Read(r, binary.LittleEndian, &lang3)
 
 	ctx := context.Background()
 	info := state.ICQMoreInfo{
@@ -1155,7 +1155,7 @@ func (h *V3Handler) handleSetWorkInfo(session *LegacySession, seq1, seq2 uint16,
 		return nil
 	}
 
-	h.sendAck(session.Addr, seq1, seq2, uin)
+	_ = h.sendAck(session.Addr, seq1, seq2, uin)
 
 	if len(data) < 6 {
 		return h.sendReplyOK(session, seq2, 0x026C)
@@ -1166,17 +1166,17 @@ func (h *V3Handler) handleSetWorkInfo(session *LegacySession, seq1, seq2 uint16,
 	city, _ := ParseLegacyString(r, true)
 	st, _ := ParseLegacyString(r, true)
 	var country uint16
-	binary.Read(r, binary.LittleEndian, &country)
+	_ = binary.Read(r, binary.LittleEndian, &country)
 	company, _ := ParseLegacyString(r, true)
 	position, _ := ParseLegacyString(r, true)
 	var dept uint16
-	binary.Read(r, binary.LittleEndian, &dept)
+	_ = binary.Read(r, binary.LittleEndian, &dept)
 	phone, _ := ParseLegacyString(r, true)
 	fax, _ := ParseLegacyString(r, true)
 	// pager field — not stored in ICQWorkInfo, skip
-	ParseLegacyString(r, true)
+	_, _ = ParseLegacyString(r, true)
 	var zipCode uint32
-	binary.Read(r, binary.LittleEndian, &zipCode)
+	_ = binary.Read(r, binary.LittleEndian, &zipCode)
 
 	// Read existing work info to preserve WebPage (set separately via 0x05BE)
 	ctx := context.Background()
@@ -1206,7 +1206,7 @@ func (h *V3Handler) handleSetWorkWeb(session *LegacySession, seq1, seq2 uint16,
 		return nil
 	}
 
-	h.sendAck(session.Addr, seq1, seq2, uin)
+	_ = h.sendAck(session.Addr, seq1, seq2, uin)
 
 	if len(data) < 6 {
 		return h.sendReplyOK(session, seq2, 0x0258)
@@ -1234,7 +1234,7 @@ func (h *V3Handler) handleVisibleList(session *LegacySession, seq1, seq2 uint16,
 		return nil
 	}
 
-	h.sendAck(session.Addr, seq1, seq2, uin)
+	_ = h.sendAck(session.Addr, seq1, seq2, uin)
 
 	h.logger.Debug("V3 visible list", "uin", uin)
 
@@ -1247,7 +1247,7 @@ func (h *V3Handler) handleInvisibleList(session *LegacySession, seq1, seq2 uint1
 		return nil
 	}
 
-	h.sendAck(session.Addr, seq1, seq2, uin)
+	_ = h.sendAck(session.Addr, seq1, seq2, uin)
 
 	h.logger.Debug("V3 invisible list", "uin", uin)
 
@@ -1260,7 +1260,7 @@ func (h *V3Handler) handleUnknownDep(session *LegacySession, seq1, seq2 uint16,
 		return nil
 	}
 
-	h.sendAck(session.Addr, seq1, seq2, uin)
+	_ = h.sendAck(session.Addr, seq1, seq2, uin)
 
 	h.logger.Debug("V3 unknown dep request", "uin", uin)
 
@@ -1278,7 +1278,7 @@ func (h *V3Handler) handleSearchStart(session *LegacySession, seq1, seq2 uint16,
 		return nil
 	}
 
-	h.sendAck(session.Addr, seq1, seq2, uin)
+	_ = h.sendAck(session.Addr, seq1, seq2, uin)
 
 	// Parse search parameters - format: TIMESTAMP(4) + TYPE(2) + COMP(2) + LEN(2) + STRING
 	if len(data) < 10 {
@@ -1325,7 +1325,7 @@ func (h *V3Handler) handleSearchStart(session *LegacySession, seq1, seq2 uint16,
 				first := truncateField(result.FirstName, 64, h.logger, "first_name", result.UIN)
 				last := truncateField(result.LastName, 64, h.logger, "last_name", result.UIN)
 				email := truncateField(result.Email, 64, h.logger, "email", result.UIN)
-				h.sendSearchFound(session, seq2, result.UIN, nick, first, last, email, 0)
+				_ = h.sendSearchFound(session, seq2, result.UIN, nick, first, last, email, 0)
 				h.logger.Debug("V3 search found user in database",
 					"searcher", uin,
 					"found", result.UIN,
@@ -1354,7 +1354,7 @@ func (h *V3Handler) handleGetDeps1(session *LegacySession, seq1, seq2 uint16, ui
 		return nil
 	}
 
-	h.sendAck(session.Addr, seq1, seq2, uin)
+	_ = h.sendAck(session.Addr, seq1, seq2, uin)
 
 	h.logger.Debug("V3 get deps1 request", "uin", uin)
 
@@ -1749,7 +1749,7 @@ func (h *V3Handler) handleGetNotes(session *LegacySession, seq1, seq2 uint16, ui
 		return nil
 	}
 
-	h.sendAck(session.Addr, seq1, seq2, uin)
+	_ = h.sendAck(session.Addr, seq1, seq2, uin)
 
 	// Parse: TIMESTAMP(4) + TARGET_UIN(4)
 	if len(data) < 8 {
@@ -1781,7 +1781,7 @@ func (h *V3Handler) handleSetNotes(session *LegacySession, seq1, seq2 uint16, ui
 		return nil
 	}
 
-	h.sendAck(session.Addr, seq1, seq2, uin)
+	_ = h.sendAck(session.Addr, seq1, seq2, uin)
 
 	// Parse notes from packet
 	// Format: TIMESTAMP(4) + NOTES_LEN(2) + NOTES
@@ -1828,7 +1828,7 @@ func (h *V3Handler) handleSetPassword(session *LegacySession, seq1, seq2 uint16,
 		return nil
 	}
 
-	h.sendAck(session.Addr, seq1, seq2, uin)
+	_ = h.sendAck(session.Addr, seq1, seq2, uin)
 
 	// Parse password from data
 	// Format: TIMESTAMP(4) + PASSWORD_LEN(2) + NEW_PASSWORD
@@ -1894,7 +1894,7 @@ func (h *V3Handler) handleSetAuth(session *LegacySession, seq1, seq2 uint16, uin
 		return nil
 	}
 
-	h.sendAck(session.Addr, seq1, seq2, uin)
+	_ = h.sendAck(session.Addr, seq1, seq2, uin)
 
 	// Parse auth mode - format: TIMESTAMP(4) + AUTH_MODE(1)
 	// From iserverd v3_process_setauth():
@@ -1941,7 +1941,7 @@ func (h *V3Handler) handleSetState(session *LegacySession, seq1, seq2 uint16, ui
 		return nil
 	}
 
-	h.sendAck(session.Addr, seq1, seq2, uin)
+	_ = h.sendAck(session.Addr, seq1, seq2, uin)
 
 	// Parse status - format: TIMESTAMP(4) + STATUS(4) + ESTATUS(4)
 	// From iserverd v3_process_status():
@@ -2006,7 +2006,7 @@ func (h *V3Handler) broadcastStatusChange(session *LegacySession, newStatus uint
 		if contactSession != nil {
 			// Use dispatcher to send status change in correct protocol format
 			// This routes to V3 or V5 handler based on the contact's protocol version
-			h.dispatcher.SendStatusChange(contactSession, session.UIN, newStatus)
+			_ = h.dispatcher.SendStatusChange(contactSession, session.UIN, newStatus)
 		}
 	}
 }
@@ -2018,7 +2018,7 @@ func (h *V3Handler) handleUsageStats(session *LegacySession, seq1, seq2 uint16,
 		return nil
 	}
 
-	h.sendAck(session.Addr, seq1, seq2, uin)
+	_ = h.sendAck(session.Addr, seq1, seq2, uin)
 
 	h.logger.Debug("V3 usage stats", "uin", uin)
 
@@ -2032,7 +2032,7 @@ func (h *V3Handler) handleGetExternals(session *LegacySession, seq1, seq2 uint16
 		return nil
 	}
 
-	h.sendAck(session.Addr, seq1, seq2, uin)
+	_ = h.sendAck(session.Addr, seq1, seq2, uin)
 
 	h.logger.Debug("V3 get externals", "uin", uin)
 
@@ -2048,7 +2048,7 @@ func (h *V3Handler) handleSysAck(session *LegacySession, seq1, seq2 uint16, uin
 		return nil
 	}
 
-	h.sendAck(session.Addr, seq1, seq2, uin)
+	_ = h.sendAck(session.Addr, seq1, seq2, uin)
 
 	ctx := context.Background()
 
@@ -2069,7 +2069,7 @@ func (h *V3Handler) handleOnlineInfo(session *LegacySession, seq1, seq2 uint16,
 		return nil
 	}
 
-	h.sendAck(session.Addr, seq1, seq2, uin)
+	_ = h.sendAck(session.Addr, seq1, seq2, uin)
 
 	h.logger.Debug("V3 online info", "uin", uin)
 
@@ -2087,7 +2087,7 @@ func (h *V3Handler) handleGetInfo1(session *LegacySession, seq1, seq2 uint16, ui
 	}
 
 	// 1. Send ACK using packet builder
-	h.sender.SendPacket(session.Addr, h.packetBuilder.BuildAck(seq1, seq2, uin))
+	_ = h.sender.SendPacket(session.Addr, h.packetBuilder.BuildAck(seq1, seq2, uin))
 
 	// 2. Unmarshal packet to extract target UIN
 	// Format: TIMESTAMP(4) + TARGET_UIN(4)

+ 0 - 13
server/icq_legacy/v3_packet_builder.go

@@ -78,19 +78,6 @@ func NewV3PacketBuilder(sessionManager *LegacySessionManager, directConnectionEn
 	}
 }
 
-// buildV3Header creates a standard V3 packet header (16 bytes).
-// Format: VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + RESERVED(4)
-func (b *V3PacketBuilderImpl) buildV3Header(command uint16, seq1, seq2 uint16, uin uint32) []byte {
-	header := make([]byte, 16)
-	binary.LittleEndian.PutUint16(header[0:2], ICQLegacyVersionV3)
-	binary.LittleEndian.PutUint16(header[2:4], command)
-	binary.LittleEndian.PutUint16(header[4:6], seq1)
-	binary.LittleEndian.PutUint16(header[6:8], seq2)
-	binary.LittleEndian.PutUint32(header[8:12], uin)
-	binary.LittleEndian.PutUint32(header[12:16], 0) // reserved
-	return header
-}
-
 // BuildLoginReply constructs a login success (HELLO) response packet.
 // From iserverd v3_send_login_reply() - this is a complex packet!
 // V3 HELLO format:

+ 39 - 39
server/icq_legacy/v4_handler.go

@@ -272,7 +272,7 @@ func (h *V4Handler) handleFirstLogin(addr *net.UDPAddr, seq1, seq2 uint16, uin u
 	)
 
 	// Send ACK first
-	h.sendAck(addr, seq1, seq2, uin)
+	_ = h.sendAck(addr, seq1, seq2, uin)
 
 	// Send registration info with admin notes
 	// This tells the client that registration is enabled and provides any admin notes
@@ -290,7 +290,7 @@ func (h *V4Handler) handleRegRequestInfo(addr *net.UDPAddr, seq1, seq2 uint16, u
 	)
 
 	// Send ACK first
-	h.sendAck(addr, seq1, seq2, uin)
+	_ = h.sendAck(addr, seq1, seq2, uin)
 
 	// Send registration info
 	return h.sendRegisterInfo(addr, seq2, uin)
@@ -310,7 +310,7 @@ func (h *V4Handler) handleRegNewUserInfo(addr *net.UDPAddr, seq1, seq2 uint16, u
 	)
 
 	// Send ACK first
-	h.sendAck(addr, seq1, seq2, uin)
+	_ = h.sendAck(addr, seq1, seq2, uin)
 
 	// Parse registration data
 	// Format from client.html (0x05e6):
@@ -484,7 +484,7 @@ func (h *V4Handler) handleGetDeps(addr *net.UDPAddr, seq1, seq2 uint16, uin uint
 	}
 
 	// 3. Send ACK using packet builder
-	h.sender.SendPacket(addr, h.packetBuilder.BuildAck(seq1, seq2, uin))
+	_ = h.sender.SendPacket(addr, h.packetBuilder.BuildAck(seq1, seq2, uin))
 
 	// 4. Create session using dataUIN (the actual login UIN from the packet data),
 	// not the header UIN which may be 0 or stale from a previous session.
@@ -528,7 +528,7 @@ func (h *V4Handler) handleLogin(session *LegacySession, addr *net.UDPAddr, seq1,
 	ctx := context.Background()
 
 	// 1. Send ACK using packet builder
-	h.sender.SendPacket(addr, h.packetBuilder.BuildAck(seq1, seq2, uin))
+	_ = h.sender.SendPacket(addr, h.packetBuilder.BuildAck(seq1, seq2, uin))
 
 	// 2. Unmarshal packet to typed struct
 	// V4 login data format (from matt-v4.txt):
@@ -731,7 +731,7 @@ func (h *V4Handler) handleContactList(session *LegacySession, seq1, seq2 uint16,
 	ctx := context.Background()
 
 	// 1. Send ACK using packet builder
-	h.sender.SendPacket(session.Addr, h.packetBuilder.BuildAck(seq1, seq2, uin))
+	_ = h.sender.SendPacket(session.Addr, h.packetBuilder.BuildAck(seq1, seq2, uin))
 
 	// 2. Unmarshal packet to typed request struct
 	req, err := h.parseContactListPacket(data, uin)
@@ -762,7 +762,7 @@ func (h *V4Handler) handleContactList(session *LegacySession, seq1, seq2 uint16,
 		if contact.Online {
 			if h.dispatcher != nil {
 				// Use central dispatcher - routes to correct protocol based on session's version
-				h.dispatcher.SendUserOnline(session, contact.UIN, contact.Status)
+				_ = h.dispatcher.SendUserOnline(session, contact.UIN, contact.Status)
 			} else {
 				onlinePkt := h.packetBuilder.BuildUserOnline(
 					session.NextServerSeqNum(),
@@ -771,7 +771,7 @@ func (h *V4Handler) handleContactList(session *LegacySession, seq1, seq2 uint16,
 				)
 				// Set the recipient UIN in the packet (offset 8-11)
 				binary.LittleEndian.PutUint32(onlinePkt[8:12], session.UIN)
-				h.sender.SendToSession(session, onlinePkt)
+				_ = h.sender.SendToSession(session, onlinePkt)
 			}
 		}
 	}
@@ -828,7 +828,7 @@ func (h *V4Handler) handleSetStatus(session *LegacySession, seq1, seq2 uint16, u
 	ctx := context.Background()
 
 	// 1. Send ACK using packet builder
-	h.sender.SendPacket(session.Addr, h.packetBuilder.BuildAck(seq1, seq2, uin))
+	_ = h.sender.SendPacket(session.Addr, h.packetBuilder.BuildAck(seq1, seq2, uin))
 
 	// 2. Unmarshal packet to typed request struct
 	// V4 status change format (from licq CPU_SetStatus): STATUS(4)
@@ -873,9 +873,9 @@ func (h *V4Handler) handleSetStatus(session *LegacySession, seq1, seq2 uint16, u
 		targetSession := h.sessions.GetSession(target.UIN)
 		if targetSession != nil {
 			if h.dispatcher != nil {
-				h.dispatcher.SendStatusChange(targetSession, uin, newStatus)
+				_ = h.dispatcher.SendStatusChange(targetSession, uin, newStatus)
 			} else {
-				h.sendUserStatus(targetSession, uin, newStatus)
+				_ = h.sendUserStatus(targetSession, uin, newStatus)
 			}
 		}
 	}
@@ -896,7 +896,7 @@ func (h *V4Handler) handleMessage(session *LegacySession, seq1, seq2 uint16, uin
 	ctx := context.Background()
 
 	// 1. Send ACK using packet builder
-	h.sender.SendPacket(session.Addr, h.packetBuilder.BuildAck(seq1, seq2, uin))
+	_ = h.sender.SendPacket(session.Addr, h.packetBuilder.BuildAck(seq1, seq2, uin))
 
 	// 2. Unmarshal packet to typed request struct
 	req, err := h.parseMessagePacket(data, uin)
@@ -929,7 +929,7 @@ func (h *V4Handler) handleMessage(session *LegacySession, seq1, seq2 uint16, uin
 		targetSession := h.sessions.GetSession(req.ToUIN)
 		if targetSession != nil {
 			if h.dispatcher != nil {
-				h.dispatcher.SendOnlineMessage(targetSession, req.FromUIN, req.MsgType, req.Message)
+				_ = h.dispatcher.SendOnlineMessage(targetSession, req.FromUIN, req.MsgType, req.Message)
 			} else {
 				msgPkt := h.packetBuilder.BuildOnlineMessage(
 					targetSession.NextServerSeqNum(),
@@ -939,7 +939,7 @@ func (h *V4Handler) handleMessage(session *LegacySession, seq1, seq2 uint16, uin
 				)
 				// Set the recipient UIN in the packet (offset 8-11)
 				binary.LittleEndian.PutUint32(msgPkt[8:12], targetSession.UIN)
-				h.sender.SendToSession(targetSession, msgPkt)
+				_ = h.sender.SendToSession(targetSession, msgPkt)
 			}
 
 			h.logger.Debug("V4 message forwarded",
@@ -1009,7 +1009,7 @@ func (h *V4Handler) handleUserAdd(session *LegacySession, seq1, seq2 uint16, uin
 		return nil
 	}
 
-	h.sendAck(session.Addr, seq1, seq2, uin)
+	_ = h.sendAck(session.Addr, seq1, seq2, uin)
 
 	// V4 format (from licq CPU_AddUser): TARGET_UIN(4)
 	// Note: V4 does NOT have a timestamp prefix.
@@ -1040,9 +1040,9 @@ func (h *V4Handler) handleUserAdd(session *LegacySession, seq1, seq2 uint16, uin
 	if targetSession != nil {
 		// Send target's online status to the user who added them
 		if h.dispatcher != nil {
-			h.dispatcher.SendUserOnline(session, targetUIN, targetSession.GetStatus())
+			_ = h.dispatcher.SendUserOnline(session, targetUIN, targetSession.GetStatus())
 		} else {
-			h.sendUserOnline(session, targetUIN, targetSession.GetStatus())
+			_ = h.sendUserOnline(session, targetUIN, targetSession.GetStatus())
 		}
 
 		// Send "you were added" notification to target user
@@ -1050,9 +1050,9 @@ func (h *V4Handler) handleUserAdd(session *LegacySession, seq1, seq2 uint16, uin
 		youWereAddedMsg := fmt.Sprintf("%d\xFE\xFE\xFE\xFE0", uin)
 
 		if h.dispatcher != nil {
-			h.dispatcher.SendOnlineMessage(targetSession, uin, ICQLegacyMsgAdded, youWereAddedMsg)
+			_ = h.dispatcher.SendOnlineMessage(targetSession, uin, ICQLegacyMsgAdded, youWereAddedMsg)
 		} else {
-			h.sendOnlineMessage(targetSession, uin, ICQLegacyMsgAdded, youWereAddedMsg, 0)
+			_ = h.sendOnlineMessage(targetSession, uin, ICQLegacyMsgAdded, youWereAddedMsg, 0)
 		}
 
 		// Also send the adder's online status to the target.
@@ -1061,9 +1061,9 @@ func (h *V4Handler) handleUserAdd(session *LegacySession, seq1, seq2 uint16, uin
 		// this, the target's client shows the adder as offline
 		// even though they're connected.
 		if h.dispatcher != nil {
-			h.dispatcher.SendUserOnline(targetSession, uin, session.GetStatus())
+			_ = h.dispatcher.SendUserOnline(targetSession, uin, session.GetStatus())
 		} else {
-			h.sendUserOnline(targetSession, uin, session.GetStatus())
+			_ = h.sendUserOnline(targetSession, uin, session.GetStatus())
 		}
 
 		h.logger.Debug("V4 sent 'you were added' notification",
@@ -1081,7 +1081,7 @@ func (h *V4Handler) handleGetInfo(session *LegacySession, seq1, seq2 uint16, uin
 		return nil
 	}
 
-	h.sendAck(session.Addr, seq1, seq2, uin)
+	_ = h.sendAck(session.Addr, seq1, seq2, uin)
 
 	// V4 format (from licq CPU_GetUserBasicInfo): TARGET_UIN(4)
 	// Note: V4 does NOT have a timestamp/subsequence prefix (unlike V2).
@@ -1097,7 +1097,7 @@ func (h *V4Handler) handleGetInfo(session *LegacySession, seq1, seq2 uint16, uin
 	)
 
 	// Send basic info
-	h.sendBasicInfo(session, seq2, targetUIN)
+	_ = h.sendBasicInfo(session, seq2, targetUIN)
 
 	return nil
 }
@@ -1110,7 +1110,7 @@ func (h *V4Handler) handleInfoReq(session *LegacySession, seq1, seq2 uint16, uin
 		return nil
 	}
 
-	h.sendAck(session.Addr, seq1, seq2, uin)
+	_ = h.sendAck(session.Addr, seq1, seq2, uin)
 
 	// Parse - format: TARGET_UIN(4) only
 	if len(data) < 4 {
@@ -1137,7 +1137,7 @@ func (h *V4Handler) handleExtInfoReq(session *LegacySession, seq1, seq2 uint16,
 		return nil
 	}
 
-	h.sendAck(session.Addr, seq1, seq2, uin)
+	_ = h.sendAck(session.Addr, seq1, seq2, uin)
 
 	// Parse - format: TARGET_UIN(4) only
 	if len(data) < 4 {
@@ -1167,7 +1167,7 @@ func (h *V4Handler) handleSearchByUIN(session *LegacySession, seq1, seq2 uint16,
 		return nil
 	}
 
-	h.sendAck(session.Addr, seq1, seq2, uin)
+	_ = h.sendAck(session.Addr, seq1, seq2, uin)
 
 	if len(data) < 4 {
 		h.logger.Debug("V4 search by UIN - data too short", "uin", uin)
@@ -1197,7 +1197,7 @@ func (h *V4Handler) handleSearchByUIN(session *LegacySession, seq1, seq2 uint16,
 		"nickname", result.Nickname,
 	)
 
-	h.sendSearchFound(session, seq2, result)
+	_ = h.sendSearchFound(session, seq2, result)
 	return h.sendSearchEnd(session, seq2, false)
 }
 
@@ -1209,7 +1209,7 @@ func (h *V4Handler) handleSearchByName(session *LegacySession, seq1, seq2 uint16
 		return nil
 	}
 
-	h.sendAck(session.Addr, seq1, seq2, uin)
+	_ = h.sendAck(session.Addr, seq1, seq2, uin)
 
 	h.logger.Info("V4 search by name", "uin", uin, "data_len", len(data))
 
@@ -1354,7 +1354,7 @@ func (h *V4Handler) handleUpdateBasic(session *LegacySession, seq1, seq2 uint16,
 		return nil
 	}
 
-	h.sendAck(session.Addr, seq1, seq2, uin)
+	_ = h.sendAck(session.Addr, seq1, seq2, uin)
 
 	// Parse: NICK_LEN(2)+NICK + FNAME_LEN(2)+FNAME + LNAME_LEN(2)+LNAME + EMAIL_LEN(2)+EMAIL + AUTH(1)
 	if len(data) >= 4 {
@@ -1364,7 +1364,7 @@ func (h *V4Handler) handleUpdateBasic(session *LegacySession, seq1, seq2 uint16,
 		last, _ := ParseLegacyString(r, true)
 		email, _ := ParseLegacyString(r, true)
 		var auth uint8
-		binary.Read(r, binary.LittleEndian, &auth)
+		_ = binary.Read(r, binary.LittleEndian, &auth)
 
 		ctx := context.Background()
 		info := state.ICQBasicInfo{
@@ -1405,7 +1405,7 @@ func (h *V4Handler) handleUpdateDetail(session *LegacySession, seq1, seq2 uint16
 		return nil
 	}
 
-	h.sendAck(session.Addr, seq1, seq2, uin)
+	_ = h.sendAck(session.Addr, seq1, seq2, uin)
 
 	// Parse: CITY_LEN(2)+CITY + COUNTRY(2) + TIMEZONE(1) + STATE_LEN(2)+STATE +
 	//        AGE(2) + SEX(1) + PHONE_LEN(2)+PHONE + HOMEPAGE_LEN(2)+HOMEPAGE +
@@ -1414,14 +1414,14 @@ func (h *V4Handler) handleUpdateDetail(session *LegacySession, seq1, seq2 uint16
 		r := bytes.NewReader(data)
 		city, _ := ParseLegacyString(r, true)
 		var country uint16
-		binary.Read(r, binary.LittleEndian, &country)
+		_ = binary.Read(r, binary.LittleEndian, &country)
 		var timezone uint8
-		binary.Read(r, binary.LittleEndian, &timezone)
+		_ = binary.Read(r, binary.LittleEndian, &timezone)
 		st, _ := ParseLegacyString(r, true)
 		var age uint16
-		binary.Read(r, binary.LittleEndian, &age)
+		_ = binary.Read(r, binary.LittleEndian, &age)
 		var sex uint8
-		binary.Read(r, binary.LittleEndian, &sex)
+		_ = binary.Read(r, binary.LittleEndian, &sex)
 		phone, _ := ParseLegacyString(r, true)
 		homepage, _ := ParseLegacyString(r, true)
 		about, _ := ParseLegacyString(r, true)
@@ -1476,7 +1476,7 @@ func (h *V4Handler) handleOfflineMsgReq(session *LegacySession, seq1, seq2 uint1
 		return nil
 	}
 
-	h.sendAck(session.Addr, seq1, seq2, uin)
+	_ = h.sendAck(session.Addr, seq1, seq2, uin)
 
 	h.logger.Debug("V4 offline message request", "uin", uin)
 
@@ -1492,7 +1492,7 @@ func (h *V4Handler) handleOfflineMsgReq(session *LegacySession, seq1, seq2 uint1
 
 	// Send each offline message as SYS_MSG_OFFLINE (0x00DC)
 	for _, msg := range messages {
-		h.sendOfflineMessage(session, msg)
+		_ = h.sendOfflineMessage(session, msg)
 	}
 
 	// Acknowledge (delete) offline messages after delivery
@@ -1685,7 +1685,7 @@ func (h *V4Handler) notifyContactsUserOnline(session *LegacySession) {
 	for _, contactUIN := range contactsToNotify {
 		contactSession := h.sessions.GetSession(contactUIN)
 		if contactSession != nil {
-			h.dispatcher.SendUserOnline(contactSession, session.UIN, session.GetStatus())
+			_ = h.dispatcher.SendUserOnline(contactSession, session.UIN, session.GetStatus())
 		}
 	}
 }
@@ -1706,7 +1706,7 @@ func (h *V4Handler) notifyContactsUserOffline(session *LegacySession) {
 	for _, contactUIN := range contactsToNotify {
 		contactSession := h.sessions.GetSession(contactUIN)
 		if contactSession != nil {
-			h.dispatcher.SendUserOffline(contactSession, session.UIN)
+			_ = h.dispatcher.SendUserOffline(contactSession, session.UIN)
 		}
 	}
 }

+ 180 - 182
server/icq_legacy/v5_handler.go

@@ -197,7 +197,7 @@ func (h *V5Handler) handleMetaUser(session *LegacySession, addr *net.UDPAddr, pk
 	// Send ACK first (as per iserverd v5_process_user_meta)
 	// ACK echoes both seq1 and seq2 from the client
 	if session != nil {
-		h.sendV5AckWithSeq2(session, pkt.SeqNum1, pkt.SeqNum2)
+		_ = h.sendV5AckWithSeq2(session, pkt.SeqNum1, pkt.SeqNum2)
 	}
 
 	subCommand := binary.LittleEndian.Uint16(pkt.Data[0:2])
@@ -431,7 +431,7 @@ func (h *V5Handler) handleGetDeps(session *LegacySession, addr *net.UDPAddr, pkt
 
 	// 3. Build and send responses using packet builder
 	// Send ACK first
-	h.sender.SendPacket(addr, h.packetBuilder.BuildAckToAddr(pkt.SessionID, uin, pkt.SeqNum1, pkt.SeqNum2))
+	_ = h.sender.SendPacket(addr, h.packetBuilder.BuildAckToAddr(pkt.SessionID, uin, pkt.SeqNum1, pkt.SeqNum2))
 
 	// Send depslist response (V3 format packet!) using packet builder
 	return h.sender.SendPacket(addr, h.packetBuilder.BuildDepsListReply(uin, pkt.SeqNum2))
@@ -448,7 +448,7 @@ func (h *V5Handler) handleContactList(session *LegacySession, pkt *V5ClientPacke
 	}
 
 	// 1. Send ACK using packet builder
-	h.sender.SendPacket(session.Addr, h.packetBuilder.BuildAck(session, pkt.SeqNum1))
+	_ = h.sender.SendPacket(session.Addr, h.packetBuilder.BuildAck(session, pkt.SeqNum1))
 
 	// 2. Unmarshal packet to typed request struct
 	req, err := h.parseContactListPacket(pkt.Data, session.UIN)
@@ -481,10 +481,10 @@ func (h *V5Handler) handleContactList(session *LegacySession, pkt *V5ClientPacke
 		if contact.Online {
 			if h.dispatcher != nil {
 				// Use central dispatcher - routes to correct protocol based on session's version
-				h.dispatcher.SendUserOnline(session, contact.UIN, contact.Status)
+				_ = h.dispatcher.SendUserOnline(session, contact.UIN, contact.Status)
 			} else {
 				// Fallback to V5 format if dispatcher not set using packet builder
-				h.sender.SendToSession(session, h.packetBuilder.BuildUserOnline(session, contact.UIN, contact.Status))
+				_ = h.sender.SendToSession(session, h.packetBuilder.BuildUserOnline(session, contact.UIN, contact.Status))
 			}
 		}
 	}
@@ -547,7 +547,7 @@ func (h *V5Handler) notifyContactsUserOnline(session *LegacySession) {
 		contactSession := h.sessions.GetSession(contactUIN)
 		if contactSession != nil {
 			// Use dispatcher to send in correct protocol format for each contact
-			h.dispatcher.SendUserOnline(contactSession, session.UIN, session.GetStatus())
+			_ = h.dispatcher.SendUserOnline(contactSession, session.UIN, session.GetStatus())
 		}
 	}
 }
@@ -602,7 +602,7 @@ func (h *V5Handler) notifyContactsUserOffline(session *LegacySession) {
 		contactSession := h.sessions.GetSession(contactUIN)
 		if contactSession != nil {
 			// Use dispatcher to send offline notification in correct protocol format
-			h.dispatcher.SendUserOffline(contactSession, session.UIN)
+			_ = h.dispatcher.SendUserOffline(contactSession, session.UIN)
 		}
 	}
 }
@@ -623,7 +623,7 @@ func (h *V5Handler) handleSetStatus(session *LegacySession, pkt *V5ClientPacket)
 	}
 
 	// 1. Send ACK using packet builder
-	h.sender.SendPacket(session.Addr, h.packetBuilder.BuildAck(session, pkt.SeqNum1))
+	_ = h.sender.SendPacket(session.Addr, h.packetBuilder.BuildAck(session, pkt.SeqNum1))
 
 	// 2. Unmarshal packet to typed request struct
 	req, err := h.parseStatusChangePacket(pkt.Data, session)
@@ -694,10 +694,10 @@ func (h *V5Handler) broadcastStatusChangeToTargets(session *LegacySession, newSt
 			if h.dispatcher != nil {
 				// Use dispatcher to send status change in correct protocol format
 				// This routes to V3 or V5 handler based on the contact's protocol version
-				h.dispatcher.SendStatusChange(targetSession, session.UIN, newStatus)
+				_ = h.dispatcher.SendStatusChange(targetSession, session.UIN, newStatus)
 			} else {
 				// Fallback to V5 format if dispatcher not set using packet builder
-				h.sender.SendToSession(targetSession, h.packetBuilder.BuildUserStatus(targetSession, session.UIN, newStatus))
+				_ = h.sender.SendToSession(targetSession, h.packetBuilder.BuildUserStatus(targetSession, session.UIN, newStatus))
 			}
 		}
 	}
@@ -715,7 +715,7 @@ func (h *V5Handler) handleMessage(session *LegacySession, pkt *V5ClientPacket) e
 	}
 
 	// 1. Send ACK using packet builder
-	h.sender.SendPacket(session.Addr, h.packetBuilder.BuildAck(session, pkt.SeqNum1))
+	_ = h.sender.SendPacket(session.Addr, h.packetBuilder.BuildAck(session, pkt.SeqNum1))
 
 	// 2. Unmarshal packet to typed request struct
 	req, err := h.parseMessagePacket(pkt.Data, session.UIN)
@@ -750,10 +750,10 @@ func (h *V5Handler) handleMessage(session *LegacySession, pkt *V5ClientPacket) e
 		if targetSession != nil {
 			if h.dispatcher != nil {
 				// Use central dispatcher - routes to correct protocol based on target's version
-				h.dispatcher.SendOnlineMessage(targetSession, req.FromUIN, req.MsgType, req.Message)
+				_ = h.dispatcher.SendOnlineMessage(targetSession, req.FromUIN, req.MsgType, req.Message)
 			} else {
 				// Fallback to V5 format if dispatcher not set
-				h.sendOnlineMessage(targetSession, req.FromUIN, req.MsgType, req.Message)
+				_ = h.sendOnlineMessage(targetSession, req.FromUIN, req.MsgType, req.Message)
 			}
 			h.logger.Debug("V5 message forwarded",
 				"from", req.FromUIN,
@@ -817,7 +817,7 @@ func (h *V5Handler) handleUserAdd(session *LegacySession, pkt *V5ClientPacket) e
 	}
 
 	// 1. Send ACK using packet builder
-	h.sender.SendPacket(session.Addr, h.packetBuilder.BuildAck(session, pkt.SeqNum1))
+	_ = h.sender.SendPacket(session.Addr, h.packetBuilder.BuildAck(session, pkt.SeqNum1))
 
 	// 2. Unmarshal packet to typed request struct
 	req, err := h.parseUserAddPacket(pkt.Data, session.UIN)
@@ -847,7 +847,7 @@ func (h *V5Handler) handleUserAdd(session *LegacySession, pkt *V5ClientPacket) e
 	// 4. Send notifications based on service result
 	if result.TargetOnline {
 		// Send target's online status to the user who added them using packet builder
-		h.sender.SendToSession(session, h.packetBuilder.BuildUserOnline(session, req.TargetUIN, result.TargetStatus))
+		_ = h.sender.SendToSession(session, h.packetBuilder.BuildUserOnline(session, req.TargetUIN, result.TargetStatus))
 
 		// Send "you were added" notification to target user if service says to
 		if result.SendYouWereAdded {
@@ -859,10 +859,10 @@ func (h *V5Handler) handleUserAdd(session *LegacySession, pkt *V5ClientPacket) e
 
 				if h.dispatcher != nil {
 					// Use dispatcher for cross-protocol support
-					h.dispatcher.SendOnlineMessage(targetSession, req.FromUIN, ICQLegacyMsgAdded, youWereAddedMsg)
+					_ = h.dispatcher.SendOnlineMessage(targetSession, req.FromUIN, ICQLegacyMsgAdded, youWereAddedMsg)
 				} else {
 					// Fallback to V5 format using packet builder
-					h.sender.SendToSession(targetSession, h.packetBuilder.BuildOnlineMessage(targetSession, req.FromUIN, ICQLegacyMsgAdded, youWereAddedMsg))
+					_ = h.sender.SendToSession(targetSession, h.packetBuilder.BuildOnlineMessage(targetSession, req.FromUIN, ICQLegacyMsgAdded, youWereAddedMsg))
 				}
 
 				// Also send the adder's online status to the target.
@@ -871,9 +871,9 @@ func (h *V5Handler) handleUserAdd(session *LegacySession, pkt *V5ClientPacket) e
 				// this, the target's client shows the adder as offline
 				// even though they're connected.
 				if h.dispatcher != nil {
-					h.dispatcher.SendUserOnline(targetSession, req.FromUIN, session.GetStatus())
+					_ = h.dispatcher.SendUserOnline(targetSession, req.FromUIN, session.GetStatus())
 				} else {
-					h.sender.SendToSession(targetSession, h.packetBuilder.BuildUserOnline(targetSession, req.FromUIN, session.GetStatus()))
+					_ = h.sender.SendToSession(targetSession, h.packetBuilder.BuildUserOnline(targetSession, req.FromUIN, session.GetStatus()))
 				}
 
 				h.logger.Debug("V5 sent 'you were added' notification",
@@ -910,7 +910,7 @@ func (h *V5Handler) handleOfflineMsgReq(session *LegacySession, pkt *V5ClientPac
 		return nil
 	}
 
-	h.sendV5Ack(session, pkt.SeqNum1)
+	_ = h.sendV5Ack(session, pkt.SeqNum1)
 
 	ctx := context.Background()
 
@@ -949,7 +949,7 @@ func (h *V5Handler) handleOfflineMsgAck(session *LegacySession, pkt *V5ClientPac
 		return nil
 	}
 
-	h.sendV5Ack(session, pkt.SeqNum1)
+	_ = h.sendV5Ack(session, pkt.SeqNum1)
 
 	ctx := context.Background()
 
@@ -970,7 +970,7 @@ func (h *V5Handler) handleOldSearchUIN(session *LegacySession, pkt *V5ClientPack
 		return nil
 	}
 
-	h.sendV5Ack(session, pkt.SeqNum1)
+	_ = h.sendV5Ack(session, pkt.SeqNum1)
 
 	// Parse - format: TARGET_UIN(4)
 	if len(pkt.Data) < 4 {
@@ -1005,7 +1005,7 @@ func (h *V5Handler) handleOldSearchUIN(session *LegacySession, pkt *V5ClientPack
 	)
 
 	// Send search result
-	h.sendV5OldSearchFound(session, pkt.SeqNum2, result)
+	_ = h.sendV5OldSearchFound(session, pkt.SeqNum2, result)
 	return h.sendV5OldSearchEnd(session, pkt.SeqNum2, false)
 }
 
@@ -1016,7 +1016,7 @@ func (h *V5Handler) handleOldSearch(session *LegacySession, pkt *V5ClientPacket)
 		return nil
 	}
 
-	h.sendV5Ack(session, pkt.SeqNum1)
+	_ = h.sendV5Ack(session, pkt.SeqNum1)
 
 	h.logger.Info("V5 old search", "uin", session.UIN, "data_len", len(pkt.Data), "status", "not_implemented")
 
@@ -1031,7 +1031,7 @@ func (h *V5Handler) handleOldInfoReq(session *LegacySession, pkt *V5ClientPacket
 		return nil
 	}
 
-	h.sendV5Ack(session, pkt.SeqNum1)
+	_ = h.sendV5Ack(session, pkt.SeqNum1)
 
 	// Parse - format: TARGET_UIN(4)
 	if len(pkt.Data) < 4 {
@@ -1075,7 +1075,7 @@ func (h *V5Handler) handleOldExtInfoReq(session *LegacySession, pkt *V5ClientPac
 		return nil
 	}
 
-	h.sendV5Ack(session, pkt.SeqNum1)
+	_ = h.sendV5Ack(session, pkt.SeqNum1)
 
 	// Parse - format: TARGET_UIN(4)
 	if len(pkt.Data) < 4 {
@@ -1114,7 +1114,7 @@ func (h *V5Handler) handleAckNewUIN(session *LegacySession, pkt *V5ClientPacket)
 		return nil
 	}
 
-	h.sendV5Ack(session, pkt.SeqNum1)
+	_ = h.sendV5Ack(session, pkt.SeqNum1)
 
 	h.logger.Info("V5 ack new UIN", "uin", session.UIN)
 
@@ -1131,7 +1131,7 @@ func (h *V5Handler) handleLogin(session *LegacySession, addr *net.UDPAddr, pkt *
 	ctx := context.Background()
 
 	// 1. Send ACK first (iserverd does this immediately) using packet builder
-	h.sender.SendPacket(addr, h.packetBuilder.BuildAckToAddr(pkt.SessionID, pkt.UIN, pkt.SeqNum1, pkt.SeqNum2))
+	_ = h.sender.SendPacket(addr, h.packetBuilder.BuildAckToAddr(pkt.SessionID, pkt.UIN, pkt.SeqNum1, pkt.SeqNum2))
 
 	// 2. Unmarshal packet to typed struct
 	// V5 login data format (verified against licq.5 CPU_Logon):
@@ -1350,7 +1350,6 @@ func (h *V5Handler) sendV5UserOnline(session *LegacySession, uin uint32, status
 
 	// Status update time (not implemented)
 	binary.LittleEndian.PutUint32(data[offset:], 0)
-	offset += 4
 
 	pkt := &V5ServerPacket{
 		Version:   ICQLegacyVersionV5,
@@ -1499,13 +1498,13 @@ func (h *V5Handler) handleMetaLoginInfo(session *LegacySession, pkt *V5ClientPac
 	)
 
 	// Send all 7 info packets as per iserverd v5_reply_metafullinfo_request2()
-	h.sendMetaInfo3(session, pkt.SeqNum2, user)        // Basic info (0x00C8)
-	h.sendMetaMore2(session, pkt.SeqNum2, user)        // More info (0x00DC)
-	h.sendMetaHpageCat(session, pkt.SeqNum2, info)     // Homepage category (0x010E)
-	h.sendMetaWork2(session, pkt.SeqNum2, info)        // Work info (0x00D2)
-	h.sendMetaAbout(session, pkt.SeqNum2, info)        // About/notes (0x00E6)
-	h.sendMetaInterests(session, pkt.SeqNum2, info)    // Interests (0x00F0)
-	h.sendMetaAffiliations(session, pkt.SeqNum2, info) // Affiliations (0x00FA)
+	_ = h.sendMetaInfo3(session, pkt.SeqNum2, user)        // Basic info (0x00C8)
+	_ = h.sendMetaMore2(session, pkt.SeqNum2, user)        // More info (0x00DC)
+	_ = h.sendMetaHpageCat(session, pkt.SeqNum2, info)     // Homepage category (0x010E)
+	_ = h.sendMetaWork2(session, pkt.SeqNum2, info)        // Work info (0x00D2)
+	_ = h.sendMetaAbout(session, pkt.SeqNum2, info)        // About/notes (0x00E6)
+	_ = h.sendMetaInterests(session, pkt.SeqNum2, info)    // Interests (0x00F0)
+	_ = h.sendMetaAffiliations(session, pkt.SeqNum2, info) // Affiliations (0x00FA)
 
 	h.logger.Info("META login info - all packets sent", "target_uin", targetUIN)
 
@@ -1524,8 +1523,8 @@ func (h *V5Handler) handleMetaSetBasic(session *LegacySession, pkt *V5ClientPack
 	lastName, _ := parseLEString(r)
 	primaryEmail, _ := parseLEString(r)
 	// Secondary and old email — skip (not stored in ICQBasicInfo)
-	parseLEString(r) // secondary email
-	parseLEString(r) // old email
+	_, _ = parseLEString(r) // secondary email
+	_, _ = parseLEString(r) // old email
 	city, _ := parseLEString(r)
 	st, _ := parseLEString(r)
 	phone, _ := parseLEString(r)
@@ -1534,11 +1533,11 @@ func (h *V5Handler) handleMetaSetBasic(session *LegacySession, pkt *V5ClientPack
 	cellPhone, _ := parseLEString(r)
 	zip, _ := parseLEString(r)
 	var countryCode uint16
-	binary.Read(r, binary.LittleEndian, &countryCode)
+	_ = binary.Read(r, binary.LittleEndian, &countryCode)
 	var gmtOffset uint8
-	binary.Read(r, binary.LittleEndian, &gmtOffset)
+	_ = binary.Read(r, binary.LittleEndian, &gmtOffset)
 	var publishEmail uint8
-	binary.Read(r, binary.LittleEndian, &publishEmail)
+	_ = binary.Read(r, binary.LittleEndian, &publishEmail)
 
 	info := state.ICQBasicInfo{
 		Nickname:     nickname,
@@ -1929,13 +1928,13 @@ func (h *V5Handler) handleMetaUserFullInfo(session *LegacySession, pkt *V5Client
 
 	// Send all 7 info packets as per iserverd v5_reply_metafullinfo_request()
 	// Uses older format: sendMetaFullUserInfo (info2), sendMetaMore, sendMetaWork
-	h.sendMetaFullUserInfo(session, pkt.SeqNum2, info) // Basic info (0x00C8) - older format
-	h.sendMetaMore(session, pkt.SeqNum2, user)         // More info (0x00DC)
-	h.sendMetaHpageCat(session, pkt.SeqNum2, info)     // Homepage category (0x010E)
-	h.sendMetaWork(session, pkt.SeqNum2, info)         // Work info (0x00D2) - older format
-	h.sendMetaAbout(session, pkt.SeqNum2, info)        // About/notes (0x00E6)
-	h.sendMetaInterests(session, pkt.SeqNum2, info)    // Interests (0x00F0)
-	h.sendMetaAffiliations(session, pkt.SeqNum2, info) // Affiliations (0x00FA)
+	_ = h.sendMetaFullUserInfo(session, pkt.SeqNum2, info) // Basic info (0x00C8) - older format
+	_ = h.sendMetaMore(session, pkt.SeqNum2, user)         // More info (0x00DC)
+	_ = h.sendMetaHpageCat(session, pkt.SeqNum2, info)     // Homepage category (0x010E)
+	_ = h.sendMetaWork(session, pkt.SeqNum2, info)         // Work info (0x00D2) - older format
+	_ = h.sendMetaAbout(session, pkt.SeqNum2, info)        // About/notes (0x00E6)
+	_ = h.sendMetaInterests(session, pkt.SeqNum2, info)    // Interests (0x00F0)
+	_ = h.sendMetaAffiliations(session, pkt.SeqNum2, info) // Affiliations (0x00FA)
 
 	return nil
 }
@@ -1978,13 +1977,13 @@ func (h *V5Handler) handleMetaUserFullInfo2(session *LegacySession, pkt *V5Clien
 
 	// Send all 7 info packets as per iserverd v5_reply_metafullinfo_request2()
 	// Uses newer format: sendMetaInfo3, sendMetaMore2, sendMetaWork2
-	h.sendMetaInfo3(session, pkt.SeqNum2, user)        // Basic info (0x00C8) - newer format
-	h.sendMetaMore2(session, pkt.SeqNum2, user)        // More info (0x00DC)
-	h.sendMetaHpageCat(session, pkt.SeqNum2, info)     // Homepage category (0x010E)
-	h.sendMetaWork2(session, pkt.SeqNum2, info)        // Work info (0x00D2) - newer format
-	h.sendMetaAbout(session, pkt.SeqNum2, info)        // About/notes (0x00E6)
-	h.sendMetaInterests(session, pkt.SeqNum2, info)    // Interests (0x00F0)
-	h.sendMetaAffiliations(session, pkt.SeqNum2, info) // Affiliations (0x00FA)
+	_ = h.sendMetaInfo3(session, pkt.SeqNum2, user)        // Basic info (0x00C8) - newer format
+	_ = h.sendMetaMore2(session, pkt.SeqNum2, user)        // More info (0x00DC)
+	_ = h.sendMetaHpageCat(session, pkt.SeqNum2, info)     // Homepage category (0x010E)
+	_ = h.sendMetaWork2(session, pkt.SeqNum2, info)        // Work info (0x00D2) - newer format
+	_ = h.sendMetaAbout(session, pkt.SeqNum2, info)        // About/notes (0x00E6)
+	_ = h.sendMetaInterests(session, pkt.SeqNum2, info)    // Interests (0x00F0)
+	_ = h.sendMetaAffiliations(session, pkt.SeqNum2, info) // Affiliations (0x00FA)
 
 	return nil
 }
@@ -2049,7 +2048,7 @@ func (h *V5Handler) handleMetaSearchName(session *LegacySession, pkt *V5ClientPa
 	offset += n
 	first, n := readLPString(data, offset)
 	offset += n
-	last, n := readLPString(data, offset)
+	last, _ := readLPString(data, offset)
 
 	h.logger.Info("META search by name",
 		"uin", pkt.UIN,
@@ -2421,7 +2420,7 @@ func (h *V5Handler) handleMetaSearchWhite(session *LegacySession, pkt *V5ClientP
 		for i, result := range results {
 			isLast := i == len(results)-1
 			moreAvailable := len(results) >= maxResults && isLast
-			h.sendMetaWhiteFound(session, pkt.SeqNum2, &result, isLast, moreAvailable)
+			_ = h.sendMetaWhiteFound(session, pkt.SeqNum2, &result, isLast, moreAvailable)
 		}
 
 		return nil
@@ -2650,7 +2649,7 @@ func (h *V5Handler) handleMetaSearchWhite2(session *LegacySession, pkt *V5Client
 		for i, result := range results {
 			isLast := i == len(results)-1
 			moreAvailable := len(results) >= maxResults && isLast
-			h.sendMetaWhiteSearchResult2(session, pkt.SeqNum2, &result, isLast, moreAvailable)
+			_ = h.sendMetaWhiteSearchResult2(session, pkt.SeqNum2, &result, isLast, moreAvailable)
 		}
 
 		return nil
@@ -2675,16 +2674,16 @@ func (h *V5Handler) sendMetaWhiteSearchResult2(session *LegacySession, seqNum ui
 	}
 
 	buf := new(bytes.Buffer)
-	binary.Write(buf, binary.LittleEndian, subCommand)
+	_ = binary.Write(buf, binary.LittleEndian, subCommand)
 
 	if result != nil {
 		buf.WriteByte(0x0A) // success
 
 		// Calculate pack_len: 15 + strings + 4 for users_left
 		packLen := uint16(15 + len(result.Nickname) + len(result.FirstName) + len(result.LastName) + len(result.Email) + 4)
-		binary.Write(buf, binary.LittleEndian, packLen)
+		_ = binary.Write(buf, binary.LittleEndian, packLen)
 
-		binary.Write(buf, binary.LittleEndian, result.UIN)
+		_ = binary.Write(buf, binary.LittleEndian, result.UIN)
 		writeLegacyString(buf, result.Nickname)
 		writeLegacyString(buf, result.FirstName)
 		writeLegacyString(buf, result.LastName)
@@ -2698,10 +2697,10 @@ func (h *V5Handler) sendMetaWhiteSearchResult2(session *LegacySession, seqNum ui
 		if moreAvailable {
 			usersLeft = 1 // indicate more results available
 		}
-		binary.Write(buf, binary.LittleEndian, usersLeft)
+		_ = binary.Write(buf, binary.LittleEndian, usersLeft)
 	} else {
-		buf.WriteByte(0x32)                               // fail - no results
-		binary.Write(buf, binary.LittleEndian, uint32(0)) // users_left
+		buf.WriteByte(0x32)                                   // fail - no results
+		_ = binary.Write(buf, binary.LittleEndian, uint32(0)) // users_left
 	}
 
 	pkt := &V5ServerPacket{
@@ -2762,13 +2761,13 @@ func (h *V5Handler) sendMetaWhiteFound(session *LegacySession, seqNum uint16, re
 	}
 
 	buf := new(bytes.Buffer)
-	binary.Write(buf, binary.LittleEndian, subCommand)
+	_ = binary.Write(buf, binary.LittleEndian, subCommand)
 
 	if result != nil {
 		buf.WriteByte(0x0A) // success
 
 		// Original format: NO pack_len field (unlike White2)
-		binary.Write(buf, binary.LittleEndian, result.UIN)
+		_ = binary.Write(buf, binary.LittleEndian, result.UIN)
 		writeLegacyString(buf, result.Nickname)
 		writeLegacyString(buf, result.FirstName)
 		writeLegacyString(buf, result.LastName)
@@ -2782,13 +2781,13 @@ func (h *V5Handler) sendMetaWhiteFound(session *LegacySession, seqNum uint16, re
 			if moreAvailable {
 				usersLeft = 1 // indicate more results available
 			}
-			binary.Write(buf, binary.LittleEndian, usersLeft)
+			_ = binary.Write(buf, binary.LittleEndian, usersLeft)
 		}
 	} else {
 		buf.WriteByte(0x32) // fail - no results
 		// users_left indicator (only if last)
 		if isLast {
-			binary.Write(buf, binary.LittleEndian, uint32(0)) // users_left
+			_ = binary.Write(buf, binary.LittleEndian, uint32(0)) // users_left
 		}
 	}
 
@@ -2828,14 +2827,14 @@ func (h *V5Handler) sendMetaWhiteSearchEnd(session *LegacySession, seqNum uint16
 	subCommand := ICQLegacySrvMetaWhiteLastFound // 0x01AE
 
 	buf := new(bytes.Buffer)
-	binary.Write(buf, binary.LittleEndian, subCommand)
+	_ = binary.Write(buf, binary.LittleEndian, subCommand)
 
 	if success {
 		buf.WriteByte(0x0A) // success but no results
 	} else {
 		buf.WriteByte(0x32) // fail - no results
 	}
-	binary.Write(buf, binary.LittleEndian, uint32(0)) // users_left
+	_ = binary.Write(buf, binary.LittleEndian, uint32(0)) // users_left
 
 	pkt := &V5ServerPacket{
 		Version:   ICQLegacyVersionV5,
@@ -3052,7 +3051,7 @@ func (h *V5Handler) sendMetaFail(session *LegacySession, seq2 uint16, subCommand
 	}
 
 	buf := new(bytes.Buffer)
-	binary.Write(buf, binary.LittleEndian, subCommand)
+	_ = binary.Write(buf, binary.LittleEndian, subCommand)
 	buf.WriteByte(0x32) // fail
 
 	pkt := &V5ServerPacket{
@@ -3076,8 +3075,8 @@ func (h *V5Handler) sendMetaInfo3(session *LegacySession, seq2 uint16, user *sta
 	}
 
 	buf := new(bytes.Buffer)
-	binary.Write(buf, binary.LittleEndian, uint16(0x00C8)) // SRV_META_USER_INFO2
-	buf.WriteByte(0x0A)                                    // success
+	_ = binary.Write(buf, binary.LittleEndian, uint16(0x00C8)) // SRV_META_USER_INFO2
+	buf.WriteByte(0x0A)                                        // success
 	writeLegacyString(buf, user.ICQInfo.Basic.Nickname)
 	writeLegacyString(buf, user.ICQInfo.Basic.FirstName)
 	writeLegacyString(buf, user.ICQInfo.Basic.LastName)
@@ -3091,8 +3090,8 @@ func (h *V5Handler) sendMetaInfo3(session *LegacySession, seq2 uint16, user *sta
 	writeLegacyString(buf, user.ICQInfo.Basic.Address)
 	writeLegacyString(buf, user.ICQInfo.Basic.CellPhone)
 	writeLegacyString(buf, user.ICQInfo.Basic.ZIPCode)
-	binary.Write(buf, binary.LittleEndian, user.ICQInfo.Basic.CountryCode)
-	binary.Write(buf, binary.LittleEndian, uint16(user.ICQInfo.Basic.GMTOffset))
+	_ = binary.Write(buf, binary.LittleEndian, user.ICQInfo.Basic.CountryCode)
+	_ = binary.Write(buf, binary.LittleEndian, uint16(user.ICQInfo.Basic.GMTOffset))
 	authFlag := uint8(0)
 	if user.ICQInfo.Permissions.AuthRequired {
 		authFlag = 1
@@ -3155,17 +3154,17 @@ func (h *V5Handler) sendMetaMore(session *LegacySession, seq2 uint16, user *stat
 	}
 
 	buf := new(bytes.Buffer)
-	binary.Write(buf, binary.LittleEndian, ICQLegacySrvMetaInfoMore) // SRV_META_INFO_MORE = 0x00DC
-	buf.WriteByte(0x0A)                                              // success
-	binary.Write(buf, binary.LittleEndian, user.Age(time.Now))       // age(2)
-	buf.WriteByte(uint8(user.ICQInfo.More.Gender))                   // gender(1)
-	writeLegacyString(buf, user.ICQInfo.More.HomePageAddr)           // homepage_len(2) + homepage
-	buf.WriteByte(tempYear)                                          // byear(1) - year minus 1900
-	buf.WriteByte(user.ICQInfo.More.BirthMonth)                      // bmonth(1)
-	buf.WriteByte(user.ICQInfo.More.BirthDay)                        // bday(1)
-	buf.WriteByte(user.ICQInfo.More.Lang1)                           // lang1(1)
-	buf.WriteByte(user.ICQInfo.More.Lang2)                           // lang2(1)
-	buf.WriteByte(user.ICQInfo.More.Lang3)                           // lang3(1)
+	_ = binary.Write(buf, binary.LittleEndian, ICQLegacySrvMetaInfoMore) // SRV_META_INFO_MORE = 0x00DC
+	buf.WriteByte(0x0A)                                                  // success
+	_ = binary.Write(buf, binary.LittleEndian, user.Age(time.Now))       // age(2)
+	buf.WriteByte(uint8(user.ICQInfo.More.Gender))                       // gender(1)
+	writeLegacyString(buf, user.ICQInfo.More.HomePageAddr)               // homepage_len(2) + homepage
+	buf.WriteByte(tempYear)                                              // byear(1) - year minus 1900
+	buf.WriteByte(user.ICQInfo.More.BirthMonth)                          // bmonth(1)
+	buf.WriteByte(user.ICQInfo.More.BirthDay)                            // bday(1)
+	buf.WriteByte(user.ICQInfo.More.Lang1)                               // lang1(1)
+	buf.WriteByte(user.ICQInfo.More.Lang2)                               // lang2(1)
+	buf.WriteByte(user.ICQInfo.More.Lang3)                               // lang3(1)
 
 	pkt := &V5ServerPacket{
 		Version:   ICQLegacyVersionV5,
@@ -3206,17 +3205,17 @@ func (h *V5Handler) sendMetaMore2(session *LegacySession, seq2 uint16, user *sta
 	}
 
 	buf := new(bytes.Buffer)
-	binary.Write(buf, binary.LittleEndian, ICQLegacySrvMetaInfoMore)    // SRV_META_INFO_MORE = 0x00DC
-	buf.WriteByte(0x0A)                                                 // success
-	binary.Write(buf, binary.LittleEndian, user.Age(time.Now))          // age(2)
-	buf.WriteByte(uint8(user.ICQInfo.More.Gender))                      // gender(1)
-	writeLegacyString(buf, user.ICQInfo.More.HomePageAddr)              // homepage_len(2) + homepage
-	binary.Write(buf, binary.LittleEndian, user.ICQInfo.More.BirthYear) // byear(2) - full year
-	buf.WriteByte(user.ICQInfo.More.BirthMonth)                         // bmonth(1)
-	buf.WriteByte(user.ICQInfo.More.BirthDay)                           // bday(1)
-	buf.WriteByte(user.ICQInfo.More.Lang1)                              // lang1(1)
-	buf.WriteByte(user.ICQInfo.More.Lang2)                              // lang2(1)
-	buf.WriteByte(user.ICQInfo.More.Lang3)                              // lang3(1)
+	_ = binary.Write(buf, binary.LittleEndian, ICQLegacySrvMetaInfoMore)    // SRV_META_INFO_MORE = 0x00DC
+	buf.WriteByte(0x0A)                                                     // success
+	_ = binary.Write(buf, binary.LittleEndian, user.Age(time.Now))          // age(2)
+	buf.WriteByte(uint8(user.ICQInfo.More.Gender))                          // gender(1)
+	writeLegacyString(buf, user.ICQInfo.More.HomePageAddr)                  // homepage_len(2) + homepage
+	_ = binary.Write(buf, binary.LittleEndian, user.ICQInfo.More.BirthYear) // byear(2) - full year
+	buf.WriteByte(user.ICQInfo.More.BirthMonth)                             // bmonth(1)
+	buf.WriteByte(user.ICQInfo.More.BirthDay)                               // bday(1)
+	buf.WriteByte(user.ICQInfo.More.Lang1)                                  // lang1(1)
+	buf.WriteByte(user.ICQInfo.More.Lang2)                                  // lang2(1)
+	buf.WriteByte(user.ICQInfo.More.Lang3)                                  // lang3(1)
 
 	pkt := &V5ServerPacket{
 		Version:   ICQLegacyVersionV5,
@@ -3239,12 +3238,12 @@ func (h *V5Handler) sendMetaHpageCat(session *LegacySession, seq2 uint16, info *
 	}
 
 	buf := new(bytes.Buffer)
-	binary.Write(buf, binary.LittleEndian, uint16(0x010E)) // SRV_META_INFO_HPAGE_CAT
-	buf.WriteByte(0x0A)                                    // success
-	buf.WriteByte(0x00)                                    // hpage_cf (enabled)
-	binary.Write(buf, binary.LittleEndian, uint16(0))      // hpage_cat
-	writeLegacyString(buf, "")                             // hpage_txt
-	buf.WriteByte(0x00)                                    // unknown
+	_ = binary.Write(buf, binary.LittleEndian, uint16(0x010E)) // SRV_META_INFO_HPAGE_CAT
+	buf.WriteByte(0x0A)                                        // success
+	buf.WriteByte(0x00)                                        // hpage_cf (enabled)
+	_ = binary.Write(buf, binary.LittleEndian, uint16(0))      // hpage_cat
+	writeLegacyString(buf, "")                                 // hpage_txt
+	buf.WriteByte(0x00)                                        // unknown
 
 	pkt := &V5ServerPacket{
 		Version:   ICQLegacyVersionV5,
@@ -3269,20 +3268,20 @@ func (h *V5Handler) sendMetaWork(session *LegacySession, seq2 uint16, info *Lega
 	}
 
 	buf := new(bytes.Buffer)
-	binary.Write(buf, binary.LittleEndian, uint16(0x00D2)) // SRV_META_INFO_WORK
-	buf.WriteByte(0x0A)                                    // success
-	writeLegacyString(buf, "")                             // wcity
-	writeLegacyString(buf, "")                             // wstate
-	writeLegacyString(buf, "")                             // wphone
-	writeLegacyString(buf, "")                             // wfax
-	writeLegacyString(buf, "")                             // waddr
-	binary.Write(buf, binary.LittleEndian, uint32(0))      // wzip (uint32 in work, string in work2)
-	binary.Write(buf, binary.LittleEndian, uint16(0))      // wcountry
-	writeLegacyString(buf, "")                             // wcompany
-	writeLegacyString(buf, "")                             // wdepart
-	writeLegacyString(buf, "")                             // wtitle
-	binary.Write(buf, binary.LittleEndian, uint16(0))      // wocup (occupation code)
-	writeLegacyString(buf, "")                             // wpage (work webpage)
+	_ = binary.Write(buf, binary.LittleEndian, uint16(0x00D2)) // SRV_META_INFO_WORK
+	buf.WriteByte(0x0A)                                        // success
+	writeLegacyString(buf, "")                                 // wcity
+	writeLegacyString(buf, "")                                 // wstate
+	writeLegacyString(buf, "")                                 // wphone
+	writeLegacyString(buf, "")                                 // wfax
+	writeLegacyString(buf, "")                                 // waddr
+	_ = binary.Write(buf, binary.LittleEndian, uint32(0))      // wzip (uint32 in work, string in work2)
+	_ = binary.Write(buf, binary.LittleEndian, uint16(0))      // wcountry
+	writeLegacyString(buf, "")                                 // wcompany
+	writeLegacyString(buf, "")                                 // wdepart
+	writeLegacyString(buf, "")                                 // wtitle
+	_ = binary.Write(buf, binary.LittleEndian, uint16(0))      // wocup (occupation code)
+	writeLegacyString(buf, "")                                 // wpage (work webpage)
 
 	pkt := &V5ServerPacket{
 		Version:   ICQLegacyVersionV5,
@@ -3307,20 +3306,20 @@ func (h *V5Handler) sendMetaWork2(session *LegacySession, seq2 uint16, info *Leg
 	}
 
 	buf := new(bytes.Buffer)
-	binary.Write(buf, binary.LittleEndian, uint16(0x00D2)) // SRV_META_INFO_WORK
-	buf.WriteByte(0x0A)                                    // success
-	writeLegacyString(buf, "")                             // wcity
-	writeLegacyString(buf, "")                             // wstate
-	writeLegacyString(buf, "")                             // wphone
-	writeLegacyString(buf, "")                             // wfax
-	writeLegacyString(buf, "")                             // waddr
-	writeLegacyString(buf, "")                             // wzip (string in work2, uint32 in work)
-	binary.Write(buf, binary.LittleEndian, uint16(0))      // wcountry
-	writeLegacyString(buf, "")                             // wcompany
-	writeLegacyString(buf, "")                             // wdepart
-	writeLegacyString(buf, "")                             // wtitle
-	binary.Write(buf, binary.LittleEndian, uint16(0))      // wocup (occupation code)
-	writeLegacyString(buf, "")                             // wpage (work webpage)
+	_ = binary.Write(buf, binary.LittleEndian, uint16(0x00D2)) // SRV_META_INFO_WORK
+	buf.WriteByte(0x0A)                                        // success
+	writeLegacyString(buf, "")                                 // wcity
+	writeLegacyString(buf, "")                                 // wstate
+	writeLegacyString(buf, "")                                 // wphone
+	writeLegacyString(buf, "")                                 // wfax
+	writeLegacyString(buf, "")                                 // waddr
+	writeLegacyString(buf, "")                                 // wzip (string in work2, uint32 in work)
+	_ = binary.Write(buf, binary.LittleEndian, uint16(0))      // wcountry
+	writeLegacyString(buf, "")                                 // wcompany
+	writeLegacyString(buf, "")                                 // wdepart
+	writeLegacyString(buf, "")                                 // wtitle
+	_ = binary.Write(buf, binary.LittleEndian, uint16(0))      // wocup (occupation code)
+	writeLegacyString(buf, "")                                 // wpage (work webpage)
 
 	pkt := &V5ServerPacket{
 		Version:   ICQLegacyVersionV5,
@@ -3343,9 +3342,9 @@ func (h *V5Handler) sendMetaAbout(session *LegacySession, seq2 uint16, info *Leg
 	}
 
 	buf := new(bytes.Buffer)
-	binary.Write(buf, binary.LittleEndian, uint16(0x00E6)) // SRV_META_INFO_ABOUT
-	buf.WriteByte(0x0A)                                    // success
-	writeLegacyString(buf, "")                             // notes
+	_ = binary.Write(buf, binary.LittleEndian, uint16(0x00E6)) // SRV_META_INFO_ABOUT
+	buf.WriteByte(0x0A)                                        // success
+	writeLegacyString(buf, "")                                 // notes
 
 	pkt := &V5ServerPacket{
 		Version:   ICQLegacyVersionV5,
@@ -3368,9 +3367,9 @@ func (h *V5Handler) sendMetaInterests(session *LegacySession, seq2 uint16, info
 	}
 
 	buf := new(bytes.Buffer)
-	binary.Write(buf, binary.LittleEndian, uint16(0x00F0)) // SRV_META_INFO_INTERESTS
-	buf.WriteByte(0x0A)                                    // success
-	buf.WriteByte(0x00)                                    // int_num (0 interests)
+	_ = binary.Write(buf, binary.LittleEndian, uint16(0x00F0)) // SRV_META_INFO_INTERESTS
+	buf.WriteByte(0x0A)                                        // success
+	buf.WriteByte(0x00)                                        // int_num (0 interests)
 
 	pkt := &V5ServerPacket{
 		Version:   ICQLegacyVersionV5,
@@ -3393,27 +3392,27 @@ func (h *V5Handler) sendMetaAffiliations(session *LegacySession, seq2 uint16, in
 	}
 
 	buf := new(bytes.Buffer)
-	binary.Write(buf, binary.LittleEndian, uint16(0x00FA)) // SRV_META_INFO_AFFILATIONS
-	buf.WriteByte(0x0A)                                    // success
+	_ = binary.Write(buf, binary.LittleEndian, uint16(0x00FA)) // SRV_META_INFO_AFFILATIONS
+	buf.WriteByte(0x0A)                                        // success
 	// Past backgrounds (3 empty entries)
-	buf.WriteByte(0x03)                               // past_num
-	binary.Write(buf, binary.LittleEndian, uint16(0)) // past_ind1
-	writeLegacyString(buf, "")                        // past_key1
-	binary.Write(buf, binary.LittleEndian, uint16(0)) // past_ind2
-	writeLegacyString(buf, "")                        // past_key2
-	binary.Write(buf, binary.LittleEndian, uint16(0)) // past_ind3
-	writeLegacyString(buf, "")                        // past_key3
+	buf.WriteByte(0x03)                                   // past_num
+	_ = binary.Write(buf, binary.LittleEndian, uint16(0)) // past_ind1
+	writeLegacyString(buf, "")                            // past_key1
+	_ = binary.Write(buf, binary.LittleEndian, uint16(0)) // past_ind2
+	writeLegacyString(buf, "")                            // past_key2
+	_ = binary.Write(buf, binary.LittleEndian, uint16(0)) // past_ind3
+	writeLegacyString(buf, "")                            // past_key3
 	// Affiliations (3 empty entries)
-	buf.WriteByte(0x03)                               // aff_num
-	binary.Write(buf, binary.LittleEndian, uint16(0)) // aff_ind1
-	writeLegacyString(buf, "")                        // aff_key1
-	binary.Write(buf, binary.LittleEndian, uint16(0)) // aff_ind2
-	writeLegacyString(buf, "")                        // aff_key2
-	binary.Write(buf, binary.LittleEndian, uint16(0)) // aff_ind3
-	writeLegacyString(buf, "")                        // aff_key3
+	buf.WriteByte(0x03)                                   // aff_num
+	_ = binary.Write(buf, binary.LittleEndian, uint16(0)) // aff_ind1
+	writeLegacyString(buf, "")                            // aff_key1
+	_ = binary.Write(buf, binary.LittleEndian, uint16(0)) // aff_ind2
+	writeLegacyString(buf, "")                            // aff_key2
+	_ = binary.Write(buf, binary.LittleEndian, uint16(0)) // aff_ind3
+	writeLegacyString(buf, "")                            // aff_key3
 	// Trailing bytes
-	binary.Write(buf, binary.LittleEndian, uint16(0x0000))
-	binary.Write(buf, binary.LittleEndian, uint16(0x0001))
+	_ = binary.Write(buf, binary.LittleEndian, uint16(0x0000))
+	_ = binary.Write(buf, binary.LittleEndian, uint16(0x0001))
 	buf.WriteByte(0x00)
 
 	pkt := &V5ServerPacket{
@@ -3439,9 +3438,9 @@ func (h *V5Handler) sendMetaFullUserInfo(session *LegacySession, seq2 uint16, in
 	// Build basic info response (SRV_META_USER_INFO2 = 0x00C8)
 	// Format: SUB_CMD(2) + SUCCESS(1) + UIN(4) + NICK_LEN(2) + NICK + FIRST_LEN(2) + FIRST + LAST_LEN(2) + LAST + EMAIL_LEN(2) + EMAIL + AUTH(1)
 	buf := new(bytes.Buffer)
-	binary.Write(buf, binary.LittleEndian, uint16(0x00C8)) // SRV_META_USER_INFO2
-	buf.WriteByte(0x0A)                                    // success
-	binary.Write(buf, binary.LittleEndian, info.UIN)
+	_ = binary.Write(buf, binary.LittleEndian, uint16(0x00C8)) // SRV_META_USER_INFO2
+	buf.WriteByte(0x0A)                                        // success
+	_ = binary.Write(buf, binary.LittleEndian, info.UIN)
 	writeLegacyString(buf, info.Nickname)
 	writeLegacyString(buf, info.FirstName)
 	writeLegacyString(buf, info.LastName)
@@ -3487,7 +3486,7 @@ func (h *V5Handler) sendMetaUserInfo(session *LegacySession, seqNum uint16, info
 	buf := new(bytes.Buffer)
 
 	// SubCommand: SRV_META_USER_INFO (0x0104)
-	binary.Write(buf, binary.LittleEndian, ICQLegacySrvMetaUserInfo)
+	_ = binary.Write(buf, binary.LittleEndian, ICQLegacySrvMetaUserInfo)
 
 	if info != nil {
 		// Success byte: 0x0A
@@ -3566,7 +3565,7 @@ func (h *V5Handler) sendV5OldSearchFound(session *LegacySession, seq2 uint16, re
 	// Build data: UIN(4) + NICK_LEN(2) + NICK + FIRST_LEN(2) + FIRST + LAST_LEN(2) + LAST + EMAIL_LEN(2) + EMAIL + AUTH(1) + 0x00(1)
 	// Note: The JUNK(4) in iserverd is the checkcode placeholder in the header, not part of data
 	buf := new(bytes.Buffer)
-	binary.Write(buf, binary.LittleEndian, result.UIN)
+	_ = binary.Write(buf, binary.LittleEndian, result.UIN)
 	writeLegacyString(buf, result.Nickname)
 	writeLegacyString(buf, result.FirstName)
 	writeLegacyString(buf, result.LastName)
@@ -3641,7 +3640,7 @@ func (h *V5Handler) sendV5OldStyleInfo(session *LegacySession, info *LegacyUserS
 	// Build data: UIN(4) + NICK_LEN(2) + NICK + FIRST_LEN(2) + FIRST + LAST_LEN(2) + LAST + EMAIL_LEN(2) + EMAIL + AUTH(1)
 	// Note: The JUNK(4) in iserverd is the checkcode placeholder in the header, not part of data
 	buf := new(bytes.Buffer)
-	binary.Write(buf, binary.LittleEndian, info.UIN)
+	_ = binary.Write(buf, binary.LittleEndian, info.UIN)
 	writeLegacyString(buf, info.Nickname)
 	writeLegacyString(buf, info.FirstName)
 	writeLegacyString(buf, info.LastName)
@@ -3706,24 +3705,24 @@ func (h *V5Handler) sendV5OldStyleInfoExt(session *LegacySession, targetUIN uint
 	// The licq.5 client reads this as UnpackUnsignedLong (4 bytes)
 	var zipCode uint32
 	if user.ICQInfo.Basic.ZIPCode != "" {
-		fmt.Sscanf(user.ICQInfo.Basic.ZIPCode, "%d", &zipCode)
+		_, _ = fmt.Sscanf(user.ICQInfo.Basic.ZIPCode, "%d", &zipCode)
 	}
 
 	// Build data matching licq.5 client's USERxDETAILS parsing:
 	// UIN(4) + CITY(string) + COUNTRY(2) + TIMEZONE(1) + STATE(string) +
 	// AGE(2) + GENDER(1) + PHONE(string) + HOMEPAGE(string) + ABOUT(string) + ZIPCODE(4)
 	buf := new(bytes.Buffer)
-	binary.Write(buf, binary.LittleEndian, targetUIN)
-	writeLegacyString(buf, hcity)                    // city (length-prefixed with null terminator)
-	binary.Write(buf, binary.LittleEndian, hcountry) // country code
-	buf.WriteByte(gmtOffset)                         // timezone / GMT offset (client reads as SetTimezone)
-	writeLegacyString(buf, hstate)                   // state (length-prefixed with null terminator)
-	binary.Write(buf, binary.LittleEndian, age)      // age
-	buf.WriteByte(gender)                            // gender
-	writeLegacyString(buf, hphone)                   // phone (length-prefixed with null terminator)
-	writeLegacyString(buf, hpage)                    // homepage (length-prefixed with null terminator)
-	writeLegacyString(buf, notes)                    // about/notes (length-prefixed with null terminator)
-	binary.Write(buf, binary.LittleEndian, zipCode)  // zip code (uint32, client reads as UnpackUnsignedLong)
+	_ = binary.Write(buf, binary.LittleEndian, targetUIN)
+	writeLegacyString(buf, hcity)                        // city (length-prefixed with null terminator)
+	_ = binary.Write(buf, binary.LittleEndian, hcountry) // country code
+	buf.WriteByte(gmtOffset)                             // timezone / GMT offset (client reads as SetTimezone)
+	writeLegacyString(buf, hstate)                       // state (length-prefixed with null terminator)
+	_ = binary.Write(buf, binary.LittleEndian, age)      // age
+	buf.WriteByte(gender)                                // gender
+	writeLegacyString(buf, hphone)                       // phone (length-prefixed with null terminator)
+	writeLegacyString(buf, hpage)                        // homepage (length-prefixed with null terminator)
+	writeLegacyString(buf, notes)                        // about/notes (length-prefixed with null terminator)
+	_ = binary.Write(buf, binary.LittleEndian, zipCode)  // zip code (uint32, client reads as UnpackUnsignedLong)
 
 	pkt := &V5ServerPacket{
 		Version:   ICQLegacyVersionV5,
@@ -3781,7 +3780,7 @@ func (h *V5Handler) sendV5InvalidUIN(session *LegacySession, uin uint32) error {
 // writeLegacyString writes a length-prefixed string to a buffer
 func writeLegacyString(buf *bytes.Buffer, s string) {
 	length := uint16(len(s) + 1)
-	binary.Write(buf, binary.LittleEndian, length)
+	_ = binary.Write(buf, binary.LittleEndian, length)
 	buf.WriteString(s)
 	buf.WriteByte(0) // null terminator
 }
@@ -3812,7 +3811,7 @@ func (h *V5Handler) handleDirectWhiteSearch(session *LegacySession, pkt *V5Clien
 		return nil
 	}
 
-	h.sendV5Ack(session, pkt.SeqNum1)
+	_ = h.sendV5Ack(session, pkt.SeqNum1)
 
 	h.logger.Info("V5 direct white pages search", "uin", session.UIN, "data_len", len(pkt.Data))
 
@@ -3826,7 +3825,7 @@ func (h *V5Handler) handleDirectNameSearch(session *LegacySession, pkt *V5Client
 		return nil
 	}
 
-	h.sendV5Ack(session, pkt.SeqNum1)
+	_ = h.sendV5Ack(session, pkt.SeqNum1)
 
 	// Parse three length-prefixed strings: nick, first, last
 	offset := 0
@@ -3858,7 +3857,7 @@ func (h *V5Handler) handleDirectNameSearch(session *LegacySession, pkt *V5Client
 	h.logger.Info("V5 direct name search - found", "uin", session.UIN, "count", len(results))
 
 	for _, r := range results {
-		h.sendV5OldSearchFound(session, pkt.SeqNum2, &r)
+		_ = h.sendV5OldSearchFound(session, pkt.SeqNum2, &r)
 	}
 	return h.sendV5OldSearchEnd(session, pkt.SeqNum2, false)
 }
@@ -3869,7 +3868,7 @@ func (h *V5Handler) handleDirectUINSearch(session *LegacySession, pkt *V5ClientP
 		return nil
 	}
 
-	h.sendV5Ack(session, pkt.SeqNum1)
+	_ = h.sendV5Ack(session, pkt.SeqNum1)
 
 	// Parse target UIN
 	if len(pkt.Data) < 4 {
@@ -3900,7 +3899,7 @@ func (h *V5Handler) handleDirectUINSearch(session *LegacySession, pkt *V5ClientP
 		"nickname", result.Nickname,
 	)
 
-	h.sendV5OldSearchFound(session, pkt.SeqNum2, result)
+	_ = h.sendV5OldSearchFound(session, pkt.SeqNum2, result)
 	return h.sendV5OldSearchEnd(session, pkt.SeqNum2, false)
 }
 
@@ -3910,7 +3909,7 @@ func (h *V5Handler) handleDirectEmailSearch(session *LegacySession, pkt *V5Clien
 		return nil
 	}
 
-	h.sendV5Ack(session, pkt.SeqNum1)
+	_ = h.sendV5Ack(session, pkt.SeqNum1)
 
 	email, _ := readLPString(pkt.Data, 0)
 
@@ -3934,7 +3933,7 @@ func (h *V5Handler) handleDirectEmailSearch(session *LegacySession, pkt *V5Clien
 	h.logger.Info("V5 direct email search - found", "uin", session.UIN, "count", len(results))
 
 	for _, r := range results {
-		h.sendV5OldSearchFound(session, pkt.SeqNum2, &r)
+		_ = h.sendV5OldSearchFound(session, pkt.SeqNum2, &r)
 	}
 	return h.sendV5OldSearchEnd(session, pkt.SeqNum2, false)
 }
@@ -4031,7 +4030,6 @@ func (h *V5Handler) sendV5UserStatus(session *LegacySession, uin uint32, status
 
 	// Extended status (high word)
 	binary.LittleEndian.PutUint16(data[offset:], uint16(status>>16))
-	offset += 2
 
 	pkt := &V5ServerPacket{
 		Version:   ICQLegacyVersionV5,

+ 30 - 30
server/icq_legacy/wire_legacy.go

@@ -652,14 +652,14 @@ func BuildV2Ack(seqNum uint16) *V2ServerPacket {
 // which the client treats as "received now".
 func BuildV2OfflineMessage(seqNum uint16, fromUIN uint32, msgType uint16, message string, timestamp time.Time) *V2ServerPacket {
 	buf := new(bytes.Buffer)
-	binary.Write(buf, binary.LittleEndian, fromUIN)
-	binary.Write(buf, binary.LittleEndian, uint16(timestamp.Year()))
+	_ = binary.Write(buf, binary.LittleEndian, fromUIN)
+	_ = binary.Write(buf, binary.LittleEndian, uint16(timestamp.Year()))
 	buf.WriteByte(byte(timestamp.Month()))
 	buf.WriteByte(byte(timestamp.Day()))
 	buf.WriteByte(byte(timestamp.Hour()))
 	buf.WriteByte(byte(timestamp.Minute()))
-	binary.Write(buf, binary.LittleEndian, msgType)
-	WriteLegacyString(buf, message)
+	_ = binary.Write(buf, binary.LittleEndian, msgType)
+	_ = WriteLegacyString(buf, message)
 
 	return &V2ServerPacket{
 		Version: ICQLegacyVersionV2,
@@ -672,9 +672,9 @@ func BuildV2OfflineMessage(seqNum uint16, fromUIN uint32, msgType uint16, messag
 // BuildV2Message creates a message delivery packet
 func BuildV2Message(seqNum uint16, fromUIN uint32, msgType uint16, message string) *V2ServerPacket {
 	buf := new(bytes.Buffer)
-	binary.Write(buf, binary.LittleEndian, fromUIN)
-	binary.Write(buf, binary.LittleEndian, msgType)
-	WriteLegacyString(buf, message)
+	_ = binary.Write(buf, binary.LittleEndian, fromUIN)
+	_ = binary.Write(buf, binary.LittleEndian, msgType)
+	_ = WriteLegacyString(buf, message)
 
 	return &V2ServerPacket{
 		Version: ICQLegacyVersionV2,
@@ -718,7 +718,7 @@ func BuildV2ContactListDone(seqNum uint16, uin uint32) *V2ServerPacket {
 // V2 search done format (from licq): SEQ(2) + MORE(1)
 func BuildV2SearchResult(seqNum uint16, user *LegacyUserInfo, isLast bool) *V2ServerPacket {
 	buf := new(bytes.Buffer)
-	binary.Write(buf, binary.LittleEndian, seqNum)
+	_ = binary.Write(buf, binary.LittleEndian, seqNum)
 
 	if user.UIN == 0 && isLast {
 		// No results — just send search done with more=0
@@ -732,11 +732,11 @@ func BuildV2SearchResult(seqNum uint16, user *LegacyUserInfo, isLast bool) *V2Se
 	}
 
 	// Send search found with user data
-	binary.Write(buf, binary.LittleEndian, user.UIN)
-	WriteLegacyString(buf, user.Nickname)
-	WriteLegacyString(buf, user.FirstName)
-	WriteLegacyString(buf, user.LastName)
-	WriteLegacyString(buf, user.Email)
+	_ = binary.Write(buf, binary.LittleEndian, user.UIN)
+	_ = WriteLegacyString(buf, user.Nickname)
+	_ = WriteLegacyString(buf, user.FirstName)
+	_ = WriteLegacyString(buf, user.LastName)
+	_ = WriteLegacyString(buf, user.Email)
 	buf.WriteByte(user.Auth)
 
 	return &V2ServerPacket{
@@ -754,17 +754,17 @@ func BuildV2SearchResult(seqNum uint16, user *LegacyUserInfo, isLast bool) *V2Se
 func BuildV2InfoReply(serverSeq uint16, checkSeq uint16, user *LegacyUserInfo) *V2ServerPacket {
 	buf := new(bytes.Buffer)
 	// checkSequence - echoes the client's info sub-sequence so DoneExtendedEvent can match
-	binary.Write(buf, binary.LittleEndian, checkSeq)
-	binary.Write(buf, binary.LittleEndian, user.UIN)
-	WriteLegacyString(buf, user.Nickname)
-	WriteLegacyString(buf, user.FirstName)
-	WriteLegacyString(buf, user.LastName)
-	WriteLegacyString(buf, user.Email)
+	_ = binary.Write(buf, binary.LittleEndian, checkSeq)
+	_ = binary.Write(buf, binary.LittleEndian, user.UIN)
+	_ = WriteLegacyString(buf, user.Nickname)
+	_ = WriteLegacyString(buf, user.FirstName)
+	_ = WriteLegacyString(buf, user.LastName)
+	_ = WriteLegacyString(buf, user.Email)
 	// AUTH byte (0 = auth not required, 1 = auth required)
 	buf.WriteByte(user.Auth)
 	// Trailing padding (observed in licq example packets)
-	binary.Write(buf, binary.LittleEndian, uint16(0))
-	binary.Write(buf, binary.LittleEndian, uint16(0))
+	_ = binary.Write(buf, binary.LittleEndian, uint16(0))
+	_ = binary.Write(buf, binary.LittleEndian, uint16(0))
 
 	return &V2ServerPacket{
 		Version: ICQLegacyVersionV2,
@@ -781,27 +781,27 @@ func BuildV2InfoReply(serverSeq uint16, checkSeq uint16, user *LegacyUserInfo) *
 func BuildV2ExtInfoReply(serverSeq uint16, checkSeq uint16, user *LegacyUserInfo) *V2ServerPacket {
 	buf := new(bytes.Buffer)
 	// checkSequence - echoes the client's info sub-sequence
-	binary.Write(buf, binary.LittleEndian, checkSeq)
+	_ = binary.Write(buf, binary.LittleEndian, checkSeq)
 	// UIN
-	binary.Write(buf, binary.LittleEndian, user.UIN)
+	_ = binary.Write(buf, binary.LittleEndian, user.UIN)
 	// City (length-prefixed string)
-	WriteLegacyString(buf, user.City)
+	_ = WriteLegacyString(buf, user.City)
 	// Country code (2 bytes)
-	binary.Write(buf, binary.LittleEndian, user.Country)
+	_ = binary.Write(buf, binary.LittleEndian, user.Country)
 	// Country stat (1 byte) - 0 = not specified
 	buf.WriteByte(0)
 	// State (length-prefixed string)
-	WriteLegacyString(buf, user.State)
+	_ = WriteLegacyString(buf, user.State)
 	// Age (2 bytes)
-	binary.Write(buf, binary.LittleEndian, user.Age)
+	_ = binary.Write(buf, binary.LittleEndian, user.Age)
 	// Gender (1 byte)
 	buf.WriteByte(user.Gender)
 	// Phone (length-prefixed string)
-	WriteLegacyString(buf, user.Phone)
+	_ = WriteLegacyString(buf, user.Phone)
 	// Homepage (length-prefixed string)
-	WriteLegacyString(buf, user.Homepage)
+	_ = WriteLegacyString(buf, user.Homepage)
 	// About (length-prefixed string)
-	WriteLegacyString(buf, user.About)
+	_ = WriteLegacyString(buf, user.About)
 
 	return &V2ServerPacket{
 		Version: ICQLegacyVersionV2,

+ 1 - 1
server/kerberos/kerberos.go

@@ -61,7 +61,7 @@ func (s *Server) ListenAndServe() error {
 	ctx, cancel := context.WithCancel(context.Background())
 	defer cancel()
 
-	g, ctx := errgroup.WithContext(ctx)
+	g, _ := errgroup.WithContext(ctx)
 	for _, server := range s.servers {
 		g.Go(func() error {
 			s.logger.Info("starting server", "addr", server.Addr)

+ 2 - 2
server/kerberos/kerberos_test.go

@@ -228,7 +228,7 @@ func TestKerberosLoginHandler(t *testing.T) {
 				for attempt := 0; attempt < maxRetries; attempt++ {
 					conn, err := net.Dial("tcp", "localhost"+tt.listeners[i].KerberosListenAddress)
 					if err == nil {
-						conn.Close()
+						_ = conn.Close()
 						break
 					}
 					if attempt == maxRetries-1 {
@@ -250,7 +250,7 @@ func TestKerberosLoginHandler(t *testing.T) {
 
 				resp, err := http.Post(fmt.Sprintf("http://localhost:%s", listener.KerberosListenAddress[1:]), "application/x-snac", b)
 				assert.NoError(t, err)
-				defer resp.Body.Close()
+				defer func() { _ = resp.Body.Close() }()
 
 				assert.Equal(t, tt.wantStatus, resp.StatusCode, "listener %d at %s", i, listener.KerberosListenAddress)
 

+ 68 - 68
server/oscar/handler.go

@@ -50,7 +50,7 @@ type Handler struct {
 }
 
 func (rt Handler) AdminConfirmRequest(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, _ io.Reader, rw ResponseWriter) error {
-	outSNAC, err := rt.AdminService.ConfirmRequest(ctx, instance, inFrame)
+	outSNAC, err := rt.ConfirmRequest(ctx, instance, inFrame)
 	if err != nil {
 		return err
 	}
@@ -76,7 +76,7 @@ func (rt Handler) AdminInfoChangeRequest(ctx context.Context, instance *state.Se
 	if err := wire.UnmarshalBE(&inBody, r); err != nil {
 		return err
 	}
-	outSNAC, err := rt.AdminService.InfoChangeRequest(ctx, instance, inFrame, inBody)
+	outSNAC, err := rt.InfoChangeRequest(ctx, instance, inFrame, inBody)
 	if err != nil {
 		return err
 	}
@@ -127,7 +127,7 @@ func (rt Handler) BARTDownloadQuery(ctx context.Context, instance *state.Session
 	if err := wire.UnmarshalBE(&inBody, r); err != nil {
 		return err
 	}
-	outSNAC, err := rt.BARTService.RetrieveItem(ctx, inFrame, inBody)
+	outSNAC, err := rt.RetrieveItem(ctx, inFrame, inBody)
 	if err != nil {
 		return err
 	}
@@ -140,7 +140,7 @@ func (rt Handler) BARTDownload2Query(ctx context.Context, instance *state.Sessio
 	if err := wire.UnmarshalBE(&inBody, r); err != nil {
 		return err
 	}
-	outSNACS, err := rt.BARTService.RetrieveItemV2(ctx, inFrame, inBody)
+	outSNACS, err := rt.RetrieveItemV2(ctx, inFrame, inBody)
 	if err != nil {
 		return err
 	}
@@ -168,7 +168,7 @@ func (rt Handler) BuddyAddBuddies(ctx context.Context, instance *state.SessionIn
 	if err := wire.UnmarshalBE(&inSNAC, r); err != nil {
 		return err
 	}
-	rejectSNAC, err := rt.BuddyService.AddBuddies(ctx, instance, inFrame, inSNAC)
+	rejectSNAC, err := rt.AddBuddies(ctx, instance, inFrame, inSNAC)
 	if err != nil {
 		return err
 	}
@@ -189,7 +189,7 @@ func (rt Handler) BuddyDelBuddies(ctx context.Context, instance *state.SessionIn
 		return err
 	}
 	rt.LogRequest(ctx, inFrame, inSNAC)
-	return rt.BuddyService.DelBuddies(ctx, instance, inSNAC)
+	return rt.DelBuddies(ctx, instance, inSNAC)
 }
 
 func (rt Handler) BuddyAddTempBuddies(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, r io.Reader, rw ResponseWriter) error {
@@ -197,7 +197,7 @@ func (rt Handler) BuddyAddTempBuddies(ctx context.Context, instance *state.Sessi
 	if err := wire.UnmarshalBE(&inSNAC, r); err != nil {
 		return err
 	}
-	rejectSNAC, err := rt.BuddyService.AddTempBuddies(ctx, instance, inFrame, inSNAC)
+	rejectSNAC, err := rt.AddTempBuddies(ctx, instance, inFrame, inSNAC)
 	if err != nil {
 		return err
 	}
@@ -218,7 +218,7 @@ func (rt Handler) BuddyDelTempBuddies(ctx context.Context, instance *state.Sessi
 		return err
 	}
 	rt.LogRequest(ctx, inFrame, inSNAC)
-	return rt.BuddyService.DelTempBuddies(ctx, instance, inSNAC)
+	return rt.DelTempBuddies(ctx, instance, inSNAC)
 }
 
 func (rt Handler) ChatChannelMsgToHost(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, r io.Reader, rw ResponseWriter) error {
@@ -239,7 +239,7 @@ func (rt Handler) ChatChannelMsgToHost(ctx context.Context, instance *state.Sess
 }
 
 func (rt Handler) ChatNavRequestChatRights(ctx context.Context, _ *state.SessionInstance, inFrame wire.SNACFrame, _ io.Reader, rw ResponseWriter) error {
-	outSNAC := rt.ChatNavService.RequestChatRights(ctx, inFrame)
+	outSNAC := rt.RequestChatRights(ctx, inFrame)
 	rt.LogRequestAndResponse(ctx, inFrame, nil, outSNAC.Frame, outSNAC.Body)
 	return rw.SendSNAC(outSNAC.Frame, outSNAC.Body)
 }
@@ -249,7 +249,7 @@ func (rt Handler) ChatNavRequestExchangeInfo(ctx context.Context, _ *state.Sessi
 	if err := wire.UnmarshalBE(&inBody, r); err != nil {
 		return err
 	}
-	outSNAC, err := rt.ChatNavService.ExchangeInfo(ctx, inFrame, inBody)
+	outSNAC, err := rt.ExchangeInfo(ctx, inFrame, inBody)
 	if err != nil {
 		return err
 	}
@@ -262,7 +262,7 @@ func (rt Handler) ChatNavRequestRoomInfo(ctx context.Context, _ *state.SessionIn
 	if err := wire.UnmarshalBE(&inBody, r); err != nil {
 		return err
 	}
-	outSNAC, err := rt.ChatNavService.RequestRoomInfo(ctx, inFrame, inBody)
+	outSNAC, err := rt.RequestRoomInfo(ctx, inFrame, inBody)
 	if err != nil {
 		return err
 	}
@@ -275,7 +275,7 @@ func (rt Handler) ChatNavCreateRoom(ctx context.Context, instance *state.Session
 	if err := wire.UnmarshalBE(&inBody, r); err != nil {
 		return err
 	}
-	outSNAC, err := rt.ChatNavService.CreateRoom(ctx, instance, inFrame, inBody)
+	outSNAC, err := rt.CreateRoom(ctx, instance, inFrame, inBody)
 	if err != nil {
 		return err
 	}
@@ -296,7 +296,7 @@ func (rt Handler) FeedbagRightsQuery(ctx context.Context, _ *state.SessionInstan
 }
 
 func (rt Handler) FeedbagQuery(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, _ io.Reader, rw ResponseWriter) error {
-	outSNAC, err := rt.FeedbagService.Query(ctx, instance, inFrame)
+	outSNAC, err := rt.Query(ctx, instance, inFrame)
 	if err != nil {
 		return err
 	}
@@ -309,7 +309,7 @@ func (rt Handler) FeedbagQueryIfModified(ctx context.Context, instance *state.Se
 	if err := wire.UnmarshalBE(&inBody, r); err != nil {
 		return err
 	}
-	outSNAC, err := rt.FeedbagService.QueryIfModified(ctx, instance, inFrame, inBody)
+	outSNAC, err := rt.QueryIfModified(ctx, instance, inFrame, inBody)
 	if err != nil {
 		return err
 	}
@@ -319,7 +319,7 @@ func (rt Handler) FeedbagQueryIfModified(ctx context.Context, instance *state.Se
 
 func (rt Handler) FeedbagUse(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, _ io.Reader, _ ResponseWriter) error {
 	rt.LogRequest(ctx, inFrame, nil)
-	return rt.FeedbagService.Use(ctx, instance)
+	return rt.Use(ctx, instance)
 }
 
 func (rt Handler) FeedbagInsertItem(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, r io.Reader, rw ResponseWriter) error {
@@ -361,7 +361,7 @@ func (rt Handler) FeedbagDeleteItem(ctx context.Context, instance *state.Session
 	if err := wire.UnmarshalBE(&inBody, r); err != nil {
 		return err
 	}
-	outSNAC, err := rt.FeedbagService.DeleteItem(ctx, instance, inFrame, inBody)
+	outSNAC, err := rt.DeleteItem(ctx, instance, inFrame, inBody)
 	if err != nil {
 		return err
 	}
@@ -378,13 +378,13 @@ func (rt Handler) FeedbagStartCluster(ctx context.Context, instance *state.Sessi
 	if err := wire.UnmarshalBE(&inBody, r); err != nil {
 		return err
 	}
-	rt.FeedbagService.StartCluster(ctx, instance, inFrame, inBody)
+	rt.StartCluster(ctx, instance, inFrame, inBody)
 	rt.LogRequest(ctx, inFrame, inBody)
 	return nil
 }
 
 func (rt Handler) FeedbagEndCluster(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, r io.Reader, rw ResponseWriter) error {
-	rt.FeedbagService.EndCluster(ctx, instance, inFrame)
+	rt.EndCluster(ctx, instance, inFrame)
 	rt.LogRequest(ctx, inFrame, nil)
 	return nil
 }
@@ -394,7 +394,7 @@ func (rt Handler) FeedbagPreAuthorizeBuddy(ctx context.Context, instance *state.
 	if err := wire.UnmarshalBE(&inBody, r); err != nil {
 		return err
 	}
-	outSNAC, err := rt.FeedbagService.PreAuthorizeBuddy(ctx, instance, inFrame, inBody)
+	outSNAC, err := rt.PreAuthorizeBuddy(ctx, instance, inFrame, inBody)
 	if err != nil {
 		return err
 	}
@@ -411,7 +411,7 @@ func (rt Handler) FeedbagRequestAuthorizeToHost(ctx context.Context, instance *s
 	if err := wire.UnmarshalBE(&inBody, r); err != nil {
 		return err
 	}
-	if err := rt.FeedbagService.RequestAuthorizeToHost(ctx, instance, inFrame, inBody); err != nil {
+	if err := rt.RequestAuthorizeToHost(ctx, instance, inFrame, inBody); err != nil {
 		return err
 	}
 	rt.LogRequest(ctx, inFrame, inBody)
@@ -423,7 +423,7 @@ func (rt Handler) FeedbagRespondAuthorizeToHost(ctx context.Context, instance *s
 	if err := wire.UnmarshalBE(&inBody, r); err != nil {
 		return err
 	}
-	if err := rt.FeedbagService.RespondAuthorizeToHost(ctx, instance.IdentScreenName(), inFrame, inBody); err != nil {
+	if err := rt.RespondAuthorizeToHost(ctx, instance.IdentScreenName(), inFrame, inBody); err != nil {
 		return err
 	}
 	rt.LogRequest(ctx, inFrame, inBody)
@@ -437,7 +437,7 @@ func (rt Handler) ICBMAddParameters(ctx context.Context, _ *state.SessionInstanc
 }
 
 func (rt Handler) ICBMParameterQuery(ctx context.Context, _ *state.SessionInstance, inFrame wire.SNACFrame, _ io.Reader, rw ResponseWriter) error {
-	outSNAC := rt.ICBMService.ParameterQuery(ctx, inFrame)
+	outSNAC := rt.ParameterQuery(ctx, inFrame)
 	rt.LogRequestAndResponse(ctx, inFrame, outSNAC, outSNAC.Frame, outSNAC.Body)
 	return rw.SendSNAC(outSNAC.Frame, outSNAC.Body)
 }
@@ -464,7 +464,7 @@ func (rt Handler) ICBMEvilRequest(ctx context.Context, instance *state.SessionIn
 	if err := wire.UnmarshalBE(&inBody, r); err != nil {
 		return err
 	}
-	outSNAC, err := rt.ICBMService.EvilRequest(ctx, instance, inFrame, inBody)
+	outSNAC, err := rt.EvilRequest(ctx, instance, inFrame, inBody)
 	if err != nil {
 		return err
 	}
@@ -479,7 +479,7 @@ func (rt Handler) ICBMClientErr(ctx context.Context, instance *state.SessionInst
 	if err != nil {
 		return err
 	}
-	return rt.ICBMService.ClientErr(ctx, instance, inFrame, inBody)
+	return rt.ClientErr(ctx, instance, inFrame, inBody)
 }
 
 func (rt Handler) ICBMClientEvent(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, r io.Reader, _ ResponseWriter) error {
@@ -488,11 +488,11 @@ func (rt Handler) ICBMClientEvent(ctx context.Context, instance *state.SessionIn
 		return err
 	}
 	rt.LogRequest(ctx, inFrame, inBody)
-	return rt.ICBMService.ClientEvent(ctx, instance, inFrame, inBody)
+	return rt.ClientEvent(ctx, instance, inFrame, inBody)
 }
 
 func (rt Handler) ICBMOfflineRetrieve(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, rw ResponseWriter) error {
-	outSNAC, err := rt.ICBMService.OfflineRetrieve(ctx, instance, inFrame)
+	outSNAC, err := rt.OfflineRetrieve(ctx, instance, inFrame)
 	if err != nil {
 		return err
 	}
@@ -523,9 +523,9 @@ func (rt Handler) ICQDBQuery(ctx context.Context, instance *state.SessionInstanc
 
 	switch icqMD.ReqType {
 	case wire.ICQDBQueryOfflineMsgReq:
-		return rt.ICQService.OfflineMsgReq(ctx, instance, icqMD.Seq)
+		return rt.OfflineMsgReq(ctx, instance, icqMD.Seq)
 	case wire.ICQDBQueryDeleteMsgReq:
-		return rt.ICQService.DeleteMsgReq(ctx, instance, icqMD.Seq)
+		return rt.DeleteMsgReq(ctx, instance, icqMD.Seq)
 	case wire.ICQDBQueryMetaReq:
 		if icqMD.Optional == nil {
 			return errors.New("got req without subtype")
@@ -541,19 +541,19 @@ func (rt Handler) ICQDBQuery(ctx context.Context, instance *state.SessionInstanc
 			if err := binary.Read(buf, binary.LittleEndian, &userInfo); err != nil {
 				return nil
 			}
-			return rt.ICQService.ShortUserInfo(ctx, instance, userInfo, icqMD.Seq)
+			return rt.ShortUserInfo(ctx, instance, userInfo, icqMD.Seq)
 		case wire.ICQDBQueryMetaReqFullInfo, wire.ICQDBQueryMetaReqFullInfo2:
 			userInfo := wire.ICQ_0x07D0_0x051F_DBQueryMetaReqSearchByUIN{}
 			if err := binary.Read(buf, binary.LittleEndian, &userInfo); err != nil {
 				return nil
 			}
-			return rt.ICQService.FullUserInfo(ctx, instance, userInfo, icqMD.Seq)
+			return rt.FullUserInfo(ctx, instance, userInfo, icqMD.Seq)
 		case wire.ICQDBQueryMetaReqXMLReq:
 			req := wire.ICQ_0x07D0_0x0898_DBQueryMetaReqXMLReq{}
 			if err := wire.UnmarshalLE(&req, buf); err != nil {
 				return err
 			}
-			if err := rt.ICQService.XMLReqData(ctx, instance, req, icqMD.Seq); err != nil {
+			if err := rt.XMLReqData(ctx, instance, req, icqMD.Seq); err != nil {
 				return err
 			}
 		case wire.ICQDBQueryMetaReqSetPermissions:
@@ -561,7 +561,7 @@ func (rt Handler) ICQDBQuery(ctx context.Context, instance *state.SessionInstanc
 			if err := wire.UnmarshalLE(&req, buf); err != nil {
 				return err
 			}
-			if err := rt.ICQService.SetPermissions(ctx, instance, req, icqMD.Seq); err != nil {
+			if err := rt.SetPermissions(ctx, instance, req, icqMD.Seq); err != nil {
 				return err
 			}
 		case wire.ICQDBQueryMetaReqSetICQPhone:
@@ -569,7 +569,7 @@ func (rt Handler) ICQDBQuery(ctx context.Context, instance *state.SessionInstanc
 			if err := wire.UnmarshalLE(&req, buf); err != nil {
 				return err
 			}
-			if err := rt.ICQService.SetICQPhone(ctx, instance, req, icqMD.Seq); err != nil {
+			if err := rt.SetICQPhone(ctx, instance, req, icqMD.Seq); err != nil {
 				return err
 			}
 		case wire.ICQDBQueryMetaReqSearchByUIN:
@@ -577,7 +577,7 @@ func (rt Handler) ICQDBQuery(ctx context.Context, instance *state.SessionInstanc
 			if err := wire.UnmarshalLE(&req, buf); err != nil {
 				return err
 			}
-			if err := rt.ICQService.FindByUIN(ctx, instance, req, icqMD.Seq); err != nil {
+			if err := rt.FindByUIN(ctx, instance, req, icqMD.Seq); err != nil {
 				return err
 			}
 		case wire.ICQDBQueryMetaReqSearchByUIN2:
@@ -585,7 +585,7 @@ func (rt Handler) ICQDBQuery(ctx context.Context, instance *state.SessionInstanc
 			if err := wire.UnmarshalLE(&req, buf); err != nil {
 				return err
 			}
-			if err := rt.ICQService.FindByUIN2(ctx, instance, req, icqMD.Seq); err != nil {
+			if err := rt.FindByUIN2(ctx, instance, req, icqMD.Seq); err != nil {
 				return err
 			}
 		case wire.ICQDBQueryMetaReqSearchByEmail:
@@ -593,7 +593,7 @@ func (rt Handler) ICQDBQuery(ctx context.Context, instance *state.SessionInstanc
 			if err := wire.UnmarshalLE(&req, buf); err != nil {
 				return err
 			}
-			if err := rt.ICQService.FindByICQEmail(ctx, instance, req, icqMD.Seq); err != nil {
+			if err := rt.FindByICQEmail(ctx, instance, req, icqMD.Seq); err != nil {
 				return err
 			}
 		case wire.ICQDBQueryMetaReqSearchByEmail3:
@@ -601,7 +601,7 @@ func (rt Handler) ICQDBQuery(ctx context.Context, instance *state.SessionInstanc
 			if err := wire.UnmarshalLE(&req, buf); err != nil {
 				return err
 			}
-			if err := rt.ICQService.FindByEmail3(ctx, instance, req, icqMD.Seq); err != nil {
+			if err := rt.FindByEmail3(ctx, instance, req, icqMD.Seq); err != nil {
 				return err
 			}
 		case wire.ICQDBQueryMetaReqSearchByDetails:
@@ -609,7 +609,7 @@ func (rt Handler) ICQDBQuery(ctx context.Context, instance *state.SessionInstanc
 			if err := wire.UnmarshalLE(&req, buf); err != nil {
 				return err
 			}
-			if err := rt.ICQService.FindByICQName(ctx, instance, req, icqMD.Seq); err != nil {
+			if err := rt.FindByICQName(ctx, instance, req, icqMD.Seq); err != nil {
 				return err
 			}
 		case wire.ICQDBQueryMetaReqSearchWhitePages:
@@ -617,7 +617,7 @@ func (rt Handler) ICQDBQuery(ctx context.Context, instance *state.SessionInstanc
 			if err := wire.UnmarshalLE(&req, buf); err != nil {
 				return err
 			}
-			if err := rt.ICQService.FindByICQInterests(ctx, instance, req, icqMD.Seq); err != nil {
+			if err := rt.FindByICQInterests(ctx, instance, req, icqMD.Seq); err != nil {
 				return err
 			}
 		case wire.ICQDBQueryMetaReqSearchWhitePages2:
@@ -625,7 +625,7 @@ func (rt Handler) ICQDBQuery(ctx context.Context, instance *state.SessionInstanc
 			if err := wire.UnmarshalLE(&req, buf); err != nil {
 				return err
 			}
-			if err := rt.ICQService.FindByWhitePages2(ctx, instance, req, icqMD.Seq); err != nil {
+			if err := rt.FindByWhitePages2(ctx, instance, req, icqMD.Seq); err != nil {
 				return err
 			}
 		case wire.ICQDBQueryMetaReqSetBasicInfo:
@@ -633,7 +633,7 @@ func (rt Handler) ICQDBQuery(ctx context.Context, instance *state.SessionInstanc
 			if err := wire.UnmarshalLE(&req, buf); err != nil {
 				return err
 			}
-			if err := rt.ICQService.SetBasicInfo(ctx, instance, req, icqMD.Seq); err != nil {
+			if err := rt.SetBasicInfo(ctx, instance, req, icqMD.Seq); err != nil {
 				return err
 			}
 		case wire.ICQDBQueryMetaReqSetWorkInfo:
@@ -641,7 +641,7 @@ func (rt Handler) ICQDBQuery(ctx context.Context, instance *state.SessionInstanc
 			if err := wire.UnmarshalLE(&req, buf); err != nil {
 				return err
 			}
-			if err := rt.ICQService.SetWorkInfo(ctx, instance, req, icqMD.Seq); err != nil {
+			if err := rt.SetWorkInfo(ctx, instance, req, icqMD.Seq); err != nil {
 				return err
 			}
 		case wire.ICQDBQueryMetaReqSetMoreInfo:
@@ -649,7 +649,7 @@ func (rt Handler) ICQDBQuery(ctx context.Context, instance *state.SessionInstanc
 			if err := wire.UnmarshalLE(&req, buf); err != nil {
 				return err
 			}
-			if err := rt.ICQService.SetMoreInfo(ctx, instance, req, icqMD.Seq); err != nil {
+			if err := rt.SetMoreInfo(ctx, instance, req, icqMD.Seq); err != nil {
 				return err
 			}
 		case wire.ICQDBQueryMetaReqSetNotes:
@@ -657,7 +657,7 @@ func (rt Handler) ICQDBQuery(ctx context.Context, instance *state.SessionInstanc
 			if err := wire.UnmarshalLE(&req, buf); err != nil {
 				return err
 			}
-			if err := rt.ICQService.SetUserNotes(ctx, instance, req, icqMD.Seq); err != nil {
+			if err := rt.SetUserNotes(ctx, instance, req, icqMD.Seq); err != nil {
 				return err
 			}
 		case wire.ICQDBQueryMetaReqSetEmails:
@@ -665,7 +665,7 @@ func (rt Handler) ICQDBQuery(ctx context.Context, instance *state.SessionInstanc
 			if err := wire.UnmarshalLE(&req, buf); err != nil {
 				return err
 			}
-			if err := rt.ICQService.SetEmails(ctx, instance, req, icqMD.Seq); err != nil {
+			if err := rt.SetEmails(ctx, instance, req, icqMD.Seq); err != nil {
 				return err
 			}
 		case wire.ICQDBQueryMetaReqSetInterests:
@@ -673,7 +673,7 @@ func (rt Handler) ICQDBQuery(ctx context.Context, instance *state.SessionInstanc
 			if err := wire.UnmarshalLE(&req, buf); err != nil {
 				return err
 			}
-			if err := rt.ICQService.SetInterests(ctx, instance, req, icqMD.Seq); err != nil {
+			if err := rt.SetInterests(ctx, instance, req, icqMD.Seq); err != nil {
 				return err
 			}
 		case wire.ICQDBQueryMetaReqSetAffiliations:
@@ -681,7 +681,7 @@ func (rt Handler) ICQDBQuery(ctx context.Context, instance *state.SessionInstanc
 			if err := wire.UnmarshalLE(&req, buf); err != nil {
 				return err
 			}
-			if err := rt.ICQService.SetAffiliations(ctx, instance, req, icqMD.Seq); err != nil {
+			if err := rt.SetAffiliations(ctx, instance, req, icqMD.Seq); err != nil {
 				return err
 			}
 		case wire.ICQDBQueryMetaReqSetFullInfo:
@@ -689,7 +689,7 @@ func (rt Handler) ICQDBQuery(ctx context.Context, instance *state.SessionInstanc
 			if err := wire.UnmarshalLE(&req, buf); err != nil {
 				return err
 			}
-			if err := rt.ICQService.SetICQInfo(ctx, instance, req, icqMD.Seq); err != nil {
+			if err := rt.SetICQInfo(ctx, instance, req, icqMD.Seq); err != nil {
 				return err
 			}
 		case wire.ICQDBQueryMetaReqStat0a8c,
@@ -728,7 +728,7 @@ func (rt Handler) LocateSetInfo(ctx context.Context, instance *state.SessionInst
 		return err
 	}
 	rt.LogRequest(ctx, inFrame, inBody)
-	return rt.LocateService.SetInfo(ctx, instance, inBody)
+	return rt.SetInfo(ctx, instance, inBody)
 }
 
 func (rt Handler) LocateSetDirInfo(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, r io.Reader, rw ResponseWriter) error {
@@ -736,7 +736,7 @@ func (rt Handler) LocateSetDirInfo(ctx context.Context, instance *state.SessionI
 	if err := wire.UnmarshalBE(&inBody, r); err != nil {
 		return err
 	}
-	outSNAC, err := rt.LocateService.SetDirInfo(ctx, instance, inFrame, inBody)
+	outSNAC, err := rt.SetDirInfo(ctx, instance, inFrame, inBody)
 	if err != nil {
 		return err
 	}
@@ -749,7 +749,7 @@ func (rt Handler) LocateGetDirInfo(ctx context.Context, _ *state.SessionInstance
 	if err := wire.UnmarshalBE(&inBody, r); err != nil {
 		return err
 	}
-	outSNAC, err := rt.LocateService.DirInfo(ctx, inFrame, inBody)
+	outSNAC, err := rt.DirInfo(ctx, inFrame, inBody)
 	if err != nil {
 		return err
 	}
@@ -762,7 +762,7 @@ func (rt Handler) LocateSetKeywordInfo(ctx context.Context, instance *state.Sess
 	if err := wire.UnmarshalBE(&inBody, r); err != nil {
 		return err
 	}
-	outSNAC, err := rt.LocateService.SetKeywordInfo(ctx, instance, inFrame, inBody)
+	outSNAC, err := rt.SetKeywordInfo(ctx, instance, inFrame, inBody)
 	if err != nil {
 		return err
 	}
@@ -816,7 +816,7 @@ func (rt Handler) ODirInfoQuery(ctx context.Context, instance *state.SessionInst
 }
 
 func (rt Handler) ODirKeywordListQuery(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, r io.Reader, rw ResponseWriter) error {
-	outSNAC, err := rt.ODirService.KeywordListQuery(ctx, inFrame)
+	outSNAC, err := rt.KeywordListQuery(ctx, inFrame)
 	if err != nil {
 		return err
 	}
@@ -825,7 +825,7 @@ func (rt Handler) ODirKeywordListQuery(ctx context.Context, instance *state.Sess
 }
 
 func (rt Handler) OServiceRateParamsQuery(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, _ io.Reader, rw ResponseWriter) error {
-	outSNAC := rt.OServiceService.RateParamsQuery(ctx, instance, inFrame)
+	outSNAC := rt.RateParamsQuery(ctx, instance, inFrame)
 	rt.LogRequestAndResponse(ctx, inFrame, nil, outSNAC.Frame, outSNAC.Body)
 	return rw.SendSNAC(outSNAC.Frame, outSNAC.Body)
 }
@@ -835,7 +835,7 @@ func (rt Handler) OServiceRateParamsSubAdd(ctx context.Context, instance *state.
 	if err := wire.UnmarshalBE(&inBody, r); err != nil {
 		return err
 	}
-	rt.OServiceService.RateParamsSubAdd(ctx, instance, inBody)
+	rt.RateParamsSubAdd(ctx, instance, inBody)
 	rt.LogRequest(ctx, inFrame, inBody)
 	return nil
 }
@@ -847,7 +847,7 @@ func (rt Handler) OServiceUserInfoQuery(ctx context.Context, instance *state.Ses
 }
 
 func (rt Handler) OServiceProbeReq(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, _ io.Reader, rw ResponseWriter) error {
-	outSNAC := rt.OServiceService.ProbeReq(ctx, inFrame)
+	outSNAC := rt.ProbeReq(ctx, inFrame)
 	rt.LogRequestAndResponse(ctx, inFrame, nil, outSNAC.Frame, outSNAC.Body)
 	return rw.SendSNAC(outSNAC.Frame, outSNAC.Body)
 }
@@ -858,7 +858,7 @@ func (rt Handler) OServiceIdleNotification(ctx context.Context, instance *state.
 		return err
 	}
 	rt.LogRequest(ctx, inFrame, inBody)
-	return rt.OServiceService.IdleNotification(ctx, instance, inBody)
+	return rt.IdleNotification(ctx, instance, inBody)
 }
 
 func (rt Handler) OServiceClientVersions(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, r io.Reader, rw ResponseWriter) error {
@@ -866,7 +866,7 @@ func (rt Handler) OServiceClientVersions(ctx context.Context, instance *state.Se
 	if err := wire.UnmarshalBE(&inBody, r); err != nil {
 		return err
 	}
-	outSNACs := rt.OServiceService.ClientVersions(ctx, instance, inFrame, inBody)
+	outSNACs := rt.ClientVersions(ctx, instance, inFrame, inBody)
 	for _, snac := range outSNACs {
 		rt.LogRequestAndResponse(ctx, inFrame, inBody, snac.Frame, snac.Body)
 		if err := rw.SendSNAC(snac.Frame, snac.Body); err != nil {
@@ -881,7 +881,7 @@ func (rt Handler) OServiceSetUserInfoFields(ctx context.Context, instance *state
 	if err := wire.UnmarshalBE(&inBody, r); err != nil {
 		return err
 	}
-	outSNAC, err := rt.OServiceService.SetUserInfoFields(ctx, instance, inFrame, inBody)
+	outSNAC, err := rt.SetUserInfoFields(ctx, instance, inFrame, inBody)
 	if err != nil {
 		return err
 	}
@@ -900,7 +900,7 @@ func (rt Handler) OServiceSetPrivacyFlags(ctx context.Context, instance *state.S
 	if err := wire.UnmarshalBE(&inBody, r); err != nil {
 		return err
 	}
-	rt.OServiceService.SetPrivacyFlags(ctx, inBody)
+	rt.SetPrivacyFlags(ctx, inBody)
 	rt.LogRequest(ctx, inFrame, inBody)
 	return nil
 }
@@ -910,7 +910,7 @@ func (rt Handler) OServiceServiceRequest(ctx context.Context, service uint16, in
 	if err := wire.UnmarshalBE(&inBody, r); err != nil {
 		return err
 	}
-	outSNAC, err := rt.OServiceService.ServiceRequest(ctx, service, instance, inFrame, inBody, listener)
+	outSNAC, err := rt.ServiceRequest(ctx, service, instance, inFrame, inBody, listener)
 	if err != nil {
 		return err
 	}
@@ -925,7 +925,7 @@ func (rt Handler) OServiceClientOnline(ctx context.Context, service uint16, inst
 	}
 	rt.Logger.InfoContext(ctx, "user signed on")
 	rt.LogRequest(ctx, inFrame, inBody)
-	return rt.OServiceService.ClientOnline(ctx, service, inBody, instance)
+	return rt.ClientOnline(ctx, service, inBody, instance)
 }
 
 func (rt Handler) PermitDenyRightsQuery(ctx context.Context, _ *state.SessionInstance, inFrame wire.SNACFrame, _ io.Reader, rw ResponseWriter) error {
@@ -940,7 +940,7 @@ func (rt Handler) PermitDenyAddDenyListEntries(ctx context.Context, instance *st
 		return err
 	}
 	rt.LogRequest(ctx, inFrame, inBody)
-	return rt.PermitDenyService.AddDenyListEntries(ctx, instance, inBody)
+	return rt.AddDenyListEntries(ctx, instance, inBody)
 }
 
 func (rt Handler) PermitDenyDelDenyListEntries(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, r io.Reader, rw ResponseWriter) error {
@@ -949,7 +949,7 @@ func (rt Handler) PermitDenyDelDenyListEntries(ctx context.Context, instance *st
 		return err
 	}
 	rt.LogRequest(ctx, inFrame, inBody)
-	return rt.PermitDenyService.DelDenyListEntries(ctx, instance, inBody)
+	return rt.DelDenyListEntries(ctx, instance, inBody)
 }
 
 func (rt Handler) PermitDenyAddPermListEntries(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, r io.Reader, rw ResponseWriter) error {
@@ -958,7 +958,7 @@ func (rt Handler) PermitDenyAddPermListEntries(ctx context.Context, instance *st
 		return err
 	}
 	rt.LogRequest(ctx, inFrame, inBody)
-	return rt.PermitDenyService.AddPermListEntries(ctx, instance, inBody)
+	return rt.AddPermListEntries(ctx, instance, inBody)
 }
 
 func (rt Handler) PermitDenyDelPermListEntries(ctx context.Context, instance *state.SessionInstance, inFrame wire.SNACFrame, r io.Reader, rw ResponseWriter) error {
@@ -967,7 +967,7 @@ func (rt Handler) PermitDenyDelPermListEntries(ctx context.Context, instance *st
 		return err
 	}
 	rt.LogRequest(ctx, inFrame, inBody)
-	return rt.PermitDenyService.DelPermListEntries(ctx, instance, inBody)
+	return rt.DelPermListEntries(ctx, instance, inBody)
 }
 
 // PermitDenySetGroupPermitMask sets the classes of users I can interact with. We don't
@@ -1036,7 +1036,7 @@ func (rt Handler) StatsReportEvents(ctx context.Context, _ *state.SessionInstanc
 		return err
 	}
 
-	outSNAC := rt.StatsService.ReportEvents(ctx, inFrame, inBody)
+	outSNAC := rt.ReportEvents(ctx, inFrame, inBody)
 	rt.LogRequestAndResponse(ctx, inFrame, inBody, outSNAC.Frame, outSNAC.Body)
 
 	return rw.SendSNAC(outSNAC.Frame, outSNAC.Body)
@@ -1047,7 +1047,7 @@ func (rt Handler) UserLookupFindByEmail(ctx context.Context, _ *state.SessionIns
 	if err := wire.UnmarshalBE(&inBody, r); err != nil {
 		return err
 	}
-	outSNAC, err := rt.UserLookupService.FindByEmail(ctx, inFrame, inBody)
+	outSNAC, err := rt.FindByEmail(ctx, inFrame, inBody)
 	if err != nil {
 		return err
 	}

+ 1 - 4
server/oscar/handler_test.go

@@ -2822,11 +2822,8 @@ func TestHandler_ICQDBQuery(t *testing.T) {
 		MetaRequest any
 	}
 	type reqParams struct {
-		ctx      context.Context
 		instance *state.SessionInstance
-		inFrame  wire.SNACFrame
 		inBody   wire.SNAC_0x15_0x02_BQuery
-		rw       ResponseWriter
 		seq      uint16
 		wantErr  error
 	}
@@ -3919,7 +3916,7 @@ func TestHandler_ICQDBQuery_QIP2005UINSearchBug(t *testing.T) {
 	b := buf.Bytes()
 	b[18] = 6 // incorrectly set TLV length to 6 (should be 4)
 
-	err := h.ICQDBQuery(nil, instance, wire.SNACFrame{}, buf, nil)
+	err := h.ICQDBQuery(context.TODO(), instance, wire.SNACFrame{}, buf, nil)
 	assert.NoError(t, err)
 }
 

+ 22 - 2
server/oscar/middleware/logger.go

@@ -15,6 +15,26 @@ const (
 	LevelTrace = slog.Level(-8)
 )
 
+type contextKey string
+
+const (
+	ipContextKey         contextKey = "ip"
+	screenNameContextKey contextKey = "screenName"
+)
+
+func WithIP(ctx context.Context, ip string) context.Context {
+	return context.WithValue(ctx, ipContextKey, ip)
+}
+
+func IPFromContext(ctx context.Context) (string, bool) {
+	ip, ok := ctx.Value(ipContextKey).(string)
+	return ip, ok
+}
+
+func WithScreenName(ctx context.Context, screenName fmt.Stringer) context.Context {
+	return context.WithValue(ctx, screenNameContextKey, screenName)
+}
+
 var levelNames = map[slog.Leveler]string{
 	LevelTrace: "TRACE",
 }
@@ -59,10 +79,10 @@ type handler struct {
 }
 
 func (h handler) Handle(ctx context.Context, r slog.Record) error {
-	if sn := ctx.Value("screenName"); sn != nil {
+	if sn := ctx.Value(screenNameContextKey); sn != nil {
 		r.AddAttrs(slog.Attr{Key: "screenName", Value: slog.StringValue(sn.(fmt.Stringer).String())})
 	}
-	if ip := ctx.Value("ip"); ip != nil {
+	if ip := ctx.Value(ipContextKey); ip != nil {
 		r.AddAttrs(slog.Attr{Key: "ip", Value: slog.StringValue(ip.(string))})
 	}
 	return h.Handler.Handle(ctx, r)

+ 70 - 70
server/oscar/server.go

@@ -38,16 +38,16 @@ func NewServer(
 	lowerWarnLevel func(ctx context.Context, instance *state.SessionInstance),
 ) *Server {
 	oscarSvc := oscarServer{
-		AuthService:        authService,
-		BuddyListRegistry:  buddyListRegistry,
-		ChatSessionManager: chatSessionManager,
-		DepartureNotifier:  departureNotifier,
-		Logger:             logger,
-		OnlineNotifier:     onlineNotifier,
-		SNACHandler:        SNACHandler,
-		RateLimitUpdater:   rateLimitUpdater,
-		SNACRateLimits:     limits,
-		IPRateLimiter:      limiter,
+		authService:        authService,
+		buddyListRegistry:  buddyListRegistry,
+		chatSessionManager: chatSessionManager,
+		departureNotifier:  departureNotifier,
+		logger:             logger,
+		onlineNotifier:     onlineNotifier,
+		snacHandler:        SNACHandler,
+		rateLimitUpdater:   rateLimitUpdater,
+		rateLimits:         limits,
+		ipRateLimiter:      limiter,
 		recalcWarning:      recalcWarning,
 		lowerWarnLevel:     lowerWarnLevel,
 	}
@@ -169,7 +169,7 @@ func (s *Server) handleConnection(ctx context.Context, conn net.Conn, listener c
 		_ = conn.Close()
 		s.connWg.Done()
 	}()
-	ctx = context.WithValue(ctx, "ip", conn.RemoteAddr().String())
+	ctx = middleware.WithIP(ctx, conn.RemoteAddr().String())
 	if err := s.handler(ctx, conn, listener); err != nil {
 		s.logger.InfoContext(ctx, "user session failed", "err", err.Error())
 	}
@@ -183,24 +183,24 @@ func (s *Server) cleanupListeners() {
 }
 
 type oscarServer struct {
-	AuthService
-	BuddyListRegistry
-	ChatSessionManager
-	DepartureNotifier
-	Logger *slog.Logger
-	OnlineNotifier
-	SNACHandler func(ctx context.Context, serverType uint16, instance *state.SessionInstance, inFrame wire.SNACFrame, r io.Reader, rw ResponseWriter, listener config.Listener) error
-	RateLimitUpdater
-	wire.SNACRateLimits
-	*IPRateLimiter
-	recalcWarning  func(ctx context.Context, instance *state.SessionInstance) error
-	lowerWarnLevel func(ctx context.Context, instance *state.SessionInstance)
+	authService        AuthService
+	buddyListRegistry  BuddyListRegistry
+	chatSessionManager ChatSessionManager
+	departureNotifier  DepartureNotifier
+	logger             *slog.Logger
+	onlineNotifier     OnlineNotifier
+	snacHandler        func(ctx context.Context, serverType uint16, instance *state.SessionInstance, inFrame wire.SNACFrame, r io.Reader, rw ResponseWriter, listener config.Listener) error
+	rateLimitUpdater   RateLimitUpdater
+	rateLimits         wire.SNACRateLimits
+	ipRateLimiter      *IPRateLimiter
+	recalcWarning      func(ctx context.Context, instance *state.SessionInstance) error
+	lowerWarnLevel     func(ctx context.Context, instance *state.SessionInstance)
 }
 
 func (s oscarServer) routeConnection(ctx context.Context, conn net.Conn, listener config.Listener) error {
 	ip, _, err := net.SplitHostPort(conn.RemoteAddr().String())
 	if err != nil {
-		s.Logger.Error("failed to parse remote address", "err", err.Error())
+		s.logger.Error("failed to parse remote address", "err", err.Error())
 		return err
 	}
 
@@ -239,12 +239,12 @@ func (s oscarServer) connectToOSCARService(
 		return errors.New("unable to get session id from payload")
 	}
 
-	cookie, err := s.CrackCookie(authCookie)
+	cookie, err := s.authService.CrackCookie(authCookie)
 	if err != nil {
 		return err
 	}
 
-	s.Logger.Debug("connecting to service", "service", wire.FoodGroupName(cookie.Service))
+	s.logger.Debug("connecting to service", "service", wire.FoodGroupName(cookie.Service))
 
 	var instance *state.SessionInstance
 	switch cookie.Service {
@@ -253,8 +253,8 @@ func (s oscarServer) connectToOSCARService(
 		sessCfg := func(sess *state.Session) {
 			sess.OnSessionClose(func() {
 				if !shuttingDown(ctx) {
-					if err := s.DepartureNotifier.BroadcastBuddyDeparted(ctx, sess.IdentScreenName()); err != nil {
-						s.Logger.ErrorContext(ctx, "error sending buddy departure notifications", "err", err.Error())
+					if err := s.departureNotifier.BroadcastBuddyDeparted(ctx, sess.IdentScreenName()); err != nil {
+						s.logger.ErrorContext(ctx, "error sending buddy departure notifications", "err", err.Error())
 					}
 				}
 
@@ -264,18 +264,18 @@ func (s oscarServer) connectToOSCARService(
 				// buddy list must be cleared before session is closed, otherwise
 				// there will be a race condition that could cause the buddy list
 				// be prematurely deleted.
-				if err := s.BuddyListRegistry.UnregisterBuddyList(ctx, instance.IdentScreenName()); err != nil {
-					s.Logger.ErrorContext(ctx, "error removing buddy list entry", "err", err.Error())
+				if err := s.buddyListRegistry.UnregisterBuddyList(ctx, instance.IdentScreenName()); err != nil {
+					s.logger.ErrorContext(ctx, "error removing buddy list entry", "err", err.Error())
 				}
-				s.ChatSessionManager.RemoveUserFromAllChats(instance.IdentScreenName())
-				s.AuthService.Signout(ctx, sess)
+				s.chatSessionManager.RemoveUserFromAllChats(instance.IdentScreenName())
+				s.authService.Signout(ctx, sess)
 			})
 		}
 
-		instance, err = s.AuthService.RegisterBOSSession(ctx, cookie, sessCfg)
+		instance, err = s.authService.RegisterBOSSession(ctx, cookie, sessCfg)
 		if err != nil {
 			if errors.Is(err, state.ErrMaxConcurrentSessionsReached) {
-				s.Logger.Debug("session registration failed", "err", err.Error())
+				s.logger.Debug("session registration failed", "err", err.Error())
 				block := wire.TLVRestBlock{}
 				// error code indicating the signon is blocked. i can't find a
 				// more appropriate error code to indicate the maximum session limit is reached
@@ -296,7 +296,7 @@ func (s oscarServer) connectToOSCARService(
 
 		if err = instance.Session().RunOnce(func() error {
 			// make buddy list visible to other users
-			if err := s.BuddyListRegistry.RegisterBuddyList(ctx, instance.IdentScreenName()); err != nil {
+			if err := s.buddyListRegistry.RegisterBuddyList(ctx, instance.IdentScreenName()); err != nil {
 				return fmt.Errorf("unable to init buddy list: %w", err)
 			}
 			// restore warning level from last session
@@ -318,17 +318,17 @@ func (s oscarServer) connectToOSCARService(
 				return
 			}
 			if instance.Session().Invisible() {
-				if err := s.DepartureNotifier.BroadcastBuddyDeparted(ctx, instance.IdentScreenName()); err != nil {
-					s.Logger.ErrorContext(ctx, "error sending buddy departure notifications", "err", err.Error())
+				if err := s.departureNotifier.BroadcastBuddyDeparted(ctx, instance.IdentScreenName()); err != nil {
+					s.logger.ErrorContext(ctx, "error sending buddy departure notifications", "err", err.Error())
 				}
 			} else {
-				if err := s.DepartureNotifier.BroadcastBuddyArrived(ctx, instance.IdentScreenName(), instance.Session().TLVUserInfo()); err != nil {
-					s.Logger.ErrorContext(ctx, "error sending buddy arrival notifications", "err", err.Error())
+				if err := s.departureNotifier.BroadcastBuddyArrived(ctx, instance.IdentScreenName(), instance.Session().TLVUserInfo()); err != nil {
+					s.logger.ErrorContext(ctx, "error sending buddy arrival notifications", "err", err.Error())
 				}
 			}
 		})
 
-		if remoteAddr, ok := ctx.Value("ip").(string); ok {
+		if remoteAddr, ok := middleware.IPFromContext(ctx); ok {
 			ip, err := netip.ParseAddrPort(remoteAddr)
 			if err != nil {
 				return errors.New("unable to parse ip addr")
@@ -342,10 +342,10 @@ func (s oscarServer) connectToOSCARService(
 			sess.OnSessionClose(func() {
 				ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
 				defer cancel()
-				s.SignoutChat(ctx, sess)
+				s.authService.SignoutChat(ctx, sess)
 			})
 		}
-		instance, err = s.AuthService.RegisterChatSession(ctx, cookie, sessCfg)
+		instance, err = s.authService.RegisterChatSession(ctx, cookie, sessCfg)
 		if err != nil {
 			return err
 		}
@@ -358,7 +358,7 @@ func (s oscarServer) connectToOSCARService(
 
 		go s.receiveSessMessages(ctx, instance, flapc)
 	default:
-		instance, err = s.AuthService.RetrieveBOSSession(ctx, cookie)
+		instance, err = s.authService.RetrieveBOSSession(ctx, cookie)
 		if err != nil {
 			return err
 		}
@@ -367,9 +367,9 @@ func (s oscarServer) connectToOSCARService(
 		}
 	}
 
-	ctx = context.WithValue(ctx, "screenName", instance.IdentScreenName())
+	ctx = middleware.WithScreenName(ctx, instance.IdentScreenName())
 
-	msg := s.OnlineNotifier.HostOnline(cookie.Service)
+	msg := s.onlineNotifier.HostOnline(cookie.Service)
 	if err := flapc.SendSNAC(msg.Frame, msg.Body); err != nil {
 		return err
 	}
@@ -397,9 +397,9 @@ func (s oscarServer) receiveSessMessages(ctx context.Context, instance *state.Se
 		case m := <-instance.ReceiveMessage():
 			// forward a notification sent from another client to this client
 			if err := flapc.SendSNAC(m.Frame, m.Body); err != nil {
-				middleware.LogRequestError(ctx, s.Logger, m.Frame, err)
+				middleware.LogRequestError(ctx, s.logger, m.Frame, err)
 			} else {
-				middleware.LogRequest(ctx, s.Logger, m.Frame, m.Body)
+				middleware.LogRequest(ctx, s.logger, m.Frame, m.Body)
 			}
 		}
 	}
@@ -413,8 +413,8 @@ func (s oscarServer) authenticate(
 	flapc *wire.FlapClient,
 	advertisedHost string,
 ) error {
-	if ok, isBUCP := s.Allow(ip); !ok {
-		s.Logger.InfoContext(ctx, "user rate limited at login, dropping connection")
+	if ok, isBUCP := s.ipRateLimiter.Allow(ip); !ok {
+		s.logger.InfoContext(ctx, "user rate limited at login, dropping connection")
 		tlv := wire.TLVRestBlock{
 			TLVList: []wire.TLV{
 				wire.NewTLVBE(wire.LoginTLVTagsErrorSubcode, wire.LoginErrRateLimitExceeded),
@@ -450,7 +450,7 @@ func (s oscarServer) authenticate(
 		return s.processFLAPAuth(ctx, flap, flapc, advertisedHost)
 	}
 
-	s.SetBUCP(ip)
+	s.ipRateLimiter.SetBUCP(ip)
 
 	return s.processBUCPAuth(ctx, flapc, advertisedHost)
 }
@@ -461,7 +461,7 @@ func (s oscarServer) processFLAPAuth(
 	flapc *wire.FlapClient,
 	advertisedHost string,
 ) error {
-	tlv, err := s.AuthService.FLAPLogin(ctx, signonFrame, advertisedHost)
+	tlv, err := s.authService.FLAPLogin(ctx, signonFrame, advertisedHost)
 	if err != nil {
 		return err
 	}
@@ -485,10 +485,10 @@ func (s oscarServer) processBUCPAuth(ctx context.Context, flapc *wire.FlapClient
 
 		switch frame.FrameType {
 		case wire.FLAPFrameSignoff:
-			s.Logger.Debug("signed off mid-login")
+			s.logger.Debug("signed off mid-login")
 			return io.EOF // client disconnected
 		case wire.FLAPFrameKeepAlive:
-			s.Logger.Debug("received flap keepalive frame")
+			s.logger.Debug("received flap keepalive frame")
 		case wire.FLAPFrameData:
 			buf := bytes.NewReader(frame.Payload)
 			fr := wire.SNACFrame{}
@@ -501,7 +501,7 @@ func (s oscarServer) processBUCPAuth(ctx context.Context, flapc *wire.FlapClient
 				if err := wire.UnmarshalBE(&challengeRequest, buf); err != nil {
 					return err
 				}
-				outSNAC, err := s.BUCPChallenge(ctx, challengeRequest, uuid.New)
+				outSNAC, err := s.authService.BUCPChallenge(ctx, challengeRequest, uuid.New)
 				if err != nil {
 					return err
 				}
@@ -512,7 +512,7 @@ func (s oscarServer) processBUCPAuth(ctx context.Context, flapc *wire.FlapClient
 
 				if outSNAC.Frame.SubGroup == wire.BUCPLoginResponse {
 					screenName, _ := challengeRequest.String(wire.LoginTLVTagsScreenName)
-					s.Logger.Debug("failed BUCP challenge: user does not exist", "screen_name", screenName)
+					s.logger.Debug("failed BUCP challenge: user does not exist", "screen_name", screenName)
 					return nil // account does not exist
 				}
 			case fr.FoodGroup == wire.BUCP && fr.SubGroup == wire.BUCPLoginRequest:
@@ -520,7 +520,7 @@ func (s oscarServer) processBUCPAuth(ctx context.Context, flapc *wire.FlapClient
 				if err := wire.UnmarshalBE(&loginRequest, buf); err != nil {
 					return err
 				}
-				outSNAC, err := s.BUCPLogin(ctx, loginRequest, advertisedHost)
+				outSNAC, err := s.authService.BUCPLogin(ctx, loginRequest, advertisedHost)
 				if err != nil {
 					return err
 				}
@@ -534,13 +534,13 @@ func (s oscarServer) processBUCPAuth(ctx context.Context, flapc *wire.FlapClient
 				}
 				return flapc.NewSignoff(wire.TLVRestBlock{})
 			default:
-				s.Logger.Debug("unexpected SNAC received during login",
+				s.logger.Debug("unexpected SNAC received during login",
 					"foodgroup", wire.FoodGroupName(fr.FoodGroup),
 					"subgroup", wire.SubGroupName(fr.FoodGroup, fr.SubGroup))
 				return io.EOF
 			}
 		default:
-			s.Logger.Debug("unexpected frame type received during login", "type", frame.FrameType)
+			s.logger.Debug("unexpected frame type received during login", "type", frame.FrameType)
 			return io.EOF
 		}
 	}
@@ -573,7 +573,7 @@ func (s oscarServer) dispatchIncomingMessages(
 	listener config.Listener,
 ) error {
 	defer func() {
-		s.Logger.InfoContext(ctx, "user disconnected")
+		s.logger.InfoContext(ctx, "user disconnected")
 	}()
 
 	// buffered so that the go routine has room to exit
@@ -610,22 +610,22 @@ func (s oscarServer) dispatchIncomingMessages(
 					return err
 				}
 
-				rateClassID, ok := s.SNACRateLimits.RateClassLookup(inFrame.FoodGroup, inFrame.SubGroup)
+				rateClassID, ok := s.rateLimits.RateClassLookup(inFrame.FoodGroup, inFrame.SubGroup)
 				if ok {
 					if status := instance.Session().EvaluateRateLimit(time.Now(), rateClassID); status == wire.RateLimitStatusLimited {
-						s.Logger.DebugContext(ctx, "rate limit exceeded, dropping SNAC",
+						s.logger.DebugContext(ctx, "rate limit exceeded, dropping SNAC",
 							"foodgroup", wire.FoodGroupName(inFrame.FoodGroup),
 							"subgroup", wire.SubGroupName(inFrame.FoodGroup, inFrame.SubGroup))
 						break
 					}
 				} else {
-					s.Logger.ErrorContext(ctx, "rate limit not found, allowing request through")
+					s.logger.ErrorContext(ctx, "rate limit not found, allowing request through")
 				}
 
 				// route a client request to the appropriate service handler. the
 				// handler may write a response to the client connection.
-				if err := s.SNACHandler(ctx, fg, instance, inFrame, flapBuf, flapc, listener); err != nil {
-					middleware.LogRequestError(ctx, s.Logger, inFrame, err)
+				if err := s.snacHandler(ctx, fg, instance, inFrame, flapBuf, flapc, listener); err != nil {
+					middleware.LogRequestError(ctx, s.logger, inFrame, err)
 					if errors.Is(err, ErrRouteNotFound) {
 						if err1 := sendInvalidSNACErr(inFrame, flapc); err1 != nil {
 							return errors.Join(err1, err)
@@ -639,18 +639,18 @@ func (s oscarServer) dispatchIncomingMessages(
 			case wire.FLAPFrameError:
 				return fmt.Errorf("got FLAPFrameError. flap: %v", flap)
 			case wire.FLAPFrameSignoff:
-				s.Logger.InfoContext(ctx, "got FLAPFrameSignoff", "flap", flap)
+				s.logger.InfoContext(ctx, "got FLAPFrameSignoff", "flap", flap)
 				return nil
 			case wire.FLAPFrameKeepAlive:
-				s.Logger.DebugContext(ctx, "keepalive heartbeat")
+				s.logger.DebugContext(ctx, "keepalive heartbeat")
 			default:
 				return fmt.Errorf("got unknown FLAP frame type. flap: %v", flap)
 			}
 		case <-time.After(1 * time.Second):
-			updates := s.RateLimitUpdater.RateLimitUpdates(ctx, instance, time.Now())
+			updates := s.rateLimitUpdater.RateLimitUpdates(ctx, instance, time.Now())
 			for _, update := range updates {
 				if err := flapc.SendSNAC(update.Frame, update.Body); err != nil {
-					middleware.LogRequestError(ctx, s.Logger, update.Frame, err)
+					middleware.LogRequestError(ctx, s.logger, update.Frame, err)
 					return err
 				}
 			}
@@ -683,8 +683,8 @@ func (s oscarServer) dispatchIncomingMessages(
 			}
 			return nil
 		case err := <-errCh:
-			if !errors.Is(io.EOF, err) {
-				s.Logger.ErrorContext(ctx, "client disconnected with error", "err", err)
+			if !errors.Is(err, io.EOF) {
+				s.logger.ErrorContext(ctx, "client disconnected with error", "err", err)
 			}
 			return nil
 		}

+ 53 - 53
server/oscar/server_test.go

@@ -94,7 +94,7 @@ func TestServer_ListenAndServeAndShutdown(t *testing.T) {
 		for attempt := 0; attempt < maxRetries; attempt++ {
 			conn, err := net.Dial("tcp", "localhost"+cfg[i].BOSListenAddress)
 			if err == nil {
-				conn.Close()
+				_ = conn.Close()
 				break
 			}
 			if attempt == maxRetries-1 {
@@ -234,9 +234,9 @@ func TestOscarServer_RouteConnection_Auth_BUCP(t *testing.T) {
 		}, nil)
 
 	rt := oscarServer{
-		AuthService:   authService,
-		Logger:        slog.Default(),
-		IPRateLimiter: NewIPRateLimiter(rate.Every(1*time.Minute), 10, 1*time.Minute),
+		authService:   authService,
+		logger:        slog.Default(),
+		ipRateLimiter: NewIPRateLimiter(rate.Every(1*time.Minute), 10, 1*time.Minute),
 	}
 	assert.NoError(t, rt.routeConnection(context.Background(), clientFake, config.Listener{BOSAdvertisedHostPlain: "localhost:5190"}))
 
@@ -309,9 +309,9 @@ func TestOscarServer_RouteConnection_Auth_FLAP(t *testing.T) {
 		}, nil)
 
 	rt := oscarServer{
-		AuthService:   authService,
-		Logger:        slog.Default(),
-		IPRateLimiter: NewIPRateLimiter(rate.Every(1*time.Minute), 10, 1*time.Minute),
+		authService:   authService,
+		logger:        slog.Default(),
+		ipRateLimiter: NewIPRateLimiter(rate.Every(1*time.Minute), 10, 1*time.Minute),
 	}
 	assert.NoError(t, rt.routeConnection(context.Background(), clientFake, config.Listener{BOSAdvertisedHostPlain: "localhost:5190"}))
 
@@ -425,13 +425,13 @@ func TestOscarServer_RouteConnection_BOS(t *testing.T) {
 	}
 
 	rt := oscarServer{
-		AuthService:        authService,
-		SNACHandler:        handler,
-		Logger:             slog.Default(),
-		OnlineNotifier:     onlineNotifier,
-		BuddyListRegistry:  buddyListRegistry,
-		ChatSessionManager: chatSessionManager,
-		DepartureNotifier:  departureNotifier,
+		authService:        authService,
+		snacHandler:        handler,
+		logger:             slog.Default(),
+		onlineNotifier:     onlineNotifier,
+		buddyListRegistry:  buddyListRegistry,
+		chatSessionManager: chatSessionManager,
+		departureNotifier:  departureNotifier,
 		recalcWarning: func(ctx context.Context, instance *state.SessionInstance) error {
 			return nil
 		},
@@ -538,13 +538,13 @@ func TestOscarServer_RouteConnection_BOS_MultiSessionSignoff(t *testing.T) {
 	}
 
 	rt := oscarServer{
-		AuthService:        authService,
-		SNACHandler:        handler,
-		Logger:             slog.Default(),
-		OnlineNotifier:     onlineNotifier,
-		BuddyListRegistry:  buddyListRegistry,
-		ChatSessionManager: chatSessionManager,
-		DepartureNotifier:  departureNotifier,
+		authService:        authService,
+		snacHandler:        handler,
+		logger:             slog.Default(),
+		onlineNotifier:     onlineNotifier,
+		buddyListRegistry:  buddyListRegistry,
+		chatSessionManager: chatSessionManager,
+		departureNotifier:  departureNotifier,
 		recalcWarning: func(ctx context.Context, instance *state.SessionInstance) error {
 			return nil
 		},
@@ -611,8 +611,8 @@ func TestOscarServer_RouteConnection_BOS_MaxConcurrentSessionsReached(t *testing
 		Return(state.ServerCookie{Service: wire.BOS}, nil)
 
 	rt := oscarServer{
-		AuthService: authService,
-		Logger:      slog.Default(),
+		authService: authService,
+		logger:      slog.Default(),
 	}
 	assert.NoError(t, rt.routeConnection(context.Background(), clientFake, config.Listener{}))
 
@@ -713,13 +713,13 @@ func TestOscarServer_RouteConnection_Chat(t *testing.T) {
 	}
 
 	rt := oscarServer{
-		AuthService:        authService,
-		SNACHandler:        handler,
-		Logger:             slog.Default(),
-		OnlineNotifier:     onlineNotifier,
-		BuddyListRegistry:  buddyListRegistry,
-		ChatSessionManager: chatSessionManager,
-		DepartureNotifier:  departureNotifier,
+		authService:        authService,
+		snacHandler:        handler,
+		logger:             slog.Default(),
+		onlineNotifier:     onlineNotifier,
+		buddyListRegistry:  buddyListRegistry,
+		chatSessionManager: chatSessionManager,
+		departureNotifier:  departureNotifier,
 	}
 	assert.NoError(t, rt.routeConnection(context.Background(), clientFake, config.Listener{}))
 
@@ -808,13 +808,13 @@ func TestOscarServer_RouteConnection_Admin(t *testing.T) {
 	}
 
 	rt := oscarServer{
-		AuthService:        authService,
-		SNACHandler:        handler,
-		Logger:             slog.Default(),
-		OnlineNotifier:     onlineNotifier,
-		BuddyListRegistry:  buddyListRegistry,
-		ChatSessionManager: chatSessionManager,
-		DepartureNotifier:  departureNotifier,
+		authService:        authService,
+		snacHandler:        handler,
+		logger:             slog.Default(),
+		onlineNotifier:     onlineNotifier,
+		buddyListRegistry:  buddyListRegistry,
+		chatSessionManager: chatSessionManager,
+		departureNotifier:  departureNotifier,
 	}
 	assert.NoError(t, rt.routeConnection(context.Background(), clientFake, config.Listener{}))
 
@@ -832,7 +832,7 @@ func Test_oscarServer_dispatchIncomingMessages_shutdownSignoff(t *testing.T) {
 	go func() {
 		defer wg.Done()
 		srv := oscarServer{
-			Logger: slog.Default(),
+			logger: slog.Default(),
 		}
 		instance := state.NewSession().AddInstance()
 		instance.SetMultiConnFlag(wire.MultiConnFlagsRecentClient)
@@ -863,7 +863,7 @@ func Test_oscarServer_dispatchIncomingMessages_disconnect_old_client(t *testing.
 	go func() {
 		defer wg.Done()
 		srv := oscarServer{
-			Logger: slog.Default(),
+			logger: slog.Default(),
 		}
 		flapc := wire.NewFlapClient(0, serverConn, serverConn)
 		err := srv.dispatchIncomingMessages(ctx, wire.BOS, instance, flapc, serverConn, config.Listener{})
@@ -892,7 +892,7 @@ func Test_oscarServer_dispatchIncomingMessages_disconnect_new_client(t *testing.
 	go func() {
 		defer wg.Done()
 		srv := oscarServer{
-			Logger: slog.Default(),
+			logger: slog.Default(),
 		}
 		flapc := wire.NewFlapClient(0, serverConn, serverConn)
 		err := srv.dispatchIncomingMessages(ctx, wire.BOS, instance, flapc, serverConn, config.Listener{})
@@ -911,8 +911,8 @@ func Test_oscarServer_dispatchIncomingMessages_disconnect_new_client(t *testing.
 
 func Test_oscarServer_receiveSessMessages_BOS_integration(t *testing.T) {
 	serverConn, clientConn := net.Pipe()
-	defer serverConn.Close()
-	defer clientConn.Close()
+	defer func() { _ = serverConn.Close() }()
+	defer func() { _ = clientConn.Close() }()
 
 	// Prepare session and mocks so we can exercise through routeConnection
 	instance := state.NewSession().AddInstance()
@@ -956,12 +956,12 @@ func Test_oscarServer_receiveSessMessages_BOS_integration(t *testing.T) {
 	chatSessionManager.EXPECT().RemoveUserFromAllChats(mock.Anything)
 
 	server := oscarServer{
-		AuthService:        authService,
-		BuddyListRegistry:  buddyListRegistry,
-		ChatSessionManager: chatSessionManager,
-		DepartureNotifier:  departureNotifier,
-		OnlineNotifier:     onlineNotifier,
-		Logger:             slog.New(slog.NewTextHandler(io.Discard, nil)),
+		authService:        authService,
+		buddyListRegistry:  buddyListRegistry,
+		chatSessionManager: chatSessionManager,
+		departureNotifier:  departureNotifier,
+		onlineNotifier:     onlineNotifier,
+		logger:             slog.New(slog.NewTextHandler(io.Discard, nil)),
 		recalcWarning:      func(ctx context.Context, instance *state.SessionInstance) error { return nil },
 		lowerWarnLevel:     func(ctx context.Context, instance *state.SessionInstance) {},
 	}
@@ -1062,8 +1062,8 @@ func Test_oscarServer_receiveSessMessages_BOS_integration(t *testing.T) {
 
 func Test_oscarServer_receiveSessMessages_Chat_integration(t *testing.T) {
 	serverConn, clientConn := net.Pipe()
-	defer serverConn.Close()
-	defer clientConn.Close()
+	defer func() { _ = serverConn.Close() }()
+	defer func() { _ = clientConn.Close() }()
 
 	// Prepare session and mocks so we can exercise through routeConnection
 	instance := state.NewSession().AddInstance()
@@ -1097,9 +1097,9 @@ func Test_oscarServer_receiveSessMessages_Chat_integration(t *testing.T) {
 		})
 
 	server := oscarServer{
-		AuthService:    authService,
-		OnlineNotifier: onlineNotifier,
-		Logger:         slog.New(slog.NewTextHandler(io.Discard, nil)),
+		authService:    authService,
+		onlineNotifier: onlineNotifier,
+		logger:         slog.New(slog.NewTextHandler(io.Discard, nil)),
 	}
 
 	// Fake client connection with address

+ 1 - 1
server/toc/cmd_client.go

@@ -1035,7 +1035,7 @@ func (s OSCARProxy) FormatNickname(ctx context.Context, me *state.SessionInstanc
 			return []string{"ERROR:" + wire.TOCErrorAdminProcessingRequest}
 		}
 	}
-	val, hasVal := replyBody.TLVBlock.String(wire.AdminTLVScreenNameFormatted)
+	val, hasVal := replyBody.String(wire.AdminTLVScreenNameFormatted)
 	if !hasVal {
 		return s.runtimeErr(ctx, fmt.Errorf("AdminService.InfoChangeRequest: missing AdminTLVScreenNameFormatted %v", replyBody))
 	}

+ 33 - 33
server/toc/cmd_client_test.go

@@ -937,23 +937,23 @@ func TestOSCARProxy_RecvClientCmd_ChatAccept(t *testing.T) {
 					Return(params.msg, params.err)
 			}
 			oServiceSvc := newMockOServiceService(t)
-			for _, params := range tc.mockParams.oServiceParams.serviceRequestParams {
+			for _, params := range tc.mockParams.serviceRequestParams {
 				oServiceSvc.EXPECT().
 					ServiceRequest(ctx, wire.BOS, matchSession(params.me), wire.SNACFrame{}, params.bodyIn, config.Listener{}).
 					Return(params.msg, params.err)
 			}
-			for _, params := range tc.mockParams.oServiceParams.clientOnlineParams {
+			for _, params := range tc.mockParams.clientOnlineParams {
 				oServiceSvc.EXPECT().
 					ClientOnline(ctx, wire.Chat, params.body, matchSession(params.me)).
 					Return(params.err)
 			}
 			authSvc := newMockAuthService(t)
-			for _, params := range tc.mockParams.authParams.registerChatSessionParams {
+			for _, params := range tc.mockParams.registerChatSessionParams {
 				authSvc.EXPECT().
 					RegisterChatSession(ctx, params.authCookie, mock.Anything).
 					Return(params.instance, params.err)
 			}
-			for _, params := range tc.mockParams.authParams.crackCookieParams {
+			for _, params := range tc.mockParams.crackCookieParams {
 				authSvc.EXPECT().
 					CrackCookie(params.cookieIn).
 					Return(params.cookieOut, params.err)
@@ -1469,23 +1469,23 @@ func TestOSCARProxy_RecvClientCmd_ChatJoin(t *testing.T) {
 					Return(params.msg, params.err)
 			}
 			bosOServiceSvc := newMockOServiceService(t)
-			for _, params := range tc.mockParams.oServiceParams.serviceRequestParams {
+			for _, params := range tc.mockParams.serviceRequestParams {
 				bosOServiceSvc.EXPECT().
 					ServiceRequest(ctx, wire.BOS, matchSession(params.me), wire.SNACFrame{}, params.bodyIn, config.Listener{}).
 					Return(params.msg, params.err)
 			}
-			for _, params := range tc.mockParams.oServiceParams.clientOnlineParams {
+			for _, params := range tc.mockParams.clientOnlineParams {
 				bosOServiceSvc.EXPECT().
 					ClientOnline(ctx, wire.Chat, params.body, matchSession(params.me)).
 					Return(params.err)
 			}
 			authSvc := newMockAuthService(t)
-			for _, params := range tc.mockParams.authParams.registerChatSessionParams {
+			for _, params := range tc.mockParams.registerChatSessionParams {
 				authSvc.EXPECT().
 					RegisterChatSession(ctx, params.authCookie, mock.Anything).
 					Return(params.instance, params.err)
 			}
-			for _, params := range tc.mockParams.authParams.crackCookieParams {
+			for _, params := range tc.mockParams.crackCookieParams {
 				authSvc.EXPECT().
 					CrackCookie(params.cookieIn).
 					Return(params.cookieOut, params.err)
@@ -2983,14 +2983,14 @@ func TestOSCARProxy_RecvClientCmd_InitDone(t *testing.T) {
 			ctx := context.Background()
 
 			oSvc := newMockOServiceService(t)
-			for _, params := range tc.mockParams.oServiceParams.clientOnlineParams {
+			for _, params := range tc.mockParams.clientOnlineParams {
 				oSvc.EXPECT().
 					ClientOnline(ctx, params.service, params.body, matchSession(params.me)).
 					Return(params.err)
 			}
 
 			fbMgr := newMockFeedbagManager(t)
-			for _, params := range tc.mockParams.feedBagParams.useFeedbagParams {
+			for _, params := range tc.mockParams.useFeedbagParams {
 				fbMgr.EXPECT().
 					UseFeedbag(ctx, params.me).
 					Return(params.err)
@@ -3599,13 +3599,13 @@ func TestOSCARProxy_RecvClientCmd_NewBuddies(t *testing.T) {
 			ctx := context.Background()
 
 			fbMgr := newMockFeedbagManager(t)
-			for _, params := range tc.mockParams.feedBagParams.feedbagParams {
+			for _, params := range tc.mockParams.feedbagParams {
 				fbMgr.EXPECT().
 					Feedbag(ctx, params.screenName).
 					Return(params.results, params.err)
 			}
 			fbSvc := newMockFeedbagService(t)
-			for _, params := range tc.mockParams.feedBagParams.feedbagServiceUpsertItemParams {
+			for _, params := range tc.mockParams.feedbagServiceUpsertItemParams {
 				fbSvc.EXPECT().
 					UpsertItem(ctx, matchSession(tc.me.IdentScreenName()), params.frame, params.items).
 					Return(params.msg, params.err)
@@ -3804,13 +3804,13 @@ func TestOSCARProxy_RecvClientCmd_NewGroup(t *testing.T) {
 			ctx := context.Background()
 
 			fbMgr := newMockFeedbagManager(t)
-			for _, params := range tc.mockParams.feedBagParams.feedbagParams {
+			for _, params := range tc.mockParams.feedbagParams {
 				fbMgr.EXPECT().
 					Feedbag(ctx, params.screenName).
 					Return(params.results, params.err)
 			}
 			fbSvc := newMockFeedbagService(t)
-			for _, params := range tc.mockParams.feedBagParams.feedbagServiceUpsertItemParams {
+			for _, params := range tc.mockParams.feedbagServiceUpsertItemParams {
 				fbSvc.EXPECT().
 					UpsertItem(ctx, matchSession(tc.me.IdentScreenName()), params.frame, params.items).
 					Return(params.msg, params.err)
@@ -4007,18 +4007,18 @@ func TestOSCARProxy_RecvClientCmd_DelGroup(t *testing.T) {
 			ctx := context.Background()
 
 			fbMgr := newMockFeedbagManager(t)
-			for _, params := range tc.mockParams.feedBagParams.feedbagParams {
+			for _, params := range tc.mockParams.feedbagParams {
 				fbMgr.EXPECT().
 					Feedbag(ctx, params.screenName).
 					Return(params.results, params.err)
 			}
 			fbSvc := newMockFeedbagService(t)
-			for _, params := range tc.mockParams.feedBagParams.feedbagServiceDeleteItemParams {
+			for _, params := range tc.mockParams.feedbagServiceDeleteItemParams {
 				fbSvc.EXPECT().
 					DeleteItem(ctx, matchSession(tc.me.IdentScreenName()), params.frame, params.inBody).
 					Return(params.msg, params.err)
 			}
-			for _, params := range tc.mockParams.feedBagParams.feedbagServiceUpsertItemParams {
+			for _, params := range tc.mockParams.feedbagServiceUpsertItemParams {
 				fbSvc.EXPECT().
 					UpsertItem(ctx, matchSession(tc.me.IdentScreenName()), params.frame, params.items).
 					Return(params.msg, params.err)
@@ -4204,13 +4204,13 @@ func TestOSCARProxy_RecvClientCmd_SetPDMode(t *testing.T) {
 			ctx := context.Background()
 
 			fbMgr := newMockFeedbagManager(t)
-			for _, params := range tc.mockParams.feedBagParams.feedbagParams {
+			for _, params := range tc.mockParams.feedbagParams {
 				fbMgr.EXPECT().
 					Feedbag(ctx, params.screenName).
 					Return(params.results, params.err)
 			}
 			fbSvc := newMockFeedbagService(t)
-			for _, params := range tc.mockParams.feedBagParams.feedbagServiceUpsertItemParams {
+			for _, params := range tc.mockParams.feedbagServiceUpsertItemParams {
 				fbSvc.EXPECT().
 					UpsertItem(ctx, matchSession(tc.me.IdentScreenName()), params.frame, params.items).
 					Return(params.msg, params.err)
@@ -4372,13 +4372,13 @@ func TestOSCARProxy_RecvClientCmd_AddPermit2(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			ctx := context.Background()
 			fbMgr := newMockFeedbagManager(t)
-			for _, params := range tc.mockParams.feedBagParams.feedbagParams {
+			for _, params := range tc.mockParams.feedbagParams {
 				fbMgr.EXPECT().
 					Feedbag(ctx, params.screenName).
 					Return(params.results, params.err)
 			}
 			fbSvc := newMockFeedbagService(t)
-			for _, params := range tc.mockParams.feedBagParams.feedbagServiceUpsertItemParams {
+			for _, params := range tc.mockParams.feedbagServiceUpsertItemParams {
 				fbSvc.EXPECT().
 					UpsertItem(ctx, matchSession(tc.me.IdentScreenName()), params.frame, params.items).
 					Return(params.msg, params.err)
@@ -4538,13 +4538,13 @@ func TestOSCARProxy_RecvClientCmd_AddDeny2(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			ctx := context.Background()
 			fbMgr := newMockFeedbagManager(t)
-			for _, params := range tc.mockParams.feedBagParams.feedbagParams {
+			for _, params := range tc.mockParams.feedbagParams {
 				fbMgr.EXPECT().
 					Feedbag(ctx, params.screenName).
 					Return(params.results, params.err)
 			}
 			fbSvc := newMockFeedbagService(t)
-			for _, params := range tc.mockParams.feedBagParams.feedbagServiceUpsertItemParams {
+			for _, params := range tc.mockParams.feedbagServiceUpsertItemParams {
 				fbSvc.EXPECT().
 					UpsertItem(ctx, matchSession(tc.me.IdentScreenName()), params.frame, params.items).
 					Return(params.msg, params.err)
@@ -4706,18 +4706,18 @@ func TestOSCARProxy_RecvClientCmd_RemoveBuddy2(t *testing.T) {
 			ctx := context.Background()
 
 			fbMgr := newMockFeedbagManager(t)
-			for _, params := range tc.mockParams.feedBagParams.feedbagParams {
+			for _, params := range tc.mockParams.feedbagParams {
 				fbMgr.EXPECT().
 					Feedbag(ctx, params.screenName).
 					Return(params.results, params.err)
 			}
 			fbSvc := newMockFeedbagService(t)
-			for _, params := range tc.mockParams.feedBagParams.feedbagServiceDeleteItemParams {
+			for _, params := range tc.mockParams.feedbagServiceDeleteItemParams {
 				fbSvc.EXPECT().
 					DeleteItem(ctx, matchSession(tc.me.IdentScreenName()), params.frame, params.inBody).
 					Return(params.msg, params.err)
 			}
-			for _, params := range tc.mockParams.feedBagParams.feedbagServiceUpsertItemParams {
+			for _, params := range tc.mockParams.feedbagServiceUpsertItemParams {
 				fbSvc.EXPECT().
 					UpsertItem(ctx, matchSession(tc.me.IdentScreenName()), params.frame, params.items).
 					Return(params.msg, params.err)
@@ -4882,13 +4882,13 @@ func TestOSCARProxy_RecvClientCmd_RemovePermit2(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			ctx := context.Background()
 			fbMgr := newMockFeedbagManager(t)
-			for _, params := range tc.mockParams.feedBagParams.feedbagParams {
+			for _, params := range tc.mockParams.feedbagParams {
 				fbMgr.EXPECT().
 					Feedbag(ctx, params.screenName).
 					Return(params.results, params.err)
 			}
 			fbSvc := newMockFeedbagService(t)
-			for _, params := range tc.mockParams.feedBagParams.feedbagServiceDeleteItemParams {
+			for _, params := range tc.mockParams.feedbagServiceDeleteItemParams {
 				fbSvc.EXPECT().
 					DeleteItem(ctx, matchSession(tc.me.IdentScreenName()), params.frame, params.inBody).
 					Return(params.msg, params.err)
@@ -5052,13 +5052,13 @@ func TestOSCARProxy_RecvClientCmd_RemoveDeny2(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			ctx := context.Background()
 			fbMgr := newMockFeedbagManager(t)
-			for _, params := range tc.mockParams.feedBagParams.feedbagParams {
+			for _, params := range tc.mockParams.feedbagParams {
 				fbMgr.EXPECT().
 					Feedbag(ctx, params.screenName).
 					Return(params.results, params.err)
 			}
 			fbSvc := newMockFeedbagService(t)
-			for _, params := range tc.mockParams.feedBagParams.feedbagServiceDeleteItemParams {
+			for _, params := range tc.mockParams.feedbagServiceDeleteItemParams {
 				fbSvc.EXPECT().
 					DeleteItem(ctx, matchSession(tc.me.IdentScreenName()), params.frame, params.inBody).
 					Return(params.msg, params.err)
@@ -6266,7 +6266,7 @@ func TestOSCARProxy_RecvClientCmd_SetIdle(t *testing.T) {
 			ctx := context.Background()
 
 			oServiceSvc := newMockOServiceService(t)
-			for _, params := range tc.mockParams.oServiceParams.idleNotificationParams {
+			for _, params := range tc.mockParams.idleNotificationParams {
 				oServiceSvc.EXPECT().
 					IdleNotification(ctx, matchSession(params.me), params.bodyIn).
 					Return(params.err)
@@ -6954,13 +6954,13 @@ func TestOSCARProxy_Signon(t *testing.T) {
 					Return(params.returnedUser, params.err)
 			}
 			fbSvc := newMockFeedbagService(t)
-			for _, params := range tc.mockParams.feedBagParams.feedbagServiceUseParams {
+			for _, params := range tc.mockParams.feedbagServiceUseParams {
 				fbSvc.EXPECT().
 					Use(matchContext(), mock.Anything).
 					Return(params.err)
 			}
 			fbMgr := newMockFeedbagManager(t)
-			for _, params := range tc.mockParams.feedBagParams.feedbagParams {
+			for _, params := range tc.mockParams.feedbagParams {
 				fbMgr.EXPECT().
 					Feedbag(matchContext(), params.screenName).
 					Return(params.results, params.err)

+ 7 - 7
server/toc/cmd_server.go

@@ -218,7 +218,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 {
-	buf, ok := snac.TLVRestBlock.Bytes(wire.ICBMTLVAOLIMData)
+	buf, ok := snac.Bytes(wire.ICBMTLVAOLIMData)
 	if !ok {
 		return s.runtimeErr(ctx, errors.New("TLVRestBlock.Bytes: missing wire.ICBMTLVAOLIMData"))[0]
 	}
@@ -228,12 +228,12 @@ func (s OSCARProxy) convertICBMInstantMsg(ctx context.Context, me *state.Session
 	}
 
 	autoResp := "F"
-	if _, isAutoReply := snac.TLVRestBlock.Bytes(wire.ICBMTLVAutoResponse); isAutoReply {
+	if _, isAutoReply := snac.Bytes(wire.ICBMTLVAutoResponse); isAutoReply {
 		autoResp = "T"
 	}
 
 	if me.SupportsTOC2MsgEnc() {
-		uFlags, _ := snac.TLVUserInfo.TLVList.Uint16BE(wire.OServiceUserInfoUserFlags)
+		uFlags, _ := snac.TLVUserInfo.Uint16BE(wire.OServiceUserInfoUserFlags)
 		classStr := userClassString(uFlags, snac.IsAway())
 		// from a packet dump found in this russian zine: https://xn--lcss68aj21b.xn--w8je.xn--tckwe/books/xakep/spec65.pdf
 		// interesting that "L" is a value, not sure what it's for.
@@ -252,7 +252,7 @@ func (s OSCARProxy) convertICBMInstantMsg(ctx context.Context, me *state.Session
 //   - file transfer, return RVOUS_PROPOSE
 //   - don't respond for other rendezvous types
 func (s OSCARProxy) convertICBMRendezvous(ctx context.Context, chatRegistry *ChatRegistry, snac wire.SNAC_0x04_0x07_ICBMChannelMsgToClient) string {
-	rdinfo, has := snac.TLVRestBlock.Bytes(wire.ICBMTLVData)
+	rdinfo, has := snac.Bytes(wire.ICBMTLVData)
 	if !has {
 		return s.runtimeErr(ctx, errors.New("TLVRestBlock.Bytes: missing rendezvous block"))[0]
 	}
@@ -293,7 +293,7 @@ func (s OSCARProxy) convertICBMRendezvous(ctx context.Context, chatRegistry *Cha
 
 		return fmt.Sprintf("CHAT_INVITE:%s:%d:%s:%s", roomName, chatID, snac.ScreenName, prompt)
 	case wire.CapFileTransfer:
-		user := snac.TLVUserInfo.ScreenName
+		user := snac.ScreenName
 		capability := strings.ToUpper(wire.CapFileTransfer.String()) // TiK requires upper-case UUID characters
 		cookie := base64.StdEncoding.EncodeToString(frag.Cookie[:])
 		seq, _ := frag.Uint16BE(wire.ICBMRdvTLVTagsSeqNum)
@@ -578,7 +578,7 @@ func userInfoToUpdateBuddy(snac wire.TLVUserInfo, me *state.SessionInstance) str
 	online, _ := snac.Uint32BE(wire.OServiceUserInfoSignonTOD)
 	idle, _ := snac.Uint16BE(wire.OServiceUserInfoIdleTime)
 
-	uFlags, _ := snac.TLVList.Uint16BE(wire.OServiceUserInfoUserFlags)
+	uFlags, _ := snac.Uint16BE(wire.OServiceUserInfoUserFlags)
 	uc := userClassString(uFlags, snac.IsAway())
 	warning := fmt.Sprintf("%d", snac.WarningLevel/10)
 
@@ -607,7 +607,7 @@ func userInfoToBuddyCaps(snac wire.TLVUserInfo, me *state.SessionInstance, logge
 	if !me.IsTOC2() {
 		return ""
 	}
-	b, hasCaps := snac.TLVList.Bytes(wire.OServiceUserInfoOscarCaps)
+	b, hasCaps := snac.Bytes(wire.OServiceUserInfoOscarCaps)
 	if !hasCaps {
 		logger.DebugContext(context.Background(), "userInfoToBuddyCaps: no buddy caps found")
 		return ""

+ 2 - 2
server/toc/cmd_server_test.go

@@ -1077,7 +1077,7 @@ func TestOSCARProxy_RecvBOS_Inserted2(t *testing.T) {
 			ctx, cancel := context.WithCancel(context.Background())
 
 			fbMgr := newMockFeedbagManager(t)
-			for _, params := range tc.mockParams.feedBagParams.feedbagParams {
+			for _, params := range tc.mockParams.feedbagParams {
 				fbMgr.EXPECT().
 					Feedbag(mock.Anything, params.screenName).
 					Return(params.results, params.err)
@@ -1253,7 +1253,7 @@ func TestOSCARProxy_RecvBOS_Deleted2(t *testing.T) {
 			ctx, cancel := context.WithCancel(context.Background())
 
 			fbMgr := newMockFeedbagManager(t)
-			for _, params := range tc.mockParams.feedBagParams.feedbagParams {
+			for _, params := range tc.mockParams.feedbagParams {
 				fbMgr.EXPECT().
 					Feedbag(mock.Anything, params.screenName).
 					Return(params.results, params.err)

+ 0 - 22
server/toc/helpers_test.go

@@ -27,11 +27,6 @@ type addBuddiesParams []struct {
 	err    error
 }
 
-type broadcastBuddyDepartedParams []struct {
-	me  state.IdentScreenName
-	err error
-}
-
 type delBuddiesParams []struct {
 	me     state.IdentScreenName
 	inBody wire.SNAC_0x03_0x05_BuddyDelBuddies
@@ -40,7 +35,6 @@ type delBuddiesParams []struct {
 
 type buddyParams struct {
 	addBuddiesParams
-	broadcastBuddyDepartedParams
 	delBuddiesParams
 }
 
@@ -144,21 +138,11 @@ type registerChatSessionParams []struct {
 	err        error
 }
 
-type signoutParams []struct {
-	me state.IdentScreenName
-}
-
-type signoutChatParams []struct {
-	me state.IdentScreenName
-}
-
 type authParams struct {
 	crackCookieParams
 	flapLoginParams
 	registerBOSSessionParams
 	registerChatSessionParams
-	signoutParams
-	signoutChatParams
 }
 
 type crackCookieParams []struct {
@@ -232,14 +216,8 @@ type registerBuddyListParams []struct {
 	err  error
 }
 
-type unregisterBuddyListParams []struct {
-	user state.IdentScreenName
-	err  error
-}
-
 type buddyListRegistryParams struct {
 	registerBuddyListParams
-	unregisterBuddyListParams
 }
 
 type setTOCConfigParams []struct {

+ 10 - 3
server/toc/server.go

@@ -34,6 +34,13 @@ var (
 	errTOCProcessing = errors.New("failed to process TOC request")
 )
 
+type contextKey string
+
+const (
+	ipContextKey         contextKey = "ip"
+	screenNameContextKey contextKey = "screenName"
+)
+
 // bufferedConn is a wrapper around net.Conn that allows peeking into the
 // incoming connection without consuming data. It is useful for multiplexing
 // TOC/HTTP and TOC/FLAP connections.
@@ -339,7 +346,7 @@ func (s *Server) dispatchFLAP(ctx context.Context, conn net.Conn) error {
 	}
 	defer closeConn()
 
-	ctx = context.WithValue(ctx, "ip", conn.RemoteAddr().String())
+	ctx = context.WithValue(ctx, ipContextKey, conn.RemoteAddr().String())
 
 	clientFlap, err := s.initFLAP(ctx, conn)
 	if err != nil {
@@ -370,9 +377,9 @@ func (s *Server) dispatchFLAP(ctx context.Context, conn net.Conn) error {
 		return nil // user not found
 	}
 
-	ctx = context.WithValue(ctx, "screenName", sessBOS.IdentScreenName())
+	ctx = context.WithValue(ctx, screenNameContextKey, sessBOS.IdentScreenName())
 
-	remoteAddr, ok := ctx.Value("ip").(string)
+	remoteAddr, ok := ctx.Value(ipContextKey).(string)
 	if ok {
 		ip, err := netip.ParseAddrPort(remoteAddr)
 		if err != nil {

+ 2 - 2
server/webapi/adapters.go

@@ -131,8 +131,8 @@ func ICBMToWebAPIEvent(icbm wire.SNAC_0x04_0x07_ICBMChannelMsgToClient) (types.E
 
 	// Extract sender screen name from TLVUserInfo
 	senderScreenName := ""
-	if icbm.TLVUserInfo.ScreenName != "" {
-		senderScreenName = icbm.TLVUserInfo.ScreenName
+	if icbm.ScreenName != "" {
+		senderScreenName = icbm.ScreenName
 	}
 
 	// Create WebAPI event

+ 2 - 19
server/webapi/handlers/amf_encoder.go

@@ -275,23 +275,6 @@ func (e *AMFEncoder) structToMap(v reflect.Value) map[string]interface{} {
 	return result
 }
 
-// sliceToArray converts a slice to an AMF3-compatible array
-func (e *AMFEncoder) sliceToArray(v reflect.Value) []interface{} {
-	length := v.Len()
-	result := make([]interface{}, length)
-
-	for i := 0; i < length; i++ {
-		elem := v.Index(i)
-		if elem.CanInterface() {
-			result[i] = e.toAMF3Compatible(elem.Interface())
-		} else {
-			result[i] = nil
-		}
-	}
-
-	return result
-}
-
 // mapToAMFMap converts a Go map to an AMF3-compatible map
 func (e *AMFEncoder) mapToAMFMap(v reflect.Value) map[string]interface{} {
 	result := make(map[string]interface{})
@@ -327,7 +310,7 @@ func (e *AMFEncoder) convertToMap(data interface{}) interface{} {
 	v := reflect.ValueOf(data)
 
 	// Handle pointers
-	if v.Kind() == reflect.Ptr {
+	if v.Kind() == reflect.Pointer {
 		if v.IsNil() {
 			return nil
 		}
@@ -369,7 +352,7 @@ func (e *AMFEncoder) isZeroValue(v reflect.Value) bool {
 		return v.Uint() == 0
 	case reflect.Float32, reflect.Float64:
 		return v.Float() == 0
-	case reflect.Interface, reflect.Ptr:
+	case reflect.Interface, reflect.Pointer:
 		return v.IsNil()
 	case reflect.Struct:
 		// For time.Time, check if it's zero

+ 0 - 1
server/webapi/handlers/auth.go

@@ -65,7 +65,6 @@ func (h *AuthHandler) ClientLogin(w http.ResponseWriter, r *http.Request) {
 		}
 		username = req.Username
 		password = req.Password
-		devID = req.DevID
 	} else {
 		// Parse form-encoded or URL parameters
 		if err := r.ParseForm(); err != nil {

+ 3 - 3
server/webapi/handlers/buddyfeed.go

@@ -310,7 +310,7 @@ func (h *BuddyFeedHandler) sendRSSFeed(w http.ResponseWriter, feed *RSSFeed) {
 	w.Header().Set("Content-Type", "application/rss+xml; charset=utf-8")
 
 	// Add XML declaration
-	w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>`))
+	_, _ = w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>`))
 
 	// Marshal and write the feed
 	encoder := xml.NewEncoder(w)
@@ -325,7 +325,7 @@ func (h *BuddyFeedHandler) sendAtomFeed(w http.ResponseWriter, feed *AtomFeed) {
 	w.Header().Set("Content-Type", "application/atom+xml; charset=utf-8")
 
 	// Add XML declaration
-	w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>`))
+	_, _ = w.Write([]byte(`<?xml version="1.0" encoding="UTF-8"?>`))
 
 	// Marshal and write the feed
 	encoder := xml.NewEncoder(w)
@@ -349,5 +349,5 @@ func (h *BuddyFeedHandler) sendEmptyRSSFeed(w http.ResponseWriter, screenName st
   </channel>
 </rss>`, screenName)
 
-	w.Write([]byte(emptyFeed))
+	_, _ = w.Write([]byte(emptyFeed))
 }

+ 10 - 8
server/webapi/handlers/buddylist.go

@@ -46,18 +46,19 @@ func (h *BuddyListHandler) AddBuddy(w http.ResponseWriter, r *http.Request) {
 	// Get session
 	session, err := h.SessionManager.GetSession(r.Context(), aimsid)
 	if err != nil {
-		if err == state.ErrNoWebAPISession {
+		switch err {
+		case state.ErrNoWebAPISession:
 			h.sendError(w, http.StatusNotFound, "session not found")
-		} else if err == state.ErrWebAPISessionExpired {
+		case state.ErrWebAPISessionExpired:
 			h.sendError(w, http.StatusGone, "session expired")
-		} else {
+		default:
 			h.sendError(w, http.StatusInternalServerError, "internal server error")
 		}
 		return
 	}
 
 	// Touch the session
-	h.SessionManager.TouchSession(r.Context(), aimsid)
+	_ = h.SessionManager.TouchSession(r.Context(), aimsid)
 
 	// Get buddy and group parameters
 	buddyName := strings.TrimSpace(r.URL.Query().Get("buddy"))
@@ -211,18 +212,19 @@ func (h *BuddyListHandler) AddTempBuddy(w http.ResponseWriter, r *http.Request)
 	// Get session
 	session, err := h.SessionManager.GetSession(r.Context(), aimsid)
 	if err != nil {
-		if err == state.ErrNoWebAPISession {
+		switch err {
+		case state.ErrNoWebAPISession:
 			h.sendError(w, http.StatusNotFound, "session not found")
-		} else if err == state.ErrWebAPISessionExpired {
+		case state.ErrWebAPISessionExpired:
 			h.sendError(w, http.StatusGone, "session expired")
-		} else {
+		default:
 			h.sendError(w, http.StatusInternalServerError, "internal server error")
 		}
 		return
 	}
 
 	// Touch the session
-	h.SessionManager.TouchSession(r.Context(), aimsid)
+	_ = h.SessionManager.TouchSession(r.Context(), aimsid)
 
 	// Get buddy names from parameters
 	// The WebAPI accepts multiple buddy names via &t= parameters

+ 0 - 37
server/webapi/handlers/chat.go

@@ -1,8 +1,6 @@
 package handlers
 
 import (
-	"encoding/json"
-	"errors"
 	"log/slog"
 	"net/http"
 	"strings"
@@ -282,38 +280,3 @@ func (h *ChatHandler) LeaveChat(w http.ResponseWriter, r *http.Request) {
 		"screenName", session.ScreenName,
 		"chatsid", chatsid)
 }
-
-// Helper to validate and convert typed JSON data for chat events
-func validateChatEventData(data json.RawMessage, eventType string) (interface{}, error) {
-	switch eventType {
-	case "message":
-		var msgData state.ChatMessageEventData
-		if err := json.Unmarshal(data, &msgData); err != nil {
-			return nil, err
-		}
-		return msgData, nil
-	case "userEntered", "userLeft":
-		var userData state.ChatUserEventData
-		if err := json.Unmarshal(data, &userData); err != nil {
-			return nil, err
-		}
-		return userData, nil
-	case "typing":
-		var typingData state.ChatTypingEventData
-		if err := json.Unmarshal(data, &typingData); err != nil {
-			return nil, err
-		}
-		return typingData, nil
-	case "userInRoom":
-		var participantData state.ChatParticipantList
-		if err := json.Unmarshal(data, &participantData); err != nil {
-			return nil, err
-		}
-		return participantData, nil
-	case "closed":
-		// No additional data for closed event
-		return nil, nil
-	default:
-		return nil, errors.New("unknown chat event type")
-	}
-}

+ 13 - 13
server/webapi/handlers/common.go

@@ -102,7 +102,7 @@ func SendResponse(w http.ResponseWriter, r *http.Request, data interface{}, logg
 
 	// If format not in URL query, check form values (for POST requests)
 	if format == "" && r.Method == "POST" {
-		r.ParseForm()
+		_ = r.ParseForm()
 		format = strings.ToLower(r.FormValue("f"))
 		if callback == "" {
 			callback = r.FormValue("callback")
@@ -161,7 +161,7 @@ func SendJSONError(w http.ResponseWriter, statusCode int, message string) {
 
 	w.Header().Set("Content-Type", "application/json")
 	w.WriteHeader(statusCode)
-	json.NewEncoder(w).Encode(resp)
+	_ = json.NewEncoder(w).Encode(resp)
 }
 
 // SendXMLError sends an XML error response.
@@ -182,7 +182,7 @@ func SendXMLError(w http.ResponseWriter, statusCode int, message string) {
 	}
 
 	xmlOutput := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>%s`, xmlData)
-	w.Write([]byte(xmlOutput))
+	_, _ = w.Write([]byte(xmlOutput))
 }
 
 // SendJSON sends a JSON response.
@@ -219,7 +219,7 @@ func SendXML(w http.ResponseWriter, data interface{}, logger *slog.Logger) {
 
 	// Set content length for proper response handling
 	w.Header().Set("Content-Length", strconv.Itoa(len(xmlOutput)))
-	w.Write([]byte(xmlOutput))
+	_, _ = w.Write([]byte(xmlOutput))
 }
 
 // SendJSONP sends a JSONP response with the specified callback.
@@ -240,10 +240,10 @@ func SendJSONP(w http.ResponseWriter, callback string, data interface{}, logger
 	}
 
 	w.Header().Set("Content-Type", "application/javascript")
-	w.Write([]byte(callback))
-	w.Write([]byte("("))
-	w.Write(jsonData)
-	w.Write([]byte(");"))
+	_, _ = w.Write([]byte(callback))
+	_, _ = w.Write([]byte("("))
+	_, _ = w.Write(jsonData)
+	_, _ = w.Write([]byte(");"))
 }
 
 // IsValidCallback validates a JSONP callback name to prevent XSS.
@@ -254,10 +254,10 @@ func IsValidCallback(callback string) bool {
 
 	// Allow alphanumeric, underscore, dollar sign, and dot (for namespace)
 	for _, r := range callback {
-		if !((r >= 'a' && r <= 'z') ||
-			(r >= 'A' && r <= 'Z') ||
-			(r >= '0' && r <= '9') ||
-			r == '_' || r == '$' || r == '.') {
+		if (r < 'a' || r > 'z') &&
+			(r < 'A' || r > 'Z') &&
+			(r < '0' || r > '9') &&
+			r != '_' && r != '$' && r != '.' {
 			return false
 		}
 	}
@@ -391,5 +391,5 @@ func SendAMFError(w http.ResponseWriter, r *http.Request, statusCode int, messag
 	w.Header().Set("Content-Type", "application/x-amf")
 	w.Header().Set("Content-Length", strconv.Itoa(len(amfData)))
 	w.WriteHeader(statusCode)
-	w.Write(amfData)
+	_, _ = w.Write(amfData)
 }

+ 11 - 9
server/webapi/handlers/events.go

@@ -64,18 +64,19 @@ func (h *EventsHandler) FetchEvents(w http.ResponseWriter, r *http.Request) {
 	// Get session
 	session, err := h.SessionManager.GetSession(r.Context(), aimsid)
 	if err != nil {
-		if err == state.ErrNoWebAPISession {
+		switch err {
+		case state.ErrNoWebAPISession:
 			h.sendError(w, http.StatusNotFound, "session not found")
-		} else if err == state.ErrWebAPISessionExpired {
+		case state.ErrWebAPISessionExpired:
 			h.sendError(w, http.StatusGone, "session expired")
-		} else {
+		default:
 			h.sendError(w, http.StatusInternalServerError, "internal server error")
 		}
 		return
 	}
 
 	// Touch the session to update last accessed time
-	h.SessionManager.TouchSession(r.Context(), aimsid)
+	_ = h.SessionManager.TouchSession(r.Context(), aimsid)
 
 	// Get sequence number parameter
 	var lastSeqNum uint64
@@ -116,7 +117,7 @@ func (h *EventsHandler) FetchEvents(w http.ResponseWriter, r *http.Request) {
 	}
 
 	// Determine the last sequence number
-	var newLastSeqNum uint64 = lastSeqNum
+	newLastSeqNum := lastSeqNum
 	if len(events) > 0 {
 		newLastSeqNum = events[len(events)-1].SeqNum
 	}
@@ -135,7 +136,8 @@ func (h *EventsHandler) FetchEvents(w http.ResponseWriter, r *http.Request) {
 	// Check response format
 	format := strings.ToLower(r.URL.Query().Get("f"))
 
-	if format == "xml" {
+	switch format {
+	case "xml":
 		// Send XML response
 		xmlResp := FetchEventsXMLResponse{}
 		xmlResp.StatusCode = 200
@@ -147,11 +149,11 @@ func (h *EventsHandler) FetchEvents(w http.ResponseWriter, r *http.Request) {
 			r.Host, aimsid, newLastSeqNum)
 
 		w.Header().Set("Content-Type", "text/xml")
-		fmt.Fprint(w, `<?xml version="1.0" encoding="UTF-8"?>`)
+		_, _ = fmt.Fprint(w, `<?xml version="1.0" encoding="UTF-8"?>`)
 		if err := xml.NewEncoder(w).Encode(xmlResp); err != nil {
 			h.Logger.Error("failed to encode XML response", "error", err)
 		}
-	} else if format == "amf" || format == "amf3" {
+	case "amf", "amf3":
 		// For AMF3, build the response with fields in the correct order
 		// The working implementation has: response { data {...}, statusCode, statusText, statusDetailCode }
 		// Convert events to ensure timestamps are float64 for AMF3
@@ -176,7 +178,7 @@ func (h *EventsHandler) FetchEvents(w http.ResponseWriter, r *http.Request) {
 
 		// Use SendResponse which will detect AMF format and encode properly
 		SendResponse(w, r, amfResp, h.Logger)
-	} else {
+	default:
 		// Send JSON/JSONP response with standard structure
 		SendResponse(w, r, resp, h.Logger)
 	}

+ 5 - 4
server/webapi/handlers/oscar_bridge.go

@@ -161,13 +161,14 @@ func (h *OSCARBridgeHandler) StartOSCARSession(w http.ResponseWriter, r *http.Re
 	// Validate WebAPI session
 	session, err := h.SessionManager.GetSession(r.Context(), aimsid)
 	if err != nil {
-		if err == state.ErrNoWebAPISession {
+		switch err {
+		case state.ErrNoWebAPISession:
 			h.Logger.Warn("session not found", "aimsid", aimsid)
 			h.sendError(w, r, http.StatusNotFound, "session not found")
-		} else if err == state.ErrWebAPISessionExpired {
+		case state.ErrWebAPISessionExpired:
 			h.Logger.Warn("session expired", "aimsid", aimsid)
 			h.sendError(w, r, http.StatusGone, "session expired")
-		} else {
+		default:
 			h.Logger.Error("failed to get session", "error", err)
 			h.sendError(w, r, http.StatusInternalServerError, "internal server error")
 		}
@@ -175,7 +176,7 @@ func (h *OSCARBridgeHandler) StartOSCARSession(w http.ResponseWriter, r *http.Re
 	}
 
 	// Touch the session to update last access time
-	h.SessionManager.TouchSession(r.Context(), aimsid)
+	_ = h.SessionManager.TouchSession(r.Context(), aimsid)
 
 	// Check if session already has an OSCAR bridge
 	if session.OSCARSession != nil {

+ 4 - 3
server/webapi/handlers/preference.go

@@ -203,11 +203,12 @@ func (h *PreferenceHandler) GetPreferences(w http.ResponseWriter, r *http.Reques
 		convertedPrefs := make(map[string]interface{})
 		for key, val := range prefs {
 			if strVal, ok := val.(string); ok {
-				if strVal == "1" {
+				switch strVal {
+				case "1":
 					convertedPrefs[key] = 1
-				} else if strVal == "0" {
+				case "0":
 					convertedPrefs[key] = 0
-				} else {
+				default:
 					// Keep non-boolean values as strings
 					convertedPrefs[key] = val
 				}

+ 5 - 4
server/webapi/handlers/presence.go

@@ -72,11 +72,12 @@ func (h *PresenceHandler) GetPresence(w http.ResponseWriter, r *http.Request) {
 	// Get session
 	session, err := h.SessionManager.GetSession(r.Context(), aimsid)
 	if err != nil {
-		if err == state.ErrNoWebAPISession {
+		switch err {
+		case state.ErrNoWebAPISession:
 			h.sendError(w, http.StatusNotFound, "session not found")
-		} else if err == state.ErrWebAPISessionExpired {
+		case state.ErrWebAPISessionExpired:
 			h.sendError(w, http.StatusGone, "session expired")
-		} else {
+		default:
 			h.sendError(w, http.StatusInternalServerError, "internal server error")
 		}
 		return
@@ -618,7 +619,7 @@ func (h *PresenceHandler) Icon(w http.ResponseWriter, r *http.Request) {
 
 	// For now, redirect to a placeholder icon
 	// In production, this would redirect to actual icon storage/CDN
-	iconURL := "/static/icons/default_" + iconType + "_" + size + ".png"
+	var iconURL string
 
 	// If it's an email lookup, extract username
 	if strings.Contains(name, "@") {

+ 5 - 4
server/webapi/handlers/session.go

@@ -468,7 +468,7 @@ func (h *SessionHandler) StartSession(w http.ResponseWriter, r *http.Request) {
 		// Write XML declaration and data as one response
 		xmlOutput := fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>%s`, xmlData)
 		w.Header().Set("Content-Length", strconv.Itoa(len(xmlOutput)))
-		fmt.Fprint(w, xmlOutput)
+		_, _ = fmt.Fprint(w, xmlOutput)
 	} else {
 		// Send response in requested format (JSON, JSONP, or AMF)
 		SendResponse(w, r, resp, h.Logger)
@@ -497,11 +497,12 @@ func (h *SessionHandler) EndSession(w http.ResponseWriter, r *http.Request) {
 	// Get session
 	session, err := h.SessionManager.GetSession(r.Context(), aimsid)
 	if err != nil {
-		if err == state.ErrNoWebAPISession {
+		switch err {
+		case state.ErrNoWebAPISession:
 			h.sendError(w, http.StatusNotFound, "session not found")
-		} else if err == state.ErrWebAPISessionExpired {
+		case state.ErrWebAPISessionExpired:
 			h.sendError(w, http.StatusGone, "session expired")
-		} else {
+		default:
 			h.sendError(w, http.StatusInternalServerError, "internal server error")
 		}
 		return

+ 1 - 1
server/webapi/server.go

@@ -260,7 +260,7 @@ func (s *Server) ListenAndServe() error {
 	ctx, cancel := context.WithCancel(context.Background())
 	defer cancel()
 
-	g, ctx := errgroup.WithContext(ctx)
+	g, _ := errgroup.WithContext(ctx)
 	for _, server := range s.servers {
 		g.Go(func() error {
 			s.logger.Info("starting server", "addr", server.Addr)

+ 2 - 1
server/webapi/types/events.go

@@ -238,11 +238,12 @@ func (q *EventQueue) Close() {
 	q.closed = true
 
 	// Send multiple signals to unblock all potential waiters
+notifyWaiters:
 	for i := 0; i < 10; i++ {
 		select {
 		case q.waitChan <- struct{}{}:
 		default:
-			break
+			break notifyWaiters
 		}
 	}
 }

+ 25 - 52
state/session_manager_test.go

@@ -263,15 +263,11 @@ func TestInMemorySessionManager_RelayToScreenNames(t *testing.T) {
 	}
 	sm.RelayToScreenNames(context.Background(), recips, want)
 
-	select {
-	case have := <-user1.ReceiveMessage():
-		assert.Equal(t, want, have)
-	}
+	have := <-user1.ReceiveMessage()
+	assert.Equal(t, want, have)
 
-	select {
-	case have := <-user2.ReceiveMessage():
-		assert.Equal(t, want, have)
-	}
+	have = <-user2.ReceiveMessage()
+	assert.Equal(t, want, have)
 
 	select {
 	case <-user3.ReceiveMessage():
@@ -294,15 +290,11 @@ func TestInMemorySessionManager_Broadcast(t *testing.T) {
 
 	sm.RelayToAll(context.Background(), want)
 
-	select {
-	case have := <-user1.ReceiveMessage():
-		assert.Equal(t, want, have)
-	}
+	have := <-user1.ReceiveMessage()
+	assert.Equal(t, want, have)
 
-	select {
-	case have := <-user2.ReceiveMessage():
-		assert.Equal(t, want, have)
-	}
+	have = <-user2.ReceiveMessage()
+	assert.Equal(t, want, have)
 }
 
 func TestInMemorySessionManager_Broadcast_SkipClosedSession(t *testing.T) {
@@ -320,10 +312,8 @@ func TestInMemorySessionManager_Broadcast_SkipClosedSession(t *testing.T) {
 
 	sm.RelayToAll(context.Background(), want)
 
-	select {
-	case have := <-user1.ReceiveMessage():
-		assert.Equal(t, want, have)
-	}
+	have := <-user1.ReceiveMessage()
+	assert.Equal(t, want, have)
 
 	select {
 	case <-user2.ReceiveMessage():
@@ -347,10 +337,8 @@ func TestInMemorySessionManager_RelayToScreenName_SessionExists(t *testing.T) {
 	recip := NewIdentScreenName("user-screen-name-1")
 	sm.RelayToScreenName(context.Background(), recip, want)
 
-	select {
-	case have := <-user1.ReceiveMessage():
-		assert.Equal(t, want, have)
-	}
+	have := <-user1.ReceiveMessage()
+	assert.Equal(t, want, have)
 
 	select {
 	case <-user2.ReceiveMessage():
@@ -387,10 +375,7 @@ func TestInMemorySessionManager_RelayToScreenName_SkipFullSession(t *testing.T)
 	msg := wire.SNACMessage{Frame: wire.SNACFrame{FoodGroup: wire.ICBM}}
 
 	wantCount := 0
-	for {
-		if user1.RelayMessageToInstance(msg) == SessQueueFull {
-			break
-		}
+	for user1.RelayMessageToInstance(msg) != SessQueueFull {
 		wantCount++
 	}
 
@@ -592,10 +577,8 @@ func TestInMemoryChatSessionManager_RelayToAllExcept_HappyPath(t *testing.T) {
 
 	sm.RelayToAllExcept(context.Background(), cookie, user2.IdentScreenName(), want)
 
-	select {
-	case have := <-user1.ReceiveMessage():
-		assert.Equal(t, want, have)
-	}
+	have := <-user1.ReceiveMessage()
+	assert.Equal(t, want, have)
 
 	select {
 	case <-user2.ReceiveMessage():
@@ -603,10 +586,8 @@ func TestInMemoryChatSessionManager_RelayToAllExcept_HappyPath(t *testing.T) {
 	default:
 	}
 
-	select {
-	case have := <-user3.ReceiveMessage():
-		assert.Equal(t, want, have)
-	}
+	have = <-user3.ReceiveMessage()
+	assert.Equal(t, want, have)
 }
 
 //func TestInMemoryChatSessionManager_AllSessions_RoomExists(t *testing.T) {
@@ -646,10 +627,8 @@ func TestInMemoryChatSessionManager_RelayToScreenName_SessionAndChatRoomExist(t
 	recip := NewIdentScreenName("user-screen-name-1")
 	sm.RelayToScreenName(context.Background(), "chat-room-1", recip, want)
 
-	select {
-	case have := <-user1.ReceiveMessage():
-		assert.Equal(t, want, have)
-	}
+	have := <-user1.ReceiveMessage()
+	assert.Equal(t, want, have)
 
 	select {
 	case <-user2.ReceiveMessage():
@@ -809,10 +788,8 @@ func TestInMemorySessionManager_RelayToAll_SkipIncompleteSignon(t *testing.T) {
 
 	sm.RelayToAll(context.Background(), want)
 
-	select {
-	case have := <-user1.ReceiveMessage():
-		assert.Equal(t, want, have)
-	}
+	have := <-user1.ReceiveMessage()
+	assert.Equal(t, want, have)
 
 	select {
 	case <-user2.ReceiveMessage():
@@ -873,10 +850,8 @@ func TestInMemorySessionManager_RelayToScreenNames_SkipIncompleteSignon(t *testi
 	}
 	sm.RelayToScreenNames(context.Background(), recips, want)
 
-	select {
-	case have := <-user1.ReceiveMessage():
-		assert.Equal(t, want, have)
-	}
+	have := <-user1.ReceiveMessage()
+	assert.Equal(t, want, have)
 
 	select {
 	case <-user2.ReceiveMessage():
@@ -884,10 +859,8 @@ func TestInMemorySessionManager_RelayToScreenNames_SkipIncompleteSignon(t *testi
 	default:
 	}
 
-	select {
-	case have := <-user3.ReceiveMessage():
-		assert.Equal(t, want, have)
-	}
+	have = <-user3.ReceiveMessage()
+	assert.Equal(t, want, have)
 }
 
 func TestInMemorySessionManager_AllSessions_SkipIncompleteSignon(t *testing.T) {

+ 3 - 3
state/user_store.go

@@ -211,7 +211,7 @@ func (f SQLiteUserStore) FindByICQName(ctx context.Context, firstName, lastName,
 
 	users, err := f.queryUsers(ctx, whereClause, args)
 	if err != nil {
-		err = fmt.Errorf("FindByICQName: %w", err)
+		return nil, fmt.Errorf("FindByICQName: %w", err)
 	}
 
 	return users, nil
@@ -275,7 +275,7 @@ func (f SQLiteUserStore) FindByAIMNameAndAddr(ctx context.Context, info AIMNameA
 
 	users, err := f.queryUsers(ctx, whereClause, args)
 	if err != nil {
-		err = fmt.Errorf("FindByAIMNameAndAddr: %w", err)
+		return nil, fmt.Errorf("FindByAIMNameAndAddr: %w", err)
 	}
 
 	return users, nil
@@ -337,7 +337,7 @@ func (f SQLiteUserStore) FindByICQKeyword(ctx context.Context, keyword string) (
 
 	users, err := f.queryUsers(ctx, whereClause, args)
 	if err != nil {
-		err = fmt.Errorf("FindByICQKeyword: %w", err)
+		return nil, fmt.Errorf("FindByICQKeyword: %w", err)
 	}
 
 	return users, nil

+ 2 - 2
state/user_store_test.go

@@ -828,7 +828,7 @@ func TestSQLiteUserStore_SetUserPassword_UserExists(t *testing.T) {
 	assert.NoError(t, err)
 
 	wantUser := User{}
-	wantUser.HashPassword("theNEWpassword")
+	_ = wantUser.HashPassword("theNEWpassword")
 
 	valid := gotUser.ValidateHash(wantUser.StrongMD5Pass)
 	assert.True(t, valid)
@@ -2779,7 +2779,7 @@ func TestSQLiteUserStore_BuddyIconMetadataExistingRef(t *testing.T) {
 	b, err := feedbagStore.BuddyIconMetadata(context.Background(), screenName)
 	assert.NoError(t, err)
 
-	if !reflect.DeepEqual(b.BARTInfo.Hash, testHash) {
+	if !reflect.DeepEqual(b.Hash, testHash) {
 		t.Fatalf("expected hash did not match")
 	}
 }

+ 1 - 1
state/webapi_analytics.go

@@ -186,7 +186,7 @@ func (a *APIAnalytics) flush(ctx context.Context) {
 		a.logger.Error("failed to prepare analytics insert statement", "error", err)
 		return
 	}
-	defer stmt.Close()
+	defer func() { _ = stmt.Close() }()
 
 	for _, log := range logs {
 		_, err := stmt.Exec(

+ 1 - 1
state/webapi_auth.go

@@ -55,7 +55,7 @@ func (s *WebAPITokenStore) ValidateToken(ctx context.Context, token string) (Ide
 	// Check if token has expired
 	if time.Now().After(expiresAt) {
 		// Clean up expired token
-		s.DeleteToken(ctx, token)
+		_ = s.DeleteToken(ctx, token)
 		return NewIdentScreenName(""), errors.New("token expired")
 	}
 

+ 9 - 3
state/webapi_buddyfeed.go

@@ -193,7 +193,9 @@ func (m *BuddyFeedManager) GetUserFeedItems(ctx context.Context, screenName stri
 		item.CreatedAt = time.Unix(createdAt, 0)
 
 		if categoriesJSON.Valid {
-			json.Unmarshal([]byte(categoriesJSON.String), &item.Categories)
+			if err := json.Unmarshal([]byte(categoriesJSON.String), &item.Categories); err != nil {
+				return nil, fmt.Errorf("failed to unmarshal feed item categories: %w", err)
+			}
 		}
 
 		items = append(items, item)
@@ -243,7 +245,9 @@ func (m *BuddyFeedManager) scanFeedItems(rows *sql.Rows) ([]BuddyFeedItem, error
 		item.CreatedAt = time.Unix(createdAt, 0)
 
 		if categoriesJSON.Valid {
-			json.Unmarshal([]byte(categoriesJSON.String), &item.Categories)
+			if err := json.Unmarshal([]byte(categoriesJSON.String), &item.Categories); err != nil {
+				return nil, fmt.Errorf("failed to unmarshal feed item categories: %w", err)
+			}
 		}
 
 		items = append(items, item)
@@ -281,7 +285,9 @@ func (m *BuddyFeedManager) AddFeedItem(ctx context.Context, feedID int64, item B
 
 	// Update feed's updated_at timestamp
 	updateQuery := `UPDATE buddy_feeds SET updated_at = ? WHERE id = ?`
-	m.db.ExecContext(ctx, updateQuery, now.Unix(), feedID)
+	if _, err := m.db.ExecContext(ctx, updateQuery, now.Unix(), feedID); err != nil {
+		return nil, fmt.Errorf("failed to update feed timestamp: %w", err)
+	}
 
 	return &item, nil
 }

+ 17 - 7
state/webapi_chat.go

@@ -332,11 +332,13 @@ func (m *WebAPIChatManager) SetTyping(ctx context.Context, chatsid, typingStatus
 			// Reset typing status to none
 			// Using background context here since this is an async timer callback
 			// and the original context may have expired
-			m.store.db.ExecContext(context.Background(), `
+			if _, err := m.store.db.ExecContext(context.Background(), `
 				UPDATE web_chat_participants 
 				SET typing_status = 'none', typing_updated_at = ?
 				WHERE room_id = ? AND screen_name = ?`,
-				time.Now().Unix(), session.RoomID, session.ScreenName)
+				time.Now().Unix(), session.RoomID, session.ScreenName); err != nil {
+				m.logger.Error("failed to reset typing status", "error", err)
+			}
 			// Broadcast the reset
 			m.broadcastChatEvent(session.RoomID, ChatEventData{
 				ChatSID:   chatsid,
@@ -563,9 +565,11 @@ func (m *WebAPIChatManager) getParticipants(ctx context.Context, roomID string)
 
 func (m *WebAPIChatManager) closeRoom(ctx context.Context, roomID string) {
 	now := time.Now().Unix()
-	m.store.db.ExecContext(ctx, `
+	if _, err := m.store.db.ExecContext(ctx, `
 		UPDATE web_chat_rooms SET closed_at = ? WHERE room_id = ?`,
-		now, roomID)
+		now, roomID); err != nil {
+		m.logger.Error("failed to close chat room", "roomID", roomID, "error", err)
+	}
 
 	// Remove from cache
 	delete(m.activeRooms, roomID)
@@ -693,9 +697,15 @@ func (m *WebAPIChatManager) CleanupInactiveSessions(ctx context.Context) {
 
 		// Mark as left
 		now := time.Now().Unix()
-		m.store.db.ExecContext(ctx, `UPDATE web_chat_sessions SET left_at = ? WHERE chat_sid = ?`, now, chatsid)
-		m.store.db.ExecContext(ctx, `DELETE FROM web_chat_participants WHERE room_id = ? AND screen_name = ?`,
-			roomID, screenName)
+		if _, err := m.store.db.ExecContext(ctx, `UPDATE web_chat_sessions SET left_at = ? WHERE chat_sid = ?`, now, chatsid); err != nil {
+			m.logger.Error("failed to mark inactive chat session left", "chatsid", chatsid, "error", err)
+			continue
+		}
+		if _, err := m.store.db.ExecContext(ctx, `DELETE FROM web_chat_participants WHERE room_id = ? AND screen_name = ?`,
+			roomID, screenName); err != nil {
+			m.logger.Error("failed to remove inactive chat participant", "roomID", roomID, "screenName", screenName, "error", err)
+			continue
+		}
 
 		// Broadcast user left
 		// Note: Broadcasting doesn't need context as it's fire-and-forget

+ 1 - 1
state/webapi_oscar_bridge.go

@@ -236,7 +236,7 @@ func (s *OSCARBridgeStore) CleanupExpiredSessions(ctx context.Context, maxAge ti
 // touchSession updates the last accessed time for a session (internal helper).
 func (s *OSCARBridgeStore) touchSession(ctx context.Context, webSessionID string) {
 	query := `UPDATE oscar_bridge_sessions SET last_accessed = ? WHERE web_session_id = ?`
-	s.store.db.ExecContext(ctx, query, time.Now(), webSessionID)
+	_, _ = s.store.db.ExecContext(ctx, query, time.Now(), webSessionID)
 }
 
 // GetAllBridgeSessions returns all active bridge sessions (for monitoring/admin).

+ 1 - 1
state/webapi_session.go

@@ -129,7 +129,7 @@ func (s *WebAPISession) handleIncomingIM(msg wire.SNACMessage) {
 
 	// Extract message text from TLV data
 	var messageText string
-	if msgData, hasMsg := body.TLVRestBlock.Bytes(wire.ICBMTLVAOLIMData); hasMsg {
+	if msgData, hasMsg := body.Bytes(wire.ICBMTLVAOLIMData); hasMsg {
 		if text, err := wire.UnmarshalICBMMessageText(msgData); err == nil {
 			messageText = text
 		}

+ 3 - 6
wire/decode.go

@@ -10,10 +10,7 @@ import (
 	"strings"
 )
 
-var (
-	ErrUnmarshalFailure  = errors.New("failed to unmarshal")
-	errNotNullTerminated = errors.New("nullterm tag is set, but string is not null-terminated")
-)
+var ErrUnmarshalFailure = errors.New("failed to unmarshal")
 
 // UnmarshalBE unmarshalls OSCAR protocol messages in big-endian format.
 func UnmarshalBE(v any, r io.Reader) error {
@@ -50,7 +47,7 @@ func unmarshal(t reflect.Type, v reflect.Value, tag reflect.StructTag, r io.Read
 			err = nil
 		}
 		return err
-	} else if v.Kind() == reflect.Ptr {
+	} else if v.Kind() == reflect.Pointer {
 		return errNonOptionalPointer
 	}
 
@@ -270,7 +267,7 @@ func unmarshalStruct(t reflect.Type, v reflect.Value, oscTag oscarTag, r io.Read
 	for i := 0; i < v.NumField(); i++ {
 		field := t.Field(i)
 		value := v.Field(i)
-		if field.Type.Kind() == reflect.Ptr {
+		if field.Type.Kind() == reflect.Pointer {
 			if i != v.NumField()-1 {
 				return fmt.Errorf("pointer type found at non-final field %s", field.Name)
 			}

+ 3 - 3
wire/encode.go

@@ -45,7 +45,7 @@ func marshal(t reflect.Type, v reflect.Value, tag reflect.StructTag, w io.Writer
 	}
 
 	if oscTag.optional {
-		if t.Kind() != reflect.Ptr {
+		if t.Kind() != reflect.Pointer {
 			return fmt.Errorf("%w: got %v", errOptionalNonPointer, t.Kind())
 		}
 		if v.IsNil() {
@@ -53,7 +53,7 @@ func marshal(t reflect.Type, v reflect.Value, tag reflect.StructTag, w io.Writer
 		}
 		// dereference pointer
 		return marshalStruct(t.Elem(), v.Elem(), oscTag, w, order)
-	} else if t.Kind() == reflect.Ptr {
+	} else if t.Kind() == reflect.Pointer {
 		return errNonOptionalPointer
 	}
 
@@ -156,7 +156,7 @@ func marshalStruct(t reflect.Type, v reflect.Value, oscTag oscarTag, w io.Writer
 		for i := 0; i < t.NumField(); i++ {
 			field := t.Field(i)
 			value := v.Field(i)
-			if field.Type.Kind() == reflect.Ptr {
+			if field.Type.Kind() == reflect.Pointer {
 				if i != t.NumField()-1 {
 					return fmt.Errorf("pointer type found at non-final field %s", field.Name)
 				}

+ 3 - 3
wire/snacs.go

@@ -2769,11 +2769,11 @@ func (f *FeedbagItem) IsEqual(other FeedbagItem) bool {
 	if f.Name != other.Name || f.GroupID != other.GroupID || f.ClassID != other.ClassID {
 		return false
 	}
-	if len(f.TLVLBlock.TLVList) != len(other.TLVLBlock.TLVList) {
+	if len(f.TLVList) != len(other.TLVList) {
 		return false
 	}
-	for i, tlv := range f.TLVLBlock.TLVList {
-		o := other.TLVLBlock.TLVList[i]
+	for i, tlv := range f.TLVList {
+		o := other.TLVList[i]
 		if tlv.Tag != o.Tag || !bytes.Equal(tlv.Value, o.Value) {
 			return false
 		}

+ 7 - 7
wire/user.go

@@ -10,9 +10,9 @@ import (
 //goland:noinspection ALL
 func WeakMD5PasswordHash(pass, authKey string) []byte {
 	hash := md5.New()
-	io.WriteString(hash, authKey)
-	io.WriteString(hash, pass)
-	io.WriteString(hash, "AOL Instant Messenger (SM)")
+	_, _ = io.WriteString(hash, authKey)
+	_, _ = io.WriteString(hash, pass)
+	_, _ = io.WriteString(hash, "AOL Instant Messenger (SM)")
 	return hash.Sum(nil)
 }
 
@@ -21,11 +21,11 @@ func WeakMD5PasswordHash(pass, authKey string) []byte {
 //goland:noinspection ALL
 func StrongMD5PasswordHash(pass, authKey string) []byte {
 	top := md5.New()
-	io.WriteString(top, pass)
+	_, _ = io.WriteString(top, pass)
 	bottom := md5.New()
-	io.WriteString(bottom, authKey)
-	bottom.Write(top.Sum(nil))
-	io.WriteString(bottom, "AOL Instant Messenger (SM)")
+	_, _ = io.WriteString(bottom, authKey)
+	_, _ = bottom.Write(top.Sum(nil))
+	_, _ = io.WriteString(bottom, "AOL Instant Messenger (SM)")
 	return bottom.Sum(nil)
 }