| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545 |
- package icq_legacy
- import (
- "context"
- "fmt"
- "log/slog"
- "net"
- "github.com/mk6i/open-oscar-server/config"
- "github.com/mk6i/open-oscar-server/state"
- "github.com/mk6i/open-oscar-server/wire"
- )
- // ProtocolDispatcher routes packets to the appropriate version handler
- type ProtocolDispatcher struct {
- v1Handler *V1Handler
- v2Handler *V2Handler
- v3Handler *V3Handler
- v4Handler *V4Handler
- v5Handler *V5Handler
- config config.ICQLegacyConfig
- logger *slog.Logger
- }
- // NewProtocolDispatcher creates a new protocol dispatcher
- func NewProtocolDispatcher(
- v1Handler *V1Handler,
- v2Handler *V2Handler,
- v3Handler *V3Handler,
- v4Handler *V4Handler,
- v5Handler *V5Handler,
- cfg config.ICQLegacyConfig,
- logger *slog.Logger,
- ) *ProtocolDispatcher {
- return &ProtocolDispatcher{
- v1Handler: v1Handler,
- v2Handler: v2Handler,
- v3Handler: v3Handler,
- v4Handler: v4Handler,
- v5Handler: v5Handler,
- config: cfg,
- logger: logger,
- }
- }
- // Dispatch routes a packet to the appropriate handler based on protocol version
- func (d *ProtocolDispatcher) Dispatch(session *LegacySession, addr *net.UDPAddr, packet []byte) error {
- version, err := DetectProtocolVersion(packet)
- if err != nil {
- return fmt.Errorf("detecting protocol version: %w", err)
- }
- // Check if version is supported
- if !d.config.SupportsVersion(int(version)) {
- return fmt.Errorf("unsupported protocol version: %d", version)
- }
- d.logger.Debug("dispatching packet",
- "version", version,
- "addr", addr.String(),
- "size", len(packet),
- )
- switch version {
- case ICQLegacyVersionV1:
- return d.v1Handler.Handle(session, addr, packet)
- case ICQLegacyVersionV2:
- return d.v2Handler.Handle(session, addr, packet)
- case ICQLegacyVersionV3:
- return d.v3Handler.Handle(session, addr, packet)
- case ICQLegacyVersionV4:
- return d.v4Handler.Handle(session, addr, packet)
- case ICQLegacyVersionV5:
- return d.v5Handler.Handle(session, addr, packet)
- default:
- return fmt.Errorf("unknown protocol version: %d", version)
- }
- }
- // SendUserOnline sends a user online notification to a session
- // This is the central dispatcher that routes to the appropriate protocol handler
- // Following iserverd's architecture in handle.cpp send_user_online()
- func (d *ProtocolDispatcher) SendUserOnline(toSession *LegacySession, onlineUIN uint32, status uint32) error {
- if toSession == nil {
- return nil
- }
- d.logger.Debug("dispatching user online notification",
- "to_uin", toSession.UIN,
- "to_version", toSession.Version,
- "online_uin", onlineUIN,
- "status", fmt.Sprintf("0x%08X", status),
- )
- switch toSession.Version {
- case ICQLegacyVersionV1:
- return d.v1Handler.sendUserOnline(toSession, onlineUIN, downgradeStatusForV2(status), nil, 0)
- case ICQLegacyVersionV2:
- return d.v2Handler.sendUserOnline(toSession, onlineUIN, downgradeStatusForV2(status), nil, 0)
- case ICQLegacyVersionV3:
- return d.v3Handler.sendUserOnline(toSession, onlineUIN, status)
- case ICQLegacyVersionV4:
- return d.v4Handler.sendUserOnline(toSession, onlineUIN, status)
- case ICQLegacyVersionV5:
- return d.v5Handler.sendV5UserOnline(toSession, onlineUIN, status)
- default:
- return nil
- }
- }
- // SendOnlineMessage sends an online message to a session
- // This is the central dispatcher that routes to the appropriate protocol handler
- // Following iserverd's architecture in handle.cpp send_online_message()
- func (d *ProtocolDispatcher) SendOnlineMessage(toSession *LegacySession, fromUIN uint32, msgType uint16, message string) error {
- if toSession == nil {
- return nil
- }
- d.logger.Debug("dispatching online message",
- "to_uin", toSession.UIN,
- "to_version", toSession.Version,
- "from_uin", fromUIN,
- "msg_type", fmt.Sprintf("0x%04X", msgType),
- )
- switch toSession.Version {
- case ICQLegacyVersionV1:
- return d.v1Handler.sendMessage(toSession, fromUIN, msgType, message)
- case ICQLegacyVersionV2:
- return d.v2Handler.sendMessage(toSession, fromUIN, msgType, message)
- case ICQLegacyVersionV3:
- return d.v3Handler.sendOnlineMessage(toSession, fromUIN, msgType, message, 0)
- case ICQLegacyVersionV4:
- return d.v4Handler.sendOnlineMessage(toSession, fromUIN, msgType, message, 0)
- case ICQLegacyVersionV5:
- return d.v5Handler.sendOnlineMessage(toSession, fromUIN, msgType, message)
- default:
- return nil
- }
- }
- // SendUserOffline sends a user offline notification to a session
- // This is the central dispatcher that routes to the appropriate protocol handler
- // Following iserverd's architecture in handle.cpp send_user_offline()
- func (d *ProtocolDispatcher) SendUserOffline(toSession *LegacySession, offlineUIN uint32) error {
- if toSession == nil {
- return nil
- }
- d.logger.Debug("dispatching user offline notification",
- "to_uin", toSession.UIN,
- "to_version", toSession.Version,
- "offline_uin", offlineUIN,
- )
- switch toSession.Version {
- case ICQLegacyVersionV1:
- return d.v1Handler.sendUserOffline(toSession, offlineUIN)
- case ICQLegacyVersionV2:
- return d.v2Handler.sendUserOffline(toSession, offlineUIN)
- case ICQLegacyVersionV3:
- return d.v3Handler.sendUserOffline(toSession, offlineUIN)
- case ICQLegacyVersionV4:
- return d.v4Handler.sendUserOffline(toSession, offlineUIN)
- case ICQLegacyVersionV5:
- return d.v5Handler.sendV5UserOffline(toSession, offlineUIN)
- default:
- return nil
- }
- }
- // SendStatusChange sends a status change notification to a session
- // This is the central dispatcher that routes to the appropriate protocol handler
- // Following iserverd's architecture in handle.cpp send_user_status()
- // This is different from SendUserOnline - it's used when a user changes status
- // while already online (e.g., from Away to Online, or Online to DND)
- func (d *ProtocolDispatcher) SendStatusChange(toSession *LegacySession, changedUIN uint32, newStatus uint32) error {
- if toSession == nil {
- return nil
- }
- d.logger.Debug("dispatching status change notification",
- "to_uin", toSession.UIN,
- "to_version", toSession.Version,
- "changed_uin", changedUIN,
- "new_status", fmt.Sprintf("0x%08X", newStatus),
- )
- switch toSession.Version {
- case ICQLegacyVersionV1:
- return d.v1Handler.sendStatusUpdate(toSession, changedUIN, downgradeStatusForV2(newStatus))
- case ICQLegacyVersionV2:
- return d.v2Handler.sendStatusUpdate(toSession, changedUIN, downgradeStatusForV2(newStatus))
- case ICQLegacyVersionV3:
- return d.v3Handler.sendUserStatus(toSession, changedUIN, newStatus)
- case ICQLegacyVersionV4:
- return d.v4Handler.sendUserStatus(toSession, changedUIN, newStatus)
- case ICQLegacyVersionV5:
- return d.v5Handler.sendV5UserStatus(toSession, changedUIN, newStatus)
- default:
- return nil
- }
- }
- // downgradeStatusForV2 maps advanced legacy statuses (N/A, Occupied, FFC)
- // to the subset that V2 clients display for remote contacts.
- //
- // The real ICQ V2 client uses combined status bits just like V5:
- //
- // DND = 0x11 (Away|Occupied), not 0x02
- //
- // So we map to the combined values the V2 client actually understands.
- //
- // DND (0x02) -> 0x11 — V2 uses 0x11 for DND, not 0x02
- // N/A (0x04) -> Away (0x01) — extended away maps to away
- // N/A (0x05) -> Away (0x01) — Away|N/A maps to away
- // Occupied (0x10) -> 0x11 — V2 uses 0x11 for DND (closest busy state)
- // Occupied (0x11) -> 0x11 — already correct
- // DND (0x13) -> 0x11 — V2 uses 0x11 for DND
- // FFC (0x20) -> Online (0x00) — free-for-chat maps to online
- //
- // Flags in the upper word (invisible, web-aware, etc.) are preserved.
- func downgradeStatusForV2(status uint32) uint32 {
- base := status & 0xFF
- flags := status & 0xFFFFFF00
- switch base {
- case 0x02: // DND (pure)
- base = 0x11
- case 0x04, 0x05: // N/A, Away|N/A
- base = 0x01 // Away
- case 0x10: // Occupied (pure)
- base = 0x11
- case 0x13: // Away|DND|Occupied
- base = 0x11
- case 0x20: // FFC
- base = 0x00 // Online
- // 0x01 (Away) and 0x11 (Occupied/DND) pass through unchanged
- }
- return flags | base
- }
- // PacketSender is the interface for sending packets
- type PacketSender interface {
- SendPacket(addr *net.UDPAddr, packet []byte) error
- SendToSession(session *LegacySession, packet []byte) error
- }
- // MessageDispatcher is the interface for cross-protocol message dispatching
- // This follows iserverd's architecture where a central dispatcher routes
- // messages to the appropriate protocol handler based on the target's version
- // From iserverd handle.cpp: send_user_online(), send_user_offline(),
- // send_user_status(), send_online_message()
- type MessageDispatcher interface {
- // SendUserOnline notifies a session that a user has come online
- // From iserverd send_user_online() in handle.cpp
- SendUserOnline(toSession *LegacySession, onlineUIN uint32, status uint32) error
- // SendUserOffline notifies a session that a user has gone offline
- // From iserverd send_user_offline() in handle.cpp
- SendUserOffline(toSession *LegacySession, offlineUIN uint32) error
- // SendStatusChange notifies a session that a user has changed their status
- // From iserverd send_user_status() in handle.cpp
- // This is different from SendUserOnline - it's used when a user changes
- // status while already online (e.g., Away -> Online, Online -> DND)
- SendStatusChange(toSession *LegacySession, changedUIN uint32, newStatus uint32) error
- // SendOnlineMessage sends an instant message to a session
- // From iserverd send_online_message() in handle.cpp
- SendOnlineMessage(toSession *LegacySession, fromUIN uint32, msgType uint16, message string) error
- }
- // BaseHandler contains common functionality shared by all protocol version handlers.
- // It provides V2-format helper methods for sending packets that are used as a
- // fallback by the simpler protocol versions.
- type BaseHandler struct {
- sessions *LegacySessionManager
- service LegacyService
- sender PacketSender
- logger *slog.Logger
- }
- // AuthService provides OSCAR authentication and BOS session registration.
- type AuthService interface {
- FLAPLogin(ctx context.Context, inFrame wire.FLAPSignonFrame, advertisedHost string) (wire.TLVRestBlock, error)
- CrackCookie(authCookie []byte) (state.ServerCookie, error)
- RegisterBOSSession(ctx context.Context, authCookie state.ServerCookie, cfg func(*state.Session)) (*state.SessionInstance, error)
- }
- // LegacyService is the interface for the ICQ legacy service layer.
- // It defines all business logic operations that protocol handlers delegate to,
- // keeping handlers thin and protocol-independent logic centralized.
- type LegacyService interface {
- // ValidateCredentials checks if the given UIN and password are valid.
- // Returns true if credentials are valid, false otherwise.
- ValidateCredentials(ctx context.Context, uin uint32, password string) (bool, error)
- // AuthenticateUser validates user credentials and returns authentication result.
- // This is the service layer method for authentication that handlers call after
- // parsing login packets. It validates credentials and returns a typed result
- // struct containing success/failure and session data.
- // The method does NOT contain any protocol-specific packet building logic.
- // Handlers are responsible for building protocol-specific responses based on
- // the returned AuthResult.
- AuthenticateUser(ctx context.Context, req AuthRequest) (*AuthResult, error)
- // RegisterNewUser creates a new user account for legacy ICQ registration.
- // Returns the newly assigned UIN on success.
- RegisterNewUser(ctx context.Context, nickname, firstName, lastName, email, password string) (uint32, error)
- // GetOfflineMessages retrieves stored offline messages for the given UIN.
- GetOfflineMessages(ctx context.Context, uin uint32) ([]LegacyOfflineMessage, error)
- // AckOfflineMessages acknowledges and deletes offline messages for the given UIN.
- AckOfflineMessages(ctx context.Context, uin uint32) error
- // ProcessMessage handles message routing and offline storage.
- // This is the service layer method for messaging that handlers call after
- // parsing message packets. It determines if the target user is online and
- // returns routing information, or stores the message for offline delivery.
- // The method does NOT contain any protocol-specific packet building logic.
- // Handlers are responsible for building protocol-specific responses based on
- // the returned MessageResult.
- ProcessMessage(ctx context.Context, session *LegacySession, req MessageRequest) (*MessageResult, error)
- // ProcessContactList processes a contact list and returns online status for each contact.
- // This is the service layer method for contact list processing that handlers call after
- // parsing contact list packets. It checks the online status of each contact and returns
- // a ContactListResult containing the status of each contact.
- // The method does NOT contain any protocol-specific packet building logic.
- // Handlers are responsible for building protocol-specific responses based on
- // the returned ContactListResult.
- ProcessContactList(ctx context.Context, instance *state.SessionInstance, req ContactListRequest) (*ContactListResult, error)
- // ProcessUserAdd processes a user add request and returns information about the target user.
- // This is the service layer method for user add operations that handlers call after
- // parsing user add packets (CMD_USER_ADD). It checks if the target user is online
- // and returns their status, along with whether to send a "you were added" notification.
- // The method does NOT contain any protocol-specific packet building logic.
- // Handlers are responsible for building protocol-specific responses based on
- // the returned UserAddResult.
- ProcessUserAdd(ctx context.Context, instance *state.SessionInstance, req UserAddRequest) (*UserAddResult, error)
- // ProcessStatusChange processes a status change and returns notification targets.
- // This is the service layer method for status changes that handlers call after
- // parsing status change packets. It determines which users should be notified
- // of the status change (users who have this user in their contact list).
- // The method does NOT contain any protocol-specific packet building logic.
- // The method does NOT directly send packets to other sessions.
- // Handlers are responsible for building protocol-specific responses based on
- // the returned StatusChangeResult.
- ProcessStatusChange(ctx context.Context, req StatusChangeRequest) (*StatusChangeResult, error)
- // GetUserInfo retrieves basic user information as a LegacyUserSearchResult.
- GetUserInfo(ctx context.Context, uin uint32) (*LegacyUserSearchResult, error)
- // GetFullUserInfo returns the complete user record including all ICQ info fields.
- // This is used by V3 info packets that need home, work, and more info fields.
- // From iserverd v3_send_home_info(), v3_send_work_info(), etc.
- GetFullUserInfo(ctx context.Context, uin uint32) (*state.User, error)
- // GetUserInfoForProtocol retrieves user info and returns it as a typed UserInfoResult.
- // This is the service layer method for user info retrieval that handlers call after
- // parsing info request packets. It consolidates user info retrieval from the database
- // and returns a typed result struct containing all user profile fields.
- // The method does NOT contain any protocol-specific packet building logic.
- GetUserInfoForProtocol(ctx context.Context, targetUIN uint32) (*UserInfoResult, error)
- // SearchByUIN searches for a user by their UIN and returns their profile info.
- SearchByUIN(ctx context.Context, uin uint32) (*LegacyUserSearchResult, error)
- // SearchByName searches for users by nickname, first name, last name, or email.
- SearchByName(ctx context.Context, nick, first, last, email string) ([]LegacyUserSearchResult, error)
- // ChangeStatus updates a user's status in the service layer.
- ChangeStatus(ctx context.Context, uin uint32, status uint32) error
- // NotifyStatusChange broadcasts a status change to OSCAR clients who have
- // this user as a buddy.
- NotifyStatusChange(ctx context.Context, uin uint32, status uint32) error
- // NotifyUserOffline broadcasts a user departure to OSCAR clients.
- NotifyUserOffline(ctx context.Context, uin uint32) error
- // NotifyUserOnline broadcasts a user arrival to OSCAR clients after legacy login.
- NotifyUserOnline(ctx context.Context, uin uint32, status uint32) error
- // User Management
- // DeleteUser removes a user account from the system.
- // This is used by the V5 META_USER_UNREGISTER (0x04C4) command.
- // The password must match the user's current password for the deletion to succeed.
- // Returns nil on success, or an error if the user doesn't exist or password is wrong.
- DeleteUser(ctx context.Context, uin uint32, password string) error
- // White Pages Search
- // WhitePagesSearch performs a comprehensive search across multiple user profile fields.
- // This is used by the V5 META_SEARCH_WHITE (0x0532) and META_SEARCH_WHITE2 (0x0533) commands.
- // From iserverd v5_search_by_white() and v5_search_by_white2() in search.cpp
- // Returns matching users up to a maximum of 40 results.
- WhitePagesSearch(ctx context.Context, criteria WhitePagesSearchCriteria) ([]LegacyUserSearchResult, error)
- // Notes Operations
- // GetNotes retrieves the user's notes from the database.
- // This is used by the V3 GET_NOTES (0x05AA) command.
- // From iserverd v3_process_notes() - returns user's notes.
- GetNotes(ctx context.Context, uin uint32) (string, error)
- // SetNotes saves the user's notes to the database.
- // This is used by the V3 SET_NOTES (0x0596) command.
- // From iserverd v3_process_setnotes() - updates user's notes.
- SetNotes(ctx context.Context, uin uint32, notes string) error
- // Password Operations
- // SetPassword changes the user's password after validating the old password.
- // This is used by the V3 SET_PASSWORD (0x049C) command.
- // From iserverd v3_process_setpass() - updates user's password.
- // Note: The iserverd implementation doesn't validate old password, but we add
- // this validation for security. The oldPassword parameter can be empty to skip
- // validation (matching iserverd behavior).
- SetPassword(ctx context.Context, uin uint32, oldPassword, newPassword string) error
- // Auth Mode Operations
- // SetAuthMode sets whether authorization is required to add the user to a contact list.
- // This is used by the V3 SET_AUTH (0x0514) command.
- // From iserverd v3_process_setauth() - updates user's auth mode.
- // When authRequired is true, other users must request authorization before adding
- // this user to their contact list.
- SetAuthMode(ctx context.Context, uin uint32, authRequired bool) error
- // Interests Operations
- // GetInterests retrieves the user's interests from the database.
- // This is used by the V5 META_USER_FULLINFO response to return user interests.
- // From iserverd v5_send_meta_interestsinfo() - returns user's interests.
- GetInterests(ctx context.Context, uin uint32) (*state.ICQInterests, error)
- // SetInterests saves the user's interests to the database.
- // This is used by the V5 META_SET_INTERESTS (0x0410) command.
- // From iserverd v5_set_interests_info() - updates user's interests.
- SetInterests(ctx context.Context, uin uint32, interests state.ICQInterests) error
- // Affiliations Operations
- // GetAffiliations retrieves the user's affiliations from the database.
- // This is used by the V5 META_USER_FULLINFO response to return user affiliations.
- // From iserverd v5_send_meta_affilationsinfo() - returns user's past and current affiliations.
- GetAffiliations(ctx context.Context, uin uint32) (*state.ICQAffiliations, error)
- // SetAffiliations saves the user's affiliations to the database.
- // This is used by the V5 META_SET_AFFILIATIONS (0x041A) command.
- // From iserverd v5_set_affilations_info() - updates user's past and current affiliations.
- SetAffiliations(ctx context.Context, uin uint32, affiliations state.ICQAffiliations) error
- // Homepage Category Operations
- // GetHomepageCategory retrieves the user's homepage category from the database.
- // This is used by the V5 META_USER_FULLINFO response to return user homepage category.
- // From iserverd v5_send_meta_hpage_cat() - returns user's homepage category.
- GetHomepageCategory(ctx context.Context, uin uint32) (*state.ICQHomepageCategory, error)
- // SetHomepageCategory saves the user's homepage category to the database.
- // This is used by the V5 META_SET_HPCAT (0x0442) command.
- // From iserverd v5_set_hpcat_info() - updates user's homepage category.
- SetHomepageCategory(ctx context.Context, uin uint32, hpcat state.ICQHomepageCategory) error
- // Profile Update Operations
- // UpdateBasicInfo updates a user's basic profile information.
- UpdateBasicInfo(ctx context.Context, uin uint32, info state.ICQBasicInfo) error
- // UpdateWorkInfo updates a user's work information.
- UpdateWorkInfo(ctx context.Context, uin uint32, info state.ICQWorkInfo) error
- // UpdateMoreInfo updates a user's additional profile information.
- UpdateMoreInfo(ctx context.Context, uin uint32, info state.ICQMoreInfo) error
- // UpdatePermissions updates a user's permission settings.
- UpdatePermissions(ctx context.Context, uin uint32, info state.ICQPermissions) error
- }
- // sendAck sends an acknowledgment packet to the session using V2 packet format.
- func (h *BaseHandler) sendAck(session *LegacySession, seqNum uint16) error {
- pkt := BuildV2Ack(seqNum)
- pkt.Version = session.Version
- return h.sender.SendToSession(session, MarshalV2ServerPacket(pkt))
- }
- // sendBadPassword sends a bad password response
- func (h *BaseHandler) sendBadPassword(addr *net.UDPAddr, seqNum uint16, version uint16) error {
- pkt := BuildV2BadPassword(seqNum)
- pkt.Version = version
- return h.sender.SendPacket(addr, MarshalV2ServerPacket(pkt))
- }
- // sendUserOnline sends a user online notification
- func (h *BaseHandler) sendUserOnline(session *LegacySession, uin uint32, status uint32, ip net.IP, port uint16) error {
- pkt := BuildV2UserOnline(session.NextServerSeqNum(), uin, status, ip, port)
- pkt.Version = session.Version
- return h.sender.SendToSession(session, MarshalV2ServerPacket(pkt))
- }
- // sendUserOffline sends a user offline notification
- func (h *BaseHandler) sendUserOffline(session *LegacySession, uin uint32) error {
- pkt := BuildV2UserOffline(session.NextServerSeqNum(), uin)
- pkt.Version = session.Version
- return h.sender.SendToSession(session, MarshalV2ServerPacket(pkt))
- }
- // sendStatusUpdate sends a status update notification
- func (h *BaseHandler) sendStatusUpdate(session *LegacySession, uin uint32, status uint32) error {
- pkt := BuildV2StatusUpdate(session.NextServerSeqNum(), uin, status)
- pkt.Version = session.Version
- return h.sender.SendToSession(session, MarshalV2ServerPacket(pkt))
- }
- // sendMessage sends a message to a session
- func (h *BaseHandler) sendMessage(session *LegacySession, fromUIN uint32, msgType uint16, message string) error {
- pkt := BuildV2Message(session.NextServerSeqNum(), fromUIN, msgType, message)
- pkt.Version = session.Version
- return h.sender.SendToSession(session, MarshalV2ServerPacket(pkt))
- }
- // sendSearchResult sends a search result
- func (h *BaseHandler) sendSearchResult(session *LegacySession, user *LegacyUserSearchResult, isLast bool, clientSubSeq uint16) error {
- info := &LegacyUserInfo{
- UIN: user.UIN,
- Nickname: truncateField(user.Nickname, 20, h.logger, "nickname", user.UIN),
- FirstName: truncateField(user.FirstName, 64, h.logger, "first_name", user.UIN),
- LastName: truncateField(user.LastName, 64, h.logger, "last_name", user.UIN),
- Email: truncateField(user.Email, 64, h.logger, "email", user.UIN),
- Auth: user.AuthRequired,
- }
- if user.UIN != 0 {
- // Send search found with user data — subseq must echo client's sub-sequence
- pkt := BuildV2SearchResult(clientSubSeq, info, false)
- pkt.SeqNum = session.NextServerSeqNum()
- pkt.Version = session.Version
- if err := h.sender.SendToSession(session, MarshalV2ServerPacket(pkt)); err != nil {
- return err
- }
- }
- if isLast {
- // Send search done with its own server seq — subseq must echo client's sub-sequence
- pkt := BuildV2SearchResult(clientSubSeq, &LegacyUserInfo{}, true)
- pkt.SeqNum = session.NextServerSeqNum()
- pkt.Version = session.Version
- return h.sender.SendToSession(session, MarshalV2ServerPacket(pkt))
- }
- return nil
- }
|