handler.go 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545
  1. package icq_legacy
  2. import (
  3. "context"
  4. "fmt"
  5. "log/slog"
  6. "net"
  7. "github.com/mk6i/open-oscar-server/config"
  8. "github.com/mk6i/open-oscar-server/state"
  9. "github.com/mk6i/open-oscar-server/wire"
  10. )
  11. // ProtocolDispatcher routes packets to the appropriate version handler
  12. type ProtocolDispatcher struct {
  13. v1Handler *V1Handler
  14. v2Handler *V2Handler
  15. v3Handler *V3Handler
  16. v4Handler *V4Handler
  17. v5Handler *V5Handler
  18. config config.ICQLegacyConfig
  19. logger *slog.Logger
  20. }
  21. // NewProtocolDispatcher creates a new protocol dispatcher
  22. func NewProtocolDispatcher(
  23. v1Handler *V1Handler,
  24. v2Handler *V2Handler,
  25. v3Handler *V3Handler,
  26. v4Handler *V4Handler,
  27. v5Handler *V5Handler,
  28. cfg config.ICQLegacyConfig,
  29. logger *slog.Logger,
  30. ) *ProtocolDispatcher {
  31. return &ProtocolDispatcher{
  32. v1Handler: v1Handler,
  33. v2Handler: v2Handler,
  34. v3Handler: v3Handler,
  35. v4Handler: v4Handler,
  36. v5Handler: v5Handler,
  37. config: cfg,
  38. logger: logger,
  39. }
  40. }
  41. // Dispatch routes a packet to the appropriate handler based on protocol version
  42. func (d *ProtocolDispatcher) Dispatch(session *LegacySession, addr *net.UDPAddr, packet []byte) error {
  43. version, err := DetectProtocolVersion(packet)
  44. if err != nil {
  45. return fmt.Errorf("detecting protocol version: %w", err)
  46. }
  47. // Check if version is supported
  48. if !d.config.SupportsVersion(int(version)) {
  49. return fmt.Errorf("unsupported protocol version: %d", version)
  50. }
  51. d.logger.Debug("dispatching packet",
  52. "version", version,
  53. "addr", addr.String(),
  54. "size", len(packet),
  55. )
  56. switch version {
  57. case ICQLegacyVersionV1:
  58. return d.v1Handler.Handle(session, addr, packet)
  59. case ICQLegacyVersionV2:
  60. return d.v2Handler.Handle(session, addr, packet)
  61. case ICQLegacyVersionV3:
  62. return d.v3Handler.Handle(session, addr, packet)
  63. case ICQLegacyVersionV4:
  64. return d.v4Handler.Handle(session, addr, packet)
  65. case ICQLegacyVersionV5:
  66. return d.v5Handler.Handle(session, addr, packet)
  67. default:
  68. return fmt.Errorf("unknown protocol version: %d", version)
  69. }
  70. }
  71. // SendUserOnline sends a user online notification to a session
  72. // This is the central dispatcher that routes to the appropriate protocol handler
  73. // Following iserverd's architecture in handle.cpp send_user_online()
  74. func (d *ProtocolDispatcher) SendUserOnline(toSession *LegacySession, onlineUIN uint32, status uint32) error {
  75. if toSession == nil {
  76. return nil
  77. }
  78. d.logger.Debug("dispatching user online notification",
  79. "to_uin", toSession.UIN,
  80. "to_version", toSession.Version,
  81. "online_uin", onlineUIN,
  82. "status", fmt.Sprintf("0x%08X", status),
  83. )
  84. switch toSession.Version {
  85. case ICQLegacyVersionV1:
  86. return d.v1Handler.sendUserOnline(toSession, onlineUIN, downgradeStatusForV2(status), nil, 0)
  87. case ICQLegacyVersionV2:
  88. return d.v2Handler.sendUserOnline(toSession, onlineUIN, downgradeStatusForV2(status), nil, 0)
  89. case ICQLegacyVersionV3:
  90. return d.v3Handler.sendUserOnline(toSession, onlineUIN, status)
  91. case ICQLegacyVersionV4:
  92. return d.v4Handler.sendUserOnline(toSession, onlineUIN, status)
  93. case ICQLegacyVersionV5:
  94. return d.v5Handler.sendV5UserOnline(toSession, onlineUIN, status)
  95. default:
  96. return nil
  97. }
  98. }
  99. // SendOnlineMessage sends an online message to a session
  100. // This is the central dispatcher that routes to the appropriate protocol handler
  101. // Following iserverd's architecture in handle.cpp send_online_message()
  102. func (d *ProtocolDispatcher) SendOnlineMessage(toSession *LegacySession, fromUIN uint32, msgType uint16, message string) error {
  103. if toSession == nil {
  104. return nil
  105. }
  106. d.logger.Debug("dispatching online message",
  107. "to_uin", toSession.UIN,
  108. "to_version", toSession.Version,
  109. "from_uin", fromUIN,
  110. "msg_type", fmt.Sprintf("0x%04X", msgType),
  111. )
  112. switch toSession.Version {
  113. case ICQLegacyVersionV1:
  114. return d.v1Handler.sendMessage(toSession, fromUIN, msgType, message)
  115. case ICQLegacyVersionV2:
  116. return d.v2Handler.sendMessage(toSession, fromUIN, msgType, message)
  117. case ICQLegacyVersionV3:
  118. return d.v3Handler.sendOnlineMessage(toSession, fromUIN, msgType, message, 0)
  119. case ICQLegacyVersionV4:
  120. return d.v4Handler.sendOnlineMessage(toSession, fromUIN, msgType, message, 0)
  121. case ICQLegacyVersionV5:
  122. return d.v5Handler.sendOnlineMessage(toSession, fromUIN, msgType, message)
  123. default:
  124. return nil
  125. }
  126. }
  127. // SendUserOffline sends a user offline notification to a session
  128. // This is the central dispatcher that routes to the appropriate protocol handler
  129. // Following iserverd's architecture in handle.cpp send_user_offline()
  130. func (d *ProtocolDispatcher) SendUserOffline(toSession *LegacySession, offlineUIN uint32) error {
  131. if toSession == nil {
  132. return nil
  133. }
  134. d.logger.Debug("dispatching user offline notification",
  135. "to_uin", toSession.UIN,
  136. "to_version", toSession.Version,
  137. "offline_uin", offlineUIN,
  138. )
  139. switch toSession.Version {
  140. case ICQLegacyVersionV1:
  141. return d.v1Handler.sendUserOffline(toSession, offlineUIN)
  142. case ICQLegacyVersionV2:
  143. return d.v2Handler.sendUserOffline(toSession, offlineUIN)
  144. case ICQLegacyVersionV3:
  145. return d.v3Handler.sendUserOffline(toSession, offlineUIN)
  146. case ICQLegacyVersionV4:
  147. return d.v4Handler.sendUserOffline(toSession, offlineUIN)
  148. case ICQLegacyVersionV5:
  149. return d.v5Handler.sendV5UserOffline(toSession, offlineUIN)
  150. default:
  151. return nil
  152. }
  153. }
  154. // SendStatusChange sends a status change notification to a session
  155. // This is the central dispatcher that routes to the appropriate protocol handler
  156. // Following iserverd's architecture in handle.cpp send_user_status()
  157. // This is different from SendUserOnline - it's used when a user changes status
  158. // while already online (e.g., from Away to Online, or Online to DND)
  159. func (d *ProtocolDispatcher) SendStatusChange(toSession *LegacySession, changedUIN uint32, newStatus uint32) error {
  160. if toSession == nil {
  161. return nil
  162. }
  163. d.logger.Debug("dispatching status change notification",
  164. "to_uin", toSession.UIN,
  165. "to_version", toSession.Version,
  166. "changed_uin", changedUIN,
  167. "new_status", fmt.Sprintf("0x%08X", newStatus),
  168. )
  169. switch toSession.Version {
  170. case ICQLegacyVersionV1:
  171. return d.v1Handler.sendStatusUpdate(toSession, changedUIN, downgradeStatusForV2(newStatus))
  172. case ICQLegacyVersionV2:
  173. return d.v2Handler.sendStatusUpdate(toSession, changedUIN, downgradeStatusForV2(newStatus))
  174. case ICQLegacyVersionV3:
  175. return d.v3Handler.sendUserStatus(toSession, changedUIN, newStatus)
  176. case ICQLegacyVersionV4:
  177. return d.v4Handler.sendUserStatus(toSession, changedUIN, newStatus)
  178. case ICQLegacyVersionV5:
  179. return d.v5Handler.sendV5UserStatus(toSession, changedUIN, newStatus)
  180. default:
  181. return nil
  182. }
  183. }
  184. // downgradeStatusForV2 maps advanced legacy statuses (N/A, Occupied, FFC)
  185. // to the subset that V2 clients display for remote contacts.
  186. //
  187. // The real ICQ V2 client uses combined status bits just like V5:
  188. //
  189. // DND = 0x11 (Away|Occupied), not 0x02
  190. //
  191. // So we map to the combined values the V2 client actually understands.
  192. //
  193. // DND (0x02) -> 0x11 — V2 uses 0x11 for DND, not 0x02
  194. // N/A (0x04) -> Away (0x01) — extended away maps to away
  195. // N/A (0x05) -> Away (0x01) — Away|N/A maps to away
  196. // Occupied (0x10) -> 0x11 — V2 uses 0x11 for DND (closest busy state)
  197. // Occupied (0x11) -> 0x11 — already correct
  198. // DND (0x13) -> 0x11 — V2 uses 0x11 for DND
  199. // FFC (0x20) -> Online (0x00) — free-for-chat maps to online
  200. //
  201. // Flags in the upper word (invisible, web-aware, etc.) are preserved.
  202. func downgradeStatusForV2(status uint32) uint32 {
  203. base := status & 0xFF
  204. flags := status & 0xFFFFFF00
  205. switch base {
  206. case 0x02: // DND (pure)
  207. base = 0x11
  208. case 0x04, 0x05: // N/A, Away|N/A
  209. base = 0x01 // Away
  210. case 0x10: // Occupied (pure)
  211. base = 0x11
  212. case 0x13: // Away|DND|Occupied
  213. base = 0x11
  214. case 0x20: // FFC
  215. base = 0x00 // Online
  216. // 0x01 (Away) and 0x11 (Occupied/DND) pass through unchanged
  217. }
  218. return flags | base
  219. }
  220. // PacketSender is the interface for sending packets
  221. type PacketSender interface {
  222. SendPacket(addr *net.UDPAddr, packet []byte) error
  223. SendToSession(session *LegacySession, packet []byte) error
  224. }
  225. // MessageDispatcher is the interface for cross-protocol message dispatching
  226. // This follows iserverd's architecture where a central dispatcher routes
  227. // messages to the appropriate protocol handler based on the target's version
  228. // From iserverd handle.cpp: send_user_online(), send_user_offline(),
  229. // send_user_status(), send_online_message()
  230. type MessageDispatcher interface {
  231. // SendUserOnline notifies a session that a user has come online
  232. // From iserverd send_user_online() in handle.cpp
  233. SendUserOnline(toSession *LegacySession, onlineUIN uint32, status uint32) error
  234. // SendUserOffline notifies a session that a user has gone offline
  235. // From iserverd send_user_offline() in handle.cpp
  236. SendUserOffline(toSession *LegacySession, offlineUIN uint32) error
  237. // SendStatusChange notifies a session that a user has changed their status
  238. // From iserverd send_user_status() in handle.cpp
  239. // This is different from SendUserOnline - it's used when a user changes
  240. // status while already online (e.g., Away -> Online, Online -> DND)
  241. SendStatusChange(toSession *LegacySession, changedUIN uint32, newStatus uint32) error
  242. // SendOnlineMessage sends an instant message to a session
  243. // From iserverd send_online_message() in handle.cpp
  244. SendOnlineMessage(toSession *LegacySession, fromUIN uint32, msgType uint16, message string) error
  245. }
  246. // BaseHandler contains common functionality shared by all protocol version handlers.
  247. // It provides V2-format helper methods for sending packets that are used as a
  248. // fallback by the simpler protocol versions.
  249. type BaseHandler struct {
  250. sessions *LegacySessionManager
  251. service LegacyService
  252. sender PacketSender
  253. logger *slog.Logger
  254. }
  255. // AuthService provides OSCAR authentication and BOS session registration.
  256. type AuthService interface {
  257. FLAPLogin(ctx context.Context, inFrame wire.FLAPSignonFrame, advertisedHost string) (wire.TLVRestBlock, error)
  258. CrackCookie(authCookie []byte) (state.ServerCookie, error)
  259. RegisterBOSSession(ctx context.Context, authCookie state.ServerCookie, cfg func(*state.Session)) (*state.SessionInstance, error)
  260. }
  261. // LegacyService is the interface for the ICQ legacy service layer.
  262. // It defines all business logic operations that protocol handlers delegate to,
  263. // keeping handlers thin and protocol-independent logic centralized.
  264. type LegacyService interface {
  265. // ValidateCredentials checks if the given UIN and password are valid.
  266. // Returns true if credentials are valid, false otherwise.
  267. ValidateCredentials(ctx context.Context, uin uint32, password string) (bool, error)
  268. // AuthenticateUser validates user credentials and returns authentication result.
  269. // This is the service layer method for authentication that handlers call after
  270. // parsing login packets. It validates credentials and returns a typed result
  271. // struct containing success/failure and session data.
  272. // The method does NOT contain any protocol-specific packet building logic.
  273. // Handlers are responsible for building protocol-specific responses based on
  274. // the returned AuthResult.
  275. AuthenticateUser(ctx context.Context, req AuthRequest) (*AuthResult, error)
  276. // RegisterNewUser creates a new user account for legacy ICQ registration.
  277. // Returns the newly assigned UIN on success.
  278. RegisterNewUser(ctx context.Context, nickname, firstName, lastName, email, password string) (uint32, error)
  279. // GetOfflineMessages retrieves stored offline messages for the given UIN.
  280. GetOfflineMessages(ctx context.Context, uin uint32) ([]LegacyOfflineMessage, error)
  281. // AckOfflineMessages acknowledges and deletes offline messages for the given UIN.
  282. AckOfflineMessages(ctx context.Context, uin uint32) error
  283. // ProcessMessage handles message routing and offline storage.
  284. // This is the service layer method for messaging that handlers call after
  285. // parsing message packets. It determines if the target user is online and
  286. // returns routing information, or stores the message for offline delivery.
  287. // The method does NOT contain any protocol-specific packet building logic.
  288. // Handlers are responsible for building protocol-specific responses based on
  289. // the returned MessageResult.
  290. ProcessMessage(ctx context.Context, session *LegacySession, req MessageRequest) (*MessageResult, error)
  291. // ProcessContactList processes a contact list and returns online status for each contact.
  292. // This is the service layer method for contact list processing that handlers call after
  293. // parsing contact list packets. It checks the online status of each contact and returns
  294. // a ContactListResult containing the status of each contact.
  295. // The method does NOT contain any protocol-specific packet building logic.
  296. // Handlers are responsible for building protocol-specific responses based on
  297. // the returned ContactListResult.
  298. ProcessContactList(ctx context.Context, instance *state.SessionInstance, req ContactListRequest) (*ContactListResult, error)
  299. // ProcessUserAdd processes a user add request and returns information about the target user.
  300. // This is the service layer method for user add operations that handlers call after
  301. // parsing user add packets (CMD_USER_ADD). It checks if the target user is online
  302. // and returns their status, along with whether to send a "you were added" notification.
  303. // The method does NOT contain any protocol-specific packet building logic.
  304. // Handlers are responsible for building protocol-specific responses based on
  305. // the returned UserAddResult.
  306. ProcessUserAdd(ctx context.Context, instance *state.SessionInstance, req UserAddRequest) (*UserAddResult, error)
  307. // ProcessStatusChange processes a status change and returns notification targets.
  308. // This is the service layer method for status changes that handlers call after
  309. // parsing status change packets. It determines which users should be notified
  310. // of the status change (users who have this user in their contact list).
  311. // The method does NOT contain any protocol-specific packet building logic.
  312. // The method does NOT directly send packets to other sessions.
  313. // Handlers are responsible for building protocol-specific responses based on
  314. // the returned StatusChangeResult.
  315. ProcessStatusChange(ctx context.Context, req StatusChangeRequest) (*StatusChangeResult, error)
  316. // GetUserInfo retrieves basic user information as a LegacyUserSearchResult.
  317. GetUserInfo(ctx context.Context, uin uint32) (*LegacyUserSearchResult, error)
  318. // GetFullUserInfo returns the complete user record including all ICQ info fields.
  319. // This is used by V3 info packets that need home, work, and more info fields.
  320. // From iserverd v3_send_home_info(), v3_send_work_info(), etc.
  321. GetFullUserInfo(ctx context.Context, uin uint32) (*state.User, error)
  322. // GetUserInfoForProtocol retrieves user info and returns it as a typed UserInfoResult.
  323. // This is the service layer method for user info retrieval that handlers call after
  324. // parsing info request packets. It consolidates user info retrieval from the database
  325. // and returns a typed result struct containing all user profile fields.
  326. // The method does NOT contain any protocol-specific packet building logic.
  327. GetUserInfoForProtocol(ctx context.Context, targetUIN uint32) (*UserInfoResult, error)
  328. // SearchByUIN searches for a user by their UIN and returns their profile info.
  329. SearchByUIN(ctx context.Context, uin uint32) (*LegacyUserSearchResult, error)
  330. // SearchByName searches for users by nickname, first name, last name, or email.
  331. SearchByName(ctx context.Context, nick, first, last, email string) ([]LegacyUserSearchResult, error)
  332. // ChangeStatus updates a user's status in the service layer.
  333. ChangeStatus(ctx context.Context, uin uint32, status uint32) error
  334. // NotifyStatusChange broadcasts a status change to OSCAR clients who have
  335. // this user as a buddy.
  336. NotifyStatusChange(ctx context.Context, uin uint32, status uint32) error
  337. // NotifyUserOffline broadcasts a user departure to OSCAR clients.
  338. NotifyUserOffline(ctx context.Context, uin uint32) error
  339. // NotifyUserOnline broadcasts a user arrival to OSCAR clients after legacy login.
  340. NotifyUserOnline(ctx context.Context, uin uint32, status uint32) error
  341. // User Management
  342. // DeleteUser removes a user account from the system.
  343. // This is used by the V5 META_USER_UNREGISTER (0x04C4) command.
  344. // The password must match the user's current password for the deletion to succeed.
  345. // Returns nil on success, or an error if the user doesn't exist or password is wrong.
  346. DeleteUser(ctx context.Context, uin uint32, password string) error
  347. // White Pages Search
  348. // WhitePagesSearch performs a comprehensive search across multiple user profile fields.
  349. // This is used by the V5 META_SEARCH_WHITE (0x0532) and META_SEARCH_WHITE2 (0x0533) commands.
  350. // From iserverd v5_search_by_white() and v5_search_by_white2() in search.cpp
  351. // Returns matching users up to a maximum of 40 results.
  352. WhitePagesSearch(ctx context.Context, criteria WhitePagesSearchCriteria) ([]LegacyUserSearchResult, error)
  353. // Notes Operations
  354. // GetNotes retrieves the user's notes from the database.
  355. // This is used by the V3 GET_NOTES (0x05AA) command.
  356. // From iserverd v3_process_notes() - returns user's notes.
  357. GetNotes(ctx context.Context, uin uint32) (string, error)
  358. // SetNotes saves the user's notes to the database.
  359. // This is used by the V3 SET_NOTES (0x0596) command.
  360. // From iserverd v3_process_setnotes() - updates user's notes.
  361. SetNotes(ctx context.Context, uin uint32, notes string) error
  362. // Password Operations
  363. // SetPassword changes the user's password after validating the old password.
  364. // This is used by the V3 SET_PASSWORD (0x049C) command.
  365. // From iserverd v3_process_setpass() - updates user's password.
  366. // Note: The iserverd implementation doesn't validate old password, but we add
  367. // this validation for security. The oldPassword parameter can be empty to skip
  368. // validation (matching iserverd behavior).
  369. SetPassword(ctx context.Context, uin uint32, oldPassword, newPassword string) error
  370. // Auth Mode Operations
  371. // SetAuthMode sets whether authorization is required to add the user to a contact list.
  372. // This is used by the V3 SET_AUTH (0x0514) command.
  373. // From iserverd v3_process_setauth() - updates user's auth mode.
  374. // When authRequired is true, other users must request authorization before adding
  375. // this user to their contact list.
  376. SetAuthMode(ctx context.Context, uin uint32, authRequired bool) error
  377. // Interests Operations
  378. // GetInterests retrieves the user's interests from the database.
  379. // This is used by the V5 META_USER_FULLINFO response to return user interests.
  380. // From iserverd v5_send_meta_interestsinfo() - returns user's interests.
  381. GetInterests(ctx context.Context, uin uint32) (*state.ICQInterests, error)
  382. // SetInterests saves the user's interests to the database.
  383. // This is used by the V5 META_SET_INTERESTS (0x0410) command.
  384. // From iserverd v5_set_interests_info() - updates user's interests.
  385. SetInterests(ctx context.Context, uin uint32, interests state.ICQInterests) error
  386. // Affiliations Operations
  387. // GetAffiliations retrieves the user's affiliations from the database.
  388. // This is used by the V5 META_USER_FULLINFO response to return user affiliations.
  389. // From iserverd v5_send_meta_affilationsinfo() - returns user's past and current affiliations.
  390. GetAffiliations(ctx context.Context, uin uint32) (*state.ICQAffiliations, error)
  391. // SetAffiliations saves the user's affiliations to the database.
  392. // This is used by the V5 META_SET_AFFILIATIONS (0x041A) command.
  393. // From iserverd v5_set_affilations_info() - updates user's past and current affiliations.
  394. SetAffiliations(ctx context.Context, uin uint32, affiliations state.ICQAffiliations) error
  395. // Homepage Category Operations
  396. // GetHomepageCategory retrieves the user's homepage category from the database.
  397. // This is used by the V5 META_USER_FULLINFO response to return user homepage category.
  398. // From iserverd v5_send_meta_hpage_cat() - returns user's homepage category.
  399. GetHomepageCategory(ctx context.Context, uin uint32) (*state.ICQHomepageCategory, error)
  400. // SetHomepageCategory saves the user's homepage category to the database.
  401. // This is used by the V5 META_SET_HPCAT (0x0442) command.
  402. // From iserverd v5_set_hpcat_info() - updates user's homepage category.
  403. SetHomepageCategory(ctx context.Context, uin uint32, hpcat state.ICQHomepageCategory) error
  404. // Profile Update Operations
  405. // UpdateBasicInfo updates a user's basic profile information.
  406. UpdateBasicInfo(ctx context.Context, uin uint32, info state.ICQBasicInfo) error
  407. // UpdateWorkInfo updates a user's work information.
  408. UpdateWorkInfo(ctx context.Context, uin uint32, info state.ICQWorkInfo) error
  409. // UpdateMoreInfo updates a user's additional profile information.
  410. UpdateMoreInfo(ctx context.Context, uin uint32, info state.ICQMoreInfo) error
  411. // UpdatePermissions updates a user's permission settings.
  412. UpdatePermissions(ctx context.Context, uin uint32, info state.ICQPermissions) error
  413. }
  414. // sendAck sends an acknowledgment packet to the session using V2 packet format.
  415. func (h *BaseHandler) sendAck(session *LegacySession, seqNum uint16) error {
  416. pkt := BuildV2Ack(seqNum)
  417. pkt.Version = session.Version
  418. return h.sender.SendToSession(session, MarshalV2ServerPacket(pkt))
  419. }
  420. // sendBadPassword sends a bad password response
  421. func (h *BaseHandler) sendBadPassword(addr *net.UDPAddr, seqNum uint16, version uint16) error {
  422. pkt := BuildV2BadPassword(seqNum)
  423. pkt.Version = version
  424. return h.sender.SendPacket(addr, MarshalV2ServerPacket(pkt))
  425. }
  426. // sendUserOnline sends a user online notification
  427. func (h *BaseHandler) sendUserOnline(session *LegacySession, uin uint32, status uint32, ip net.IP, port uint16) error {
  428. pkt := BuildV2UserOnline(session.NextServerSeqNum(), uin, status, ip, port)
  429. pkt.Version = session.Version
  430. return h.sender.SendToSession(session, MarshalV2ServerPacket(pkt))
  431. }
  432. // sendUserOffline sends a user offline notification
  433. func (h *BaseHandler) sendUserOffline(session *LegacySession, uin uint32) error {
  434. pkt := BuildV2UserOffline(session.NextServerSeqNum(), uin)
  435. pkt.Version = session.Version
  436. return h.sender.SendToSession(session, MarshalV2ServerPacket(pkt))
  437. }
  438. // sendStatusUpdate sends a status update notification
  439. func (h *BaseHandler) sendStatusUpdate(session *LegacySession, uin uint32, status uint32) error {
  440. pkt := BuildV2StatusUpdate(session.NextServerSeqNum(), uin, status)
  441. pkt.Version = session.Version
  442. return h.sender.SendToSession(session, MarshalV2ServerPacket(pkt))
  443. }
  444. // sendMessage sends a message to a session
  445. func (h *BaseHandler) sendMessage(session *LegacySession, fromUIN uint32, msgType uint16, message string) error {
  446. pkt := BuildV2Message(session.NextServerSeqNum(), fromUIN, msgType, message)
  447. pkt.Version = session.Version
  448. return h.sender.SendToSession(session, MarshalV2ServerPacket(pkt))
  449. }
  450. // sendSearchResult sends a search result
  451. func (h *BaseHandler) sendSearchResult(session *LegacySession, user *LegacyUserSearchResult, isLast bool, clientSubSeq uint16) error {
  452. info := &LegacyUserInfo{
  453. UIN: user.UIN,
  454. Nickname: truncateField(user.Nickname, 20, h.logger, "nickname", user.UIN),
  455. FirstName: truncateField(user.FirstName, 64, h.logger, "first_name", user.UIN),
  456. LastName: truncateField(user.LastName, 64, h.logger, "last_name", user.UIN),
  457. Email: truncateField(user.Email, 64, h.logger, "email", user.UIN),
  458. Auth: user.AuthRequired,
  459. }
  460. if user.UIN != 0 {
  461. // Send search found with user data — subseq must echo client's sub-sequence
  462. pkt := BuildV2SearchResult(clientSubSeq, info, false)
  463. pkt.SeqNum = session.NextServerSeqNum()
  464. pkt.Version = session.Version
  465. if err := h.sender.SendToSession(session, MarshalV2ServerPacket(pkt)); err != nil {
  466. return err
  467. }
  468. }
  469. if isLast {
  470. // Send search done with its own server seq — subseq must echo client's sub-sequence
  471. pkt := BuildV2SearchResult(clientSubSeq, &LegacyUserInfo{}, true)
  472. pkt.SeqNum = session.NextServerSeqNum()
  473. pkt.Version = session.Version
  474. return h.sender.SendToSession(session, MarshalV2ServerPacket(pkt))
  475. }
  476. return nil
  477. }