v3_packet_builder.go 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855
  1. package icq_legacy
  2. import (
  3. "encoding/binary"
  4. )
  5. // V3PacketBuilder constructs V3 protocol packets.
  6. // This interface separates packet construction from business logic,
  7. // following the OSCAR foodgroup architecture pattern.
  8. //
  9. // V3 packet format (from iserverd source):
  10. // VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + DATA...
  11. // Total header: 12 bytes (NO checksum in V3, unlike V5!)
  12. type V3PacketBuilder interface {
  13. // BuildLoginReply constructs a login success (HELLO) response packet.
  14. // The packet contains timing parameters and client IP information.
  15. BuildLoginReply(session *LegacySession, seq1, seq2 uint16) []byte
  16. // BuildBadPassword constructs a bad password/authentication failure response.
  17. BuildBadPassword(seq1, seq2 uint16, uin uint32) []byte
  18. // BuildAck constructs an acknowledgment packet for the given sequence numbers.
  19. BuildAck(seq1, seq2 uint16, uin uint32) []byte
  20. // BuildNotConnected constructs a "not connected" error response.
  21. BuildNotConnected(seq2 uint16, uin uint32) []byte
  22. // BuildUserOnline constructs a user online notification packet.
  23. // Sent to notify a user that one of their contacts has come online.
  24. BuildUserOnline(seqNum uint16, uin uint32, status uint32) []byte
  25. // BuildUserOffline constructs a user offline notification packet.
  26. // Sent to notify a user that one of their contacts has gone offline.
  27. BuildUserOffline(seqNum uint16, uin uint32) []byte
  28. // BuildUserStatus constructs a user status change notification packet.
  29. // Sent when a user changes status while already online.
  30. BuildUserStatus(seqNum uint16, uin uint32, status uint32) []byte
  31. // BuildContactListDone constructs a contact list processing complete response.
  32. BuildContactListDone(seqNum uint16, seq2 uint16, uin uint32) []byte
  33. // BuildOnlineMessage constructs an online system message packet.
  34. BuildOnlineMessage(seqNum uint16, fromUIN uint32, msgType uint16, message string) []byte
  35. // BuildBasicInfo constructs a basic user info response packet.
  36. BuildBasicInfo(seqNum uint16, seq2 uint16, uin uint32, info *UserInfoResult) []byte
  37. // BuildHomeInfo constructs a home info response packet.
  38. BuildHomeInfo(seqNum uint16, seq2 uint16, uin uint32, info *UserInfoResult) []byte
  39. // BuildWorkInfo constructs a work info response packet.
  40. BuildWorkInfo(seqNum uint16, seq2 uint16, uin uint32, info *UserInfoResult) []byte
  41. // BuildHomeWeb constructs a home webpage info response packet.
  42. BuildHomeWeb(seqNum uint16, seq2 uint16, uin uint32, info *UserInfoResult) []byte
  43. // BuildWorkWeb constructs a work webpage info response packet.
  44. BuildWorkWeb(seqNum uint16, seq2 uint16, uin uint32, info *UserInfoResult) []byte
  45. // BuildDeptsList constructs a pre-auth deps list response packet.
  46. // Historically called "departments list" in iserverd (from its Users_Deps database table).
  47. BuildDeptsList(seq2 uint16, uin uint32) []byte
  48. }
  49. // V3PacketBuilderImpl implements the V3PacketBuilder interface.
  50. // It constructs V3 protocol packets following the iserverd packet formats.
  51. type V3PacketBuilderImpl struct {
  52. sessionManager *LegacySessionManager
  53. directConnectionEnabled func(version int) bool
  54. }
  55. // NewV3PacketBuilder creates a new V3PacketBuilder instance.
  56. func NewV3PacketBuilder(sessionManager *LegacySessionManager, directConnectionEnabled func(version int) bool) V3PacketBuilder {
  57. return &V3PacketBuilderImpl{
  58. sessionManager: sessionManager,
  59. directConnectionEnabled: directConnectionEnabled,
  60. }
  61. }
  62. // BuildLoginReply constructs a login success (HELLO) response packet.
  63. // From iserverd v3_send_login_reply() - this is a complex packet!
  64. // V3 HELLO format:
  65. // VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + RESERVED(4) +
  66. // CLIENT_IP(4) + RESERVED(4) + EXTERNALS_NUM(4) + DEPLIST_VERSION(4) +
  67. // MUST_CHANGE_PASS(1) + CAN_BROADCAST(1) + UNKNOWN(4) +
  68. // PING_FREQ(2) + PACKET_TIMEOUT(2) + RETRY_TIMEOUT(2) + NUM_RETRIES(2)
  69. func (b *V3PacketBuilderImpl) BuildLoginReply(session *LegacySession, seq1, seq2 uint16) []byte {
  70. // Get client IP as uint32 (network byte order to ICQ format)
  71. var clientIP uint32
  72. if session.Addr != nil && session.Addr.IP != nil {
  73. ip := session.Addr.IP.To4()
  74. if ip != nil {
  75. // ICQ stores IP in little-endian format
  76. clientIP = uint32(ip[0]) | uint32(ip[1])<<8 | uint32(ip[2])<<16 | uint32(ip[3])<<24
  77. }
  78. }
  79. pkt := make([]byte, 46)
  80. offset := 0
  81. // Header
  82. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacyVersionV3)
  83. offset += 2
  84. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacySrvHello)
  85. offset += 2
  86. binary.LittleEndian.PutUint16(pkt[offset:], 0) // seq1 = 0
  87. offset += 2
  88. binary.LittleEndian.PutUint16(pkt[offset:], seq2)
  89. offset += 2
  90. binary.LittleEndian.PutUint32(pkt[offset:], session.UIN)
  91. offset += 4
  92. binary.LittleEndian.PutUint32(pkt[offset:], 0) // reserved
  93. offset += 4
  94. // Client IP
  95. binary.LittleEndian.PutUint32(pkt[offset:], clientIP)
  96. offset += 4
  97. binary.LittleEndian.PutUint32(pkt[offset:], 0) // reserved
  98. offset += 4
  99. // Externals number and deplist version
  100. binary.LittleEndian.PutUint32(pkt[offset:], 0) // externals_num
  101. offset += 4
  102. binary.LittleEndian.PutUint32(pkt[offset:], 1) // deplist_version
  103. offset += 4
  104. // Flags
  105. pkt[offset] = 0 // must_change_password
  106. offset++
  107. pkt[offset] = 0 // can_broadcast
  108. offset++
  109. // Unknown value (0xFA)
  110. binary.LittleEndian.PutUint32(pkt[offset:], 0x000000FA)
  111. offset += 4
  112. // Timing parameters
  113. binary.LittleEndian.PutUint16(pkt[offset:], 50) // ping_frequency (seconds)
  114. offset += 2
  115. binary.LittleEndian.PutUint16(pkt[offset:], 60) // packet_timeout (seconds)
  116. offset += 2
  117. binary.LittleEndian.PutUint16(pkt[offset:], 10) // retry_timeout (seconds)
  118. offset += 2
  119. binary.LittleEndian.PutUint16(pkt[offset:], 3) // num_retries
  120. offset += 2
  121. return pkt[:offset]
  122. }
  123. // BuildBadPassword constructs a wrong password response.
  124. func (b *V3PacketBuilderImpl) BuildBadPassword(seq1, seq2 uint16, uin uint32) []byte {
  125. pkt := make([]byte, 16)
  126. binary.LittleEndian.PutUint16(pkt[0:2], ICQLegacyVersionV3)
  127. binary.LittleEndian.PutUint16(pkt[2:4], ICQLegacySrvWrongPasswd)
  128. binary.LittleEndian.PutUint16(pkt[4:6], 0)
  129. binary.LittleEndian.PutUint16(pkt[6:8], seq2)
  130. binary.LittleEndian.PutUint32(pkt[8:12], uin)
  131. binary.LittleEndian.PutUint32(pkt[12:16], 0)
  132. return pkt
  133. }
  134. // BuildAck constructs an ACK packet (V3 format from iserverd v3_send_ack).
  135. // V3 ACK format: VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + RESERVED(4)
  136. func (b *V3PacketBuilderImpl) BuildAck(seq1, seq2 uint16, uin uint32) []byte {
  137. pkt := make([]byte, 16)
  138. binary.LittleEndian.PutUint16(pkt[0:2], ICQLegacyVersionV3)
  139. binary.LittleEndian.PutUint16(pkt[2:4], ICQLegacySrvAck)
  140. binary.LittleEndian.PutUint16(pkt[4:6], seq1)
  141. binary.LittleEndian.PutUint16(pkt[6:8], seq2)
  142. binary.LittleEndian.PutUint32(pkt[8:12], uin)
  143. binary.LittleEndian.PutUint32(pkt[12:16], 0) // reserved
  144. return pkt
  145. }
  146. // BuildNotConnected constructs a not connected error response.
  147. func (b *V3PacketBuilderImpl) BuildNotConnected(seq2 uint16, uin uint32) []byte {
  148. pkt := make([]byte, 16)
  149. binary.LittleEndian.PutUint16(pkt[0:2], ICQLegacyVersionV3)
  150. binary.LittleEndian.PutUint16(pkt[2:4], ICQLegacySrvNotConnected)
  151. binary.LittleEndian.PutUint16(pkt[4:6], 0)
  152. binary.LittleEndian.PutUint16(pkt[6:8], seq2)
  153. binary.LittleEndian.PutUint32(pkt[8:12], uin)
  154. binary.LittleEndian.PutUint32(pkt[12:16], 0)
  155. return pkt
  156. }
  157. // BuildUserOnline constructs a user online notification packet.
  158. // From iserverd v3_send_user_online()
  159. // V3 USER_ONLINE format:
  160. // header(16) + UIN(4) + IP(4) + PORT(4) + INT_IP(4) + DC_TYPE(1) + STATUS(2) + ESTAT(2) + DCVER(2) + UNKNOWN(2)
  161. // Total: 16 + 4 + 4 + 4 + 4 + 1 + 2 + 2 + 2 + 2 = 41 bytes
  162. func (b *V3PacketBuilderImpl) BuildUserOnline(seqNum uint16, uin uint32, status uint32) []byte {
  163. pkt := make([]byte, 41)
  164. offset := 0
  165. // Header
  166. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacyVersionV3)
  167. offset += 2
  168. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacySrvUserOnline)
  169. offset += 2
  170. binary.LittleEndian.PutUint16(pkt[offset:], seqNum)
  171. offset += 2
  172. binary.LittleEndian.PutUint16(pkt[offset:], 0) // seq2
  173. offset += 2
  174. // Note: UIN field in header will be set by caller (recipient's UIN)
  175. // For now we use 0 as placeholder - caller should set session.UIN
  176. binary.LittleEndian.PutUint32(pkt[offset:], 0) // recipient UIN (set by caller)
  177. offset += 4
  178. binary.LittleEndian.PutUint32(pkt[offset:], 0) // reserved
  179. offset += 4
  180. // UIN of the user who is online
  181. binary.LittleEndian.PutUint32(pkt[offset:], uin)
  182. offset += 4
  183. // Look up connection info if direct connections enabled for V3
  184. var externalIP, tcpPort, internalIP uint32
  185. var dcType uint8
  186. var dcVersion uint16
  187. if b.directConnectionEnabled != nil && b.directConnectionEnabled(int(ICQLegacyVersionV3)) && b.sessionManager != nil {
  188. if onlineSession := b.sessionManager.GetSession(uin); onlineSession != nil {
  189. externalIP = onlineSession.GetExternalIP()
  190. tcpPort = onlineSession.GetTCPPort()
  191. internalIP = onlineSession.GetInternalIP()
  192. dcType = onlineSession.DCType
  193. dcVersion = onlineSession.GetDCVersion()
  194. }
  195. }
  196. // IP address
  197. binary.LittleEndian.PutUint32(pkt[offset:], externalIP)
  198. offset += 4
  199. // TCP port
  200. binary.LittleEndian.PutUint32(pkt[offset:], tcpPort)
  201. offset += 4
  202. // Internal IP
  203. binary.LittleEndian.PutUint32(pkt[offset:], internalIP)
  204. offset += 4
  205. // DC type
  206. pkt[offset] = dcType
  207. offset++
  208. // Status (low word)
  209. binary.LittleEndian.PutUint16(pkt[offset:], uint16(status&0xFFFF))
  210. offset += 2
  211. // Extended status (high word)
  212. binary.LittleEndian.PutUint16(pkt[offset:], uint16(status>>16))
  213. offset += 2
  214. // DC version
  215. binary.LittleEndian.PutUint16(pkt[offset:], dcVersion)
  216. offset += 2
  217. // Unknown
  218. binary.LittleEndian.PutUint16(pkt[offset:], 0)
  219. offset += 2
  220. return pkt[:offset]
  221. }
  222. // BuildUserOffline constructs a user offline notification packet.
  223. // From iserverd v3_send_user_offline()
  224. // V3 USER_OFFLINE format: header(16) + UIN(4)
  225. func (b *V3PacketBuilderImpl) BuildUserOffline(seqNum uint16, uin uint32) []byte {
  226. pkt := make([]byte, 20)
  227. offset := 0
  228. // Header
  229. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacyVersionV3)
  230. offset += 2
  231. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacySrvUserOffline)
  232. offset += 2
  233. binary.LittleEndian.PutUint16(pkt[offset:], seqNum)
  234. offset += 2
  235. binary.LittleEndian.PutUint16(pkt[offset:], 0) // seq2
  236. offset += 2
  237. binary.LittleEndian.PutUint32(pkt[offset:], 0) // recipient UIN (set by caller)
  238. offset += 4
  239. binary.LittleEndian.PutUint32(pkt[offset:], 0) // reserved
  240. offset += 4
  241. // UIN of the user who went offline
  242. binary.LittleEndian.PutUint32(pkt[offset:], uin)
  243. offset += 4
  244. return pkt[:offset]
  245. }
  246. // BuildUserStatus constructs a user status change notification packet.
  247. // From iserverd v3_send_user_status() in make_packet.cpp
  248. // This is used when a user changes status while already online.
  249. // Format: header(16) + UIN(4) + STATUS(2) + ESTAT(2)
  250. func (b *V3PacketBuilderImpl) BuildUserStatus(seqNum uint16, uin uint32, status uint32) []byte {
  251. pkt := make([]byte, 24)
  252. offset := 0
  253. // Header
  254. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacyVersionV3)
  255. offset += 2
  256. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacySrvUserStatus) // 0x01A4
  257. offset += 2
  258. binary.LittleEndian.PutUint16(pkt[offset:], seqNum)
  259. offset += 2
  260. binary.LittleEndian.PutUint16(pkt[offset:], 0) // seq2
  261. offset += 2
  262. binary.LittleEndian.PutUint32(pkt[offset:], 0) // recipient UIN (set by caller)
  263. offset += 4
  264. binary.LittleEndian.PutUint32(pkt[offset:], 0) // reserved
  265. offset += 4
  266. // UIN of the user whose status changed
  267. binary.LittleEndian.PutUint32(pkt[offset:], uin)
  268. offset += 4
  269. // Status (low word)
  270. binary.LittleEndian.PutUint16(pkt[offset:], uint16(status&0xFFFF))
  271. offset += 2
  272. // Extended status (high word)
  273. binary.LittleEndian.PutUint16(pkt[offset:], uint16(status>>16))
  274. offset += 2
  275. return pkt[:offset]
  276. }
  277. // BuildContactListDone constructs a contact list processed response.
  278. func (b *V3PacketBuilderImpl) BuildContactListDone(seqNum uint16, seq2 uint16, uin uint32) []byte {
  279. pkt := make([]byte, 16)
  280. binary.LittleEndian.PutUint16(pkt[0:2], ICQLegacyVersionV3)
  281. binary.LittleEndian.PutUint16(pkt[2:4], ICQLegacySrvUserListDone)
  282. binary.LittleEndian.PutUint16(pkt[4:6], seqNum)
  283. binary.LittleEndian.PutUint16(pkt[6:8], seq2)
  284. binary.LittleEndian.PutUint32(pkt[8:12], uin)
  285. binary.LittleEndian.PutUint32(pkt[12:16], 0)
  286. return pkt
  287. }
  288. // BuildOnlineMessage constructs an online system message packet.
  289. // From iserverd v3_send_user_message()
  290. // Format: header(16) + FROM_UIN(4) + MSG_TYPE(2) + MSG_LEN(2) + MESSAGE
  291. // Note: V3 packets do NOT have a checkcode - they have a reserved field (always 0)
  292. func (b *V3PacketBuilderImpl) BuildOnlineMessage(seqNum uint16, fromUIN uint32, msgType uint16, message string) []byte {
  293. msgBytes := []byte(message)
  294. pktSize := 16 + 4 + 2 + 2 + len(msgBytes) + 1
  295. pkt := make([]byte, pktSize)
  296. offset := 0
  297. // Header
  298. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacyVersionV3)
  299. offset += 2
  300. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacySrvSysMsgOnline) // 0x0104
  301. offset += 2
  302. binary.LittleEndian.PutUint16(pkt[offset:], seqNum)
  303. offset += 2
  304. binary.LittleEndian.PutUint16(pkt[offset:], 0) // seq2
  305. offset += 2
  306. binary.LittleEndian.PutUint32(pkt[offset:], 0) // recipient UIN (set by caller)
  307. offset += 4
  308. binary.LittleEndian.PutUint32(pkt[offset:], 0) // reserved (V3 has NO checkcode!)
  309. offset += 4
  310. // From UIN
  311. binary.LittleEndian.PutUint32(pkt[offset:], fromUIN)
  312. offset += 4
  313. // Message type (mask high byte for V3 clients)
  314. binary.LittleEndian.PutUint16(pkt[offset:], msgType&0x00FF)
  315. offset += 2
  316. // Message length (including null terminator)
  317. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(msgBytes)+1))
  318. offset += 2
  319. // Message content
  320. copy(pkt[offset:], msgBytes)
  321. offset += len(msgBytes)
  322. pkt[offset] = 0 // null terminator
  323. offset++
  324. return pkt[:offset]
  325. }
  326. // BuildBasicInfo constructs a basic user info response packet.
  327. // From iserverd v3_send_basic_info()
  328. // Packet format (verified via Ghidra RE of ICQ98a client handler):
  329. //
  330. // Header: VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + RESERVED(4) = 16 bytes
  331. // Data: TARGET_UIN(4) + NICK_LEN(2) + NICK + FIRST_LEN(2) + FIRST + LAST_LEN(2) + LAST +
  332. // EMAIL_LEN(2) + EMAIL + STATUS(1) + AUTH(1)
  333. //
  334. // All strings are length-prefixed (2 bytes) and null-terminated
  335. func (b *V3PacketBuilderImpl) BuildBasicInfo(seqNum uint16, seq2 uint16, uin uint32, info *UserInfoResult) []byte {
  336. // Default values if info is nil
  337. nick := ""
  338. first := ""
  339. last := ""
  340. email := ""
  341. status := uint8(0)
  342. auth := uint8(0)
  343. if info != nil {
  344. nick = info.Nickname
  345. first = info.FirstName
  346. last = info.LastName
  347. email = info.Email
  348. auth = info.AuthRequired
  349. }
  350. // Calculate packet size
  351. // Header(16) + TARGET_UIN(4) + nick_len(2) + nick + null(1) + first_len(2) + first + null(1) +
  352. // last_len(2) + last + null(1) + email_len(2) + email + null(1) + status(1) + auth(1)
  353. pktSize := 16 + 4 + 2 + len(nick) + 1 + 2 + len(first) + 1 + 2 + len(last) + 1 + 2 + len(email) + 1 + 1 + 1
  354. pkt := make([]byte, pktSize)
  355. offset := 0
  356. // Header - Client expects 0x0118 (ICQLegacySrvInfoReply)
  357. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacyVersionV3)
  358. offset += 2
  359. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacySrvInfoReply) // 0x0118
  360. offset += 2
  361. binary.LittleEndian.PutUint16(pkt[offset:], seqNum)
  362. offset += 2
  363. binary.LittleEndian.PutUint16(pkt[offset:], seq2)
  364. offset += 2
  365. binary.LittleEndian.PutUint32(pkt[offset:], uin) // recipient's UIN
  366. offset += 4
  367. binary.LittleEndian.PutUint32(pkt[offset:], 0) // reserved
  368. offset += 4
  369. // TARGET_UIN - the UIN of the user whose info this is
  370. targetUIN := uint32(0)
  371. if info != nil {
  372. targetUIN = info.UIN
  373. }
  374. binary.LittleEndian.PutUint32(pkt[offset:], targetUIN)
  375. offset += 4
  376. // Nick (length-prefixed with null terminator)
  377. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(nick)+1))
  378. offset += 2
  379. copy(pkt[offset:], nick)
  380. offset += len(nick)
  381. pkt[offset] = 0
  382. offset++
  383. // First name (length-prefixed with null terminator)
  384. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(first)+1))
  385. offset += 2
  386. copy(pkt[offset:], first)
  387. offset += len(first)
  388. pkt[offset] = 0
  389. offset++
  390. // Last name (length-prefixed with null terminator)
  391. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(last)+1))
  392. offset += 2
  393. copy(pkt[offset:], last)
  394. offset += len(last)
  395. pkt[offset] = 0
  396. offset++
  397. // Email (length-prefixed with null terminator)
  398. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(email)+1))
  399. offset += 2
  400. copy(pkt[offset:], email)
  401. offset += len(email)
  402. pkt[offset] = 0
  403. offset++
  404. // Status byte
  405. pkt[offset] = status
  406. offset++
  407. // Auth flag
  408. pkt[offset] = auth
  409. offset++
  410. return pkt[:offset]
  411. }
  412. // BuildHomeInfo constructs a home info response packet.
  413. // From iserverd v3_send_home_info()
  414. // Packet format:
  415. //
  416. // Header: VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + RESERVED(4) = 16 bytes
  417. // Data: HADDR_LEN(2) + HADDR + HCITY_LEN(2) + HCITY + HSTATE_LEN(2) + HSTATE +
  418. // HCOUNTRY(2) + HPHONE_LEN(2) + HPHONE + HFAX_LEN(2) + HFAX + HCELL_LEN(2) + HCELL +
  419. // HZIP(4) + GENDER(1) + AGE(2) + BDAY(1) + BMONTH(1) + BYEAR(1) + 0x00(1)
  420. func (b *V3PacketBuilderImpl) BuildHomeInfo(seqNum uint16, seq2 uint16, uin uint32, info *UserInfoResult) []byte {
  421. // Default values
  422. haddr := ""
  423. hcity := ""
  424. hstate := ""
  425. hcountry := uint16(0)
  426. hphone := ""
  427. hfax := ""
  428. hcell := ""
  429. hzip := uint32(0)
  430. gender := uint8(0)
  431. age := uint16(0)
  432. bday := uint8(0)
  433. bmonth := uint8(0)
  434. byear := uint8(0)
  435. if info != nil {
  436. hcity = info.City
  437. hstate = info.State
  438. hcountry = info.Country
  439. hphone = info.Phone
  440. gender = info.Gender
  441. age = uint16(info.Age)
  442. }
  443. // Calculate packet size
  444. pktSize := 16 + 2 + len(haddr) + 1 + 2 + len(hcity) + 1 + 2 + len(hstate) + 1 +
  445. 2 + 2 + len(hphone) + 1 + 2 + len(hfax) + 1 + 2 + len(hcell) + 1 +
  446. 4 + 1 + 2 + 1 + 1 + 1 + 1
  447. pkt := make([]byte, pktSize)
  448. offset := 0
  449. // Header
  450. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacyVersionV3)
  451. offset += 2
  452. binary.LittleEndian.PutUint16(pkt[offset:], 0x0320) // ICQ_CMDxSND_USERxINFO_HOME
  453. offset += 2
  454. binary.LittleEndian.PutUint16(pkt[offset:], seqNum)
  455. offset += 2
  456. binary.LittleEndian.PutUint16(pkt[offset:], seq2)
  457. offset += 2
  458. binary.LittleEndian.PutUint32(pkt[offset:], uin)
  459. offset += 4
  460. binary.LittleEndian.PutUint32(pkt[offset:], 0)
  461. offset += 4
  462. // Home address
  463. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(haddr)+1))
  464. offset += 2
  465. copy(pkt[offset:], haddr)
  466. offset += len(haddr)
  467. pkt[offset] = 0
  468. offset++
  469. // Home city
  470. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(hcity)+1))
  471. offset += 2
  472. copy(pkt[offset:], hcity)
  473. offset += len(hcity)
  474. pkt[offset] = 0
  475. offset++
  476. // Home state
  477. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(hstate)+1))
  478. offset += 2
  479. copy(pkt[offset:], hstate)
  480. offset += len(hstate)
  481. pkt[offset] = 0
  482. offset++
  483. // Home country
  484. binary.LittleEndian.PutUint16(pkt[offset:], hcountry)
  485. offset += 2
  486. // Home phone
  487. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(hphone)+1))
  488. offset += 2
  489. copy(pkt[offset:], hphone)
  490. offset += len(hphone)
  491. pkt[offset] = 0
  492. offset++
  493. // Home fax
  494. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(hfax)+1))
  495. offset += 2
  496. copy(pkt[offset:], hfax)
  497. offset += len(hfax)
  498. pkt[offset] = 0
  499. offset++
  500. // Home cell
  501. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(hcell)+1))
  502. offset += 2
  503. copy(pkt[offset:], hcell)
  504. offset += len(hcell)
  505. pkt[offset] = 0
  506. offset++
  507. // Home zip
  508. binary.LittleEndian.PutUint32(pkt[offset:], hzip)
  509. offset += 4
  510. // Gender
  511. pkt[offset] = gender
  512. offset++
  513. // Age
  514. binary.LittleEndian.PutUint16(pkt[offset:], age)
  515. offset += 2
  516. // Birthday: day, month, year
  517. pkt[offset] = bday
  518. offset++
  519. pkt[offset] = bmonth
  520. offset++
  521. pkt[offset] = byear
  522. offset++
  523. // Trailing zero
  524. pkt[offset] = 0
  525. offset++
  526. return pkt[:offset]
  527. }
  528. // BuildWorkInfo constructs a work info response packet.
  529. // From iserverd v3_send_work_info()
  530. // Packet format:
  531. //
  532. // Header: VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + RESERVED(4) = 16 bytes
  533. // Data: WADDR_LEN(2) + WADDR + WCITY_LEN(2) + WCITY + WSTATE_LEN(2) + WSTATE +
  534. // WCOUNTRY(2) + WCOMPANY_LEN(2) + WCOMPANY + WTITLE_LEN(2) + WTITLE +
  535. // WDEPART(4) + WPHONE_LEN(2) + WPHONE + WFAX_LEN(2) + WFAX +
  536. // WPAGER_LEN(2) + WPAGER + WZIP(4)
  537. func (b *V3PacketBuilderImpl) BuildWorkInfo(seqNum uint16, seq2 uint16, uin uint32, info *UserInfoResult) []byte {
  538. // Default values
  539. waddr := ""
  540. wcity := ""
  541. wstate := ""
  542. wcountry := uint16(0)
  543. wcompany := ""
  544. wtitle := ""
  545. wdepart := uint32(0)
  546. wphone := ""
  547. wfax := ""
  548. wpager := ""
  549. wzip := uint32(0)
  550. if info != nil {
  551. wcompany = info.Company
  552. wtitle = info.Position
  553. }
  554. // Calculate packet size
  555. pktSize := 16 + 2 + len(waddr) + 1 + 2 + len(wcity) + 1 + 2 + len(wstate) + 1 +
  556. 2 + 2 + len(wcompany) + 1 + 2 + len(wtitle) + 1 + 4 +
  557. 2 + len(wphone) + 1 + 2 + len(wfax) + 1 + 2 + len(wpager) + 1 + 4
  558. pkt := make([]byte, pktSize)
  559. offset := 0
  560. // Header
  561. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacyVersionV3)
  562. offset += 2
  563. binary.LittleEndian.PutUint16(pkt[offset:], 0x02F8) // ICQ_CMDxSND_USERxINFO_WORK
  564. offset += 2
  565. binary.LittleEndian.PutUint16(pkt[offset:], seqNum)
  566. offset += 2
  567. binary.LittleEndian.PutUint16(pkt[offset:], seq2)
  568. offset += 2
  569. binary.LittleEndian.PutUint32(pkt[offset:], uin)
  570. offset += 4
  571. binary.LittleEndian.PutUint32(pkt[offset:], 0)
  572. offset += 4
  573. // Work address
  574. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(waddr)+1))
  575. offset += 2
  576. copy(pkt[offset:], waddr)
  577. offset += len(waddr)
  578. pkt[offset] = 0
  579. offset++
  580. // Work city
  581. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(wcity)+1))
  582. offset += 2
  583. copy(pkt[offset:], wcity)
  584. offset += len(wcity)
  585. pkt[offset] = 0
  586. offset++
  587. // Work state
  588. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(wstate)+1))
  589. offset += 2
  590. copy(pkt[offset:], wstate)
  591. offset += len(wstate)
  592. pkt[offset] = 0
  593. offset++
  594. // Work country
  595. binary.LittleEndian.PutUint16(pkt[offset:], wcountry)
  596. offset += 2
  597. // Work company
  598. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(wcompany)+1))
  599. offset += 2
  600. copy(pkt[offset:], wcompany)
  601. offset += len(wcompany)
  602. pkt[offset] = 0
  603. offset++
  604. // Work title
  605. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(wtitle)+1))
  606. offset += 2
  607. copy(pkt[offset:], wtitle)
  608. offset += len(wtitle)
  609. pkt[offset] = 0
  610. offset++
  611. // Work department
  612. binary.LittleEndian.PutUint32(pkt[offset:], wdepart)
  613. offset += 4
  614. // Work phone
  615. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(wphone)+1))
  616. offset += 2
  617. copy(pkt[offset:], wphone)
  618. offset += len(wphone)
  619. pkt[offset] = 0
  620. offset++
  621. // Work fax
  622. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(wfax)+1))
  623. offset += 2
  624. copy(pkt[offset:], wfax)
  625. offset += len(wfax)
  626. pkt[offset] = 0
  627. offset++
  628. // Work pager
  629. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(wpager)+1))
  630. offset += 2
  631. copy(pkt[offset:], wpager)
  632. offset += len(wpager)
  633. pkt[offset] = 0
  634. offset++
  635. // Work zip
  636. binary.LittleEndian.PutUint32(pkt[offset:], wzip)
  637. offset += 4
  638. return pkt[:offset]
  639. }
  640. // BuildHomeWeb constructs a home webpage info response packet.
  641. // From iserverd v3_send_home_web()
  642. // Packet format:
  643. //
  644. // Header: VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + RESERVED(4) = 16 bytes
  645. // Data: HPAGE_LEN(2) + HPAGE (null-terminated string)
  646. func (b *V3PacketBuilderImpl) BuildHomeWeb(seqNum uint16, seq2 uint16, uin uint32, info *UserInfoResult) []byte {
  647. // Default value
  648. hpage := ""
  649. if info != nil {
  650. hpage = info.Homepage
  651. }
  652. // Calculate packet size
  653. // Header(16) + hpage_len(2) + hpage + null(1)
  654. pktSize := 16 + 2 + len(hpage) + 1
  655. pkt := make([]byte, pktSize)
  656. offset := 0
  657. // Header
  658. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacyVersionV3)
  659. offset += 2
  660. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacySrvUserInfoHWeb) // 0x0334
  661. offset += 2
  662. binary.LittleEndian.PutUint16(pkt[offset:], seqNum)
  663. offset += 2
  664. binary.LittleEndian.PutUint16(pkt[offset:], seq2)
  665. offset += 2
  666. binary.LittleEndian.PutUint32(pkt[offset:], uin)
  667. offset += 4
  668. binary.LittleEndian.PutUint32(pkt[offset:], 0)
  669. offset += 4
  670. // Home page URL (length-prefixed with null terminator)
  671. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(hpage)+1))
  672. offset += 2
  673. copy(pkt[offset:], hpage)
  674. offset += len(hpage)
  675. pkt[offset] = 0
  676. offset++
  677. return pkt[:offset]
  678. }
  679. // BuildWorkWeb constructs a work webpage info response packet.
  680. // From iserverd v3_send_work_web()
  681. // Packet format:
  682. //
  683. // Header: VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + RESERVED(4) = 16 bytes
  684. // Data: WPAGE_LEN(2) + WPAGE (null-terminated string)
  685. func (b *V3PacketBuilderImpl) BuildWorkWeb(seqNum uint16, seq2 uint16, uin uint32, info *UserInfoResult) []byte {
  686. // Default value - work webpage is not in UserInfoResult, so we use empty string
  687. // In the future, this could be added to UserInfoResult if needed
  688. wpage := ""
  689. // Calculate packet size
  690. // Header(16) + wpage_len(2) + wpage + null(1)
  691. pktSize := 16 + 2 + len(wpage) + 1
  692. pkt := make([]byte, pktSize)
  693. offset := 0
  694. // Header
  695. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacyVersionV3)
  696. offset += 2
  697. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacySrvUserInfoWWeb) // 0x030C
  698. offset += 2
  699. binary.LittleEndian.PutUint16(pkt[offset:], seqNum)
  700. offset += 2
  701. binary.LittleEndian.PutUint16(pkt[offset:], seq2)
  702. offset += 2
  703. binary.LittleEndian.PutUint32(pkt[offset:], uin)
  704. offset += 4
  705. binary.LittleEndian.PutUint32(pkt[offset:], 0)
  706. offset += 4
  707. // Work page URL (length-prefixed with null terminator)
  708. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(wpage)+1))
  709. offset += 2
  710. copy(pkt[offset:], wpage)
  711. offset += len(wpage)
  712. pkt[offset] = 0
  713. offset++
  714. return pkt[:offset]
  715. }
  716. // BuildDeptsList constructs a pre-auth deps list response packet.
  717. // Historically called "departments list" in iserverd (from its Users_Deps database table).
  718. // From iserverd v3_send_depslist()
  719. // V3 DEPS_LIST format: header(16) + DEPLIST_VERSION(4) + COUNT(4) + [deps...] + TRAILER(4)
  720. func (b *V3PacketBuilderImpl) BuildDeptsList(seq2 uint16, uin uint32) []byte {
  721. // For now, send empty list with version 1
  722. pkt := make([]byte, 28)
  723. binary.LittleEndian.PutUint16(pkt[0:2], ICQLegacyVersionV3)
  724. binary.LittleEndian.PutUint16(pkt[2:4], ICQLegacySrvUserDepsList) // 0x0032
  725. binary.LittleEndian.PutUint16(pkt[4:6], 0) // servseq
  726. binary.LittleEndian.PutUint16(pkt[6:8], seq2)
  727. binary.LittleEndian.PutUint32(pkt[8:12], uin)
  728. binary.LittleEndian.PutUint32(pkt[12:16], 0) // reserved
  729. binary.LittleEndian.PutUint32(pkt[16:20], 1) // deplist version
  730. binary.LittleEndian.PutUint32(pkt[20:24], 0) // count = 0 (empty deps list)
  731. // Trailer from iserverd
  732. binary.LittleEndian.PutUint16(pkt[24:26], 0x0002)
  733. binary.LittleEndian.PutUint16(pkt[26:28], 0x002a)
  734. return pkt
  735. }