280 lines
5.5 KiB
Go
280 lines
5.5 KiB
Go
package udp
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/binary"
|
|
"net"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type ConnectionState int
|
|
|
|
const (
|
|
StateClosed ConnectionState = iota
|
|
StateEstablished
|
|
StateClosing
|
|
)
|
|
|
|
type Connection struct {
|
|
addr *net.UDPAddr
|
|
conn *net.UDPConn
|
|
handler PacketHandler
|
|
state ConnectionState
|
|
mutex sync.RWMutex
|
|
|
|
// Session data
|
|
sessionID uint32
|
|
key uint32
|
|
compressed bool
|
|
encoded bool
|
|
maxLength uint32
|
|
|
|
// Sequence tracking
|
|
nextInSeq uint16
|
|
nextOutSeq uint16
|
|
|
|
// Queues
|
|
inboundQueue []*ApplicationPacket
|
|
outboundQueue []*ProtocolPacket
|
|
ackQueue []uint16
|
|
|
|
// Timing
|
|
lastPacketTime time.Time
|
|
|
|
// Crypto
|
|
crypto *Crypto
|
|
}
|
|
|
|
func NewConnection(addr *net.UDPAddr, conn *net.UDPConn, handler PacketHandler) *Connection {
|
|
return &Connection{
|
|
addr: addr,
|
|
conn: conn,
|
|
handler: handler,
|
|
state: StateClosed,
|
|
maxLength: 512,
|
|
lastPacketTime: time.Now(),
|
|
crypto: NewCrypto(),
|
|
}
|
|
}
|
|
|
|
func (c *Connection) ProcessPacket(data []byte) {
|
|
c.lastPacketTime = time.Now()
|
|
|
|
packet, err := ParseProtocolPacket(data)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
switch packet.Opcode {
|
|
case OpSessionRequest:
|
|
c.handleSessionRequest(packet)
|
|
case OpSessionResponse:
|
|
c.handleSessionResponse(packet)
|
|
case OpPacket:
|
|
c.handleDataPacket(packet)
|
|
case OpAck:
|
|
c.handleAck(packet)
|
|
case OpKeepAlive:
|
|
c.sendKeepAlive()
|
|
case OpSessionDisconnect:
|
|
c.Close()
|
|
}
|
|
}
|
|
|
|
func (c *Connection) handleSessionRequest(packet *ProtocolPacket) {
|
|
if len(packet.Data) < 12 {
|
|
return
|
|
}
|
|
|
|
// Parse session request
|
|
c.sessionID = binary.LittleEndian.Uint32(packet.Data[4:8])
|
|
requestedMaxLen := binary.LittleEndian.Uint32(packet.Data[8:12])
|
|
|
|
if requestedMaxLen > 0 {
|
|
c.maxLength = requestedMaxLen
|
|
}
|
|
|
|
// Generate encryption key
|
|
keyBytes := make([]byte, 4)
|
|
rand.Read(keyBytes)
|
|
c.key = binary.LittleEndian.Uint32(keyBytes)
|
|
|
|
// Send session response
|
|
c.sendSessionResponse()
|
|
c.state = StateEstablished
|
|
}
|
|
|
|
func (c *Connection) handleSessionResponse(packet *ProtocolPacket) {
|
|
// Client-side session response handling
|
|
if len(packet.Data) < 20 {
|
|
return
|
|
}
|
|
|
|
c.sessionID = binary.LittleEndian.Uint32(packet.Data[0:4])
|
|
c.key = binary.LittleEndian.Uint32(packet.Data[4:8])
|
|
format := packet.Data[9]
|
|
c.compressed = (format & 0x01) != 0
|
|
c.encoded = (format & 0x04) != 0
|
|
c.maxLength = binary.LittleEndian.Uint32(packet.Data[12:16])
|
|
|
|
c.state = StateEstablished
|
|
}
|
|
|
|
func (c *Connection) handleDataPacket(packet *ProtocolPacket) {
|
|
if len(packet.Data) < 2 {
|
|
return
|
|
}
|
|
|
|
seq := binary.BigEndian.Uint16(packet.Data[0:2])
|
|
payload := packet.Data[2:]
|
|
|
|
// Simple in-order processing for now
|
|
if seq == c.nextInSeq {
|
|
c.nextInSeq++
|
|
c.sendAck(seq)
|
|
|
|
// Process application packet
|
|
if appPacket, err := c.processApplicationData(payload); err == nil {
|
|
c.handler.HandlePacket(c, appPacket)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Connection) handleAck(packet *ProtocolPacket) {
|
|
if len(packet.Data) < 2 {
|
|
return
|
|
}
|
|
|
|
seq := binary.BigEndian.Uint16(packet.Data[0:2])
|
|
// Remove acknowledged packets from retransmit queue
|
|
_ = seq // TODO: implement retransmit queue
|
|
}
|
|
|
|
func (c *Connection) processApplicationData(data []byte) (*ApplicationPacket, error) {
|
|
// Decrypt if needed
|
|
if c.crypto.IsEncrypted() {
|
|
data = c.crypto.Decrypt(data)
|
|
}
|
|
|
|
// Decompress if needed
|
|
if c.compressed && len(data) > 0 {
|
|
var err error
|
|
data, err = Decompress(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return ParseApplicationPacket(data)
|
|
}
|
|
|
|
func (c *Connection) SendPacket(packet *ApplicationPacket) {
|
|
c.mutex.Lock()
|
|
defer c.mutex.Unlock()
|
|
|
|
data := packet.Serialize()
|
|
|
|
// Compress if needed
|
|
if c.compressed && len(data) > 128 {
|
|
if compressed, err := Compress(data); err == nil {
|
|
data = compressed
|
|
}
|
|
}
|
|
|
|
// Encrypt if needed
|
|
if c.crypto.IsEncrypted() {
|
|
data = c.crypto.Encrypt(data)
|
|
}
|
|
|
|
// Create protocol packet
|
|
protocolData := make([]byte, 2+len(data))
|
|
binary.BigEndian.PutUint16(protocolData[0:2], c.nextOutSeq)
|
|
copy(protocolData[2:], data)
|
|
c.nextOutSeq++
|
|
|
|
protocolPacket := &ProtocolPacket{
|
|
Opcode: OpPacket,
|
|
Data: protocolData,
|
|
}
|
|
|
|
c.sendProtocolPacket(protocolPacket)
|
|
}
|
|
|
|
func (c *Connection) sendSessionResponse() {
|
|
data := make([]byte, 20)
|
|
binary.LittleEndian.PutUint32(data[0:4], c.sessionID)
|
|
binary.LittleEndian.PutUint32(data[4:8], c.key)
|
|
data[8] = 2 // UnknownA
|
|
|
|
var format uint8
|
|
if c.compressed {
|
|
format |= 0x01
|
|
}
|
|
if c.encoded {
|
|
format |= 0x04
|
|
}
|
|
data[9] = format
|
|
|
|
data[10] = 0 // UnknownB
|
|
binary.LittleEndian.PutUint32(data[12:16], c.maxLength)
|
|
binary.LittleEndian.PutUint32(data[16:20], 0) // UnknownD
|
|
|
|
packet := &ProtocolPacket{
|
|
Opcode: OpSessionResponse,
|
|
Data: data,
|
|
}
|
|
|
|
c.sendProtocolPacket(packet)
|
|
}
|
|
|
|
func (c *Connection) sendAck(seq uint16) {
|
|
data := make([]byte, 2)
|
|
binary.BigEndian.PutUint16(data, seq)
|
|
|
|
packet := &ProtocolPacket{
|
|
Opcode: OpAck,
|
|
Data: data,
|
|
}
|
|
|
|
c.sendProtocolPacket(packet)
|
|
}
|
|
|
|
func (c *Connection) sendKeepAlive() {
|
|
packet := &ProtocolPacket{
|
|
Opcode: OpKeepAlive,
|
|
Data: []byte{},
|
|
}
|
|
|
|
c.sendProtocolPacket(packet)
|
|
}
|
|
|
|
func (c *Connection) sendProtocolPacket(packet *ProtocolPacket) {
|
|
data := packet.Serialize()
|
|
c.conn.WriteToUDP(data, c.addr)
|
|
}
|
|
|
|
func (c *Connection) Close() {
|
|
c.mutex.Lock()
|
|
defer c.mutex.Unlock()
|
|
|
|
if c.state == StateEstablished {
|
|
c.state = StateClosing
|
|
|
|
// Send disconnect
|
|
disconnectData := make([]byte, 6)
|
|
binary.LittleEndian.PutUint32(disconnectData[0:4], c.sessionID)
|
|
disconnectData[4] = 0
|
|
disconnectData[5] = 6
|
|
|
|
packet := &ProtocolPacket{
|
|
Opcode: OpSessionDisconnect,
|
|
Data: disconnectData,
|
|
}
|
|
|
|
c.sendProtocolPacket(packet)
|
|
}
|
|
|
|
c.state = StateClosed
|
|
}
|