| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530 |
- package icq_legacy
- import (
- "encoding/binary"
- "fmt"
- )
- // V4PacketBuilder constructs V4 protocol packets.
- // V4 is similar to V3 but with checkcode calculation for packet validation.
- // This interface separates packet construction from business logic,
- // following the OSCAR foodgroup architecture pattern.
- //
- // V4 packet format (from wumpus documentation):
- // VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + CHECKCODE(4) + DATA...
- // Total header: 16 bytes (V3 format with checkcode at offset 12)
- //
- // Note: Server sends V3 format packets to V4 clients, but with a valid checkcode
- // that the client validates using the V4 checkcode algorithm.
- type V4PacketBuilder interface {
- // BuildLoginReply constructs a login success (HELLO) response packet.
- // The packet contains timing parameters and client IP information.
- BuildLoginReply(session *LegacySession, 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
- // 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
- // BuildRegisterInfo constructs a registration info response packet.
- // Sent to clients starting the registration wizard.
- BuildRegisterInfo(seq2 uint16, uin uint32) []byte
- // BuildRegistrationOK constructs a registration success response packet.
- // Contains the new UIN and password for the registered user.
- BuildRegistrationOK(seq2 uint16, newUIN uint32, password string) []byte
- // CalculateCheckcode calculates the V4 checkcode for a packet.
- // The checkcode is used by V4 clients to validate server packets.
- CalculateCheckcode(packet []byte) uint32
- }
- // V4PacketBuilderImpl implements the V4PacketBuilder interface.
- // It constructs V4 protocol packets with proper checkcode calculation.
- type V4PacketBuilderImpl struct {
- sessionManager *LegacySessionManager
- directConnectionEnabled func(version int) bool
- }
- // NewV4PacketBuilder creates a new V4PacketBuilder instance.
- func NewV4PacketBuilder(sessionManager *LegacySessionManager, directConnectionEnabled func(version int) bool) V4PacketBuilder {
- return &V4PacketBuilderImpl{
- sessionManager: sessionManager,
- directConnectionEnabled: directConnectionEnabled,
- }
- }
- // CalculateCheckcode calculates the checkcode for server packets to V4 clients.
- // Based on reverse engineering of ICQ 98a client (encrypt_v4_packet and validate_v4_checkcode)
- //
- // The client validates checkcode as follows:
- //
- // checkA = (byte[8] << 24) | (byte[4] << 16) | (byte[2] << 8) | byte[6]
- // result = checkA ^ checkcode
- // valid if: (result >> 16) & 0xFF == ~packet[result >> 24]
- // and: result & 0xFF == ~table[(result >> 8) & 0xFF]
- //
- // So we construct checkcode such that after XOR with checkA, the result contains:
- //
- // byte 3 (bits 24-31): packetOfs - offset into packet
- // byte 2 (bits 16-23): ~packet[packetOfs] - inverted byte at that offset
- // byte 1 (bits 8-15): tableOfs - offset into table
- // byte 0 (bits 0-7): ~table[tableOfs] - inverted byte at that table offset
- func (b *V4PacketBuilderImpl) CalculateCheckcode(packet []byte) uint32 {
- if len(packet) < 12 {
- return 0
- }
- // checkA: pack bytes 8, 4, 2, 6 from packet (MSB to LSB)
- checkA := uint32(packet[8])<<24 | uint32(packet[4])<<16 | uint32(packet[2])<<8 | uint32(packet[6])
- // Choose offsets (we use fixed values, but could be random)
- // Avoid offset 0x10-0x13 (checkcode position) - use offset 4 (seq1 low byte)
- packetOfs := byte(4)
- tableOfs := byte(0)
- // Get values and INVERT them (this is what the client expects)
- packetVal := ^packet[packetOfs] // inverted
- tableVal := ^V4Table[tableOfs] // inverted
- // Build checkB with inverted values
- checkB := uint32(packetOfs)<<24 | uint32(packetVal)<<16 | uint32(tableOfs)<<8 | uint32(tableVal)
- // checkcode = checkA ^ checkB
- return checkA ^ checkB
- }
- // BuildLoginReply constructs a login success (HELLO) response packet.
- // V3 server packet format with checkcode for V4 clients.
- // From ICQ98a client reverse engineering:
- // Header: 16 bytes (VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + CHECKCODE(4))
- // Data: IP(4) + 10 WORDs(20) = 24 bytes
- // Total: 40 bytes
- func (b *V4PacketBuilderImpl) BuildLoginReply(session *LegacySession, seq2 uint16) []byte {
- // Get client IP as 4 bytes in network byte order (big-endian)
- var ipBytes [4]byte
- if session.Addr != nil && session.Addr.IP != nil {
- ip := session.Addr.IP.To4()
- if ip != nil {
- copy(ipBytes[:], ip)
- }
- }
- pkt := make([]byte, 40)
- offset := 0
- // V3 Header (16 bytes)
- serverSeq := session.NextServerSeqNum()
- binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacyVersionV3) // 03 00
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacySrvHello) // 5a 00
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], serverSeq) // server's seq1
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], seq2) // seq2 of login packet
- offset += 2
- binary.LittleEndian.PutUint32(pkt[offset:], session.UIN) // UIN
- offset += 4
- // Calculate and add checkcode
- checkcode := b.CalculateCheckcode(pkt)
- binary.LittleEndian.PutUint32(pkt[offset:], checkcode)
- offset += 4
- // Data section for LOGIN_REPLY (0x005A):
- // IP is in network byte order (big-endian): c0 a8 0a 01 = 192.168.10.1
- pkt[offset] = ipBytes[0]
- pkt[offset+1] = ipBytes[1]
- pkt[offset+2] = ipBytes[2]
- pkt[offset+3] = ipBytes[3]
- offset += 4
- // 6 WORDs that get stored in globals:
- binary.LittleEndian.PutUint16(pkt[offset:], 0x0000) // -> DAT_004df54c
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], 0x0000) // -> DAT_004df438
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], 0x0019) // -> DAT_004df7a8 (keep-alive interval = 25)
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], 0x0002) // -> DAT_004df550
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], 0x0001) // -> DAT_004d4178
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], 0x00FA) // -> DAT_004df820 (250 = timeout?)
- offset += 2
- // 4 WORDs that are discarded:
- binary.LittleEndian.PutUint16(pkt[offset:], 0x002D) // discarded
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], 0x0005) // discarded
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], 0x000A) // discarded
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], 0x0005) // discarded
- offset += 2
- return pkt[:offset]
- }
- // BuildBadPassword constructs a wrong password response.
- // V3 server packet format: VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + ZERO(4)
- func (b *V4PacketBuilderImpl) 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], seq1)
- binary.LittleEndian.PutUint16(pkt[6:8], seq2)
- binary.LittleEndian.PutUint32(pkt[8:12], uin)
- binary.LittleEndian.PutUint32(pkt[12:16], 0) // Zero field
- return pkt
- }
- // BuildAck constructs an ACK packet in V3 format with checkcode.
- func (b *V4PacketBuilderImpl) 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)
- // Calculate and add checkcode
- checkcode := b.CalculateCheckcode(pkt)
- binary.LittleEndian.PutUint32(pkt[12:16], checkcode)
- return pkt
- }
- // BuildNotConnected constructs a not connected error response.
- // V3 server packet format: VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + ZERO(4)
- func (b *V4PacketBuilderImpl) 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) // Zero field
- return pkt
- }
- // BuildUserOnline constructs a user online notification packet.
- // From iserverd v3_send_user_online() - V3 and V4 share the same server packet format.
- // Data: UIN(4) + IP(4) + PORT(4) + REAL_IP(4) + DC_TYPE(1) + STATUS(2) + ESTATUS(2) + DCVER(2) + UNKNOWN(2)
- // Total data: 25 bytes, total packet: 41 bytes
- func (b *V4PacketBuilderImpl) BuildUserOnline(seqNum uint16, uin uint32, status uint32) []byte {
- pkt := make([]byte, 41)
- binary.LittleEndian.PutUint16(pkt[0:2], ICQLegacyVersionV3)
- binary.LittleEndian.PutUint16(pkt[2:4], ICQLegacySrvUserOnline)
- binary.LittleEndian.PutUint16(pkt[4:6], seqNum)
- binary.LittleEndian.PutUint16(pkt[6:8], 0)
- binary.LittleEndian.PutUint32(pkt[8:12], 0) // recipient UIN (set by caller)
- // Calculate and add checkcode
- checkcode := b.CalculateCheckcode(pkt)
- binary.LittleEndian.PutUint32(pkt[12:16], checkcode)
- offset := 16
- // UIN of user who came online
- binary.LittleEndian.PutUint32(pkt[offset:], uin)
- offset += 4
- // Look up connection info if direct connections enabled for V4
- var externalIP, tcpPort, internalIP uint32
- var dcType uint8
- var dcVersion uint16
- if b.directConnectionEnabled != nil && b.directConnectionEnabled(int(ICQLegacyVersionV4)) && 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/real IP
- binary.LittleEndian.PutUint32(pkt[offset:], internalIP)
- offset += 4
- // DC type
- pkt[offset] = dcType
- offset++
- // Status (low 16 bits)
- binary.LittleEndian.PutUint16(pkt[offset:], uint16(status&0xFFFF))
- offset += 2
- // Extended status (high 16 bits)
- binary.LittleEndian.PutUint16(pkt[offset:], uint16(status>>16))
- offset += 2
- // DC version
- binary.LittleEndian.PutUint16(pkt[offset:], dcVersion)
- offset += 2
- // Unknown (2 bytes)
- offset += 2
- return pkt[:offset]
- }
- // BuildContactListDone constructs a contact list processed response.
- // V3 server packet format with checkcode: VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + CHECKCODE(4)
- func (b *V4PacketBuilderImpl) 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)
- // Calculate and add checkcode
- checkcode := b.CalculateCheckcode(pkt)
- binary.LittleEndian.PutUint32(pkt[12:16], checkcode)
- return pkt
- }
- // BuildOnlineMessage constructs an online system message packet.
- // V3 server packet format with checkcode.
- // Format: header(16) + FROM_UIN(4) + MSG_TYPE(2) + MSG_LEN(2) + MESSAGE
- func (b *V4PacketBuilderImpl) 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
- // Calculate and add checkcode
- checkcode := b.CalculateCheckcode(pkt)
- binary.LittleEndian.PutUint32(pkt[offset:], 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.
- // V3 server packet format with checkcode.
- // Data format (verified via Ghidra RE of ICQ98a client handler):
- //
- // TARGET_UIN(4) + NICK_LEN(2) + NICK + FNAME_LEN(2) + FNAME + LNAME_LEN(2) + LNAME +
- // EMAIL_LEN(2) + EMAIL + STATUS(1) + AUTH(1)
- func (b *V4PacketBuilderImpl) BuildBasicInfo(seqNum uint16, seq2 uint16, uin uint32, info *UserInfoResult) []byte {
- // Default values if info is nil
- nick := ""
- first := ""
- last := ""
- email := ""
- statusByte := byte(0)
- auth := byte(0)
- targetUIN := uint32(0)
- if info != nil {
- nick = info.Nickname
- first = info.FirstName
- last = info.LastName
- email = info.Email
- auth = info.AuthRequired
- targetUIN = info.UIN
- if nick == "" {
- nick = fmt.Sprintf("%d", targetUIN)
- }
- }
- // 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
- // Calculate and add checkcode
- checkcode := b.CalculateCheckcode(pkt)
- binary.LittleEndian.PutUint32(pkt[offset:], checkcode)
- offset += 4
- // TARGET_UIN - the UIN of the user whose info this is
- 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] = statusByte
- offset++
- // Auth flag
- pkt[offset] = auth
- offset++
- return pkt[:offset]
- }
- // BuildRegisterInfo constructs a registration info response packet.
- // V3 server packet format with checkcode.
- // From iserverd v3_send_registration_info(): sends 0x037A with admin notes
- func (b *V4PacketBuilderImpl) BuildRegisterInfo(seq2 uint16, uin uint32) []byte {
- // Admin notes message - tells the user about registration
- adminNotes := "Welcome to Open OSCAR Server!\nRegistration is enabled.\x00"
- // Build packet
- // Header (16 bytes) + NOTES_LEN(2) + NOTES + REG_ENABLED(1) + UNKNOWN(4)
- pktSize := 16 + 2 + len(adminNotes) + 1 + 4
- pkt := make([]byte, pktSize)
- offset := 0
- // V3 Header
- binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacyVersionV3)
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacySrvRegisterInfo)
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], 0) // seq1 = 0 for server-initiated
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], seq2)
- offset += 2
- binary.LittleEndian.PutUint32(pkt[offset:], uin)
- offset += 4
- // Calculate and add checkcode
- checkcode := b.CalculateCheckcode(pkt)
- binary.LittleEndian.PutUint32(pkt[offset:], checkcode)
- offset += 4
- // Admin notes length and string
- binary.LittleEndian.PutUint16(pkt[offset:], uint16(len(adminNotes)))
- offset += 2
- copy(pkt[offset:], adminNotes)
- offset += len(adminNotes)
- // Registration enabled flag (1 = enabled)
- pkt[offset] = 0x01
- offset++
- // Unknown fields (from iserverd: 0x0002, 0x002A)
- binary.LittleEndian.PutUint16(pkt[offset:], 0x0002)
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], 0x002A)
- offset += 2
- return pkt[:offset]
- }
- // BuildRegistrationOK constructs a registration success response packet.
- // V4 client (licq) expects ICQ_CMDxRCV_NEWxUIN (0x0046).
- // The client reads the new UIN from the header's UIN field, then calls icqLogon().
- // V3 server packet format with checkcode: VERSION(2) + COMMAND(2) + SEQ1(2) + SEQ2(2) + UIN(4) + CHECKCODE(4)
- func (b *V4PacketBuilderImpl) BuildRegistrationOK(seq2 uint16, newUIN uint32, password string) []byte {
- pkt := make([]byte, 16)
- offset := 0
- // V3 Header - UIN field contains the NEW UIN
- binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacyVersionV3)
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], ICQLegacySrvNewUIN) // 0x0046
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], 0) // seq1 = 0 for server-initiated
- offset += 2
- binary.LittleEndian.PutUint16(pkt[offset:], seq2)
- offset += 2
- binary.LittleEndian.PutUint32(pkt[offset:], newUIN) // NEW UIN in header
- offset += 4
- // Calculate and add checkcode
- checkcode := b.CalculateCheckcode(pkt)
- binary.LittleEndian.PutUint32(pkt[offset:], checkcode)
- offset += 4
- return pkt[:offset]
- }
|