| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855 |
- package icq_legacy
- import (
- "encoding/binary"
- )
- // V3PacketBuilder constructs V3 protocol packets.
- // This interface separates packet construction from business logic,
- // following the OSCAR foodgroup architecture pattern.
- //
- // V3 packet format (from iserverd source):
- // VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + DATA...
- // Total header: 12 bytes (NO checksum in V3, unlike V5!)
- type V3PacketBuilder interface {
- // BuildLoginReply constructs a login success (HELLO) response packet.
- // The packet contains timing parameters and client IP information.
- BuildLoginReply(session *LegacySession, seq1, seq2 uint16) []byte
- // BuildBadPassword constructs a bad password/authentication failure response.
- BuildBadPassword(seq1, seq2 uint16, uin uint32) []byte
- // BuildAck constructs an acknowledgment packet for the given sequence numbers.
- BuildAck(seq1, seq2 uint16, uin uint32) []byte
- // BuildNotConnected constructs a "not connected" error response.
- BuildNotConnected(seq2 uint16, uin uint32) []byte
- // BuildUserOnline constructs a user online notification packet.
- // Sent to notify a user that one of their contacts has come online.
- BuildUserOnline(seqNum uint16, uin uint32, status uint32) []byte
- // BuildUserOffline constructs a user offline notification packet.
- // Sent to notify a user that one of their contacts has gone offline.
- BuildUserOffline(seqNum uint16, uin uint32) []byte
- // BuildUserStatus constructs a user status change notification packet.
- // Sent when a user changes status while already online.
- BuildUserStatus(seqNum uint16, uin uint32, status uint32) []byte
- // BuildContactListDone constructs a contact list processing complete response.
- BuildContactListDone(seqNum uint16, seq2 uint16, uin uint32) []byte
- // BuildOnlineMessage constructs an online system message packet.
- BuildOnlineMessage(seqNum uint16, fromUIN uint32, msgType uint16, message string) []byte
- // BuildBasicInfo constructs a basic user info response packet.
- BuildBasicInfo(seqNum uint16, seq2 uint16, uin uint32, info *UserInfoResult) []byte
- // BuildHomeInfo constructs a home info response packet.
- BuildHomeInfo(seqNum uint16, seq2 uint16, uin uint32, info *UserInfoResult) []byte
- // BuildWorkInfo constructs a work info response packet.
- BuildWorkInfo(seqNum uint16, seq2 uint16, uin uint32, info *UserInfoResult) []byte
- // BuildHomeWeb constructs a home webpage info response packet.
- BuildHomeWeb(seqNum uint16, seq2 uint16, uin uint32, info *UserInfoResult) []byte
- // BuildWorkWeb constructs a work webpage info response packet.
- BuildWorkWeb(seqNum uint16, seq2 uint16, uin uint32, info *UserInfoResult) []byte
- // BuildDeptsList constructs a pre-auth deps list response packet.
- // Historically called "departments list" in iserverd (from its Users_Deps database table).
- BuildDeptsList(seq2 uint16, uin uint32) []byte
- }
- // V3PacketBuilderImpl implements the V3PacketBuilder interface.
- // It constructs V3 protocol packets following the iserverd packet formats.
- type V3PacketBuilderImpl struct {
- sessionManager *LegacySessionManager
- directConnectionEnabled func(version int) bool
- }
- // NewV3PacketBuilder creates a new V3PacketBuilder instance.
- func NewV3PacketBuilder(sessionManager *LegacySessionManager, directConnectionEnabled func(version int) bool) V3PacketBuilder {
- return &V3PacketBuilderImpl{
- sessionManager: sessionManager,
- directConnectionEnabled: directConnectionEnabled,
- }
- }
- // BuildLoginReply constructs a login success (HELLO) response packet.
- // From iserverd v3_send_login_reply() - this is a complex packet!
- // V3 HELLO format:
- // VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + RESERVED(4) +
- // CLIENT_IP(4) + RESERVED(4) + EXTERNALS_NUM(4) + DEPLIST_VERSION(4) +
- // MUST_CHANGE_PASS(1) + CAN_BROADCAST(1) + UNKNOWN(4) +
- // PING_FREQ(2) + PACKET_TIMEOUT(2) + RETRY_TIMEOUT(2) + NUM_RETRIES(2)
- func (b *V3PacketBuilderImpl) BuildLoginReply(session *LegacySession, seq1, seq2 uint16) []byte {
- // Get client IP as uint32 (network byte order to ICQ format)
- var clientIP uint32
- if session.Addr != nil && session.Addr.IP != nil {
- ip := session.Addr.IP.To4()
- if ip != nil {
- // ICQ stores IP in little-endian format
- clientIP = uint32(ip[0]) | uint32(ip[1])<<8 | uint32(ip[2])<<16 | uint32(ip[3])<<24
- }
- }
- pkt := make([]byte, 46)
- offset := 0
- // Header
- binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacyVersionV3)
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacySrvHello)
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], 0) // seq1 = 0
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], seq2)
- offset += 2
- binary.LittleEndian.PutUint32(pkt[offset:], session.UIN)
- offset += 4
- binary.LittleEndian.PutUint32(pkt[offset:], 0) // reserved
- offset += 4
- // Client IP
- binary.LittleEndian.PutUint32(pkt[offset:], clientIP)
- offset += 4
- binary.LittleEndian.PutUint32(pkt[offset:], 0) // reserved
- offset += 4
- // Externals number and deplist version
- binary.LittleEndian.PutUint32(pkt[offset:], 0) // externals_num
- offset += 4
- binary.LittleEndian.PutUint32(pkt[offset:], 1) // deplist_version
- offset += 4
- // Flags
- pkt[offset] = 0 // must_change_password
- offset++
- pkt[offset] = 0 // can_broadcast
- offset++
- // Unknown value (0xFA)
- binary.LittleEndian.PutUint32(pkt[offset:], 0x000000FA)
- offset += 4
- // Timing parameters
- binary.LittleEndian.PutUint16(pkt[offset:], 50) // ping_frequency (seconds)
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], 60) // packet_timeout (seconds)
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], 10) // retry_timeout (seconds)
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], 3) // num_retries
- offset += 2
- return pkt[:offset]
- }
- // BuildBadPassword constructs a wrong password response.
- func (b *V3PacketBuilderImpl) BuildBadPassword(seq1, seq2 uint16, uin uint32) []byte {
- pkt := make([]byte, 16)
- binary.LittleEndian.PutUint16(pkt[0:2], ICQLegacyVersionV3)
- binary.LittleEndian.PutUint16(pkt[2:4], ICQLegacySrvWrongPasswd)
- binary.LittleEndian.PutUint16(pkt[4:6], 0)
- binary.LittleEndian.PutUint16(pkt[6:8], seq2)
- binary.LittleEndian.PutUint32(pkt[8:12], uin)
- binary.LittleEndian.PutUint32(pkt[12:16], 0)
- return pkt
- }
- // BuildAck constructs an ACK packet (V3 format from iserverd v3_send_ack).
- // V3 ACK format: VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + RESERVED(4)
- func (b *V3PacketBuilderImpl) BuildAck(seq1, seq2 uint16, uin uint32) []byte {
- pkt := make([]byte, 16)
- binary.LittleEndian.PutUint16(pkt[0:2], ICQLegacyVersionV3)
- binary.LittleEndian.PutUint16(pkt[2:4], ICQLegacySrvAck)
- binary.LittleEndian.PutUint16(pkt[4:6], seq1)
- binary.LittleEndian.PutUint16(pkt[6:8], seq2)
- binary.LittleEndian.PutUint32(pkt[8:12], uin)
- binary.LittleEndian.PutUint32(pkt[12:16], 0) // reserved
- return pkt
- }
- // BuildNotConnected constructs a not connected error response.
- func (b *V3PacketBuilderImpl) BuildNotConnected(seq2 uint16, uin uint32) []byte {
- pkt := make([]byte, 16)
- binary.LittleEndian.PutUint16(pkt[0:2], ICQLegacyVersionV3)
- binary.LittleEndian.PutUint16(pkt[2:4], ICQLegacySrvNotConnected)
- binary.LittleEndian.PutUint16(pkt[4:6], 0)
- binary.LittleEndian.PutUint16(pkt[6:8], seq2)
- binary.LittleEndian.PutUint32(pkt[8:12], uin)
- binary.LittleEndian.PutUint32(pkt[12:16], 0)
- return pkt
- }
- // BuildUserOnline constructs a user online notification packet.
- // From iserverd v3_send_user_online()
- // V3 USER_ONLINE format:
- // header(16) + UIN(4) + IP(4) + PORT(4) + INT_IP(4) + DC_TYPE(1) + STATUS(2) + ESTAT(2) + DCVER(2) + UNKNOWN(2)
- // Total: 16 + 4 + 4 + 4 + 4 + 1 + 2 + 2 + 2 + 2 = 41 bytes
- func (b *V3PacketBuilderImpl) BuildUserOnline(seqNum uint16, uin uint32, status uint32) []byte {
- pkt := make([]byte, 41)
- offset := 0
- // Header
- binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacyVersionV3)
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacySrvUserOnline)
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], seqNum)
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], 0) // seq2
- offset += 2
- // Note: UIN field in header will be set by caller (recipient's UIN)
- // For now we use 0 as placeholder - caller should set session.UIN
- binary.LittleEndian.PutUint32(pkt[offset:], 0) // recipient UIN (set by caller)
- offset += 4
- binary.LittleEndian.PutUint32(pkt[offset:], 0) // reserved
- offset += 4
- // UIN of the user who is online
- binary.LittleEndian.PutUint32(pkt[offset:], uin)
- offset += 4
- // Look up connection info if direct connections enabled for V3
- var externalIP, tcpPort, internalIP uint32
- var dcType uint8
- var dcVersion uint16
- if b.directConnectionEnabled != nil && b.directConnectionEnabled(int(ICQLegacyVersionV3)) && b.sessionManager != nil {
- if onlineSession := b.sessionManager.GetSession(uin); onlineSession != nil {
- externalIP = onlineSession.GetExternalIP()
- tcpPort = onlineSession.GetTCPPort()
- internalIP = onlineSession.GetInternalIP()
- dcType = onlineSession.DCType
- dcVersion = onlineSession.GetDCVersion()
- }
- }
- // IP address
- binary.LittleEndian.PutUint32(pkt[offset:], externalIP)
- offset += 4
- // TCP port
- binary.LittleEndian.PutUint32(pkt[offset:], tcpPort)
- offset += 4
- // Internal IP
- binary.LittleEndian.PutUint32(pkt[offset:], internalIP)
- offset += 4
- // DC type
- pkt[offset] = dcType
- offset++
- // Status (low word)
- binary.LittleEndian.PutUint16(pkt[offset:], uint16(status&0xFFFF))
- offset += 2
- // Extended status (high word)
- binary.LittleEndian.PutUint16(pkt[offset:], uint16(status>>16))
- offset += 2
- // DC version
- binary.LittleEndian.PutUint16(pkt[offset:], dcVersion)
- offset += 2
- // Unknown
- binary.LittleEndian.PutUint16(pkt[offset:], 0)
- offset += 2
- return pkt[:offset]
- }
- // BuildUserOffline constructs a user offline notification packet.
- // From iserverd v3_send_user_offline()
- // V3 USER_OFFLINE format: header(16) + UIN(4)
- func (b *V3PacketBuilderImpl) BuildUserOffline(seqNum uint16, uin uint32) []byte {
- pkt := make([]byte, 20)
- offset := 0
- // Header
- binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacyVersionV3)
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacySrvUserOffline)
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], seqNum)
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], 0) // seq2
- offset += 2
- binary.LittleEndian.PutUint32(pkt[offset:], 0) // recipient UIN (set by caller)
- offset += 4
- binary.LittleEndian.PutUint32(pkt[offset:], 0) // reserved
- offset += 4
- // UIN of the user who went offline
- binary.LittleEndian.PutUint32(pkt[offset:], uin)
- offset += 4
- return pkt[:offset]
- }
- // BuildUserStatus constructs a user status change notification packet.
- // From iserverd v3_send_user_status() in make_packet.cpp
- // This is used when a user changes status while already online.
- // Format: header(16) + UIN(4) + STATUS(2) + ESTAT(2)
- func (b *V3PacketBuilderImpl) BuildUserStatus(seqNum uint16, uin uint32, status uint32) []byte {
- pkt := make([]byte, 24)
- offset := 0
- // Header
- binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacyVersionV3)
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacySrvUserStatus) // 0x01A4
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], seqNum)
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], 0) // seq2
- offset += 2
- binary.LittleEndian.PutUint32(pkt[offset:], 0) // recipient UIN (set by caller)
- offset += 4
- binary.LittleEndian.PutUint32(pkt[offset:], 0) // reserved
- offset += 4
- // UIN of the user whose status changed
- binary.LittleEndian.PutUint32(pkt[offset:], uin)
- offset += 4
- // Status (low word)
- binary.LittleEndian.PutUint16(pkt[offset:], uint16(status&0xFFFF))
- offset += 2
- // Extended status (high word)
- binary.LittleEndian.PutUint16(pkt[offset:], uint16(status>>16))
- offset += 2
- return pkt[:offset]
- }
- // BuildContactListDone constructs a contact list processed response.
- func (b *V3PacketBuilderImpl) BuildContactListDone(seqNum uint16, seq2 uint16, uin uint32) []byte {
- pkt := make([]byte, 16)
- binary.LittleEndian.PutUint16(pkt[0:2], ICQLegacyVersionV3)
- binary.LittleEndian.PutUint16(pkt[2:4], ICQLegacySrvUserListDone)
- binary.LittleEndian.PutUint16(pkt[4:6], seqNum)
- binary.LittleEndian.PutUint16(pkt[6:8], seq2)
- binary.LittleEndian.PutUint32(pkt[8:12], uin)
- binary.LittleEndian.PutUint32(pkt[12:16], 0)
- return pkt
- }
- // BuildOnlineMessage constructs an online system message packet.
- // From iserverd v3_send_user_message()
- // Format: header(16) + FROM_UIN(4) + MSG_TYPE(2) + MSG_LEN(2) + MESSAGE
- // Note: V3 packets do NOT have a checkcode - they have a reserved field (always 0)
- func (b *V3PacketBuilderImpl) BuildOnlineMessage(seqNum uint16, fromUIN uint32, msgType uint16, message string) []byte {
- msgBytes := []byte(message)
- pktSize := 16 + 4 + 2 + 2 + len(msgBytes) + 1
- pkt := make([]byte, pktSize)
- offset := 0
- // Header
- binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacyVersionV3)
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacySrvSysMsgOnline) // 0x0104
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], seqNum)
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], 0) // seq2
- offset += 2
- binary.LittleEndian.PutUint32(pkt[offset:], 0) // recipient UIN (set by caller)
- offset += 4
- binary.LittleEndian.PutUint32(pkt[offset:], 0) // reserved (V3 has NO checkcode!)
- offset += 4
- // From UIN
- binary.LittleEndian.PutUint32(pkt[offset:], fromUIN)
- offset += 4
- // Message type (mask high byte for V3 clients)
- binary.LittleEndian.PutUint16(pkt[offset:], msgType&0x00FF)
- offset += 2
- // Message length (including null terminator)
- binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(msgBytes)+1))
- offset += 2
- // Message content
- copy(pkt[offset:], msgBytes)
- offset += len(msgBytes)
- pkt[offset] = 0 // null terminator
- offset++
- return pkt[:offset]
- }
- // BuildBasicInfo constructs a basic user info response packet.
- // From iserverd v3_send_basic_info()
- // Packet format (verified via Ghidra RE of ICQ98a client handler):
- //
- // Header: VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + RESERVED(4) = 16 bytes
- // Data: TARGET_UIN(4) + NICK_LEN(2) + NICK + FIRST_LEN(2) + FIRST + LAST_LEN(2) + LAST +
- // EMAIL_LEN(2) + EMAIL + STATUS(1) + AUTH(1)
- //
- // All strings are length-prefixed (2 bytes) and null-terminated
- func (b *V3PacketBuilderImpl) BuildBasicInfo(seqNum uint16, seq2 uint16, uin uint32, info *UserInfoResult) []byte {
- // Default values if info is nil
- nick := ""
- first := ""
- last := ""
- email := ""
- status := uint8(0)
- auth := uint8(0)
- if info != nil {
- nick = info.Nickname
- first = info.FirstName
- last = info.LastName
- email = info.Email
- auth = info.AuthRequired
- }
- // Calculate packet size
- // Header(16) + TARGET_UIN(4) + nick_len(2) + nick + null(1) + first_len(2) + first + null(1) +
- // last_len(2) + last + null(1) + email_len(2) + email + null(1) + status(1) + auth(1)
- pktSize := 16 + 4 + 2 + len(nick) + 1 + 2 + len(first) + 1 + 2 + len(last) + 1 + 2 + len(email) + 1 + 1 + 1
- pkt := make([]byte, pktSize)
- offset := 0
- // Header - Client expects 0x0118 (ICQLegacySrvInfoReply)
- binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacyVersionV3)
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacySrvInfoReply) // 0x0118
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], seqNum)
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], seq2)
- offset += 2
- binary.LittleEndian.PutUint32(pkt[offset:], uin) // recipient's UIN
- offset += 4
- binary.LittleEndian.PutUint32(pkt[offset:], 0) // reserved
- offset += 4
- // TARGET_UIN - the UIN of the user whose info this is
- targetUIN := uint32(0)
- if info != nil {
- targetUIN = info.UIN
- }
- binary.LittleEndian.PutUint32(pkt[offset:], targetUIN)
- offset += 4
- // Nick (length-prefixed with null terminator)
- binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(nick)+1))
- offset += 2
- copy(pkt[offset:], nick)
- offset += len(nick)
- pkt[offset] = 0
- offset++
- // First name (length-prefixed with null terminator)
- binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(first)+1))
- offset += 2
- copy(pkt[offset:], first)
- offset += len(first)
- pkt[offset] = 0
- offset++
- // Last name (length-prefixed with null terminator)
- binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(last)+1))
- offset += 2
- copy(pkt[offset:], last)
- offset += len(last)
- pkt[offset] = 0
- offset++
- // Email (length-prefixed with null terminator)
- binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(email)+1))
- offset += 2
- copy(pkt[offset:], email)
- offset += len(email)
- pkt[offset] = 0
- offset++
- // Status byte
- pkt[offset] = status
- offset++
- // Auth flag
- pkt[offset] = auth
- offset++
- return pkt[:offset]
- }
- // BuildHomeInfo constructs a home info response packet.
- // From iserverd v3_send_home_info()
- // Packet format:
- //
- // Header: VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + RESERVED(4) = 16 bytes
- // Data: HADDR_LEN(2) + HADDR + HCITY_LEN(2) + HCITY + HSTATE_LEN(2) + HSTATE +
- // HCOUNTRY(2) + HPHONE_LEN(2) + HPHONE + HFAX_LEN(2) + HFAX + HCELL_LEN(2) + HCELL +
- // HZIP(4) + GENDER(1) + AGE(2) + BDAY(1) + BMONTH(1) + BYEAR(1) + 0x00(1)
- func (b *V3PacketBuilderImpl) BuildHomeInfo(seqNum uint16, seq2 uint16, uin uint32, info *UserInfoResult) []byte {
- // Default values
- haddr := ""
- hcity := ""
- hstate := ""
- hcountry := uint16(0)
- hphone := ""
- hfax := ""
- hcell := ""
- hzip := uint32(0)
- gender := uint8(0)
- age := uint16(0)
- bday := uint8(0)
- bmonth := uint8(0)
- byear := uint8(0)
- if info != nil {
- hcity = info.City
- hstate = info.State
- hcountry = info.Country
- hphone = info.Phone
- gender = info.Gender
- age = uint16(info.Age)
- }
- // Calculate packet size
- pktSize := 16 + 2 + len(haddr) + 1 + 2 + len(hcity) + 1 + 2 + len(hstate) + 1 +
- 2 + 2 + len(hphone) + 1 + 2 + len(hfax) + 1 + 2 + len(hcell) + 1 +
- 4 + 1 + 2 + 1 + 1 + 1 + 1
- pkt := make([]byte, pktSize)
- offset := 0
- // Header
- binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacyVersionV3)
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], 0x0320) // ICQ_CMDxSND_USERxINFO_HOME
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], seqNum)
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], seq2)
- offset += 2
- binary.LittleEndian.PutUint32(pkt[offset:], uin)
- offset += 4
- binary.LittleEndian.PutUint32(pkt[offset:], 0)
- offset += 4
- // Home address
- binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(haddr)+1))
- offset += 2
- copy(pkt[offset:], haddr)
- offset += len(haddr)
- pkt[offset] = 0
- offset++
- // Home city
- binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(hcity)+1))
- offset += 2
- copy(pkt[offset:], hcity)
- offset += len(hcity)
- pkt[offset] = 0
- offset++
- // Home state
- binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(hstate)+1))
- offset += 2
- copy(pkt[offset:], hstate)
- offset += len(hstate)
- pkt[offset] = 0
- offset++
- // Home country
- binary.LittleEndian.PutUint16(pkt[offset:], hcountry)
- offset += 2
- // Home phone
- binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(hphone)+1))
- offset += 2
- copy(pkt[offset:], hphone)
- offset += len(hphone)
- pkt[offset] = 0
- offset++
- // Home fax
- binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(hfax)+1))
- offset += 2
- copy(pkt[offset:], hfax)
- offset += len(hfax)
- pkt[offset] = 0
- offset++
- // Home cell
- binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(hcell)+1))
- offset += 2
- copy(pkt[offset:], hcell)
- offset += len(hcell)
- pkt[offset] = 0
- offset++
- // Home zip
- binary.LittleEndian.PutUint32(pkt[offset:], hzip)
- offset += 4
- // Gender
- pkt[offset] = gender
- offset++
- // Age
- binary.LittleEndian.PutUint16(pkt[offset:], age)
- offset += 2
- // Birthday: day, month, year
- pkt[offset] = bday
- offset++
- pkt[offset] = bmonth
- offset++
- pkt[offset] = byear
- offset++
- // Trailing zero
- pkt[offset] = 0
- offset++
- return pkt[:offset]
- }
- // BuildWorkInfo constructs a work info response packet.
- // From iserverd v3_send_work_info()
- // Packet format:
- //
- // Header: VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + RESERVED(4) = 16 bytes
- // Data: WADDR_LEN(2) + WADDR + WCITY_LEN(2) + WCITY + WSTATE_LEN(2) + WSTATE +
- // WCOUNTRY(2) + WCOMPANY_LEN(2) + WCOMPANY + WTITLE_LEN(2) + WTITLE +
- // WDEPART(4) + WPHONE_LEN(2) + WPHONE + WFAX_LEN(2) + WFAX +
- // WPAGER_LEN(2) + WPAGER + WZIP(4)
- func (b *V3PacketBuilderImpl) BuildWorkInfo(seqNum uint16, seq2 uint16, uin uint32, info *UserInfoResult) []byte {
- // Default values
- waddr := ""
- wcity := ""
- wstate := ""
- wcountry := uint16(0)
- wcompany := ""
- wtitle := ""
- wdepart := uint32(0)
- wphone := ""
- wfax := ""
- wpager := ""
- wzip := uint32(0)
- if info != nil {
- wcompany = info.Company
- wtitle = info.Position
- }
- // Calculate packet size
- pktSize := 16 + 2 + len(waddr) + 1 + 2 + len(wcity) + 1 + 2 + len(wstate) + 1 +
- 2 + 2 + len(wcompany) + 1 + 2 + len(wtitle) + 1 + 4 +
- 2 + len(wphone) + 1 + 2 + len(wfax) + 1 + 2 + len(wpager) + 1 + 4
- pkt := make([]byte, pktSize)
- offset := 0
- // Header
- binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacyVersionV3)
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], 0x02F8) // ICQ_CMDxSND_USERxINFO_WORK
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], seqNum)
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], seq2)
- offset += 2
- binary.LittleEndian.PutUint32(pkt[offset:], uin)
- offset += 4
- binary.LittleEndian.PutUint32(pkt[offset:], 0)
- offset += 4
- // Work address
- binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(waddr)+1))
- offset += 2
- copy(pkt[offset:], waddr)
- offset += len(waddr)
- pkt[offset] = 0
- offset++
- // Work city
- binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(wcity)+1))
- offset += 2
- copy(pkt[offset:], wcity)
- offset += len(wcity)
- pkt[offset] = 0
- offset++
- // Work state
- binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(wstate)+1))
- offset += 2
- copy(pkt[offset:], wstate)
- offset += len(wstate)
- pkt[offset] = 0
- offset++
- // Work country
- binary.LittleEndian.PutUint16(pkt[offset:], wcountry)
- offset += 2
- // Work company
- binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(wcompany)+1))
- offset += 2
- copy(pkt[offset:], wcompany)
- offset += len(wcompany)
- pkt[offset] = 0
- offset++
- // Work title
- binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(wtitle)+1))
- offset += 2
- copy(pkt[offset:], wtitle)
- offset += len(wtitle)
- pkt[offset] = 0
- offset++
- // Work department
- binary.LittleEndian.PutUint32(pkt[offset:], wdepart)
- offset += 4
- // Work phone
- binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(wphone)+1))
- offset += 2
- copy(pkt[offset:], wphone)
- offset += len(wphone)
- pkt[offset] = 0
- offset++
- // Work fax
- binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(wfax)+1))
- offset += 2
- copy(pkt[offset:], wfax)
- offset += len(wfax)
- pkt[offset] = 0
- offset++
- // Work pager
- binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(wpager)+1))
- offset += 2
- copy(pkt[offset:], wpager)
- offset += len(wpager)
- pkt[offset] = 0
- offset++
- // Work zip
- binary.LittleEndian.PutUint32(pkt[offset:], wzip)
- offset += 4
- return pkt[:offset]
- }
- // BuildHomeWeb constructs a home webpage info response packet.
- // From iserverd v3_send_home_web()
- // Packet format:
- //
- // Header: VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + RESERVED(4) = 16 bytes
- // Data: HPAGE_LEN(2) + HPAGE (null-terminated string)
- func (b *V3PacketBuilderImpl) BuildHomeWeb(seqNum uint16, seq2 uint16, uin uint32, info *UserInfoResult) []byte {
- // Default value
- hpage := ""
- if info != nil {
- hpage = info.Homepage
- }
- // Calculate packet size
- // Header(16) + hpage_len(2) + hpage + null(1)
- pktSize := 16 + 2 + len(hpage) + 1
- pkt := make([]byte, pktSize)
- offset := 0
- // Header
- binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacyVersionV3)
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacySrvUserInfoHWeb) // 0x0334
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], seqNum)
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], seq2)
- offset += 2
- binary.LittleEndian.PutUint32(pkt[offset:], uin)
- offset += 4
- binary.LittleEndian.PutUint32(pkt[offset:], 0)
- offset += 4
- // Home page URL (length-prefixed with null terminator)
- binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(hpage)+1))
- offset += 2
- copy(pkt[offset:], hpage)
- offset += len(hpage)
- pkt[offset] = 0
- offset++
- return pkt[:offset]
- }
- // BuildWorkWeb constructs a work webpage info response packet.
- // From iserverd v3_send_work_web()
- // Packet format:
- //
- // Header: VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + RESERVED(4) = 16 bytes
- // Data: WPAGE_LEN(2) + WPAGE (null-terminated string)
- func (b *V3PacketBuilderImpl) BuildWorkWeb(seqNum uint16, seq2 uint16, uin uint32, info *UserInfoResult) []byte {
- // Default value - work webpage is not in UserInfoResult, so we use empty string
- // In the future, this could be added to UserInfoResult if needed
- wpage := ""
- // Calculate packet size
- // Header(16) + wpage_len(2) + wpage + null(1)
- pktSize := 16 + 2 + len(wpage) + 1
- pkt := make([]byte, pktSize)
- offset := 0
- // Header
- binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacyVersionV3)
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacySrvUserInfoWWeb) // 0x030C
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], seqNum)
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], seq2)
- offset += 2
- binary.LittleEndian.PutUint32(pkt[offset:], uin)
- offset += 4
- binary.LittleEndian.PutUint32(pkt[offset:], 0)
- offset += 4
- // Work page URL (length-prefixed with null terminator)
- binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(wpage)+1))
- offset += 2
- copy(pkt[offset:], wpage)
- offset += len(wpage)
- pkt[offset] = 0
- offset++
- return pkt[:offset]
- }
- // BuildDeptsList constructs a pre-auth deps list response packet.
- // Historically called "departments list" in iserverd (from its Users_Deps database table).
- // From iserverd v3_send_depslist()
- // V3 DEPS_LIST format: header(16) + DEPLIST_VERSION(4) + COUNT(4) + [deps...] + TRAILER(4)
- func (b *V3PacketBuilderImpl) BuildDeptsList(seq2 uint16, uin uint32) []byte {
- // For now, send empty list with version 1
- pkt := make([]byte, 28)
- binary.LittleEndian.PutUint16(pkt[0:2], ICQLegacyVersionV3)
- binary.LittleEndian.PutUint16(pkt[2:4], ICQLegacySrvUserDepsList) // 0x0032
- binary.LittleEndian.PutUint16(pkt[4:6], 0) // servseq
- binary.LittleEndian.PutUint16(pkt[6:8], seq2)
- binary.LittleEndian.PutUint32(pkt[8:12], uin)
- binary.LittleEndian.PutUint32(pkt[12:16], 0) // reserved
- binary.LittleEndian.PutUint32(pkt[16:20], 1) // deplist version
- binary.LittleEndian.PutUint32(pkt[20:24], 0) // count = 0 (empty deps list)
- // Trailer from iserverd
- binary.LittleEndian.PutUint16(pkt[24:26], 0x0002)
- binary.LittleEndian.PutUint16(pkt[26:28], 0x002a)
- return pkt
- }
|