#[non_exhaustive]#[repr(usize)]pub enum Slot {
Show 14 variants
CongestionControl = 1_000,
TwccSender = 2_000,
Pacer = 3_000,
NackResponder = 4_000,
FecEncoder = 5_000,
FecDecoder = 6_000,
NackGenerator = 7_000,
TwccReceiver = 8_000,
Rfc8888 = 9_000,
ReceiverReport = 10_000,
SenderReport = 11_000,
IntervalPli = 12_000,
JitterBuffer = 13_000,
Custom(usize),
}Expand description
Where an interceptor belongs in the chain, measured by distance from the wire.
This is the chain contract’s ordering table expressed as data, so that one place decides it and a test can check a builder against it. The doc comments carry that table’s indices; the gaps are slots nothing fills yet.
Read walks the list forwards and write walks it in reverse, so a smaller slot is closer to the network in both directions.
Variants (Non-exhaustive)§
This enum is marked as non-exhaustive
CongestionControl = 1_000
CongestionControlInterceptor — the send history, and the ingest of returning feedback.
Both legs. Read: ingests inbound TWCC or CCFB and hands the reports to the estimator. Write: records every departing RTP packet in the send history.
Why here — wire-most. The write walk runs from the application down to the wire, so the lowest slot is the last thing a departing packet meets. Two facts depend on that. The departure instant it records is the moment the packet actually left, after the pacer held it; recording at enqueue instead would charge the pacer’s own queueing delay to the network and drive the estimate down for a delay this endpoint created. And the transport-wide sequence number the history keys on has already been assigned at 2_000, so a report naming that number can be matched to the packet that carried it.
TwccSender = 2_000
TwccSenderInterceptor — stamps each departing RTP packet with the transport-wide sequence
number the remote will report against.
Outbound only. Inbound packets pass through untouched.
Why here — below every generator, above the history. The write walk must reach it
before Slot::CongestionControl, or the history would key on a number that does not
exist yet. It must sit below everything that produces a packet — the NACK responder at
4_000, the FEC encoder at 5_000, every RTCP generator above them — because a packet that
never passes this slot is never numbered, and the remote cannot report on what it cannot
name. A retransmission is exactly that case.
Pacer = 3_000
PacerInterceptor — gates departures, releasing at the estimated rate.
Outbound, with one inbound read. Write: queues RTP and releases it on a timer; RTCP
passes straight through, because feedback is only useful while it is fresh. Read: observes
Attribute::TargetBitrateChanged going past.
Why here — the meter every generated byte must cross. Everything that produces a packet sits application-ward of this slot, so retransmissions, FEC repair and generated RTCP are all metered rather than bursting past the estimate. It is above the TWCC sender so that numbering happens at release rather than at enqueue, which keeps the numbers in the order the packets actually reach the wire.
The estimate reaches it on the read leg because that is the only leg it can: the controller is wire-ward of here, so on the write leg it sees packets after this interceptor — too late to inform it.
NackResponder = 4_000
NackResponderInterceptor — answers a NACK by resending from its own buffer.
Both legs. Write: buffers each departing RTP packet against a later request. Read: watches for inbound NACK and queues the retransmissions it asks for.
Why here — the lowest of the generators. A retransmission it emits re-enters the belt at this slot and continues down, so it is still paced (3_000), numbered (2_000) and recorded (1_000). It has to be: a retransmission is new bytes on the wire, and an estimator that does not see them believes the path is carrying less than it is — then raises the rate during loss, which is the worst moment to do it.
FecEncoder = 5_000
FlexFec03SendInterceptor — generates repair packets for the media it sees leaving.
Outbound only.
Why here — a generator, so above the pacer. Its repair packets are real bytes and are metered and recorded like any other. Being above the NACK responder also means the media it protects has already been buffered for retransmission, so the two recovery mechanisms cover the same packets rather than racing to protect different ones.
FecDecoder = 6_000
FlexFec03ReceiveInterceptor — rebuilds packets the path dropped.
Inbound only. Nothing on the write leg.
Why here — before anything reads a sequence number. The read walk runs wire to application, so this recovers a packet before the NACK generator at 7_000 can notice it was missing. Placed the other way round, this endpoint would ask the remote to retransmit packets it was about to rebuild locally — paying for the same data twice, and adding a round trip to data it already had.
NackGenerator = 7_000
NackGeneratorInterceptor — asks the remote for what did not arrive.
Both legs. Read: detects gaps in the inbound sequence space. Write: emits NACK on a timer.
Why here — after recovery, before re-timing. After the FEC decoder (6_000), so a rebuilt packet counts as arrived and is not requested again. Before the jitter buffer (13_000), so it judges loss from arrival order rather than from playout order. And, being a generator, application-ward of the pacer so its NACKs are metered.
TwccReceiver = 8_000
TwccReceiverInterceptor — reports arrival times to the remote sender’s congestion
controller.
Both legs. Read: records when each inbound packet arrived. Write: emits
TransportLayerCC on a timer.
Why here — the write leg is what pins it. It reads as a receive-side interceptor, and
moving it wire-ward looks harmless because nothing it does affects what this endpoint sends.
It does not work: it generates, and below the pacer its feedback would leave unpaced and
unrecorded by the send history. It is also an arrival recorder, so it must precede the
jitter buffer — see Slot::JitterBuffer.
Rfc8888 = 9_000
Rfc8888Interceptor — the same job as Slot::TwccReceiver in a different format.
Both legs, and pinned by the same two constraints: a generator above the pacer, an arrival recorder before the jitter buffer. It sits next to the TWCC receiver because the two are alternatives — registering both reports every packet to the remote twice, and its estimator cannot tell the two formats apart, so it reads the path as carrying double.
ReceiverReport = 10_000
ReceiverReportInterceptor — RFC 3550 reception quality, not congestion-control feedback.
Both legs. Read: accumulates loss, jitter and the extended sequence number from inbound RTP. Write: emits RR on a timer.
Why here — the same pair of constraints as the arrival recorders above. Above the pacer because it generates; before the jitter buffer because the jitter it measures must be the path’s, not this endpoint’s buffering.
SenderReport = 11_000
SenderReportInterceptor — emits SR on a timer, describing what this endpoint has sent.
Outbound only.
Why here — generator, and nothing else constrains it. Above the pacer so its reports are metered. Nothing inbound informs it, so it has no read-side ordering requirement and its exact position among the generators does not matter.
IntervalPli = 12_000
IntervalPliInterceptor — asks the remote for a keyframe on a timer.
Outbound only.
Why here — generator, and nothing else constrains it, exactly as for
Slot::SenderReport.
JitterBuffer = 13_000
JitterBufferInterceptor — holds inbound packets to smooth arrival jitter, then releases
them in order on a timer.
Inbound only.
Why here — application-most, because it re-times what passes through it. Every arrival recorder must precede it. One placed after would read a packet’s playout instant and report it to the remote as its arrival time; the remote’s congestion controller would then see this endpoint’s own buffering depth as network delay variation — a delay signal manufactured locally and indistinguishable, at the far end, from a congested path.
Custom(usize)
Anywhere else, for an interceptor this crate knows nothing about.
The named slots are spaced a thousand apart so one of your own fits between any two of them
without renumbering anything: Slot::from(6_500) sits after the FEC decoder and before the
NACK generator. Reach it through From<usize> rather than by
naming the variant, so the spelling survives this gaining a richer representation.
Choosing a number. Work out which legs your interceptor uses, then apply the same rules the named slots obey:
- It produces packets — retransmissions, repair, RTCP, anything the wire has not seen yet.
Put it above
Slot::Pacer(> 3_000), or its output leaves unpaced and the send history never counts the bytes. This is the constraint people miss, because an interceptor that only reports on what it received still produces packets to report with. - It reads inbound sequence numbers or arrival times — loss detection, arrival recording,
reception statistics. Put it below
Slot::JitterBuffer(< 13_000), so it sees the order and timing the path produced rather than the order this endpoint replays. - It repairs or recovers inbound packets. Put it below anything that would otherwise ask
for them again — below
Slot::NackGenerator(< 7_000), as the FEC decoder is. - It only observes, and emits nothing. Nothing pins it; pick a slot that reads well next to its neighbours.
Both legs walk this one list — read from low to high, write from high to low — so a slot is
a position in both directions at once. An interceptor that acts on each leg is subject to
the constraints of each, and those can pull in opposite directions:
Slot::TwccReceiver is the worked example.