212 lines
5.9 KiB
Go
212 lines
5.9 KiB
Go
package udp
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"eq2emu/internal/opcodes"
|
|
"errors"
|
|
"fmt"
|
|
"sort"
|
|
)
|
|
|
|
// FragmentManager handles packet fragmentation and reassembly
|
|
type FragmentManager struct {
|
|
fragments map[uint16]*FragmentGroup // Active fragment groups by base sequence
|
|
maxLength uint32 // Maximum packet size before fragmentation
|
|
}
|
|
|
|
// FragmentGroup tracks fragments belonging to the same original packet
|
|
type FragmentGroup struct {
|
|
BaseSequence uint16 // Base sequence number
|
|
TotalLength uint32 // Expected total reassembled length
|
|
Fragments []FragmentPiece // Individual fragment pieces
|
|
FirstSeen bool // Whether we've seen the first fragment
|
|
}
|
|
|
|
// FragmentPiece represents a single fragment
|
|
type FragmentPiece struct {
|
|
Sequence uint16 // Fragment sequence number
|
|
Data []byte // Fragment payload data
|
|
IsFirst bool // Whether this is the first fragment
|
|
}
|
|
|
|
// NewFragmentManager creates a manager with specified maximum packet length
|
|
func NewFragmentManager(maxLength uint32) *FragmentManager {
|
|
return &FragmentManager{
|
|
fragments: make(map[uint16]*FragmentGroup),
|
|
maxLength: maxLength,
|
|
}
|
|
}
|
|
|
|
// FragmentPacket splits large packets into fragments
|
|
// Returns nil if packet doesn't need fragmentation
|
|
func (fm *FragmentManager) FragmentPacket(data []byte, startSeq uint16) []*ProtocolPacket {
|
|
if uint32(len(data)) <= fm.maxLength {
|
|
return nil // No fragmentation needed
|
|
}
|
|
|
|
totalLength := uint32(len(data))
|
|
chunkSize := int(fm.maxLength - 6) // Reserve 6 bytes for headers
|
|
if chunkSize <= 0 {
|
|
chunkSize = 1
|
|
}
|
|
|
|
var packets []*ProtocolPacket
|
|
seq := startSeq
|
|
|
|
for offset := 0; offset < len(data); offset += chunkSize {
|
|
end := offset + chunkSize
|
|
if end > len(data) {
|
|
end = len(data)
|
|
}
|
|
|
|
var fragmentData []byte
|
|
if offset == 0 {
|
|
// First fragment includes total length
|
|
fragmentData = make([]byte, 6+end-offset)
|
|
binary.BigEndian.PutUint16(fragmentData[0:2], seq)
|
|
binary.LittleEndian.PutUint32(fragmentData[2:6], totalLength)
|
|
copy(fragmentData[6:], data[offset:end])
|
|
} else {
|
|
// Subsequent fragments
|
|
fragmentData = make([]byte, 2+end-offset)
|
|
binary.BigEndian.PutUint16(fragmentData[0:2], seq)
|
|
copy(fragmentData[2:], data[offset:end])
|
|
}
|
|
|
|
packets = append(packets, &ProtocolPacket{
|
|
Opcode: opcodes.OpFragment,
|
|
Data: fragmentData,
|
|
})
|
|
seq++
|
|
}
|
|
|
|
return packets
|
|
}
|
|
|
|
// ProcessFragment handles incoming fragments and returns complete packet when ready
|
|
func (fm *FragmentManager) ProcessFragment(packet *ProtocolPacket) ([]byte, bool, error) {
|
|
if len(packet.Data) < 2 {
|
|
return nil, false, errors.New("fragment too small")
|
|
}
|
|
|
|
seq := binary.BigEndian.Uint16(packet.Data[0:2])
|
|
|
|
// Parse fragment data
|
|
fragment := FragmentPiece{Sequence: seq}
|
|
|
|
if len(packet.Data) >= 6 {
|
|
// Check if this is the first fragment (has total length)
|
|
possibleLength := binary.LittleEndian.Uint32(packet.Data[2:6])
|
|
if possibleLength > 0 && possibleLength < 10*1024*1024 { // Reasonable limit
|
|
fragment.IsFirst = true
|
|
fragment.Data = packet.Data[6:]
|
|
|
|
// Create new fragment group
|
|
group := &FragmentGroup{
|
|
BaseSequence: seq,
|
|
TotalLength: possibleLength,
|
|
Fragments: []FragmentPiece{fragment},
|
|
FirstSeen: true,
|
|
}
|
|
fm.fragments[seq] = group
|
|
|
|
return fm.tryAssemble(seq)
|
|
}
|
|
}
|
|
|
|
// Not first fragment - find matching group
|
|
fragment.Data = packet.Data[2:]
|
|
group := fm.findFragmentGroup(seq)
|
|
if group == nil {
|
|
return nil, false, errors.New("orphaned fragment")
|
|
}
|
|
|
|
group.Fragments = append(group.Fragments, fragment)
|
|
return fm.tryAssemble(group.BaseSequence)
|
|
}
|
|
|
|
// findFragmentGroup locates the fragment group for a sequence number
|
|
func (fm *FragmentManager) findFragmentGroup(seq uint16) *FragmentGroup {
|
|
// Look for groups where this sequence fits
|
|
for baseSeq, group := range fm.fragments {
|
|
if seq >= baseSeq && seq < baseSeq+100 { // Reasonable window
|
|
return group
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// tryAssemble attempts to reassemble fragments into complete packet
|
|
func (fm *FragmentManager) tryAssemble(baseSeq uint16) ([]byte, bool, error) {
|
|
group, exists := fm.fragments[baseSeq]
|
|
if !exists || !group.FirstSeen {
|
|
return nil, false, nil
|
|
}
|
|
|
|
// Calculate expected fragment count
|
|
chunkSize := int(fm.maxLength - 6)
|
|
expectedCount := int(group.TotalLength) / chunkSize
|
|
if int(group.TotalLength)%chunkSize != 0 {
|
|
expectedCount++
|
|
}
|
|
|
|
if len(group.Fragments) < expectedCount {
|
|
return nil, false, nil // Still waiting for fragments
|
|
}
|
|
|
|
// Sort fragments by sequence number
|
|
sort.Slice(group.Fragments, func(i, j int) bool {
|
|
return group.Fragments[i].Sequence < group.Fragments[j].Sequence
|
|
})
|
|
|
|
// Reassemble packet
|
|
result := make([]byte, 0, group.TotalLength)
|
|
for _, frag := range group.Fragments[:expectedCount] {
|
|
result = append(result, frag.Data...)
|
|
}
|
|
|
|
// Validate length
|
|
if uint32(len(result)) != group.TotalLength {
|
|
delete(fm.fragments, baseSeq)
|
|
return nil, false, fmt.Errorf("assembled length %d != expected %d", len(result), group.TotalLength)
|
|
}
|
|
|
|
// Clean up
|
|
delete(fm.fragments, baseSeq)
|
|
return result, true, nil
|
|
}
|
|
|
|
// CleanupStale removes old incomplete fragment groups
|
|
func (fm *FragmentManager) CleanupStale(maxAge uint16) {
|
|
// Simple cleanup - remove groups with very old base sequences
|
|
for baseSeq := range fm.fragments {
|
|
if baseSeq < maxAge {
|
|
delete(fm.fragments, baseSeq)
|
|
}
|
|
}
|
|
}
|
|
|
|
// GetStats returns fragmentation statistics
|
|
func (fm *FragmentManager) GetStats() FragmentStats {
|
|
return FragmentStats{
|
|
ActiveGroups: len(fm.fragments),
|
|
MaxLength: fm.maxLength,
|
|
}
|
|
}
|
|
|
|
// FragmentStats contains fragmentation statistics
|
|
type FragmentStats struct {
|
|
ActiveGroups int // Number of incomplete fragment groups
|
|
MaxLength uint32 // Maximum packet length setting
|
|
}
|
|
|
|
// Clear removes all pending fragments
|
|
func (fm *FragmentManager) Clear() {
|
|
fm.fragments = make(map[uint16]*FragmentGroup)
|
|
}
|
|
|
|
// SetMaxLength updates the maximum packet length
|
|
func (fm *FragmentManager) SetMaxLength(maxLength uint32) {
|
|
fm.maxLength = maxLength
|
|
}
|