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() } }