handler.go 24 KB

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