v4_packet_builder.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530
  1. package icq_legacy
  2. import (
  3. "encoding/binary"
  4. "fmt"
  5. )
  6. // V4PacketBuilder constructs V4 protocol packets.
  7. // V4 is similar to V3 but with checkcode calculation for packet validation.
  8. // This interface separates packet construction from business logic,
  9. // following the OSCAR foodgroup architecture pattern.
  10. //
  11. // V4 packet format (from wumpus documentation):
  12. // VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + CHECKCODE(4) + DATA...
  13. // Total header: 16 bytes (V3 format with checkcode at offset 12)
  14. //
  15. // Note: Server sends V3 format packets to V4 clients, but with a valid checkcode
  16. // that the client validates using the V4 checkcode algorithm.
  17. type V4PacketBuilder interface {
  18. // BuildLoginReply constructs a login success (HELLO) response packet.
  19. // The packet contains timing parameters and client IP information.
  20. BuildLoginReply(session *LegacySession, seq2 uint16) []byte
  21. // BuildBadPassword constructs a bad password/authentication failure response.
  22. BuildBadPassword(seq1, seq2 uint16, uin uint32) []byte
  23. // BuildAck constructs an acknowledgment packet for the given sequence numbers.
  24. BuildAck(seq1, seq2 uint16, uin uint32) []byte
  25. // BuildNotConnected constructs a "not connected" error response.
  26. BuildNotConnected(seq2 uint16, uin uint32) []byte
  27. // BuildUserOnline constructs a user online notification packet.
  28. // Sent to notify a user that one of their contacts has come online.
  29. BuildUserOnline(seqNum uint16, uin uint32, status uint32) []byte
  30. // BuildContactListDone constructs a contact list processing complete response.
  31. BuildContactListDone(seqNum uint16, seq2 uint16, uin uint32) []byte
  32. // BuildOnlineMessage constructs an online system message packet.
  33. BuildOnlineMessage(seqNum uint16, fromUIN uint32, msgType uint16, message string) []byte
  34. // BuildBasicInfo constructs a basic user info response packet.
  35. BuildBasicInfo(seqNum uint16, seq2 uint16, uin uint32, info *UserInfoResult) []byte
  36. // BuildRegisterInfo constructs a registration info response packet.
  37. // Sent to clients starting the registration wizard.
  38. BuildRegisterInfo(seq2 uint16, uin uint32) []byte
  39. // BuildRegistrationOK constructs a registration success response packet.
  40. // Contains the new UIN and password for the registered user.
  41. BuildRegistrationOK(seq2 uint16, newUIN uint32, password string) []byte
  42. // CalculateCheckcode calculates the V4 checkcode for a packet.
  43. // The checkcode is used by V4 clients to validate server packets.
  44. CalculateCheckcode(packet []byte) uint32
  45. }
  46. // V4PacketBuilderImpl implements the V4PacketBuilder interface.
  47. // It constructs V4 protocol packets with proper checkcode calculation.
  48. type V4PacketBuilderImpl struct {
  49. sessionManager *LegacySessionManager
  50. directConnectionEnabled func(version int) bool
  51. }
  52. // NewV4PacketBuilder creates a new V4PacketBuilder instance.
  53. func NewV4PacketBuilder(sessionManager *LegacySessionManager, directConnectionEnabled func(version int) bool) V4PacketBuilder {
  54. return &V4PacketBuilderImpl{
  55. sessionManager: sessionManager,
  56. directConnectionEnabled: directConnectionEnabled,
  57. }
  58. }
  59. // CalculateCheckcode calculates the checkcode for server packets to V4 clients.
  60. // Based on reverse engineering of ICQ 98a client (encrypt_v4_packet and validate_v4_checkcode)
  61. //
  62. // The client validates checkcode as follows:
  63. //
  64. // checkA = (byte[8] << 24) | (byte[4] << 16) | (byte[2] << 8) | byte[6]
  65. // result = checkA ^ checkcode
  66. // valid if: (result >> 16) & 0xFF == ~packet[result >> 24]
  67. // and: result & 0xFF == ~table[(result >> 8) & 0xFF]
  68. //
  69. // So we construct checkcode such that after XOR with checkA, the result contains:
  70. //
  71. // byte 3 (bits 24-31): packetOfs - offset into packet
  72. // byte 2 (bits 16-23): ~packet[packetOfs] - inverted byte at that offset
  73. // byte 1 (bits 8-15): tableOfs - offset into table
  74. // byte 0 (bits 0-7): ~table[tableOfs] - inverted byte at that table offset
  75. func (b *V4PacketBuilderImpl) CalculateCheckcode(packet []byte) uint32 {
  76. if len(packet) < 12 {
  77. return 0
  78. }
  79. // checkA: pack bytes 8, 4, 2, 6 from packet (MSB to LSB)
  80. checkA := uint32(packet[8])<<24 | uint32(packet[4])<<16 | uint32(packet[2])<<8 | uint32(packet[6])
  81. // Choose offsets (we use fixed values, but could be random)
  82. // Avoid offset 0x10-0x13 (checkcode position) - use offset 4 (seq1 low byte)
  83. packetOfs := byte(4)
  84. tableOfs := byte(0)
  85. // Get values and INVERT them (this is what the client expects)
  86. packetVal := ^packet[packetOfs] // inverted
  87. tableVal := ^V4Table[tableOfs] // inverted
  88. // Build checkB with inverted values
  89. checkB := uint32(packetOfs)<<24 | uint32(packetVal)<<16 | uint32(tableOfs)<<8 | uint32(tableVal)
  90. // checkcode = checkA ^ checkB
  91. return checkA ^ checkB
  92. }
  93. // BuildLoginReply constructs a login success (HELLO) response packet.
  94. // V3 server packet format with checkcode for V4 clients.
  95. // From ICQ98a client reverse engineering:
  96. // Header: 16 bytes (VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + CHECKCODE(4))
  97. // Data: IP(4) + 10 WORDs(20) = 24 bytes
  98. // Total: 40 bytes
  99. func (b *V4PacketBuilderImpl) BuildLoginReply(session *LegacySession, seq2 uint16) []byte {
  100. // Get client IP as 4 bytes in network byte order (big-endian)
  101. var ipBytes [4]byte
  102. if session.Addr != nil && session.Addr.IP != nil {
  103. ip := session.Addr.IP.To4()
  104. if ip != nil {
  105. copy(ipBytes[:], ip)
  106. }
  107. }
  108. pkt := make([]byte, 40)
  109. offset := 0
  110. // V3 Header (16 bytes)
  111. serverSeq := session.NextServerSeqNum()
  112. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacyVersionV3) // 03 00
  113. offset += 2
  114. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacySrvHello) // 5a 00
  115. offset += 2
  116. binary.LittleEndian.PutUint16(pkt[offset:], serverSeq) // server's seq1
  117. offset += 2
  118. binary.LittleEndian.PutUint16(pkt[offset:], seq2) // seq2 of login packet
  119. offset += 2
  120. binary.LittleEndian.PutUint32(pkt[offset:], session.UIN) // UIN
  121. offset += 4
  122. // Calculate and add checkcode
  123. checkcode := b.CalculateCheckcode(pkt)
  124. binary.LittleEndian.PutUint32(pkt[offset:], checkcode)
  125. offset += 4
  126. // Data section for LOGIN_REPLY (0x005A):
  127. // IP is in network byte order (big-endian): c0 a8 0a 01 = 192.168.10.1
  128. pkt[offset] = ipBytes[0]
  129. pkt[offset+1] = ipBytes[1]
  130. pkt[offset+2] = ipBytes[2]
  131. pkt[offset+3] = ipBytes[3]
  132. offset += 4
  133. // 6 WORDs that get stored in globals:
  134. binary.LittleEndian.PutUint16(pkt[offset:], 0x0000) // -> DAT_004df54c
  135. offset += 2
  136. binary.LittleEndian.PutUint16(pkt[offset:], 0x0000) // -> DAT_004df438
  137. offset += 2
  138. binary.LittleEndian.PutUint16(pkt[offset:], 0x0019) // -> DAT_004df7a8 (keep-alive interval = 25)
  139. offset += 2
  140. binary.LittleEndian.PutUint16(pkt[offset:], 0x0002) // -> DAT_004df550
  141. offset += 2
  142. binary.LittleEndian.PutUint16(pkt[offset:], 0x0001) // -> DAT_004d4178
  143. offset += 2
  144. binary.LittleEndian.PutUint16(pkt[offset:], 0x00FA) // -> DAT_004df820 (250 = timeout?)
  145. offset += 2
  146. // 4 WORDs that are discarded:
  147. binary.LittleEndian.PutUint16(pkt[offset:], 0x002D) // discarded
  148. offset += 2
  149. binary.LittleEndian.PutUint16(pkt[offset:], 0x0005) // discarded
  150. offset += 2
  151. binary.LittleEndian.PutUint16(pkt[offset:], 0x000A) // discarded
  152. offset += 2
  153. binary.LittleEndian.PutUint16(pkt[offset:], 0x0005) // discarded
  154. offset += 2
  155. return pkt[:offset]
  156. }
  157. // BuildBadPassword constructs a wrong password response.
  158. // V3 server packet format: VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + ZERO(4)
  159. func (b *V4PacketBuilderImpl) BuildBadPassword(seq1, seq2 uint16, uin uint32) []byte {
  160. pkt := make([]byte, 16)
  161. binary.LittleEndian.PutUint16(pkt[0:2], ICQLegacyVersionV3)
  162. binary.LittleEndian.PutUint16(pkt[2:4], ICQLegacySrvWrongPasswd)
  163. binary.LittleEndian.PutUint16(pkt[4:6], seq1)
  164. binary.LittleEndian.PutUint16(pkt[6:8], seq2)
  165. binary.LittleEndian.PutUint32(pkt[8:12], uin)
  166. binary.LittleEndian.PutUint32(pkt[12:16], 0) // Zero field
  167. return pkt
  168. }
  169. // BuildAck constructs an ACK packet in V3 format with checkcode.
  170. func (b *V4PacketBuilderImpl) BuildAck(seq1, seq2 uint16, uin uint32) []byte {
  171. pkt := make([]byte, 16)
  172. binary.LittleEndian.PutUint16(pkt[0:2], ICQLegacyVersionV3)
  173. binary.LittleEndian.PutUint16(pkt[2:4], ICQLegacySrvAck)
  174. binary.LittleEndian.PutUint16(pkt[4:6], seq1)
  175. binary.LittleEndian.PutUint16(pkt[6:8], seq2)
  176. binary.LittleEndian.PutUint32(pkt[8:12], uin)
  177. // Calculate and add checkcode
  178. checkcode := b.CalculateCheckcode(pkt)
  179. binary.LittleEndian.PutUint32(pkt[12:16], checkcode)
  180. return pkt
  181. }
  182. // BuildNotConnected constructs a not connected error response.
  183. // V3 server packet format: VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + ZERO(4)
  184. func (b *V4PacketBuilderImpl) BuildNotConnected(seq2 uint16, uin uint32) []byte {
  185. pkt := make([]byte, 16)
  186. binary.LittleEndian.PutUint16(pkt[0:2], ICQLegacyVersionV3)
  187. binary.LittleEndian.PutUint16(pkt[2:4], ICQLegacySrvNotConnected)
  188. binary.LittleEndian.PutUint16(pkt[4:6], 0)
  189. binary.LittleEndian.PutUint16(pkt[6:8], seq2)
  190. binary.LittleEndian.PutUint32(pkt[8:12], uin)
  191. binary.LittleEndian.PutUint32(pkt[12:16], 0) // Zero field
  192. return pkt
  193. }
  194. // BuildUserOnline constructs a user online notification packet.
  195. // From iserverd v3_send_user_online() - V3 and V4 share the same server packet format.
  196. // Data: UIN(4) + IP(4) + PORT(4) + REAL_IP(4) + DC_TYPE(1) + STATUS(2) + ESTATUS(2) + DCVER(2) + UNKNOWN(2)
  197. // Total data: 25 bytes, total packet: 41 bytes
  198. func (b *V4PacketBuilderImpl) BuildUserOnline(seqNum uint16, uin uint32, status uint32) []byte {
  199. pkt := make([]byte, 41)
  200. binary.LittleEndian.PutUint16(pkt[0:2], ICQLegacyVersionV3)
  201. binary.LittleEndian.PutUint16(pkt[2:4], ICQLegacySrvUserOnline)
  202. binary.LittleEndian.PutUint16(pkt[4:6], seqNum)
  203. binary.LittleEndian.PutUint16(pkt[6:8], 0)
  204. binary.LittleEndian.PutUint32(pkt[8:12], 0) // recipient UIN (set by caller)
  205. // Calculate and add checkcode
  206. checkcode := b.CalculateCheckcode(pkt)
  207. binary.LittleEndian.PutUint32(pkt[12:16], checkcode)
  208. offset := 16
  209. // UIN of user who came online
  210. binary.LittleEndian.PutUint32(pkt[offset:], uin)
  211. offset += 4
  212. // Look up connection info if direct connections enabled for V4
  213. var externalIP, tcpPort, internalIP uint32
  214. var dcType uint8
  215. var dcVersion uint16
  216. if b.directConnectionEnabled != nil && b.directConnectionEnabled(int(ICQLegacyVersionV4)) && b.sessionManager != nil {
  217. if onlineSession := b.sessionManager.GetSession(uin); onlineSession != nil {
  218. externalIP = onlineSession.GetExternalIP()
  219. tcpPort = onlineSession.GetTCPPort()
  220. internalIP = onlineSession.GetInternalIP()
  221. dcType = onlineSession.DCType
  222. dcVersion = onlineSession.GetDCVersion()
  223. }
  224. }
  225. // IP address
  226. binary.LittleEndian.PutUint32(pkt[offset:], externalIP)
  227. offset += 4
  228. // TCP port
  229. binary.LittleEndian.PutUint32(pkt[offset:], tcpPort)
  230. offset += 4
  231. // Internal/real IP
  232. binary.LittleEndian.PutUint32(pkt[offset:], internalIP)
  233. offset += 4
  234. // DC type
  235. pkt[offset] = dcType
  236. offset++
  237. // Status (low 16 bits)
  238. binary.LittleEndian.PutUint16(pkt[offset:], uint16(status&0xFFFF))
  239. offset += 2
  240. // Extended status (high 16 bits)
  241. binary.LittleEndian.PutUint16(pkt[offset:], uint16(status>>16))
  242. offset += 2
  243. // DC version
  244. binary.LittleEndian.PutUint16(pkt[offset:], dcVersion)
  245. offset += 2
  246. // Unknown (2 bytes)
  247. offset += 2
  248. return pkt[:offset]
  249. }
  250. // BuildContactListDone constructs a contact list processed response.
  251. // V3 server packet format with checkcode: VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + CHECKCODE(4)
  252. func (b *V4PacketBuilderImpl) BuildContactListDone(seqNum uint16, seq2 uint16, uin uint32) []byte {
  253. pkt := make([]byte, 16)
  254. binary.LittleEndian.PutUint16(pkt[0:2], ICQLegacyVersionV3)
  255. binary.LittleEndian.PutUint16(pkt[2:4], ICQLegacySrvUserListDone)
  256. binary.LittleEndian.PutUint16(pkt[4:6], seqNum)
  257. binary.LittleEndian.PutUint16(pkt[6:8], seq2)
  258. binary.LittleEndian.PutUint32(pkt[8:12], uin)
  259. // Calculate and add checkcode
  260. checkcode := b.CalculateCheckcode(pkt)
  261. binary.LittleEndian.PutUint32(pkt[12:16], checkcode)
  262. return pkt
  263. }
  264. // BuildOnlineMessage constructs an online system message packet.
  265. // V3 server packet format with checkcode.
  266. // Format: header(16) + FROM_UIN(4) + MSG_TYPE(2) + MSG_LEN(2) + MESSAGE
  267. func (b *V4PacketBuilderImpl) BuildOnlineMessage(seqNum uint16, fromUIN uint32, msgType uint16, message string) []byte {
  268. msgBytes := []byte(message)
  269. pktSize := 16 + 4 + 2 + 2 + len(msgBytes) + 1
  270. pkt := make([]byte, pktSize)
  271. offset := 0
  272. // Header
  273. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacyVersionV3)
  274. offset += 2
  275. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacySrvSysMsgOnline) // 0x0104
  276. offset += 2
  277. binary.LittleEndian.PutUint16(pkt[offset:], seqNum)
  278. offset += 2
  279. binary.LittleEndian.PutUint16(pkt[offset:], 0) // seq2
  280. offset += 2
  281. binary.LittleEndian.PutUint32(pkt[offset:], 0) // recipient UIN (set by caller)
  282. offset += 4
  283. // Calculate and add checkcode
  284. checkcode := b.CalculateCheckcode(pkt)
  285. binary.LittleEndian.PutUint32(pkt[offset:], checkcode)
  286. offset += 4
  287. // From UIN
  288. binary.LittleEndian.PutUint32(pkt[offset:], fromUIN)
  289. offset += 4
  290. // Message type (mask high byte for V3 clients)
  291. binary.LittleEndian.PutUint16(pkt[offset:], msgType&0x00FF)
  292. offset += 2
  293. // Message length (including null terminator)
  294. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(msgBytes)+1))
  295. offset += 2
  296. // Message content
  297. copy(pkt[offset:], msgBytes)
  298. offset += len(msgBytes)
  299. pkt[offset] = 0 // null terminator
  300. offset++
  301. return pkt[:offset]
  302. }
  303. // BuildBasicInfo constructs a basic user info response packet.
  304. // V3 server packet format with checkcode.
  305. // Data format (verified via Ghidra RE of ICQ98a client handler):
  306. //
  307. // TARGET_UIN(4) + NICK_LEN(2) + NICK + FNAME_LEN(2) + FNAME + LNAME_LEN(2) + LNAME +
  308. // EMAIL_LEN(2) + EMAIL + STATUS(1) + AUTH(1)
  309. func (b *V4PacketBuilderImpl) BuildBasicInfo(seqNum uint16, seq2 uint16, uin uint32, info *UserInfoResult) []byte {
  310. // Default values if info is nil
  311. nick := ""
  312. first := ""
  313. last := ""
  314. email := ""
  315. statusByte := byte(0)
  316. auth := byte(0)
  317. targetUIN := uint32(0)
  318. if info != nil {
  319. nick = info.Nickname
  320. first = info.FirstName
  321. last = info.LastName
  322. email = info.Email
  323. auth = info.AuthRequired
  324. targetUIN = info.UIN
  325. if nick == "" {
  326. nick = fmt.Sprintf("%d", targetUIN)
  327. }
  328. }
  329. // Calculate packet size
  330. // Header(16) + TARGET_UIN(4) + nick_len(2) + nick + null(1) + first_len(2) + first + null(1) +
  331. // last_len(2) + last + null(1) + email_len(2) + email + null(1) + status(1) + auth(1)
  332. pktSize := 16 + 4 + 2 + len(nick) + 1 + 2 + len(first) + 1 + 2 + len(last) + 1 + 2 + len(email) + 1 + 1 + 1
  333. pkt := make([]byte, pktSize)
  334. offset := 0
  335. // Header - Client expects 0x0118 (ICQLegacySrvInfoReply)
  336. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacyVersionV3)
  337. offset += 2
  338. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacySrvInfoReply) // 0x0118
  339. offset += 2
  340. binary.LittleEndian.PutUint16(pkt[offset:], seqNum)
  341. offset += 2
  342. binary.LittleEndian.PutUint16(pkt[offset:], seq2)
  343. offset += 2
  344. binary.LittleEndian.PutUint32(pkt[offset:], uin) // recipient's UIN
  345. offset += 4
  346. // Calculate and add checkcode
  347. checkcode := b.CalculateCheckcode(pkt)
  348. binary.LittleEndian.PutUint32(pkt[offset:], checkcode)
  349. offset += 4
  350. // TARGET_UIN - the UIN of the user whose info this is
  351. binary.LittleEndian.PutUint32(pkt[offset:], targetUIN)
  352. offset += 4
  353. // Nick (length-prefixed with null terminator)
  354. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(nick)+1))
  355. offset += 2
  356. copy(pkt[offset:], nick)
  357. offset += len(nick)
  358. pkt[offset] = 0
  359. offset++
  360. // First name (length-prefixed with null terminator)
  361. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(first)+1))
  362. offset += 2
  363. copy(pkt[offset:], first)
  364. offset += len(first)
  365. pkt[offset] = 0
  366. offset++
  367. // Last name (length-prefixed with null terminator)
  368. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(last)+1))
  369. offset += 2
  370. copy(pkt[offset:], last)
  371. offset += len(last)
  372. pkt[offset] = 0
  373. offset++
  374. // Email (length-prefixed with null terminator)
  375. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(email)+1))
  376. offset += 2
  377. copy(pkt[offset:], email)
  378. offset += len(email)
  379. pkt[offset] = 0
  380. offset++
  381. // Status byte
  382. pkt[offset] = statusByte
  383. offset++
  384. // Auth flag
  385. pkt[offset] = auth
  386. offset++
  387. return pkt[:offset]
  388. }
  389. // BuildRegisterInfo constructs a registration info response packet.
  390. // V3 server packet format with checkcode.
  391. // From iserverd v3_send_registration_info(): sends 0x037A with admin notes
  392. func (b *V4PacketBuilderImpl) BuildRegisterInfo(seq2 uint16, uin uint32) []byte {
  393. // Admin notes message - tells the user about registration
  394. adminNotes := "Welcome to Open OSCAR Server!\nRegistration is enabled.\x00"
  395. // Build packet
  396. // Header (16 bytes) + NOTES_LEN(2) + NOTES + REG_ENABLED(1) + UNKNOWN(4)
  397. pktSize := 16 + 2 + len(adminNotes) + 1 + 4
  398. pkt := make([]byte, pktSize)
  399. offset := 0
  400. // V3 Header
  401. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacyVersionV3)
  402. offset += 2
  403. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacySrvRegisterInfo)
  404. offset += 2
  405. binary.LittleEndian.PutUint16(pkt[offset:], 0) // seq1 = 0 for server-initiated
  406. offset += 2
  407. binary.LittleEndian.PutUint16(pkt[offset:], seq2)
  408. offset += 2
  409. binary.LittleEndian.PutUint32(pkt[offset:], uin)
  410. offset += 4
  411. // Calculate and add checkcode
  412. checkcode := b.CalculateCheckcode(pkt)
  413. binary.LittleEndian.PutUint32(pkt[offset:], checkcode)
  414. offset += 4
  415. // Admin notes length and string
  416. binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(adminNotes)))
  417. offset += 2
  418. copy(pkt[offset:], adminNotes)
  419. offset += len(adminNotes)
  420. // Registration enabled flag (1 = enabled)
  421. pkt[offset] = 0x01
  422. offset++
  423. // Unknown fields (from iserverd: 0x0002, 0x002A)
  424. binary.LittleEndian.PutUint16(pkt[offset:], 0x0002)
  425. offset += 2
  426. binary.LittleEndian.PutUint16(pkt[offset:], 0x002A)
  427. offset += 2
  428. return pkt[:offset]
  429. }
  430. // BuildRegistrationOK constructs a registration success response packet.
  431. // V4 client (licq) expects ICQ_CMDxRCV_NEWxUIN (0x0046).
  432. // The client reads the new UIN from the header's UIN field, then calls icqLogon().
  433. // V3 server packet format with checkcode: VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + CHECKCODE(4)
  434. func (b *V4PacketBuilderImpl) BuildRegistrationOK(seq2 uint16, newUIN uint32, password string) []byte {
  435. pkt := make([]byte, 16)
  436. offset := 0
  437. // V3 Header - UIN field contains the NEW UIN
  438. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacyVersionV3)
  439. offset += 2
  440. binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacySrvNewUIN) // 0x0046
  441. offset += 2
  442. binary.LittleEndian.PutUint16(pkt[offset:], 0) // seq1 = 0 for server-initiated
  443. offset += 2
  444. binary.LittleEndian.PutUint16(pkt[offset:], seq2)
  445. offset += 2
  446. binary.LittleEndian.PutUint32(pkt[offset:], newUIN) // NEW UIN in header
  447. offset += 4
  448. // Calculate and add checkcode
  449. checkcode := b.CalculateCheckcode(pkt)
  450. binary.LittleEndian.PutUint32(pkt[offset:], checkcode)
  451. offset += 4
  452. return pkt[:offset]
  453. }