RFC 5225
RObust Header Compression Version 2 (ROHCv2): Profiles for RTP, UDP, IP, ESP and UDP-Lite
RObust Header Compression Version 2 (ROHCv2): Profiles for RTP, UDP, IP, ESP and UDP-Lite ๐
Okay bestie, here's the title block tea โ This is RFC 5225, and it's an Internet standards track doc, so it's the real deal ๐ It was written by G. Pelletier and K. Sandlund, both from Ericsson, and it's dated April 2008. There's no Obsoletes or Updates line in the title block, but the abstract says it supersedes the profile definitions in RFC 3095, RFC 3843 and RFC 4019 without obsoleting them ๐ฆ This is Part 1 of 22!
Status of This Memo ๐
This document is an Internet standards track protocol for the Internet community, and it's asking for discussion and suggestions for improvements ๐ฌ For the standardization state and status of this protocol, check the current edition of the "Internet Official Protocol Standards" (STD 1). Distribution of this memo is unlimited ๐ธ
Abstract โจ
Okay so this doc specifies ROHC (Robust Header Compression) profiles that compress RTP/UDP/IP (Real-Time Transport Protocol, User Datagram Protocol, Internet Protocol), RTP/UDP-Lite/IP (User Datagram Protocol Lite), UDP/IP, UDP-Lite/IP, IP and ESP/IP (Encapsulating Security Payload) headers, and the compression is seriously efficient ๐ฅ
This spec is the second version of the profiles from RFC 3095, RFC 3843 and RFC 4019. It supersedes their definitions, but it does not obsolete them ๐
The ROHCv2 profiles bring in a bunch of simplifications to the rules and algorithms that control how the compression endpoints behave ๐ They also define robustness mechanisms that a compressor implementation may use to boost the chances of successful decompression when packets get lost and/or reordered on the ROHC channel ๐ซถ Finally, the ROHCv2 profiles define their own specific set of header formats, written in the ROHC formal notation ๐
Table of Contents ๐ง
1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . 4
2. Terminology . . . . . . . . . . . . . . . . . . . . . . . . . 4
3. Acronyms . . . . . . . . . . . . . . . . . . . . . . . . . . 7
4. Background (Informative) . . . . . . . . . . . . . . . . . . 7
4.1. Classification of Header Fields . . . . . . . . . . . . . 7
4.2. Improvements of ROHCv2 over RFC 3095 Profiles . . . . . . 8
4.3. Operational Characteristics of ROHCv2 Profiles . . . . . 10
5. Overview of the ROHCv2 Profiles (Informative) . . . . . . . . 10
5.1. Compressor Concepts . . . . . . . . . . . . . . . . . . . 11
5.1.1. Optimistic Approach . . . . . . . . . . . . . . . . . 11
5.1.2. Tradeoff between Robustness to Losses and to
Reordering . . . . . . . . . . . . . . . . . . . . . 11
5.1.3. Interactions with the Decompressor Context . . . . . 13
5.2. Decompressor Concepts . . . . . . . . . . . . . . . . . . 14
5.2.1. Decompressor State Machine . . . . . . . . . . . . . 14
5.2.2. Decompressor Context Management . . . . . . . . . . . 17
5.2.3. Feedback Logic . . . . . . . . . . . . . . . . . . . 19
6. ROHCv2 Profiles (Normative) . . . . . . . . . . . . . . . . . 19
6.1. Channel Parameters, Segmentation, and Reordering . . . . 19
6.2. Profile Operation, Per-context . . . . . . . . . . . . . 20
6.3. Control Fields . . . . . . . . . . . . . . . . . . . . . 21
6.3.1. Master Sequence Number (MSN) . . . . . . . . . . . . 21
6.3.2. Reordering Ratio . . . . . . . . . . . . . . . . . . 21
6.3.3. IP-ID Behavior . . . . . . . . . . . . . . . . . . . 22
6.3.4. UDP-Lite Coverage Behavior . . . . . . . . . . . . . 22
6.3.5. Timestamp Stride . . . . . . . . . . . . . . . . . . 22
6.3.6. Time Stride . . . . . . . . . . . . . . . . . . . . . 22
6.3.7. CRC-3 for Control Fields . . . . . . . . . . . . . . 23
6.4. Reconstruction and Verification . . . . . . . . . . . . . 23
6.5. Compressed Header Chains . . . . . . . . . . . . . . . . 24
6.6. Header Formats and Encoding Methods . . . . . . . . . . . 25
6.6.1. baseheader_extension_headers . . . . . . . . . . . . 26
6.6.2. baseheader_outer_headers . . . . . . . . . . . . . . 26
6.6.3. inferred_udp_length . . . . . . . . . . . . . . . . . 26
6.6.4. inferred_ip_v4_header_checksum . . . . . . . . . . . 26
6.6.5. inferred_mine_header_checksum . . . . . . . . . . . . 27
6.6.6. inferred_ip_v4_length . . . . . . . . . . . . . . . . 28
6.6.7. inferred_ip_v6_length . . . . . . . . . . . . . . . . 28
6.6.8. Scaled RTP Timestamp Compression . . . . . . . . . . 29
6.6.9. timer_based_lsb . . . . . . . . . . . . . . . . . . . 30
6.6.10. inferred_scaled_field . . . . . . . . . . . . . . . . 31
6.6.11. control_crc3_encoding . . . . . . . . . . . . . . . . 32
6.6.12. inferred_sequential_ip_id . . . . . . . . . . . . . . 33
6.6.13. list_csrc(cc_value) . . . . . . . . . . . . . . . . . 34
6.7. Encoding Methods with External Parameters as Arguments . 38
6.8. Header Formats . . . . . . . . . . . . . . . . . . . . . 40
6.8.1. Initialization and Refresh Header Format (IR) . . . . 40
6.8.2. Compressed Header Formats (CO) . . . . . . . . . . . 41
6.9. Feedback Formats and Options . . . . . . . . . . . . . . 100
6.9.1. Feedback Formats . . . . . . . . . . . . . . . . . . 100
6.9.2. Feedback Options . . . . . . . . . . . . . . . . . . 102
7. Security Considerations . . . . . . . . . . . . . . . . . . . 104
8. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 105
9. Acknowledgements . . . . . . . . . . . . . . . . . . . . . . 105
10. References . . . . . . . . . . . . . . . . . . . . . . . . . 106
10.1. Normative References . . . . . . . . . . . . . . . . . . 106
10.2. Informative References . . . . . . . . . . . . . . . . . 107
Appendix A. Detailed Classification of Header Fields . . . . . 108
A.1. IPv4 Header Fields . . . . . . . . . . . . . . . . . . . 109
A.2. IPv6 Header Fields . . . . . . . . . . . . . . . . . . . 112
A.3. UDP Header Fields . . . . . . . . . . . . . . . . . . . 113
A.4. UDP-Lite Header Fields . . . . . . . . . . . . . . . . . 114
A.5. RTP Header Fields . . . . . . . . . . . . . . . . . . . . 115
A.6. ESP Header Fields . . . . . . . . . . . . . . . . . . . . 117
A.7. IPv6 Extension Header Fields . . . . . . . . . . . . . . 117
A.8. GRE Header Fields . . . . . . . . . . . . . . . . . . . . 118
A.9. MINE Header Fields . . . . . . . . . . . . . . . . . . . 119
A.10. AH Header Fields . . . . . . . . . . . . . . . . . . . . 120
Appendix B. Compressor Implementation Guidelines . . . . . . . 121
B.1. Reference Management . . . . . . . . . . . . . . . . . . 121
B.2. Window-based LSB Encoding (W-LSB) . . . . . . . . . . . 121
B.3. W-LSB Encoding and Timer-based Compression . . . . . . . 122
1. Introduction ๐ฆ
The ROHC WG has developed a header compression framework that lets you define various profiles on top of it, for different protocol sets or compression requirements ๐ The ROHC framework was first documented in [RFC3095], together with profiles for compressing RTP/UDP/IP (Real-Time Transport Protocol, User Datagram Protocol, Internet Protocol), UDP/IP, IP and ESP/IP (Encapsulating Security Payload) headers. Later on, extra profiles for IP headers [RFC3843] and UDP-Lite (User Datagram Protocol Lite) headers [RFC4019] were specified to finish off the initial set of ROHC profiles ๐
This document defines an updated version of each of the profiles above ๐ The definitions depend on the ROHC framework as found in [RFC4995]. That framework is required reading if you want to get how the profile definitions, rules, and their role all fit together ๐
Specifically, this document defines header compression schemes for these profiles, with their profile IDs:
o RTP/UDP/IP : profile 0x0101
o UDP/IP : profile 0x0102
o ESP/IP : profile 0x0103
o IP : profile 0x0104
o RTP/UDP-Lite/IP : profile 0x0107
o UDP-Lite/IP : profile 0x0108
Each of the profiles above can compress these types of extension headers ๐ง
- AH [RFC4302]
- GRE [RFC2784][RFC2890]
- MINE [RFC2004]
- IPv6 Destination Options header[RFC2460]
- IPv6 Hop-by-hop Options header[RFC2460]
- IPv6 Routing header [RFC2460]
2. Terminology ๐
okay bestie, the key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this doc are to be read the way RFC 2119 [RFC2119] describes. ๐โจ
this doc matches the terminology in the ROHC framework [RFC4995] and the formal notation for ROHC [RFC4997]. it also uses or defines these terms too ๐
Acknowledgment Number ๐ซถ
The Acknowledgment Number tells you which packet is being acknowledged in the RoHCv2 feedback element (See Section 6.9). Its value normally lines up with the Master Sequence Number (MSN) of the header that was last successfully decompressed, for the compression context (CID) the feedback is about. ๐
Chaining of Items ๐
A chain of items groups fields that have similar characteristics. ROHCv2 defines chain items for static, dynamic and irregular fields. You chain by adding an item to the chain for each header, in the order those headers show up in the uncompressed packet. Chaining is useful for building compressed headers out of any number of the protocol headers that a ROHCv2 profile has a compressed format for. it's giving modular ๐งฉ
CRC-3 Control Fields Validation ๐
This is the validation of the control fields. The decompressor does it when it gets a Compressed (CO) header that has a 3-bit Cyclic Redundancy Check (CRC) calculated over control fields. That 3-bit CRC covers the controls fields carried in the CO header, plus specific control fields in the context. In the formal header format definitions, this 3-bit CRC is labeled control_crc3 and uses the control_crc3_encoding (See also Section 6.6.11). ngl this one is a whole vibe ๐ซง
Delta ๐
The delta is the difference in the absolute value of a field between two consecutive packets that the same compression endpoint is processing. ๐
Reordering Depth ๐
This is how many packets a packet is late by within its sequence, because of reordering between the compressor and the decompressor. In other words, it's reordering between packets that belong to the same context (CID). See the definition of sequentially late packet below. ๐
ROHCv2 Header Types ๐
ROHCv2 profiles use two different header types: the Initialization and Refresh (IR) header type, and the Compressed (CO) header type. ๐ธ
Sequentially Early Packet โฉ
This is a packet that reaches the decompressor before one or several packets that got delayed over the channel. All of those packets belong to the same header-compressed flow and are associated with the same compression context (CID). When a sequentially early packet arrives, the packets delayed on the link can't be told apart from lost packets. the suspense is literally killing me ๐ญ
Sequentially Late Packet โช
A packet is late within its sequence if it reaches the decompressor after one or several other packets with the same CID have already been received, even though the sequentially late packet was sent from the compressor before those other packets. How the decompressor detects a sequentially late packet is outside the scope of this specification, but it can, for example, use the MSN for this purpose. ๐ต๏ธโโ๏ธ
Timestamp Stride (ts_stride) ๐ฐ๏ธ
The timestamp stride (ts_stride) is the expected increase in the timestamp value between two RTP packets with consecutive sequence numbers. For example, for a media encoding with a sample rate of 8 kHz producing one frame every 20 ms, the RTP timestamp will typically increase by n * 160 (= 8000 * 0.02), for some integer n. the math is mathing ๐งฎโจ
Time Stride (time_stride) โฑ๏ธ
The time stride (time_stride) is the time interval equivalent to one ts_stride. In the RTP timestamp example above, that's 20 ms. ๐ฆ
3. Acronyms ๐ง
this section lists most of the acronyms used for reference, on top of the ones defined in [RFC4995]. ๐
AH Authentication Header.
ESP Encapsulating Security Payload.
GRE Generic Routing Encapsulation.
FC Full Context state (decompressor).
IP Internet Protocol.
LSB Least Significant Bits.
MINE Minimal Encapsulation in IP.
MSB Most Significant Bits.
MSN Master Sequence Number.
NC No Context state (decompressor).
OA Optimistic Approach.
RC Repair Context state (decompressor).
ROHC Header compression framework (RFC 4995).
ROHCv2 Set of header compression profiles defined in this document.
RTP Real-time Transport Protocol.
SSRC Synchronization source. Field in RTP header.
CSRC Contributing source. The RTP header contains an optional
list of contributing sources.
TC Traffic Class. Field in the IPv6 header. See also TOS.
TOS Type Of Service. Field in the IPv4 header. See also TC.
TS RTP Timestamp.
TTL Time to Live. Field in the IPv4 header.
UDP User Datagram Protocol.
UDP-Lite User Datagram Protocol Lite.
4. Background (Informative) ๐
this section gives background info on the compression profiles defined in this doc. the basics of general header compression and of the ROHC framework are in sections 3 and 4 of [RFC4995], respectively. the basics of the formal notation for ROHC are defined in [RFC4997]. [RFC4224] describes how out-of-order delivery affects profiles based on [RFC3095]. ๐ท
4.1. Classification of Header Fields ๐ท๏ธ
Section 3.1 of [RFC4995] explains that header compression works because there's a LOT of redundancy between field values in the headers of a packet, especially between the headers of consecutive packets. ๐ซถ
Appendix A lists and classifies in detail every header field relevant to this doc. The appendix wraps up with recommendations on how the different fields should be handled by header compression algorithms. ๐
The big takeaway is that most header fields can be compressed away easily, since they never or seldom change. A small number of fields need more sophisticated mechanisms, though. ๐ง
These fields are:
IPv4 Identification (16 bits) - IP-ID
ESP Sequence Number (32 bits) - ESP SN
UDP Checksum (16 bits) - Checksum
UDP-Lite Checksum (16 bits) - Checksum
UDP-Lite Checksum Coverage (16 bits) - CCov
RTP Marker ( 1 bit ) - M-bit
RTP Sequence Number (16 bits) - RTP SN
RTP Timestamp (32 bits) - TS
For RTP specifically, the analysis in Appendix A shows that the RTP Timestamp (TS) field usually has a strong correlation with the RTP Sequence Number (SN), which goes up by one for each packet an RTP source emits. The RTP M-bit is expected to have the same value most of the time, but it needs to be communicated explicitly every so often. ๐ถ
For UDP, the Checksum field can't be inferred or recalculated at the receiving end without breaking its end-to-end properties, so it gets sent as-is when enabled (mandatory with IPv6). The same goes for the UDP-Lite Checksum (mandatory with both IPv4 and IPv6), while the UDP-Lite Checksum Coverage may in some cases be compressible. ๐
For IPv4, a correlation similar to the one between the RTP TS and the RTP SN is often seen between the Identifier field (IP-ID) and the master sequence number (MSN) used for compression (e.g., the RTP SN when compressing RTP headers). it's the same energy, different outfit ๐โจ
4.2. Improvements of ROHCv2 over RFC 3095 Profiles ๐
The ROHCv2 profiles can reach compression efficiency and robustness that are both at least equivalent to RFC 3095 profiles [RFC3095], when used under the same operating conditions. In particular, the size and bit layout of the smallest compressed header (i.e., PT-0 format U/O-0 in RFC 3095, and pt_0_crc3 in ROHCv2) are identical. ๐ฏ
There are a bunch of differences and improvements between the profiles defined here and their earlier version in RFC 3095. This section gives an overview of some of the most significant improvements: ๐
Tolerance to reordering ๐
Profiles defined in RFC 3095 require the channel between compressor and decompressor to provide in-order delivery between compression endpoints. ROHCv2 profiles, however, can handle a limited amount of reordering after the compression point robustly and efficiently, as part of the compression algorithm itself. On top of that, this improved support for reordering means ROHCv2 profiles can handle prelink reordering more efficiently. ๐โโ๏ธ๐จ
Operational logic โ๏ธ
Profiles in RFC 3095 define multiple operational modes, each with different updating logic and compressed header formats. ROHCv2 profiles operate in unidirectional operation until feedback is first received for a context (CID). At that point, bidirectional operation is used. The formats are independent of which operational logic is used. ๐
IP extension header ๐งฉ
Profiles in RFC 3095 compress IP Extension headers using list compression. ROHCv2 profiles instead treat extension headers the same way as other protocol headers, i.e., using the chaining mechanism. That means they assume extension headers aren't added or removed during the lifetime of a context (CID), otherwise compression has to be restarted for that flow. ๐
IP encapsulation ๐ช
Profiles in RFC 3095 can compress at most two levels of IP headers. ROHCv2 profiles can compress an arbitrary number of IP headers. ๐
List compression ๐
ROHCv2 profiles do not support reference-based list compression. ๐ซ
Robustness and repairs ๐ ๏ธ
ROHCv2 profiles don't define a format for the IR-DYN packet. Instead, each profile defines a compressed header that can be used to perform a more robust context repair, using a 7-bit CRC verification. This also means that only the IR header can change the association between a CID and the profile it uses. ๐
Feedback ๐ฌ
ROHCv2 profiles mandate a CRC in the format of the FEEDBACK-2, while this is optional in RFC 3095. A different set of feedback options is also used in ROHCv2 compared to RFC 3095. ๐
4.3. Operational Characteristics of ROHCv2 Profiles ๐
okay so robust header compression can run over all kinds of link technologies ๐ธ and Section 4.4 of [RFC4995] already lists the operational characteristics of the ROHC channel. ROHCv2 profiles cover a wide range of apps, so this section just highlights the operational stuff that's specific to them โจ
Packet length
ROHCv2 profiles assume the lower layer tells them the length of a compressed packet ๐. ROHCv2 compressed headers don't carry any length info for the payload, bestie ๐
Out-of-order delivery between compression endpoints
ROHCv2 profiles don't put any strict requirement on delivery sequence between the compression endpoints ๐ฆ. Like, packets can show up in a different order than the compressor sent them and still have a fair chance of being decompressed successfully ๐ซถ
ngl though, frequent out-of-order delivery and/or significant reordering depth will negatively impact compression efficiency ๐ญ. If the compressor can work with a proper estimate of the reordering characteristics of the path between the compression endpoints, it can send larger headers more often to boost robustness against decompression failures from out-of-order delivery ๐ฅ. Otherwise, compression efficiency takes a hit, because decompression failures and recovery attempts happen more often ๐
5. Overview of the ROHCv2 Profiles (Informative) ๐ง
this section is basically a crash course on concepts that are important for the ROHCv2 profiles ๐. These concepts can be used as guidelines for implementations, but they're not part of the normative definition of the profiles. They relate to compression efficiency without affecting the interoperability characteristics of an implementation ๐
5.1. Compressor Concepts ๐
Header compression can be thought of as the compressor interacting with a decompressor state machine, with one state machine per context ๐ฆ. The compressor's job is to convey the information the decompressor needs to successfully decompress a packet, based on a certain level of confidence about the state of the decompressor context ๐
That confidence comes from how often and what type of information the compressor sends when it updates the decompressor context using the optimistic approach (Section 5.1.1). It can also come, optionally, from feedback messages (See Section 6.9) that the decompressor sends back ๐ซถ
5.1.1. Optimistic Approach โจ
A compressor always uses the optimistic approach when it does context updates ๐ฅ. It normally repeats the same type of update until it's fairly confident the decompressor has received the info. If the decompressor successfully gets any one of the headers carrying this update, the state becomes available for the decompressor to process smaller compressed headers ๐
If field X in the uncompressed header changes value, the compressor uses a header type that contains an encoding of field X until it's gained confidence that the decompressor has received at least one packet with the new value for X ๐. Normally, the compressor picks the compressed format with the smallest header that can still convey the changes needed to get that confidence ๐
How many repetitions it takes to get this confidence is normally related to the packet loss and out-of-order delivery characteristics of the link where header compression is used ๐ญ. That's why it's not defined in this document. It's outside the scope of this specification and left to implementors to decide ๐
5.1.2. Tradeoff between Robustness to Losses and to Reordering ๐ธ
The ability of a header compression algorithm to handle sequentially late packets is mainly limited by two things: the interpretation interval offset of the sliding window used for lsb encoded fields [RFC4997], and the optimistic approach (See Section 5.1.1) for seldom changing fields ๐ง
lsb encoded fields:
The interpretation interval offset sets an upper limit on the maximum reordering depth, so the decompressor can recover the original value of a dynamically changing (i.e., sequentially incrementing) field that's encoded with a window-based lsb encoding ๐ฆ. Its value is typically bound to the number of lsb compressed bits in the compressed header format, so it grows with the number of bits transmitted ๐. The offset and the lsb encoding only give robustness for the field they compress, plus (implicitly) any other sequentially changing fields derived from that field ๐
Here's the figure, bestie ๐:
<--- interpretation interval (size is 2^k) ---->
|------------------+---------------------------|
v_ref-p v_ref v_ref + (2^k-1) - p
Lower Upper
Bound Bound
<--- reordering --> <--------- losses --------->
where p is the maximum negative delta, corresponding to the maximum reordering depth for which the lsb encoding can recover the original value of the field ๐
where (2^k-1) - p is the maximum positive delta, corresponding to the maximum number of consecutive losses for which the lsb encoding can recover the original value of the field ๐ฅ
where v_ref is the reference value, as defined in the lsb encoding method in [RFC4997] ๐
So there's a tradeoff between robustness against reordering and robustness against packet losses ๐ซ . It depends on the number of MSN bits needed and how the interpretation interval gets split between negative and positive deltas in the MSN โ๏ธ
Seldom changing fields:
The optimistic approach (Section 5.1.1) provides the upper limit for the maximum reordering depth for seldom changing fields ๐
So yes, there's a tradeoff between compression efficiency and robustness ๐. When only MSN info needs to be conveyed to the decompressor, the tradeoff is about the number of compressed MSN bits in the compressed header format. Otherwise, it's about how the optimistic approach gets implemented ๐ฅ
Compressor implementations should adjust their optimistic approach strategy to match both the packet loss and reordering characteristics of the link where header compression is applied ๐ฏ. For example, the number of repetitions for each update of a non-lsb encoded field can be increased ๐. The compressor can make sure each update is repeated until it's reasonably confident that at least one packet with the change has reached the decompressor before the first packet sent after that sequence ๐ซถ
5.1.3. Interactions with the Decompressor Context ๐ฆ
The compressor normally starts compression assuming the decompressor has no useful info to process the new flow, so it sends Initialization and Refresh (IR) packets ๐ฑ
When it sends the first IR packet for a compressed flow, the compressor doesn't expect to get feedback for that flow until feedback is first received ๐. At that point, the compressor may assume the decompressor will keep sending feedback to repair its context when necessary. The former is called unidirectional operation, and the latter is called bidirectional operation ๐
The compressor can then adjust the compression level (basically, which header format it selects) based on its confidence that the decompressor has the info needed to successfully process the compressed headers it picks ๐
So in other words, the compressor has to make sure the decompressor operates with state info that's sufficient to decompress the type of compressed header(s) it receives. It also has to let the decompressor recover that state info as soon as possible otherwise ๐ง. The compressor picks the type of compressed header based on these factors:
- the outcome of the encoding method applied to each field;
- the optimistic approach, with respect to the characteristics of the channel;
- the type of operation (unidirectional or bidirectional), and if in bidirectional operation, feedback received from the decompressor (ACKs, NACKs, STATIC-NACK, and options).
Encoding methods normally use previous value(s) from a history of packets whose headers the compressor has already compressed ๐. The optimistic approach is meant to make sure at least one compressed header with the info to update the state for a field gets received ๐. Finally, feedback tells the compressor what actions the decompressor has taken about its assumptions on the validity of its context (Section 5.2.2), and what type of compressed header the decompressor can or can't decompress ๐
The decompressor can detect decompression failures for any compressed (CO) header format, using the CRC verification ๐. Depending on how often and/or what type of failure it is, it might send a negative acknowledgement (NACK) or an explicit request for a complete context update (STATIC-NACK) ๐. But the decompressor can't tell what caused the failure, including which field(s) decompressed wrong ๐ต. So the compressor is always responsible for working out the most suitable response to a negative acknowledgement, using its confidence in the state of the decompressor context when it picks the compressed header type for a header ๐
5.2. Decompressor Concepts ๐
The decompressor normally uses the last received and successfully validated (IR packets) or verified (CO packets) header as the reference for future decompression ๐ฆ
The decompressor is responsible for verifying the outcome of every decompression attempt, updating its context when that works, and, once it has started using feedback, requesting context repairs by using feedback coherently ๐
Specifically, the outcome of every decompression attempt is verified using the CRC present in the compressed header ๐. The decompressor updates the context info when that outcome verifies successfully ๐ซถ. Finally, if the decompressor uses feedback once for a compressed flow, it keeps doing so for as long as the corresponding context stays associated with the same profile โจ
5.2.1. Decompressor State Machine โจ
okay so the decompressor can be modelled as a state machine with three states: No Context (NC), Repair Context (RC), and Full Context (FC) ๐
It starts out with no valid context, which is the NC state. When an IR packet shows up, it runs CRC-8 validation on the IR header. If that passes, the decompressor updates its context, uses that header as the reference header, and moves to FC ๐ฆ Once it's in FC it doesn't normally leave, because only repeated decompression failures will push it down to a lower state. When context damage is detected, it moves to RC and stays there until it either successfully verifies a decompression attempt for a compressed header with a 7-bit CRC, or successfully validates an IR header. When static context damage is detected, it goes back to NC ๐
Here's the state machine diagram. The details of the transitions and the decompression logic are in the sub-sections after the figure.
CRC-8(IR) Validation
+----->----->----->----->----->----->----->----->----->----->----+
| CRC-8(IR) |
| !CRC-8(IR) or CRC-7(CO) or or CRC-7(CO) |
| PT not allowed CRC-8(IR) or CRC-3(CO) |
| +--->---+ +--->----->----->----->---+ +--->---->---+ |
| | | | | | | |
| | v | v | v v
+-----------------+ +----------------------+ +--------------------+
| No Context (NC) | | Repair Context (RC) | | Full Context (FC) |
+-----------------+ +----------------------+ +--------------------+
^ ^ Static Context | ^ !CRC-7(CO) or | ^ Context Damage | |
| | Damage Detected | | PT not allowed | | Detected | |
| +--<-----<-----<--+ +----<------<----+ +--<-----<-----<--+ |
| |
| Static Context Damage Detected |
+--<-----<-----<-----<-----<-----<-----<-----<-----<---------+
where:
CRC-8(IR): a successful CRC-8 validation for the IR header.!CRC-8(IR): an unsuccessful CRC-8 validation for the IR header.CRC-7(CO)and/orCRC-3(CO): a successful CRC verification for the decompression of a CO header, depending on how many CRC bits that CO header carries.!CRC-7(CO): a failure to CRC verify the decompression of a CO header carrying a 7-bit CRC.PT not allowed: the decompressor has received a packet type (PT) that its current context doesn't have enough valid state information to decompress.
Static Context Damage Detected: see the definition in Section 5.2.2.
Context Damage Detected: see the definition in Section 5.2.2.
5.2.1.1. No Context (NC) State ๐ธ
Initially, while hanging out in the No Context (NC) state, the decompressor hasn't yet successfully validated an IR header.
Attempting decompression: in NC, only packets carrying enough information on the static fields (aka IR packets) can be decompressed.
Upward transition: the decompressor can move to Full Context (FC) when the CRC validation of an 8-bit CRC in an IR header succeeds.
Feedback logic: in NC, the decompressor should send a STATIC-NACK if it gets a packet that isn't IR, or if an IR header fails the CRC-8 validation. This is subject to the feedback rate limitation in Section 5.2.3.
5.2.1.2. Repair Context (RC) State ๐ง
In the Repair Context (RC) state, the decompressor has successfully decompressed packets for this context, but it isn't confident the entire context is valid ๐ซถ
Attempting decompression: in RC, only headers covered by an 8-bit CRC (aka IR) or CO headers carrying a 7-bit CRC can be decompressed.
Upward transition: the decompressor can move to FC when the CRC verification succeeds for a CO header carrying a 7-bit CRC, or when the validation of an 8-bit CRC in an IR header succeeds.
Downward transition: the decompressor moves back to NC if it assumes static context damage.
Feedback logic: in RC, the decompressor should send a STATIC-NACK when the CRC-8 validation of an IR header fails, or when a CO header carrying a 7-bit CRC fails and static context damage is assumed. This is subject to the feedback rate limitation in Section 5.2.3. If any other packet type shows up, the decompressor should treat it as a CRC verification failure to figure out whether a NACK is needed.
5.2.1.3. Full Context (FC) State ๐
In the Full Context (FC) state, the decompressor assumes its entire context is valid. Main character energy ๐
Attempting decompression: in FC, decompression can be attempted no matter what type of packet comes in.
Downward transition: the decompressor moves back to RC if it assumes context damage. If it assumes static context damage, it goes straight to NC.
Feedback logic: in FC, the decompressor should send a NACK when CRC-8 validation or CRC verification of any header type fails and context damage is assumed. Or it should send a STATIC-NACK if static context damage is assumed. This is subject to the feedback rate limitation described in Section 5.2.3.
5.2.2. Decompressor Context Management ๐
All header formats carry a CRC and are context updating. A packet whose CRC succeeds updates the reference values of all header fields, either explicitly (from the information about a field carried in the compressed header) or implicitly (fields inferred from other fields) ๐
The decompressor may assume that some or all of the context is invalid when it fails to validate or verify one or more headers using the CRC. Here's the tea: the decompressor can't know the exact reason for a CRC failure or which field caused it. So the validity of the context doesn't refer to which specific parts of the context are considered valid or not ๐
Validity of the context is really about detecting a problem with the context. The decompressor first assumes that the kind of information most likely behind the failure is the state that normally changes for each packet, which is context damage of the dynamic part of the context. If failures keep happening and repairs don't work, the decompressor then assumes the entire context, including the static part, needs repairing, which is static context damage. Failing to validate the 3-bit CRC that protects control fields should be treated as a decompression failure when the decompressor asserts that its context is valid.
Context Damage Detection
The assumption of context damage means the decompressor won't attempt decompression of a CO header that carries only a 3-bit CRC. It will only attempt decompression of IR headers or of CO headers protected by a CRC-7.
Static Context Damage Detection
The assumption of static context damage means the decompressor refrains from attempting decompression of any header type other than the IR header.
How these assumptions get made, meaning how context damage is detected, is up to each implementation. It can be based on the residual error rate, where a low error rate makes the decompressor assume damage more often than a high-rate link would ๐
The decompressor puts these assumptions into action by choosing which type of compressed header it will attempt to decompress. In other words, the validity of the context is about whether a decompressor is able to attempt (or not) decompression of specific packet types ๐ฅ
When ROHCv2 profiles are used over a channel that can't guarantee in-order delivery, the decompressor may refrain from updating its context with the content of a sequentially late packet that it successfully decompresses. The point is to avoid updating the context with information that's older than what the decompressor already has in its context ๐ญ
5.2.3. Feedback Logic ๐
ROHCv2 profiles may be used in environments with or without feedback capabilities from decompressor to compressor. ROHCv2 does assume, though, that if a ROHC feedback channel is available and the decompressor uses it at least once for a specific context, that channel will be used for the entire compression operation for that context (aka bidirectional operation) ๐ฆ
The ROHC framework defines 3 types of feedback messages: ACK, NACK, and STATIC-NACK. Their semantics are defined in Section 5.2.4.1. of [RFC4995]. What feedback gets sent is tied to the decompressor's context management, meaning the way its context damage detection algorithms work, as described in Section 5.2.2 ๐ง
The decompressor should send a NACK when it assumes context damage, and it should send a STATIC-NACK when it assumes static context damage. The decompressor isn't strictly expected to send ACK feedback after a successful decompression, except to improve compression efficiency โจ
When ROHCv2 profiles are used over a channel that can't guarantee in-order delivery, the decompressor may refrain from sending ACK feedback for a sequentially late packet that it successfully decompresses.
The decompressor should limit the rate at which it sends feedback, for both ACKs and STATIC-NACK/NACKs. It should also avoid sending unnecessary duplicates of the same type of feedback message that may be tied to the same event ๐
6. ROHCv2 Profiles (Normative) ๐
6.1. Channel Parameters, Segmentation, and Reordering ๐
Okay, this one's a big rule, so listen up ๐จ The compressor MUST NOT use ROHC segmentation (check out Section 5.2.5 of [RFC4995]). That means the Maximum Reconstructed Reception Unit (MRRU) MUST be set to 0, but only if the configuration of the ROHC channel includes at least one ROHCv2 profile in the list of supported profiles (aka the PROFILES parameter) AND the channel can't guarantee in-order delivery of packets between compression endpoints ๐ธ
6.2. Profile Operation, Per-context ๐
Okay bestie, so ROHCv2 profiles don't act the same way for every context. It depends on how the decompressor uses the feedback channel, if it uses one at all ๐ฆ Once the decompressor starts using the feedback channel for a context, that's when the feedback channel for that CID gets established โจ
The compressor always starts out assuming the decompressor won't send feedback when a new context gets initialized ๐ (see also the definition of a new context in Section 5.1.1. of [RFC4995]). So there's no established feedback channel for the new context yet. Even with the optimistic approach, decompression can still fail, because the decompressor might not have enough info to decompress the packets correctly ๐ That's why, until the decompressor sets up a feedback channel, the compressor SHOULD periodically send IR packets ๐ธ The timing can be based on timeouts, on how many compressed packets have gone out for the flow, or on any other strategy the implementer picks ๐
Getting positive feedback (ACKs) or negative feedback (NACKs or STATIC-NACKs) from the decompressor establishes the feedback channel for whichever context (CID) the feedback was about ๐ฅ Once a context has an established feedback channel, the compressor can use that feedback to estimate what state the decompressor is in. This is lowkey a big efficiency win, because it gives the compressor what it needs to reach the confidence level it wants ๐ When the feedback channel is up, periodic refreshes become superfluous, so the compressor can just rely on the optimistic approach and the decompressor's feedback instead ๐
The decompressor MAY send positive feedback (ACKs) to initially establish the feedback channel for a particular flow. Either positive feedback (ACKs) or negative feedback (NACKs or STATIC-NACKs) sets up this channel ๐ง Once the decompressor has a feedback channel for a CID, it is REQUIRED to keep sending feedback for the whole lifetime of the context. That lasts until it receives an IR packet that links the CID to a different profile. The feedback is for sending error recovery requests and, optionally, acknowledgments of significant context updates ๐ซถ
Compression without an established feedback channel is less efficient, ngl ๐ญ That's because of the periodic refreshes and because there's no feedback to kick off error recovery. There's also a slightly higher chance of loss propagation compared to when the decompressor uses feedback โญ
6.3. Control Fields ๐
ROHCv2 defines a bunch of control fields that the decompressor uses when it interprets the header formats it gets from the compressor ๐ The control fields listed in the subsections below are defined using the formal notation from [RFC4997] in Section 6.8.2.4 of this document โจ
6.3.1. Master Sequence Number (MSN) ๐ฆ
The Master Sequence Number (MSN) field either comes from a field that's already in one of the headers of the protocol being compressed (for example, RTP SN), or the compressor creates it. There's one MSN space per context ๐
The MSN field has two jobs ๐
- Telling apart reference headers when feedback data comes in.
- Inferring the value of incrementing fields (for example, IPv4 Identifier).
Every ROHCv2 header has one MSN field, so the MSN is always present in every header type the compressor sends ๐ซถ The MSN is sent in full in IR headers, and it can be lsb encoded inside CO header formats. The decompressor always includes the LSBs of the MSN in the Acknowledgment Number field of its feedback (see Section 6.9) ๐ The compressor can later use that field to figure out which packet the decompressor is acknowledging ๐
For profiles where the compressor creates the MSN (that's 0x0102, 0x0104, and 0x0108), the following applies ๐ฅ
- The compressor only initializes the MSN for a context when that context is first created or when the profile tied to a context changes.
- When the MSN gets initialized, it's set to a random value.
- The value of the MSN SHOULD be incremented by one for each packet the compressor sends for a specific CID.
6.3.2. Reordering Ratio ๐ธ
The control field reorder_ratio says how much reordering the lsb encoding of the MSN is able to handle. This is useful when header compression runs over links with varying reordering characteristics ๐ The reorder_ratio control field lets the compressor tune the robustness of the lsb encoding method against reordering and consecutive losses, as described in Section 5.1.2 โจ
6.3.3. IP-ID Behavior ๐ง
The IP-ID field of the IPv4 header can change in a few different patterns: sequential in network byte order, sequential byte-swapped, random, or constant. (A constant value of zero isn't conformant with [RFC0791], but it's been spotted in the wild ๐) Each IP header has one IP-ID behavior control field. The control field for the innermost IP header's IP-ID behavior decides which set of header formats gets used. That same IP-ID behavior control field also determines what goes in the irregular chain item, for each IP header ๐
ROHCv2 profiles MUST NOT assign a sequential behavior (network byte order or byte-swapped) to any IP-ID except the one in the innermost IP header when compressing more than one level of IP headers. That's because only the innermost IP-ID is likely to correlate closely enough with the MSN to be compressed as a sequentially changing field ๐ฆ So a compressor MUST assign either the constant zero IP-ID or the random IP-ID behavior to tunneling headers ๐ฅ
6.3.4. UDP-Lite Coverage Behavior ๐
The control field coverage_behavior says how the checksum coverage field of the UDP-Lite header gets compressed with RoHCv2 ๐ It can indicate one of these encoding methods: irregular, static, or inferred encoding โจ
6.3.5. Timestamp Stride ๐
The ts_stride control field is used in scaled RTP timestamp encoding (see Section 6.6.8). It defines the expected increase in the RTP timestamp between consecutive RTP sequence numbers ๐
6.3.6. Time Stride ๐
The time_stride control field is used in timer-based compression encoding (see Section 6.6.9). When timer-based compression is in use, time_stride should be set to the expected difference in arrival time between consecutive RTP packets ๐ง
6.3.7. CRC-3 for Control Fields ๐ซถ
ROHCv2 profiles define a CRC-3 that gets calculated over a number of control fields. This 3-bit CRC, which protects the control fields, is present in the header format for the co_common and co_repair header types ๐ฅ
The decompressor MUST always validate the integrity of the control fields covered by this 3-bit CRC when it processes a co_common or co_repair compressed header ๐
Failure to validate the control fields using this CRC should be considered a decompression failure by the decompressor, in the algorithm that checks whether the context is valid. However, if the decompression attempt can be verified using either the CRC-3 or the CRC-7 calculated over the uncompressed header, the decompressor MAY still forward the decompressed header to upper layers. That's because the protected control fields aren't always used to decompress the header (co_common or co_repair) that updates their respective value ๐
The CRC polynomial and coverage of this CRC-3 are defined in Section 6.6.11 โจ
6.4. Reconstruction and Verification ๐ฅ
Validation of the IR header (8-bit CRC) ๐ธ
The decompressor MUST always validate the integrity of the IR header using the 8-bit CRC carried within the IR header. When the header is validated, the decompressor updates the context with the information in the IR header. Otherwise, if the IR can't be validated, the context MUST NOT be updated and the IR header MUST NOT be delivered to upper layers ๐
Verification of CO headers (3-bit CRC or 7-bit CRC) ๐
The decompressor MUST always verify the decompression of a CO header using the CRC carried within the compressed header. When the decompression is verified and successful, the decompressor updates the context with the information received in the CO header. Otherwise, if the reconstructed header fails the CRC verification, these updates MUST NOT be performed ๐ฆ
A packet whose decompression attempt can't be verified using the CRC MUST NOT be delivered to upper layers ๐
Decompressor implementations may attempt corrective or repair measures on CO headers before performing the above actions, and the result of any decompression attempt MUST be verified using the CRC ๐ซถ
6.5. Compressed Header Chains โจ
okay so some header types use one or more chains that hold sub-header info ๐ง the whole point of a chain is to group fields that act alike, like static, dynamic, or irregular fields. it's giving organized closet energy ๐
chaining works by adding one item per header to the chain, in the order those headers show up in the uncompressed packet, starting with the fields in the outermost header ๐
in the text below, the term <protocol_name> is a formal notation name for each protocol header. the mapping is in this table, bestie ๐ธ
+----------------------------------+---------------+
| Protocol | protocol_name |
+----------------------------------+---------------+
| IPv4 RFC 0791 | ipv4 |
| IPv6 RFC 2460 | ipv6 |
| UDP RFC 0768 | udp |
| RTP RFC 3550 | rtp |
| ESP RFC 4303 | esp |
| UDP-Lite RFC 3828 | udp_lite |
| AH RFC 4302 | ah |
| GRE RFC 2784, RFC 2890 | gre |
| MINE RFC 2004 | mine |
| IPv6 Destination Option RFC 2460 | dest_opt |
| IPv6 Hop-by-hop Options RFC 2460 | hop_opt |
| IPv6 Routing Header RFC 2460 | rout_opt |
+----------------------------------+---------------+
Static chain:
the static chain has one item for each header in the chain of compressed protocol headers, starting from the outermost IP header. in the formal header descriptions, the static chain item for each header type is labeled <protocol_name>_static. the static chain is only used in the IR header format ๐ฆ
Dynamic chain:
the dynamic chain also has one item for each compressed header, starting from the outermost IP header. in the formal descriptions, the dynamic chain item for each header type is labeled <protocol_name>_dynamic. the dynamic chain is only used in the IR and co_repair header formats ๐
Irregular chain:
the irregular chain is set up like the static chain. for each compressed header that uses the general format of Section 6.8, the irregular chain gets tacked on at a specific spot in that general format. in the formal descriptions, each header type's irregular chain item is a format whose name ends with _irregular. the irregular chain shows up in every CO header except the co_repair format ๐ซถ
the irregular chain for the innermost IP header is different from the one for the outer IP headers, because the innermost IP header is part of the compressed base header. in the formal notation, the argument "is_innermost", which gets passed to the matching encoding method (ipv4 or ipv6), decides which irregular chain items get used. the irregular chain item for the outer IP headers is also decided by one flag for TTL/Hop Limit and TOS/TC. that flag is defined in the format of some of the compressed base headers โจ
ROHCv2 profiles compress extension headers the same way they compress other headers, so extension headers get a static chain, a dynamic chain, and an irregular chain ๐
ROHCv2 profiles define chains for every header that can be compressed, which means RTP [RFC3550], UDP [RFC0768], ESP [RFC4303], UDP-Lite [RFC3828], IPv4 [RFC0791], IPv6 [RFC2460], AH [RFC4302], GRE [RFC2784][RFC2890], MINE [RFC2004], IPv6 Destination Options header [RFC2460], IPv6 Hop-by-hop Options header [RFC2460], and IPv6 Routing header [RFC2460] ๐
6.6. Header Formats and Encoding Methods ๐ธ
the header formats are written in ROHC formal notation ๐ง some of the encoding methods they use are defined in [RFC4997], and the rest are defined right here in this section ๐
6.6.1. baseheader_extension_headers
the baseheader_extension_headers encoding method skips every field of the extension headers on the innermost IP header and doesn't encode any of them. those fields get encoded in the irregular chain instead ๐ฆ
this encoding is used in CO headers (see Section 6.8.2). the innermost IP header gets combined with other headers (UDP, UDP-Lite, RTP) to make the compressed base header. in this case there can be some extension headers sitting between the IP headers and the other headers ๐
the base header has a representation of the extension headers so the formal notation stays valid, and this encoding method is what provides that representation ๐
6.6.2. baseheader_outer_headers
the baseheader_outer_headers encoding method skips every field of the extension headers that don't belong to the innermost IP header, and doesn't encode any of them. changing fields in the outer headers get handled by the irregular chain instead ๐
just like baseheader_extension_headers above, this method keeps the header format definitions syntactically correct. it covers tunneling IP headers and their extension headers (every header before the innermost IP header) for CO headers (see Section 6.8.2) โจ
6.6.3. inferred_udp_length
the decompressor works out the UDP length field as the sum of the UDP header length and the UDP payload length ๐ง so the compressor must make sure the UDP length field matches the length field(s) of the preceding subheaders. in other words, there must not be any padding after the UDP payload that the IP Length covers ๐
this encoding method is also used for the UDP-Lite Checksum Coverage field when that field acts the same way as the UDP length field (meaning the checksum always covers the entire UDP-Lite payload) ๐
6.6.4. inferred_ip_v4_header_checksum
this encoding method compresses the header checksum field of the IPv4 header. RFC 791 [RFC0791] defines that checksum like this:
Header Checksum: 16 bits
A checksum on the header only. Since some header fields change
(e.g., time to live), this is recomputed and verified at each
point that the internet header is processed.
The checksum algorithm is:
The checksum field is the 16 bit one's complement of the one's
complement sum of all 16 bit words in the header. For purposes
of computing the checksum, the value of the checksum field is
zero.
as described above, the header checksum keeps individual hops from processing a corrupted header. most of the data this checksum protects gets compressed away, and that data is taken from state stored in the context instead. so this checksum becomes cumulative to the ROHC CRC ๐ when this encoding method is used, the decompressor recomputes the checksum ๐ฅ
the inferred_ip_v4_header_checksum encoding method squeezes the header checksum field of the IPv4 header down to zero bits. no bits get sent in compressed headers for this field. the decompressor infers the value using the computation above ๐
the compressor MAY use the header checksum to validate the header before compressing it, so it doesn't end up processing a corrupted header ๐ซถ
6.6.5. inferred_mine_header_checksum
this encoding method compresses the minimal encapsulation header checksum. RFC 2004 [RFC2004] defines that checksum like this:
Header Checksum
The 16-bit one's complement of the one's complement sum of all
16-bit words in the minimal forwarding header. For purposes of
computing the checksum, the value of the checksum field is 0.
The IP header and IP payload (after the minimal forwarding
header) are not included in this checksum computation.
the inferred_mine_header_checksum encoding method squeezes the minimal encapsulation header checksum down to zero bits. no bits get sent in compressed headers for this field. the decompressor infers the value with the computation above ๐ธ
the reasons for inferring this checksum are the same as the ones explained above in Section 6.6.4 ๐ฆ
the compressor MAY use the minimal encapsulation header checksum to validate the header before compressing it, so it doesn't process a corrupted header ๐
6.6.6. inferred_ip_v4_length
this encoding method compresses the total length field of the IPv4 header. RFC 791 [RFC0791] defines that field like this:
Total Length: 16 bits
Total Length is the length of the datagram, measured in octets,
including internet header and data. This field allows the
length of a datagram to be up to 65,535 octets.
the inferred_ip_v4_length encoding method compresses the IPv4 header checksum down to zero bits. no bits get sent in compressed headers for this field. the decompressor infers the value by counting the octets in the entire packet after decompression ๐ง
6.6.7. inferred_ip_v6_length
this encoding method compresses the payload length field in the IPv6 header. RFC 2460 [RFC2460] defines that field like this:
Payload Length: 16-bit unsigned integer
Length of the IPv6 payload, i.e., the rest of the packet
following this IPv6 header, in octets. (Note that any
extension headers present are considered part of the payload,
i.e., included in the length count.)
the "inferred_ip_v6_length" encoding method compresses the payload length field of the IPv6 header down to zero bits. no bits get sent in compressed headers for this field. the decompressor infers the value by counting the octets in the entire packet after decompression ๐
โ ๏ธ IPv6 headers that use the jumbo payload option of RFC 2675 [RFC2675] won't be compressible with this encoding method, because the payload length field value doesn't match the length of the packet ๐ญ
6.6.8. Scaled RTP Timestamp Compression โจ
okay bestie, this part gives us the extra details on how the RTP timestamp gets scaled, which is defined in the formal notation in Section 6.8.2.4 ๐
The RTP timestamp (TS) usually goes up by a multiple of however much the RTP Sequence Number (SN) goes up, so it's literally a perfect candidate for scaled encoding ๐ฆ This scaling factor is called ts_stride in the profile definition. The compressor sets it based on how much the TS changes compared to how much the RTP SN changes.
The default value of ts_stride is 160, as defined in Section 6.8.2.4 ๐ If you want a different value, the compressor has to explicitly update ts_stride for the decompressor using one of the header formats that can carry that info.
When the compressor uses a scaling factor that's different from the default ts_stride, it can only start using the new one once it's confident the decompressor has worked out the residue (ts_offset) of the scaling function for the timestamp. ngl this is the tricky part ๐ญ The compressor gets there by sending unscaled timestamp values, so the decompressor can figure out the residue based on the current ts_stride. The compressor MAY send the unscaled timestamp in the same compressed header(s) used to establish the value of ts_stride โจ
Once the compressor has enough confidence that both the scaling factor and the residue are established in the decompressor, it can start compressing packets with the new scaling factor ๐ธ
When the compressor detects that the residue (ts_offset) value has changed, it MUST NOT select a compressed header format that uses the scaled timestamp encoding before it has re-established the residue as described above. not me being strict about this, but this is a hard rule ๐
When the value of the timestamp field wraps around, the residue of the scaling function is likely to change. When that happens, the compressor re-establishes the new residue value as described above ๐
If the decompressor receives a compressed header containing scaled timestamp bits while ts_stride equals zero, it MUST NOT deliver the packet to upper layers and it SHOULD treat this as a CRC verification failure. > this is giving "do not pass go" energy ๐ซ
Whether or not the scaling is applied to the RTP TS field is up to the compressor implementation (so using scaling is OPTIONAL), and it's signalled by the tsc_indicator control field ๐ If scaling is applied, the value of ts_stride the compressor uses is up to the implementation. Setting ts_stride to the expected increase in the RTP timestamp between consecutive unit increases of the RTP SN gives the most gain for the scaled encoding ๐ Other values might give the same gain in some situations, but could reduce the gain in others.
When scaled timestamp encoding is used for header formats that don't transmit any lsb-encoded timestamp bits, the inferred_scaled_field encoding of Section 6.6.10 is used to encode the timestamp ๐ง
6.6.9. timer_based_lsb ๐ฐ๏ธ
The timer-based compression encoding method, timer_based_lsb, compresses a field whose change pattern approximates a linear function of the time of day. it's giving clock vibes โฐ
This encoding uses the local clock to get an approximation of the value it encodes. That approximated value becomes a reference value, along with the num_lsbs_param least-significant bits received as the encoded value. The num_lsbs_param is a number of bits that's enough to uniquely represent the encoded value even with jitter between the compression endpoints ๐ซถ
ts_scaled =:= timer_based_lsb(<time_stride_param>,
<num_lsbs_param>, <offset_param>)
The parameters num_lsbs_param and offset_param are the parameters for the lsb encoding, which means the number of least significant bits and the interpretation interval offset, respectively. The parameter time_stride_param represents the context value of the control field time_stride.
This encoding method always uses a scaled version of the field it compresses ๐
The decoder calculates an approximation of the scaled value of the field like this:
tsc_ref_advanced = tsc_ref + (a_n - a_ref) / time_stride.
where:
tsc_refis a reference value of the scaled representation of the field.a_nis the arrival time associated with the value to decode.a_refis the arrival time associated with the reference header.tsc_ref_advancedis an approximation of the scaled value of the field.
Then the lsb encoding is applied using the num_lsbs_param bits received in the compressed header and tsc_ref_advanced as ref_value (as per Section 4.11.5 of [RFC4997]) ๐
Appendix B.3 has an example of how the compressor can calculate jitter ๐
The control field time_stride controls whether or not the timer_based_lsb method is used in the CO header. The decompressor SHOULD send the CLOCK_RESOLUTION option with a zero value, if:
- it receives a non-zero
time_stridevalue, and - it has not previously sent a
CLOCK_RESOLUTIONfeedback with a non-zero value.
The point of this is to let compression recover if a compressor accidentally turns on timer-based compression ๐
Supporting and using timer-based compression is OPTIONAL for both the compressor and the decompressor. The compressor is not required to set the time_stride control field to a non-zero value, even when it has received a non-zero value for the CLOCK_RESOLUTION option โจ
6.6.10. inferred_scaled_field ๐ฎ
The inferred_scaled_field encoding method encodes a field that's defined as changing in relation to the MSN, and whose increase with respect to the MSN can be scaled by some scaling factor. It's used in compressed header formats that don't contain any bits for the scaled field. In that case, the decompressor infers the unscaled value of the scaled field from the MSN field. The unscaled value is calculated with this formula:
unscaled_value = delta_msn * stride + reference_unscaled_value
where delta_msn is the difference in MSN between the reference value of the MSN in the context and the value of the MSN decompressed from this packet, reference_unscaled_value is the value of the field being scaled in the context, and stride is the scaling value for this field ๐งฎ
For example, when this encoding method is applied to the RTP timestamp in the RTP profile, the calculation becomes:
timestamp = delta_msn * ts_stride + reference_timestamp
6.6.11. control_crc3_encoding ๐
The control_crc3_encoding method provides a CRC calculated over a number of control fields. The definition is the same as for the "crc" encoding method in Section 4.11.6 of [RFC4997], except the data covered by the CRC is a concatenated list of control fields ๐
So in other words, control_crc3_encoding is equivalent to this:
control_crc_encoding(ctrl_data_value, ctrl_data_length)
{
UNCOMPRESSED {
}
COMPRESSED {
control_crc3 =:=
crc(3, 0x06, 0x07, ctrl_data_value, ctrl_data_length) [ 3 ];
}
}
where the parameter ctrl_data_value binds to the concatenated values of the following control fields, in this order:
reorder_ratio, 2 bits padded with 6 MSB of zeroests_stride, 32 bits (only for profiles 0x0101 and 0x0107)time_stride, 32 bits (only for profiles 0x0101 and 0x0107)msn, 16 bits (not applicable for profiles 0x0101, 0x0103, and 0x0107)coverage_behavior, 2 bits padded with 6 MSB of zeroes (only for profiles 0x0107 and 0x0108)ip_id_behavior, one octet for each IP header in the compressible header chain, starting from the outermost header. Each octet consists of 2 bits padded with 6 MSBs of zeroes.
The ctrl_data_length binds to the sum of the length of the control field(s) that apply to the specific profile.
The decompressor uses the resulting 3-bit CRC to validate the control fields that are updated by the co_common and co_repair header formats. This CRC cannot be used to verify the outcome of a decompression attempt ๐
โโ๏ธ
This CRC protects the update of control fields, since the updated values aren't always used to decompress the header that carries them, so they aren't protected by the CRC-7 verification. This stops impairments that could happen if decompression of a co_common or co_repair succeeds and the decompressor sends positive feedback, but for some reason the control fields get updated incorrectly ๐ญ
6.6.12. inferred_sequential_ip_id ๐
This encoding method is used with a sequential IP-ID behavior (sequential or sequential byte-swapped) and when there are no coded IP-ID bits in the compressed header. In this case, the IP-ID offset from the MSN is constant, and the IP-ID increases by the same amount as the MSN (similar to the inferred_scaled_field encoding method) ๐ฆ
The decompressor calculates the IP-ID value with this formula:
IP-ID = delta_msn + reference_IP_ID_value
where delta_msn is the difference between the reference value of the MSN in the context and the uncompressed value of the MSN associated with the compressed header, and reference_IP_ID_value is the value of the IP-ID in the context. For swapped IP-ID behavior (when ip_id_behavior_innermost is set to IP_ID_BEHAVIOR_SEQUENTIAL_SWAPPED), reference_IP_ID_value and IP-ID are byte-swapped with regard to the corresponding fields in the context ๐
If the IP-ID behavior is random or zero, this encoding method doesn't update any fields ๐
6.6.13. list_csrc(cc_value) ๐
This encoding method compresses the list of RTP CSRC identifiers using list compression. This encoding establishes content for the different CSRC identifiers (the items) and a list describing the order they appear in ๐ธ
The compressor passes an argument (cc_value) to this encoding method. This is the value of the CC field taken from the RTP header. The decompressor is required to bind the value of this argument to the number of items in the list, which lets it correctly reconstruct the CC field ๐
6.6.13.1. List Compression ๐
Okay bestie, so the CSRC identifiers in an uncompressed packet can be written as an ordered list ๐. The order and presence usually stay the same from packet to packet, which is so convenient. Here's the general shape:
+--------+--------+--...--+--------+
list: | item 1 | item 2 | | item n |
+--------+--------+--...--+--------+
When we do list compression on a CSRC list, each item is the uncompressed value of one CSRC identifier. ๐
The basic principles of list-based compression go like this:
When initializing the context:
- The complete representation of the list of CSRC identifiers is transmitted.
Then, once the context has been initialized:
- When the list is unchanged, a compressed header that does not contain information about the list can be used.
- When the list changes, a compressed list is sent in the compressed header, including a representation of its structure and order. Previously unknown items are sent uncompressed in the list, while previously known items are only represented by an index pointing to the item stored in the context. โจ
6.6.13.2. Table-based Item Compression ๐ฆ
Table-based item compression is the part that squeezes the individual items sent in compressed lists. The compressor gives each item a unique identifier, called "Index", so every "Item" in a list gets its own little name tag ๐ท๏ธ.
Compressor Logic
The compressor conceptually keeps an item table with every item in it, indexed using "Index". The (Index, Item) pair gets sent together in compressed lists until the compressor is confident enough that the decompressor has seen the mapping between items and their indexes. That confidence comes from receiving an acknowledgment from the decompressor, or from sending (Index, Item) pairs using the optimistic approach. Once the compressor has that confidence, only the index gets sent in compressed lists to show that this item is present ๐ฅ.
The compressor MAY reset its item table upon receiving a negative acknowledgement. ๐
The compressor MAY reassign an existing index to a new item by re-establishing the mapping using the procedure described above. ๐
Decompressor Logic
The decompressor conceptually keeps an item table that holds all the (Index, Item) pairs it has received. That table gets updated whenever an (Index, Item) pair comes in and decompression works out (CRC verification, or CRC-8 validation). When an Index shows up without an accompanying Item, the decompressor pulls the item out of the table ๐ง.
If an index is received without an accompanying Item and the decompressor does not have any context for this index, the decompressor MUST NOT deliver the packet to upper layers. ๐
6.6.13.3. Encoding of Compressed Lists ๐
Each item present in a compressed list is represented by:
- an Index into the table of items, and a presence bit indicating if a compressed representation of the item is present in the list.
- an item (if the presence bit is set).
If the presence bit is not set, the item must already be known by the decompressor.
A compressed list of items uses the following encoding:
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| Reserved |PS | m |
+---+---+---+---+---+---+---+---+
| XI_1, ..., XI_m | m octets, or m * 4 bits
/ --- --- --- ---/
| : Padding : if PS = 0 and m is odd
+---+---+---+---+---+---+---+---+
| |
/ Item_1, ..., Item_n / variable
| |
+---+---+---+---+---+---+---+---+
Reserved: MUST be set to zero; otherwise, the decompressor MUST discard the packet. ๐
PS: Indicates size of XI fields:
- PS = 0 indicates 4-bit XI fields.
- PS = 1 indicates 8-bit XI fields.
m: Number of XI item(s) in the compressed list. This is also the value of the cc_value argument of the list_csrc encoding (see Section 6.6.13).
XI_1, ..., XI_m: m XI items. Each XI represents one item in the list of items of the uncompressed header, in the same order as they appear in the uncompressed header. ๐
The format of an XI item looks like this:
0 1 2 3
+---+---+---+---+
PS = 0: | X | Index |
+---+---+---+---+
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
PS = 1: | X | Reserved | Index |
+---+---+---+---+---+---+---+---+
X: Indicates whether the item is present in the list:
- X = 1 indicates that the item corresponding to the Index is sent in the Item_1, ..., Item_n list.
- X = 0 indicates that the item corresponding to the Index is not sent.
Reserved: MUST be set to zero; otherwise, the decompressor MUST discard the packet. ๐ซถ
Index: An index into the item table. See Section 6.6.13.4.
When 4-bit XI items are used, the XI items get packed into octets like this:
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| XI_k | XI_k + 1 |
+---+---+---+---+---+---+---+---+
Padding: A 4-bit Padding field is present when PS = 0 and the number of XIs is odd. The Padding field MUST be set to zero; otherwise, the decompressor MUST discard the packet. โจ
Item 1, ..., item n: Each item corresponds to an XI with X = 1 in XI 1, ..., XI m. Each entry in the Item list is the uncompressed representation of one CSRC identifier. ๐
6.6.13.4. Item Table Mappings ๐ธ
The item table for list compression is limited to 16 different items, since the RTP header can only carry at most 15 simultaneous CSRC identifiers. Having more than 16 items in the item table will only cause a slight overhead to the compressor when items are swapped in and out of the table ๐.
6.6.13.5. Compressed Lists in Dynamic Chain ๐
A compressed list that is part of the dynamic chain must have all of its list items present, i.e., all X-bits in the XI list MUST be set. All items previously established in the item table that are not present in the list decompressed from this packet MUST also be retained in the decompressor context. ๐
6.7. Encoding Methods with External Parameters as Arguments ๐
A number of encoding methods in Section 6.8.2.4 have one or more arguments whose parameter values are outside the scope of the ROHC-FN [RFC4997] specification of the header formats. Not gonna lie, this part is a lot, but it's giving organization ๐.
The following is a list of encoding methods with external parameters as arguments, from Section 6.8.2.4:
udp(profile_value, reorder_ratio_value)udp_lite(profile_value, reorder_ratio_value, coverage_behavior_value)esp(profile_value, reorder_ratio_value)rtp(profile_value, ts_stride_value, time_stride_value, reorder_ratio_value)ipv4(profile_value, is_innermost, outer_ip_flag, ip_id_behavior_value, reorder_ratio_value))ipv6(profile_value, is_innermost, outer_ip_flag, reorder_ratio_value))iponly_baseheader(profile_value, outer_ip_flag, ip_id_behavior_value, reorder_ratio_value)udp_baseheader(profile_value, outer_ip_flag, ip_id_behavior_value, reorder_ratio_value)udplite_baseheader(profile_value, outer_ip_flag, ip_id_behavior_value, reorder_ratio_value)esp_baseheader(profile_value, outer_ip_flag, ip_id_behavior_value, reorder_ratio_value)rtp_baseheader(profile_value, ts_stride_value, time_stride_value, outer_ip_flag, ip_id_behavior_value, reorder_ratio_value)udplite_rtp_baseheader(profile_value, ts_stride_value, time_stride_value, outer_ip_flag, ip_id_behavior_value, reorder_ratio_value, coverage_behavior_value)
The following applies for all parameters listed below: at the compressor, the value of the parameter is set according to the recommendations for each parameter. At the decompressor, the value of the parameter is set to undefined and will get bound by encoding methods, except where otherwise noted. ๐งฉ
The following is a list of external arguments with their respective definitions:
- profile_value:
Set to the 16-bit number that identifies the profile used to compress this packet. When processing the static chain at the decompressor, this parameter is set to the value of the profile field in the IR header (see Section 6.8.1).
- reorder_ratio_value:
Set to a 2-bit integer value, using one of the constants whose name begins with the prefix REORDERING_ and as defined in Section 6.8.2.4.
- ip_id_behavior_value:
Set to a 2-bit integer value, using one of the constants whose name begins with the prefix IP_ID_BEHAVIOR_ and as defined in Section 6.8.2.4.
- coverage_behavior_value:
Set to a 2-bit integer value, using one of the constants whose name begins with the prefix UDP_LITE_COVERAGE_ and as defined in Section 6.8.2.4.
- outer_ip_flag:
This parameter is set to 1 if at least one of the TOS/TC or TTL/Hop Limit fields in outer IP headers has changed compared to their reference values in the context; otherwise, it is set to 0. This flag may only be set to 1 for the "co_common" header format in the different profiles. ๐
- is_innermost:
This boolean flag is set to 1 when processing the innermost of the compressible IP headers; otherwise, it is set to 0. ๐ช
- ts_stride_value:
The value of this parameter should be set to the expected increase in the RTP Timestamp between consecutive RTP sequence numbers. The value selected is implementation-specific. See also Section 6.6.8. โฑ๏ธ
- time_stride_value:
The value of this parameter should be set to the expected inter-arrival time between consecutive packets for the flow. The value selected is implementation-specific. This parameter MUST be set to zero, unless the compressor has received a feedback message with the CLOCK_RESOLUTION option set to a non-zero value. See also Section 6.6.9. ๐ฐ๏ธ
6.8. Header Formats ๐ฆ
ROHCv2 profiles use two different header types: the Initialization and Refresh (IR) header type, and the Compressed header type (CO).
The CO header type defines a number of header formats: there are two sets of base header formats, with a few additional formats that are common to both sets. โจ
6.8.1. Initialization and Refresh Header Format (IR) ๐
okay so the IR header copies the exact structure of the ROHC IR header from Section 5.2.2.1 of [RFC4995] โจ no need to reinvent the wheel bestie
Header type: IR
This header format carries the static part and the dynamic part of the context. It's the one that sets everything up ๐
Here's the ROHCv2 IR header layout ๐
0 1 2 3 4 5 6 7
--- --- --- --- --- --- --- ---
: Add-CID octet : if for small CIDs and (CID != 0)
+---+---+---+---+---+---+---+---+
| 1 1 1 1 1 1 0 1 | IR type octet
+---+---+---+---+---+---+---+---+
: :
/ 0-2 octets of CID / 1-2 octets if for large CIDs
: :
+---+---+---+---+---+---+---+---+
| Profile | 1 octet
+---+---+---+---+---+---+---+---+
| CRC | 1 octet
+---+---+---+---+---+---+---+---+
| |
/ Static chain / variable length
| |
- - - - - - - - - - - - - - - -
| |
/ Dynamic chain / variable length
| |
- - - - - - - - - - - - - - - -
CRC: this is an 8-bit CRC over the entire IR-header, including any CID fields and up until the end of the dynamic chain ๐ฅ it uses the polynomial defined in [RFC4995]. When you're computing the CRC, the CRC field itself is zero, so it doesn't count itself lol ๐
Static chain: See Section 6.5.
Dynamic chain: See Section 6.5.
6.8.2. Compressed Header Formats (CO) ๐ฆ
6.8.2.1. Design Rationale for Compressed Base Headers
okay this part is so organized it's giving librarian energy ๐ the compressed header formats come in two separate sets for each profile. One set is for headers where the innermost IP header has a sequential IP-ID (either network byte order or byte-swapped). The other set is for headers without a sequential IP-ID (random, zero, or no IP-ID at all). There are also some common header formats that both sets share. In the descriptions below, anything in the sequential set has "seq" in its name, and anything in the non-sequential set has "rnd" in its name ๐
The design is derived from the field behavior analysis in Appendix A, so it's not random vibes, it's receipts ๐งพ
All of the compressed base headers transmit lsb-encoded MSN bits and a CRC. Consistent and iconic ๐
The following header formats exist for all profiles defined in this document, and they're common to both the sequential and the random header format sets:
co_common: This format can update the context when the established change pattern of a dynamic field changes, for any of the dynamic fields. Not every dynamic field gets updated by sending its uncompressed value though; some can only be sent in a compressed representation. It's especially useful when a rarely changing field needs an update. This format has a set of flags that show which fields are present in the header, so its size can vary to match. It's protected by a 7-bit CRC. It can update control fields, so it also carries a 3-bit CRC to protect those. It's similar in purpose to the UOR-2-extension 3 format of [RFC3095] ๐co_repair: This format can update the context of all the dynamic fields by sending their uncompressed values. It's especially useful when context damage is assumed (for example, after receiving a NACK) and a context repair is performed ๐ฉน It's protected by a 7-bit CRC, and it also carries a 3-bit CRC over the control fields it can update. It's similar in purpose to the IR-DYN format of [RFC3095] when doing context repairs.pt_0_crc3: This format only conveys the MSN, so it can only update the MSN and the fields derived from the MSN, such as IP-ID and the RTP Timestamp (for applicable profiles). It's protected by a 3-bit CRC. It's equivalent to the UO-0 header format in [RFC3095] โจpt_0_crc7: This one has the same properties aspt_0_crc3, but it's protected by a 7-bit CRC and carries a larger amount of lsb-encoded MSN bits. It's useful in environments where a lot of reordering or a high residual error rate can happen ๐ซถ
The following header format descriptions apply to profiles 0x0101 and 0x0107.
pt_1_rnd: This format can convey changes to the MSN and to the RTP Marker bit, and it can update the RTP timestamp using scaled timestamp encoding. It's protected by a 3-bit CRC. It's similar in purpose to the UO-1 format in [RFC3095] ๐ธpt_1_seq_id: This format can convey changes to the MSN and to the IP-ID. It's protected by a 3-bit CRC. It's similar in purpose to the UO-1-ID format in [RFC3095].pt_1_seq_ts: This format can convey changes to the MSN and to the RTP Marker bit, and it can update the RTP Timestamp using scaled timestamp encoding. It's protected by a 3-bit CRC. It's similar in purpose to the UO-1-TS format in [RFC3095].pt_2_rnd: This format can convey changes to the MSN, to the RTP Marker bit, and to the RTP Timestamp. It's protected by a 7-bit CRC. It's similar in purpose to the UOR-2 format in [RFC3095] ๐pt_2_seq_id: This format can convey changes to the MSN and to the IP-ID. It's protected by a 7-bit CRC. It's similar in purpose to the UO-2-ID format in [RFC3095].pt_2_seq_ts: This format can convey changes to the MSN, to the RTP Marker bit, and it can update the RTP Timestamp using scaled timestamp encoding. It's protected by a 7-bit CRC. It's similar in purpose to the UO-2-TS format in [RFC3095].pt_2_seq_both: This format can convey changes to both the RTP Timestamp and the IP-ID, plus the MSN and the Marker bit. It's protected by a 7-bit CRC. It's similar in purpose to the UOR-2-ID extension 1 format in [RFC3095] ๐
The following header format descriptions apply to profiles 0x0102, 0x0103, 0x0104, and 0x0108.
pt_1_seq_id: This format can convey changes to the MSN and to the IP-ID. It's protected by a 7-bit CRC. It's similar in purpose to the UO-1-ID format in [RFC3095] ๐งpt_2_seq_id: This format can convey changes to the MSN and to the IP-ID. It's protected by a 7-bit CRC. It's similar in purpose to the UO-2-ID format in [RFC3095].
6.8.2.2. co_repair Header Format
The ROHCv2 co_repair header has the following format ๐
0 1 2 3 4 5 6 7
--- --- --- --- --- --- --- ---
: Add-CID octet : if for small CIDs and CID 1-15
+---+---+---+---+---+---+---+---+
| 1 1 1 1 1 0 1 1 | discriminator
+---+---+---+---+---+---+---+---+
: :
/ 0, 1, or 2 octets of CID / 1-2 octets if large CIDs
: :
+---+---+---+---+---+---+---+---+
|r1 | CRC-7 |
+---+---+---+---+---+---+---+---+
| r2 | CRC-3 |
+---+---+---+---+---+---+---+---+
| |
/ Dynamic chain / variable length
| |
- - - - - - - - - - - - - - - -
r1: MUST be set to zero; otherwise, the decompressor MUST discard the packet. no exceptions, no vibes-based decoding ๐ โโ๏ธ
CRC-7: this is a 7-bit CRC over the entire uncompressed header. It's computed using the crc7 (data_value, data_length) encoding method defined in Section 6.8.2.4, where data_value corresponds to the entire uncompressed header chain and data_length corresponds to the length of this header chain ๐งฎ
r2: MUST be set to zero; otherwise, the decompressor MUST discard the packet. same energy as r1, periodt ๐
CRC-3: Encoded using the control_crc3_encoding method defined in Section 6.6.11.
Dynamic chain: See Section 6.5.
6.8.2.3. General CO Header Format
The CO header format communicates irregularities in the packet header โจ All CO formats carry a CRC and can update the context. Every CO header format uses the general format in this section, except for co_repair, which has its own layout in Section 6.8.2.2.
Here's the general format for a compressed header ๐
0 1 2 3 4 5 6 7
--- --- --- --- --- --- --- ---
: Add-CID octet : if for small CIDs and CID 1-15
+---+---+---+---+---+---+---+---+
| first octet of base header | (with type indication)
+---+---+---+---+---+---+---+---+
: :
/ 0, 1, or 2 octets of CID / 1-2 octets if large CIDs
: :
+---+---+---+---+---+---+---+---+
/ remainder of base header / variable length
+---+---+---+---+---+---+---+---+
: :
/ Irregular Chain / variable length
: :
--- --- --- --- --- --- --- ---
The base header in the figure above is the compressed version of the innermost IP header, plus any other headers in the uncompressed packet. The base header formats are defined in Section 6.8.2.4. In the formal descriptions, each profile's base header is labeled <profile_name>_baseheader, where <profile_name> comes from this table ๐
+------------------+----------------+
| Profile number | profile_name |
+------------------+----------------+
| 0x0101 | rtp |
| 0x0102 | udp |
| 0x0103 | esp |
| 0x0104 | ip |
| 0x0107 | udplite_rtp |
| 0x0108 | udplite |
+------------------+----------------+
6.8.2.4. Header Formats in ROHC-FN
This section defines the complete set of base header formats for ROHCv2 profiles. They're written in the ROHC Formal Notation [RFC4997], so it's a bit technical, but we love a precise bestie ๐ค๐ซถ
The NOTE and constants below set up the rest of the definitions ๐
// NOTE: The irregular, static, and dynamic chains (see Section 6.5)
// are defined across multiple encoding methods and are embodied
// in the correspondingly named formats within those encoding
// methods. In particular, note that the static and dynamic
// chains ordinarily go together. The uncompressed fields are
// defined across these two formats combined, rather than in one
// or the other of them. The irregular chain items are likewise
// combined with a baseheader format.
////////////////////////////////////////////
// Constants
////////////////////////////////////////////
// IP-ID behavior constants
IP_ID_BEHAVIOR_SEQUENTIAL = 0;
IP_ID_BEHAVIOR_SEQUENTIAL_SWAPPED = 1;
IP_ID_BEHAVIOR_RANDOM = 2;
IP_ID_BEHAVIOR_ZERO = 3;
// UDP-lite checksum coverage behavior constants
UDP_LITE_COVERAGE_INFERRED = 0;
UDP_LITE_COVERAGE_STATIC = 1;
UDP_LITE_COVERAGE_IRREGULAR = 2;
// The value 3 is reserved and cannot be used for coverage behavior
// Variable reordering offset
REORDERING_NONE = 0;
REORDERING_QUARTER = 1;
REORDERING_HALF = 2;
REORDERING_THREEQUARTERS = 3;
// Profile names and versions
PROFILE_RTP_0101 = 0x0101;
PROFILE_UDP_0102 = 0x0102;
PROFILE_ESP_0103 = 0x0103;
PROFILE_IP_0104 = 0x0104;
PROFILE_RTP_0107 = 0x0107; // With UDP-LITE
PROFILE_UDPLITE_0108 = 0x0108; // Without RTP
// Default values for RTP timestamp encoding TS_STRIDE_DEFAULT = 160; TIME_STRIDE_DEFAULT = 0;
//////////////////////////////////////////// // Global control fields ////////////////////////////////////////////
CONTROL {
profile [ 16 ]; msn [ 16 ]; reorder_ratio [ 2 ]; // ip_id fields are for innermost IP header only ip_id_offset [ 16 ]; ip_id_behavior_innermost [ 2 ]; // The following are only used in RTP-based profiles ts_stride [ 32 ]; time_stride [ 32 ]; ts_scaled [ 32 ]; ts_offset [ 32 ]; // UDP-lite-based profiles only coverage_behavior [ 2 ]; }
okay the CONTROL block is literally the guest list for the whole profile ๐ every field gets its own little spot, and the comments tell you which ones only show up at the RTP party or the UDP-lite party ๐
/////////////////////////////////////////////// // Encoding methods not specified in FN syntax: ///////////////////////////////////////////////
baseheader_extension_headers "defined in Section 6.6.1"; baseheader_outer_headers "defined in Section 6.6.2"; control_crc3_encoding "defined in Section 6.6.11"; inferred_ip_v4_header_checksum "defined in Section 6.6.4"; inferred_ip_v4_length "defined in Section 6.6.6"; inferred_ip_v6_length "defined in Section 6.6.7"; inferred_mine_header_checksum "defined in Section 6.6.5"; inferred_scaled_field "defined in Section 6.6.10"; inferred_sequential_ip_id "defined in Section 6.6.12"; inferred_udp_length "defined in Section 6.6.3"; list_csrc(cc_value) "defined in Section 6.6.13"; timer_based_lsb(time_stride, k, p) "defined in Section 6.6.9";
these are the "ask me later, it's in Section 6.6.x" methods ๐ they're not spelled out here, so just follow the pointer, bestie ๐ง
//////////////////////////////////////////// // General encoding methods ////////////////////////////////////////////
static_or_irreg(flag, width) { UNCOMPRESSED { field [ width ]; }
COMPRESSED irreg_enc { ENFORCE(flag == 1); field =:= irregular(width) [ width ]; }
COMPRESSED static_enc {
ENFORCE(flag == 0); field =:= static [ 0 ]; } }
optional_32(flag) { UNCOMPRESSED { item [ 0, 32 ]; }
COMPRESSED present { ENFORCE(flag == 1); item =:= irregular(32) [ 32 ]; }
COMPRESSED not_present { ENFORCE(flag == 0); item =:= compressed_value(0, 0) [ 0 ]; } }
// Send the entire value, or keep previous value sdvl_or_static(flag) { UNCOMPRESSED { field [ 32 ]; }
COMPRESSED present_7bit { ENFORCE(flag == 1); ENFORCE(field.UVALUE < 2^7); ENFORCE(field.CVALUE == field.UVALUE); discriminator =:= '0' [ 1 ]; field [ 7 ]; }
COMPRESSED present_14bit { ENFORCE(flag == 1); ENFORCE(field.UVALUE < 2^14); ENFORCE(field.CVALUE == field.UVALUE); discriminator =:= '10' [ 2 ]; field [ 14 ]; }
COMPRESSED present_21bit { ENFORCE(flag == 1); ENFORCE(field.UVALUE < 2^21); ENFORCE(field.CVALUE == field.UVALUE); discriminator =:= '110' [ 3 ]; field [ 21 ]; }
COMPRESSED present_28bit { ENFORCE(flag == 1); ENFORCE(field.UVALUE < 2^28); ENFORCE(field.CVALUE == field.UVALUE); discriminator =:= '1110' [ 4 ]; field [ 28 ]; }
COMPRESSED present_32bit { ENFORCE(flag == 1); ENFORCE(field.CVALUE == field.UVALUE); discriminator =:= '11111111' [ 8 ]; field [ 32 ]; }
COMPRESSED not_present { ENFORCE(flag == 0); field =:= static; } }
// Send the entire value, or revert to default value sdvl_or_default(flag, default_value) { UNCOMPRESSED { field [ 32 ]; }
COMPRESSED present_7bit { ENFORCE(flag == 1); ENFORCE(field.UVALUE < 2^7); ENFORCE(field.CVALUE == field.UVALUE); discriminator =:= '0' [ 1 ]; field [ 7 ]; }
COMPRESSED present_14bit { ENFORCE(flag == 1); ENFORCE(field.UVALUE < 2^14); ENFORCE(field.CVALUE == field.UVALUE); discriminator =:= '10' [ 2 ]; field [ 14 ]; } COMPRESSED present_21bit { ENFORCE(flag == 1); ENFORCE(field.UVALUE < 2^21); ENFORCE(field.CVALUE == field.UVALUE); discriminator =:= '110' [ 3 ]; field [ 21 ]; }
COMPRESSED present_28bit { ENFORCE(flag == 1); ENFORCE(field.UVALUE < 2^28); ENFORCE(field.CVALUE == field.UVALUE); discriminator =:= '1110' [ 4 ]; field [ 28 ]; }
COMPRESSED present_32bit { ENFORCE(flag == 1); ENFORCE(field.CVALUE == field.UVALUE); discriminator =:= '11111111' [ 8 ]; field [ 32 ]; }
COMPRESSED not_present { ENFORCE(flag == 0); field =:= uncompressed_value(32, default_value); } }
lsb_7_or_31 { UNCOMPRESSED { item [ 32 ]; }
COMPRESSED lsb_7 { discriminator =:= '0' [ 1 ]; item =:= lsb(7, ((2^7) / 4) - 1) [ 7 ]; }
COMPRESSED lsb_31 { discriminator =:= '1' [ 1 ]; item =:= lsb(31, ((2^31) / 4) - 1) [ 31 ]; } }
crc3(data_value, data_length) {
UNCOMPRESSED { } COMPRESSED { crc_value =:= crc(3, 0x06, 0x07, data_value, data_length) [ 3 ]; } }
crc7(data_value, data_length) { UNCOMPRESSED { }
COMPRESSED { crc_value =:= crc(7, 0x79, 0x7f, data_value, data_length) [ 7 ]; } }
// Encoding method for updating a scaled field and its associated // control fields. Should be used both when the value is scaled // or unscaled in a compressed format. // Does not have an uncompressed side. field_scaling(stride_value, scaled_value, unscaled_value, residue_value) { UNCOMPRESSED { // Nothing }
COMPRESSED no_scaling { ENFORCE(stride_value == 0); ENFORCE(residue_value == unscaled_value); ENFORCE(scaled_value == 0); }
COMPRESSED scaling_used { ENFORCE(stride_value != 0); ENFORCE(residue_value == (unscaled_value % stride_value)); ENFORCE(unscaled_value == scaled_value * stride_value + residue_value); } }
the general encoding methods are the reusable building blocks ๐งฑ like, the same few patterns keep popping up everywhere, which is so iconic of them ๐
//////////////////////////////////////////// // IPv6 Destination options header ////////////////////////////////////////////
ip_dest_opt { UNCOMPRESSED {
next_header [ 8 ]; length [ 8 ]; value [ length.UVALUE * 64 + 48 ]; }
DEFAULT { length =:= static; next_header =:= static; value =:= static; }
COMPRESSED dest_opt_static { next_header =:= irregular(8) [ 8 ]; length =:= irregular(8) [ 8 ]; }
COMPRESSED dest_opt_dynamic { value =:= irregular(length.UVALUE * 64 + 48) [ length.UVALUE * 64 + 48 ]; }
COMPRESSED dest_opt_irregular { }
}
//////////////////////////////////////////// // IPv6 Hop-by-Hop options header ////////////////////////////////////////////
ip_hop_opt { UNCOMPRESSED { next_header [ 8 ]; length [ 8 ]; value [ length.UVALUE * 64 + 48 ]; }
DEFAULT { length =:= static; next_header =:= static; value =:= static; }
COMPRESSED hop_opt_static { next_header =:= irregular(8) [ 8 ]; length =:= irregular(8) [ 8 ]; } COMPRESSED hop_opt_dynamic { value =:= irregular(length.UVALUE*64+48) [ length.UVALUE * 64 + 48 ]; }
COMPRESSED hop_opt_irregular { }
}
//////////////////////////////////////////// // IPv6 Routing header ////////////////////////////////////////////
ip_rout_opt { UNCOMPRESSED { next_header [ 8 ]; length [ 8 ]; value [ length.UVALUE * 64 + 48 ]; }
DEFAULT { length =:= static; next_header =:= static; value =:= static; }
COMPRESSED rout_opt_static { next_header =:= irregular(8) [ 8 ]; length =:= irregular(8) [ 8 ]; value =:= irregular(length.UVALUE*64+48) [ length.UVALUE * 64 + 48 ]; }
COMPRESSED rout_opt_dynamic { }
COMPRESSED rout_opt_irregular { } }
three IPv6 header blocks in a row, all cute and matching ๐ฆ dest, hop-by-hop, and routing, each with its own static/dynamic/irregular vibe ๐
//////////////////////////////////////////// // GRE Header ////////////////////////////////////////////
optional_lsb_7_or_31(flag) {
UNCOMPRESSED { item [ 0, 32 ]; }
COMPRESSED present { ENFORCE(flag == 1); item =:= lsb_7_or_31 [ 8, 32 ]; }
COMPRESSED not_present { ENFORCE(flag == 0); item =:= compressed_value(0, 0) [ 0 ]; } }
optional_checksum(flag_value) { UNCOMPRESSED { value [ 0, 16 ]; reserved1 [ 0, 16 ]; }
COMPRESSED cs_present { ENFORCE(flag_value == 1); value =:= irregular(16) [ 16 ]; reserved1 =:= uncompressed_value(16, 0) [ 0 ]; }
COMPRESSED not_present { ENFORCE(flag_value == 0); value =:= compressed_value(0, 0) [ 0 ]; reserved1 =:= compressed_value(0, 0) [ 0 ]; } }
gre_proto { UNCOMPRESSED { protocol [ 16 ]; }
COMPRESSED ether_v4 { discriminator =:= '0' [ 1 ]; protocol =:= uncompressed_value(16, 0x0800) [ 0 ]; }
COMPRESSED ether_v6 { discriminator =:= '1' [ 1 ]; protocol =:= uncompressed_value(16, 0x86DD) [ 0 ]; } }
gre { UNCOMPRESSED { c_flag [ 1 ]; r_flag =:= uncompressed_value(1, 0) [ 1 ]; k_flag [ 1 ]; s_flag [ 1 ]; reserved0 =:= uncompressed_value(9, 0) [ 9 ]; version =:= uncompressed_value(3, 0) [ 3 ]; protocol [ 16 ]; checksum_and_res [ 0, 32 ]; key [ 0, 32 ]; sequence_number [ 0, 32 ]; }
DEFAULT { c_flag =:= static; k_flag =:= static; s_flag =:= static; protocol =:= static; key =:= static; sequence_number =:= static; }
COMPRESSED gre_static { ENFORCE((c_flag.UVALUE == 1 && checksum_and_res.ULENGTH == 32)
| checksum_and_res.ULENGTH == 0); |
ENFORCE((s_flag.UVALUE == 1 && sequence_number.ULENGTH == 32)
| sequence_number.ULENGTH == 0); |
protocol =:= gre_proto [ 1 ]; c_flag =:= irregular(1) [ 1 ]; k_flag =:= irregular(1) [ 1 ]; s_flag =:= irregular(1) [ 1 ]; padding =:= compressed_value(4, 0) [ 4 ]; key =:= optional_32(k_flag.UVALUE) [ 0, 32 ]; }
COMPRESSED gre_dynamic { checksum_and_res =:= optional_checksum(c_flag.UVALUE) [ 0, 16 ]; sequence_number =:= optional_32(s_flag.UVALUE) [ 0, 32 ]; }
COMPRESSED gre_irregular {
checksum_and_res =:= optional_checksum(c_flag.UVALUE) [ 0, 16 ]; sequence_number =:= optional_lsb_7_or_31(s_flag.UVALUE) [ 0, 8, 32 ]; }
}
GRE is the flag-heavy one, bestie, so read the ENFORCE lines slowly ๐ฅ every bit has a job and the tiny reserved fields are literally there to keep the line tidy ๐
///////////////////////////////////////////// // MINE header /////////////////////////////////////////////
mine { UNCOMPRESSED { next_header [ 8 ]; s_bit [ 1 ]; res_bits [ 7 ]; checksum [ 16 ]; orig_dest [ 32 ]; orig_src [ 0, 32 ]; }
DEFAULT { next_header =:= static; s_bit =:= static; res_bits =:= static; checksum =:= inferred_mine_header_checksum; orig_dest =:= static; orig_src =:= static; }
COMPRESSED mine_static { next_header =:= irregular(8) [ 8 ]; s_bit =:= irregular(1) [ 1 ]; // Reserved bits are included to achieve byte-alignment res_bits =:= irregular(7) [ 7 ]; orig_dest =:= irregular(32) [ 32 ]; orig_src =:= optional_32(s_bit.UVALUE) [ 0, 32 ]; }
COMPRESSED mine_dynamic { }
COMPRESSED mine_irregular { } }
/////////////////////////////////////////////
okay bestie, this part is all about the header grammars ๐ first up is Authentication Header (AH), and it's giving very structured, ngl ๐
// Authentication Header (AH)
/////////////////////////////////////////////
ah
{
UNCOMPRESSED {
next_header [ 8 ];
length [ 8 ];
res_bits =:= uncompressed_value(16, 0) [ 16 ];
spi [ 32 ];
sequence_number [ 32 ];
icv [ length.UVALUE*32-32 ];
}
DEFAULT {
next_header =:= static;
length =:= static;
spi =:= static;
sequence_number =:= static;
}
COMPRESSED ah_static {
next_header =:= irregular(8) [ 8 ];
length =:= irregular(8) [ 8 ];
spi =:= irregular(32) [ 32 ];
}
COMPRESSED ah_dynamic {
sequence_number =:= irregular(32) [ 32 ];
icv =:=
irregular(length.UVALUE*32-32) [ length.UVALUE*32-32 ];
}
COMPRESSED ah_irregular {
sequence_number =:= lsb_7_or_31 [ 8, 32 ];
icv =:=
irregular(length.UVALUE*32-32) [ length.UVALUE*32-32 ];
}
}
the way the AH block splits fields into an uncompressed form and a few compressed variants (ah_static, ah_dynamic, ah_irregular) is lowkey iconic ๐ฆ next up is the IPv6 header, starting with the flow label encoding ๐ง
/////////////////////////////////////////////
// IPv6 Header
/////////////////////////////////////////////
fl_enc
{
UNCOMPRESSED {
flow_label [ 20 ];
}
COMPRESSED fl_zero {
discriminator =:= '0' [ 1 ];
flow_label =:= uncompressed_value(20, 0) [ 0 ];
reserved =:= '0000' [ 4 ];
}
COMPRESSED fl_non_zero {
discriminator =:= '1' [ 1 ];
flow_label =:= irregular(20) [ 20 ];
}
}
ipv6(profile_value, is_innermost, outer_ip_flag, reorder_ratio_value)
{
UNCOMPRESSED {
version =:= uncompressed_value(4, 6) [ 4 ];
tos_tc [ 8 ];
flow_label [ 20 ];
payload_length [ 16 ];
next_header [ 8 ];
ttl_hopl [ 8 ];
src_addr [ 128 ];
dst_addr [ 128 ];
}
CONTROL {
ENFORCE(profile == profile_value);
ENFORCE(reorder_ratio.UVALUE == reorder_ratio_value);
ENFORCE(innermost_ip.UVALUE == is_innermost);
innermost_ip [ 1 ];
}
DEFAULT {
tos_tc =:= static;
flow_label =:= static;
payload_length =:= inferred_ip_v6_length;
next_header =:= static;
ttl_hopl =:= static;
src_addr =:= static;
dst_addr =:= static;
}
COMPRESSED ipv6_static {
version_flag =:= '1' [ 1 ];
innermost_ip =:= irregular(1) [ 1 ];
reserved =:= '0' [ 1 ];
flow_label =:= fl_enc [ 5, 21 ];
next_header =:= irregular(8) [ 8 ];
src_addr =:= irregular(128) [ 128 ];
dst_addr =:= irregular(128) [ 128 ];
}
COMPRESSED ipv6_endpoint_dynamic {
ENFORCE((is_innermost == 1) &&
(profile_value == PROFILE_IP_0104));
tos_tc =:= irregular(8) [ 8 ];
ttl_hopl =:= irregular(8) [ 8 ];
reserved =:= compressed_value(6, 0) [ 6 ];
reorder_ratio =:= irregular(2) [ 2 ];
msn =:= irregular(16) [ 16 ];
}
COMPRESSED ipv6_regular_dynamic {
ENFORCE((is_innermost == 0) ||
(profile_value != PROFILE_IP_0104));
tos_tc =:= irregular(8) [ 8 ];
ttl_hopl =:= irregular(8) [ 8 ];
}
COMPRESSED ipv6_outer_irregular {
ENFORCE(is_innermost == 0);
tos_tc =:=
static_or_irreg(outer_ip_flag, 8) [ 0, 8 ];
ttl_hopl =:=
static_or_irreg(outer_ip_flag, 8) [ 0, 8 ];
}
COMPRESSED ipv6_innermost_irregular {
ENFORCE(is_innermost == 1);
}
}
the flow label gets its own little sub-grammar, fl_zero vs fl_non_zero, and the ipv6 block is basically a full choreography of which fields are static, which are irregular, and which get enforced ๐ธ now for the IPv4 side, where the IP-ID handling gets extra dramatic ๐ฅ
/////////////////////////////////////////////
// IPv4 Header
/////////////////////////////////////////////
ip_id_enc_dyn(behavior)
{
UNCOMPRESSED {
ip_id [ 16 ];
}
COMPRESSED ip_id_seq {
ENFORCE((behavior == IP_ID_BEHAVIOR_SEQUENTIAL) ||
(behavior == IP_ID_BEHAVIOR_SEQUENTIAL_SWAPPED));
ENFORCE(ip_id_offset.UVALUE == ip_id.UVALUE - msn.UVALUE);
ip_id =:= irregular(16) [ 16 ];
}
COMPRESSED ip_id_random {
ENFORCE(behavior == IP_ID_BEHAVIOR_RANDOM);
ip_id =:= irregular(16) [ 16 ];
}
COMPRESSED ip_id_zero {
ENFORCE(behavior == IP_ID_BEHAVIOR_ZERO);
ip_id =:= uncompressed_value(16, 0) [ 0 ];
}
}
ip_id_enc_irreg(behavior)
{
UNCOMPRESSED {
ip_id [ 16 ];
}
COMPRESSED ip_id_seq {
ENFORCE(behavior == IP_ID_BEHAVIOR_SEQUENTIAL);
}
COMPRESSED ip_id_seq_swapped {
ENFORCE(behavior == IP_ID_BEHAVIOR_SEQUENTIAL_SWAPPED);
}
COMPRESSED ip_id_rand {
ENFORCE(behavior == IP_ID_BEHAVIOR_RANDOM);
ip_id =:= irregular(16) [ 16 ];
}
COMPRESSED ip_id_zero {
ENFORCE(behavior == IP_ID_BEHAVIOR_ZERO);
ip_id =:= uncompressed_value(16, 0) [ 0 ];
}
}
ipv4(profile_value, is_innermost, outer_ip_flag, ip_id_behavior_value,
reorder_ratio_value)
{
UNCOMPRESSED {
version =:= uncompressed_value(4, 4) [ 4 ];
hdr_length =:= uncompressed_value(4, 5) [ 4 ];
tos_tc [ 8 ];
length =:= inferred_ip_v4_length [ 16 ];
ip_id [ 16 ];
rf =:= uncompressed_value(1, 0) [ 1 ];
df [ 1 ];
mf =:= uncompressed_value(1, 0) [ 1 ];
frag_offset =:= uncompressed_value(13, 0) [ 13 ];
ttl_hopl [ 8 ];
protocol [ 8 ];
checksum =:= inferred_ip_v4_header_checksum [ 16 ];
src_addr [ 32 ];
dst_addr [ 32 ];
}
CONTROL {
ENFORCE(profile == profile_value);
ENFORCE(reorder_ratio.UVALUE == reorder_ratio_value);
ENFORCE(innermost_ip.UVALUE == is_innermost);
ip_id_behavior_outer [ 2 ];
innermost_ip [ 1 ];
}
DEFAULT {
tos_tc =:= static;
df =:= static;
ttl_hopl =:= static;
protocol =:= static;
src_addr =:= static;
dst_addr =:= static;
ip_id_behavior_outer =:= static;
}
COMPRESSED ipv4_static {
version_flag =:= '0' [ 1 ];
innermost_ip =:= irregular(1) [ 1 ];
reserved =:= '000000' [ 6 ];
protocol =:= irregular(8) [ 8 ];
src_addr =:= irregular(32) [ 32 ];
dst_addr =:= irregular(32) [ 32 ];
}
COMPRESSED ipv4_endpoint_innermost_dynamic {
ENFORCE((is_innermost == 1) && (profile_value == PROFILE_IP_0104));
ENFORCE(ip_id_behavior_innermost.UVALUE == ip_id_behavior_value);
reserved =:= '000' [ 3 ];
reorder_ratio =:= irregular(2) [ 2 ];
df =:= irregular(1) [ 1 ];
ip_id_behavior_innermost =:= irregular(2) [ 2 ];
tos_tc =:= irregular(8) [ 8 ];
ttl_hopl =:= irregular(8) [ 8 ];
ip_id =:= ip_id_enc_dyn(ip_id_behavior_innermost.UVALUE) [ 0, 16 ];
msn =:= irregular(16) [ 16 ];
}
COMPRESSED ipv4_regular_innermost_dynamic {
ENFORCE((is_innermost == 1) && (profile_value != PROFILE_IP_0104));
ENFORCE(ip_id_behavior_innermost.UVALUE == ip_id_behavior_value);
reserved =:= '00000' [ 5 ];
df =:= irregular(1) [ 1 ];
ip_id_behavior_innermost =:= irregular(2) [ 2 ];
tos_tc =:= irregular(8) [ 8 ];
ttl_hopl =:= irregular(8) [ 8 ];
ip_id =:= ip_id_enc_dyn(ip_id_behavior_innermost.UVALUE) [ 0, 16 ];
}
COMPRESSED ipv4_outer_dynamic {
ENFORCE(is_innermost == 0);
ENFORCE(ip_id_behavior_outer.UVALUE == ip_id_behavior_value);
reserved =:= '00000' [ 5 ];
df =:= irregular(1) [ 1 ];
ip_id_behavior_outer =:= irregular(2) [ 2 ];
tos_tc =:= irregular(8) [ 8 ];
ttl_hopl =:= irregular(8) [ 8 ];
ip_id =:= ip_id_enc_dyn(ip_id_behavior_outer.UVALUE) [ 0, 16 ];
}
COMPRESSED ipv4_outer_irregular {
ENFORCE(is_innermost == 0);
ip_id =:=
ip_id_enc_irreg(ip_id_behavior_outer.UVALUE) [ 0, 16 ];
tos_tc =:= static_or_irreg(outer_ip_flag, 8) [ 0, 8 ];
ttl_hopl =:= static_or_irreg(outer_ip_flag, 8) [ 0, 8 ];
}
COMPRESSED ipv4_innermost_irregular {
ENFORCE(is_innermost == 1);
ip_id =:=
ip_id_enc_irreg(ip_id_behavior_innermost.UVALUE) [ 0, 16 ];
}
}
the IP-ID behaviours get split into dynamic and irregular encoders, and the ipv4 block is a whole multi-variant lineup ๐ next we hit UDP, which has its own checksum drama ๐
/////////////////////////////////////////////
// UDP Header
/////////////////////////////////////////////
udp(profile_value, reorder_ratio_value)
{
UNCOMPRESSED {
ENFORCE((profile_value == PROFILE_RTP_0101) ||
(profile_value == PROFILE_UDP_0102));
src_port [ 16 ];
dst_port [ 16 ];
udp_length =:= inferred_udp_length [ 16 ];
checksum [ 16 ];
}
CONTROL {
ENFORCE(profile == profile_value);
ENFORCE(reorder_ratio.UVALUE == reorder_ratio_value);
checksum_used [ 1 ];
}
DEFAULT {
src_port =:= static;
dst_port =:= static;
checksum_used =:= static;
}
COMPRESSED udp_static {
src_port =:= irregular(16) [ 16 ];
dst_port =:= irregular(16) [ 16 ];
}
COMPRESSED udp_endpoint_dynamic {
ENFORCE(profile_value == PROFILE_UDP_0102);
ENFORCE(profile == PROFILE_UDP_0102);
ENFORCE(checksum_used.UVALUE == (checksum.UVALUE != 0));
checksum =:= irregular(16) [ 16 ];
msn =:= irregular(16) [ 16 ];
reserved =:= compressed_value(6, 0) [ 6 ];
reorder_ratio =:= irregular(2) [ 2 ];
}
COMPRESSED udp_regular_dynamic {
ENFORCE(profile_value == PROFILE_RTP_0101);
ENFORCE(checksum_used.UVALUE == (checksum.UVALUE != 0));
checksum =:= irregular(16) [ 16 ];
}
COMPRESSED udp_zero_checksum_irregular {
ENFORCE(checksum_used.UVALUE == 0);
checksum =:= uncompressed_value(16, 0) [ 0 ];
}
COMPRESSED udp_with_checksum_irregular {
ENFORCE(checksum_used.UVALUE == 1);
checksum =:= irregular(16) [ 16 ];
}
}
the udp variants are all about whether the checksum is used or not, which is honestly so relatable ๐ last bit for this part is the RTP header, starting with the CSRC list stuff ๐ฆ
/////////////////////////////////////////////
// RTP Header
/////////////////////////////////////////////
csrc_list_dynchain(presence, cc_value)
{
UNCOMPRESSED {
csrc_list;
}
COMPRESSED no_list {
ENFORCE(cc_value == 0);
ENFORCE(presence == 0);
csrc_list =:= uncompressed_value(0, 0) [ 0 ];
}
COMPRESSED list_present {
ENFORCE(presence == 1);
csrc_list =:= list_csrc(cc_value) [ VARIABLE ];
}
}
rtp(profile_value, ts_stride_value, time_stride_value,
reorder_ratio_value)
part 11 done, bestie ๐ the rtp grammar picks up right where we left off, and I'm obsessed already ๐
okay so the rtp block is wrapping up and the compressed states are lowkey everything ๐ โจ
{
UNCOMPRESSED {
ENFORCE((profile_value == PROFILE_RTP_0101) ||
(profile_value == PROFILE_RTP_0107));
rtp_version =:= uncompressed_value(2, 0) [ 2 ];
pad_bit [ 1 ];
extension [ 1 ];
cc [ 4 ];
marker [ 1 ];
payload_type [ 7 ];
sequence_number [ 16 ];
timestamp [ 32 ];
ssrc [ 32 ];
csrc_list [ cc.UVALUE * 32 ];
}
CONTROL {
ENFORCE(profile == profile_value);
ENFORCE(reorder_ratio.UVALUE == reorder_ratio_value);
ENFORCE(time_stride_value == time_stride.UVALUE);
ENFORCE(ts_stride_value == ts_stride.UVALUE);
dummy_field =:= field_scaling(ts_stride.UVALUE,
ts_scaled.UVALUE, timestamp.UVALUE, ts_offset.UVALUE) [ 0 ];
}
INITIAL {
ts_stride =:= uncompressed_value(32, TS_STRIDE_DEFAULT);
time_stride =:= uncompressed_value(32, TIME_STRIDE_DEFAULT);
}
DEFAULT {
ENFORCE(msn.UVALUE == sequence_number.UVALUE);
pad_bit =:= static;
extension =:= static;
cc =:= static;
marker =:= static;
payload_type =:= static;
sequence_number =:= static;
timestamp =:= static;
ssrc =:= static;
csrc_list =:= static;
ts_stride =:= static;
time_stride =:= static;
ts_scaled =:= static;
ts_offset =:= static;
}
COMPRESSED rtp_static {
ssrc =:= irregular(32) [ 32 ];
}
COMPRESSED rtp_dynamic {
reserved =:= compressed_value(1, 0) [ 1 ];
reorder_ratio =:= irregular(2) [ 2 ];
list_present =:= irregular(1) [ 1 ];
tss_indicator =:= irregular(1) [ 1 ];
tis_indicator =:= irregular(1) [ 1 ];
pad_bit =:= irregular(1) [ 1 ];
extension =:= irregular(1) [ 1 ];
marker =:= irregular(1) [ 1 ];
payload_type =:= irregular(7) [ 7 ];
sequence_number =:= irregular(16) [ 16 ];
timestamp =:= irregular(32) [ 32 ];
ts_stride =:= sdvl_or_default(tss_indicator.CVALUE,
TS_STRIDE_DEFAULT) [ VARIABLE ];
time_stride =:= sdvl_or_default(tis_indicator.CVALUE,
TIME_STRIDE_DEFAULT) [ VARIABLE ];
csrc_list =:= csrc_list_dynchain(list_present.CVALUE,
cc.UVALUE) [ VARIABLE ];
}
COMPRESSED rtp_irregular {
}
}
/////////////////////////////////////////////
// UDP-Lite Header
/////////////////////////////////////////////
checksum_coverage_dynchain(behavior)
{
UNCOMPRESSED {
checksum_coverage [ 16 ];
}
COMPRESSED inferred_coverage {
ENFORCE(behavior == UDP_LITE_COVERAGE_INFERRED);
checksum_coverage =:= inferred_udp_length [ 0 ];
}
COMPRESSED static_coverage {
ENFORCE(behavior == UDP_LITE_COVERAGE_STATIC);
checksum_coverage =:= irregular(16) [ 16 ];
}
COMPRESSED irregular_coverage {
ENFORCE(behavior == UDP_LITE_COVERAGE_IRREGULAR);
checksum_coverage =:= irregular(16) [ 16 ];
}
}
checksum_coverage_irregular(behavior)
{
UNCOMPRESSED {
checksum_coverage [ 16 ];
}
COMPRESSED inferred_coverage {
ENFORCE(behavior == UDP_LITE_COVERAGE_INFERRED);
checksum_coverage =:= inferred_udp_length [ 0 ];
}
COMPRESSED static_coverage {
ENFORCE(behavior == UDP_LITE_COVERAGE_STATIC);
checksum_coverage =:= static [ 0 ];
}
COMPRESSED irregular_coverage {
ENFORCE(behavior == UDP_LITE_COVERAGE_IRREGULAR);
checksum_coverage =:= irregular(16) [ 16 ];
}
}
udp_lite(profile_value, reorder_ratio_value, coverage_behavior_value)
{
UNCOMPRESSED {
ENFORCE((profile_value == PROFILE_RTP_0107) ||
(profile_value == PROFILE_UDPLITE_0108));
src_port [ 16 ];
dst_port [ 16 ];
checksum_coverage [ 16 ];
checksum [ 16 ];
}
CONTROL {
ENFORCE(profile == profile_value);
ENFORCE(coverage_behavior.UVALUE == coverage_behavior_value);
ENFORCE(reorder_ratio.UVALUE == reorder_ratio_value);
}
DEFAULT {
src_port =:= static;
dst_port =:= static;
coverage_behavior =:= static;
}
COMPRESSED udp_lite_static {
src_port =:= irregular(16) [ 16 ];
dst_port =:= irregular(16) [ 16 ];
}
COMPRESSED udp_lite_endpoint_dynamic {
ENFORCE(profile_value == PROFILE_UDPLITE_0108);
reserved =:= compressed_value(4, 0) [ 4 ];
coverage_behavior =:= irregular(2) [ 2 ];
reorder_ratio =:= irregular(2) [ 2 ];
checksum_coverage =:=
checksum_coverage_dynchain(coverage_behavior.UVALUE) [ 16 ];
checksum =:= irregular(16) [ 16 ];
msn =:= irregular(16) [ 16 ];
}
COMPRESSED udp_lite_regular_dynamic {
ENFORCE(profile_value == PROFILE_RTP_0107);
coverage_behavior =:= irregular(2) [ 2 ];
reserved =:= compressed_value(6, 0) [ 6 ];
checksum_coverage =:=
checksum_coverage_dynchain(coverage_behavior.UVALUE) [ 16 ];
checksum =:= irregular(16) [ 16 ];
}
COMPRESSED udp_lite_irregular {
checksum_coverage =:=
checksum_coverage_irregular(coverage_behavior.UVALUE) [ 0, 16 ];
checksum =:= irregular(16) [ 16 ];
}
}
/////////////////////////////////////////////
// ESP Header
/////////////////////////////////////////////
esp(profile_value, reorder_ratio_value)
{
UNCOMPRESSED {
ENFORCE(profile_value == PROFILE_ESP_0103);
ENFORCE(msn.UVALUE == sequence_number.UVALUE % 65536);
spi [ 32 ];
sequence_number [ 32 ];
}
CONTROL {
ENFORCE(profile == profile_value);
ENFORCE(reorder_ratio.UVALUE == reorder_ratio_value);
}
DEFAULT {
spi =:= static;
sequence_number =:= static;
}
COMPRESSED esp_static {
spi =:= irregular(32) [ 32 ];
}
COMPRESSED esp_dynamic {
sequence_number =:= irregular(32) [ 32 ];
reserved =:= compressed_value(6, 0) [ 6 ];
reorder_ratio =:= irregular(2) [ 2 ];
}
COMPRESSED esp_irregular {
}
}
///////////////////////////////////////////////////
// Encoding methods used in the profiles' CO headers
///////////////////////////////////////////////////
// Variable reordering offset used for MSN
msn_lsb(k)
{
UNCOMPRESSED {
master [ VARIABLE ];
}
COMPRESSED none {
ENFORCE(reorder_ratio.UVALUE == REORDERING_NONE);
master =:= lsb(k, 1);
}
COMPRESSED quarter {
ENFORCE(reorder_ratio.UVALUE == REORDERING_QUARTER);
master =:= lsb(k, ((2^k) / 4) - 1);
}
COMPRESSED half {
ENFORCE(reorder_ratio.UVALUE == REORDERING_HALF);
master =:= lsb(k, ((2^k) / 2) - 1);
}
COMPRESSED threequarters {
ENFORCE(reorder_ratio.UVALUE == REORDERING_THREEQUARTERS);
master =:= lsb(k, (((2^k) * 3) / 4) - 1);
}
}
ip_id_lsb(behavior, k)
{
UNCOMPRESSED {
ip_id [ 16 ];
}
CONTROL {
ip_id_nbo [ 16 ];
}
COMPRESSED nbo {
ENFORCE(behavior == IP_ID_BEHAVIOR_SEQUENTIAL);
ENFORCE(ip_id_offset.UVALUE == ip_id.UVALUE - msn.UVALUE);
ip_id_offset =:= lsb(k, ((2^k) / 4) - 1) [ k ];
}
COMPRESSED non_nbo {
ENFORCE(behavior == IP_ID_BEHAVIOR_SEQUENTIAL_SWAPPED);
ENFORCE(ip_id_nbo.UVALUE ==
(ip_id.UVALUE / 256) + (ip_id.UVALUE % 256) * 256);
ENFORCE(ip_id_nbo.ULENGTH == 16);
ENFORCE(ip_id_offset.UVALUE == ip_id_nbo.UVALUE - msn.UVALUE);
ip_id_offset =:= lsb(k, ((2^k) / 4) - 1) [ k ];
}
}
ip_id_sequential_variable(behavior, indicator)
{
UNCOMPRESSED {
ip_id [ 16 ];
}
COMPRESSED short {
ENFORCE((behavior == IP_ID_BEHAVIOR_SEQUENTIAL) ||
(behavior == IP_ID_BEHAVIOR_SEQUENTIAL_SWAPPED));
ENFORCE(indicator == 0);
ip_id =:= ip_id_lsb(behavior, 8) [ 8 ];
}
COMPRESSED long {
ENFORCE((behavior == IP_ID_BEHAVIOR_SEQUENTIAL) ||
(behavior == IP_ID_BEHAVIOR_SEQUENTIAL_SWAPPED));
ENFORCE(indicator == 1);
ENFORCE(ip_id_offset.UVALUE == ip_id.UVALUE - msn.UVALUE);
ip_id =:= irregular(16) [ 16 ];
}
COMPRESSED not_present {
ENFORCE((behavior == IP_ID_BEHAVIOR_RANDOM) ||
(behavior == IP_ID_BEHAVIOR_ZERO));
}
}
dont_fragment(version)
{
UNCOMPRESSED {
df [ 0, 1 ];
}
COMPRESSED v4 {
ENFORCE(version == 4);
df =:= irregular(1) [ 1 ];
}
COMPRESSED v6 {
ENFORCE(version == 6);
unused =:= compressed_value(1, 0) [ 1 ];
}
}
pt_irr_or_static(flag)
{
UNCOMPRESSED {
payload_type [ 7 ];
}
COMPRESSED not_present {
ENFORCE(flag == 0);
payload_type =:= static [ 0 ];
}
COMPRESSED present {
ENFORCE(flag == 1);
reserved =:= compressed_value(1, 0) [ 1 ];
payload_type =:= irregular(7) [ 7 ];
}
}
csrc_list_presence(presence, cc_value)
{
UNCOMPRESSED {
csrc_list;
}
COMPRESSED no_list {
ENFORCE(presence == 0);
csrc_list =:= static [ 0 ];
}
COMPRESSED list_present {
ENFORCE(presence == 1);
csrc_list =:= list_csrc(cc_value) [ VARIABLE ];
}
}
scaled_ts_lsb(time_stride_value, k)
{
UNCOMPRESSED {
timestamp [ 32 ];
}
COMPRESSED timerbased {
ENFORCE(time_stride_value != 0);
timestamp =:= timer_based_lsb(time_stride_value, k,
((2^k) / 2) - 1);
}
COMPRESSED regular {
ENFORCE(time_stride_value == 0);
timestamp =:= lsb(k, ((2^k) / 4) - 1);
}
}
// Self-describing variable length encoding with reordering offset
sdvl_sn_lsb(field_width)
{
UNCOMPRESSED {
field [ field_width ];
}
COMPRESSED lsb7 {
discriminator =:= '0' [ 1 ];
field =:= msn_lsb(7) [ 7 ];
}
COMPRESSED lsb14 {
discriminator =:= '10' [ 2 ];
field =:= msn_lsb(14) [ 14 ];
}
COMPRESSED lsb21 {
discriminator =:= '110' [ 3 ];
field =:= msn_lsb(21) [ 21 ];
}
COMPRESSED lsb28 {
discriminator =:= '1110' [ 4 ];
field =:= msn_lsb(28) [ 28 ];
}
COMPRESSED lsb32 {
discriminator =:= '11111111' [ 8 ];
field =:= irregular(field_width) [ field_width ];
}
}
// Self-describing variable length encoding
sdvl_lsb(field_width)
{
UNCOMPRESSED {
field [ field_width ];
}
COMPRESSED lsb7 {
discriminator =:= '0' [ 1 ];
field =:= lsb(7, ((2^7) / 4) - 1) [ 7 ];
}
COMPRESSED lsb14 {
discriminator =:= '10' [ 2 ];
field =:= lsb(14, ((2^14) / 4) - 1) [ 14 ];
}
COMPRESSED lsb21 {
discriminator =:= '110' [ 3 ];
field =:= lsb(21, ((2^21) / 4) - 1) [ 21 ];
}
COMPRESSED lsb28 {
discriminator =:= '1110' [ 4 ];
field =:= lsb(28, ((2^28) / 4) - 1) [ 28 ];
}
COMPRESSED lsb32 {
discriminator =:= '11111111' [ 8 ];
field =:= irregular(field_width) [ field_width ];
}
}
sdvl_scaled_ts_lsb(time_stride)
okay bestie, we're picking up right where the last part left off, and this chunk is all grammar, so no heavy prose yet ๐
{
UNCOMPRESSED {
field [ 32 ];
}
COMPRESSED lsb7 {
discriminator =:= '0' [ 1 ];
field =:= scaled_ts_lsb(time_stride, 7) [ 7 ];
}
COMPRESSED lsb14 {
discriminator =:= '10' [ 2 ];
field =:= scaled_ts_lsb(time_stride, 14) [ 14 ];
}
COMPRESSED lsb21 {
discriminator =:= '110' [ 3 ];
field =:= scaled_ts_lsb(time_stride, 21) [ 21 ];
}
COMPRESSED lsb28 {
discriminator =:= '1110' [ 4 ];
field =:= scaled_ts_lsb(time_stride, 28) [ 28 ];
}
COMPRESSED lsb32 {
discriminator =:= '11111111' [ 8 ];
field =:= irregular(32) [ 32 ];
}
}
ooh, next up is the variable_scaled_timestamp grammar ๐
it has its own little ENFORCE checks on tss_flag, tsc_flag and ts_stride, so it's giving careful gatekeeper energy โจ
variable_scaled_timestamp(tss_flag, tsc_flag, ts_stride, time_stride)
{
UNCOMPRESSED {
scaled_value [ 32 ];
}
COMPRESSED present {
ENFORCE((tss_flag == 0) && (tsc_flag == 1));
ENFORCE(ts_stride != 0);
scaled_value =:= sdvl_scaled_ts_lsb(time_stride) [ VARIABLE ];
}
COMPRESSED not_present {
ENFORCE(((tss_flag == 1) && (tsc_flag == 0)) ||
((tss_flag == 0) && (tsc_flag == 0)));
}
}
and the unscaled sibling, variable_unscaled_timestamp, is the same vibe but flipped ๐ฆ
variable_unscaled_timestamp(tss_flag, tsc_flag)
{
UNCOMPRESSED {
timestamp [ 32 ];
}
COMPRESSED present {
ENFORCE(((tss_flag == 1) && (tsc_flag == 0)) ||
((tss_flag == 0) && (tsc_flag == 0)));
timestamp =:= sdvl_lsb(32);
}
COMPRESSED not_present {
ENFORCE((tss_flag == 0) && (tsc_flag == 1));
}
}
next is profile_1_7_flags1_enc, which is a whole flags byte situation ๐ธ the present and not_present branches are basically mirror images of each other, iconic
profile_1_7_flags1_enc(flag, ip_version)
{
UNCOMPRESSED {
ip_outer_indicator [ 1 ];
ttl_hopl_indicator [ 1 ];
tos_tc_indicator [ 1 ];
df [ 0, 1 ];
ip_id_behavior [ 2 ];
reorder_ratio [ 2 ];
}
COMPRESSED not_present {
ENFORCE(flag == 0);
ENFORCE(ip_outer_indicator.CVALUE == 0);
ENFORCE(ttl_hopl_indicator.CVALUE == 0);
ENFORCE(tos_tc_indicator.CVALUE == 0);
df =:= static;
ip_id_behavior =:= static;
reorder_ratio =:= static;
}
COMPRESSED present {
ENFORCE(flag == 1);
ip_outer_indicator =:= irregular(1) [ 1 ];
ttl_hopl_indicator =:= irregular(1) [ 1 ];
tos_tc_indicator =:= irregular(1) [ 1 ];
df =:= dont_fragment(ip_version) [ 1 ];
ip_id_behavior =:= irregular(2) [ 2 ];
reorder_ratio =:= irregular(2) [ 2 ];
}
}
then there's profile_1_flags2_enc, which has a reserved field tucked at the end ๐ง
profile_1_flags2_enc(flag)
{
UNCOMPRESSED {
list_indicator [ 1 ];
pt_indicator [ 1 ];
time_stride_indicator [ 1 ];
pad_bit [ 1 ];
extension [ 1 ];
}
COMPRESSED not_present{
ENFORCE(flag == 0);
ENFORCE(list_indicator.UVALUE == 0);
ENFORCE(pt_indicator.UVALUE == 0);
ENFORCE(time_stride_indicator.UVALUE == 0);
pad_bit =:= static;
extension =:= static;
}
COMPRESSED present {
ENFORCE(flag == 1);
list_indicator =:= irregular(1) [ 1 ];
pt_indicator =:= irregular(1) [ 1 ];
time_stride_indicator =:= irregular(1) [ 1 ];
pad_bit =:= irregular(1) [ 1 ];
extension =:= irregular(1) [ 1 ];
reserved =:= compressed_value(3, 0) [ 3 ];
}
}
the flags for profiles 2, 3 and 4 come next, and they're the same energy but a little shorter ๐
profile_2_3_4_flags_enc(flag, ip_version)
{
UNCOMPRESSED {
ip_outer_indicator [ 1 ];
df [ 0, 1 ];
ip_id_behavior [ 2 ];
}
COMPRESSED not_present {
ENFORCE(flag == 0);
ENFORCE(ip_outer_indicator.CVALUE == 0);
df =:= static;
ip_id_behavior =:= static;
}
COMPRESSED present {
ENFORCE(flag == 1);
ip_outer_indicator =:= irregular(1) [ 1 ];
df =:= dont_fragment(ip_version) [ 1 ];
ip_id_behavior =:= irregular(2) [ 2 ];
reserved =:= compressed_value(4, 0) [ 4 ];
}
}
then profile_8_flags_enc adds a coverage_behavior field, which is giving main character energy ๐
profile_8_flags_enc(flag, ip_version)
{
UNCOMPRESSED {
ip_outer_indicator [ 1 ];
df [ 0, 1 ];
ip_id_behavior [ 2 ];
coverage_behavior [ 2 ];
}
COMPRESSED not_present {
ENFORCE(flag == 0);
ENFORCE(ip_outer_indicator.CVALUE == 0);
df =:= static;
ip_id_behavior =:= static;
coverage_behavior =:= static;
}
COMPRESSED present {
ENFORCE(flag == 1);
reserved =:= compressed_value(2, 0) [ 2 ];
ip_outer_indicator =:= irregular(1) [ 1 ];
df =:= dont_fragment(ip_version) [ 1 ];
ip_id_behavior =:= irregular(2) [ 2 ];
coverage_behavior =:= irregular(2) [ 2 ];
}
}
and profile_7_flags2_enc is the flags2 cousin with a one-bit reserved field and the same coverage_behavior vibes ๐
profile_7_flags2_enc(flag)
{
UNCOMPRESSED {
list_indicator [ 1 ];
pt_indicator [ 1 ];
time_stride_indicator [ 1 ];
pad_bit [ 1 ];
extension [ 1 ];
coverage_behavior [ 2 ];
}
COMPRESSED not_present{
ENFORCE(flag == 0);
ENFORCE(list_indicator.CVALUE == 0);
ENFORCE(pt_indicator.CVALUE == 0);
ENFORCE(time_stride_indicator.CVALUE == 0);
pad_bit =:= static;
extension =:= static;
coverage_behavior =:= static;
}
COMPRESSED present {
ENFORCE(flag == 1);
reserved =:= compressed_value(1, 0) [ 1 ];
list_indicator =:= irregular(1) [ 1 ];
pt_indicator =:= irregular(1) [ 1 ];
time_stride_indicator =:= irregular(1) [ 1 ];
pad_bit =:= irregular(1) [ 1 ];
extension =:= irregular(1) [ 1 ];
coverage_behavior =:= irregular(2) [ 2 ];
}
}
and now we've hit the RTP profile section ๐ฆ the separator comment is giving dramatic movie-trailer energy, so here it is with the RTP base header, which is the big one
////////////////////////////////////////////
// RTP profile
////////////////////////////////////////////
rtp_baseheader(profile_value, ts_stride_value, time_stride_value,
outer_ip_flag, ip_id_behavior_value,
reorder_ratio_value)
{
UNCOMPRESSED v4 {
ENFORCE(msn.UVALUE == sequence_number.UVALUE);
outer_headers =:= baseheader_outer_headers [ VARIABLE ];
ip_version =:= uncompressed_value(4, 4) [ 4 ];
header_length =:= uncompressed_value(4, 5) [ 4 ];
tos_tc [ 8 ];
length =:= inferred_ip_v4_length [ 16 ];
ip_id [ 16 ];
rf =:= uncompressed_value(1, 0) [ 1 ];
df [ 1 ];
mf =:= uncompressed_value(1, 0) [ 1 ];
frag_offset =:= uncompressed_value(13, 0) [ 13 ];
ttl_hopl [ 8 ];
next_header [ 8 ];
ip_checksum =:= inferred_ip_v4_header_checksum [ 16 ];
src_addr [ 32 ];
dest_addr [ 32 ];
extension_headers =:= baseheader_extension_headers [ VARIABLE ];
src_port [ 16 ];
dst_port [ 16 ];
udp_length =:= inferred_udp_length [ 16 ];
udp_checksum [ 16 ];
rtp_version =:= uncompressed_value(2, 2) [ 2 ];
pad_bit [ 1 ];
extension [ 1 ];
cc [ 4 ];
marker [ 1 ];
payload_type [ 7 ];
sequence_number [ 16 ];
timestamp [ 32 ];
ssrc [ 32 ];
csrc_list [ VARIABLE ];
}
UNCOMPRESSED v6 {
ENFORCE(ip_id_behavior_innermost.UVALUE == IP_ID_BEHAVIOR_RANDOM);
ENFORCE(msn.UVALUE == sequence_number.UVALUE);
outer_headers =:= baseheader_outer_headers [ VARIABLE ];
ip_version =:= uncompressed_value(4, 6) [ 4 ];
tos_tc [ 8 ];
flow_label [ 20 ];
payload_length =:= inferred_ip_v6_length [ 16 ];
next_header [ 8 ];
ttl_hopl [ 8 ];
src_addr [ 128 ];
dest_addr [ 128 ];
extension_headers =:= baseheader_extension_headers [ VARIABLE ];
src_port [ 16 ];
dst_port [ 16 ];
udp_length =:= inferred_udp_length [ 16 ];
udp_checksum [ 16 ];
rtp_version =:= uncompressed_value(2, 2) [ 2 ];
pad_bit [ 1 ];
extension [ 1 ];
cc [ 4 ];
marker [ 1 ];
payload_type [ 7 ];
sequence_number [ 16 ];
timestamp [ 32 ];
ssrc [ 32 ];
csrc_list [ VARIABLE ];
df =:= uncompressed_value(0,0) [ 0 ];
ip_id =:= uncompressed_value(0,0) [ 0 ];
}
CONTROL {
ENFORCE(profile_value == PROFILE_RTP_0101);
ENFORCE(profile == profile_value);
ENFORCE(time_stride.UVALUE == time_stride_value);
ENFORCE(ts_stride.UVALUE == ts_stride_value);
ENFORCE(reorder_ratio.UVALUE == reorder_ratio_value);
ENFORCE(ip_id_behavior_innermost.UVALUE == ip_id_behavior_value);
dummy_field =:= field_scaling(ts_stride.UVALUE,
ts_scaled.UVALUE, timestamp.UVALUE, ts_offset.UVALUE) [ 0 ];
}
INITIAL {
ts_stride =:= uncompressed_value(32, TS_STRIDE_DEFAULT);
time_stride =:= uncompressed_value(32, TIME_STRIDE_DEFAULT);
}
DEFAULT {
ENFORCE(outer_ip_flag == 0);
tos_tc =:= static;
dest_addr =:= static;
ttl_hopl =:= static;
src_addr =:= static;
df =:= static;
flow_label =:= static;
next_header =:= static;
src_port =:= static;
dst_port =:= static;
pad_bit =:= static;
extension =:= static;
cc =:= static;
// When marker not present in packets, it is assumed 0
marker =:= uncompressed_value(1, 0);
payload_type =:= static;
sequence_number =:= static;
timestamp =:= static;
ssrc =:= static;
csrc_list =:= static;
ts_stride =:= static;
time_stride =:= static;
ts_scaled =:= static;
ts_offset =:= static;
reorder_ratio =:= static;
ip_id_behavior_innermost =:= static;
}
Okay bestie, the RTP profile is getting so detailed ๐ฆ Here's the co_common replacement for UOR-2-ext3, which packs a bunch of flags and indicators into one header. The whole thing is giving main character energy โจ
// Replacement for UOR-2-ext3
COMPRESSED co_common {
ENFORCE(outer_ip_flag == outer_ip_indicator.CVALUE);
discriminator =:= '11111010' [ 8 ];
marker =:= irregular(1) [ 1 ];
header_crc =:= crc7(THIS.UVALUE, THIS.ULENGTH) [ 7 ];
flags1_indicator =:= irregular(1) [ 1 ];
flags2_indicator =:= irregular(1) [ 1 ];
tsc_indicator =:= irregular(1) [ 1 ];
tss_indicator =:= irregular(1) [ 1 ];
ip_id_indicator =:= irregular(1) [ 1 ];
control_crc3 =:= control_crc3_encoding [ 3 ];
outer_ip_indicator : ttl_hopl_indicator :
tos_tc_indicator : df : ip_id_behavior_innermost : reorder_ratio
=:= profile_1_7_flags1_enc(flags1_indicator.CVALUE,
ip_version.UVALUE) [ 0, 8 ];
list_indicator : pt_indicator : tis_indicator : pad_bit :
extension =:= profile_1_flags2_enc(
flags2_indicator.CVALUE) [ 0, 8 ];
tos_tc =:= static_or_irreg(tos_tc_indicator.CVALUE, 8) [ 0, 8 ];
ttl_hopl =:= static_or_irreg(ttl_hopl_indicator.CVALUE,
ttl_hopl.ULENGTH) [ 0, 8 ];
payload_type =:= pt_irr_or_static(pt_indicator) [ 0, 8 ];
sequence_number =:=
sdvl_sn_lsb(sequence_number.ULENGTH) [ VARIABLE ];
ip_id =:= ip_id_sequential_variable(
ip_id_behavior_innermost.UVALUE,
ip_id_indicator.CVALUE) [ 0, 8, 16 ];
ts_scaled =:= variable_scaled_timestamp(tss_indicator.CVALUE,
tsc_indicator.CVALUE, ts_stride.UVALUE,
time_stride.UVALUE) [ VARIABLE ];
timestamp =:= variable_unscaled_timestamp(tss_indicator.CVALUE,
tsc_indicator.CVALUE) [ VARIABLE ];
ts_stride =:= sdvl_or_static(tss_indicator.CVALUE) [ VARIABLE ];
time_stride =:= sdvl_or_static(tis_indicator.CVALUE) [ VARIABLE ];
csrc_list =:= csrc_list_presence(list_indicator.CVALUE,
cc.UVALUE) [ VARIABLE ];
}
// UO-0
COMPRESSED pt_0_crc3 {
discriminator =:= '0' [ 1 ];
msn =:= msn_lsb(4) [ 4 ];
header_crc =:= crc3(THIS.UVALUE, THIS.ULENGTH) [ 3 ];
timestamp =:= inferred_scaled_field [ 0 ];
ip_id =:= inferred_sequential_ip_id [ 0 ];
}
// New format, Type 0 with strong CRC and more SN bits
COMPRESSED pt_0_crc7 {
discriminator =:= '1000' [ 4 ];
msn =:= msn_lsb(5) [ 5 ];
header_crc =:= crc7(THIS.UVALUE, THIS.ULENGTH) [ 7 ];
timestamp =:= inferred_scaled_field [ 0 ];
ip_id =:= inferred_sequential_ip_id [ 0 ];
}
// UO-1 replacement
COMPRESSED pt_1_rnd {
ENFORCE(ts_stride.UVALUE != 0);
ENFORCE((ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_RANDOM) ||
(ip_id_behavior_innermost.UVALUE == IP_ID_BEHAVIOR_ZERO));
discriminator =:= '101' [ 3 ];
marker =:= irregular(1) [ 1 ];
msn =:= msn_lsb(4) [ 4 ];
ts_scaled =:= scaled_ts_lsb(time_stride.UVALUE, 5) [ 5 ];
header_crc =:= crc3(THIS.UVALUE, THIS.ULENGTH) [ 3 ];
}
// UO-1-ID replacement
COMPRESSED pt_1_seq_id {
ENFORCE((ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_SEQUENTIAL) ||
(ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_SEQUENTIAL_SWAPPED));
discriminator =:= '1001' [ 4 ];
ip_id =:= ip_id_lsb(ip_id_behavior_innermost.UVALUE, 4) [ 4 ];
msn =:= msn_lsb(5) [ 5 ];
header_crc =:= crc3(THIS.UVALUE, THIS.ULENGTH) [ 3 ];
timestamp =:= inferred_scaled_field [ 0 ];
}
// UO-1-TS replacement
COMPRESSED pt_1_seq_ts {
ENFORCE(ts_stride.UVALUE != 0);
ENFORCE((ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_SEQUENTIAL) ||
(ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_SEQUENTIAL_SWAPPED));
discriminator =:= '101' [ 3 ];
marker =:= irregular(1) [ 1 ];
msn =:= msn_lsb(4) [ 4 ];
ts_scaled =:= scaled_ts_lsb(time_stride.UVALUE, 5) [ 5 ];
header_crc =:= crc3(THIS.UVALUE, THIS.ULENGTH) [ 3 ];
ip_id =:= inferred_sequential_ip_id [ 0 ];
}
// UOR-2 replacement
COMPRESSED pt_2_rnd {
ENFORCE(ts_stride.UVALUE != 0);
ENFORCE((ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_RANDOM) ||
(ip_id_behavior_innermost.UVALUE == IP_ID_BEHAVIOR_ZERO));
discriminator =:= '110' [ 3 ];
msn =:= msn_lsb(7) [ 7 ];
ts_scaled =:= scaled_ts_lsb(time_stride.UVALUE, 6) [ 6 ];
marker =:= irregular(1) [ 1 ];
header_crc =:= crc7(THIS.UVALUE, THIS.ULENGTH) [ 7 ];
}
// UOR-2-ID replacement
COMPRESSED pt_2_seq_id {
ENFORCE((ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_SEQUENTIAL) ||
(ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_SEQUENTIAL_SWAPPED));
discriminator =:= '11000' [ 5 ];
msn =:= msn_lsb(7) [ 7 ];
ip_id =:= ip_id_lsb(ip_id_behavior_innermost.UVALUE, 5) [ 5 ];
header_crc =:= crc7(THIS.UVALUE, THIS.ULENGTH) [ 7 ];
timestamp =:= inferred_scaled_field [ 0 ];
}
// UOR-2-ID-ext1 replacement (both TS and IP-ID)
COMPRESSED pt_2_seq_both {
ENFORCE(ts_stride.UVALUE != 0);
ENFORCE((ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_SEQUENTIAL) ||
(ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_SEQUENTIAL_SWAPPED));
discriminator =:= '11001' [ 5 ];
msn =:= msn_lsb(7) [ 7 ];
ip_id =:= ip_id_lsb(ip_id_behavior_innermost.UVALUE, 5) [ 5 ];
header_crc =:= crc7(THIS.UVALUE, THIS.ULENGTH) [ 7 ];
ts_scaled =:= scaled_ts_lsb(time_stride.UVALUE, 7) [ 7 ];
marker =:= irregular(1) [ 1 ];
}
// UOR-2-TS replacement
COMPRESSED pt_2_seq_ts {
ENFORCE(ts_stride.UVALUE != 0);
ENFORCE((ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_SEQUENTIAL) ||
(ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_SEQUENTIAL_SWAPPED));
discriminator =:= '1101' [ 4 ];
msn =:= msn_lsb(7) [ 7 ];
ts_scaled =:= scaled_ts_lsb(time_stride.UVALUE, 5) [ 5 ];
marker =:= irregular(1) [ 1 ];
header_crc =:= crc7(THIS.UVALUE, THIS.ULENGTH) [ 7 ];
ip_id =:= inferred_sequential_ip_id [ 0 ];
}
}
Next up is the UDP profile section ๐ธ It starts with its own separator banner, so the vibe shifts into a whole new chapter ๐
////////////////////////////////////////////
// UDP profile
////////////////////////////////////////////
udp_baseheader(profile_value, outer_ip_flag, ip_id_behavior_value,
reorder_ratio_value)
okay so this is where the UDP profile wraps up with its two header layouts, the rules that lock things down, and the compressed packet types. the code below is verbatim, bestie, so it's giving precision ๐ โจ
Right after it, we start the ESP profile, which is literally the next vibe ๐ง
{
UNCOMPRESSED v4 {
outer_headers =:= baseheader_outer_headers [ VARIABLE ];
ip_version =:= uncompressed_value(4, 4) [ 4 ];
header_length =:= uncompressed_value(4, 5) [ 4 ];
tos_tc [ 8 ];
length =:= inferred_ip_v4_length [ 16 ];
ip_id [ 16 ];
rf =:= uncompressed_value(1, 0) [ 1 ];
df [ 1 ];
mf =:= uncompressed_value(1, 0) [ 1 ];
frag_offset =:= uncompressed_value(13, 0) [ 13 ];
ttl_hopl [ 8 ];
next_header [ 8 ];
ip_checksum =:= inferred_ip_v4_header_checksum [ 16 ];
src_addr [ 32 ];
dest_addr [ 32 ];
extension_headers =:= baseheader_extension_headers [ VARIABLE ];
src_port [ 16 ];
dst_port [ 16 ];
udp_length =:= inferred_udp_length [ 16 ];
udp_checksum [ 16 ];
}
UNCOMPRESSED v6 {
ENFORCE(ip_id_behavior.UVALUE == IP_ID_BEHAVIOR_RANDOM);
outer_headers =:= baseheader_outer_headers [ VARIABLE ];
ip_version =:= uncompressed_value(4, 6) [ 4 ];
tos_tc [ 8 ];
flow_label [ 20 ];
payload_length =:= inferred_ip_v6_length [ 16 ];
next_header [ 8 ];
ttl_hopl [ 8 ];
src_addr [ 128 ];
dest_addr [ 128 ];
extension_headers =:= baseheader_extension_headers [ VARIABLE ];
src_port [ 16 ];
dst_port [ 16 ];
udp_length =:= inferred_udp_length [ 16 ];
udp_checksum [ 16 ];
df =:= uncompressed_value(0,0) [ 0 ];
ip_id =:= uncompressed_value(0,0) [ 0 ];
}
CONTROL {
ENFORCE(profile_value == PROFILE_UDP_0102);
ENFORCE(profile == profile_value);
ENFORCE(reorder_ratio.UVALUE == reorder_ratio_value);
ENFORCE(ip_id_behavior_innermost.UVALUE == ip_id_behavior_value);
}
DEFAULT {
ENFORCE(outer_ip_flag == 0);
tos_tc =:= static;
dest_addr =:= static;
ip_version =:= static;
ttl_hopl =:= static;
src_addr =:= static;
df =:= static;
flow_label =:= static;
next_header =:= static;
src_port =:= static;
dst_port =:= static;
reorder_ratio =:= static;
ip_id_behavior_innermost =:= static;
}
// Replacement for UOR-2-ext3
COMPRESSED co_common {
ENFORCE(outer_ip_flag == outer_ip_indicator.CVALUE);
discriminator =:= '11111010' [ 8 ];
ip_id_indicator =:= irregular(1) [ 1 ];
header_crc =:= crc7(THIS.UVALUE, THIS.ULENGTH) [ 7 ];
flags_indicator =:= irregular(1) [ 1 ];
ttl_hopl_indicator =:= irregular(1) [ 1 ];
tos_tc_indicator =:= irregular(1) [ 1 ];
reorder_ratio =:= irregular(2) [ 2 ];
control_crc3 =:= control_crc3_encoding [ 3 ];
outer_ip_indicator : df : ip_id_behavior_innermost =:=
profile_2_3_4_flags_enc(
flags_indicator.CVALUE, ip_version.UVALUE) [ 0, 8 ];
tos_tc =:= static_or_irreg(tos_tc_indicator.CVALUE, 8) [ 0, 8 ];
ttl_hopl =:= static_or_irreg(ttl_hopl_indicator.CVALUE,
ttl_hopl.ULENGTH) [ 0, 8 ];
msn =:= msn_lsb(8) [ 8 ];
ip_id =:= ip_id_sequential_variable(ip_id_behavior_innermost.UVALUE,
ip_id_indicator.CVALUE) [ 0, 8, 16 ];
}
// UO-0
COMPRESSED pt_0_crc3 {
discriminator =:= '0' [ 1 ];
msn =:= msn_lsb(4) [ 4 ];
header_crc =:= crc3(THIS.UVALUE, THIS.ULENGTH) [ 3 ];
ip_id =:= inferred_sequential_ip_id [ 0 ];
}
// New format, Type 0 with strong CRC and more SN bits
COMPRESSED pt_0_crc7 {
discriminator =:= '100' [ 3 ];
msn =:= msn_lsb(6) [ 6 ];
header_crc =:= crc7(THIS.UVALUE, THIS.ULENGTH) [ 7 ];
ip_id =:= inferred_sequential_ip_id [ 0 ];
}
// UO-1-ID replacement (PT-1 only used for sequential)
COMPRESSED pt_1_seq_id {
ENFORCE((ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_SEQUENTIAL) ||
(ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_SEQUENTIAL_SWAPPED));
discriminator =:= '101' [ 3 ];
header_crc =:= crc3(THIS.UVALUE, THIS.ULENGTH) [ 3 ];
msn =:= msn_lsb(6) [ 6 ];
ip_id =:= ip_id_lsb(ip_id_behavior_innermost.UVALUE, 4) [ 4 ];
}
// UOR-2-ID replacement
COMPRESSED pt_2_seq_id {
ENFORCE((ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_SEQUENTIAL) ||
(ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_SEQUENTIAL_SWAPPED));
discriminator =:= '110' [ 3 ];
ip_id =:= ip_id_lsb(ip_id_behavior_innermost.UVALUE, 6) [ 6 ];
header_crc =:= crc7(THIS.UVALUE, THIS.ULENGTH) [ 7 ];
msn =:= msn_lsb(8) [ 8 ];
}
}
////////////////////////////////////////////
// ESP profile
////////////////////////////////////////////
esp_baseheader(profile_value, outer_ip_flag, ip_id_behavior_value,
reorder_ratio_value)
okay bestie, this part is where the ESP profile's body wraps up and the IP-only profile starts ๐ซถโจ
{
UNCOMPRESSED v4 {
ENFORCE(msn.UVALUE == sequence_number.UVALUE % 65536);
outer_headers =:= baseheader_outer_headers [ VARIABLE ];
ip_version =:= uncompressed_value(4, 4) [ 4 ];
header_length =:= uncompressed_value(4, 5) [ 4 ];
tos_tc [ 8 ];
length =:= inferred_ip_v4_length [ 16 ];
ip_id [ 16 ];
rf =:= uncompressed_value(1, 0) [ 1 ];
df [ 1 ];
mf =:= uncompressed_value(1, 0) [ 1 ];
frag_offset =:= uncompressed_value(13, 0) [ 13 ];
ttl_hopl [ 8 ];
next_header [ 8 ];
ip_checksum =:= inferred_ip_v4_header_checksum [ 16 ];
src_addr [ 32 ];
dest_addr [ 32 ];
extension_headers =:= baseheader_extension_headers [ VARIABLE ];
spi [ 32 ];
sequence_number [ 32 ];
}
UNCOMPRESSED v6 {
ENFORCE(msn.UVALUE == (sequence_number.UVALUE % 65536));
ENFORCE(ip_id_behavior_innermost.UVALUE == IP_ID_BEHAVIOR_RANDOM);
outer_headers =:= baseheader_outer_headers [ VARIABLE ];
ip_version =:= uncompressed_value(4, 6) [ 4 ];
tos_tc [ 8 ];
flow_label [ 20 ];
payload_length =:= inferred_ip_v6_length [ 16 ];
next_header [ 8 ];
ttl_hopl [ 8 ];
src_addr [ 128 ];
dest_addr [ 128 ];
extension_headers =:= baseheader_extension_headers [ VARIABLE ];
spi [ 32 ];
sequence_number [ 32 ];
df =:= uncompressed_value(0,0) [ 0 ];
ip_id =:= uncompressed_value(0,0) [ 0 ];
}
CONTROL {
ENFORCE(profile_value == PROFILE_ESP_0103);
ENFORCE(profile == profile_value);
ENFORCE(ip_id_behavior_innermost.UVALUE == ip_id_behavior_value);
ENFORCE(reorder_ratio.UVALUE == reorder_ratio_value);
}
DEFAULT {
ENFORCE(outer_ip_flag == 0);
tos_tc =:= static;
dest_addr =:= static;
ttl_hopl =:= static;
src_addr =:= static;
df =:= static;
flow_label =:= static;
next_header =:= static;
spi =:= static;
sequence_number =:= static;
reorder_ratio =:= static;
ip_id_behavior_innermost =:= static;
}
// Replacement for UOR-2-ext3
COMPRESSED co_common {
ENFORCE(outer_ip_flag == outer_ip_indicator.CVALUE);
discriminator =:= '11111010' [ 8 ];
ip_id_indicator =:= irregular(1) [ 1 ];
header_crc =:= crc7(THIS.UVALUE, THIS.ULENGTH) [ 7 ];
flags_indicator =:= irregular(1) [ 1 ];
ttl_hopl_indicator =:= irregular(1) [ 1 ];
tos_tc_indicator =:= irregular(1) [ 1 ];
reorder_ratio =:= irregular(2) [ 2 ];
control_crc3 =:= control_crc3_encoding [ 3 ];
outer_ip_indicator : df : ip_id_behavior_innermost =:=
profile_2_3_4_flags_enc(
flags_indicator.CVALUE, ip_version.UVALUE) [ 0, 8 ];
tos_tc =:= static_or_irreg(tos_tc_indicator.CVALUE, 8) [ 0, 8 ];
ttl_hopl =:= static_or_irreg(ttl_hopl_indicator.CVALUE,
ttl_hopl.ULENGTH) [ 0, 8 ];
sequence_number =:=
sdvl_sn_lsb(sequence_number.ULENGTH) [ VARIABLE ];
ip_id =:= ip_id_sequential_variable(ip_id_behavior_innermost.UVALUE,
ip_id_indicator.CVALUE) [ 0, 8, 16 ];
}
// Sequence number sent instead of MSN due to field length
// UO-0
COMPRESSED pt_0_crc3 {
discriminator =:= '0' [ 1 ];
sequence_number =:= msn_lsb(4) [ 4 ];
header_crc =:= crc3(THIS.UVALUE, THIS.ULENGTH) [ 3 ];
ip_id =:= inferred_sequential_ip_id [ 0 ];
}
// New format, Type 0 with strong CRC and more SN bits
COMPRESSED pt_0_crc7 {
discriminator =:= '100' [ 3 ];
sequence_number =:= msn_lsb(6) [ 6 ];
header_crc =:= crc7(THIS.UVALUE, THIS.ULENGTH) [ 7 ];
ip_id =:= inferred_sequential_ip_id [ 0 ];
}
// UO-1-ID replacement (PT-1 only used for sequential)
COMPRESSED pt_1_seq_id {
ENFORCE((ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_SEQUENTIAL) ||
(ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_SEQUENTIAL_SWAPPED));
discriminator =:= '101' [ 3 ];
header_crc =:= crc3(THIS.UVALUE, THIS.ULENGTH) [ 3 ];
sequence_number =:= msn_lsb(6) [ 6 ];
ip_id =:= ip_id_lsb(ip_id_behavior_innermost.UVALUE, 4) [ 4 ];
}
// UOR-2-ID replacement
COMPRESSED pt_2_seq_id {
ENFORCE((ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_SEQUENTIAL) ||
(ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_SEQUENTIAL_SWAPPED));
discriminator =:= '110' [ 3 ];
ip_id =:= ip_id_lsb(ip_id_behavior_innermost.UVALUE, 6) [ 6 ];
header_crc =:= crc7(THIS.UVALUE, THIS.ULENGTH) [ 7 ];
sequence_number =:= msn_lsb(8) [ 8 ];
}
}
Next up is the IP-only profile, and it's giving a lot of dรฉjร vu with the ESP one, but the details are different so pay attention ๐๐
////////////////////////////////////////////
// IP-only profile
////////////////////////////////////////////
iponly_baseheader(profile_value, outer_ip_flag, ip_id_behavior_value,
reorder_ratio_value)
{
UNCOMPRESSED v4 {
outer_headers =:= baseheader_outer_headers [ VARIABLE ];
ip_version =:= uncompressed_value(4, 4) [ 4 ];
header_length =:= uncompressed_value(4, 5) [ 4 ];
tos_tc [ 8 ];
length =:= inferred_ip_v4_length [ 16 ];
ip_id [ 16 ];
rf =:= uncompressed_value(1, 0) [ 1 ];
df [ 1 ];
mf =:= uncompressed_value(1, 0) [ 1 ];
frag_offset =:= uncompressed_value(13, 0) [ 13 ];
ttl_hopl [ 8 ];
next_header [ 8 ];
ip_checksum =:= inferred_ip_v4_header_checksum [ 16 ];
src_addr [ 32 ];
dest_addr [ 32 ];
extension_headers =:= baseheader_extension_headers [ VARIABLE ];
}
UNCOMPRESSED v6 {
ENFORCE(ip_id_behavior_innermost.UVALUE == IP_ID_BEHAVIOR_RANDOM);
outer_headers =:= baseheader_outer_headers [ VARIABLE ];
ip_version =:= uncompressed_value(4, 6) [ 4 ];
tos_tc [ 8 ];
flow_label [ 20 ];
payload_length =:= inferred_ip_v6_length [ 16 ];
next_header [ 8 ];
ttl_hopl [ 8 ];
src_addr [ 128 ];
dest_addr [ 128 ];
extension_headers =:= baseheader_extension_headers [ VARIABLE ];
df =:= uncompressed_value(0,0) [ 0 ];
ip_id =:= uncompressed_value(0,0) [ 0 ];
}
CONTROL {
ENFORCE(profile_value == PROFILE_IP_0104);
ENFORCE(profile == profile_value);
ENFORCE(reorder_ratio.UVALUE == reorder_ratio_value);
ENFORCE(ip_id_behavior_innermost.UVALUE == ip_id_behavior_value);
}
DEFAULT {
ENFORCE(outer_ip_flag == 0);
tos_tc =:= static;
dest_addr =:= static;
ttl_hopl =:= static;
src_addr =:= static;
df =:= static;
flow_label =:= static;
next_header =:= static;
reorder_ratio =:= static;
ip_id_behavior_innermost =:= static;
}
// Replacement for UOR-2-ext3
COMPRESSED co_common {
ENFORCE(outer_ip_flag == outer_ip_indicator.CVALUE);
discriminator =:= '11111010' [ 8 ];
ip_id_indicator =:= irregular(1) [ 1 ];
header_crc =:= crc7(THIS.UVALUE, THIS.ULENGTH) [ 7 ];
flags_indicator =:= irregular(1) [ 1 ];
ttl_hopl_indicator =:= irregular(1) [ 1 ];
tos_tc_indicator =:= irregular(1) [ 1 ];
reorder_ratio =:= irregular(2) [ 2 ];
control_crc3 =:= control_crc3_encoding [ 3 ];
outer_ip_indicator : df : ip_id_behavior_innermost =:=
profile_2_3_4_flags_enc(
flags_indicator.CVALUE, ip_version.UVALUE) [ 0, 8 ];
tos_tc =:= static_or_irreg(tos_tc_indicator.CVALUE, 8) [ 0, 8 ];
ttl_hopl =:= static_or_irreg(ttl_hopl_indicator.CVALUE,
ttl_hopl.ULENGTH) [ 0, 8 ];
msn =:= msn_lsb(8) [ 8 ];
ip_id =:= ip_id_sequential_variable(ip_id_behavior_innermost.UVALUE,
ip_id_indicator.CVALUE) [ 0, 8, 16 ];
}
// UO-0
COMPRESSED pt_0_crc3 {
discriminator =:= '0' [ 1 ];
msn =:= msn_lsb(4) [ 4 ];
header_crc =:= crc3(THIS.UVALUE, THIS.ULENGTH) [ 3 ];
ip_id =:= inferred_sequential_ip_id [ 0 ];
}
// New format, Type 0 with strong CRC and more SN bits
COMPRESSED pt_0_crc7 {
discriminator =:= '100' [ 3 ];
msn =:= msn_lsb(6) [ 6 ];
header_crc =:= crc7(THIS.UVALUE, THIS.ULENGTH) [ 7 ];
ip_id =:= inferred_sequential_ip_id [ 0 ];
}
// UO-1-ID replacement (PT-1 only used for sequential)
COMPRESSED pt_1_seq_id {
ENFORCE((ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_SEQUENTIAL) ||
(ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_SEQUENTIAL_SWAPPED));
discriminator =:= '101' [ 3 ];
header_crc =:= crc3(THIS.UVALUE, THIS.ULENGTH) [ 3 ];
msn =:= msn_lsb(6) [ 6 ];
ip_id =:= ip_id_lsb(ip_id_behavior_innermost.UVALUE, 4) [ 4 ];
}
// UOR-2-ID replacement
COMPRESSED pt_2_seq_id {
ENFORCE((ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_SEQUENTIAL) ||
(ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_SEQUENTIAL_SWAPPED));
discriminator =:= '110' [ 3 ];
ip_id =:= ip_id_lsb(ip_id_behavior_innermost.UVALUE, 6) [ 6 ];
header_crc =:= crc7(THIS.UVALUE, THIS.ULENGTH) [ 7 ];
msn =:= msn_lsb(8) [ 8 ];
}
}
And now we're kicking off the UDP-lite/RTP profile section, which is giving main character energy ๐ฆ๐ Here's where the header signature starts, and it keeps going in the next part โจ
////////////////////////////////////////////
// UDP-lite/RTP profile
////////////////////////////////////////////
udplite_rtp_baseheader(profile_value, ts_stride_value,
time_stride_value, outer_ip_flag,
ip_id_behavior_value, reorder_ratio_value,
coverage_behavior_value)
Okay bestie, the compressed packet formats are here and they are so organized ๐ โจ
{
UNCOMPRESSED v4 {
ENFORCE(msn.UVALUE == sequence_number.UVALUE);
outer_headers =:= baseheader_outer_headers [ VARIABLE ];
ip_version =:= uncompressed_value(4, 4) [ 4 ];
header_length =:= uncompressed_value(4, 5) [ 4 ];
tos_tc [ 8 ];
length =:= inferred_ip_v4_length [ 16 ];
ip_id [ 16 ];
rf =:= uncompressed_value(1, 0) [ 1 ];
df [ 1 ];
mf =:= uncompressed_value(1, 0) [ 1 ];
frag_offset =:= uncompressed_value(13, 0) [ 13 ];
ttl_hopl [ 8 ];
next_header [ 8 ];
ip_checksum =:= inferred_ip_v4_header_checksum [ 16 ];
src_addr [ 32 ];
dest_addr [ 32 ];
extension_headers =:= baseheader_extension_headers [ VARIABLE ];
src_port [ 16 ];
dst_port [ 16 ];
checksum_coverage [ 16 ];
udp_checksum [ 16 ];
rtp_version =:= uncompressed_value(2, 2) [ 2 ];
pad_bit [ 1 ];
extension [ 1 ];
cc [ 4 ];
marker [ 1 ];
payload_type [ 7 ];
sequence_number [ 16 ];
timestamp [ 32 ];
ssrc [ 32 ];
csrc_list [ VARIABLE ];
}
UNCOMPRESSED v6 {
ENFORCE(ip_id_behavior_innermost.UVALUE == IP_ID_BEHAVIOR_RANDOM);
outer_headers =:= baseheader_outer_headers [ VARIABLE ];
ip_version =:= uncompressed_value(4, 6) [ 4 ];
tos_tc [ 8 ];
flow_label [ 20 ];
payload_length =:= inferred_ip_v6_length [ 16 ];
next_header [ 8 ];
ttl_hopl [ 8 ];
src_addr [ 128 ];
dest_addr [ 128 ];
extension_headers =:= baseheader_extension_headers [ VARIABLE ];
src_port [ 16 ];
dst_port [ 16 ];
checksum_coverage [ 16 ];
udp_checksum [ 16 ];
rtp_version =:= uncompressed_value(2, 2) [ 2 ];
pad_bit [ 1 ];
extension [ 1 ];
cc [ 4 ];
marker [ 1 ];
payload_type [ 7 ];
sequence_number [ 16 ];
timestamp [ 32 ];
ssrc [ 32 ];
csrc_list [ VARIABLE ];
df =:= uncompressed_value(0,0) [ 0 ];
ip_id =:= uncompressed_value(0,0) [ 0 ];
}
CONTROL {
ENFORCE(profile_value == PROFILE_RTP_0107);
ENFORCE(profile == profile_value);
ENFORCE(time_stride.UVALUE == time_stride_value);
ENFORCE(ts_stride.UVALUE == ts_stride_value);
ENFORCE(coverage_behavior.UVALUE == coverage_behavior_value);
ENFORCE(reorder_ratio.UVALUE == reorder_ratio_value);
ENFORCE(ip_id_behavior_innermost.UVALUE == ip_id_behavior_value);
dummy_field =:= field_scaling(ts_stride.UVALUE,
ts_scaled.UVALUE, timestamp.UVALUE, ts_offset.UVALUE) [ 0 ];
}
INITIAL {
ts_stride =:= uncompressed_value(32, TS_STRIDE_DEFAULT);
time_stride =:= uncompressed_value(32, TIME_STRIDE_DEFAULT);
}
DEFAULT {
ENFORCE(outer_ip_flag == 0);
tos_tc =:= static;
dest_addr =:= static;
ttl_hopl =:= static;
src_addr =:= static;
df =:= static;
flow_label =:= static;
next_header =:= static;
src_port =:= static;
dst_port =:= static;
pad_bit =:= static;
extension =:= static;
cc =:= static;
// When marker not present in packets, it is assumed 0
marker =:= uncompressed_value(1, 0);
payload_type =:= static;
sequence_number =:= static;
timestamp =:= static;
ssrc =:= static;
csrc_list =:= static;
ts_stride =:= static;
time_stride =:= static;
ts_scaled =:= static;
ts_offset =:= static;
reorder_ratio =:= static;
ip_id_behavior_innermost =:= static;
}
// Replacement for UOR-2-ext3
COMPRESSED co_common {
ENFORCE(outer_ip_flag == outer_ip_indicator.CVALUE);
discriminator =:= '11111010' [ 8 ];
marker =:= irregular(1) [ 1 ];
header_crc =:= crc7(THIS.UVALUE, THIS.ULENGTH) [ 7 ];
flags1_indicator =:= irregular(1) [ 1 ];
flags2_indicator =:= irregular(1) [ 1 ];
tsc_indicator =:= irregular(1) [ 1 ];
tss_indicator =:= irregular(1) [ 1 ];
ip_id_indicator =:= irregular(1) [ 1 ];
control_crc3 =:= control_crc3_encoding [ 3 ];
outer_ip_indicator : ttl_hopl_indicator :
tos_tc_indicator : df : ip_id_behavior_innermost : reorder_ratio
=:= profile_1_7_flags1_enc(flags1_indicator.CVALUE,
ip_version.UVALUE) [ 0, 8 ];
list_indicator : pt_indicator : tis_indicator : pad_bit :
extension : coverage_behavior =:=
profile_7_flags2_enc(flags2_indicator.CVALUE) [ 0, 8 ];
tos_tc =:= static_or_irreg(tos_tc_indicator.CVALUE, 8) [ 0, 8 ];
ttl_hopl =:=
static_or_irreg(ttl_hopl_indicator.CVALUE, 8) [ 0, 8 ];
payload_type =:= pt_irr_or_static(pt_indicator.CVALUE) [ 0, 8 ];
sequence_number =:=
sdvl_sn_lsb(sequence_number.ULENGTH) [ VARIABLE ];
ip_id =:= ip_id_sequential_variable(ip_id_behavior_innermost.UVALUE,
ip_id_indicator.CVALUE) [ 0, 8, 16 ];
ts_scaled =:= variable_scaled_timestamp(tss_indicator.CVALUE,
tsc_indicator.CVALUE, ts_stride.UVALUE,
time_stride.UVALUE) [ VARIABLE ];
timestamp =:= variable_unscaled_timestamp(tss_indicator.CVALUE,
tsc_indicator.CVALUE) [ VARIABLE ];
ts_stride =:= sdvl_or_static(tss_indicator.CVALUE) [ VARIABLE ];
time_stride =:= sdvl_or_static(tis_indicator.CVALUE) [ VARIABLE ];
csrc_list =:=
csrc_list_presence(list_indicator.CVALUE,
cc.UVALUE) [ VARIABLE ];
}
// UO-0
COMPRESSED pt_0_crc3 {
discriminator =:= '0' [ 1 ];
msn =:= msn_lsb(4) [ 4 ];
header_crc =:= crc3(THIS.UVALUE, THIS.ULENGTH) [ 3 ];
timestamp =:= inferred_scaled_field [ 0 ];
ip_id =:= inferred_sequential_ip_id [ 0 ];
}
// New format, Type 0 with strong CRC and more SN bits
COMPRESSED pt_0_crc7 {
discriminator =:= '1000' [ 4 ];
msn =:= msn_lsb(5) [ 5 ];
header_crc =:= crc7(THIS.UVALUE, THIS.ULENGTH) [ 7 ];
timestamp =:= inferred_scaled_field [ 0 ];
ip_id =:= inferred_sequential_ip_id [ 0 ];
}
// UO-1 replacement
COMPRESSED pt_1_rnd {
ENFORCE(ts_stride.UVALUE != 0);
ENFORCE((ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_RANDOM) ||
(ip_id_behavior_innermost.UVALUE == IP_ID_BEHAVIOR_ZERO));
discriminator =:= '101' [ 3 ];
marker =:= irregular(1) [ 1 ];
msn =:= msn_lsb(4) [ 4 ];
ts_scaled =:= scaled_ts_lsb(time_stride.UVALUE, 5) [ 5 ];
header_crc =:= crc3(THIS.UVALUE, THIS.ULENGTH) [ 3 ];
}
// UO-1-ID replacement
COMPRESSED pt_1_seq_id {
ENFORCE((ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_SEQUENTIAL) ||
(ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_SEQUENTIAL_SWAPPED));
discriminator =:= '1001' [ 4 ];
ip_id =:= ip_id_lsb(ip_id_behavior_innermost.UVALUE, 4) [ 4 ];
msn =:= msn_lsb(5) [ 5 ];
header_crc =:= crc3(THIS.UVALUE, THIS.ULENGTH) [ 3 ];
timestamp =:= inferred_scaled_field [ 0 ];
}
// UO-1-TS replacement
COMPRESSED pt_1_seq_ts {
ENFORCE(ts_stride.UVALUE != 0);
ENFORCE((ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_SEQUENTIAL) ||
(ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_SEQUENTIAL_SWAPPED));
discriminator =:= '101' [ 3 ];
marker =:= irregular(1) [ 1 ];
msn =:= msn_lsb(4) [ 4 ];
ts_scaled =:= scaled_ts_lsb(time_stride.UVALUE, 5) [ 5 ];
header_crc =:= crc3(THIS.UVALUE, THIS.ULENGTH) [ 3 ];
ip_id =:= inferred_sequential_ip_id [ 0 ];
}
// UOR-2 replacement
COMPRESSED pt_2_rnd {
ENFORCE(ts_stride.UVALUE != 0);
ENFORCE((ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_RANDOM) ||
(ip_id_behavior_innermost.UVALUE == IP_ID_BEHAVIOR_ZERO));
discriminator =:= '110' [ 3 ];
msn =:= msn_lsb(7) [ 7 ];
ts_scaled =:= scaled_ts_lsb(time_stride.UVALUE, 6) [ 6 ];
marker =:= irregular(1) [ 1 ];
header_crc =:= crc7(THIS.UVALUE, THIS.ULENGTH) [ 7 ];
}
// UOR-2-ID replacement
COMPRESSED pt_2_seq_id {
ENFORCE((ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_SEQUENTIAL) ||
(ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_SEQUENTIAL_SWAPPED));
discriminator =:= '11000' [ 5 ];
msn =:= msn_lsb(7) [ 7 ];
ip_id =:= ip_id_lsb(ip_id_behavior_innermost.UVALUE, 5) [ 5 ];
header_crc =:= crc7(THIS.UVALUE, THIS.ULENGTH) [ 7 ];
timestamp =:= inferred_scaled_field [ 0 ];
}
// UOR-2-ID-ext1 replacement (both TS and IP-ID)
COMPRESSED pt_2_seq_both {
ENFORCE(ts_stride.UVALUE != 0);
ENFORCE((ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_SEQUENTIAL) ||
(ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_SEQUENTIAL_SWAPPED));
discriminator =:= '11001' [ 5 ];
msn =:= msn_lsb(7) [ 7 ];
ip_id =:= ip_id_lsb(ip_id_behavior_innermost.UVALUE, 5) [ 5 ];
header_crc =:= crc7(THIS.UVALUE, THIS.ULENGTH) [ 7 ];
ts_scaled =:= scaled_ts_lsb(time_stride.UVALUE, 7) [ 7 ];
marker =:= irregular(1) [ 1 ];
}
// UOR-2-TS replacement
COMPRESSED pt_2_seq_ts {
ENFORCE(ts_stride.UVALUE != 0);
ENFORCE((ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_SEQUENTIAL) ||
(ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_SEQUENTIAL_SWAPPED));
discriminator =:= '1101' [ 4 ];
msn =:= msn_lsb(7) [ 7 ];
ts_scaled =:= scaled_ts_lsb(time_stride.UVALUE, 5) [ 5 ];
marker =:= irregular(1) [ 1 ];
header_crc =:= crc7(THIS.UVALUE, THIS.ULENGTH) [ 7 ];
ip_id =:= inferred_sequential_ip_id [ 0 ];
}
}
////////////////////////////////////////////
// UDP-lite profile
////////////////////////////////////////////
udplite_baseheader(profile_value, outer_ip_flag, ip_id_behavior_value,
reorder_ratio_value, coverage_behavior_value)
{
UNCOMPRESSED v4 {
outer_headers =:= baseheader_outer_headers [ VARIABLE ];
ip_version =:= uncompressed_value(4, 4) [ 4 ];
header_length =:= uncompressed_value(4, 5) [ 4 ];
tos_tc [ 8 ];
length =:= inferred_ip_v4_length [ 16 ];
ip_id [ 16 ];
rf =:= uncompressed_value(1, 0) [ 1 ];
df [ 1 ];
mf =:= uncompressed_value(1, 0) [ 1 ];
frag_offset =:= uncompressed_value(13, 0) [ 13 ];
ttl_hopl [ 8 ];
next_header [ 8 ];
ip_checksum =:= inferred_ip_v4_header_checksum [ 16 ];
src_addr [ 32 ];
dest_addr [ 32 ];
extension_headers =:= baseheader_extension_headers [ VARIABLE ];
src_port [ 16 ];
dst_port [ 16 ];
checksum_coverage [ 16 ];
udp_checksum [ 16 ];
}
UNCOMPRESSED v6 {
ENFORCE(ip_id_behavior_innermost.UVALUE == IP_ID_BEHAVIOR_RANDOM);
outer_headers =:= baseheader_outer_headers [ VARIABLE ];
ip_version =:= uncompressed_value(4, 6) [ 4 ];
tos_tc [ 8 ];
flow_label [ 20 ];
payload_length =:= inferred_ip_v6_length [ 16 ];
next_header [ 8 ];
ttl_hopl [ 8 ];
src_addr [ 128 ];
dest_addr [ 128 ];
extension_headers =:= baseheader_extension_headers [ VARIABLE ];
src_port [ 16 ];
dst_port [ 16 ];
checksum_coverage [ 16 ];
udp_checksum [ 16 ];
df =:= uncompressed_value(0,0) [ 0 ];
ip_id =:= uncompressed_value(0,0) [ 0 ];
}
CONTROL {
ENFORCE(profile_value == PROFILE_UDPLITE_0108);
ENFORCE(profile == profile_value);
ENFORCE(coverage_behavior.UVALUE == coverage_behavior_value);
ENFORCE(reorder_ratio.UVALUE == reorder_ratio_value);
ENFORCE(ip_id_behavior_innermost.UVALUE == ip_id_behavior_value);
}
DEFAULT {
ENFORCE(outer_ip_flag == 0);
tos_tc =:= static;
dest_addr =:= static;
ttl_hopl =:= static;
src_addr =:= static;
df =:= static;
flow_label =:= static;
next_header =:= static;
src_port =:= static;
dst_port =:= static;
reorder_ratio =:= static;
ip_id_behavior_innermost =:= static;
}
// Replacement for UOR-2-ext3
COMPRESSED co_common {
ENFORCE(outer_ip_flag == outer_ip_indicator.CVALUE);
discriminator =:= '11111010' [ 8 ];
ip_id_indicator =:= irregular(1) [ 1 ];
header_crc =:= crc7(THIS.UVALUE, THIS.ULENGTH) [ 7 ];
flags_indicator =:= irregular(1) [ 1 ];
ttl_hopl_indicator =:= irregular(1) [ 1 ];
tos_tc_indicator =:= irregular(1) [ 1 ];
reorder_ratio =:= irregular(2) [ 2 ];
control_crc3 =:= control_crc3_encoding [ 3 ];
outer_ip_indicator : df : ip_id_behavior_innermost :
coverage_behavior =:=
profile_8_flags_enc(flags_indicator.CVALUE,
ip_version.UVALUE) [ 0, 8 ];
tos_tc =:= static_or_irreg(tos_tc_indicator.CVALUE, 8) [ 0, 8 ];
ttl_hopl =:= static_or_irreg(ttl_hopl_indicator.CVALUE,
ttl_hopl.ULENGTH) [ 0, 8 ];
msn =:= msn_lsb(8) [ 8 ];
ip_id =:= ip_id_sequential_variable(ip_id_behavior_innermost.UVALUE,
ip_id_indicator.CVALUE) [ 0, 8, 16 ];
}
// UO-0
COMPRESSED pt_0_crc3 {
discriminator =:= '0' [ 1 ];
msn =:= msn_lsb(4) [ 4 ];
header_crc =:= crc3(THIS.UVALUE, THIS.ULENGTH) [ 3 ];
ip_id =:= inferred_sequential_ip_id [ 0 ];
}
// New format, Type 0 with strong CRC and more SN bits
COMPRESSED pt_0_crc7 {
discriminator =:= '100' [ 3 ];
msn =:= msn_lsb(6) [ 6 ];
header_crc =:= crc7(THIS.UVALUE, THIS.ULENGTH) [ 7 ];
ip_id =:= inferred_sequential_ip_id [ 0 ];
}
// UO-1-ID replacement (PT-1 only used for sequential)
COMPRESSED pt_1_seq_id {
ENFORCE((ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_SEQUENTIAL) ||
(ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_SEQUENTIAL_SWAPPED));
discriminator =:= '101' [ 3 ];
header_crc =:= crc3(THIS.UVALUE, THIS.ULENGTH) [ 3 ];
msn =:= msn_lsb(6) [ 6 ];
ip_id =:= ip_id_lsb(ip_id_behavior_innermost.UVALUE, 4) [ 4 ];
}
// UOR-2-ID replacement
COMPRESSED pt_2_seq_id {
ENFORCE((ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_SEQUENTIAL) ||
(ip_id_behavior_innermost.UVALUE ==
IP_ID_BEHAVIOR_SEQUENTIAL_SWAPPED));
discriminator =:= '110' [ 3 ];
ip_id =:= ip_id_lsb(ip_id_behavior_innermost.UVALUE, 6) [ 6 ];
header_crc =:= crc7(THIS.UVALUE, THIS.ULENGTH) [ 7 ];
msn =:= msn_lsb(8) [ 8 ];
}
}
6.9. Feedback Formats and Options ๐
6.9.1. Feedback Formats
Okay so this section is all about the feedback formats for ROHCv2 profiles ๐ . It reuses the formats from Section 5.2.3 of [RFC4995], so no reinventing the wheel here, bestie ๐ซถ.
The Acknowledgment Number field holds the least significant bits of the MSN (see Section 6.3.1) for the reference header being acknowledged ๐. A reference header is one that got successfully CRC-8 validated or CRC verified โจ. If there's no reference header around, the feedback MUST carry an ACKNUMBER-NOT-VALID option ๐.
FEEDBACK-1
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| Acknowledgment Number |
+---+---+---+---+---+---+---+---+
Acknowledgment Number: The eight least significant bits of the
MSN.
A FEEDBACK-1 is an ACK, period ๐ฆ. If you want a NACK or a STATIC-NACK, you need FEEDBACK-2 instead ๐ธ.
FEEDBACK-2
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
|Acktype| Acknowledgment Number |
+---+---+---+---+---+---+---+---+
| Acknowledgment Number |
+---+---+---+---+---+---+---+---+
| CRC |
+---+---+---+---+---+---+---+---+
/ Feedback options /
+---+---+---+---+---+---+---+---+
Here's how the FEEDBACK-2 fields work ๐:
- Acktype:
0 = ACK1 = NACK2 = STATIC-NACK3 is reserved (MUST NOT be used for parsability)
- Acknowledgment Number: The least significant bits of the MSN.
- CRC: 8-bit CRC computed over the entire feedback payload including any CID fields but excluding the feedback type, the 'Size' field, and the 'Code' octet, using the polynomial defined in Section 5.3.1.1 of [RFC4995]. If the CID is given with an Add-CID octet, the Add-CID octet immediately precedes the FEEDBACK-1 or FEEDBACK-2 format. For purposes of computing the CRC, the CRC field is zero.
- Feedback options: A variable number of feedback options, see Section 6.9.2. Options may appear in any order.
A FEEDBACK-2 of type NACK or STATIC-NACK is always implicitly an acknowledgment for a successfully decompressed packet ๐ฅ. That packet is one whose LSBs match the Acknowledgment Number of the feedback element, unless the ACKNUMBER-NOT-VALID option (see Section 6.9.2.2) shows up in the feedback element ๐.
The FEEDBACK-2 format always carries a CRC, so it's way more robust than FEEDBACK-1 ๐ง. When receiving FEEDBACK-2, the compressor MUST verify the information by computing the CRC and comparing the result with the CRC carried in the feedback format. If the two are not identical, the feedback element MUST be discarded ๐.
6.9.2. Feedback Options
A feedback option has variable length and the following general format ๐:
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| Opt Type | Opt Len |
+---+---+---+---+---+---+---+---+
/ Option Data / Opt Len (octets)
+---+---+---+---+---+---+---+---+
- Opt Type: Unsigned integer that represents the type of the feedback option. Section 6.9.2.1 through Section 6.9.2.4 describes the ROHCv2 feedback options.
- Opt Len: Unsigned integer that represents the length of the Option Data field, in octets.
- Option Data: Feedback type specific data. Present if the value of the Opt Len field is set to a non-zero value.
6.9.2.1. The REJECT Option
The REJECT option tells the compressor that the decompressor doesn't have enough resources to handle the flow ๐ฅบ.
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| Opt Type = 2 | Opt Len = 0 |
+---+---+---+---+---+---+---+---+
When the compressor gets a REJECT option, it MUST stop compressing the packet flow ๐. It SHOULD refrain from trying to increase the number of compressed packet flows for some time ๐ซ . The REJECT option MUST NOT appear more than once in the FEEDBACK-2 format; otherwise, the compressor MUST discard the entire feedback element ๐ญ.
6.9.2.2. The ACKNUMBER-NOT-VALID Option
okay so the ACKNUMBER-NOT-VALID option is the decompressor basically saying "hey, that Acknowledgment Number field? not valid ๐ โโ๏ธ" ๐
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| Opt Type = 3 | Opt Len = 0 |
+---+---+---+---+---+---+---+---+
bestie, when this option shows up, the compressor MUST NOT use the Acknowledgment Number of the feedback to hunt down the matching sent header. no cap, that's a hard rule ๐ซ When the option is used, the Acknowledgment Number field of the FEEDBACK-2 format gets set to 0. so a NACK or a STATIC-NACK feedback type sent with the ACKNUMBER-NOT-VALID option ends up being the same as a STATIC-NACK, at least when it comes to the kind of context repair the decompressor is asking for ๐งโจ
the ACKNUMBER-NOT-VALID option MUST NOT appear more than once in the FEEDBACK-2 format. if it does, the compressor MUST discard the entire feedback element. ๐๏ธ
6.9.2.3. The CONTEXT_MEMORY Option
the CONTEXT_MEMORY option is the decompressor's way of telling the compressor "ngl, I don't have enough memory to handle this flow the way it's currently being compressed" ๐ฅบ๐ง
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| Opt Type = 9 | Opt Len = 0 |
+---+---+---+---+---+---+---+---+
when a compressor gets a CONTEXT_MEMORY option, it SHOULD take steps to compress the flow in a way that uses less decompressor memory, or it should stop compressing the flow altogether. the CONTEXT_MEMORY option MUST NOT appear more than once in the FEEDBACK-2 format. if it does, the compressor MUST discard the entire feedback element. ๐ซถ
6.9.2.4. The CLOCK_RESOLUTION Option
the CLOCK_RESOLUTION option tells the compressor what clock resolution the decompressor has. it also says whether the decompressor can do timer-based compression of the RTP TS timestamp (see Section 6.6.9). โฐโจ this one is applied per channel, which means it covers any context tied to a profile where the option matters between a given compressor and decompressor pair ๐ฆ
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| Opt Type = 10 | Opt Len = 1 |
+---+---+---+---+---+---+---+---+
| Clock resolution (ms) |
+---+---+---+---+---+---+---+---+
Clock resolution: an unsigned integer that holds the decompressor's clock resolution, measured in milliseconds. ๐ธ
the smallest clock resolution you can indicate is 1 millisecond. the value zero is special: it means the decompressor cannot do timer-based compression of the RTP Timestamp. ๐ the CLOCK_RESOLUTION option MUST NOT appear more than once in the FEEDBACK-2 format. if it does, the compressor MUST discard the entire feedback element. ๐
6.9.2.5. Unknown Option Types
if an option type that isn't defined in this document shows up, the compressor MUST discard the entire feedback element. not me being strict, but this is the rule ๐
7. Security Considerations
okay this section is serious and I'm not going to pretend otherwise ๐ญ๐จ
impairments like bit errors on the received compressed headers, missing packets, and reordering between packets could make the header decompressor rebuild erroneous packets. these are packets that don't match the original packet, but still have valid IP, UDP (or UDP-Lite), and RTP headers, and maybe even valid UDP (or UDP-Lite) checksums. that's giving sneaky ๐
the header compression profiles defined here use an internal checksum to verify reconstructed headers. this lowers the chance that a header decompressor hands erroneous packets up to upper layers without anyone noticing. in particular, the chance that consecutive erroneous packets slip past the internal checksum is close to zero ๐ก๏ธโจ
this small but non-zero probability stays the same when integrity protection is applied after compression and verified before decompression, in cases where an attacker could discard or reorder packets between the compression endpoints. ๐
the impairments mentioned above could be caused by a malfunctioning or malicious header compressor. such corruption may be detected with end-to-end integrity mechanisms that the compression won't affect. also, the internal checksum can be useful when compressors are malfunctioning. ๐ง
denial-of-service attacks are possible if an intruder can inject (for example) bogus IR or FEEDBACK packets onto the link and cause compression efficiency to drop. however, an intruder who can inject arbitrary packets at the link layer like this raises extra security issues that dwarf the ones related to header compression. ๐๐จ
8. IANA Considerations
the IANA has assigned these ROHC profile identifiers for the profiles defined in this document ๐
Identifier Profile
---------- -------
0x0101 ROHCv2 RTP
0x0102 ROHCv2 UDP
0x0103 ROHCv2 ESP
0x0104 ROHCv2 IP
0x0107 ROHCv2 RTP/UDP-Lite
0x0108 ROHCv2 UDP-Lite
9. Acknowledgements โจ
the authors want to thank Mark West, Robert Finking, Haipeng Jin, and Rohit Kapoor for being committed document reviewers, and for the constructive discussions while this document was being developed. ๐ซถ thanks to Carl Knutsson for his huge contribution to this spec, and to Jani Juvan and Anders Edqvist for useful comments and feedback ๐ also thanks to Elwyn Davies for his review as the General Area Review Team (Gen-ART) reviewer, and to Stephen Kent for his review on behalf of the IETF security directorate, during IETF last-call ๐ finally, thanks to everyone who has contributed to earlier ROHC specifications and supported this effort. you're all iconic ๐
10. References
10.1. Normative References
- [RFC0768] Postel, J., "User Datagram Protocol", STD 6, RFC 768, August 1980.
- [RFC0791] Postel, J., "Internet Protocol", STD 5, RFC 791, September 1981.
- [RFC2004] Perkins, C., "Minimal Encapsulation within IP", RFC 2004, October 1996.
- [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, March 1997.
- [RFC2460] Deering, S. and R. Hinden, "Internet Protocol, Version 6 (IPv6) Specification", RFC 2460, December 1998.
- [RFC2784] Farinacci, D., Li, T., Hanks, S., Meyer, D., and P. Traina, "Generic Routing Encapsulation (GRE)", RFC 2784, March 2000.
- [RFC2890] Dommety, G., "Key and Sequence Number Extensions to GRE", RFC 2890, September 2000.
- [RFC3550] Schulzrinne, H., Casner, S., Frederick, R., and V. Jacobson, "RTP: A Transport Protocol for Real-Time Applications", STD 64, RFC 3550, July 2003.
- [RFC3828] Larzon, L-A., Degermark, M., Pink, S., Jonsson, L-E., and G. Fairhurst, "The Lightweight User Datagram Protocol (UDP-Lite)", RFC 3828, July 2004.
- [RFC4019] Pelletier, G., "RObust Header Compression (ROHC): Profiles for User Datagram Protocol (UDP) Lite", RFC 4019, April 2005.
- [RFC4302] Kent, S., "IP Authentication Header", RFC 4302, December 2005.
- [RFC4303] Kent, S., "IP Encapsulating Security Payload (ESP)", RFC 4303, December 2005.
- [RFC4995] Jonsson, L-E., Pelletier, G., and K. Sandlund, "The RObust Header Compression (ROHC) Framework", RFC 4995, July 2007.
- [RFC4997] Finking, R. and G. Pelletier, "Formal Notation for RObust Header Compression (ROHC-FN)", RFC 4997, July 2007.
10.2. Informative References
- [RFC2675] Borman, D., Deering, S., and R. Hinden, "IPv6 Jumbograms", RFC 2675, August 1999.
- [RFC3095] Bormann, C., Burmeister, C., Degermark, M., Fukushima, H., Hannu, H., Jonsson, L-E., Hakenberg, R., Koren, T., Le, K., Liu, Z., Martensson, A., Miyazaki, A., Svanbro, K., Wiebke, T., Yoshimura, T., and H. Zheng, "RObust Header Compression (ROHC): Framework and four profiles: RTP, UDP, ESP, and uncompressed", RFC 3095, July 2001.
- [RFC3843] Jonsson, L-E. and G. Pelletier, "RObust Header Compression (ROHC): A Compression Profile for IP", RFC 3843, June 2004.
- [RFC4224] Pelletier, G., Jonsson, L-E., and K. Sandlund, "RObust Header Compression (ROHC): ROHC over Channels That Can Reorder Packets", RFC 4224, January 2006.
Appendix A. Detailed Classification of Header Fields ๐ฆ
header compression works because most header fields don't change randomly from packet to packet. lots of them are static or change in a pretty predictable way. so when you design a header compression scheme, understanding how each field behaves is super important ๐ง ๐
this appendix classifies and analyzes every field in the headers these profiles can compress. the analysis is based on how the traffic that's expected to be compressed most often behaves (for example, RTP field behavior is based on voice and/or video traffic) ๐ค๐ฅ
the fields fall into these classes:
- INFERRED: these fields hold values that can be worked out from other values, like the size of the frame carrying the packet. so they don't need to be included in compressed packets ๐งฎ
- STATIC: these fields should stay constant for the life of the flow. in general, it's enough to design the compressed format so these fields only get updated by IR packets. ๐ง
- STATIC-DEF: these fields stay constant for the life of the flow, and their values can be used to define a flow. they're only sent in IR packets. ๐ชช
- STATIC-KNOWN: these fields have well-known values, so they don't need to be communicated at all. ๐
- SEMISTATIC: these fields are unchanged most of the time. occasionally the value changes, but it goes back to its original value. for ROHCv2, the values of such fields don't need to be changeable with the smallest compressed packet formats, but they should be changeable with slightly larger compressed packets. ๐
- RARELY CHANGING (RACH): these fields change their values now and then, and then keep the new values. for ROHCv2, the values of such fields don't need to be changeable with the smallest compressed packet formats, but they should be changeable with slightly larger compressed packets. ๐ฑ
- IRREGULAR: no useful change pattern can be found for these fields, so they should be sent uncompressed in every compressed packet. ๐ฒ
- PATTERN: these fields change between every packet, but they change in a predictable pattern. ๐
A.1. IPv4 Header Fields ๐
okay so here's the IPv4 table, bestie ๐
+------------------------+----------------+
| Field | Class |
+------------------------+----------------+
| Version | STATIC-KNOWN |
| Header Length | STATIC-KNOWN |
| Type Of Service | RACH |
| Packet Length | INFERRED |
| Identification | |
| Sequential | PATTERN |
| Seq. swap | PATTERN |
| Random | IRREGULAR |
| Zero | STATIC |
| Reserved flag | STATIC-KNOWN |
| Don't Fragment flag | RACH |
| More Fragments flag | STATIC-KNOWN |
| Fragment Offset | STATIC-KNOWN |
| Time To Live | RACH |
| Protocol | STATIC-DEF |
| Header Checksum | INFERRED |
| Source Address | STATIC-DEF |
| Destination Address | STATIC-DEF |
+------------------------+----------------+
Version
The version field tells you which IP version is in use, and here it's always set to the value four. โจ
Header Length
As long as there are no options in the IP header, the header length stays constant at the value five. ๐ง If options are present, the field could be RACH or STATIC-DEF, but only option-less headers get compressed by ROHCv2 profiles. So it's classified as STATIC-KNOWN. no cap, that's the whole logic ๐
Type Of Service
For the kinds of flows that ROHCv2 profiles compress, the DSCP (Differentiated Services Code Point) and ECN (Explicit Congestion Notification) fields are expected to change relatively seldom. ๐ธ
Packet Length
Packet length info is expected to come from the link layer, so this one is INFERRED. ๐ฆ
IPv4 Identification ๐ฅ
okay this one is giving main character energy ๐ The Identification field (IP-ID) is how you figure out which fragments belong together when you're reassembling fragmented datagrams. The IPv4 spec doesn't say exactly how to assign these values. It only says each packet should get an IP-ID that's unique for the source-destination pair and protocol for as long as the datagram (or any of its fragments) could be alive in the network. So there are lots of ways to assign IP-ID values, and the expected behaviors have been sorted into four classes.
Sequential
In this behavior, the IP-ID is expected to go up by one for most packets. It may jump by more than one, depending on the transmitting IPv4 stack. ๐
Sequential Swapped
This one acts like Sequential, except the two bytes of IP-ID are byte-swapped. So you can swap them before compression and it'll behave exactly like Sequential. iykyk ๐
Random
Some IP stacks use a pseudo-random number generator to assign IP-ID values. There's no correlation between the IDs of subsequent datagrams, so there's no way to predict the next one. For header compression, that means the IP-ID field needs to be sent uncompressed with every datagram, which costs two extra octets of header. ๐
Zero
This behavior isn't a legal IPv4 implementation, but some existing IPv4 stacks do it anyway. All IP packets have the IP-ID set to zero. ๐ซ
Flags
The Reserved flag must be set to zero, so it's STATIC-KNOWN. The Don't Fragment (DF) flag changes rarely, so it's RACH. The More Fragments (MF) flag is expected to be zero, because IP fragments won't be compressed by ROHC, so it's STATIC-KNOWN. โจ
Fragment Offset
Assuming no fragmentation happens, the fragment offset is always zero, so it's STATIC-KNOWN. ๐งธ
Time To Live
The Time To Live field is expected to stay constant for the lifetime of a flow, or it might bounce between a limited number of values because of route changes. ๐ค๏ธ
Protocol
This field has the same value in all packets of a flow, so it's STATIC-DEF. ๐
Header Checksum
The header checksum protects individual hops from processing a corrupted header. But when almost all of the IP header info is compressed away, there's no point in keeping this extra checksum. It can be regenerated on the decompressor side instead, so it's INFERRED. big brain move ๐ง โจ
Source and Destination addresses
These fields are part of the definition of a flow, so they must stay constant for all packets in the flow. ๐
A.2. IPv6 Header Fields ๐ฆ
+----------------------+----------------+
| Field | Class |
+----------------------+----------------+
| Version | STATIC-KNOWN |
| Traffic Class | RACH |
| Flow Label | STATIC-DEF |
| Payload Length | INFERRED |
| Next Header | STATIC-DEF |
| Hop Limit | RACH |
| Source Address | STATIC-DEF |
| Destination Address | STATIC-DEF |
+----------------------+----------------+
Version
The version field tells you which IP version is in use, and here it's set to the value six. ๐
Traffic Class
For the kinds of flows that ROHCv2 profiles compress, the DSCP and ECN fields are expected to change relatively seldom. ๐ธ
Flow Label
This field may be used to identify packets that belong to a specific flow. If it isn't used, the value should be set to zero. Otherwise, every packet in the same flow must have the same value in this field. So it's STATIC-DEF. โจ
Payload Length
Packet length info (and, as a result, payload length) is expected to come from the link layer, so this one is INFERRED. ๐
Next Header
This field has the same value in all packets of a flow, so it's STATIC-DEF. ๐
Hop Limit
The Hop Limit field is expected to stay constant for the lifetime of a flow, or it might alternate between a limited number of values because of route changes. ๐ค๏ธ
Source and Destination addresses
These fields are part of the definition of a flow, so they must stay constant for all packets in the flow. That's why they're classified as STATIC-DEF. ๐ซถ
A.3. UDP Header Fields ๐ง
+------------------+-------------+
| Field | Class |
+------------------+-------------+
| Source Port | STATIC-DEF |
| Destination Port | STATIC-DEF |
| Length | INFERRED |
| Checksum | |
| Disabled | STATIC |
| Enabled | IRREGULAR |
+------------------+-------------+
Source and Destination ports
These fields are part of the definition of a flow, so they must stay constant for all packets in the flow. ๐ท
Length
Packet length info is expected to come from the link layer, so this one is INFERRED. ๐ฆ
Checksum
The checksum can be optional. If it's disabled, its value is constantly zero and can be compressed away. If it's enabled, its value depends on the payload. For compression purposes, that's basically the same as it changing randomly with every packet. ๐
A.4. UDP-Lite Header Fields ๐ธ
+--------------------+-------------+
| Field | Class |
+--------------------+-------------+
| Source Port | STATIC-DEF |
| Destination Port | STATIC-DEF |
| Checksum Coverage | |
| Zero | STATIC-DEF |
| Constant | INFERRED |
| Variable | IRREGULAR |
| Checksum | IRREGULAR |
+--------------------+-------------+
Source and Destination Port
These fields are part of the definition of a flow, so they must stay constant for all packets in the flow. ๐
Checksum Coverage
The Checksum Coverage field can behave in different ways. It may be zero, it may be equal to the datagram length, or it may be any value between eight octets and the length of the datagram. From a compression point of view, this field is expected to be either entirely predictable (when it follows the same behavior as the UDP Length field, or when it takes a constant value), or to change randomly for each packet, which makes it unpredictable for header compression. In every case, the behavior itself isn't expected to change for this field during the lifetime of a packet flow, or it changes relatively seldom. ๐งฉ
Checksum
The information used to calculate the UDP-Lite checksum depends on the value of the checksum coverage, and it always includes at least the UDP-Lite header. The checksum is a changing field, so it must always be sent as-is. ๐ฅ
A.5. RTP Header Fields โจ
Okay so RTP header fields, let's get into it ๐ Here's the classification table, bestie:
+----------------+----------------+
| Field | Class |
+----------------+----------------+
| Version | STATIC-KNOWN |
| Padding | RACH |
| Extension | RACH |
| CSRC Counter | RACH |
| Marker | SEMISTATIC |
| Payload Type | RACH |
| Sequence Number| PATTERN |
| Timestamp | PATTERN |
| SSRC | STATIC-DEF |
| CSRC | RACH |
+----------------+----------------+
Version ๐ This field is expected to have the value two, so it gets classified as STATIC-KNOWN. Literally no drama.
Padding ๐ซถ Whether this one gets used depends on the application. But if payload padding is in play, it's probably going to show up in most or all packets. It's classed as RACH so it can handle the case where the value changes.
Extension โจ If the application uses RTP extensions, they're often in every packet, though using them at all is pretty rare. So to compress a flow that only uses extensions in a few packets, this one is RACH. Efficient and iconic.
CSRC Count ๐ธ This field tells you how many CSRC items are in the CSRC list. It's expected to stay mostly constant from packet to packet, and when it does change, it changes by small amounts. As long as no RTP mixer is used, the value of this field will be zero.
Marker ๐ฆ For audio, the marker bit should be set only in the first packet of a talkspurt. For video, it should be set in the last packet of every picture. So in both cases the RTP marker is SEMISTATIC, no cap.
Payload Type ๐ Applications could adapt to congestion by changing the payload type and/or frame sizes. But that's not expected to happen often, so this field is RACH.
RTP Sequence Number ๐ง The RTP Sequence Number goes up by one for each packet sent. Simple, clean, bestie approved.
Timestamp โญ This one depends on the media type:
In the audio case:
As long as there are no pauses in the audio stream, the RTP Timestamp goes up by a constant value. That value matches the number of samples in the speech frame, so it mostly follows the RTP Sequence Number. When there's been a silent period and a new talkspurt starts, the timestamp jumps in proportion to how long the silence lasted. The increment will probably still be within a fairly limited range, though ๐
In the video case:
Between two consecutive packets, the timestamp will either stay unchanged or increase by a multiple of a fixed value that corresponds to the picture clock frequency. For certain coding schemes, the timestamp can also decrease by a multiple of that fixed value. Expressed as a multiple of the picture clock frequency, the change in timestamp value is in most cases within a limited range ๐
SSRC ๐ฅ This field is part of defining a flow, so it has to stay constant for every packet in the flow. That's why it's STATIC-DEF. Locked in.
Contributing Sources (CSRC) ๐ The participants in a session are identified by the CSRC fields. These are usually expected to stay the same from packet to packet, but they'll infrequently change by a few additions and/or removals.
A.6. ESP Header Fields ๐
+------------------+-------------+
| Field | Class |
+------------------+-------------+
| SPI | STATIC-DEF |
| Sequence Number | PATTERN |
+------------------+-------------+
SPI ๐ซถ This field is used to identify a distinct flow between two IPsec peers, and it changes rarely. So it's classified as STATIC-DEF. Very stable, very reliable, very her.
ESP Sequence Number ๐ The ESP Sequence Number goes up by one for each packet sent. Same energy as always.
A.7. IPv6 Extension Header Fields ๐
+-----------------------+---------------+
| Field | Class |
+-----------------------+---------------+
| Next Header | STATIC-DEF |
| Ext Hdr Len | |
| Routing | STATIC-DEF |
| Hop-by-hop | STATIC |
| Destination | STATIC |
| Options | |
| Routing | STATIC-DEF |
| Hop-by-hop | RACH |
| Destination | RACH |
+-----------------------+---------------+
Next Header ๐ This field will have the same value in all packets of a flow, so it's classified as STATIC-DEF. Consistent queen behavior.
Ext Hdr Len ๐ง For the Routing header, the length is expected to stay constant for the whole flow. If the length does change, the ROHC compressor should classify that as a new flow. For Hop-by-hop and Destination options headers, the length is expected to stay static, but an IR packet can update it.
Options ๐ฆ For the Routing header, the option content is expected to stay constant for the duration of the flow. If the routing information changes, the ROHC compressor should classify that as a new flow. For Hop-by-hop and Destination options headers, the options are expected to stay static, but an IR packet can update them. Lowkey so specific, I love it ๐
A.8. GRE Header Fields ๐
+--------------------+---------------+
| Field | Class |
+--------------------+---------------+
| C flag | STATIC |
| K flag | STATIC |
| S flag | STATIC |
| R flag | STATIC-KNOWN |
| Reserved0, Version | STATIC-KNOWN |
| Protocol | STATIC-DEF |
| Checksum | IRREGULAR |
| Reserved | STATIC-KNOWN |
| Sequence Number | PATTERN |
| Key | STATIC-DEF |
+--------------------+---------------+
Flags โจ The four flag bits are not expected to change for the duration of the flow. The R flag is expected to always be set to zero.
Reserved0, Version ๐ธ Both of these fields are expected to be set to zero for the whole duration of any flow.
Protocol ๐ This field will have the same value in all packets of a flow, so it's classified as STATIC-DEF. Stable and unbothered ๐
Checksum ๐ When the checksum field is present, it's expected to behave unpredictably. Chaotic energy, so it gets IRREGULAR status.
Reserved ๐ซถ When present, this field is expected to be set to zero.
Sequence Number ๐ฅ When present, the Sequence Number goes up by one for each packet.
Key ๐ When present, the Key field is used to define the flow, and it does not change. Iconic.
A.9. MINE Header Fields ๐ฆ
+---------------------+----------------+
| Field | Class |
+---------------------+----------------+
| Protocol | STATIC-DEF |
| S bit | STATIC-DEF |
| Reserved | STATIC-KNOWN |
| Checksum | INFERRED |
| Source Address | STATIC-DEF |
| Destination Address | STATIC-DEF |
+---------------------+----------------+
Protocol ๐ This field will have the same value in all packets of a flow, so it's classified as STATIC-DEF.
S bit ๐ The S bit is not expected to change during a flow.
Reserved ๐ The reserved field is expected to be set to zero.
Checksum ๐ง The header checksum protects individual routing hops from processing a corrupted header. Since all fields of this header are compressed away, there's no need to include this checksum in compressed packets. It can be regenerated at the decompressor side. So it's INFERRED, and that's giving clever ๐
Source and Destination Addresses โญ These fields can be used to define the flow, and they're not expected to change.
A.10. AH Header Fields ๐
+---------------------+----------------+
| Field | Class |
+---------------------+----------------+
| Next Header | STATIC-DEF |
| Payload Length | STATIC |
| Reserved | STATIC-KNOWN |
| SPI | STATIC-DEF |
| Sequence Number | PATTERN |
| ICV | IRREGULAR |
+---------------------+----------------+
Next Header ๐ This field will have the same value in all packets of a flow, so it's STATIC-DEF. Always the same, always slaying.
Payload Length ๐ธ It's expected that the length of the header is constant for the duration of the flow.
Reserved ๐ซถ The value of this field will be set to zero.
SPI ๐ฆ This field is used to identify a specific flow. It only changes when the sequence number wraps around, which is why it's STATIC-DEF.
Sequence Number โจ The Sequence Number goes up by one for each packet sent.
ICV ๐ The ICV is expected to behave unpredictably, so it's classified as IRREGULAR. Total wild card ๐
Appendix B. Compressor Implementation Guidelines ๐ง
This section describes some guiding principles for implementing a ROHCv2 compressor, with a focus on how to efficiently select appropriate packet formats. Okay, so the text in this appendix should be considered guidelines. It does not define any normative requirement on how ROHCv2 profiles are implemented, so no pressure ๐ธ
B.1. Reference Management ๐
The compressor usually maintains a sliding window of reference headers. That window contains as many references as needed for the optimistic approach. Each reference contains a description of which changes occurred in the flow between two consecutive headers in the flow. A new reference is inserted into the window each time a packet is compressed by this context. A reference may, for example, be implemented as a stored copy of the uncompressed header being represented. When the compressor is confident that a specific reference is no longer used by the decompressor (for example, by using the optimistic approach or feedback received), the reference is removed from the sliding window. Clean housekeeping, iconic ๐
B.2. Window-based LSB Encoding (W-LSB) ๐
Section 5.1.1 describes how the optimistic approach impacts the packet format selection for the compressor. Exactly how the compressor selects a packet format is up to the implementation to decide. But the following is an example of how this process can be performed for lsb-encoded fields through the use of Window-based LSB encoding (W-LSB).
With W-LSB encoding, the compressor uses a number of references (a window) from its context. What references to use is determined by its optimistic approach. The compressor extracts the value of the field to be W-LSB encoded from each reference in the window, and finds the maximum and minimum values. Once it determines these values, the compressor uses the assumption that the decompressor has a value for this field within the range given by these boundaries (inclusively) as its reference. The compressor can then select a number of LSBs from the value to be compressed, so that the LSBs can be decompressed regardless of whether the decompressor uses the minimum value, the maximum value, or any other value in the range of possible references. That's the whole vibe: pick the bits that work no matter what the other side has, and you're golden โจ
B.3. W-LSB Encoding and Timer-based Compression โจ
Okay bestie, this is where we figure out how many LSB bits the compressor should actually send for the timestamp field ๐ฐ๏ธ๐ Section 6.6.9 already covers what the decompressor does for timer-based RTP timestamp compression, so this part is all about the compressor's side of the story. The answer depends on two kinds of jitter added together: the jitter before the compressor, and the jitter between the compressor and the decompressor ๐
The jitter before the compressor can be estimated with this computation:
Max_Jitter_BC =
max {|(T_n - T_j) - ((a_n - a_j) / time_stride)|,
for all headers j in the sliding window}
Here, (T_n - T_j) is the timestamp difference between the header being compressed right now and a reference header, and (a_n - a_j) is the difference in arrival time between those same two headers ๐ฆ
There's also a second piece: the compressor needs an upper bound for the jitter between the compressor and decompressor, which is Max_Jitter_CD. Information like that may come from lower layers, for example ๐ง
Here's a bonus little trick: a compressor implementation can check whether the difference in clock resolution between the compressor and decompressor causes an error when it does integer arithmetic. If it does, the implementation can count that error as extra jitter ๐ธ
Once you've got both jitter estimates, the number of bits you need to send comes from this calculation:
ceiling(log2(2 * (Max_Jitter_BC + Max_Jitter_CD + 2) + 1))
Last step: that number is used to pick a packet format that carries at least that many scaled timestamp bits. Periodt ๐ โจ
Authors' Addresses
- Ghyslain Pelletier Ericsson Box 920 Lulea SE-971 28 Sweden Phone:
+46 (0) 8 404 29 43EMail:ghyslain.pelletier@ericsson.com - Kristofer Sandlund Ericsson Box 920 Lulea SE-971 28 Sweden Phone:
+46 (0) 8 404 41 58EMail:kristofer.sandlund@ericsson.com
Full Copyright Statement ๐ซถ
Copyright (C) The IETF Trust (2008).
This document is subject to the rights, licenses and restrictions contained in BCP 78. Except as set forth there, the authors keep all of their rights.
This document and the information in it are provided on an "AS IS" basis, and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY, THE IETF TRUST AND THE INTERNET ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
Intellectual Property ๐
The IETF doesn't take a position on whether any Intellectual Property Rights or other rights that might be claimed to apply to the technology in this document are valid or in scope, or whether any license under those rights might or might not be available. It also doesn't claim to have made any independent effort to find such rights. Info about how rights work for RFC documents is in BCP 78 and BCP 79 ๐
Copies of IPR disclosures sent to the IETF Secretariat, any assurances of licenses to be made available, or the results of an attempt to get a general license or permission to use these proprietary rights for implementers or users of this specification can be found through the IETF's online IPR repository at http://www.ietf.org/ipr.
The IETF invites anyone interested to bring to its attention any copyrights, patents or patent applications, or other proprietary rights that may cover technology needed to implement this standard. Send that info to the IETF at ietf-ipr@ietf.org ๐
๐ฆ where this page comes from
- ietf: rfc5225 2008-04
- ๐ girlified by claude-haiku-5-5 on : a language model's rewrite of the original, under its terms