v2_packet_builder.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. package icq_legacy
  2. import (
  3. "encoding/binary"
  4. "net"
  5. )
  6. // V2PacketBuilder constructs V2 protocol packets.
  7. // This interface separates packet construction from business logic,
  8. // following the OSCAR foodgroup architecture pattern.
  9. //
  10. // V2 is the simplest ICQ protocol version with no encryption.
  11. // Packet format: VERSION(2) + COMMAND(2) + SEQNUM(2) + DATA
  12. type V2PacketBuilder interface {
  13. // BuildLoginReply constructs a login success response packet.
  14. // The packet contains the user's UIN, IP address, and session info.
  15. BuildLoginReply(session *LegacySession, clientConnectionID uint16) []byte
  16. // BuildBadPassword constructs a bad password/authentication failure response.
  17. BuildBadPassword(seqNum uint16, version uint16) []byte
  18. // BuildAck constructs an acknowledgment packet for the given sequence number.
  19. BuildAck(seqNum uint16, version uint16) []byte
  20. // BuildUserOnline constructs a user online notification packet.
  21. // Sent to notify a user that one of their contacts has come online.
  22. BuildUserOnline(seqNum uint16, uin uint32, status uint32, ip net.IP, port uint16) []byte
  23. // BuildUserOffline constructs a user offline notification packet.
  24. // Sent to notify a user that one of their contacts has gone offline.
  25. BuildUserOffline(seqNum uint16, uin uint32) []byte
  26. // BuildContactListDone constructs a contact list processing complete response.
  27. // Sent after processing a user's contact list to indicate completion.
  28. BuildContactListDone(seqNum uint16, uin uint32) []byte
  29. // BuildMessage constructs a message delivery packet.
  30. // Used for both online messages and offline message delivery.
  31. BuildMessage(seqNum uint16, fromUIN uint32, msgType uint16, message string) []byte
  32. // BuildSearchResult constructs a user search result packet.
  33. // If isLast is true, uses the "search done" command; otherwise "search found".
  34. BuildSearchResult(seqNum uint16, info *UserInfoResult, isLast bool) []byte
  35. // BuildStatusUpdate constructs a status change notification packet.
  36. // Sent to notify contacts when a user changes their status.
  37. BuildStatusUpdate(seqNum uint16, uin uint32, status uint32) []byte
  38. // BuildOfflineMsgDone constructs an end-of-offline-messages packet.
  39. // Sent after delivering all offline messages to indicate completion.
  40. BuildOfflineMsgDone(seqNum uint16) []byte
  41. // BuildDepsList constructs a pre-auth response packet (0x0032).
  42. // Historically called "departments list" in iserverd (from its Users_Deps database table).
  43. // Used in the pre-login flow: client sends 0x03F2 with credentials, server validates
  44. // and sends this response, then the client proceeds with the real login (0x03E8).
  45. BuildDepsList(seqNum uint16, uin uint32) []byte
  46. }
  47. // V2PacketBuilderImpl implements the V2PacketBuilder interface.
  48. // It constructs V2 protocol packets using the wire package helpers.
  49. type V2PacketBuilderImpl struct{}
  50. // NewV2PacketBuilder creates a new V2PacketBuilder instance.
  51. func NewV2PacketBuilder() V2PacketBuilder {
  52. return &V2PacketBuilderImpl{}
  53. }
  54. // BuildLoginReply constructs a login success response packet.
  55. // V2 LOGIN_REPLY format (from protocol spec):
  56. // USER_UIN(4) + USER_IP(4) + LOGIN_SEQ_NUM(2) + X1(4) + X2(4) + X3(4) + X4(4) + X5(6) = 32 bytes
  57. func (b *V2PacketBuilderImpl) BuildLoginReply(session *LegacySession, clientConnectionID uint16) []byte {
  58. var clientIP net.IP
  59. if session.Addr != nil {
  60. clientIP = session.Addr.IP
  61. }
  62. serverSeq := session.NextServerSeqNum()
  63. pkt := BuildV2LoginReply(serverSeq, clientConnectionID, session.UIN, clientIP)
  64. pkt.Version = session.Version
  65. return MarshalV2ServerPacket(pkt)
  66. }
  67. // BuildBadPassword constructs a bad password/authentication failure response.
  68. func (b *V2PacketBuilderImpl) BuildBadPassword(seqNum uint16, version uint16) []byte {
  69. pkt := BuildV2BadPassword(seqNum)
  70. pkt.Version = version
  71. return MarshalV2ServerPacket(pkt)
  72. }
  73. // BuildAck constructs an acknowledgment packet.
  74. func (b *V2PacketBuilderImpl) BuildAck(seqNum uint16, version uint16) []byte {
  75. pkt := BuildV2Ack(seqNum)
  76. pkt.Version = version
  77. return MarshalV2ServerPacket(pkt)
  78. }
  79. // BuildUserOnline constructs a user online notification packet.
  80. // V2 USER_ONLINE format (from protocol spec):
  81. // REMOTE_UIN(4) + REMOTE_IP(4) + REMOTE_PORT(4) + REMOTE_REAL_IP(4) + X1(1) + STATUS(4) + X2(4) = 25 bytes
  82. func (b *V2PacketBuilderImpl) BuildUserOnline(seqNum uint16, uin uint32, status uint32, ip net.IP, port uint16) []byte {
  83. pkt := BuildV2UserOnline(seqNum, uin, status, ip, port)
  84. return MarshalV2ServerPacket(pkt)
  85. }
  86. // BuildUserOffline constructs a user offline notification packet.
  87. func (b *V2PacketBuilderImpl) BuildUserOffline(seqNum uint16, uin uint32) []byte {
  88. pkt := BuildV2UserOffline(seqNum, uin)
  89. return MarshalV2ServerPacket(pkt)
  90. }
  91. // BuildContactListDone constructs a contact list processing complete response.
  92. func (b *V2PacketBuilderImpl) BuildContactListDone(seqNum uint16, uin uint32) []byte {
  93. pkt := BuildV2ContactListDone(seqNum, uin)
  94. return MarshalV2ServerPacket(pkt)
  95. }
  96. // BuildMessage constructs a message delivery packet.
  97. // Format: FROM_UIN(4) + MSG_TYPE(2) + MSG_LEN(2) + MESSAGE
  98. func (b *V2PacketBuilderImpl) BuildMessage(seqNum uint16, fromUIN uint32, msgType uint16, message string) []byte {
  99. pkt := BuildV2Message(seqNum, fromUIN, msgType, message)
  100. return MarshalV2ServerPacket(pkt)
  101. }
  102. // BuildSearchResult constructs a user search result packet.
  103. // Format: SEQ(2) + UIN(4) + NICK_LEN(2) + NICK + FNAME_LEN(2) + FNAME + LNAME_LEN(2) + LNAME + EMAIL_LEN(2) + EMAIL + AUTH(1)
  104. func (b *V2PacketBuilderImpl) BuildSearchResult(seqNum uint16, info *UserInfoResult, isLast bool) []byte {
  105. // Convert UserInfoResult to LegacyUserInfo
  106. wireInfo := &LegacyUserInfo{
  107. UIN: info.UIN,
  108. Nickname: info.Nickname,
  109. FirstName: info.FirstName,
  110. LastName: info.LastName,
  111. Email: info.Email,
  112. Auth: info.AuthRequired,
  113. }
  114. pkt := BuildV2SearchResult(seqNum, wireInfo, isLast)
  115. return MarshalV2ServerPacket(pkt)
  116. }
  117. // BuildStatusUpdate constructs a status change notification packet.
  118. // Format: UIN(4) + STATUS(4)
  119. func (b *V2PacketBuilderImpl) BuildStatusUpdate(seqNum uint16, uin uint32, status uint32) []byte {
  120. pkt := BuildV2StatusUpdate(seqNum, uin, status)
  121. return MarshalV2ServerPacket(pkt)
  122. }
  123. // BuildOfflineMsgDone constructs an end-of-offline-messages packet.
  124. func (b *V2PacketBuilderImpl) BuildOfflineMsgDone(seqNum uint16) []byte {
  125. pkt := &V2ServerPacket{
  126. Version: ICQLegacyVersionV2,
  127. Command: ICQLegacySrvSysMsgDone,
  128. SeqNum: seqNum,
  129. }
  130. return MarshalV2ServerPacket(pkt)
  131. }
  132. // BuildDepsList constructs a pre-auth response packet (0x0032).
  133. // Historically called "departments list" in iserverd (from its Users_Deps database table).
  134. // Used in the pre-login flow: client sends 0x03F2 with credentials, server validates
  135. // and sends this response, then the client proceeds with the real login (0x03E8).
  136. //
  137. // V2 format: VERSION(2) + COMMAND(2) + SEQ(2) + DATA
  138. // Data: UIN(4) + DEPLIST_VERSION(4) + COUNT(4) + TRAILER(4)
  139. func (b *V2PacketBuilderImpl) BuildDepsList(seqNum uint16, uin uint32) []byte {
  140. data := make([]byte, 16)
  141. offset := 0
  142. // UIN (4 bytes)
  143. binary.LittleEndian.PutUint32(data[offset:], uin)
  144. offset += 4
  145. // Deplist version (4 bytes)
  146. binary.LittleEndian.PutUint32(data[offset:], 1)
  147. offset += 4
  148. // Count = 0 (empty list) (4 bytes)
  149. binary.LittleEndian.PutUint32(data[offset:], 0)
  150. offset += 4
  151. // Trailer (from iserverd)
  152. binary.LittleEndian.PutUint16(data[offset:], 0x0002)
  153. offset += 2
  154. binary.LittleEndian.PutUint16(data[offset:], 0x002A)
  155. offset += 2
  156. pkt := &V2ServerPacket{
  157. Version: ICQLegacyVersionV2,
  158. Command: ICQLegacySrvUserDepsList,
  159. SeqNum: seqNum,
  160. Data: data[:offset],
  161. }
  162. return MarshalV2ServerPacket(pkt)
  163. }