An SRTP packet carries only a 16-bit sequence number; how does the receiver estimate the 32-bit rollover counter near a wrap?
answer
- the ROC is never on the wire
- three candidates around the stored ROC
- closest to 2^16 x ROC + s_l
- update only after authentication
basics
~20 sAn SRTP receiver tries ROC-1, ROC and ROC+1 with the packet's sequence number and picks whichever 48-bit index lies closest to its highest index, 2^16 x ROC + s_l; it updates ROC and s_l only after the packet authenticates.
solid answer
~50 sThe 48-bit index is `2^16 × ROC + SEQ`, but only the 16-bit `SEQ` is sent. The receiver keeps its own `ROC` and `s_l`, the highest sequence number seen, and RFC 3711 says it SHOULD estimate the index as `2^16 × v + SEQ`, with `v` chosen from `{ROC-1, ROC, ROC+1}` so the result is closest to `2^16 × ROC + s_l`. In practice: if `s_l` is high and `SEQ` is low, the sequence has wrapped, so `v = ROC+1`; if `s_l` is low and `SEQ` is high, it is a late packet from before the wrap, so `v = ROC-1`; otherwise `v = ROC`. Only after the packet authenticates does the receiver update: `ROC+1` advances both ROC and `s_l`, `ROC` raises `s_l` if `SEQ` is larger, `ROC-1` changes nothing. It stays correct while loss or reordering stays under 2^15 packets.
code
pseudocode · 12 lines// RFC 3711 Appendix A, signed arithmetic
if s_l < 32768:
if SEQ - s_l > 32768:
v = (ROC - 1) mod 2^32 // late, before the wrap
else:
v = ROC
else:
if s_l - 32768 > SEQ:
v = (ROC + 1) mod 2^32 // the sequence wrapped
else:
v = ROC
return SEQ + v * 65536go deeper
Recall that the SRTP index is the 32-bit rollover counter times 65,536 plus the 16-bit sequence number, and that only the sequence number is sent.
Compute an index across a wrap and for a late packet just before it, choosing among ROC-1, ROC and ROC+1, and state when ROC and s_l are allowed to change.
Explain where estimation fails: 2^15 lost or reordered packets, a random initial sequence number near the wrap, no authentication. Connect a wrong estimate to every tag failing.
Judge whether implicit indexing is an acceptable design for a platform's streams, given loss profiles and late joiners, versus signalling ROC through key management.
## The problem: half the index is invisible SRTP numbers every packet with a **48-bit index**, `i = 2^16 × ROC + SEQ`. `SEQ` is the 16-bit RTP sequence number, carried in the clear header. **ROC**, the rollover counter, is a 32-bit count of how many times `SEQ` has wrapped from 65,535 back to 0. The sender sets ROC to zero at the start of the session and increments it (modulo 2^32) at each wrap. **ROC is never sent.** The receiver has to work it out. It matters because the full index feeds the replay window, the counter-mode keystream and, for the default HMAC-SHA1 transform, the authentication tag (whose input is the authenticated portion of the packet concatenated with ROC). A wrong estimate is not a cosmetic error: the tag fails and the packet is discarded. ## What the receiver keeps - **ROC** — its current rollover counter, zero at session setup. A receiver joining an ongoing session MUST be given the current value out of band, through key management. - **s_l** — the highest sequence number received, initialised to the `SEQ` of the first observed packet unless key management supplies it. ## The estimate RFC 3711 says the receiver SHOULD estimate `i = 2^16 × v + SEQ`, with `v` chosen from `{ROC-1, ROC, ROC+1}` (modulo 2^32) so that `i` is closest, modulo 2^48, to `2^16 × ROC + s_l`. Its Appendix A gives the usual implementation, splitting the sequence space in half at 32,768: | Stored `s_l` | Arriving `SEQ` | Reading | `v` | |---|---|---|---| | low half (< 32,768) | more than 32,768 above `s_l` | a late packet from before the last wrap | `ROC-1` | | high half (≥ 32,768) | more than 32,768 below `s_l` | the sequence has just wrapped | `ROC+1` | | anything else | close to `s_l` | same cycle | `ROC` | ## Worked examples 1. **Across the wrap.** The receiver holds ROC = 7, `s_l` = 65,530. A packet arrives with `SEQ` = 3. Since 65,530 − 32,768 = 32,762 > 3, `v` = 8 and the index is 8 × 65,536 + 3 = **524,291**. 2. **A late packet after the wrap.** The receiver now holds ROC = 8, `s_l` = 5. A packet with `SEQ` = 65,533 arrives. Since 65,533 − 5 = 65,528 > 32,768, `v` = 7 and the index is 7 × 65,536 + 65,533 = **524,285**. The highest index is 8 × 65,536 + 5 = 524,293, so this packet is 8 behind: inside a 64-packet replay window and accepted if its bit is clear. 3. **The same cycle.** ROC = 7, `s_l` = 40,000, `SEQ` = 40,100: `v` = 7, index 7 × 65,536 + 40,100 = **498,852**. A receiver that naively used its stored ROC in example 1 would compute 7 × 65,536 + 3 = 458,755 — an index 65,527 behind its highest — and the tag, computed with the wrong ROC, would fail. ## Updating state only after authentication After the packet has been processed and authenticated, the receiver uses `v` to update: 1. `v = ROC-1`: no change to `s_l` or ROC. 2. `v = ROC`: set `s_l = SEQ` only if `SEQ` is larger than `s_l`; ROC unchanged. 3. `v = ROC+1`: set `s_l = SEQ` and `ROC = v`. Waiting for authentication is what keeps a forged packet with a well-chosen sequence number from bumping ROC and desynchronising the receiver. After a re-key, ROC keeps its sequence of values; it MUST NOT be reset to zero. ## Where it breaks RFC 3711 is explicit about the limit: synchronisation is lost only if **2^15 packets** (32,768) are lost, or a packet arrives that far out of sequence. Two practical corners: - **A random starting sequence number.** RFC 3550 says the initial `SEQ` SHOULD be random. If it starts near 65,535 and the first packets are lost across the wrap, the receiver may initialise `s_l` after the wrap and never learn ROC should be 1. RFC 4568 (SDES) avoids this by asking that the initial `SEQ` SHOULD be in 0..2^15−1. - **Without authentication**, neither the initialisation of `s_l` nor the ROC update can be made fully robust. The estimation algorithm itself is, in RFC 3711's words, a matter of implementation; RFC 3550's own Appendix A.1 handling of sequence wrap is cited as a more robust model.
- Why does SRTP choose v from three candidates instead of just incrementing ROC whenever SEQ is lower than s_l?Because a lower SEQ usually means reordering, not a wrap. Incrementing on every lower number would push ROC forward on each late packet and desynchronise the receiver. Choosing the candidate whose index lies closest to the highest one handles both a genuine wrap and a late packet from just before it, as long as reordering stays under 2^15 packets.
saying these in an interview costs you the question
- The receiver reads the rollover counter from a field in each SRTP packet.
- Any sequence number lower than the last one means the counter wrapped.
- The receiver bumps ROC as soon as it sees a wrap, before authentication.
- ROC restarts at zero whenever the master key is changed.
- The 16-bit sequence number alone is the SRTP index.