packets, streams
This commit is contained in:
parent
fc8c72b1d5
commit
321f82c96a
286
app_packet.go
Normal file
286
app_packet.go
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
package eq2net
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AppPacket handles application-level game packets with opcode abstraction
|
||||||
|
// This layer sits above the protocol layer and handles game-specific opcodes
|
||||||
|
type AppPacket struct {
|
||||||
|
EQPacket
|
||||||
|
|
||||||
|
EmuOpcode uint16 // Emulator opcode (internal representation)
|
||||||
|
OpcodeSize uint8 // Size of opcode in bytes (1 or 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default opcode size for application packets
|
||||||
|
var DefaultAppOpcodeSize uint8 = 2
|
||||||
|
|
||||||
|
// NewAppPacket creates a new application packet with emulator opcode
|
||||||
|
func NewAppPacket(emuOpcode uint16, data []byte) *AppPacket {
|
||||||
|
p := &AppPacket{
|
||||||
|
EQPacket: *NewEQPacket(0, data), // Network opcode will be set during conversion
|
||||||
|
EmuOpcode: emuOpcode,
|
||||||
|
OpcodeSize: DefaultAppOpcodeSize,
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAppPacketWithSize creates an application packet with specified opcode size
|
||||||
|
func NewAppPacketWithSize(emuOpcode uint16, data []byte, opcodeSize uint8) *AppPacket {
|
||||||
|
p := &AppPacket{
|
||||||
|
EQPacket: *NewEQPacket(0, data),
|
||||||
|
EmuOpcode: emuOpcode,
|
||||||
|
OpcodeSize: opcodeSize,
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseAppPacket creates an application packet from raw data
|
||||||
|
// The data should NOT include the protocol header, just the app opcode + payload
|
||||||
|
func ParseAppPacket(data []byte, opcodeSize uint8) (*AppPacket, error) {
|
||||||
|
if len(data) < int(opcodeSize) {
|
||||||
|
return nil, fmt.Errorf("packet too small for opcode: need %d bytes, got %d", opcodeSize, len(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract opcode
|
||||||
|
var opcode uint16
|
||||||
|
if opcodeSize == 1 {
|
||||||
|
opcode = uint16(data[0])
|
||||||
|
} else {
|
||||||
|
// Handle special encoding for 2-byte opcodes
|
||||||
|
if data[0] == 0x00 && len(data) > 2 {
|
||||||
|
// Special case: extra 0x00 prefix for opcodes with low byte = 0x00
|
||||||
|
opcode = binary.BigEndian.Uint16(data[1:3])
|
||||||
|
data = data[3:] // Skip the extra byte
|
||||||
|
} else {
|
||||||
|
opcode = binary.BigEndian.Uint16(data[0:2])
|
||||||
|
data = data[2:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p := &AppPacket{
|
||||||
|
EQPacket: *NewEQPacket(opcode, data[opcodeSize:]),
|
||||||
|
EmuOpcode: opcode, // Initially same as network opcode
|
||||||
|
OpcodeSize: opcodeSize,
|
||||||
|
}
|
||||||
|
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetEmuOpcode sets the emulator opcode
|
||||||
|
// This is used when converting between emulator and network opcodes
|
||||||
|
func (p *AppPacket) SetEmuOpcode(opcode uint16) {
|
||||||
|
p.EmuOpcode = opcode
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEmuOpcode returns the emulator opcode
|
||||||
|
func (p *AppPacket) GetEmuOpcode() uint16 {
|
||||||
|
return p.EmuOpcode
|
||||||
|
}
|
||||||
|
|
||||||
|
// SerializeApp serializes the application packet with proper opcode encoding
|
||||||
|
func (p *AppPacket) SerializeApp() []byte {
|
||||||
|
opcodeBytes := p.OpcodeSize
|
||||||
|
extraBytes := 0
|
||||||
|
|
||||||
|
// Handle special encoding rules for 2-byte opcodes
|
||||||
|
if p.OpcodeSize == 2 && (p.Opcode&0x00FF) == 0 {
|
||||||
|
// Opcodes with low byte = 0x00 need an extra 0x00 prefix
|
||||||
|
extraBytes = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create buffer
|
||||||
|
buf := make([]byte, int(opcodeBytes)+extraBytes+len(p.Buffer))
|
||||||
|
offset := 0
|
||||||
|
|
||||||
|
// Write opcode
|
||||||
|
if p.OpcodeSize == 1 {
|
||||||
|
buf[offset] = byte(p.Opcode)
|
||||||
|
offset++
|
||||||
|
} else {
|
||||||
|
if extraBytes > 0 {
|
||||||
|
// Special encoding: add 0x00 prefix
|
||||||
|
buf[offset] = 0x00
|
||||||
|
offset++
|
||||||
|
binary.BigEndian.PutUint16(buf[offset:], p.Opcode)
|
||||||
|
offset += 2
|
||||||
|
} else {
|
||||||
|
binary.BigEndian.PutUint16(buf[offset:], p.Opcode)
|
||||||
|
offset += 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy packet data
|
||||||
|
if len(p.Buffer) > 0 {
|
||||||
|
copy(buf[offset:], p.Buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combine combines multiple application packets into a single packet
|
||||||
|
// Returns true if successful, false if size limit exceeded
|
||||||
|
func (p *AppPacket) Combine(other *AppPacket) bool {
|
||||||
|
// Create a new buffer with both packets
|
||||||
|
myData := p.SerializeApp()
|
||||||
|
otherData := other.SerializeApp()
|
||||||
|
|
||||||
|
// Check size limit (application packets can be larger than protocol)
|
||||||
|
if len(myData)+len(otherData) > 8192 { // Reasonable max size
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combine the data
|
||||||
|
newBuffer := make([]byte, len(myData)+len(otherData))
|
||||||
|
copy(newBuffer, myData)
|
||||||
|
copy(newBuffer[len(myData):], otherData)
|
||||||
|
|
||||||
|
// Update packet
|
||||||
|
p.Buffer = newBuffer
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy creates a deep copy of the application packet
|
||||||
|
func (p *AppPacket) Copy() *AppPacket {
|
||||||
|
newPacket := &AppPacket{
|
||||||
|
EQPacket: *p.Clone(),
|
||||||
|
EmuOpcode: p.EmuOpcode,
|
||||||
|
OpcodeSize: p.OpcodeSize,
|
||||||
|
}
|
||||||
|
return newPacket
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetVersion sets the protocol version for opcode conversion
|
||||||
|
func (p *AppPacket) SetVersion(version int16) {
|
||||||
|
p.Version = version
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConvertToProtocolPacket wraps this application packet in a protocol packet
|
||||||
|
// This is used when sending application data over the network
|
||||||
|
func (p *AppPacket) ConvertToProtocolPacket() *ProtocolPacket {
|
||||||
|
// Serialize the application packet
|
||||||
|
appData := p.SerializeApp()
|
||||||
|
|
||||||
|
// Create protocol packet with OP_Packet opcode
|
||||||
|
proto := &ProtocolPacket{
|
||||||
|
EQPacket: *NewEQPacket(OP_Packet, appData),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy network info
|
||||||
|
proto.SrcIP = p.SrcIP
|
||||||
|
proto.SrcPort = p.SrcPort
|
||||||
|
proto.DstIP = p.DstIP
|
||||||
|
proto.DstPort = p.DstPort
|
||||||
|
proto.Timestamp = p.Timestamp
|
||||||
|
proto.Version = p.Version
|
||||||
|
|
||||||
|
return proto
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppCombine performs application-level packet combining (EQ2-specific)
|
||||||
|
// This is different from protocol-level combining
|
||||||
|
func AppCombine(packets []*AppPacket) *AppPacket {
|
||||||
|
if len(packets) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(packets) == 1 {
|
||||||
|
return packets[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate total size needed
|
||||||
|
var totalSize int
|
||||||
|
for _, p := range packets {
|
||||||
|
data := p.SerializeApp()
|
||||||
|
if len(data) >= 255 {
|
||||||
|
totalSize += 3 + len(data) // 0xFF marker + 2-byte size + data
|
||||||
|
} else {
|
||||||
|
totalSize += 1 + len(data) // 1-byte size + data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build combined buffer
|
||||||
|
buffer := make([]byte, totalSize)
|
||||||
|
offset := 0
|
||||||
|
|
||||||
|
for _, p := range packets {
|
||||||
|
data := p.SerializeApp()
|
||||||
|
size := len(data)
|
||||||
|
|
||||||
|
if size >= 255 {
|
||||||
|
// Oversized packet: use 0xFF marker followed by 2-byte size
|
||||||
|
buffer[offset] = 0xFF
|
||||||
|
offset++
|
||||||
|
binary.BigEndian.PutUint16(buffer[offset:], uint16(size))
|
||||||
|
offset += 2
|
||||||
|
} else {
|
||||||
|
// Normal packet: 1-byte size
|
||||||
|
buffer[offset] = byte(size)
|
||||||
|
offset++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy packet data
|
||||||
|
copy(buffer[offset:], data)
|
||||||
|
offset += size
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create combined packet with OP_AppCombined
|
||||||
|
combined := &AppPacket{
|
||||||
|
EQPacket: *NewEQPacket(OP_AppCombined, buffer),
|
||||||
|
OpcodeSize: DefaultAppOpcodeSize,
|
||||||
|
}
|
||||||
|
|
||||||
|
return combined
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractAppPackets extracts individual packets from an app-combined packet
|
||||||
|
func ExtractAppPackets(combined *AppPacket) ([]*AppPacket, error) {
|
||||||
|
if combined.Opcode != OP_AppCombined {
|
||||||
|
return nil, fmt.Errorf("not an app-combined packet")
|
||||||
|
}
|
||||||
|
|
||||||
|
var packets []*AppPacket
|
||||||
|
data := combined.Buffer
|
||||||
|
offset := 0
|
||||||
|
|
||||||
|
for offset < len(data) {
|
||||||
|
if offset >= len(data) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
var size int
|
||||||
|
// Read size
|
||||||
|
if data[offset] == 0xFF {
|
||||||
|
// Oversized packet
|
||||||
|
if offset+3 > len(data) {
|
||||||
|
return nil, fmt.Errorf("invalid oversized packet header")
|
||||||
|
}
|
||||||
|
offset++
|
||||||
|
size = int(binary.BigEndian.Uint16(data[offset:]))
|
||||||
|
offset += 2
|
||||||
|
} else {
|
||||||
|
// Normal packet
|
||||||
|
size = int(data[offset])
|
||||||
|
offset++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract packet data
|
||||||
|
if offset+size > len(data) {
|
||||||
|
return nil, fmt.Errorf("packet size exceeds buffer")
|
||||||
|
}
|
||||||
|
|
||||||
|
packetData := data[offset : offset+size]
|
||||||
|
offset += size
|
||||||
|
|
||||||
|
// Parse the packet
|
||||||
|
app, err := ParseAppPacket(packetData, combined.OpcodeSize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse app packet: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
packets = append(packets, app)
|
||||||
|
}
|
||||||
|
|
||||||
|
return packets, nil
|
||||||
|
}
|
@ -39,3 +39,17 @@ func NewCiphers(key int64) (*Ciphers, error) {
|
|||||||
server: serverCipher,
|
server: serverCipher,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Decrypt decrypts data received from the client
|
||||||
|
func (c *Ciphers) Decrypt(data []byte) {
|
||||||
|
if c.client != nil {
|
||||||
|
c.client.XORKeyStream(data, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt encrypts data to be sent to the client
|
||||||
|
func (c *Ciphers) Encrypt(data []byte) {
|
||||||
|
if c.server != nil {
|
||||||
|
c.server.XORKeyStream(data, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
24
opcodes.go
Normal file
24
opcodes.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package eq2net
|
||||||
|
|
||||||
|
// Login/chat use 1-byte opcodes
|
||||||
|
const AppOpcodeSize = 1
|
||||||
|
|
||||||
|
// The game uses 2-byte opcodes
|
||||||
|
const GameOpcodeSize = 2
|
||||||
|
|
||||||
|
// Protocol-level opcodes for the actual UDP stream
|
||||||
|
const (
|
||||||
|
OP_SessionRequest = 0x0001
|
||||||
|
OP_SessionResponse = 0x0002
|
||||||
|
OP_Combined = 0x0003
|
||||||
|
OP_SessionDisconnect = 0x0005
|
||||||
|
OP_KeepAlive = 0x0006
|
||||||
|
OP_SessionStatRequest = 0x0007
|
||||||
|
OP_SessionStatResponse = 0x0008
|
||||||
|
OP_Packet = 0x0009
|
||||||
|
OP_Fragment = 0x000D
|
||||||
|
OP_OutOfOrderAck = 0x0011
|
||||||
|
OP_Ack = 0x0015
|
||||||
|
OP_AppCombined = 0x0019
|
||||||
|
OP_OutOfSession = 0x001D
|
||||||
|
)
|
179
packet.go
Normal file
179
packet.go
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
package eq2net
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type EQPacket struct {
|
||||||
|
Buffer []byte // Raw packet data
|
||||||
|
Opcode uint16 // Packet opcode
|
||||||
|
|
||||||
|
SrcIP net.IP // Source IP address
|
||||||
|
SrcPort uint16 // Source port
|
||||||
|
DstIP net.IP // Destination IP address
|
||||||
|
DstPort uint16 // Destination port
|
||||||
|
|
||||||
|
Timestamp time.Time // When packet was created/received
|
||||||
|
Priority uint32 // Priority in processing
|
||||||
|
Version int16 // Protocol version
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEQPacket(opcode uint16, data []byte) *EQPacket {
|
||||||
|
p := &EQPacket{
|
||||||
|
Opcode: opcode,
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
Priority: 0,
|
||||||
|
Version: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(data) > 0 {
|
||||||
|
p.Buffer = make([]byte, len(data))
|
||||||
|
copy(p.Buffer, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size returns the size of the packet data (excluding opcode)
|
||||||
|
func (p *EQPacket) Size() uint32 {
|
||||||
|
return uint32(len(p.Buffer))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotalSize returns the total size including opcode
|
||||||
|
func (p *EQPacket) TotalSize() uint32 {
|
||||||
|
opcodeSize := uint32(1)
|
||||||
|
if p.Opcode > 0xFF {
|
||||||
|
opcodeSize = 2
|
||||||
|
}
|
||||||
|
return p.Size() + opcodeSize
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRawOpcode returns the raw opcode value
|
||||||
|
func (p *EQPacket) GetRawOpcode() uint16 {
|
||||||
|
return p.Opcode
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOpcodeName returns the string name of the opcode
|
||||||
|
func (p *EQPacket) GetOpcodeName() string {
|
||||||
|
switch p.Opcode {
|
||||||
|
case OP_SessionRequest:
|
||||||
|
return "OP_SessionRequest"
|
||||||
|
case OP_SessionResponse:
|
||||||
|
return "OP_SessionResponse"
|
||||||
|
case OP_Combined:
|
||||||
|
return "OP_Combined"
|
||||||
|
case OP_SessionDisconnect:
|
||||||
|
return "OP_SessionDisconnect"
|
||||||
|
case OP_KeepAlive:
|
||||||
|
return "OP_KeepAlive"
|
||||||
|
case OP_SessionStatRequest:
|
||||||
|
return "OP_SessionStatRequest"
|
||||||
|
case OP_SessionStatResponse:
|
||||||
|
return "OP_SessionStatResponse"
|
||||||
|
case OP_Packet:
|
||||||
|
return "OP_Packet"
|
||||||
|
case OP_Fragment:
|
||||||
|
return "OP_Fragment"
|
||||||
|
case OP_OutOfOrderAck:
|
||||||
|
return "OP_OutOfOrderAck"
|
||||||
|
case OP_Ack:
|
||||||
|
return "OP_Ack"
|
||||||
|
case OP_AppCombined:
|
||||||
|
return "OP_AppCombined"
|
||||||
|
case OP_OutOfSession:
|
||||||
|
return "OP_OutOfSession"
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("Unknown(0x%04X)", p.Opcode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize writes the packet to a byte buffer
|
||||||
|
func (p *EQPacket) Serialize() []byte {
|
||||||
|
// Determine opcode size
|
||||||
|
opcodeSize := 1
|
||||||
|
if p.Opcode > 0xFF {
|
||||||
|
opcodeSize = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create buffer
|
||||||
|
buf := make([]byte, opcodeSize+len(p.Buffer))
|
||||||
|
|
||||||
|
// Write opcode
|
||||||
|
if opcodeSize == 2 {
|
||||||
|
binary.BigEndian.PutUint16(buf[0:2], p.Opcode)
|
||||||
|
} else {
|
||||||
|
buf[0] = 0x00
|
||||||
|
buf[1] = byte(p.Opcode)
|
||||||
|
opcodeSize = 2 // Even 1-byte opcodes use 2 bytes on wire
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy data
|
||||||
|
if len(p.Buffer) > 0 {
|
||||||
|
copy(buf[opcodeSize:], p.Buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNetworkInfo sets the network address info
|
||||||
|
func (p *EQPacket) SetNetworkInfo(srcIP net.IP, srcPort uint16, dstIP net.IP, dstPort uint16) {
|
||||||
|
p.SrcIP = srcIP
|
||||||
|
p.SrcPort = srcPort
|
||||||
|
p.DstIP = dstIP
|
||||||
|
p.DstPort = dstPort
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone creates a deep copy of the packet
|
||||||
|
func (p *EQPacket) Clone() *EQPacket {
|
||||||
|
newPacket := &EQPacket{
|
||||||
|
Opcode: p.Opcode,
|
||||||
|
SrcIP: make(net.IP, len(p.SrcIP)),
|
||||||
|
SrcPort: p.SrcPort,
|
||||||
|
DstIP: make(net.IP, len(p.DstIP)),
|
||||||
|
DstPort: p.DstPort,
|
||||||
|
Timestamp: p.Timestamp,
|
||||||
|
Priority: p.Priority,
|
||||||
|
Version: p.Version,
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.SrcIP != nil {
|
||||||
|
copy(newPacket.SrcIP, p.SrcIP)
|
||||||
|
}
|
||||||
|
if p.DstIP != nil {
|
||||||
|
copy(newPacket.DstIP, p.DstIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(p.Buffer) > 0 {
|
||||||
|
newPacket.Buffer = make([]byte, len(p.Buffer))
|
||||||
|
copy(newPacket.Buffer, p.Buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newPacket
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParsePacket creates a packet from raw network data
|
||||||
|
func ParsePacket(data []byte) (*EQPacket, error) {
|
||||||
|
if len(data) < 2 {
|
||||||
|
return nil, fmt.Errorf("packet too small: %d bytes", len(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract opcode (always 2 bytes on wire)
|
||||||
|
opcode := binary.BigEndian.Uint16(data[0:2])
|
||||||
|
|
||||||
|
// Create packet
|
||||||
|
p := &EQPacket{
|
||||||
|
Opcode: opcode,
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy remaining data if any
|
||||||
|
if len(data) > 2 {
|
||||||
|
p.Buffer = make([]byte, len(data)-2)
|
||||||
|
copy(p.Buffer, data[2:])
|
||||||
|
}
|
||||||
|
|
||||||
|
return p, nil
|
||||||
|
}
|
361
protocol_packet.go
Normal file
361
protocol_packet.go
Normal file
@ -0,0 +1,361 @@
|
|||||||
|
package eq2net
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/zlib"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.sharkk.net/EQ2/Protocol/crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ProtocolPacket handles low-level protocol operations including
|
||||||
|
// compression, encryption, sequencing, and packet combining
|
||||||
|
type ProtocolPacket struct {
|
||||||
|
EQPacket
|
||||||
|
|
||||||
|
Compressed bool
|
||||||
|
Encrypted bool
|
||||||
|
PacketPrepared bool
|
||||||
|
|
||||||
|
Sequence uint16
|
||||||
|
Acknowledged bool
|
||||||
|
SentTime int32
|
||||||
|
AttemptCount int8
|
||||||
|
|
||||||
|
SubPackets []*ProtocolPacket
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewProtocolPacket creates a new protocol packet
|
||||||
|
func NewProtocolPacket(opcode uint16, data []byte) *ProtocolPacket {
|
||||||
|
p := &ProtocolPacket{
|
||||||
|
EQPacket: *NewEQPacket(opcode, data),
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseProtocolPacket creates a protocol packet from raw network data
|
||||||
|
func ParseProtocolPacket(data []byte) (*ProtocolPacket, error) {
|
||||||
|
base, err := ParsePacket(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
p := &ProtocolPacket{
|
||||||
|
EQPacket: *base,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for compression flag
|
||||||
|
if len(p.Buffer) > 0 {
|
||||||
|
switch p.Buffer[0] {
|
||||||
|
case 0x5a: // Zlib compressed
|
||||||
|
p.Compressed = true
|
||||||
|
case 0xa5: // Simple encoding
|
||||||
|
p.Compressed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize writes the protocol packet to a byte buffer with optional offset
|
||||||
|
func (p *ProtocolPacket) SerializeWithOffset(offset int8) []byte {
|
||||||
|
opcodeSize := 2 // Protocol packets always use 2-byte opcodes
|
||||||
|
buf := make([]byte, opcodeSize+len(p.Buffer)-int(offset))
|
||||||
|
|
||||||
|
if p.Opcode > 0xff {
|
||||||
|
binary.BigEndian.PutUint16(buf[0:2], p.Opcode)
|
||||||
|
} else {
|
||||||
|
buf[0] = 0x00
|
||||||
|
buf[1] = byte(p.Opcode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(p.Buffer) > int(offset) {
|
||||||
|
copy(buf[opcodeSize:], p.Buffer[offset:])
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateCRC checks if the packet has a valid CRC
|
||||||
|
// Returns true if CRC is valid or packet is exempt
|
||||||
|
func (p *ProtocolPacket) ValidateCRC(key uint32) bool {
|
||||||
|
// Session control packets are exempt from CRC
|
||||||
|
if p.Opcode == OP_SessionRequest ||
|
||||||
|
p.Opcode == OP_SessionResponse ||
|
||||||
|
p.Opcode == OP_OutOfSession {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need full packet data including opcode for CRC check
|
||||||
|
fullData := p.Serialize()
|
||||||
|
if len(fullData) < 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// CRC is in last 2 bytes
|
||||||
|
if len(fullData) < 4 { // Minimum: 2 byte opcode + 2 byte CRC
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
dataLen := len(fullData) - 2
|
||||||
|
calculatedCRC := crypto.CalculateCRC(fullData[:dataLen], key)
|
||||||
|
packetCRC := binary.BigEndian.Uint16(fullData[dataLen:])
|
||||||
|
|
||||||
|
// CRC of 0 means no CRC check required
|
||||||
|
return packetCRC == 0 || calculatedCRC == packetCRC
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compress compresses the packet data using zlib or simple encoding
|
||||||
|
func (p *ProtocolPacket) Compress() error {
|
||||||
|
if p.Compressed {
|
||||||
|
return fmt.Errorf("packet already compressed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only compress packets larger than 30 bytes
|
||||||
|
if len(p.Buffer) > 30 {
|
||||||
|
compressed, err := p.zlibCompress()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.Buffer = compressed
|
||||||
|
p.Compressed = true
|
||||||
|
} else if len(p.Buffer) > 0 {
|
||||||
|
// Simple encoding for small packets
|
||||||
|
p.simpleEncode()
|
||||||
|
p.Compressed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decompress decompresses the packet data
|
||||||
|
func (p *ProtocolPacket) Decompress() error {
|
||||||
|
if !p.Compressed {
|
||||||
|
return fmt.Errorf("packet not compressed")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(p.Buffer) == 0 {
|
||||||
|
return fmt.Errorf("no data to decompress")
|
||||||
|
}
|
||||||
|
|
||||||
|
var decompressed []byte
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch p.Buffer[0] {
|
||||||
|
case 0x5a: // Zlib compressed
|
||||||
|
decompressed, err = p.zlibDecompress()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case 0xa5: // Simple encoding
|
||||||
|
decompressed = p.simpleDecoded()
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown compression flag: 0x%02x", p.Buffer[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Buffer = decompressed
|
||||||
|
p.Compressed = false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// zlibCompress performs zlib compression
|
||||||
|
func (p *ProtocolPacket) zlibCompress() ([]byte, error) {
|
||||||
|
var compressed bytes.Buffer
|
||||||
|
|
||||||
|
// Write compression flag
|
||||||
|
compressed.WriteByte(0x5a)
|
||||||
|
|
||||||
|
// Compress the data
|
||||||
|
w := zlib.NewWriter(&compressed)
|
||||||
|
_, err := w.Write(p.Buffer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = w.Close()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return compressed.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// zlibDecompress performs zlib decompression
|
||||||
|
func (p *ProtocolPacket) zlibDecompress() ([]byte, error) {
|
||||||
|
if len(p.Buffer) < 2 {
|
||||||
|
return nil, fmt.Errorf("compressed data too small")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip compression flag
|
||||||
|
r, err := zlib.NewReader(bytes.NewReader(p.Buffer[1:]))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
var decompressed bytes.Buffer
|
||||||
|
_, err = decompressed.ReadFrom(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return decompressed.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// simpleEncode adds simple encoding flag
|
||||||
|
func (p *ProtocolPacket) simpleEncode() {
|
||||||
|
// Add encoding flag at the beginning
|
||||||
|
newBuffer := make([]byte, len(p.Buffer)+1)
|
||||||
|
newBuffer[0] = 0xa5
|
||||||
|
copy(newBuffer[1:], p.Buffer)
|
||||||
|
p.Buffer = newBuffer
|
||||||
|
}
|
||||||
|
|
||||||
|
// simpleDecoded removes simple encoding flag
|
||||||
|
func (p *ProtocolPacket) simpleDecoded() []byte {
|
||||||
|
if len(p.Buffer) > 1 {
|
||||||
|
return p.Buffer[1:]
|
||||||
|
}
|
||||||
|
return []byte{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combine bundles multiple protocol packets into a single combined packet
|
||||||
|
func (p *ProtocolPacket) Combine(other *ProtocolPacket) bool {
|
||||||
|
// Check if this is already a combined packet
|
||||||
|
if p.Opcode != OP_Combined {
|
||||||
|
// Convert to combined packet
|
||||||
|
firstPacket := p.Clone()
|
||||||
|
p.Opcode = OP_Combined
|
||||||
|
p.SubPackets = []*ProtocolPacket{
|
||||||
|
{EQPacket: *firstPacket},
|
||||||
|
}
|
||||||
|
p.Buffer = p.buildCombinedBuffer()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check size limit (max 255 bytes for combined packets)
|
||||||
|
totalSize := len(p.Buffer) + int(other.TotalSize()) + 1 // +1 for size byte
|
||||||
|
if totalSize > 255 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the new packet
|
||||||
|
p.SubPackets = append(p.SubPackets, other)
|
||||||
|
p.Buffer = p.buildCombinedBuffer()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildCombinedBuffer creates the buffer for a combined packet
|
||||||
|
func (p *ProtocolPacket) buildCombinedBuffer() []byte {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
for _, subPacket := range p.SubPackets {
|
||||||
|
serialized := subPacket.Serialize()
|
||||||
|
buf.WriteByte(byte(len(serialized))) // Size byte
|
||||||
|
buf.Write(serialized)
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractSubPackets extracts individual packets from a combined packet
|
||||||
|
func (p *ProtocolPacket) ExtractSubPackets() ([]*ProtocolPacket, error) {
|
||||||
|
if p.Opcode != OP_Combined {
|
||||||
|
return nil, fmt.Errorf("not a combined packet")
|
||||||
|
}
|
||||||
|
|
||||||
|
var packets []*ProtocolPacket
|
||||||
|
offset := 0
|
||||||
|
data := p.Buffer
|
||||||
|
|
||||||
|
for offset < len(data) {
|
||||||
|
// Read size byte
|
||||||
|
if offset >= len(data) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
size := int(data[offset])
|
||||||
|
offset++
|
||||||
|
|
||||||
|
// Extract sub-packet data
|
||||||
|
if offset+size > len(data) {
|
||||||
|
return nil, fmt.Errorf("invalid combined packet: size exceeds buffer")
|
||||||
|
}
|
||||||
|
|
||||||
|
subData := data[offset : offset+size]
|
||||||
|
offset += size
|
||||||
|
|
||||||
|
// Parse sub-packet
|
||||||
|
subPacket, err := ParseProtocolPacket(subData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse sub-packet: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
packets = append(packets, subPacket)
|
||||||
|
}
|
||||||
|
|
||||||
|
return packets, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeApplicationPacket converts this protocol packet to an application packet
|
||||||
|
// This is used when a protocol packet contains application-level data
|
||||||
|
func (p *ProtocolPacket) MakeApplicationPacket(opcodeSize uint8) *AppPacket {
|
||||||
|
if len(p.Buffer) < int(opcodeSize) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the application opcode from the buffer
|
||||||
|
var appOpcode uint16
|
||||||
|
if opcodeSize == 1 {
|
||||||
|
appOpcode = uint16(p.Buffer[0])
|
||||||
|
} else {
|
||||||
|
appOpcode = binary.BigEndian.Uint16(p.Buffer[0:2])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create application packet with remaining data
|
||||||
|
app := &AppPacket{
|
||||||
|
EQPacket: *NewEQPacket(appOpcode, p.Buffer[opcodeSize:]),
|
||||||
|
OpcodeSize: opcodeSize,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy network info
|
||||||
|
app.SrcIP = p.SrcIP
|
||||||
|
app.SrcPort = p.SrcPort
|
||||||
|
app.DstIP = p.DstIP
|
||||||
|
app.DstPort = p.DstPort
|
||||||
|
app.Timestamp = p.Timestamp
|
||||||
|
app.Version = p.Version
|
||||||
|
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChatEncode applies XOR-based chat encryption
|
||||||
|
func (p *ProtocolPacket) ChatEncode(key uint32) {
|
||||||
|
if len(p.Buffer) <= 2 {
|
||||||
|
return // Skip opcode bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
data := p.Buffer[2:] // Skip first 2 bytes (opcode)
|
||||||
|
keyBytes := make([]byte, 4)
|
||||||
|
binary.LittleEndian.PutUint32(keyBytes, key)
|
||||||
|
|
||||||
|
// Process 4-byte blocks with rolling key
|
||||||
|
i := 0
|
||||||
|
for ; i+4 <= len(data); i += 4 {
|
||||||
|
// XOR with current key
|
||||||
|
block := binary.LittleEndian.Uint32(data[i : i+4])
|
||||||
|
encrypted := block ^ key
|
||||||
|
binary.LittleEndian.PutUint32(data[i:i+4], encrypted)
|
||||||
|
// Update key with encrypted data
|
||||||
|
key = encrypted
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle remaining bytes with last key byte
|
||||||
|
keyByte := byte(key & 0xFF)
|
||||||
|
for ; i < len(data); i++ {
|
||||||
|
data[i] ^= keyByte
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChatDecode applies XOR-based chat decryption (same as encode due to XOR properties)
|
||||||
|
func (p *ProtocolPacket) ChatDecode(key uint32) {
|
||||||
|
p.ChatEncode(key) // XOR encryption is symmetric
|
||||||
|
}
|
715
stream.go
Normal file
715
stream.go
Normal file
@ -0,0 +1,715 @@
|
|||||||
|
package eq2net
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.sharkk.net/EQ2/Protocol/crypto"
|
||||||
|
"github.com/panjf2000/gnet/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StreamState represents the current state of an EQStream connection
|
||||||
|
type StreamState int
|
||||||
|
|
||||||
|
const (
|
||||||
|
CLOSED StreamState = iota // No connection
|
||||||
|
CONNECTING // Session request sent, awaiting response
|
||||||
|
ESTABLISHED // Active connection ready for data
|
||||||
|
DISCONNECTING // Graceful disconnect in progress
|
||||||
|
)
|
||||||
|
|
||||||
|
// StreamType identifies the type of EQ stream
|
||||||
|
type StreamType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
UnknownStream StreamType = iota
|
||||||
|
LoginStream
|
||||||
|
WorldStream
|
||||||
|
ZoneStream
|
||||||
|
ChatStream
|
||||||
|
VoiceStream
|
||||||
|
)
|
||||||
|
|
||||||
|
// Stream configuration constants
|
||||||
|
const (
|
||||||
|
MaxPacketSize = 512 // Maximum packet size
|
||||||
|
MaxCombinedSize = 255 // Maximum combined packet size
|
||||||
|
RetransmitTimeoutMin = 500 // Minimum retransmit timeout (ms)
|
||||||
|
RetransmitTimeoutMax = 5000 // Maximum retransmit timeout (ms)
|
||||||
|
RetransmitMaxAttempts = 10 // Maximum retransmission attempts
|
||||||
|
KeepAliveInterval = 30000 // Keep-alive interval (ms)
|
||||||
|
SessionTimeout = 90000 // Session timeout (ms)
|
||||||
|
|
||||||
|
// Packet flags
|
||||||
|
FLAG_COMPRESSED = 0x01 // Packet is compressed
|
||||||
|
FLAG_ENCODED = 0x04 // Packet is encoded/encrypted
|
||||||
|
|
||||||
|
// Rate limiting
|
||||||
|
RATEBASE = 1048576 // Base rate: 1 MB
|
||||||
|
DECAYBASE = 78642 // Decay rate: RATEBASE/10
|
||||||
|
)
|
||||||
|
|
||||||
|
// RetransmitEntry tracks packets awaiting acknowledgment
|
||||||
|
type RetransmitEntry struct {
|
||||||
|
packet *ProtocolPacket
|
||||||
|
sentTime time.Time
|
||||||
|
attempts int
|
||||||
|
}
|
||||||
|
|
||||||
|
// EQStream manages a single EverQuest network stream connection
|
||||||
|
type EQStream struct {
|
||||||
|
// Network connection
|
||||||
|
conn gnet.Conn
|
||||||
|
remoteIP net.IP
|
||||||
|
remotePort uint16
|
||||||
|
|
||||||
|
// Session identification
|
||||||
|
sessionID uint32
|
||||||
|
streamType StreamType
|
||||||
|
|
||||||
|
// State management
|
||||||
|
state StreamState
|
||||||
|
stateMu sync.RWMutex
|
||||||
|
|
||||||
|
// Encryption
|
||||||
|
crypto *crypto.Ciphers
|
||||||
|
cryptoKey uint32
|
||||||
|
|
||||||
|
// Compression settings
|
||||||
|
compressed bool
|
||||||
|
encoded bool
|
||||||
|
|
||||||
|
// Sequence numbers
|
||||||
|
nextInSeq uint16
|
||||||
|
nextOutSeq uint16
|
||||||
|
lastAckSeq uint16
|
||||||
|
seqMu sync.Mutex
|
||||||
|
|
||||||
|
// Packet queues
|
||||||
|
outbound chan *ProtocolPacket
|
||||||
|
inbound chan *AppPacket
|
||||||
|
retransmit map[uint16]*RetransmitEntry
|
||||||
|
retransmitMu sync.RWMutex
|
||||||
|
futurePackets map[uint16]*ProtocolPacket
|
||||||
|
futureMu sync.Mutex
|
||||||
|
|
||||||
|
// Combined packet handling
|
||||||
|
combinedApp *AppPacket
|
||||||
|
combinedMu sync.Mutex
|
||||||
|
combineTimer *time.Timer
|
||||||
|
|
||||||
|
// Flow control
|
||||||
|
currentRate uint32
|
||||||
|
rateThreshold uint32
|
||||||
|
rateMu sync.Mutex
|
||||||
|
|
||||||
|
// Timing
|
||||||
|
lastActivity time.Time
|
||||||
|
lastPing time.Time
|
||||||
|
avgDelta time.Duration
|
||||||
|
timingMu sync.RWMutex
|
||||||
|
|
||||||
|
// Statistics
|
||||||
|
packetsReceived uint64
|
||||||
|
packetsSent uint64
|
||||||
|
bytesReceived uint64
|
||||||
|
bytesSent uint64
|
||||||
|
statsMu sync.RWMutex
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
opcodeSize uint8
|
||||||
|
maxPacketSize uint32
|
||||||
|
|
||||||
|
// Callbacks
|
||||||
|
onAppPacket func(*AppPacket) // Called when app packet is ready
|
||||||
|
onDisconnect func() // Called on disconnect
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEQStream creates a new EQ stream
|
||||||
|
func NewEQStream(conn gnet.Conn) *EQStream {
|
||||||
|
stream := &EQStream{
|
||||||
|
conn: conn,
|
||||||
|
state: CLOSED,
|
||||||
|
sessionID: 0,
|
||||||
|
streamType: UnknownStream,
|
||||||
|
compressed: true,
|
||||||
|
encoded: false,
|
||||||
|
nextInSeq: 0,
|
||||||
|
nextOutSeq: 0,
|
||||||
|
lastAckSeq: 0,
|
||||||
|
outbound: make(chan *ProtocolPacket, 1024),
|
||||||
|
inbound: make(chan *AppPacket, 1024),
|
||||||
|
retransmit: make(map[uint16]*RetransmitEntry),
|
||||||
|
futurePackets: make(map[uint16]*ProtocolPacket),
|
||||||
|
currentRate: RATEBASE,
|
||||||
|
rateThreshold: DECAYBASE,
|
||||||
|
lastActivity: time.Now(),
|
||||||
|
opcodeSize: 2,
|
||||||
|
maxPacketSize: MaxPacketSize,
|
||||||
|
cryptoKey: 0x33624702, // Default CRC key
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse remote address
|
||||||
|
if addr := conn.RemoteAddr(); addr != nil {
|
||||||
|
if tcpAddr, ok := addr.(*net.TCPAddr); ok {
|
||||||
|
stream.remoteIP = tcpAddr.IP
|
||||||
|
stream.remotePort = uint16(tcpAddr.Port)
|
||||||
|
} else if udpAddr, ok := addr.(*net.UDPAddr); ok {
|
||||||
|
stream.remoteIP = udpAddr.IP
|
||||||
|
stream.remotePort = uint16(udpAddr.Port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stream
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetState returns the current stream state
|
||||||
|
func (s *EQStream) GetState() StreamState {
|
||||||
|
s.stateMu.RLock()
|
||||||
|
defer s.stateMu.RUnlock()
|
||||||
|
return s.state
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetState updates the stream state
|
||||||
|
func (s *EQStream) SetState(state StreamState) {
|
||||||
|
s.stateMu.Lock()
|
||||||
|
defer s.stateMu.Unlock()
|
||||||
|
s.state = state
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsActive returns true if the stream is established
|
||||||
|
func (s *EQStream) IsActive() bool {
|
||||||
|
return s.GetState() == ESTABLISHED
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsClosed returns true if the stream is closed
|
||||||
|
func (s *EQStream) IsClosed() bool {
|
||||||
|
return s.GetState() == CLOSED
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProcessPacket processes an incoming protocol packet
|
||||||
|
func (s *EQStream) ProcessPacket(p *ProtocolPacket) error {
|
||||||
|
s.updateActivity()
|
||||||
|
s.updateStats(true, uint64(p.TotalSize()))
|
||||||
|
|
||||||
|
// Handle based on opcode
|
||||||
|
switch p.Opcode {
|
||||||
|
case OP_SessionRequest:
|
||||||
|
return s.handleSessionRequest(p)
|
||||||
|
case OP_SessionResponse:
|
||||||
|
return s.handleSessionResponse(p)
|
||||||
|
case OP_SessionDisconnect:
|
||||||
|
return s.handleDisconnect(p)
|
||||||
|
case OP_KeepAlive:
|
||||||
|
return s.handleKeepAlive(p)
|
||||||
|
case OP_Ack:
|
||||||
|
return s.handleAck(p)
|
||||||
|
case OP_OutOfOrderAck:
|
||||||
|
return s.handleOutOfOrderAck(p)
|
||||||
|
case OP_Packet:
|
||||||
|
return s.handleDataPacket(p)
|
||||||
|
case OP_Fragment:
|
||||||
|
return s.handleFragment(p)
|
||||||
|
case OP_Combined:
|
||||||
|
return s.handleCombined(p)
|
||||||
|
case OP_AppCombined:
|
||||||
|
return s.handleAppCombined(p)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown opcode: 0x%04X", p.Opcode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleSessionRequest processes a session request packet
|
||||||
|
func (s *EQStream) handleSessionRequest(p *ProtocolPacket) error {
|
||||||
|
if len(p.Buffer) < 4 {
|
||||||
|
return fmt.Errorf("session request too small")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse session ID
|
||||||
|
s.sessionID = binary.BigEndian.Uint32(p.Buffer[0:4])
|
||||||
|
|
||||||
|
// Send session response
|
||||||
|
s.sendSessionResponse()
|
||||||
|
|
||||||
|
// Update state
|
||||||
|
s.SetState(ESTABLISHED)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleSessionResponse processes a session response packet
|
||||||
|
func (s *EQStream) handleSessionResponse(p *ProtocolPacket) error {
|
||||||
|
if len(p.Buffer) < 4 {
|
||||||
|
return fmt.Errorf("session response too small")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse session parameters
|
||||||
|
s.sessionID = binary.BigEndian.Uint32(p.Buffer[0:4])
|
||||||
|
|
||||||
|
// Parse compression/encoding flags if present
|
||||||
|
if len(p.Buffer) >= 5 {
|
||||||
|
flags := p.Buffer[4]
|
||||||
|
s.compressed = (flags & FLAG_COMPRESSED) != 0
|
||||||
|
s.encoded = (flags & FLAG_ENCODED) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize encryption if needed
|
||||||
|
if s.encoded && s.crypto == nil {
|
||||||
|
// This would be initialized with proper key exchange
|
||||||
|
// For now, using default key
|
||||||
|
var err error
|
||||||
|
s.crypto, err = crypto.NewCiphers(int64(s.cryptoKey))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to initialize encryption: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update state
|
||||||
|
s.SetState(ESTABLISHED)
|
||||||
|
|
||||||
|
// Send keep alive to confirm
|
||||||
|
s.sendKeepAlive()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleDisconnect processes a disconnect packet
|
||||||
|
func (s *EQStream) handleDisconnect(p *ProtocolPacket) error {
|
||||||
|
s.SetState(CLOSED)
|
||||||
|
|
||||||
|
// Call disconnect callback if set
|
||||||
|
if s.onDisconnect != nil {
|
||||||
|
s.onDisconnect()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleKeepAlive processes a keep-alive packet
|
||||||
|
func (s *EQStream) handleKeepAlive(p *ProtocolPacket) error {
|
||||||
|
// Update activity timestamp
|
||||||
|
s.updateActivity()
|
||||||
|
|
||||||
|
// Send keep-alive response if needed
|
||||||
|
// Some implementations echo the keep-alive
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleAck processes an acknowledgment packet
|
||||||
|
func (s *EQStream) handleAck(p *ProtocolPacket) error {
|
||||||
|
if len(p.Buffer) < 2 {
|
||||||
|
return fmt.Errorf("ack packet too small")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse acknowledged sequence number
|
||||||
|
ackSeq := binary.BigEndian.Uint16(p.Buffer[0:2])
|
||||||
|
|
||||||
|
// Remove from retransmit queue
|
||||||
|
s.retransmitMu.Lock()
|
||||||
|
delete(s.retransmit, ackSeq)
|
||||||
|
s.retransmitMu.Unlock()
|
||||||
|
|
||||||
|
// Update last acknowledged sequence
|
||||||
|
s.seqMu.Lock()
|
||||||
|
if s.sequenceGreaterThan(ackSeq, s.lastAckSeq) {
|
||||||
|
s.lastAckSeq = ackSeq
|
||||||
|
}
|
||||||
|
s.seqMu.Unlock()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleOutOfOrderAck processes an out-of-order acknowledgment
|
||||||
|
func (s *EQStream) handleOutOfOrderAck(p *ProtocolPacket) error {
|
||||||
|
// Similar to handleAck but indicates out-of-order reception
|
||||||
|
return s.handleAck(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleDataPacket processes a data packet
|
||||||
|
func (s *EQStream) handleDataPacket(p *ProtocolPacket) error {
|
||||||
|
// Check sequence number
|
||||||
|
seq := p.Sequence
|
||||||
|
|
||||||
|
s.seqMu.Lock()
|
||||||
|
expectedSeq := s.nextInSeq
|
||||||
|
s.seqMu.Unlock()
|
||||||
|
|
||||||
|
seqOrder := s.compareSequence(expectedSeq, seq)
|
||||||
|
|
||||||
|
switch seqOrder {
|
||||||
|
case SeqInOrder:
|
||||||
|
// Process packet immediately
|
||||||
|
s.seqMu.Lock()
|
||||||
|
s.nextInSeq++
|
||||||
|
s.seqMu.Unlock()
|
||||||
|
|
||||||
|
// Send ACK
|
||||||
|
s.sendAck(seq)
|
||||||
|
|
||||||
|
// Convert to application packet and queue
|
||||||
|
if appPacket := p.MakeApplicationPacket(s.opcodeSize); appPacket != nil {
|
||||||
|
select {
|
||||||
|
case s.inbound <- appPacket:
|
||||||
|
default:
|
||||||
|
// Queue full, drop packet
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call callback if set
|
||||||
|
if s.onAppPacket != nil {
|
||||||
|
s.onAppPacket(appPacket)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for queued future packets
|
||||||
|
s.processFuturePackets()
|
||||||
|
|
||||||
|
case SeqFuture:
|
||||||
|
// Queue for later processing
|
||||||
|
s.futureMu.Lock()
|
||||||
|
s.futurePackets[seq] = p
|
||||||
|
s.futureMu.Unlock()
|
||||||
|
|
||||||
|
// Send out-of-order ACK
|
||||||
|
s.sendOutOfOrderAck(seq)
|
||||||
|
|
||||||
|
case SeqPast:
|
||||||
|
// Duplicate packet, just ACK it
|
||||||
|
s.sendAck(seq)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleFragment processes a fragmented packet
|
||||||
|
func (s *EQStream) handleFragment(p *ProtocolPacket) error {
|
||||||
|
// TODO: Implement fragment reassembly
|
||||||
|
// This requires tracking fragment sequences and reassembling
|
||||||
|
return fmt.Errorf("fragment handling not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleCombined processes a combined packet
|
||||||
|
func (s *EQStream) handleCombined(p *ProtocolPacket) error {
|
||||||
|
// Extract sub-packets
|
||||||
|
subPackets, err := p.ExtractSubPackets()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to extract sub-packets: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process each sub-packet
|
||||||
|
for _, subPacket := range subPackets {
|
||||||
|
if err := s.ProcessPacket(subPacket); err != nil {
|
||||||
|
// Log error but continue processing other packets
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleAppCombined processes an application-level combined packet
|
||||||
|
func (s *EQStream) handleAppCombined(p *ProtocolPacket) error {
|
||||||
|
// Convert to app packet first
|
||||||
|
appPacket := p.MakeApplicationPacket(s.opcodeSize)
|
||||||
|
if appPacket == nil {
|
||||||
|
return fmt.Errorf("failed to convert to app packet")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract application packets
|
||||||
|
appPackets, err := ExtractAppPackets(appPacket)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to extract app packets: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Queue each application packet
|
||||||
|
for _, app := range appPackets {
|
||||||
|
select {
|
||||||
|
case s.inbound <- app:
|
||||||
|
default:
|
||||||
|
// Queue full
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.onAppPacket != nil {
|
||||||
|
s.onAppPacket(app)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// QueuePacket queues an outbound packet for transmission
|
||||||
|
func (s *EQStream) QueuePacket(p *ProtocolPacket, reliable bool) error {
|
||||||
|
if s.GetState() != ESTABLISHED {
|
||||||
|
return fmt.Errorf("stream not established")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add sequence number for reliable packets
|
||||||
|
if reliable {
|
||||||
|
s.seqMu.Lock()
|
||||||
|
p.Sequence = s.nextOutSeq
|
||||||
|
s.nextOutSeq++
|
||||||
|
s.seqMu.Unlock()
|
||||||
|
|
||||||
|
// Add to retransmit queue
|
||||||
|
s.retransmitMu.Lock()
|
||||||
|
s.retransmit[p.Sequence] = &RetransmitEntry{
|
||||||
|
packet: p,
|
||||||
|
sentTime: time.Now(),
|
||||||
|
attempts: 0,
|
||||||
|
}
|
||||||
|
s.retransmitMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add CRC if needed
|
||||||
|
if p.Opcode != OP_SessionRequest && p.Opcode != OP_SessionResponse {
|
||||||
|
s.addCRC(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compress if beneficial
|
||||||
|
if s.compressed && len(p.Buffer) > 30 {
|
||||||
|
p.Compress()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt if enabled
|
||||||
|
if s.encoded && s.crypto != nil {
|
||||||
|
s.encryptPacket(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send packet
|
||||||
|
return s.sendPacket(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper methods
|
||||||
|
|
||||||
|
func (s *EQStream) updateActivity() {
|
||||||
|
s.timingMu.Lock()
|
||||||
|
s.lastActivity = time.Now()
|
||||||
|
s.timingMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *EQStream) updateStats(received bool, bytes uint64) {
|
||||||
|
s.statsMu.Lock()
|
||||||
|
if received {
|
||||||
|
s.packetsReceived++
|
||||||
|
s.bytesReceived += bytes
|
||||||
|
} else {
|
||||||
|
s.packetsSent++
|
||||||
|
s.bytesSent += bytes
|
||||||
|
}
|
||||||
|
s.statsMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *EQStream) sendPacket(p *ProtocolPacket) error {
|
||||||
|
data := p.Serialize()
|
||||||
|
s.updateStats(false, uint64(len(data)))
|
||||||
|
return s.conn.AsyncWrite(data, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *EQStream) sendSessionResponse() {
|
||||||
|
resp := NewProtocolPacket(OP_SessionResponse, nil)
|
||||||
|
|
||||||
|
// Build response data
|
||||||
|
data := make([]byte, 5)
|
||||||
|
binary.BigEndian.PutUint32(data[0:4], s.sessionID)
|
||||||
|
|
||||||
|
// Set flags
|
||||||
|
flags := byte(0)
|
||||||
|
if s.compressed {
|
||||||
|
flags |= FLAG_COMPRESSED
|
||||||
|
}
|
||||||
|
if s.encoded {
|
||||||
|
flags |= FLAG_ENCODED
|
||||||
|
}
|
||||||
|
data[4] = flags
|
||||||
|
|
||||||
|
resp.Buffer = data
|
||||||
|
s.sendPacket(resp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *EQStream) sendKeepAlive() {
|
||||||
|
ka := NewProtocolPacket(OP_KeepAlive, nil)
|
||||||
|
s.sendPacket(ka)
|
||||||
|
|
||||||
|
s.timingMu.Lock()
|
||||||
|
s.lastPing = time.Now()
|
||||||
|
s.timingMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *EQStream) sendAck(seq uint16) {
|
||||||
|
ack := NewProtocolPacket(OP_Ack, nil)
|
||||||
|
ack.Buffer = make([]byte, 2)
|
||||||
|
binary.BigEndian.PutUint16(ack.Buffer, seq)
|
||||||
|
s.sendPacket(ack)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *EQStream) sendOutOfOrderAck(seq uint16) {
|
||||||
|
ack := NewProtocolPacket(OP_OutOfOrderAck, nil)
|
||||||
|
ack.Buffer = make([]byte, 2)
|
||||||
|
binary.BigEndian.PutUint16(ack.Buffer, seq)
|
||||||
|
s.sendPacket(ack)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *EQStream) addCRC(p *ProtocolPacket) {
|
||||||
|
// Serialize packet without CRC
|
||||||
|
data := p.Serialize()
|
||||||
|
|
||||||
|
// Calculate CRC
|
||||||
|
crc := crypto.CalculateCRC(data, s.cryptoKey)
|
||||||
|
|
||||||
|
// Append CRC to buffer
|
||||||
|
crcBytes := make([]byte, 2)
|
||||||
|
binary.BigEndian.PutUint16(crcBytes, crc)
|
||||||
|
p.Buffer = append(p.Buffer, crcBytes...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *EQStream) encryptPacket(p *ProtocolPacket) {
|
||||||
|
if s.crypto == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encrypt the buffer (skip opcode)
|
||||||
|
if len(p.Buffer) > 2 {
|
||||||
|
s.crypto.Encrypt(p.Buffer[2:])
|
||||||
|
p.Encrypted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *EQStream) processFuturePackets() {
|
||||||
|
s.futureMu.Lock()
|
||||||
|
defer s.futureMu.Unlock()
|
||||||
|
|
||||||
|
s.seqMu.Lock()
|
||||||
|
expectedSeq := s.nextInSeq
|
||||||
|
s.seqMu.Unlock()
|
||||||
|
|
||||||
|
// Check if we have the next expected packet
|
||||||
|
if p, ok := s.futurePackets[expectedSeq]; ok {
|
||||||
|
delete(s.futurePackets, expectedSeq)
|
||||||
|
|
||||||
|
// Process it (this will recursively check for more)
|
||||||
|
go s.ProcessPacket(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sequence number comparison
|
||||||
|
|
||||||
|
type SeqOrder int
|
||||||
|
|
||||||
|
const (
|
||||||
|
SeqPast SeqOrder = iota // Sequence is from the past
|
||||||
|
SeqInOrder // Sequence is expected
|
||||||
|
SeqFuture // Sequence is from the future
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *EQStream) compareSequence(expected, received uint16) SeqOrder {
|
||||||
|
if received == expected {
|
||||||
|
return SeqInOrder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle wraparound
|
||||||
|
if s.sequenceGreaterThan(received, expected) {
|
||||||
|
return SeqFuture
|
||||||
|
}
|
||||||
|
|
||||||
|
return SeqPast
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *EQStream) sequenceGreaterThan(a, b uint16) bool {
|
||||||
|
// Handle sequence number wraparound
|
||||||
|
// If the difference is more than half the sequence space,
|
||||||
|
// assume wraparound occurred
|
||||||
|
diff := int32(a) - int32(b)
|
||||||
|
if diff < 0 {
|
||||||
|
diff = -diff
|
||||||
|
}
|
||||||
|
|
||||||
|
if diff > 32768 { // Half of uint16 max
|
||||||
|
return a < b
|
||||||
|
}
|
||||||
|
|
||||||
|
return a > b
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckRetransmissions checks for packets that need retransmission
|
||||||
|
func (s *EQStream) CheckRetransmissions() {
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
s.retransmitMu.Lock()
|
||||||
|
defer s.retransmitMu.Unlock()
|
||||||
|
|
||||||
|
for seq, entry := range s.retransmit {
|
||||||
|
// Calculate timeout based on average delta and attempts
|
||||||
|
timeout := time.Duration(RetransmitTimeoutMin) * time.Millisecond
|
||||||
|
if s.avgDelta > 0 {
|
||||||
|
timeout = s.avgDelta * 3
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exponential backoff
|
||||||
|
for i := 0; i < entry.attempts; i++ {
|
||||||
|
timeout *= 2
|
||||||
|
if timeout > time.Duration(RetransmitTimeoutMax)*time.Millisecond {
|
||||||
|
timeout = time.Duration(RetransmitTimeoutMax) * time.Millisecond
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if now.Sub(entry.sentTime) > timeout {
|
||||||
|
if entry.attempts >= RetransmitMaxAttempts {
|
||||||
|
// Give up on this packet
|
||||||
|
delete(s.retransmit, seq)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retransmit
|
||||||
|
entry.attempts++
|
||||||
|
entry.sentTime = now
|
||||||
|
s.sendPacket(entry.packet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckTimeout checks if the stream has timed out
|
||||||
|
func (s *EQStream) CheckTimeout() bool {
|
||||||
|
s.timingMu.RLock()
|
||||||
|
lastActivity := s.lastActivity
|
||||||
|
s.timingMu.RUnlock()
|
||||||
|
|
||||||
|
if time.Since(lastActivity) > time.Duration(SessionTimeout)*time.Millisecond {
|
||||||
|
s.SetState(CLOSED)
|
||||||
|
if s.onDisconnect != nil {
|
||||||
|
s.onDisconnect()
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send keep-alive if needed
|
||||||
|
if time.Since(lastActivity) > time.Duration(KeepAliveInterval)*time.Millisecond {
|
||||||
|
s.sendKeepAlive()
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendDisconnect sends a disconnect packet
|
||||||
|
func (s *EQStream) SendDisconnect() {
|
||||||
|
disc := NewProtocolPacket(OP_SessionDisconnect, nil)
|
||||||
|
s.sendPacket(disc)
|
||||||
|
s.SetState(DISCONNECTING)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the stream
|
||||||
|
func (s *EQStream) Close() {
|
||||||
|
s.SendDisconnect()
|
||||||
|
s.SetState(CLOSED)
|
||||||
|
|
||||||
|
// Clean up resources
|
||||||
|
close(s.outbound)
|
||||||
|
close(s.inbound)
|
||||||
|
|
||||||
|
if s.combineTimer != nil {
|
||||||
|
s.combineTimer.Stop()
|
||||||
|
}
|
||||||
|
}
|
54
stream_factory.go
Normal file
54
stream_factory.go
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package eq2net
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StreamFactory provides a factory pattern for creating streams
|
||||||
|
type StreamFactory struct {
|
||||||
|
servers map[StreamType]*StreamServer
|
||||||
|
serversMu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStreamFactory creates a new stream factory
|
||||||
|
func NewStreamFactory() *StreamFactory {
|
||||||
|
return &StreamFactory{
|
||||||
|
servers: make(map[StreamType]*StreamServer),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateServer creates a new stream server
|
||||||
|
func (f *StreamFactory) CreateServer(streamType StreamType, address string, port int) (*StreamServer, error) {
|
||||||
|
f.serversMu.Lock()
|
||||||
|
defer f.serversMu.Unlock()
|
||||||
|
|
||||||
|
// Check if server already exists for this type
|
||||||
|
if _, exists := f.servers[streamType]; exists {
|
||||||
|
return nil, fmt.Errorf("server already exists for stream type %d", streamType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new server
|
||||||
|
server := NewStreamServer(address, port, streamType)
|
||||||
|
f.servers[streamType] = server
|
||||||
|
|
||||||
|
return server, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetServer returns a server by stream type
|
||||||
|
func (f *StreamFactory) GetServer(streamType StreamType) *StreamServer {
|
||||||
|
f.serversMu.RLock()
|
||||||
|
defer f.serversMu.RUnlock()
|
||||||
|
return f.servers[streamType]
|
||||||
|
}
|
||||||
|
|
||||||
|
// StopAll stops all servers
|
||||||
|
func (f *StreamFactory) StopAll() {
|
||||||
|
f.serversMu.Lock()
|
||||||
|
defer f.serversMu.Unlock()
|
||||||
|
|
||||||
|
for _, server := range f.servers {
|
||||||
|
server.Stop()
|
||||||
|
}
|
||||||
|
f.servers = make(map[StreamType]*StreamServer)
|
||||||
|
}
|
263
stream_server.go
Normal file
263
stream_server.go
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
package eq2net
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/panjf2000/gnet/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StreamServer manages multiple EQStream connections using gnet
|
||||||
|
type StreamServer struct {
|
||||||
|
gnet.BuiltinEventEngine
|
||||||
|
|
||||||
|
streams map[string]*EQStream // "IP:Port" -> Stream mapping
|
||||||
|
streamsMu sync.RWMutex
|
||||||
|
|
||||||
|
address string
|
||||||
|
port int
|
||||||
|
streamType StreamType
|
||||||
|
maxStreams int
|
||||||
|
|
||||||
|
onNewStream func(*EQStream)
|
||||||
|
onStreamClosed func(*EQStream)
|
||||||
|
onAppPacket func(*EQStream, *AppPacket)
|
||||||
|
|
||||||
|
totalConnections uint64
|
||||||
|
activeConnections uint32
|
||||||
|
statsMu sync.RWMutex
|
||||||
|
|
||||||
|
shutdown chan bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStreamServer creates a new EQ stream server
|
||||||
|
func NewStreamServer(address string, port int, streamType StreamType) *StreamServer {
|
||||||
|
return &StreamServer{
|
||||||
|
streams: make(map[string]*EQStream),
|
||||||
|
address: address,
|
||||||
|
port: port,
|
||||||
|
streamType: streamType,
|
||||||
|
maxStreams: 10000,
|
||||||
|
shutdown: make(chan bool),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start starts the stream server
|
||||||
|
func (s *StreamServer) Start() error {
|
||||||
|
addr := fmt.Sprintf("%s:%d", s.address, s.port)
|
||||||
|
|
||||||
|
// Configure gnet options
|
||||||
|
options := []gnet.Option{
|
||||||
|
gnet.WithMulticore(true),
|
||||||
|
gnet.WithReusePort(true),
|
||||||
|
gnet.WithTicker(true),
|
||||||
|
gnet.WithTCPKeepAlive(time.Minute * 5),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start gnet server
|
||||||
|
return gnet.Run(s, fmt.Sprintf("udp://%s", addr), options...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop stops the stream server
|
||||||
|
func (s *StreamServer) Stop() {
|
||||||
|
close(s.shutdown)
|
||||||
|
|
||||||
|
// Close all active streams
|
||||||
|
s.streamsMu.Lock()
|
||||||
|
for _, stream := range s.streams {
|
||||||
|
stream.Close()
|
||||||
|
}
|
||||||
|
s.streams = make(map[string]*EQStream)
|
||||||
|
s.streamsMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnBoot is called when the server starts
|
||||||
|
func (s *StreamServer) OnBoot(eng gnet.Engine) gnet.Action {
|
||||||
|
fmt.Printf("EQStream server started on %s:%d\n", s.address, s.port)
|
||||||
|
return gnet.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnShutdown is called when the server stops
|
||||||
|
func (s *StreamServer) OnShutdown(eng gnet.Engine) {
|
||||||
|
fmt.Printf("EQStream server shutdown\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnOpen is called when a new connection is established
|
||||||
|
func (s *StreamServer) OnOpen(c gnet.Conn) (out []byte, action gnet.Action) {
|
||||||
|
// Create new stream
|
||||||
|
stream := NewEQStream(c)
|
||||||
|
stream.streamType = s.streamType
|
||||||
|
|
||||||
|
// Set up callbacks
|
||||||
|
stream.onAppPacket = func(p *AppPacket) {
|
||||||
|
if s.onAppPacket != nil {
|
||||||
|
s.onAppPacket(stream, p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.onDisconnect = func() {
|
||||||
|
s.removeStream(stream)
|
||||||
|
if s.onStreamClosed != nil {
|
||||||
|
s.onStreamClosed(stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store stream in connection context
|
||||||
|
c.SetContext(stream)
|
||||||
|
|
||||||
|
// Add to stream map
|
||||||
|
key := s.getStreamKey(c)
|
||||||
|
s.streamsMu.Lock()
|
||||||
|
s.streams[key] = stream
|
||||||
|
s.activeConnections++
|
||||||
|
s.totalConnections++
|
||||||
|
s.streamsMu.Unlock()
|
||||||
|
|
||||||
|
// Call new stream callback
|
||||||
|
if s.onNewStream != nil {
|
||||||
|
s.onNewStream(stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, gnet.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnClose is called when a connection is closed
|
||||||
|
func (s *StreamServer) OnClose(c gnet.Conn, err error) (action gnet.Action) {
|
||||||
|
if stream, ok := c.Context().(*EQStream); ok {
|
||||||
|
stream.Close()
|
||||||
|
s.removeStream(stream)
|
||||||
|
|
||||||
|
if s.onStreamClosed != nil {
|
||||||
|
s.onStreamClosed(stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return gnet.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnTraffic is called when data is received
|
||||||
|
func (s *StreamServer) OnTraffic(c gnet.Conn) (action gnet.Action) {
|
||||||
|
stream, ok := c.Context().(*EQStream)
|
||||||
|
if !ok {
|
||||||
|
return gnet.Close
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read all available data
|
||||||
|
for {
|
||||||
|
// Peek at the data to determine packet size
|
||||||
|
buf, err := c.Peek(-1)
|
||||||
|
if err != nil || len(buf) < 2 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse packet length from opcode and data
|
||||||
|
// For UDP, each datagram is a complete packet
|
||||||
|
packet, err := ParseProtocolPacket(buf)
|
||||||
|
if err != nil {
|
||||||
|
// Invalid packet, skip it
|
||||||
|
c.Discard(len(buf))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume the packet data
|
||||||
|
c.Discard(len(buf))
|
||||||
|
|
||||||
|
// Process packet
|
||||||
|
if err := stream.ProcessPacket(packet); err != nil {
|
||||||
|
// Log error but continue processing
|
||||||
|
fmt.Printf("Error processing packet: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return gnet.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnTick is called periodically for maintenance tasks
|
||||||
|
func (s *StreamServer) OnTick() (delay time.Duration, action gnet.Action) {
|
||||||
|
// Check for timeouts and retransmissions
|
||||||
|
s.streamsMu.RLock()
|
||||||
|
streams := make([]*EQStream, 0, len(s.streams))
|
||||||
|
for _, stream := range s.streams {
|
||||||
|
streams = append(streams, stream)
|
||||||
|
}
|
||||||
|
s.streamsMu.RUnlock()
|
||||||
|
|
||||||
|
// Process each stream
|
||||||
|
for _, stream := range streams {
|
||||||
|
// Check for timeout
|
||||||
|
if stream.CheckTimeout() {
|
||||||
|
s.removeStream(stream)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for retransmissions
|
||||||
|
stream.CheckRetransmissions()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return delay until next tick (100ms)
|
||||||
|
return time.Millisecond * 100, gnet.None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper methods
|
||||||
|
|
||||||
|
func (s *StreamServer) getStreamKey(c gnet.Conn) string {
|
||||||
|
if addr := c.RemoteAddr(); addr != nil {
|
||||||
|
return addr.String()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StreamServer) removeStream(stream *EQStream) {
|
||||||
|
// Find and remove stream from map
|
||||||
|
s.streamsMu.Lock()
|
||||||
|
for key, str := range s.streams {
|
||||||
|
if str == stream {
|
||||||
|
delete(s.streams, key)
|
||||||
|
s.activeConnections--
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.streamsMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOnNewStream sets the callback for new streams
|
||||||
|
func (s *StreamServer) SetOnNewStream(callback func(*EQStream)) {
|
||||||
|
s.onNewStream = callback
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOnStreamClosed sets the callback for closed streams
|
||||||
|
func (s *StreamServer) SetOnStreamClosed(callback func(*EQStream)) {
|
||||||
|
s.onStreamClosed = callback
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOnAppPacket sets the callback for application packets
|
||||||
|
func (s *StreamServer) SetOnAppPacket(callback func(*EQStream, *AppPacket)) {
|
||||||
|
s.onAppPacket = callback
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStream returns a stream by remote address
|
||||||
|
func (s *StreamServer) GetStream(address string) *EQStream {
|
||||||
|
s.streamsMu.RLock()
|
||||||
|
defer s.streamsMu.RUnlock()
|
||||||
|
return s.streams[address]
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetActiveStreams returns all active streams
|
||||||
|
func (s *StreamServer) GetActiveStreams() []*EQStream {
|
||||||
|
s.streamsMu.RLock()
|
||||||
|
defer s.streamsMu.RUnlock()
|
||||||
|
|
||||||
|
streams := make([]*EQStream, 0, len(s.streams))
|
||||||
|
for _, stream := range s.streams {
|
||||||
|
streams = append(streams, stream)
|
||||||
|
}
|
||||||
|
return streams
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStats returns server statistics
|
||||||
|
func (s *StreamServer) GetStats() (total uint64, active uint32) {
|
||||||
|
s.statsMu.RLock()
|
||||||
|
defer s.statsMu.RUnlock()
|
||||||
|
return s.totalConnections, s.activeConnections
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user