v5_packet_builder.go 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787
  1. package icq_legacy
  2. import (
  3. "encoding/binary"
  4. )
  5. // V5PacketBuilder constructs V5 protocol packets (encrypted).
  6. // This interface separates packet construction from business logic,
  7. // following the OSCAR foodgroup architecture pattern.
  8. //
  9. // V5 packet format (from iserverd source):
  10. // Server packets: VERSION(2) + ZERO(1) + SESSION_ID(4) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + CHECKCODE(4) + DATA
  11. // Total header: 21 bytes
  12. //
  13. // V5 uses full packet encryption via AddV5ServerCheckcode() which computes
  14. // and inserts the checkcode at offset 0x11 (17).
  15. type V5PacketBuilder interface {
  16. // BuildLoginReply constructs a login success (HELLO) response packet.
  17. // The packet contains timing parameters and client IP information.
  18. BuildLoginReply(session *LegacySession, seq1, seq2 uint16) []byte
  19. // BuildBadPassword constructs a bad password/authentication failure response.
  20. BuildBadPassword(sessionID uint32, uin uint32, seq2 uint16) []byte
  21. // BuildAck constructs an acknowledgment packet for the given sequence number.
  22. BuildAck(session *LegacySession, seq1 uint16) []byte
  23. // BuildAckWithSeq2 constructs an ACK echoing both seq1 and seq2 from the client.
  24. BuildAckWithSeq2(session *LegacySession, seq1, seq2 uint16) []byte
  25. // BuildUserOnline constructs a user online notification packet.
  26. // Sent to notify a user that one of their contacts has come online.
  27. BuildUserOnline(session *LegacySession, uin uint32, status uint32) []byte
  28. // BuildUserOffline constructs a user offline notification packet.
  29. // Sent to notify a user that one of their contacts has gone offline.
  30. BuildUserOffline(session *LegacySession, uin uint32) []byte
  31. // BuildUserStatus constructs a user status change notification packet.
  32. // Sent when a user changes status while already online.
  33. BuildUserStatus(session *LegacySession, uin uint32, status uint32) []byte
  34. // BuildContactListDone constructs a contact list processing complete response.
  35. BuildContactListDone(session *LegacySession, seq2 uint16) []byte
  36. // BuildOnlineMessage constructs an online system message packet.
  37. BuildOnlineMessage(session *LegacySession, fromUIN uint32, msgType uint16, message string) []byte
  38. // BuildOfflineMessage constructs an offline message packet.
  39. // Format: FROM_UIN(4) + YEAR(2) + MONTH(1) + DAY(1) + HOUR(1) + MINUTE(1) + MSG_TYPE(2) + MSG_LEN(2) + MESSAGE
  40. BuildOfflineMessage(session *LegacySession, msg *LegacyOfflineMessage) []byte
  41. // BuildOfflineMsgDone constructs an end of offline messages packet.
  42. BuildOfflineMsgDone(session *LegacySession, seq2 uint16) []byte
  43. // BuildMetaAck constructs a META_USER acknowledgment packet.
  44. // seq2 is the client's seq2 from the request, echoed back for correlation.
  45. // The server's own seq1 (servseq) is obtained from session.NextServerSeqNum().
  46. BuildMetaAck(session *LegacySession, seq2 uint16, subCommand uint16) []byte
  47. // BuildMetaUserInfo constructs a META_USER info response packet.
  48. // seq2 is the client's seq2 from the request, echoed back for correlation.
  49. BuildMetaUserInfo(session *LegacySession, seq2 uint16, info *UserInfoResult) []byte
  50. // BuildSearchResult constructs a search result packet.
  51. // seq2 is the client's seq2 from the request, echoed back for correlation.
  52. // From iserverd: server uses user.servseq for seq1 and echoes client's seq2.
  53. BuildSearchResult(session *LegacySession, seq2 uint16, results []UserInfoResult, isLast bool) []byte
  54. // BuildDepsListReply constructs a pre-auth response packet (V3 format).
  55. // Historically called "departments list" in iserverd. In V5, this is a deprecated
  56. // empty V3-format response sent during the pre-auth pseudo-login (0x03F2).
  57. // From iserverd v5_send_depslist() - sends a V3 packet with magic checksum.
  58. BuildDepsListReply(uin uint32, seq2 uint16) []byte
  59. // BuildAckToAddr constructs an ACK packet for sending to an address (before session exists).
  60. // Used during login flow when session is not yet established.
  61. BuildAckToAddr(sessionID uint32, uin uint32, seq1, seq2 uint16) []byte
  62. // BuildFirstLoginReply constructs a first login reply packet.
  63. // From iserverd v5_process_firstlog() - sent in response to CMD_FIRST_LOGIN.
  64. BuildFirstLoginReply(sessionID uint32, uin uint32, seq1, seq2 uint16, sessionID2 uint32) []byte
  65. // EncryptPacket encrypts a V5 packet using the session ID.
  66. // Note: V5 server packets use AddV5ServerCheckcode() which is called by MarshalV5ServerPacket.
  67. EncryptPacket(packet []byte, sessionID uint32) []byte
  68. }
  69. // V5PacketBuilderImpl implements the V5PacketBuilder interface.
  70. // It constructs V5 protocol packets following the iserverd packet formats.
  71. type V5PacketBuilderImpl struct {
  72. // sessionManager is used to look up online user connection info for peer-to-peer
  73. sessionManager *LegacySessionManager
  74. directConnectionEnabled func(version int) bool
  75. }
  76. // NewV5PacketBuilder creates a new V5PacketBuilder instance.
  77. func NewV5PacketBuilder(sessionManager *LegacySessionManager, directConnectionEnabled func(version int) bool) V5PacketBuilder {
  78. return &V5PacketBuilderImpl{
  79. sessionManager: sessionManager,
  80. directConnectionEnabled: directConnectionEnabled,
  81. }
  82. }
  83. // BuildLoginReply constructs a login success (HELLO) response packet.
  84. // From iserverd v5_send_login_reply()
  85. //
  86. // Data format (20 bytes total):
  87. // - 0x008C(2) + 0x0000(2) + PING_TIME(2) + TIMEOUT(2) + 0x000A(2) + RETRIES(2) + CLIENT_IP(4) + SERVER_ID(4)
  88. func (b *V5PacketBuilderImpl) BuildLoginReply(session *LegacySession, seq1, seq2 uint16) []byte {
  89. // Get client IP as uint32 (little-endian)
  90. var clientIP uint32
  91. if session.Addr != nil && session.Addr.IP != nil {
  92. ip := session.Addr.IP.To4()
  93. if ip != nil {
  94. clientIP = uint32(ip[0]) | uint32(ip[1])<<8 | uint32(ip[2])<<16 | uint32(ip[3])<<24
  95. }
  96. }
  97. // Build data - 20 bytes (using iserverd format)
  98. data := make([]byte, 20)
  99. offset := 0
  100. binary.LittleEndian.PutUint16(data[offset:], 0x008C) // keep alive interval low
  101. offset += 2
  102. binary.LittleEndian.PutUint16(data[offset:], 0x0000) // keep alive interval high
  103. offset += 2
  104. binary.LittleEndian.PutUint16(data[offset:], 50) // ping time (60-10)
  105. offset += 2
  106. binary.LittleEndian.PutUint16(data[offset:], 60) // timeout
  107. offset += 2
  108. binary.LittleEndian.PutUint16(data[offset:], 0x000A) // unknown
  109. offset += 2
  110. binary.LittleEndian.PutUint16(data[offset:], 5) // retries
  111. offset += 2
  112. binary.LittleEndian.PutUint32(data[offset:], clientIP) // client IP
  113. offset += 4
  114. binary.LittleEndian.PutUint32(data[offset:], 0x80CDC19B) // server ID
  115. pkt := &V5ServerPacket{
  116. Version: ICQLegacyVersionV5,
  117. SessionID: session.SessionID,
  118. Command: ICQLegacySrvHello,
  119. SeqNum1: 0,
  120. SeqNum2: seq2,
  121. UIN: session.UIN,
  122. Data: data,
  123. }
  124. return MarshalV5ServerPacket(pkt)
  125. }
  126. // BuildBadPassword constructs a bad password response.
  127. func (b *V5PacketBuilderImpl) BuildBadPassword(sessionID uint32, uin uint32, seq2 uint16) []byte {
  128. pkt := &V5ServerPacket{
  129. Version: ICQLegacyVersionV5,
  130. SessionID: sessionID,
  131. Command: ICQLegacySrvWrongPasswd,
  132. SeqNum1: 0,
  133. SeqNum2: seq2,
  134. UIN: uin,
  135. }
  136. return MarshalV5ServerPacket(pkt)
  137. }
  138. // BuildAck constructs an ACK packet.
  139. func (b *V5PacketBuilderImpl) BuildAck(session *LegacySession, seq1 uint16) []byte {
  140. pkt := &V5ServerPacket{
  141. Version: ICQLegacyVersionV5,
  142. SessionID: session.SessionID,
  143. Command: ICQLegacySrvAck,
  144. SeqNum1: seq1,
  145. SeqNum2: 0,
  146. UIN: session.UIN,
  147. }
  148. return MarshalV5ServerPacket(pkt)
  149. }
  150. // BuildAckWithSeq2 constructs an ACK echoing both seq1 and seq2 from the client.
  151. func (b *V5PacketBuilderImpl) BuildAckWithSeq2(session *LegacySession, seq1, seq2 uint16) []byte {
  152. pkt := &V5ServerPacket{
  153. Version: ICQLegacyVersionV5,
  154. SessionID: session.SessionID,
  155. Command: ICQLegacySrvAck,
  156. SeqNum1: seq1,
  157. SeqNum2: seq2,
  158. UIN: session.UIN,
  159. }
  160. return MarshalV5ServerPacket(pkt)
  161. }
  162. // BuildUserOnline constructs a user online notification packet.
  163. // From iserverd v5_send_user_online()
  164. //
  165. // Verified against licq.5 client (icqd-udp.cpp ICQ_CMDxRCV_USERxONLINE):
  166. // Client reads: UIN(4) + IP(4) + PORT(2) + JUNK_SHORT(2) + REAL_IP(4) +
  167. //
  168. // MODE(1) + STATUS(4) + DC_VERSION(4)
  169. //
  170. // Total client reads: 25 bytes. Extra bytes after that are ignored.
  171. //
  172. // Our packet format (49 bytes data, iserverd-compatible):
  173. // - UIN(4): UIN of user who is online
  174. // - IP(4): External IP (0 for V3 clients, real IP for V5)
  175. // - TCP_PORT(4): TCP port - client reads low 2 bytes as port, high 2 as junk
  176. // - INT_IP(4): Internal/LAN IP (0 for V3)
  177. // - DC_TYPE(1): Direct connection type (0 for V3)
  178. // - STATUS(2)+ESTAT(2): Combined status - client reads as single uint32
  179. // - DCVER(4): Direct connection protocol version
  180. // - DC_COOKIE(4): Direct connection cookie
  181. // - WEB_PORT(4): Web port
  182. // - CLI_FUTURES(4): Client futures/capabilities
  183. // - INFO_UTIME(4): Info update timestamp
  184. // - MORE_UTIME(4): More info update timestamp
  185. // - STAT_UTIME(4): Status update timestamp
  186. func (b *V5PacketBuilderImpl) BuildUserOnline(session *LegacySession, uin uint32, status uint32) []byte {
  187. data := make([]byte, 49)
  188. offset := 0
  189. // UIN of the user who is online
  190. binary.LittleEndian.PutUint32(data[offset:], uin)
  191. offset += 4
  192. // Get the online user's session to retrieve their connection info
  193. var onlineSession *LegacySession
  194. if b.sessionManager != nil {
  195. onlineSession = b.sessionManager.GetSession(uin)
  196. }
  197. // From iserverd: V3 clients get zeroed connection info for TCP protection
  198. // V5 clients get real connection info for peer-to-peer
  199. var externalIP, tcpPort, internalIP uint32
  200. var dcType uint8
  201. var dcVersion uint32
  202. if b.directConnectionEnabled != nil && b.directConnectionEnabled(int(ICQLegacyVersionV5)) && onlineSession != nil && onlineSession.Version == ICQLegacyVersionV5 {
  203. // V5 client - send real connection info for peer-to-peer
  204. externalIP = onlineSession.GetExternalIP()
  205. tcpPort = onlineSession.GetTCPPort()
  206. internalIP = onlineSession.GetInternalIP()
  207. dcType = onlineSession.DCType
  208. dcVersion = uint32(onlineSession.GetDCVersion())
  209. }
  210. // else: V3/V4 clients or unknown - keep zeros for privacy
  211. // IP address
  212. binary.LittleEndian.PutUint32(data[offset:], externalIP)
  213. offset += 4
  214. // TCP port
  215. binary.LittleEndian.PutUint32(data[offset:], tcpPort)
  216. offset += 4
  217. // Internal IP
  218. binary.LittleEndian.PutUint32(data[offset:], internalIP)
  219. offset += 4
  220. // DC type
  221. data[offset] = dcType
  222. offset++
  223. // Status (low word)
  224. binary.LittleEndian.PutUint16(data[offset:], uint16(status&0xFFFF))
  225. offset += 2
  226. // Extended status (high word)
  227. binary.LittleEndian.PutUint16(data[offset:], uint16(status>>16))
  228. offset += 2
  229. // DC version
  230. binary.LittleEndian.PutUint32(data[offset:], dcVersion)
  231. offset += 4
  232. // DC cookie (not implemented)
  233. binary.LittleEndian.PutUint32(data[offset:], 0)
  234. offset += 4
  235. // Web port (not implemented)
  236. binary.LittleEndian.PutUint32(data[offset:], 0)
  237. offset += 4
  238. // Client futures (not implemented)
  239. binary.LittleEndian.PutUint32(data[offset:], 0)
  240. offset += 4
  241. // Info update time (not implemented)
  242. binary.LittleEndian.PutUint32(data[offset:], 0)
  243. offset += 4
  244. // More update time (not implemented)
  245. binary.LittleEndian.PutUint32(data[offset:], 0)
  246. offset += 4
  247. // Status update time (not implemented)
  248. binary.LittleEndian.PutUint32(data[offset:], 0)
  249. pkt := &V5ServerPacket{
  250. Version: ICQLegacyVersionV5,
  251. SessionID: session.SessionID,
  252. Command: ICQLegacySrvUserOnline,
  253. SeqNum1: session.NextServerSeqNum(),
  254. SeqNum2: 0,
  255. UIN: session.UIN,
  256. Data: data,
  257. }
  258. return MarshalV5ServerPacket(pkt)
  259. }
  260. // BuildUserOffline constructs a user offline notification packet.
  261. // From iserverd v5_send_user_offline()
  262. func (b *V5PacketBuilderImpl) BuildUserOffline(session *LegacySession, uin uint32) []byte {
  263. data := make([]byte, 4)
  264. binary.LittleEndian.PutUint32(data[0:4], uin)
  265. pkt := &V5ServerPacket{
  266. Version: ICQLegacyVersionV5,
  267. SessionID: session.SessionID,
  268. Command: ICQLegacySrvUserOffline,
  269. SeqNum1: session.NextServerSeqNum(),
  270. SeqNum2: 0,
  271. UIN: session.UIN,
  272. Data: data,
  273. }
  274. return MarshalV5ServerPacket(pkt)
  275. }
  276. // BuildUserStatus constructs a user status change notification packet.
  277. // From iserverd v5_send_user_status()
  278. func (b *V5PacketBuilderImpl) BuildUserStatus(session *LegacySession, uin uint32, status uint32) []byte {
  279. data := make([]byte, 8)
  280. binary.LittleEndian.PutUint32(data[0:4], uin)
  281. binary.LittleEndian.PutUint16(data[4:6], uint16(status&0xFFFF))
  282. binary.LittleEndian.PutUint16(data[6:8], uint16(status>>16))
  283. pkt := &V5ServerPacket{
  284. Version: ICQLegacyVersionV5,
  285. SessionID: session.SessionID,
  286. Command: ICQLegacySrvUserStatus,
  287. SeqNum1: session.NextServerSeqNum(),
  288. SeqNum2: 0,
  289. UIN: session.UIN,
  290. Data: data,
  291. }
  292. return MarshalV5ServerPacket(pkt)
  293. }
  294. // BuildContactListDone constructs a contact list processed response.
  295. func (b *V5PacketBuilderImpl) BuildContactListDone(session *LegacySession, seq2 uint16) []byte {
  296. pkt := &V5ServerPacket{
  297. Version: ICQLegacyVersionV5,
  298. SessionID: session.SessionID,
  299. Command: ICQLegacySrvUserListDone,
  300. SeqNum1: session.NextServerSeqNum(),
  301. SeqNum2: seq2,
  302. UIN: session.UIN,
  303. }
  304. return MarshalV5ServerPacket(pkt)
  305. }
  306. // BuildOnlineMessage constructs an online system message packet.
  307. // From iserverd v5_send_user_message()
  308. // Format: FROM_UIN(4) + MSG_TYPE(2) + MSG_LEN(2) + MESSAGE
  309. func (b *V5PacketBuilderImpl) BuildOnlineMessage(session *LegacySession, fromUIN uint32, msgType uint16, message string) []byte {
  310. msgBytes := []byte(message)
  311. dataLen := 4 + 2 + 2 + len(msgBytes) + 1
  312. data := make([]byte, dataLen)
  313. offset := 0
  314. // From UIN
  315. binary.LittleEndian.PutUint32(data[offset:], fromUIN)
  316. offset += 4
  317. // Message type
  318. binary.LittleEndian.PutUint16(data[offset:], msgType)
  319. offset += 2
  320. // Message length (including null terminator)
  321. binary.LittleEndian.PutUint16(data[offset:], uint16(len(msgBytes)+1))
  322. offset += 2
  323. // Message content
  324. copy(data[offset:], msgBytes)
  325. offset += len(msgBytes)
  326. data[offset] = 0 // null terminator
  327. pkt := &V5ServerPacket{
  328. Version: ICQLegacyVersionV5,
  329. SessionID: session.SessionID,
  330. Command: ICQLegacySrvSysMsgOnline,
  331. SeqNum1: session.NextServerSeqNum(),
  332. SeqNum2: 0,
  333. UIN: session.UIN,
  334. Data: data,
  335. }
  336. return MarshalV5ServerPacket(pkt)
  337. }
  338. // BuildOfflineMessage constructs an offline message packet.
  339. // From iserverd v5_send_offline_message()
  340. // Format: FROM_UIN(4) + YEAR(2) + MONTH(1) + DAY(1) + HOUR(1) + MINUTE(1) + MSG_TYPE(2) + MSG_LEN(2) + MESSAGE
  341. func (b *V5PacketBuilderImpl) BuildOfflineMessage(session *LegacySession, msg *LegacyOfflineMessage) []byte {
  342. msgBytes := []byte(msg.Message)
  343. dataLen := 4 + 2 + 1 + 1 + 1 + 1 + 2 + 2 + len(msgBytes) + 1
  344. data := make([]byte, dataLen)
  345. offset := 0
  346. // From UIN
  347. binary.LittleEndian.PutUint32(data[offset:], msg.FromUIN)
  348. offset += 4
  349. // Timestamp: YEAR(2) + MONTH(1) + DAY(1) + HOUR(1) + MINUTE(1)
  350. binary.LittleEndian.PutUint16(data[offset:], uint16(msg.Timestamp.Year()))
  351. offset += 2
  352. data[offset] = byte(msg.Timestamp.Month())
  353. offset++
  354. data[offset] = byte(msg.Timestamp.Day())
  355. offset++
  356. data[offset] = byte(msg.Timestamp.Hour())
  357. offset++
  358. data[offset] = byte(msg.Timestamp.Minute())
  359. offset++
  360. // Message type
  361. binary.LittleEndian.PutUint16(data[offset:], msg.MsgType)
  362. offset += 2
  363. // Message length (including null terminator)
  364. binary.LittleEndian.PutUint16(data[offset:], uint16(len(msgBytes)+1))
  365. offset += 2
  366. // Message content
  367. copy(data[offset:], msgBytes)
  368. offset += len(msgBytes)
  369. data[offset] = 0 // null terminator
  370. pkt := &V5ServerPacket{
  371. Version: ICQLegacyVersionV5,
  372. SessionID: session.SessionID,
  373. Command: ICQLegacySrvSysMsgOffline, // 0x00DC
  374. SeqNum1: session.NextServerSeqNum(),
  375. SeqNum2: 0,
  376. UIN: session.UIN,
  377. Data: data,
  378. }
  379. return MarshalV5ServerPacket(pkt)
  380. }
  381. // BuildOfflineMsgDone constructs an end of offline messages packet.
  382. func (b *V5PacketBuilderImpl) BuildOfflineMsgDone(session *LegacySession, seq2 uint16) []byte {
  383. pkt := &V5ServerPacket{
  384. Version: ICQLegacyVersionV5,
  385. SessionID: session.SessionID,
  386. Command: ICQLegacySrvSysMsgDone,
  387. SeqNum1: session.NextServerSeqNum(),
  388. SeqNum2: seq2,
  389. UIN: session.UIN,
  390. }
  391. return MarshalV5ServerPacket(pkt)
  392. }
  393. // BuildMetaAck constructs a META_USER acknowledgment packet.
  394. // From iserverd v5_send_meta_set_ack()
  395. func (b *V5PacketBuilderImpl) BuildMetaAck(session *LegacySession, seq2 uint16, subCommand uint16) []byte {
  396. // META ACK data: SUB_COMMAND(2) + RESULT(1)
  397. data := make([]byte, 3)
  398. binary.LittleEndian.PutUint16(data[0:2], subCommand)
  399. data[2] = 0x0A // Success code
  400. pkt := &V5ServerPacket{
  401. Version: ICQLegacyVersionV5,
  402. SessionID: session.SessionID,
  403. Command: ICQLegacySrvMetaUser,
  404. SeqNum1: session.NextServerSeqNum(),
  405. SeqNum2: seq2,
  406. UIN: session.UIN,
  407. Data: data,
  408. }
  409. return MarshalV5ServerPacket(pkt)
  410. }
  411. // BuildMetaUserInfo constructs a META_USER info response packet.
  412. // From iserverd v5_send_meta_user_info()
  413. func (b *V5PacketBuilderImpl) BuildMetaUserInfo(session *LegacySession, seq2 uint16, info *UserInfoResult) []byte {
  414. if info == nil {
  415. // Send empty/fail response
  416. data := make([]byte, 3)
  417. binary.LittleEndian.PutUint16(data[0:2], ICQLegacySrvMetaUserInfo)
  418. data[2] = 0x32 // Fail code
  419. pkt := &V5ServerPacket{
  420. Version: ICQLegacyVersionV5,
  421. SessionID: session.SessionID,
  422. Command: ICQLegacySrvMetaUser,
  423. SeqNum1: session.NextServerSeqNum(),
  424. SeqNum2: seq2,
  425. UIN: session.UIN,
  426. Data: data,
  427. }
  428. return MarshalV5ServerPacket(pkt)
  429. }
  430. // Build user info data
  431. // Format: SUB_COMMAND(2) + RESULT(1) + UIN(4) + NICK_LEN(2) + NICK + FIRST_LEN(2) + FIRST +
  432. // LAST_LEN(2) + LAST + EMAIL_LEN(2) + EMAIL + AUTH(1) + ZERO(1) + ZERO(1)
  433. nick := info.Nickname
  434. first := info.FirstName
  435. last := info.LastName
  436. email := info.Email
  437. dataLen := 3 + 4 + 2 + len(nick) + 1 + 2 + len(first) + 1 + 2 + len(last) + 1 + 2 + len(email) + 1 + 3
  438. data := make([]byte, dataLen)
  439. offset := 0
  440. // Sub-command
  441. binary.LittleEndian.PutUint16(data[offset:], ICQLegacySrvMetaUserInfo)
  442. offset += 2
  443. // Result code (0x0A = success)
  444. data[offset] = 0x0A
  445. offset++
  446. // UIN
  447. binary.LittleEndian.PutUint32(data[offset:], info.UIN)
  448. offset += 4
  449. // Nickname (length-prefixed with null terminator)
  450. binary.LittleEndian.PutUint16(data[offset:], uint16(len(nick)+1))
  451. offset += 2
  452. copy(data[offset:], nick)
  453. offset += len(nick)
  454. data[offset] = 0
  455. offset++
  456. // First name (length-prefixed with null terminator)
  457. binary.LittleEndian.PutUint16(data[offset:], uint16(len(first)+1))
  458. offset += 2
  459. copy(data[offset:], first)
  460. offset += len(first)
  461. data[offset] = 0
  462. offset++
  463. // Last name (length-prefixed with null terminator)
  464. binary.LittleEndian.PutUint16(data[offset:], uint16(len(last)+1))
  465. offset += 2
  466. copy(data[offset:], last)
  467. offset += len(last)
  468. data[offset] = 0
  469. offset++
  470. // Email (length-prefixed with null terminator)
  471. binary.LittleEndian.PutUint16(data[offset:], uint16(len(email)+1))
  472. offset += 2
  473. copy(data[offset:], email)
  474. offset += len(email)
  475. data[offset] = 0
  476. offset++
  477. // Auth required
  478. data[offset] = info.AuthRequired
  479. offset++
  480. // Two trailing zeros
  481. data[offset] = 0
  482. offset++
  483. data[offset] = 0
  484. pkt := &V5ServerPacket{
  485. Version: ICQLegacyVersionV5,
  486. SessionID: session.SessionID,
  487. Command: ICQLegacySrvMetaUser,
  488. SeqNum1: session.NextServerSeqNum(),
  489. SeqNum2: seq2,
  490. UIN: session.UIN,
  491. Data: data,
  492. }
  493. return MarshalV5ServerPacket(pkt)
  494. }
  495. // BuildSearchResult constructs a search result packet.
  496. // From iserverd v5_send_user_found2()
  497. //
  498. // Format matches iserverd exactly:
  499. //
  500. // SUB_COMMAND(2) + RESULT(1) + [PACK_LEN(2) + UIN(4) + NICK_LEN(2) + NICK +
  501. // FIRST_LEN(2) + FIRST + LAST_LEN(2) + LAST + EMAIL_LEN(2) + EMAIL +
  502. // AUTH(1) + WEBAWARE(1) + 0x00(1)] + [USERS_LEFT(4) if last]
  503. func (b *V5PacketBuilderImpl) BuildSearchResult(session *LegacySession, seq2 uint16, results []UserInfoResult, isLast bool) []byte {
  504. if len(results) == 0 {
  505. // Send empty last result with failure code to indicate no results
  506. // From iserverd: sub_cmd + result(0x32=failure)
  507. data := make([]byte, 3)
  508. binary.LittleEndian.PutUint16(data[0:2], ICQLegacySrvMetaUserLastFound)
  509. data[2] = 0x32 // META_FAILURE - no results found
  510. pkt := &V5ServerPacket{
  511. Version: ICQLegacyVersionV5,
  512. SessionID: session.SessionID,
  513. Command: ICQLegacySrvMetaUser,
  514. SeqNum1: session.NextServerSeqNum(),
  515. SeqNum2: seq2,
  516. UIN: session.UIN,
  517. Data: data,
  518. }
  519. return MarshalV5ServerPacket(pkt)
  520. }
  521. info := results[0]
  522. nick := info.Nickname
  523. first := info.FirstName
  524. last := info.LastName
  525. email := info.Email
  526. // Calculate pack length matching iserverd:
  527. // UIN(4) + 4xLEN_PREFIX(8) + 4xNULL_TERM(4) + AUTH(1) + WEBAWARE(1) + 0x00(1) = 19
  528. // Plus string content lengths. users_left is OUTSIDE pack_len.
  529. // iserverd: pack_len = 15 + strlen(nick) + strlen(first) + strlen(last) + strlen(email) + 4
  530. // where 15 = UIN(4) + 4xLEN(8) + AUTH(1) + WEBAWARE(1) + PAD(1)
  531. // and +4 = 4 null terminators
  532. packLen := 19 + len(nick) + len(first) + len(last) + len(email)
  533. // Total data: sub_cmd(2) + result(1) + pack_len(2) + payload(packLen) + [users_left(4) if last]
  534. dataLen := 3 + 2 + packLen
  535. if isLast {
  536. dataLen += 4 // users_left is outside pack_len
  537. }
  538. data := make([]byte, dataLen)
  539. offset := 0
  540. // Sub-command
  541. subCmd := ICQLegacySrvMetaUserFound
  542. if isLast {
  543. subCmd = ICQLegacySrvMetaUserLastFound
  544. }
  545. binary.LittleEndian.PutUint16(data[offset:], subCmd)
  546. offset += 2
  547. // Result code (0x0A = META_SUCCESS)
  548. data[offset] = 0x0A
  549. offset++
  550. // Pack length
  551. binary.LittleEndian.PutUint16(data[offset:], uint16(packLen))
  552. offset += 2
  553. // UIN
  554. binary.LittleEndian.PutUint32(data[offset:], info.UIN)
  555. offset += 4
  556. // Nickname (length-prefixed with null terminator)
  557. binary.LittleEndian.PutUint16(data[offset:], uint16(len(nick)+1))
  558. offset += 2
  559. copy(data[offset:], nick)
  560. offset += len(nick)
  561. data[offset] = 0
  562. offset++
  563. // First name (length-prefixed with null terminator)
  564. binary.LittleEndian.PutUint16(data[offset:], uint16(len(first)+1))
  565. offset += 2
  566. copy(data[offset:], first)
  567. offset += len(first)
  568. data[offset] = 0
  569. offset++
  570. // Last name (length-prefixed with null terminator)
  571. binary.LittleEndian.PutUint16(data[offset:], uint16(len(last)+1))
  572. offset += 2
  573. copy(data[offset:], last)
  574. offset += len(last)
  575. data[offset] = 0
  576. offset++
  577. // Email (length-prefixed with null terminator)
  578. binary.LittleEndian.PutUint16(data[offset:], uint16(len(email)+1))
  579. offset += 2
  580. copy(data[offset:], email)
  581. offset += len(email)
  582. data[offset] = 0
  583. offset++
  584. // Auth required (from iserverd: tuser.auth)
  585. data[offset] = info.AuthRequired
  586. offset++
  587. // Web aware (from iserverd: tuser.webaware)
  588. data[offset] = 0
  589. offset++
  590. // Padding byte (from iserverd: 0x00)
  591. data[offset] = 0x00
  592. offset++
  593. // Users left (only on last result, from iserverd)
  594. if isLast {
  595. binary.LittleEndian.PutUint32(data[offset:], 0)
  596. }
  597. pkt := &V5ServerPacket{
  598. Version: ICQLegacyVersionV5,
  599. SessionID: session.SessionID,
  600. Command: ICQLegacySrvMetaUser,
  601. SeqNum1: session.NextServerSeqNum(),
  602. SeqNum2: seq2,
  603. UIN: session.UIN,
  604. Data: data,
  605. }
  606. return MarshalV5ServerPacket(pkt)
  607. }
  608. // EncryptPacket encrypts a V5 packet using the session ID.
  609. // Note: V5 server packets use AddV5ServerCheckcode() which is called by MarshalV5ServerPacket.
  610. // This method is provided for interface completeness but the encryption is handled
  611. // automatically by MarshalV5ServerPacket.
  612. func (b *V5PacketBuilderImpl) EncryptPacket(packet []byte, sessionID uint32) []byte {
  613. // V5 server packets are already processed by MarshalV5ServerPacket which calls
  614. // AddV5ServerCheckcode(). This method returns the packet as-is since encryption
  615. // is handled during marshaling.
  616. return packet
  617. }
  618. // BuildDepsListReply constructs a pre-auth response packet (V3 format).
  619. // Historically called "departments list" in iserverd. In V5, this is a deprecated
  620. // empty V3-format response sent during the pre-auth pseudo-login (0x03F2).
  621. // From iserverd v5_send_depslist()
  622. // V3 format packet: VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + CHECKSUM(4)
  623. func (b *V5PacketBuilderImpl) BuildDepsListReply(uin uint32, seq2 uint16) []byte {
  624. buf := make([]byte, 16)
  625. binary.LittleEndian.PutUint16(buf[0:2], ICQLegacyVersionV3)
  626. binary.LittleEndian.PutUint16(buf[2:4], ICQLegacySrvUserDepsList)
  627. binary.LittleEndian.PutUint16(buf[4:6], 0x0000) // seq1
  628. binary.LittleEndian.PutUint16(buf[6:8], seq2)
  629. binary.LittleEndian.PutUint32(buf[8:12], uin)
  630. binary.LittleEndian.PutUint32(buf[12:16], 0x8FFCACBF) // magic checksum
  631. return buf
  632. }
  633. // BuildAckToAddr constructs an ACK packet for sending to an address (before session exists).
  634. // Used during login flow when session is not yet established.
  635. func (b *V5PacketBuilderImpl) BuildAckToAddr(sessionID uint32, uin uint32, seq1, seq2 uint16) []byte {
  636. pkt := &V5ServerPacket{
  637. Version: ICQLegacyVersionV5,
  638. SessionID: sessionID,
  639. Command: ICQLegacySrvAck,
  640. SeqNum1: seq1,
  641. SeqNum2: seq2,
  642. UIN: uin,
  643. }
  644. return MarshalV5ServerPacket(pkt)
  645. }
  646. // BuildFirstLoginReply constructs a first login reply packet.
  647. // From iserverd v5_process_firstlog() - sent in response to CMD_FIRST_LOGIN.
  648. // Reply includes session_id2 and 0x0001.
  649. func (b *V5PacketBuilderImpl) BuildFirstLoginReply(sessionID uint32, uin uint32, seq1, seq2 uint16, sessionID2 uint32) []byte {
  650. // Data: SESSION_ID2(4) + 0x0001(2)
  651. data := make([]byte, 6)
  652. binary.LittleEndian.PutUint32(data[0:4], sessionID2)
  653. binary.LittleEndian.PutUint16(data[4:6], 0x0001)
  654. pkt := &V5ServerPacket{
  655. Version: ICQLegacyVersionV5,
  656. SessionID: sessionID,
  657. Command: ICQLegacySrvAck,
  658. SeqNum1: seq1,
  659. SeqNum2: seq2,
  660. UIN: uin,
  661. Data: data,
  662. }
  663. return MarshalV5ServerPacket(pkt)
  664. }