package udpsprotocol // SequenceGate orders DATA packets by the producer's packet counter. // // Reassembly completes in arrival order, not counter order, so a packet that // was delayed or duplicated on the wire is handed up after a newer one has // already been consumed. Its samples then carry an older time base than the // data already in the ring: they land on top of samples that are already // there, and the span they should have filled stays empty. That is a hole on // one side and a collision on the other, from a packet that is entirely // well-formed — the counter is the only thing that distinguishes it. // // A SequenceGate is not safe for concurrent use; each receive loop owns one. type SequenceGate struct { last uint32 valid bool // Stale counts packets rejected for not advancing the counter (reordered // or duplicated), for diagnostics. Stale uint64 } // Reset forgets the sequence. Call it on (re)connect: the producer's counter // restarts independently of ours, so a counter carried over from the previous // connection would reject the whole new stream. func (g *SequenceGate) Reset() { g.last = 0 g.valid = false } // Accept reports whether a DATA packet with this counter should be delivered, // and how many packets went missing immediately before it. // // The counter is a wrapping uint32, so ordering is done on the signed // difference: a plain comparison would call the first packet after the wrap // stale and reject everything from then on. func (g *SequenceGate) Accept(counter uint32) (ok bool, lost uint32) { if !g.valid { g.valid = true g.last = counter return true, 0 } delta := int32(counter - g.last) if delta <= 0 { g.Stale++ return false, 0 } g.last = counter return true, uint32(delta) - 1 }