RFC 5225

RObust Header Compression Version 2 (ROHCv2): Profiles for RTP, UDP, IP, ESP and UDP-Lite

๐Ÿ“… published April 2008, from ietf

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 ๐Ÿงš

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/or CRC-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_ref is a reference value of the scaled representation of the field.
  • a_n is the arrival time associated with the value to decode.
  • a_ref is the arrival time associated with the reference header.
  • tsc_ref_advanced is 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_stride value, and
  • it has not previously sent a CLOCK_RESOLUTION feedback 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 zeroes
  • ts_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:

  1. The complete representation of the list of CSRC identifiers is transmitted.

Then, once the context has been initialized:

  1. When the list is unchanged, a compressed header that does not contain information about the list can be used.
  2. 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 as pt_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 = ACK
    • 1 = NACK
    • 2 = STATIC-NACK
    • 3 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 43 EMail: ghyslain.pelletier@ericsson.com
  • Kristofer Sandlund Ericsson Box 920 Lulea SE-971 28 Sweden Phone: +46 (0) 8 404 41 58 EMail: 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