Files
Martino Ferrari e3389f932b FIrs
2026-05-15 17:42:14 +02:00

106 lines
2.3 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package main
import (
"sync"
"time"
)
// fragmentSet holds the received fragments for one (counter, type) pair.
type fragmentSet struct {
total int
received int
fragments [][]byte // indexed by fragmentIdx
lastSeen time.Time
}
// Reassembler reassembles fragmented UDP payloads.
// Key: uint64(counter)<<8 | uint64(pktType)
type Reassembler struct {
mu sync.Mutex
sets map[uint64]*fragmentSet
expiry time.Duration // how long to keep incomplete sets
}
// NewReassembler creates a Reassembler that expires stale fragment sets after ttl.
func NewReassembler(ttl time.Duration) *Reassembler {
r := &Reassembler{
sets: make(map[uint64]*fragmentSet),
expiry: ttl,
}
go r.gcLoop()
return r
}
func reassemblyKey(counter uint32, pktType uint8) uint64 {
return uint64(counter)<<8 | uint64(pktType)
}
// AddFragment registers one fragment. Returns the reassembled payload and true
// when all fragments for this (counter, type) have been received, otherwise
// returns nil, false.
func (r *Reassembler) AddFragment(h PacketHeader, payload []byte) ([]byte, bool) {
key := reassemblyKey(h.Counter, h.Type)
total := int(h.TotalFragments)
idx := int(h.FragmentIdx)
// Fast path: single-fragment packet.
if total == 1 && idx == 0 {
return payload, true
}
r.mu.Lock()
defer r.mu.Unlock()
fs, ok := r.sets[key]
if !ok {
fs = &fragmentSet{
total: total,
fragments: make([][]byte, total),
}
r.sets[key] = fs
}
if idx >= len(fs.fragments) {
// Stale or corrupt discard.
return nil, false
}
if fs.fragments[idx] == nil {
fs.fragments[idx] = payload
fs.received++
}
fs.lastSeen = time.Now()
if fs.received < fs.total {
return nil, false
}
// All fragments received concatenate in order.
delete(r.sets, key)
total_len := 0
for _, f := range fs.fragments {
total_len += len(f)
}
out := make([]byte, 0, total_len)
for _, f := range fs.fragments {
out = append(out, f...)
}
return out, true
}
// gcLoop periodically removes fragment sets that have been incomplete for too long.
func (r *Reassembler) gcLoop() {
ticker := time.NewTicker(r.expiry / 2)
defer ticker.Stop()
for range ticker.C {
r.mu.Lock()
now := time.Now()
for k, fs := range r.sets {
if now.Sub(fs.lastSeen) > r.expiry {
delete(r.sets, k)
}
}
r.mu.Unlock()
}
}