RFC 4996

RObust Header Compression (ROHC): A Profile for TCP/IP (ROHC-TCP)

📅 published July 2007, from ietf

RFC 4996: RObust Header Compression (ROHC): A Profile for TCP/IP (ROHC-TCP) 🎀

Okay bestie, let's start with the basics 💖. This one is RFC 4996, and it's called RObust Header Compression (ROHC): A Profile for TCP/IP (ROHC-TCP). It came out of the Network Working Group in July 2007 🌸. The authors are G. Pelletier, K. Sandlund, L-E. Jonsson, and M. West, and the affiliations listed are Ericsson and Siemens/Roke Manor. It's a Standards Track document, and the header doesn't list anything it obsoletes or updates ✨.

Status of This Memo 📜

Okay so this doc is an Internet standards track protocol for the whole Internet community, and it literally asks for discussion and suggestions for improvements 🫶. If you want to know where this protocol is in standardization, check the current edition of the "Internet Official Protocol Standards" (STD 1). And distribution of this memo is unlimited, so share away 🦋.

The copyright line is Copyright (C) The IETF Trust (2007). Keep it in your notes 💅.

Abstract 🌈

So the big idea is a ROHC (Robust Header Compression) profile for compressing TCP/IP packets 💜. It's called ROHC-TCP, and it gives you efficient AND robust compression of TCP headers. That includes the TCP options people actually use a lot, like SACK (Selective Acknowledgments) and Timestamps ⏱️.

ROHC-TCP works especially well over links with big error rates and long round-trip times 🌧️. A ton of bandwidth-limited links, where header compression is basically essential, have exactly those characteristics, so it's giving perfect match 👑.

Table of Contents 🗂️

1.  Introduction . . . . . . . . . . . . . . . . . . . . . . . . .  3
2.  Terminology  . . . . . . . . . . . . . . . . . . . . . . . . .  4
3.  Background . . . . . . . . . . . . . . . . . . . . . . . . . .  5
  3.1.  Existing TCP/IP Header Compression Schemes . . . . . . . .  5
  3.2.  Classification of TCP/IP Header Fields . . . . . . . . . .  6
4.  Overview of the TCP/IP Profile (Informative) . . . . . . . . .  8
  4.1.  General Concepts . . . . . . . . . . . . . . . . . . . . .  8
  4.2.  Compressor and Decompressor Interactions . . . . . . . . .  8
    4.2.1.  Compressor Operation . . . . . . . . . . . . . . . . .  8
    4.2.2.  Decompressor Feedback  . . . . . . . . . . . . . . . .  9
  4.3.  Packet Formats and Encoding Methods  . . . . . . . . . . .  9
    4.3.1.  Compressing TCP Options  . . . . . . . . . . . . . . . 10
    4.3.2.  Compressing Extension Headers  . . . . . . . . . . . . 10
  4.4.  Expected Compression Ratios with ROHC-TCP  . . . . . . . . 10
5.  Compressor and Decompressor Logic (Normative)  . . . . . . . . 11
  5.1.  Context Initialization . . . . . . . . . . . . . . . . . . 11
  5.2.  Compressor Operation . . . . . . . . . . . . . . . . . . . 11
    5.2.1.  Compression Logic  . . . . . . . . . . . . . . . . . . 11
    5.2.2.  Feedback Logic . . . . . . . . . . . . . . . . . . . . 13
    5.2.3.  Context Replication  . . . . . . . . . . . . . . . . . 14
  5.3.  Decompressor Operation . . . . . . . . . . . . . . . . . . 14
    5.3.1.  Decompressor States and Logic  . . . . . . . . . . . . 14
    5.3.2.  Feedback Logic . . . . . . . . . . . . . . . . . . . . 18
    5.3.3.  Context Replication  . . . . . . . . . . . . . . . . . 18
6.  Encodings in ROHC-TCP (Normative)  . . . . . . . . . . . . . . 18
  6.1.  Control Fields in ROHC-TCP . . . . . . . . . . . . . . . . 18
    6.1.1.  Master Sequence Number (MSN) . . . . . . . . . . . . . 19
    6.1.2.  IP-ID Behavior . . . . . . . . . . . . . . . . . . . . 19
    6.1.3.  Explicit Congestion Notification (ECN) . . . . . . . . 20
  6.2.  Compressed Header Chains . . . . . . . . . . . . . . . . . 21
  6.3.  Compressing TCP Options with List Compression  . . . . . . 23
    6.3.1.  List Compression . . . . . . . . . . . . . . . . . . . 23
    6.3.2.  Table-Based Item Compression . . . . . . . . . . . . . 24
    6.3.3.  Encoding of Compressed Lists . . . . . . . . . . . . . 25
    6.3.4.  Item Table Mappings  . . . . . . . . . . . . . . . . . 26
    6.3.5.  Compressed Lists in Dynamic Chain  . . . . . . . . . . 28
    6.3.6.  Irregular Chain Items for TCP Options  . . . . . . . . 28
    6.3.7.  Replication of TCP Options . . . . . . . . . . . . . . 28
  6.4.  Profile-Specific Encoding Methods  . . . . . . . . . . . . 29
    6.4.1.  inferred_ip_v4_header_checksum . . . . . . . . . . . . 29
    6.4.2.  inferred_mine_header_checksum  . . . . . . . . . . . . 30
    6.4.3.  inferred_ip_v4_length  . . . . . . . . . . . . . . . . 30
    6.4.4.  inferred_ip_v6_length  . . . . . . . . . . . . . . . . 31
    6.4.5.  inferred_offset  . . . . . . . . . . . . . . . . . . . 31
    6.4.6.  baseheader_extension_headers . . . . . . . . . . . . . 31
    6.4.7.  baseheader_outer_headers . . . . . . . . . . . . . . . 32
    6.4.8.  Scaled Encoding of Fields  . . . . . . . . . . . . . . 32
  6.5.  Encoding Methods With External Parameters  . . . . . . . . 34
7.  Packet Types (Normative) . . . . . . . . . . . . . . . . . . . 36
  7.1.  Initialization and Refresh (IR) Packets  . . . . . . . . . 36
  7.2.  Context Replication (IR-CR) Packets  . . . . . . . . . . . 38
  7.3.  Compressed (CO) Packets  . . . . . . . . . . . . . . . . . 41
8.  Header Formats (Normative) . . . . . . . . . . . . . . . . . . 42
  8.1.  Design Rationale for Compressed Base Headers . . . . . . . 42
  8.2.  Formal Definition of Header Formats  . . . . . . . . . . . 45
  8.3.  Feedback Formats and Options . . . . . . . . . . . . . . . 86
    8.3.1.  Feedback Formats . . . . . . . . . . . . . . . . . . . 86
    8.3.2.  Feedback Options . . . . . . . . . . . . . . . . . . . 87
9.  Security Considerations  . . . . . . . . . . . . . . . . . . . 89
10. IANA Considerations  . . . . . . . . . . . . . . . . . . . . . 89
11. Acknowledgments  . . . . . . . . . . . . . . . . . . . . . . . 90
12. References . . . . . . . . . . . . . . . . . . . . . . . . . . 90
  12.1. Normative References . . . . . . . . . . . . . . . . . . . 90
  12.2. Informative References . . . . . . . . . . . . . . . . . . 91

1. Introduction ✨

Okay so why bother compressing headers on low- or medium-speed links for TCP/IP traffic? That's already been covered in [RFC2507] 💖. Then [RFC4163] explains why robustness is such a big deal for a TCP compression scheme 🛡️. The existing TCP/IP header compression schemes ([RFC1144], [RFC2507]) have a few problems, though. They're limited in how they handle the TCP options field, and they can't compress the headers of handshaking packets (SYNs and FINs) 😭.

So the wish list is pretty clear. A header compression scheme should be able to handle loss on the link between the compression and decompression points, and loss before the compression point too 🔥. It also needs to handle short-lived TCP transfers efficiently, along with TCP options like SACK ([RFC2018], [RFC2883]) and Timestamps ([RFC1323]) 🦋.

The ROHC WG built a header compression framework that different profiles can sit on top of, for different protocol sets or different compression strategies 🧚. This document defines a TCP/IP compression profile for that ROHC framework [RFC4995], and it's compliant with the requirements listed in [RFC4163] 💅.

Specifically, it describes ROHC-TCP, a header compression scheme that's robust against packet loss. It also has enhanced capabilities, especially for compressing header fields including TCP options 🌈. The profile identifier for TCP/IP compression is 0x0006 🎀.

2. Terminology 🌸

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC2119] 💜.

This document reuses some of the terminology from [RFC4995]. On top of that, it uses or defines the following terms 🫶:

Base context

The base context is a context that's been validated by both the compressor and the decompressor 💖. A base context can be used as the reference when you build a new context with replication 🔥.

Base Context Identifier (Base CID)

The Base CID is the CID that identifies the base context, and it's where you can pull the info needed for context replication 🎀.

Base header

This is a compressed representation of the innermost IP and TCP headers of the uncompressed packet 🌟.

Chaining of items

A chain groups fields based on similar characteristics 💅. ROHC-TCP defines chain items for static, dynamic, replicable, or irregular fields 🧩. Chaining works by appending an item for each header, e.g., to the chain, in the order they appear in the uncompressed packet 📋. It's super useful for building compressed headers out of an arbitrary number of any of the protocol headers that ROHC-TCP defines a compressed format for 👑.

Context Replication (CR)

Context replication is the mechanism that establishes and initializes a new context based on another existing valid context, which is the base context 🦋. It exists to cut down the overhead of the context establishment procedure. It's especially useful for compressing multiple short-lived TCP connections that happen at the same time or really close together 🌧️.

ROHC-TCP packet types

ROHC-TCP uses three packet types: the Initialization and Refresh (IR) packet type, the Context Replication (IR-CR) packet type, and the Compressed (CO) packet type ✨.

Short-lived TCP transfer

These are TCP connections that only transmit small amounts of packets for each single connection 🍓.

3. Background 🧚

This section gives some background on TCP/IP header compression 💖. The fundamentals of general header compression are in [RFC4995] 📚. The following subsections first describe two existing TCP/IP header compression schemes and talk about their limitations 🙈. Then they classify the TCP/IP header fields, and finally they summarize some of the characteristics of short-lived TCP transfers 💜.

A behavior analysis of TCP/IP header fields is in [RFC4413] 🔍.

3.1. Existing TCP/IP Header Compression Schemes 🎀

okay so bestie, there are already two schemes that squish TCP/IP headers down: Compressed TCP (CTCP) and IP Header Compression (IPHC) 🦋 Both are giving the same energy. They only send the differences from the previous header, which is how the header gets way smaller 💖

CTCP [RFC1144] notices when transport-level retransmissions happen, and when they do, it sends a header that fully updates the context 🔄 It works great on reliable links, but ngl it's kinda fragile on less reliable ones 💀 Even a single lost packet knocks the compressor and decompressor out of sync. The TCP receiver then throws away every remaining packet in the current window because of a checksum error 😭 That means TCP's fast retransmit algorithm [RFC2581] never gets triggered. So the compressor just has to sit there until TCP times out and retransmits a packet to resync 🥲

IPHC [RFC2507] is the glow-up 💅 It improves on CTCP when it comes to the inconsistent contexts between compressor and decompressor. It does this by adding a local repair mechanism called TWICE on top of CTCP's repair mechanism, plus a link-layer mechanism that uses negative acknowledgments to ask for a header that updates the context 🌸

The TWICE algorithm assumes that only the Sequence Number field of TCP segments is changing, and that the deltas between consecutive packets are constant in most cases. Plot twist: that assumption isn't always true, especially when TCP Timestamps and SACK options are in use 🙈

The full header request mechanism needs a feedback channel, and that channel might be unavailable in some situations 🫠 It's used to explicitly ask for the next packet to go out with an uncompressed header, so resync can happen without waiting for a TCP timeout. But it doesn't perform well on links with long round-trip times, so it's not always the move 🌍

Both CTCP and IPHC are kinda limited when it comes to the TCP options field 😤 For IPHC, any change in the options field (caused by Timestamps or SACK, for example) makes the entire field uncompressible. For CTCP, a change like that effectively disables TCP/IP header compression altogether 💀

Finally, the existing TCP/IP compression schemes don't compress the headers of handshaking packets (SYNs and FINs) 🤧 Compressing those could really boost the overall header compression ratio when lots of short-lived TCP connections share the same channel 🚀


3.2. Classification of TCP/IP Header Fields 🦋

Header compression works because there's a ton of redundancy between header field values inside packets, especially between consecutive ones 👯 To use that for TCP/IP header compression, we need to understand how the various header fields change over time 🧠

[RFC4413] classified every field of the TCP/IP packet header in detail. The big takeaway? Most of the fields can be compressed away easily, since they seldom or never change 💖 These fields need fancier mechanisms, though:

     - IPv4 Identification       (16 bits) - IP-ID
     - TCP Sequence Number       (32 bits) - SN
     - TCP Acknowledgment Number (32 bits)
     - TCP Reserved              ( 4 bits)
     - TCP ECN flags             ( 2 bits) - ECN
     - TCP Window                (16 bits)
     - TCP Options
       o  Maximum Segment Size   (32 bits) - MSS
       o  Window Scale           (24 bits) - WSCALE
       o  SACK Permitted         (16 bits)
       o  TCP SACK               (80, 144, 208, or 272 bits) - SACK
       o  TCP Timestamp          (80 bits) - TS

IP-ID values can be assigned in a few ways, usually sequential, sequential jump, or random, as described in Section 4.1.3 of [RFC4413] 🎲 Some IPv4 stacks use sequential assignment but don't send this field in network byte order. Instead, the two octets get sent reversed 🙃 In that case, the compressor can compress the IP-ID field after swapping the bytes. The decompressor then swaps the bytes of the IP-ID after decompression to regenerate the original IP-ID. For TCP compression, [RFC4413] found no obvious candidate among the TCP fields that's suitable for inferring the IP-ID 🤷

The change pattern of several TCP fields (Sequence Number, Acknowledgment Number, Window, etc.) is very hard to predict 😵 The sequence and acknowledgment numbers are especially important to understand for a TCP/IP header compression scheme [RFC4413].

The TCP Sequence Number can be anywhere within a range defined by the TCP Window, at any point on the path (wherever a compressor might be deployed). Missing packets or retransmissions can make the Sequence Number fluctuate within the limits of that window 🌊 The TCP Window also bounds the jumps in the acknowledgment number.

Another important behavior is the dependency between the sequence number and the acknowledgment number. TCP connections can be near-symmetrical or show a strong asymmetrical bias with respect to data traffic. In the asymmetrical case, the connections mainly have one-way traffic (Web browsing and file downloading, for example) 📥 On the forward path (from server to client), only the sequence number changes while the acknowledgment number stays constant for most packets. On the backward path (from client to server), only the acknowledgment number changes while the sequence number stays constant for most packets 🔁 So a compression scheme for TCP should have packet formats suitable for either case: packet formats that can carry only sequence number bits, only acknowledgment number bits, or both 🎀

Short-lived TCP transfers are a problem too 😩 They degrade the performance of header compression schemes that establish a new context by initially sending full headers. Multiple simultaneous or near-simultaneous TCP connections may look super similar in header field values and context values, which makes it possible to reuse information between flows when initializing a new context ♻️ Context replication [RFC4164] does exactly that. It makes context establishment faster and more efficient by replicating part of an existing context to a new flow ⚡ The conclusion from [RFC4413] is that part of the IP sub-context, some TCP fields, and some context values can be replicated, since they seldom change or change with only a small jump.

ROHC-TCP also compresses these headers:

  • IPv6 Destination Options header [RFC2460]
  • IPv6 Routing header [RFC2460]
  • IPv6 Hop-by-Hop Options header [RFC2460]
  • Authentication Header (AH) [RFC4302]
  • NULL-encrypted Encapsulating Security Payload (ESP) header [RFC4303]
  • Generic Routing Encapsulation (GRE) [RFC2784][RFC2890]
  • Minimal Encapsulation header (MINE) [RFC2004]

Headers specific to Mobile IP (for IPv4 or IPv6) don't get any special treatment in this document, for reasons similar to those described in [RFC3095] 🙅

4. Overview of the TCP/IP Profile (Informative) ✨

4.1. General Concepts 🌸

ROHC-TCP uses the ROHC protocol as described in [RFC4995]. It supports context replication as defined in [RFC4164]. Context replication can be especially handy for short-lived TCP flows [RFC4413] 💜

4.2. Compressor and Decompressor Interactions 🧚

4.2.1. Compressor Operation

Header compression with ROHC can be conceptually characterized as the interaction of a compressor with a decompressor state machine 🎡 The compressor's task is to minimally send the information needed to successfully decompress a packet, based on a certain confidence regarding the state of the decompressor context.

For ROHC-TCP compression, the compressor normally starts out assuming the decompressor has no useful information to process the new flow, so it sends Initialization and Refresh (IR) packets 🌱 Alternatively, the compressor may also support Context Replication (CR) and use IR-CR packets [RFC4164], which attempt to reuse context information related to another flow.

The compressor can then adjust the compression level based on its confidence that the decompressor has the information it needs to process the Compressed (CO) packets it selects 🎯 In other words, the compressor's task is to get the decompressor into a state that allows decompression of the most efficient CO packet(s), and to let the decompressor move to that state as soon as possible otherwise.

4.2.2. Decompressor Feedback 📣

The ROHC-TCP profile can be used in environments with or without feedback capabilities from decompressor to compressor 🔙 ROHC-TCP does assume something, though: if a ROHC feedback channel is available, and the decompressor uses it at least once for a specific ROHC-TCP context, then that channel will be used during the entire compression operation for that context. If the feedback channel disappears, compression should be restarted 🔄

Receiving either positive acknowledgments (ACKs) or negative acknowledgments (NACKs) establishes the feedback channel from the decompressor for the context the feedback was received for 🙌 Once there is an established feedback channel for a specific context, the compressor should make use of this feedback to estimate the current state of the decompressor. This helps boost compression efficiency by providing the information the compressor needs to reach the necessary confidence level 📈

The ROHC-TCP feedback mechanism is limited in its applicability by the number of (least significant bit (LSB) encoded) master sequence number (MSN) (see Section 6.1.1) bits used in the FEEDBACK-2 format (see Section 8.3). It is not suitable for a decompressor to use feedback altogether where the MSN bits in the feedback could wrap around within one round-trip time. Instead, unidirectional operation -- where the compressor periodically sends larger context-updating packets -- is more appropriate 🚫

4.3. Packet Formats and Encoding Methods 📦

The packet formats and encoding methods used for ROHC-TCP are defined using the formal notation [RFC4997]. The formal notation provides an unambiguous representation of the packet formats and a clear definition of the encoding methods 🧁

4.3.1. Compressing TCP Options 🎀

The TCP options in ROHC-TCP are compressed using a list compression encoding. This lets option content be established so that TCP options can be added to the context without having to send all TCP options uncompressed ✨

4.3.2. Compressing Extension Headers 🧩

ROHC-TCP compresses the extension headers listed in Section 3.2 💅 These headers are treated exactly like other headers, so they have a static chain, a dynamic chain, an irregular chain, and a chain for context replication (Section 6.2).

This means that headers appearing in or disappearing from the flow being compressed will lead to changes to the static chain. However, the change pattern of extension headers is not deemed to impair compression efficiency with respect to this design strategy 🌈✨

4.4. Expected Compression Ratios with ROHC-TCP ✨

okay so this part is the ratio tea 🍵 and it's giving receipts. The table shows typical compression ratios you can expect when using ROHC-TCP and IPHC [RFC2507] 💅

Ngl the numbers only hold if the compression context is already properly initialized, so the table assumes that's done. For the TS option, the Timestamp is assumed to change with small values. All TCP options include a suitable number of No Operation (NOP) options [RFC0793] for padding and/or alignment. And for the IPv4 examples, a sequential IP-ID behavior is assumed 🌸

                         Total Header Size (octets)
                          ROHC-TCP          IPHC
                 Unc.   DATA    ACK      DATA    ACK
IPv4+TCP+TS       52       8      8        18     18
IPv4+TCP+TS       52       7      6        16     16   (1)
IPv6+TCP+TS       72       8      7        18     18
IPv6+TCP+no opt   60       6      5         6      6
IPv6+TCP+SACK     80       -     15         -     80   (2)
IPv6+TCP+SACK     80       -      9         -     26   (3)
  • (1) The payload size of the data stream is constant.
  • (2) The SACK option appears in the header, but was not present in the previous packet. Two SACK blocks are assumed.
  • (3) The SACK option appears in the header, and was also present in the previous packet (with different SACK blocks). Two SACK blocks are assumed.

The next table is the initial compression ratios, aka the first-impression glow-up ✨ The data stream is assumed to be IPv4+TCP, with a sequential behavior for the IP-ID. The SYN packet is assumed to carry the TS, MSS, and WSCALE options, plus an appropriate number of NOP options 🎀

                   Total Header Size (octets)
                    Unc.   ROHC-TCP   IPHC
1st packet (SYN)     60      49        60
2nd packet           52      12        52

Okay and the reason the second packet looks so good is that the figures assume the compressor has already received an acknowledgment from the decompressor before compressing that second packet. That's expected when feedback is used in ROHC-TCP 🦋 It works out because in the most common case the TCP ACKs take the same return path, and TCP doesn't send more packets until the TCP SYN packet has been acknowledged 🫶


5. Compressor and Decompressor Logic (Normative) 👑

this is the main character section, bestie 😭 Everything here is normative, so implementers are gonna be reading every word.

5.1. Context Initialization

The static context of ROHC-TCP flows can be initialized in either of two ways:

  1. By using an IR packet as in Section 7.1, where the profile number is 0x06 and the static chain ends with the static part of a TCP header.
  2. By replicating an existing context using the mechanism defined by [RFC4164]. This is done with the IR-CR packet defined in Section 7.2, where the profile number is 0x06.

5.2. Compressor Operation

5.2.1. Compression Logic

okay so the compressor's whole job is figuring out what data has to be sent for each TCP/IP packet, so the decompressor can rebuild the original packet from its current state 🧚 The type of compressed header it picks depends on a few things:

  • The change behavior of header fields in the flow, e.g., conveying the necessary information within the restrictions of the set of available packet formats.
  • The compressor's level of confidence regarding decompressor state, e.g., by selecting header formats updating the same type of information for a number of consecutive packets or from the reception of decompressor feedback (ACKs and/or NACKs).
  • Additional robustness required for the flow, e.g., periodic refreshes of static and dynamic information using IR and IR-DYN packets when decompressor feedback is not expected.

The next subsections go deeper on how each of these affects packet type selection 🌈

In this section, a "higher compression state" means that less data will be sent in compressed packets, i.e., smaller compressed headers are used, while a lower compression state means that a larger amount of data will be sent using larger compressed headers 💜

5.2.1.1. Optimistic Approach

The optimistic approach is the principle by which a compressor sends the same type of information for a number of packets (consecutively or not) until it is fairly confident that the decompressor has received the information. It's the robustness move for when ROHC-TCP is used to compress packets over lossy links 🛡️

So if field X in the uncompressed packet changes value, the compressor MUST use a packet type that contains an encoding for field X until it has gained confidence that the decompressor has received at least one packet containing the new value for X. The compressor SHOULD choose a compressed format with the smallest header that can convey the changes needed to fulfill the optimistic approach condition used. 🍓

5.2.1.2. Periodic Context Refreshes

When the optimistic approach is used, there will always be a possibility of decompression failures, since the decompressor may not have received sufficient information for correct decompression. Not ideal, but that's the tradeoff 😭

So until the decompressor has established a feedback channel, the compressor SHOULD periodically move to a lower compression state and send IR and/or IR-DYN packets. These refreshes can be based on timeouts, on the number of compressed packets sent for the flow, or any other strategy specific to the implementation. Once the feedback channel is established, the decompressor MAY stop performing periodic refreshes 🎀

5.2.2. Feedback Logic

The semantics of feedback messages, acknowledgments (ACKs) and negative acknowledgments (NACKs or STATIC-NACKs), are defined in Section 5.2.4.1 of [RFC4995] 🦋

5.2.2.1. Optional Acknowledgments (ACKs)

The compressor MAY use acknowledgment feedback (ACKs) to move to a higher compression state.

When it gets an ACK for a context-updating packet, the compressor gains confidence that the decompressor has received the acknowledged packet and that it has observed changes in the packet flow up to the acknowledged packet ✨

This functionality is optional, so a compressor MUST NOT expect to get such ACKs, even if a feedback channel is available and has been established for that flow. Basically, don't count on the ACKs showing up, bestie 🙈

5.2.2.2. Negative Acknowledgments (NACKs)

The compressor uses feedback from the decompressor to move to a lower compression state (NACKs) 📉

On reception of a NACK feedback, the compressor SHOULD:

  • assume that only the static part of the decompressor is valid, and
  • re-send all dynamic information (via an IR or IR-DYN packet) the next time it compresses a packet for the indicated flow

unless it has confidence that information sent after the packet being acknowledged already provides a suitable response to the NACK feedback. In addition, the compressor MAY use a CO packet carrying a 7-bit Cyclic Redundancy Check (CRC) if it can determine with enough confidence what information provides a suitable response to the NACK feedback 🔥

On reception of a STATIC-NACK feedback, the compressor SHOULD:

  • assume that the decompressor has no valid context, and
  • re-send all static and all dynamic information (via an IR packet) the next time it compresses a packet for the indicated flow

unless it has confidence that information sent after the packet that is being acknowledged already provides a suitable response to the STATIC-NACK feedback 💀

5.2.3. Context Replication

A compressor MAY support context replication by implementing the additional compression and feedback logic defined in [RFC4164] 🧚

5.3. Decompressor Operation

5.3.1. Decompressor States and Logic

okay the decompressor has three states: No Context (NC), Static Context (SC), and Full Context (FC) 👑 It starts in its lowest compression state, the NC state. Successful decompression will always move the decompressor to the FC state. Once it's in FC, the decompressor state machine normally never leaves it. Only repeated decompression failures will force the decompressor to transit downwards to a lower state 😭

Here's the state machine for the decompressor. The details of the transitions between states and the decompression logic are in the subsections after the figure 🌸

                             Success
            +-->------>------>------>------>------>--+
            |                                        |
No Static   |            No Dynamic        Success   |    Success
 +-->--+    |             +-->--+      +--->----->---+    +-->--+
 |     |    |             |     |      |             |    |     |
 |     v    |             |     v      |             v    |     v
+-----------------+   +---------------------+   +-------------------+
| No Context (NC) |   | Static Context (SC) |   | Full Context (FC) |
+-----------------+   +---------------------+   +-------------------+
  ^                         |        ^                         |
  |  Static Context         |        | Context Damage Assumed  |
  |  Damage Assumed         |        |                         |
  +-----<------<------<-----+        +-----<------<------<-----+

5.3.1.1. Reconstruction and Verification

When decompressing an IR or an IR-DYN packet, the decompressor MUST validate the integrity of the received header using CRC-8 validation [RFC4995]. If validation fails, the packet MUST NOT be delivered to upper layers. 🚫

Upon receiving an IR-CR packet, the decompressor MUST perform the actions as specified in [RFC4164].

When decompressing other packet types (e.g., CO packets), the decompressor MUST validate the outcome of the decompression attempt using CRC verification [RFC4995]. If verification fails, a decompressor implementation MAY attempt corrective or repair measures on the packet, and the result of any attempt MUST be validated using the CRC verification; otherwise, the packet MUST NOT be delivered to upper layers. 🛠️

When the CRC-8 validation or the CRC verification of the received header is successful, the decompressor SHOULD update its context with the information received in the current header; the decompressor then passes the reconstructed packet to the system's network layer. Otherwise, the decompressor context MUST NOT be updated. ✨

If the received packet is older than the current reference packet, e.g., based on the master sequence number (MSN) in the compressed packet, the decompressor MAY refrain from updating the context using the information received in the current packet, even if the correctness of its header was successfully verified 🥹

5.3.1.2. Detecting Context Damage ✨

okay so every header format has a CRC and they're all context updating 🔥 bestie, when a packet's CRC checks out, it updates the reference values of every header field. Some get updated explicitly from the info in the compressed header, and others get inferred from other fields 💅

the decompressor can assume that some or all of the context is invalid after one or more failures to validate or verify a header with the CRC 💀 It can't know the exact reason for a CRC failure or which field caused it, so "valid context" isn't about which exact context entry is good or bad 🙈

validity is really about spotting that something is wrong with the context 🎀 The decompressor first assumes the culprit is the stuff that changes every packet, so that's damage to the dynamic part of the context. If repeated failures and failed repairs keep happening, it then assumes the whole context needs fixing, including the static part. That's static context damage 😭

Context Damage Detection

The assumption of context damage means the decompressor won't try to decompress a CO header that carries a 3-bit CRC. It will only try IR, IR-DYN, or IR-CR headers, or CO headers protected by a CRC-7 🦋

Static Context Damage Detection

The assumption of static context damage means the decompressor holds off on decompressing any header type except the IR header 🧚

how it decides these assumptions are made, aka how damage gets detected, is up to each implementation 🌸 It could be based on the residual error rate. A low error rate means it assumes damage more often than it would on a high-rate link ✨

the decompressor puts these assumptions into action by choosing which compressed header types it's allowed to attempt. So validity of the context really comes down to whether the decompressor tries (or doesn't try) to decompress specific packet types 👑


5.3.1.3. No Context (NC) State 🌈

initially, while the decompressor is in the No Context (NC) state, it hasn't decompressed a single packet successfully yet 🍓

Allowing decompression:

In the NC state, only packets with enough static field info (IR and IR-CR packets) can be decompressed. Anything else MUST NOT be decompressed and MUST NOT be delivered to upper layers 🙅‍♀️

Feedback logic:

In the NC state, the decompressor should send a STATIC-NACK if it gets a packet that isn't an IR packet, or if decompressing an IR packet failed. This is subject to the feedback rate limitation in Section 5.3.2 💖

once a packet has been validated and decompressed correctly, the decompressor MUST transit to the FC state 🎉

5.3.1.4. Static Context (SC) State 🫶

when the decompressor is in the Static Context (SC) state, only the static part of its context is valid 🧸

it moves back to the NC state if static context damage is detected 💔

Allowing decompression:

In the SC state, packets with enough info on the dynamic fields covered by an 8-bit CRC (like IR and IR-DYN), or CO packets covered by a 7-bit CRC, can be decompressed. Anything else MUST NOT be decompressed and MUST NOT be delivered to upper layers 🚫

Feedback logic:

In the SC state, the decompressor should send a STATIC-NACK if CRC validation of an IR, IR-DYN, or IR-CR fails and static context damage is assumed. If any other packet type shows up, the decompressor should send a NACK. Both of these cases are subject to the feedback rate limitation in Section 5.3.2 ✨

once a packet has been validated and decompressed correctly, the decompressor MUST transit to the FC state 🥳

5.3.1.5. Full Context (FC) State 👑

in the Full Context (FC) state, both the static and dynamic parts of the context are valid. It moves back to the SC state if context damage is detected 🌟

Allowing decompression:

in the FC state, decompression can be attempted no matter what type of packet comes in 🦄

Feedback logic:

in the FC state, the decompressor should send a NACK if decompressing any packet type fails and context damage is assumed. This is subject to the feedback rate limitation in Section 5.3.2 🔥


5.3.2. Feedback Logic 💌

the decompressor MAY send positive feedback (ACKs) to get the feedback channel set up for a particular flow. Either positive feedback (ACKs) or negative feedback (NACKs) sets up this channel 🎀

once the feedback channel is up, the decompressor is REQUIRED to keep sending NACKs or STATIC-NACKs for as long as the context stays associated with the same profile. In this case that's profile 0x0006, following the logic for each state in Section 5.3.1 🫶

the decompressor MAY send ACKs after successfully decompressing any packet type. Especially when a packet with a significant context update decompresses correctly, the decompressor MAY send an ACK 🦋

the decompressor should limit how fast it sends feedback, for both ACKs and STATIC-NACK/NACKs. It should also avoid sending unnecessary duplicates of the same type of feedback message for the same event 💅

5.3.3. Context Replication 🧚

ROHC-TCP supports context replication, so the decompressor MUST implement the additional decompressor and feedback logic defined in [RFC4164] 💜


6. Encodings in ROHC-TCP (Normative) 🍓

6.1. Control Fields in ROHC-TCP 🌸

in ROHC-TCP, a bunch of control fields are used by the decompressor to interpret the format of packets it gets from the compressor ✨

a control field is sent from the compressor to the decompressor, but it isn't part of the uncompressed header 🎀 Its values can be set up in the context of both the compressor and the decompressor. Once they're established at the decompressor, the values should be kept until another packet updates them 💖

6.1.1. Master Sequence Number (MSN) 🫧

there's no field in the TCP header that can act as the master sequence number for TCP compression, as explained in [RFC4413], Section 5.6 😭

so ROHC-TCP introduces a control field called the Master Sequence Number (MSN) field. The MSN field is made at the compressor instead of using one of the fields already in the uncompressed header. The compressor increments the MSN by one for every packet it sends 🦋

the MSN field has these two jobs:

  1. Telling packets apart when sending feedback data.
  2. Inferring the value of incrementing fields such as the IP-ID.

the MSN field is in every packet the compressor sends. The MSN is LSB encoded inside CO packets, and the 16-bit MSN is sent in full in IR/IR-DYN packets. The decompressor always sends the MSN as part of its feedback info, and the compressor can later use the MSN to figure out which packet the decompressor is acknowledging 💅

when the MSN is initialized, it SHOULD be initialized to a random value 🎲 The compressor should only initialize a new MSN for the initial IR or IR-CR packet sent for a CID that matches a context not already tied to this profile. In other words, if the compressor reuses the same CID to compress many TCP flows one after another, the MSN doesn't get reinitialized. It just keeps incrementing monotonically 🌈

for context replication, the compressor doesn't use the MSN of the base context when sending the IR-CR packet, unless the replication process overwrites the base context (aka Base CID == CID) 🙈 Instead, the compressor uses the MSN value if it already exists in the ROHC-TCP context being associated with the new flow (CID). Otherwise, the MSN gets initialized to a new value 💜

6.1.2. IP-ID Behavior 🌷

the IP-ID field of the IPv4 header can follow different change patterns 🧸 Basically, a compressor watches how the IP-ID value changes and picks encoding methods and packet formats that match the pattern it sees best ✨

ROHC-TCP defines different compression techniques for the IP-ID so it can handle whatever behavior shows up: sequential in network byte order (NBO), sequential byte-swapped, random (RND), or constant to a value of zero 🎀

the compressor watches the IP-ID value over a number of packets to figure out which of those alternatives is the closest match to the pattern it sees. Then it can select packet formats and encoding methods based on that behavior 🦋

if more than one level of IP headers is present, ROHC-TCP can assign a sequential behavior (NBO or byte-swapped) only to the IP-ID of the innermost IP header 💅 That's because only this IP-ID can have a close enough correlation with the MSN (see also Section 6.1.1) to compress it as a sequentially changing field. So a compressor MUST NOT assign either the sequential (NBO) or the sequential byte-swapped behavior to tunneling headers 🙅‍♀️

the control field for IP-ID behavior decides which set of packet formats gets used. These control fields also determine what goes in the irregular chain item (see Section 6.2) for each IP header 🔥

6.1.3. Explicit Congestion Notification (ECN) 🌩️

when ECN [RFC3168] is used on a flow, the ECN bits can change pretty often 😭 ROHC-TCP keeps a control field in the context that says whether ECN is used or not. This control field is sent in the dynamic chain of the TCP header, and its value can be updated using specific compressed headers carrying a 7-bit CRC 💖

when this control field says ECN is in use, the items of all IP and TCP headers in the irregular chain include bits for ECN. To keep things octet-aligned, all of the TCP reserved bits get sent, and for outer IP headers, the entire Type of Service/Traffic Class (TOS/TC) field goes into the irregular chain 🍓 When there's only one IP header in the packet (aka no IP tunneling), this lets the compressor handle ECN bit changes by adding a single octet to the compressed header ✨

the reason the ECN bits of all IP headers go into the compressed packet when the control field is set is that the profile needs to compress flows with IP tunnels efficiently using the "full-functionality option" of Section 9.1 of [RFC3168]. For these flows, a change in the ECN bits of an inner IP header gets propagated to the outer IP headers 🧚 When the "limited-functionality" option is used, the compressor will sometimes send one octet more than necessary per tunnel header. The design team considered that a reasonable tradeoff for this profile 🫶

6.2. Compressed Header Chains 🔗✨

Okay bestie, so some packet types carry one or more chains full of sub-header info 🎀. A chain groups fields that act alike, like static, dynamic, or irregular ones 🧩. You build it by adding one item per header, in the order those headers appear in the uncompressed packet, starting from the outermost header. It's giving organized 💅.

Every header that ROHC-TCP compresses gets its own chain. Here's the lineup, with the encoding method name for each one 🌸:

  • TCP [RFC0793], encoding method: "tcp"
  • IPv4 [RFC0791], encoding method: "ipv4"
  • IPv6 [RFC2460], encoding method: "ipv6"
  • AH [RFC4302], encoding method: "ah"
  • GRE [RFC2784][RFC2890], encoding method: "gre"
  • MINE [RFC2004], encoding method: "mine"
  • NULL-encrypted ESP [RFC4303], encoding method: "esp_null"
  • IPv6 Destination Options header [RFC2460], encoding method: "ip_dest_opt"
  • IPv6 Hop-by-Hop Options header [RFC2460], encoding method: "ip_hop_opt"
  • IPv6 Routing header [RFC2460], encoding method: "ip_rout_opt"

Static chain 🧊: This chain has one item per header in the protocol header chain being compressed, starting at the outermost IP header and ending at the TCP header. In the formal packet format descriptions, each header's static chain item is a format whose name ends in "_static". The static chain is only used in IR packets.

Dynamic chain 💫: This chain also has one item per header, from the outermost IP header down to TCP. The dynamic chain item for the TCP header also holds a compressed list of TCP options (see Section 6.3). In the formal descriptions, each header type's dynamic item is a format whose name ends in "_dynamic". The dynamic chain is used in both IR and IR-DYN packets.

Replicate chain 🪞: One item per header again, outermost IP to TCP. The replicate chain item for TCP also contains a compressed list of TCP options (see Section 6.3). In the formal descriptions, each header type's replicate item is a format whose name ends in "_replicate". Header fields that aren't present in the replicate chain get replicated from the base context. The replicate chain is only used in the IR-CR packet.

Irregular chain 🌀: This one is structured like the static chain. For each compressed packet, the irregular chain gets appended at the specified spot in the general format of compressed packets, as defined in Section 7.3. It also includes the irregular chain items for TCP options, as defined in Section 6.3.6. Those go right after the TCP header's irregular item, in the same order the options appear in the uncompressed packet. In the formal descriptions, each header type's irregular item is a format whose name ends in "_irregular". The irregular chain is used only in CO packets.

The irregular chain format for the innermost IP header is different from the outer IP headers, because that header is part of the compressed base header. Plot twist, but it makes sense 🙈.


6.3. Compressing TCP Options with List Compression 📜

This section breaks down exactly how list compression gets applied to TCP options 💖. In the ROHC-TCP packet format definitions, the most common TCP options each have one encoding method, listed in the table below:

+-----------------+------------------------+
|   Option name   |  Encoding method name  |
+-----------------+------------------------+
|      NOP        | tcp_opt_nop            |
|      EOL        | tcp_opt_eol            |
|      MSS        | tcp_opt_mss            |
|  WINDOW SCALE   | tcp_opt_wscale         |
|   TIMESTAMP     | tcp_opt_ts             |
| SACK-PERMITTED  | tcp_opt_sack_permitted |
|      SACK       | tcp_opt_sack           |
| Generic options | tcp_opt_generic        |
+-----------------+------------------------+

Each of these encoding methods comes with three formats: an uncompressed one, one suffixed by "_list_item", and one suffixed by "_irregular". Sometimes one encoding method has several "_list_item" or "_irregular" formats, and then the bindings inside those formats decide which one gets used. There's more on that in the sections that follow 🎀.

6.3.1. List Compression 🧃

The TCP options in an uncompressed packet can be seen as an ordered list. Usually the order and presence stay the same from packet to packet. Here's the general shape of that list:

      +--------+--------+--...--+--------+
list: | item 1 | item 2 |       | item n |
      +--------+--------+--...--+--------+

To squish this list down, ROHC-TCP uses a list compression scheme. It compresses each item on its own, then combines them into one compressed list 🪄. The core principles go like this:

  1. When a context is first being set up, a complete representation of the compressed list of options gets sent. Every option that has any content is included in the compressed list the compressor sends.

Then, once the context is initialized, the vibes shift 🌈:

  1. When the structure AND the content of the list stay the same, no information about the list gets sent in compressed headers. Lowkey the best case ✨.
  2. When the list's structure stays constant, and only the content defined in the irregular format of one or more options changes, no information about the list needs to go in the compressed base header. The irregular content rides along as part of the irregular chain instead, as described in Section 6.3.6.
  3. When the list's structure changes, a compressed list is sent in the compressed base header. It includes a representation of the structure and order. Content defined in an option's irregular format can still be sent as part of the irregular chain (as described in Section 6.3.6), as long as that item's content isn't part of the compressed list.

6.3.2. Table-Based Item Compression 🗂️

Table-based item compression squishes the individual items that get sent in compressed lists 💅. The compressor gives every item a unique identifier, called "Index", for each "Item" in a list.

Compressor Logic 🧠: The compressor conceptually keeps an item table holding all the items, indexed by "Index". The (Index, Item) pair keeps getting sent in compressed lists until the compressor feels confident the decompressor has seen the mapping between each item and its index. That confidence comes either from an acknowledgment from the decompressor, or from sending (Index, Item) pairs using the optimistic approach. Once confidence is there, the index alone gets sent in compressed lists to say "this item is present" 🦋.

The compressor may reassign an existing index to a new item, by re-establishing the mapping using the procedure described above. Plot twist: indexes can be recycled 🔄.

Decompressor Logic 🧩: The decompressor conceptually keeps an item table holding every (Index, Item) pair it has received. The item table gets updated whenever an (Index, Item) pair arrives and decompression is successfully verified using the CRC. When an index shows up without an accompanying item, the decompressor pulls the item out of the table 🫶.

If an index without an accompanying item is received and the decompressor does not have any context for this index, the header MUST be discarded and a NACK SHOULD be sent. 🙈

6.3.3. Encoding of Compressed Lists 🧾

Each item in a compressed list is represented by 💜:

  • an index into the table of items
  • a presence bit, which says whether a compressed representation of the item is in the list
  • an item (only if the presence bit is set)

Decompression of an item will fail if the presence bit isn't set and the decompressor has no entry in the context for that item. Yikes 💀.

A compressed list of TCP options 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 the size of the XI fields:

  • PS = 0 indicates 4-bit XI fields;
  • PS = 1 indicates 8-bit XI fields.

m 🔢: The number of XI item(s) in the compressed list.

XI_1, ..., XI_m 🧁: m XI items. Each XI represents one TCP option in the uncompressed packet, in the same order as they appear there.

The format of an XI item looks like this:

        +---+---+---+---+
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 and is instead included in the irregular chain.

Reserved 🚧: MUST be set to zero; otherwise, the decompressor MUST discard the packet.

Index 🔑: An index into the item table. See Section 6.3.4.

When 4-bit XI items are used, the XI items get placed in 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. The format of the entries in the item list is described in Section 6.2.

6.3.4. Item Table Mappings 💅

Okay bestie, the item table for TCP options list compression only gets 16 slots 🎀. That's because it's super unlikely any packet flow will have more than 16 unique options, so this is plenty ✨.

The mapping between TCP option types and table indexes is laid out below 🦋:

+-----------------+---------------+
|   Option name   |  Table index  |
+-----------------+---------------+
|      NOP        |       0       |
|      EOL        |       1       |
|      MSS        |       2       |
|  WINDOW SCALE   |       3       |
|   TIMESTAMP     |       4       |
| SACK-PERMITTED  |       5       |
|      SACK       |       6       |
| Generic options |      7-15     |
+-----------------+---------------+

Some TCP options are way more popular than others 👑, so a chunk of the table is reserved for them (see the table above) to make compressing them easier. Both the compressor and the decompressor MUST use these item-to-index mappings to (de)compress TCP options when using list compression 🫶.

The option types with a reserved index are expected to only show up once in a list 🌸. But if the same option type pops up twice in one options list and the two copies have different content, the compressor should compress the second occurrence by mapping it to a generic compressed option 🙈. If the two copies are exactly the same, though, the compressor can still use the same table index for both 💜.

The NOP option

NOP can show up more than once in the list 💅. Since its value is always the same, no context information needs to be transmitted, so several NOPs can map to the same index ✨. Because NOP has no content when it's compressed as a _list_item, it never actually appears in the item list 🙈. For consistency, though, the compressor should still make an entry in the list by setting the presence bit, same as it does for the other option types 🎀.

List compression always keeps the original order of each item in the decompressed list 🧚. That's true whether or not an item is present in the compressed _list_item, and whether or not several items of the same type map to the same index (like NOP does) 💖.

The EOL option

The compressed format for EOL can be bigger than one octet, and it's defined so that it includes the option padding 🌈. This makes sense because EOL is supposed to end the parsing of the options, but it can also be followed by padding octets that all have the value zero 🫠.

The Generic option

The Generic option can be used to compress any type of TCP option that doesn't have a reserved index in the item table 💜.


6.3.5. Compressed Lists in Dynamic Chain 🍓

A compressed list for TCP options that lives in the dynamic chain (like in IR or IR-DYN packets) must have all its list items present 💖. In other words, every X-bit in the XI list MUST be set ✨.

6.3.6. Irregular Chain Items for TCP Options 🦋

The _list_item represents the option inside the compressed item list, while the _irregular format is for the option fields that are expected to change with each packet 🌸. When an item of the specified type is present in the current context, these irregular fields show up in every compressed packet, as part of the irregular chain 🎀. Since lots of TCP option types aren't expected to change for the whole flow, a lot of the _irregular formats are empty 💅.

The irregular chain for TCP options is structured the same way as the TCP options in the uncompressed packet 🧚. If a compressed list is present in the compressed packet, the irregular chain for TCP options must not contain irregular items for the list items that are sent inside the compressed list (aka the items with the X-bit set in its XI) 🙈. Items that aren't in the compressed list but are in the uncompressed list must have their irregular items present in the irregular chain 👑.

6.3.7. Replication of TCP Options 🌈

The entire table of TCP options items is always replicated when using the IR-CR packet ✨. In the IR-CR packet, the list of options for the new flow is also sent as a compressed list 💖.


6.4. Profile-Specific Encoding Methods ✨

This section defines encoding methods that are specific to this profile 💅. These methods get used in the formal definition of the packet formats in Section 8 🫶.

6.4.1. inferred_ip_v4_header_checksum 💖

This encoding method compresses the Header Checksum field of the IPv4 header 🎀. The checksum is defined in [RFC0791] 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.

So the header checksum protects individual hops from processing a corrupted header 🛡️. But when almost all the IP header info is compressed away, and decompression gets verified by a CRC computed over the original header for every compressed packet, this extra checksum is kinda pointless 🙄. It can just be recomputed on the decompressor side instead ✨.

The inferred_ip_v4_header_checksum encoding method compresses the IPv4 header checksum down to a size of zero bits 🧚. With this method, the decompressor infers the value of the field using the computation above 💜.

This encoding method implicitly assumes that the compressor will not process a corrupted header. Otherwise, it can't guarantee that the checksum recomputed by the decompressor will be bitwise identical to its original value before compression 🙈.

6.4.2. inferred_mine_header_checksum 🌸

This encoding method compresses the minimal encapsulation header checksum 🦋. That checksum is defined in [RFC2004] 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 compresses the minimal encapsulation header checksum down to a size of zero bits 💅. That means no bits get sent in compressed headers for this field 🫠. The decompressor infers the value using the computation above 🎀.

The reasons for inferring this checksum, and the assumptions behind it, are similar to the ones explained above in Section 6.4.1 💖.

6.4.3. inferred_ip_v4_length 🌈

This encoding method compresses the Total Length field of the IPv4 header 🍓. The Total Length field is defined in [RFC0791] 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 a size of zero bits ✨. With this method, the decompressor infers the value of the field by counting, in octets, the length of the entire packet after decompression 🧚.

6.4.4. inferred_ip_v6_length 💜

This encoding method compresses the Payload Length field of the IPv6 header 🫶. That length field is defined in [RFC2460] 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 a size of zero bits 🌸. With this method, the decompressor infers the value of the field by counting, in octets, the length of the entire packet after decompression 🦋.

6.4.5. inferred_offset 💖

This encoding method compresses the data offset field of the TCP header 🎀.

The inferred_offset encoding method is used on the Data Offset field of the TCP header 💅. That field is defined in [RFC0793] as:

   Data Offset: 4 bits

      The number of 32 bit words in the TCP Header.  This indicates
      where the data begins.  The TCP header (even one including
      options) is an integral number of 32 bits long.

The inferred_offset encoding method compresses the Data Offset field of the TCP header down to a size of zero bits 🫠. With this method, the decompressor infers the value of the field by first decompressing the TCP options list, and then setting:

             data offset = (options length / 4) + 5

The equation above uses integer arithmetic 🤓.

6.4.6. baseheader_extension_headers 🧚

In CO packets (see Section 7.3), the innermost IP header and the TCP header get combined to create a compressed base header ✨. Sometimes the IP header has a bunch of extension headers sitting between itself and the TCP header 🌈.

To stay formally correct, the base header has to define some representation of these extension headers, and that's what this encoding method is for 💜. It skips over all the extension headers and doesn't encode any of their fields 💖. Changed fields in these headers are encoded in the irregular chain 🦋.

6.4.7. baseheader_outer_headers 👑

This encoding method, along with the baseheader_extension_headers encoding method above, is needed so the specification stays formally correct 🎀. It's used in CO packets (see Section 7.3) to describe tunneling IP headers and their respective extension headers, meaning all the headers located before the innermost IP header 🫶.

This encoding method skips over all the fields in these headers and doesn't perform any encoding 💅. Changed fields in outer headers are handled by the irregular chain instead 🌸.

6.4.8. Scaled Encoding of Fields 📈✨

okay so some header fields have this pattern where the value goes up by a constant amount each time, or by multiples of that same amount 🦋 The TCP Sequence Number and the TCP Acknowledgment Number are the big examples here 💖 For fields like these, ROHC-TCP lets you scale the value down before doing LSB encoding, so the compressor only has to send fewer bits 🍓 it's giving efficiency, no cap.

To use scaled encoding, the field has to fit this equation:

unscaled_value = scaling_factor * scaled_value + residue

The compressor must be confident that the decompressor has established values for the "residue" and the "scaling_factor" first 🫶 That way the decompressor can correctly decompress the field when only an LSB-encoded "scaled_value" shows up in the compressed packet.

Once the compressor is confident that the scaling_factor and the residue have been established in the decompressor, it may send compressed packets using the scaled representation of the field ✨ But the compressor MUST NOT use scaled encoding with the value of the scaling_factor set to zero. Zero is a hard no, bestie 🙅‍♀️

If the compressor detects that the residue has changed, or if it uses a different value for the scaling factor, it MUST NOT use scaled encoding until it is confident that the decompressor has received the new value(s) of these fields 😭 be patient, it'll come back.

When the unscaled value of the field wraps around, the residue is likely to change even if the scaling_factor stays constant 💀 In that case, the compressor must act the same way it would for any other change in the residue.

The next subsections go through how scaled encoding gets applied to specific fields in ROHC-TCP, and how the scaling_factor and residue get established for each one 🧚

6.4.8.1. Scaled TCP Sequence Number Encoding

For some TCP flows, like data transfers, the payload size stays constant for periods of time 🎀 For these flows, the TCP Sequence Number is bound to increase by multiples of the payload size between packets, so this field is a great target for scaled encoding 🔥 The payload size gets used as the scaling factor (aka the value for scaling_factor). That means the scaling factor never has to be sent explicitly. The decompressor just infers it from the length of the payload in the compressed packet 🧠

Establishing scaling_factor:

The scaling factor gets established by sending unscaled TCP Sequence Number bits, so the decompressor can infer the scaling_factor from the payload size 🫧

Establishing residue:

The residue gets established the exact same way as the scaling_factor, which is by sending unscaled TCP Sequence Number bits 💅

A detailed specification of how the TCP Sequence Number uses scaled encoding lives in the packet format definitions, in Section 8.2 📖

6.4.8.2. Scaled Acknowledgment Number Encoding

Just like the TCP Sequence Number, the expected increase in the TCP Acknowledgment Number is often constant, so it's also a good fit for scaled encoding 🌸

Here's where it gets a little different. For the TCP Acknowledgment Number, the scaling factor depends on the size of packets flowing in the opposite direction 🙈 That info might not be available to the compressor/decompressor pair. So ROHC-TCP uses an explicitly transmitted scaling factor to compress the TCP Acknowledgment Number instead 📨

Establishing scaling_factor:

The scaling factor gets established by explicitly transmitting its value (called ack_stride in the formal notation in Section 8.2) to the decompressor. This happens using one of the packet types that can carry that information 🦋

Establishing residue:

The residue gets established by sending unscaled TCP Acknowledgment Number bits, so the decompressor can infer its value from the unscaled value and the scaling factor (ack_stride) 🧮

A detailed specification of how the TCP Acknowledgment Number uses scaled encoding lives in the packet format definitions, in Section 8.2 📚

The compressor MAY use the scaled acknowledgment number encoding 🌈 What value it uses as the scaling factor is up to the compressor implementation. If there's a co-located decompressor processing packets of the same TCP flow in the opposite direction, the compressor can use the scaling factor for the sequence number of that flow to pick a suitable scaling factor for the TCP Acknowledgment Number of this flow. Smart, right? 🧠✨

6.5. Encoding Methods With External Parameters

Some encoding methods in Section 8.2 have one or more arguments whose values come from outside the scope of the ROHC-FN specification of the header formats 🎀 This section lists those encoding methods along with a definition of each of their parameters 💖

  • esp_null(next_header_value):

next_header_value: Set to the value of the Next Header field located in the ESP trailer, usually 12 octets from the end of the packet. Compression of null-encrypted ESP headers should only be performed when the compressor has prior knowledge of the exact location of the Next Header field 🔐

  • ipv6(is_innermost, ttl_irregular_chain_flag, ip_inner_ecn):

is_innermost: This Boolean flag is set to true when processing the innermost IP header. Otherwise, it is set to false 🪆

ttl_irregular_chain_flag: This parameter must be set to the value that was used for the corresponding "ttl_irregular_chain_flag" parameter of the co_baseheader encoding method (defined below) when extracting the irregular chain for a compressed header. Otherwise, it is set to zero and ignored for other types of chains 🌸

ip_inner_ecn: This parameter is bound by the encoding method, so it should be undefined when calling this encoding method. This value is then used to bind the corresponding parameter in the tcp encoding method, because its value is needed when processing the irregular chain for TCP. Check the definition of the "ip_inner_ecn" parameter for the tcp encoding method below 👀

  • ipv4(is_innermost, ttl_irregular_chain_flag, ip_inner_ecn):

See the definition of arguments for ipv6 above. Same vibes, different IP version 🧚

  • tcp_opt_eol(nbits):

nbits: Set to the length of the padding data located after the EOL option type octet, running to the end of the TCP options in the uncompressed header 🍓

  • tcp_opt_sack(ack_value):

ack_value: Set to the value of the Acknowledgment Number field of the TCP header 💜

  • tcp(payload_size, ack_stride_value, ip_inner_ecn):

payload_size: Set to the length (in octets) of the payload following the TCP header 🎀

ack_stride_value: This parameter is the scaling factor used when scaling the TCP Acknowledgment Number. Its value is set by the compressor implementation. See Section 6.4.8.2 for recommendations on how to set this value 🫶

ip_inner_ecn: This parameter binds with the value given to the corresponding "ip_inner_ecn" parameter by the ipv4 or the ipv6 encoding method when processing the innermost IP header of this packet. See also the definition of the "ip_inner_ecn" parameter for the ipv6 and ipv4 encoding methods above 🔗

  • co_baseheader(payload_size, ack_stride_value, ttl_irregular_chain_flag):

payload_size: Set to the length (in octets) of the payload following the TCP header 🎀

ack_stride_value: This parameter is the scaling factor used when scaling the TCP Acknowledgment Number. Its value is set by the compressor implementation. See Section 6.4.8.2 for recommendations on how to set this value 🫶

ttl_irregular_chain_flag: This parameter is set to one if the TTL/Hop Limit of an outer header has changed compared to its reference in the context. Otherwise, it is set to zero. The value used here is also used for the "ttl_irregular_chain_flag" argument of the ipv4 and ipv6 encoding methods when processing the irregular chain, as defined above for those methods 🔁


7. Packet Types (Normative) 📦

ROHC-TCP uses three different packet types 💖 There's the Initialization and Refresh (IR) packet type, the Context Replication (IR-CR) packet type, and the Compressed (CO) packet type ✨

Each packet type defines a bunch of packet formats. The IR type has two packet formats, the IR-CR type has one, and the CO type has two sets of eight base header formats, plus one additional format that both sets share 🧩

The profile identifier for ROHC-TCP is 0x0006 🌈

7.1. Initialization and Refresh (IR) Packets ✨

ROHC-TCP borrows the basic structure of the ROHC IR and IR-DYN packets from [RFC4995] (Sections 5.2.2.1 and 5.2.2.2, respectively). 🎀

Packet type: IR 💖

This packet type carries the static part and the dynamic part of the context. Lowkey the whole packet, just in two pieces. 🧚

For the ROHC-TCP IR packet, the value of the x bit MUST be set to one. 🔥 It has the following format, which corresponds to the "Header" and "Payload" fields described in Section 5.2.1 of [RFC4995]:

  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 = 0x06        | 1 octet
+---+---+---+---+---+---+---+---+
|              CRC              | 1 octet
+---+---+---+---+---+---+---+---+
|                               |
/         Static chain          / variable length
|                               |
 - - - - - - - - - - - - - - - -
|                               |
/         Dynamic chain         / variable length
|                               |
 - - - - - - - - - - - - - - - -
|                               |
/            Payload            / variable length
|                               |
 - - - - - - - - - - - - - - - -
  • CRC: 8-bit CRC, computed according to Section 5.3.1.1. of [RFC4995]. The CRC covers the entire IR header, thus excluding payload, padding, and feedback, if any. 🧮
  • Static chain: See Section 6.2. 📦
  • Dynamic chain: See Section 6.2. 🔄
  • Payload: The payload of the corresponding original packet, if any. The payload consists of all data after the last octet of the TCP header to end of the uncompressed packet. The presence of a payload is inferred from the packet length. 🍓

Packet type: IR-DYN 🌸

This packet type carries just the dynamic part of the context. Main character energy for the dynamic bits. 👑

The ROHC-TCP IR-DYN packet has the following format, which corresponds to the "Header" and "Payload" fields described in Section 5.2.1 of [RFC4995]:

  0   1   2   3   4   5   6   7
 --- --- --- --- --- --- --- ---
:         Add-CID octet         : if for small CIDs and (CID != 0)
+---+---+---+---+---+---+---+---+
| 1   1   1   1   1   0   0   0 | IR-DYN type octet
+---+---+---+---+---+---+---+---+
:                               :
/       0-2 octets of CID       / 1-2 octets if for large CIDs
:                               :
+---+---+---+---+---+---+---+---+
|         Profile = 0x06        | 1 octet
+---+---+---+---+---+---+---+---+
|              CRC              | 1 octet
+---+---+---+---+---+---+---+---+
|                               |
/         Dynamic chain         / variable length
|                               |
 - - - - - - - - - - - - - - - -
|                               |
/            Payload            / variable length
|                               |
 - - - - - - - - - - - - - - - -
  • CRC: 8-bit CRC, computed according to Section 5.3.1.1 of [RFC4995]. The CRC covers the entire IR-DYN header, thus excluding payload, padding, and feedback, if any. 🧮
  • Dynamic chain: See Section 6.2. 🔄
  • Payload: The payload of the corresponding original packet, if any. The payload consists of all data after the last octet of the TCP header to end of the uncompressed packet. The presence of a payload is inferred from the packet length. 🍓

7.2. Context Replication (IR-CR) Packets 🦋

Context replication needs its own IR packet format that uniquely identifies the IR-CR packet for the ROHC-TCP profile. This section defines the profile-specific part of the IR-CR packet [RFC4164]. 🫶

Packet type: IR-CR 💜

This packet type carries a reference to a base context, along with the static and dynamic parts of the replicated context that differ from the base context. It's giving "copy the homework but only change the parts that are different" 😭

The ROHC-TCP IR-CR packet follows the general format of the ROHC CR packet, as defined in [RFC4164], Section 3.5.2. With consideration to the extensibility of the IR packet type defined in [RFC4995], the ROHC-TCP profile supports context replication through the profile-specific part of the IR packet. This is achieved using the bit (x) left in the IR header for "Profile specific information". For ROHC-TCP, this bit is defined as a flag indicating whether this packet is an IR packet or an IR-CR packet. For the ROHC-TCP IR-CR packet, the value of the x bit MUST be set to zero. 🙈

The ROHC-TCP IR-CR has the following format, which corresponds to the "Header" and "Payload" fields described in Section 5.2.1 of [RFC4995]:

     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   0 | IR-CR type octet
   +---+---+---+---+---+---+---+---+
   :                               :
   /       0-2 octets of CID       / 1-2 octets if for large CIDs
   :                               :
   +---+---+---+---+---+---+---+---+
   |         Profile = 0x06        | 1 octet
   +---+---+---+---+---+---+---+---+
   |              CRC              | 1 octet
   +---+---+---+---+---+---+---+---+
   | B |             CRC7          | 1 octet
   +---+---+---+---+---+---+---+---+
   :   Reserved    |   Base CID    : 1 octet, for small CID, if B=1
   +---+---+---+---+---+---+---+---+
   :                               :
   /           Base CID            / 1-2 octets, for large CIDs,
   :                               : if B=1
   +---+---+---+---+---+---+---+---+
   |                               |
   /        Replicate chain        / variable length
   |                               |
    - - - - - - - - - - - - - - - -
   |                               |
   /            Payload            / variable length
   |                               |
    - - - - - - - - - - - - - - - -
  • B: B = 1 indicates that the Base CID field is present. 🔖
  • CRC: This CRC covers the entire IR-CR header, thus excluding payload, padding, and feedback, if any. This 8-bit CRC is calculated according to Section 5.3.1.1 of [RFC4995]. 🧮
  • CRC7: The CRC over the original, uncompressed, header. Calculated according to Section 3.5.1.1 of [RFC4164]. 🧮
  • Reserved: MUST be set to zero; otherwise, the decompressor MUST discard the packet. 🚫 Don't mess with the reserved bits, bestie.
  • Base CID: CID of base context. Encoded according to [RFC4164], Section 3.5.3. 🆔
  • Replicate chain: See Section 6.2. 🔗
  • Payload: The payload of the corresponding original packet, if any. The presence of a payload is inferred from the packet length. 🍓

7.3. Compressed (CO) Packets 💅

The ROHC-TCP CO packets communicate irregularities in the packet header. All CO packets carry a CRC and can update the context. Every single one has its own little safety check, iconic 🛡️✨

The general format for a compressed TCP header is as follows, which corresponds to the "Header" and "Payload" fields described in Section 5.2.1 of [RFC4995]:

      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 number of octets
    +---+---+---+---+---+---+---+---+
    :        Irregular chain        :
    /   (including irregular chain  /  variable
    :    items for TCP options)     :
     --- --- --- --- --- --- --- ---
    |                               |
    /            Payload            / variable length
    |                               |
     - - - - - - - - - - - - - - - -
  • Base header: The complete set of base headers is defined in Section 8. 🧩
  • Irregular chain: See Section 6.2 and Section 6.3.6. 🌀
  • Payload: The payload of the corresponding original packet, if any. The presence of a payload is inferred from the packet length. 🍓

Next up is Section 8, the header formats. That's where the real normative magic lives, so stay tuned 🌈💖

8.1. Design Rationale for Compressed Base Headers ✨

okay so bestie, the compressed header formats come in two separate sets 🎀. One set is for packets where the innermost IP header has a sequential IP-ID (network byte order or byte swapped). The other set is for packets without a sequential IP-ID, which means random, zero, or no IP-ID at all 💅.

We call these the "sequential" set and the "random" set of header formats 🦋.

There's also one compressed format that BOTH sets share 🫶 so it works no matter how the IP-ID behaves. It can send rarely changing fields, and it sends the frequently changing fields in variable lengths. It can even change control fields like IP-ID behavior and ECN behavior 🔥.

Here's the CRC situation 🙈. Every compressed base header has a 3-bit CRC, UNLESS it updates a control field like "ip_id_behavior" or "ecn_used", because those control fields change how the next headers get read. Headers that can touch those control fields get a 7-bit CRC instead 💜.

When I talk about LSB-encoded fields, "p" is the "offset_param" and "k" is the "num_lsbs_param" from [RFC4997] 🧚. The encoding methods in the compressed base headers come from these design criteria:

  • MSN

The MSN is a number the compressor generates, so it only has to be big enough for robust operation and to handle a little reordering [RFC4163] 🍓. That's why every compressed base header has an MSN field that's LSB-encoded with k=4 and p=4, which handles a reordering depth of up to 4 packets. More robustness tips for when reordering is possible are in [RFC4224] ✨.

  • TCP Sequence Number

ROHC-TCP is built for bulk data transfers 🌈, where the sequence number is expected to go up by about 1460 octets (that fits in 11 bits). Retransmissions mean a NEGATIVE delta to the sequence number, so the LSB interpretation interval has to handle negative offsets about as big as the positive ones. That means one more bit is needed 😭.

For ROHC-TCP to be robust to losses, we add two more bits to the LSB encoding of the sequence number. So the base headers should have at least 14 bits of LSB-encoded sequence number when it's present. Following that logic, the LSB offset is as large as the positive offset, so p = 2^(k-1)-1 💖.

  • TCP Acknowledgment Number

Same vibe as the sequence number, but often only every other data packet gets acknowledged, so the expected delta is twice as big as for sequence numbers 👑.

That means at least 15 bits of acknowledgment number should be used in the compressed base headers. The ack number is expected to keep climbing, and the only exception is packet reordering (on the ROHC channel [RFC3759] or before the compression point). So the negative offset for LSB encoding is 1/4 of the total interval, meaning p = 2^(k-2)-1 🎀.

  • TCP Window

The TCP Window field goes up in increments about the same size as the TCP Sequence Number, so the window needs to send at least 14 bits when it's used 🌸.

  • IP-ID

For the "sequential" set, every compressed base header has LSB-encoded IP-ID offset bits. The offset is the difference between the MSN field and the IP-ID field. At least 3 bits of IP-ID should always be present, but 4 to 7 bits is preferable 🦋. When k=3 then p=1, and when k>3 then p=3, since the offset is expected to increase most of the time ⭐.

Each set of header formats has eight different compressed base headers 💅. Why so many? Because the TCP Sequence Number, TCP Acknowledgment Number, and TCP Window change frequently in a non-linear way 🙃.

The header format design comes from the field behavior analysis in [RFC4413] 🔥.

ALL of the compressed base headers send LSB-encoded MSN bits, the TCP Push flag, and a CRC 🫶. On top of that, every base header in the sequential set has LSB-encoded IP-ID bits 🧚.

These formats exist in BOTH the sequential and random sets 👑:

  • Format 1: carries changes to the TCP Sequence Number and is expected to be used on the downstream of a data transfer 💖.
  • Format 2: carries the TCP Sequence Number in scaled form and is expected to be useful on the downstream of a data transfer where the payload size stays constant across multiple packets 🌈.
  • Format 3: carries changes in the TCP Acknowledgment Number and is expected to be useful for the acknowledgment direction of a data transfer 🎀.
  • Format 4: is similar to format 3, but carries a scaled TCP Acknowledgment Number 🍓.
  • Format 5: carries both the TCP Sequence Number and the TCP Acknowledgment Number and is expected to be useful for flows that send data in both directions 🦋.
  • Format 6: is similar to format 5, but carries the TCP Sequence Number in scaled form, when the payload size is static for certain intervals in a data flow ✨.
  • Format 7: carries changes to both the TCP Acknowledgment Number and the TCP Window and is expected to be useful for the acknowledgment flows of data connections 💜.
  • Format 8: conveys changes to some of the more seldom changing fields in the TCP flow, like ECN behavior, RST/SYN/FIN flags, the TTL/Hop Limit, and the TCP options list. It carries a 7-bit CRC, because it can change the structure of the contents of the irregular chain for subsequent packets. Note that this can be seen as a reduced form of the common packet format 🙈.
  • Common header format: can be used for ALL kinds of IP-ID behavior and should be useful when some of the more rarely changing fields in the IP or TCP header change 🌸. Since it can update the control fields that decide how the decompressor interprets packets, it carries a 7-bit CRC to reduce the chance of context corruption 🔥. It can basically convey changes to any of the dynamic fields in the IP and TCP headers, and it uses a big set of flags to say which fields are present 💅.

8.2. Formal Definition of Header Formats 🧚

Okay this is the formal part, so here are the definitions in their own spec-y glory 🤓.

 ////////////////////////////////////////////
 // Constants
 ////////////////////////////////////////////

 IP_ID_BEHAVIOR_SEQUENTIAL = 0;
 IP_ID_BEHAVIOR_SEQUENTIAL_SWAPPED = 1;
 IP_ID_BEHAVIOR_RANDOM = 2;
 IP_ID_BEHAVIOR_ZERO = 3;

 ////////////////////////////////////////////
 // Global control fields
 ////////////////////////////////////////////

 CONTROL {
   ecn_used            [ 1 ];
   msn                 [ 16 ];
 }

 ///////////////////////////////////////////////
 // Encoding methods not specified in FN syntax
 ///////////////////////////////////////////////

 list_tcp_options               "defined in Section 6.3.3";
 inferred_ip_v4_header_checksum "defined in Section 6.4.1";
 inferred_mine_header_checksum  "defined in Section 6.4.2";
 inferred_ip_v4_length          "defined in Section 6.4.3";
 inferred_ip_v6_length          "defined in Section 6.4.4";
 inferred_offset                "defined in Section 6.4.5";
 baseheader_extension_headers   "defined in Section 6.4.6";
 baseheader_outer_headers       "defined in Section 6.4.7";

 ////////////////////////////////////////////
 // General encoding methods
 ////////////////////////////////////////////

 static_or_irreg(flag, width)
 {
   UNCOMPRESSED {
     field [ width ];
   }

   COMPRESSED irreg_enc {
     field =:= irregular(width) [ width ];
     ENFORCE(flag == 1);
   }

   COMPRESSED static_enc {
     field =:= static [ 0 ];
     ENFORCE(flag == 0);
   }
 }

 zero_or_irreg(flag, width)
 {
   UNCOMPRESSED {
     field [ width ];
   }

   COMPRESSED non_zero {
     field =:= irregular(width) [ width ];
     ENFORCE(flag == 0);
   }

   COMPRESSED zero {
     field =:= uncompressed_value(width, 0) [ 0 ];
     ENFORCE(flag == 1);
   }
 }

 variable_length_32_enc(flag)
 {
   UNCOMPRESSED {
     field [ 32 ];
   }

   COMPRESSED not_present {

     field =:= static [ 0 ];
     ENFORCE(flag == 0);
   }

   COMPRESSED lsb_8_bit {
     field =:= lsb(8, 63) [ 8 ];
     ENFORCE(flag == 1);
   }

   COMPRESSED lsb_16_bit {
     field =:= lsb(16, 16383) [ 16 ];
     ENFORCE(flag == 2);
   }

   COMPRESSED irreg_32_bit {
     field =:= irregular(32) [ 32 ];
     ENFORCE(flag == 3);
   }
 }

 optional32(flag)
 {
   UNCOMPRESSED {
     item [ 0, 32 ];
   }

   COMPRESSED present {
     item =:= irregular(32) [ 32 ];
     ENFORCE(flag == 1);
   }

   COMPRESSED not_present {
     item =:= compressed_value(0, 0) [ 0 ];
     ENFORCE(flag == 0);
   }
 }
 lsb_7_or_31
 {
   UNCOMPRESSED {
     item [ 32 ];
   }

   COMPRESSED lsb_7 {
     discriminator =:= '0'       [ 1 ];
     item          =:= lsb(7, 8) [ 7 ];
   }

   COMPRESSED lsb_31 {

     discriminator =:= '1'          [ 1 ];
     item          =:= lsb(31, 256) [ 31 ];
   }
 }

 opt_lsb_7_or_31(flag)
 {
   UNCOMPRESSED {
     item [ 0, 32 ];
   }

   COMPRESSED present {
     item =:= lsb_7_or_31 [ 8, 32 ];
     ENFORCE(flag == 1);
   }

   COMPRESSED not_present {
     item =:= compressed_value(0, 0) [ 0 ];
     ENFORCE(flag == 0);
   }
 }

 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 ];
   }
 }

 one_bit_choice
 {
   UNCOMPRESSED {
     field [ 1 ];

   }

   COMPRESSED zero {
     field [ 1 ];
     ENFORCE(field.UVALUE == 0);
   }

   COMPRESSED nonzero {
     field [ 1 ];
     ENFORCE(field.UVALUE == 1);
   }
 }


 // 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.
 field_scaling(stride_value, scaled_value, unscaled_value)
 {
   UNCOMPRESSED {
     residue_field [ 32 ];
   }

   COMPRESSED no_scaling {
     ENFORCE(stride_value == 0);
     ENFORCE(residue_field.UVALUE == unscaled_value);
     ENFORCE(scaled_value == 0);
   }

   COMPRESSED scaling_used {
     ENFORCE(stride_value != 0);
     ENFORCE(residue_field.UVALUE == (unscaled_value % stride_value));
     ENFORCE(unscaled_value ==
             scaled_value * stride_value + residue_field.UVALUE);
   }
 }
 ////////////////////////////////////////////
 // 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;
   }

okay so the destination options get their own compression closet first 🧚💖

   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_0_replicate {
     discriminator =:= '00000000' [ 8 ];
   }

   COMPRESSED dest_opt_1_replicate {
     discriminator =:= '10000000'                     [ 8 ];
     length        =:= irregular(8)                   [ 8 ];
     value         =:=
       irregular(length.UVALUE*64+48) [ length.UVALUE * 64 + 48 ];
   }

   COMPRESSED dest_opt_irregular {
   }
 }

Next up is the IPv6 Hop-by-Hop options header, which gets the same treatment, bestie 🌸🫶

 ////////////////////////////////////////////
 // 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_0_replicate {
     discriminator =:= '00000000' [ 8 ];
   }

   COMPRESSED hop_opt_1_replicate {
     discriminator =:= '10000000'                     [ 8 ];
     length        =:= irregular(8)                   [ 8 ];
     value         =:=
       irregular(length.UVALUE*64+48) [ length.UVALUE * 64 + 48 ];
   }

   COMPRESSED hop_opt_irregular {
   }
 }

Routing header time, and it's giving a little bit of drama in the rout_opt_dynamic department 💅✨

 ////////////////////////////////////////////
 // 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_0_replicate {
     discriminator =:= '00000000' [ 8 ];
   }

   COMPRESSED rout_opt_0_replicate {
     discriminator =:= '10000000'                     [ 8 ];
     length        =:= irregular(8)                   [ 8 ];
     value         =:=
       irregular(length.UVALUE*64+48) [ length.UVALUE * 64 + 48 ];
   }

   COMPRESSED rout_opt_irregular {
   }
 }

Now the GRE header, which is a whole vibe 🦋🍓

 ////////////////////////////////////////////
 // GRE Header
 ////////////////////////////////////////////

 optional_checksum(flag_value)
 {
   UNCOMPRESSED {
     value     [ 0, 16 ];
     reserved1 [ 0, 16 ];
   }

   COMPRESSED cs_present {
     value     =:= irregular(16)             [ 16 ];
     reserved1 =:= uncompressed_value(16, 0) [ 0 ];
     ENFORCE(flag_value == 1);
   }

   COMPRESSED not_present {
     value     =:= compressed_value(0, 0) [ 0 ];
     reserved1 =:= compressed_value(0, 0) [ 0 ];
     ENFORCE(flag_value == 0);
   }
 }

 gre_proto
 {
   UNCOMPRESSED {
     protocol [ 16 ];

   }

   COMPRESSED ether_v4 {
     discriminator =:= compressed_value(1, 0)         [ 1 ];
     protocol      =:= uncompressed_value(16, 0x0800) [ 0 ];
   }

   COMPRESSED ether_v6 {
     discriminator =:= compressed_value(1, 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 {
     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      =:= optional32(k_flag.UVALUE) [ 0, 32 ];
   }

   COMPRESSED gre_dynamic {
     checksum_and_res =:=

       optional_checksum(c_flag.UVALUE)             [ 0, 16 ];
     sequence_number  =:= optional32(s_flag.UVALUE) [ 0, 32 ];
   }

   COMPRESSED gre_0_replicate {
     discriminator    =:= '00000000'    [ 8 ];
     checksum_and_res =:=
       optional_checksum(c_flag.UVALUE) [ 0, 16 ];
     sequence_number  =:=
       optional32(s_flag.UVALUE)        [ 0, 8, 32 ];
   }

   COMPRESSED gre_1_replicate {
     discriminator    =:= '10000'                   [ 5 ];
     c_flag           =:= irregular(1)              [ 1 ];
     k_flag           =:= irregular(1)              [ 1 ];
     s_flag           =:= irregular(1)              [ 1 ];
     checksum_and_res =:=
       optional_checksum(c_flag.UVALUE)             [ 0, 16 ];
     key              =:= optional32(k_flag.UVALUE) [ 0, 32 ];
     sequence_number  =:= optional32(s_flag.UVALUE) [ 0, 32 ];
   }

   COMPRESSED gre_irregular {
     checksum_and_res =:=
       optional_checksum(c_flag.UVALUE) [ 0, 16 ];
     sequence_number  =:=
       opt_lsb_7_or_31(s_flag.UVALUE)   [ 0, 8, 32 ];
   }
 }

Next is the MINE header, then AH, then ESP. Three headers, one big compression glow-up 🌈⭐

 /////////////////////////////////////////////
 // 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    =:= optional32(s_bit.UVALUE) [ 0, 32 ];
   }

   COMPRESSED mine_dynamic {
   }

   COMPRESSED mine_0_replicate {
     discriminator =:= '00000000' [ 8 ];
   }

   COMPRESSED mine_1_replicate {
     discriminator =:= '10000000'               [ 8 ];
     s_bit         =:= irregular(1)             [ 1 ];
     res_bits      =:= irregular(7)             [ 7 ];
     orig_dest     =:= irregular(32)            [ 32 ];
     orig_src      =:= optional32(s_bit.UVALUE) [ 0, 32 ];
   }

   COMPRESSED mine_irregular {
   }
 }

 /////////////////////////////////////////////
 // Authentication Header (AH)
 /////////////////////////////////////////////

 ah
 {
   UNCOMPRESSED {
     next_header     [ 8 ];
     length          [ 8 ];
     res_bits        [ 16 ];
     spi             [ 32 ];
     sequence_number [ 32 ];
     auth_data       [ length.UVALUE*32-32 ];

   }

   DEFAULT {
     next_header     =:= static;
     length          =:= static;
     res_bits        =:= 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 {
     res_bits        =:= irregular(16) [ 16 ];
     sequence_number =:= irregular(32) [ 32 ];
     auth_data       =:=
       irregular(length.UVALUE*32-32)  [ length.UVALUE*32-32 ];
   }

   COMPRESSED ah_0_replicate {
     discriminator   =:= '00000000'    [ 8 ];
     sequence_number =:= irregular(32) [ 32 ];
     auth_data       =:=
       irregular(length.UVALUE*32-32)  [ length.UVALUE*32-32 ];
   }

   COMPRESSED ah_1_replicate {
     discriminator   =:= '10000000'    [ 8 ];
     length          =:= irregular(8)  [ 8 ];
     res_bits        =:= irregular(16) [ 16 ];
     spi             =:= irregular(32) [ 32 ];
     sequence_number =:= irregular(32) [ 32 ];
     auth_data       =:=
       irregular(length.UVALUE*32-32)  [ length.UVALUE*32-32 ];
   }

   COMPRESSED ah_irregular {
     sequence_number =:= lsb_7_or_31  [ 8, 32 ];
     auth_data       =:=
       irregular(length.UVALUE*32-32) [ length.UVALUE*32-32 ];
   }
 }

 /////////////////////////////////////////////
 // ESP header (NULL encrypted)
 /////////////////////////////////////////////

 // The value of the Next Header field from the trailer
 // part of the packet is passed as a parameter.
 esp_null(next_header_value)
 {
   UNCOMPRESSED {
     spi             [ 32 ];
     sequence_number [ 32 ];
   }

   CONTROL {
     nh_field [ 8 ];
   }

   DEFAULT {
     spi             =:= static;
     sequence_number =:= static;
     nh_field        =:= static;
   }

   COMPRESSED esp_static {
     nh_field =:= compressed_value(8, next_header_value) [ 8 ];
     spi      =:= irregular(32)                          [ 32 ];
   }

   COMPRESSED esp_dynamic {
     sequence_number =:= irregular(32) [ 32 ];
   }

   COMPRESSED esp_0_replicate {
     discriminator   =:= '00000000'    [ 8 ];
     sequence_number =:= irregular(32) [ 32 ];
   }
   COMPRESSED esp_1_replicate {
     discriminator   =:= '10000000'    [ 8 ];
     spi             =:= irregular(32) [ 32 ];
     sequence_number =:= irregular(32) [ 32 ];
   }

   COMPRESSED esp_irregular {
     sequence_number =:= lsb_7_or_31 [ 8, 32 ];
   }
 }

Last stop for this part is the IPv6 header, where the flow label and the ipv6 encoding get their moment ✨👑

 /////////////////////////////////////////////
 // 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 ];
   }
 }

 // The is_innermost flag is true if this is the innermost IP header
 // If extracting the irregular chain for a compressed packet:
 //   - ttl_irregular_chain_flag must have the same value as it had when
 //     processing co_baseheader.
 //   - ip_inner_ecn is bound in this encoding method and the value that
 //     it gets bound to should be passed to the tcp encoding method
 //   For other formats than the irregular chain, these two are ignored
 ipv6(is_innermost, ttl_irregular_chain_flag, ip_inner_ecn)
 {
   UNCOMPRESSED {
     version         =:= uncompressed_value(4, 6) [ 4 ];
     dscp                                         [ 6 ];
     ip_ecn_flags                                 [ 2 ];
     flow_label                                   [ 20 ];
     payload_length                               [ 16 ];
     next_header                                  [ 8 ];
     ttl_hopl                                     [ 8 ];
     src_addr                                     [ 128 ];
     dst_addr                                     [ 128 ];
   }

   DEFAULT {
     dscp           =:= static;
     ip_ecn_flags   =:= static;
     flow_label     =:= static;
     payload_length =:= inferred_ip_v6_length;
     next_header    =:= static;
     ttl_hopl       =:= static;
     src_addr       =:= static;
     dst_addr       =:= static;
   }

okay so we're picking up right in the middle of the IPv6 compressed headers, and bestie, the way these grammars are laid out is so clean 💅🎀

   COMPRESSED ipv6_static {
     version_flag =:= '1'            [ 1 ];
     reserved     =:= '00'           [ 2 ];
     flow_label   =:= fl_enc         [ 5, 21 ];
     next_header  =:= irregular(8)   [ 8 ];
     src_addr     =:= irregular(128) [ 128 ];
     dst_addr     =:= irregular(128) [ 128 ];
   }

   COMPRESSED ipv6_dynamic {
     dscp         =:= irregular(6) [ 6 ];
     ip_ecn_flags =:= irregular(2) [ 2 ];
     ttl_hopl     =:= irregular(8) [ 8 ];
   }

   COMPRESSED ipv6_replicate {
     dscp         =:= irregular(6) [ 6 ];
     ip_ecn_flags =:= irregular(2) [ 2 ];
     reserved     =:= '000'        [ 3 ];
     flow_label   =:= fl_enc       [ 5, 21 ];
   }

   COMPRESSED ipv6_outer_without_ttl_irregular {
     dscp         =:= static_or_irreg(ecn_used.UVALUE, 6) [ 0, 6 ];
     ip_ecn_flags =:= static_or_irreg(ecn_used.UVALUE, 2) [ 0, 2 ];
     ENFORCE(ttl_irregular_chain_flag == 0);
     ENFORCE(is_innermost == false);
   }

   COMPRESSED ipv6_outer_with_ttl_irregular {
     dscp         =:= static_or_irreg(ecn_used.UVALUE, 6) [ 0, 6 ];
     ip_ecn_flags =:= static_or_irreg(ecn_used.UVALUE, 2) [ 0, 2 ];
     ttl_hopl     =:= irregular(8)                        [ 8 ];
     ENFORCE(ttl_irregular_chain_flag == 1);
     ENFORCE(is_innermost == false);
   }

   COMPRESSED ipv6_innermost_irregular {
     ENFORCE(ip_inner_ecn == ip_ecn_flags.UVALUE);
     ENFORCE(is_innermost == true);
   }
 }

and now the IPv4 header, which gets its own little spotlight ✨🦋

 /////////////////////////////////////////////
 // IPv4 Header
 /////////////////////////////////////////////

 ip_id_enc_dyn(behavior)
 {
   UNCOMPRESSED {
     ip_id [ 16 ];
   }

   COMPRESSED ip_id_seq {
     ip_id =:= irregular(16) [ 16 ];
     ENFORCE((behavior == IP_ID_BEHAVIOR_SEQUENTIAL) ||
             (behavior == IP_ID_BEHAVIOR_SEQUENTIAL_SWAPPED) ||
             (behavior == IP_ID_BEHAVIOR_RANDOM));
   }

   COMPRESSED ip_id_zero {
     ip_id =:= uncompressed_value(16, 0) [ 0 ];
     ENFORCE(behavior == IP_ID_BEHAVIOR_ZERO);
   }
 }

 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 {
     ip_id =:= irregular(16) [ 16 ];
     ENFORCE(behavior == IP_ID_BEHAVIOR_RANDOM);
   }

   COMPRESSED ip_id_zero {
     ip_id =:= uncompressed_value(16, 0) [ 0 ];
     ENFORCE(behavior == IP_ID_BEHAVIOR_ZERO);
   }
 }

 ip_id_behavior_choice(is_inner)
 {

   UNCOMPRESSED {
     behavior [ 2 ];
   }

   DEFAULT {
     behavior =:= irregular(2);
   }

   COMPRESSED sequential {
     behavior [ 2 ];
     ENFORCE(is_inner == true);
     ENFORCE(behavior.UVALUE == IP_ID_BEHAVIOR_SEQUENTIAL);
   }

   COMPRESSED sequential_swapped {
     behavior [ 2 ];
     ENFORCE(is_inner == true);
     ENFORCE(behavior.UVALUE ==
             IP_ID_BEHAVIOR_SEQUENTIAL_SWAPPED);
   }

   COMPRESSED random {
     behavior [ 2 ];
     ENFORCE(behavior.UVALUE == IP_ID_BEHAVIOR_RANDOM);
   }

   COMPRESSED zero {
     behavior [ 2 ];
     ENFORCE(behavior.UVALUE == IP_ID_BEHAVIOR_ZERO);
   }
 }

 // The is_innermost flag is true if this is the innermost IP header
 // If extracting the irregular chain for a compressed packet:
 //   - ttl_irregular_chain_flag must have the same value as it had when
 //     processing co_baseheader.
 //   - ip_inner_ecn is bound in this encoding method and the value that
 //     it gets bound to should be passed to the tcp encoding method
 //   For other formats than the irregular chain, these two are ignored
 ipv4(is_innermost, ttl_irregular_chain_flag, ip_inner_ecn)
 {
   UNCOMPRESSED {
     version        =:= uncompressed_value(4, 4)  [ 4 ];
     hdr_length     =:= uncompressed_value(4, 5)  [ 4 ];
     dscp                                         [ 6 ];
     ip_ecn_flags                                 [ 2 ];
     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                                     [ 16 ];
     src_addr                                     [ 32 ];
     dst_addr                                     [ 32 ];
   }

   CONTROL {
     ip_id_behavior [ 2 ];
   }

   DEFAULT {
     dscp           =:= static;
     ip_ecn_flags   =:= static;
     length         =:= inferred_ip_v4_length;
     df             =:= static;
     ttl_hopl       =:= static;
     protocol       =:= static;
     checksum       =:= inferred_ip_v4_header_checksum;
     src_addr       =:= static;
     dst_addr       =:= static;
     ip_id_behavior =:= static;
   }

   COMPRESSED ipv4_static {
     version_flag =:= '0'           [ 1 ];
     reserved     =:= '0000000'     [ 7 ];
     protocol     =:= irregular(8)  [ 8 ];
     src_addr     =:= irregular(32) [ 32 ];
     dst_addr     =:= irregular(32) [ 32 ];
   }

   COMPRESSED ipv4_dynamic {
     reserved       =:= '00000'             [ 5 ];
     df             =:= irregular(1)        [ 1 ];
     ip_id_behavior =:= ip_id_behavior_choice(is_innermost) [ 2 ];
     dscp           =:= irregular(6)        [ 6 ];
     ip_ecn_flags   =:= irregular(2)        [ 2 ];
     ttl_hopl       =:= irregular(8)        [ 8 ];
     ip_id          =:=
       ip_id_enc_dyn(ip_id_behavior.UVALUE) [ 0, 16 ];
   }

   COMPRESSED ipv4_replicate {

     reserved       =:= '0000'              [ 4 ];
     ip_id_behavior =:= ip_id_behavior_choice(is_innermost) [ 2 ];
     ttl_flag       =:= irregular(1)        [ 1 ];
     df             =:= irregular(1)        [ 1 ];
     dscp           =:= irregular(6)        [ 6 ];
     ip_ecn_flags   =:= irregular(2)        [ 2 ];
     ip_id          =:=
       ip_id_enc_dyn(ip_id_behavior.UVALUE) [ 0, 16 ];
     ttl_hopl     =:=
         static_or_irreg(ttl_flag.UVALUE, 8) [ 0, 8 ];
   }

   COMPRESSED ipv4_outer_without_ttl_irregular {
     ip_id        =:=
       ip_id_enc_irreg(ip_id_behavior.UVALUE) [ 0, 16 ];
     dscp         =:= static_or_irreg(ecn_used.UVALUE, 6) [ 0, 6 ];
     ip_ecn_flags =:= static_or_irreg(ecn_used.UVALUE, 2) [ 0, 2 ];
     ENFORCE(ttl_irregular_chain_flag == 0);
     ENFORCE(is_innermost == false);
   }

   COMPRESSED ipv4_outer_with_ttl_irregular {
     ip_id        =:=
       ip_id_enc_irreg(ip_id_behavior.UVALUE)             [ 0, 16 ];
     dscp         =:= static_or_irreg(ecn_used.UVALUE, 6) [ 0, 6 ];
     ip_ecn_flags =:= static_or_irreg(ecn_used.UVALUE, 2) [ 0, 2 ];
     ttl_hopl     =:= irregular(8)                        [ 8 ];
     ENFORCE(is_innermost == false);
     ENFORCE(ttl_irregular_chain_flag == 1);
   }

   COMPRESSED ipv4_innermost_irregular {
     ip_id          =:=
       ip_id_enc_irreg(ip_id_behavior.UVALUE) [ 0, 16 ];
     ENFORCE(ip_inner_ecn == ip_ecn_flags.UVALUE);
     ENFORCE(is_innermost == true);
   }
 }

last stretch of this part: the TCP options! these are where the real fun starts, because each option gets its own compressed flavour 🌸🧚

 /////////////////////////////////////////////
 // TCP Options
 /////////////////////////////////////////////

 // nbits is bound to the remaining length (in bits) of TCP
 // options, including the EOL type byte.
 tcp_opt_eol(nbits)
 {
   UNCOMPRESSED {

     type     =:= uncompressed_value(8, 0) [ 8 ];
     padding  =:=
       uncompressed_value(nbits-8, 0)      [ nbits-8 ];
   }

   CONTROL {
     pad_len [ 8 ];
   }

   COMPRESSED eol_list_item {
     pad_len =:= compressed_value(8, nbits-8) [ 8 ];
   }

   COMPRESSED eol_irregular {
     pad_len =:= static;
     ENFORCE(nbits-8 == pad_len.UVALUE);
   }
 }

 tcp_opt_nop
 {
   UNCOMPRESSED {
     type =:= uncompressed_value(8, 1) [ 8 ];
   }

   COMPRESSED nop_list_item {
   }

   COMPRESSED nop_irregular {
   }
 }

 tcp_opt_mss
 {
   UNCOMPRESSED {
     type   =:= uncompressed_value(8, 2) [ 8 ];
     length =:= uncompressed_value(8, 4) [ 8 ];
     mss                                 [ 16 ];
   }

   COMPRESSED mss_list_item {
     mss =:= irregular(16) [ 16 ];
   }

   COMPRESSED mss_irregular {
     mss    =:= static;
   }
 }
 tcp_opt_wscale
 {
   UNCOMPRESSED {
     type   =:= uncompressed_value(8, 3) [ 8 ];
     length =:= uncompressed_value(8, 3) [ 8 ];
     wscale                              [ 8 ];
   }

   COMPRESSED wscale_list_item {
     wscale =:= irregular(8) [ 8 ];
   }

   COMPRESSED wscale_irregular {
     wscale =:= static;
   }
 }

 ts_lsb
 {
   UNCOMPRESSED {
     tsval [ 32 ];
   }

   COMPRESSED tsval_7 {
     discriminator =:= '0'        [ 1 ];
     tsval         =:= lsb(7, -1) [ 7 ];
   }

   COMPRESSED tsval_14 {
     discriminator =:= '10'        [ 2 ];
     tsval         =:= lsb(14, -1) [ 14 ];
   }

   COMPRESSED tsval_21 {
     discriminator =:= '110'               [ 3 ];
     tsval         =:= lsb(21, 0x00040000) [ 21 ];
   }

   COMPRESSED tsval_29 {
     discriminator =:= '111'               [ 3 ];
     tsval         =:= lsb(29, 0x04000000) [ 29 ];
   }
 }

 tcp_opt_ts
 {
   UNCOMPRESSED {
     type   =:= uncompressed_value(8, 8)  [ 8 ];
     length =:= uncompressed_value(8, 10) [ 8 ];
     tsval                                [ 32 ];
     tsecho                               [ 32 ];
   }

   COMPRESSED tsopt_list_item {
     tsval  =:= irregular(32) [ 32 ];
     tsecho =:= irregular(32) [ 32 ];
   }

   COMPRESSED tsopt_irregular {
     tsval  =:= ts_lsb [ 8, 16, 24, 32 ];
     tsecho =:= ts_lsb [ 8, 16, 24, 32 ];
   }
 }

 sack_var_length_enc(base)
 {
   UNCOMPRESSED {
     sack_field [ 32 ];
   }

   CONTROL {
     sack_offset [ 32 ];
     ENFORCE(sack_offset.UVALUE == (sack_field.UVALUE - base));
   }


   COMPRESSED lsb_15 {
     discriminator =:= '0'         [ 1 ];
     sack_offset   =:= lsb(15, -1) [ 15 ];
   }

   COMPRESSED lsb_22 {
     discriminator =:= '10'        [ 2 ];
     sack_offset   =:= lsb(22, -1) [ 22 ];
   }

   COMPRESSED lsb_30 {
     discriminator =:= '11'        [ 2 ];
     sack_offset   =:= lsb(30, -1) [ 30 ];
   }
 }

 sack_block(prev_block_end)
 {
   UNCOMPRESSED {
     block_start [ 32 ];
     block_end   [ 32 ];
   }

   COMPRESSED {
     block_start =:=
       sack_var_length_enc(prev_block_end) [ 16, 24, 32 ];
     block_end   =:=
       sack_var_length_enc(block_start)    [ 16, 24, 32 ];
   }
 }

 // The value of the parameter is set to the ack_number value
 // of the TCP header
 tcp_opt_sack(ack_value)
 {

   UNCOMPRESSED {
     type    =:= uncompressed_value(8, 5) [ 8 ];
     length                               [ 8 ];
     block_1                              [ 64 ];
     block_2                              [ 0, 64 ];
     block_3                              [ 0, 64 ];
     block_4                              [ 0, 64 ];
   }

   DEFAULT {
     length  =:= static;
     block_2 =:= uncompressed_value(0, 0);
     block_3 =:= uncompressed_value(0, 0);
     block_4 =:= uncompressed_value(0, 0);
   }

   COMPRESSED sack1_list_item {
     discriminator =:= '00000001';
     block_1       =:= sack_block(ack_value);
     ENFORCE(length.UVALUE == 10);
   }

   COMPRESSED sack2_list_item {
     discriminator =:= '00000010';
     block_1       =:= sack_block(ack_value);
     block_2       =:= sack_block(block_1_end.UVALUE);
     ENFORCE(length.UVALUE == 18);
   }

   COMPRESSED sack3_list_item {
     discriminator =:= '00000011';
     block_1       =:= sack_block(ack_value);
     block_2       =:= sack_block(block_1_end.UVALUE);
     block_3       =:= sack_block(block_2_end.UVALUE);
     ENFORCE(length.UVALUE == 26);
   }

Okay bestie, we're still deep in the grammar, and this part is all about how the SACK options, the generic TCP options, and the port and ECN fields get compressed 🎀✨

   COMPRESSED sack4_list_item {
     discriminator =:= '00000100';
     block_1       =:= sack_block(ack_value);
     block_2       =:= sack_block(block_1_end.UVALUE);
     block_3       =:= sack_block(block_2_end.UVALUE);
     block_4       =:= sack_block(block_3_end.UVALUE);
     ENFORCE(length.UVALUE == 34);
   }

   COMPRESSED sack_unchanged_irregular {
     discriminator =:= '00000000';
     block_1       =:= static;
     block_2       =:= static;
     block_3       =:= static;
     block_4       =:= static;
   }

   COMPRESSED sack1_irregular {
     discriminator =:= '00000001';
     block_1       =:= sack_block(ack_value);
     ENFORCE(length.UVALUE == 10);
   }

   COMPRESSED sack2_irregular {
     discriminator =:= '00000010';
     block_1       =:= sack_block(ack_value);
     block_2       =:= sack_block(block_1_end.UVALUE);
     ENFORCE(length.UVALUE == 18);
   }

   COMPRESSED sack3_irregular {
     discriminator =:= '00000011';
     block_1       =:= sack_block(ack_value);
     block_2       =:= sack_block(block_1_end.UVALUE);
     block_3       =:= sack_block(block_2_end.UVALUE);
     ENFORCE(length.UVALUE == 26);
   }

   COMPRESSED sack4_irregular {
     discriminator =:= '00000100';
     block_1       =:= sack_block(ack_value);
     block_2       =:= sack_block(block_1_end.UVALUE);
     block_3       =:= sack_block(block_2_end.UVALUE);
     block_4       =:= sack_block(block_3_end.UVALUE);
     ENFORCE(length.UVALUE == 34);
   }
 }

 tcp_opt_sack_permitted
 {
   UNCOMPRESSED {
     type   =:= uncompressed_value(8, 4) [ 8 ];
     length =:= uncompressed_value(8, 2) [ 8 ];
   }

   COMPRESSED sack_permitted_list_item {
   }

   COMPRESSED sack_permitted_irregular {
   }
 }

 tcp_opt_generic
 {
   UNCOMPRESSED {
     type                                    [ 8 ];
     length_msb =:= uncompressed_value(1, 0) [ 1 ];
     length_lsb                              [ 7 ];
     contents                           [ length_len.UVALUE*8-16 ];
   }

   CONTROL {
     option_static [ 1 ];
   }

   DEFAULT {
     type       =:= static;
     length_lsb =:= static;
     contents   =:= static;
   }

   COMPRESSED generic_list_item {
     type          =:= irregular(8)      [ 8 ];
     option_static =:= one_bit_choice    [ 1 ];
     length_lsb    =:= irregular(7)      [ 7 ];
     contents      =:=
       irregular(length_lsb.UVALUE*8-16) [ length_len.UVALUE*8-16 ];
   }

   // Used when context of option has option_static set to one
   COMPRESSED generic_static_irregular {

     ENFORCE(option_static.UVALUE == 1);
   }

   // An item that can change, but currently is unchanged
   COMPRESSED generic_stable_irregular {
     discriminator =:= '11111111' [ 8 ];
     ENFORCE(option_static.UVALUE == 0);
   }

   // An item that is assumed to change constantly.
   // Length is not allowed to change here, since a length change is
   // most likely to cause new NOPs or an EOL length change.
   COMPRESSED generic_full_irregular {
     discriminator =:= '00000000'        [ 8 ];
     contents      =:=
       irregular(length_lsb.UVALUE*8-16) [ length_lsb.UVALUE*8-16 ];
     ENFORCE(option_static.UVALUE == 0);
   }
 }

 tcp_list_presence_enc(presence)
 {
   UNCOMPRESSED {
     tcp_options;
   }

   COMPRESSED list_not_present {
     tcp_options =:= static [ 0 ];
     ENFORCE(presence == 0);
   }

   COMPRESSED list_present {
     tcp_options =:= list_tcp_options [ VARIABLE ];
     ENFORCE(presence == 1);
   }
 }

Next up is the TCP header itself, plus the little helpers that keep ports, ECN and the RST/SYN/FIN flags tidy 💅🦋

 /////////////////////////////////////////////
 // TCP Header
 /////////////////////////////////////////////

 port_replicate(flags)
 {
   UNCOMPRESSED {
     port [ 16 ];
   }

   COMPRESSED port_static_enc {

     port =:= static [ 0 ];
     ENFORCE(flags == 0b00);
   }

   COMPRESSED port_lsb8 {
     port =:= lsb(8, 64) [ 8 ];
     ENFORCE(flags == 0b01);
   }

   COMPRESSED port_irr_enc {
     port =:= irregular(16) [ 16 ];
     ENFORCE(flags == 0b10);
   }
 }

 tcp_irreg_ip_ecn(ip_inner_ecn)
 {
   UNCOMPRESSED {
     ip_ecn_flags [ 2 ];
   }

   COMPRESSED ecn_present {
     // This field does not exist in the uncompressed header
     // and therefore cannot use uncompressed_value.
     ip_ecn_flags =:=
       compressed_value(2, ip_inner_ecn) [ 2 ];
     ENFORCE(ecn_used.UVALUE == 1);
   }

   COMPRESSED ecn_not_present {
     ip_ecn_flags =:= static [ 0 ];
     ENFORCE(ecn_used.UVALUE == 0);
   }
 }

 rsf_index_enc
 {
   UNCOMPRESSED {
     rsf_flag [ 3 ];
   }

   COMPRESSED none {
     rsf_idx  =:= '00' [ 2 ];
     rsf_flag =:= uncompressed_value(3, 0x00);
   }

   COMPRESSED rst_only {
     rsf_idx  =:= '01' [ 2 ];
     rsf_flag =:= uncompressed_value(3, 0x04);
   }

   COMPRESSED syn_only {
     rsf_idx  =:= '10' [ 2 ];
     rsf_flag =:= uncompressed_value(3, 0x02);
   }

   COMPRESSED fin_only {
     rsf_idx  =:= '11' [ 2 ];
     rsf_flag =:= uncompressed_value(3, 0x01);
   }
 }

 optional_2bit_padding(used_flag)
 {
   UNCOMPRESSED {
   }

   COMPRESSED used {
     padding =:= compressed_value(2, 0x0) [ 2 ];
     ENFORCE(used_flag == 1);
   }

   COMPRESSED unused {
     padding =:= compressed_value(0, 0x0);
     ENFORCE(used_flag == 0);
   }
 }

 // ack_stride_value is the user-selected stride for scaling the
 // TCP ack_number
 // ip_inner_ecn is the value bound when processing the innermost
 // IP header (ipv4 or ipv6 encoding method)
 tcp(payload_size, ack_stride_value, ip_inner_ecn)
 {
   UNCOMPRESSED {
     src_port      [ 16 ];
     dst_port      [ 16 ];
     seq_number    [ 32 ];
     ack_number    [ 32 ];
     data_offset   [ 4 ];
     tcp_res_flags [ 4 ];
     tcp_ecn_flags [ 2 ];
     urg_flag      [ 1 ];
     ack_flag      [ 1 ];
     psh_flag      [ 1 ];
     rsf_flags     [ 3 ];
     window        [ 16 ];
     checksum      [ 16 ];
     urg_ptr       [ 16 ];
     options       [ (data_offset.UVALUE-5)*32 ];
   }

   CONTROL {
     seq_number_scaled                    [ 32 ];
     seq_number_residue =:=
         field_scaling(payload_size, seq_number_scaled.UVALUE,
                       seq_number.UVALUE) [ 32 ];
     ack_stride                           [ 16 ];
     ack_number_scaled                    [ 32 ];
     ack_number_residue =:=
         field_scaling(ack_stride.UVALUE, ack_number_scaled.UVALUE,
                       ack_number.UVALUE) [ 32 ];
     ENFORCE(ack_stride.UVALUE == ack_stride_value);
   }

   INITIAL {
     ack_stride     =:= uncompressed_value(16, 0);
   }

   DEFAULT {
     src_port      =:= static;
     dst_port      =:= static;
     seq_number    =:= static;
     ack_number    =:= static;
     data_offset   =:= inferred_offset;
     tcp_res_flags =:= static;
     tcp_ecn_flags =:= static;
     urg_flag      =:= static;
     ack_flag      =:= uncompressed_value(1, 1);
     rsf_flags     =:= uncompressed_value(3, 0);
     window        =:= static;
     urg_ptr       =:= static;
   }

   COMPRESSED tcp_static {
     src_port =:= irregular(16) [ 16 ];
     dst_port =:= irregular(16) [ 16 ];
   }

   COMPRESSED tcp_dynamic {
     ecn_used        =:= one_bit_choice          [ 1 ];
     ack_stride_flag =:= irregular(1)            [ 1 ];
     ack_zero        =:= irregular(1)            [ 1 ];
     urp_zero        =:= irregular(1)            [ 1 ];
     tcp_res_flags   =:= irregular(4)            [ 4 ];
     tcp_ecn_flags   =:= irregular(2)            [ 2 ];
     urg_flag        =:= irregular(1)            [ 1 ];
     ack_flag        =:= irregular(1)            [ 1 ];
     psh_flag        =:= irregular(1)            [ 1 ];
     rsf_flags       =:= irregular(3)            [ 3 ];
     msn             =:= irregular(16)           [ 16 ];
     seq_number      =:= irregular(32)           [ 32 ];
     ack_number      =:=
       zero_or_irreg(ack_zero.CVALUE, 32)        [ 0, 32 ];
     window          =:= irregular(16)           [ 16 ];
     checksum        =:= irregular(16)           [ 16 ];
     urg_ptr         =:=
       zero_or_irreg(urp_zero.CVALUE, 16)        [ 0, 16 ];
     ack_stride      =:=
       static_or_irreg(ack_stride_flag.CVALUE, 16) [ 0, 16 ];
     options         =:= list_tcp_options        [ VARIABLE ];
   }

   COMPRESSED tcp_replicate {
     reserved          =:= '0'                      [ 1 ];
     window_presence   =:= irregular(1)             [ 1 ];
     list_present      =:= irregular(1)             [ 1 ];
     src_port_presence =:= irregular(2)             [ 2 ];
     dst_port_presence =:= irregular(2)             [ 2 ];
     ack_stride_flag   =:= irregular(1)             [ 1 ];
     ack_presence      =:= irregular(1)             [ 1 ];
     urp_presence      =:= irregular(1)             [ 1 ];
     urg_flag          =:= irregular(1)             [ 1 ];
     ack_flag          =:= irregular(1)             [ 1 ];
     psh_flag          =:= irregular(1)             [ 1 ];
     rsf_flags         =:= rsf_index_enc            [ 2 ];
     ecn_used          =:= one_bit_choice           [ 1 ];
     msn               =:= irregular(16)            [ 16 ];
     seq_number        =:= irregular(32)            [ 32 ];
     src_port          =:=
       port_replicate(src_port_presence)      [ 0, 8, 16 ];
     dst_port          =:=
       port_replicate(dst_port_presence)      [ 0, 8, 16 ];
     window            =:=
       static_or_irreg(window_presence, 16)   [ 0, 16 ];
     urg_point         =:=
       static_or_irreg(urp_presence, 16)    [ 0, 16 ];
     ack_number        =:=
       static_or_irreg(ack_presence, 32)    [ 0, 32 ];
     ecn_padding       =:=
       optional_2bit_padding(ecn_used.CVALUE)     [ 0, 2 ];
     tcp_res_flags =:=

       static_or_irreg(ecn_used.CVALUE, 4)        [ 0, 4 ];
     tcp_ecn_flags     =:=
       static_or_irreg(ecn_used.CVALUE, 2)        [ 0, 2 ];
     checksum          =:= irregular(16)            [ 16 ];
     ack_stride        =:=
       static_or_irreg(ack_stride_flag.CVALUE, 16)  [ 0, 16 ];
     options           =:=
       tcp_list_presence_enc(list_present.CVALUE) [ VARIABLE ];
   }

   COMPRESSED tcp_irregular {
     ip_ecn_flags  =:= tcp_irreg_ip_ecn(ip_inner_ecn)  [ 0, 2 ];
     tcp_res_flags =:=
       static_or_irreg(ecn_used.CVALUE, 4)            [ 0, 4 ];
     tcp_ecn_flags =:=
       static_or_irreg(ecn_used.CVALUE, 2)             [ 0, 2 ];
     checksum      =:= irregular(16)                   [ 16 ];
   }
 }

Last stop for this part: the IP-ID encodings, which are where the sequential behaviour lives, and the bits of the inner IP header that ride along with them 🌸✨

 ///////////////////////////////////////////////////
 // Encoding methods used in compressed base headers
 ///////////////////////////////////////////////////

 dscp_enc(flag)
 {
   UNCOMPRESSED {
     dscp [ 6 ];
   }

   COMPRESSED static_enc {
     dscp =:= static [ 0 ];
     ENFORCE(flag == 0);
   }

   COMPRESSED irreg {
     dscp    =:= irregular(6)           [ 6 ];
     padding =:= compressed_value(2, 0) [ 2 ];
     ENFORCE(flag == 1);
   }
 }

 ip_id_lsb(behavior, k, p)
 {
   UNCOMPRESSED {
     ip_id [ 16 ];
   }
   CONTROL {
     ip_id_offset [ 16 ];
     ip_id_nbo    [ 16 ];
   }

   COMPRESSED nbo {
     ip_id_offset =:= lsb(k, p) [ k ];
     ENFORCE(behavior == IP_ID_BEHAVIOR_SEQUENTIAL);
     ENFORCE(ip_id_offset.UVALUE == ip_id.UVALUE - msn.UVALUE);
   }

   COMPRESSED non_nbo {
     ip_id_offset =:= lsb(k, p) [ k ];
     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);
   }
 }

 optional_ip_id_lsb(behavior, indicator)
 {
   UNCOMPRESSED {
     ip_id [ 16 ];
   }

   COMPRESSED short {
     ip_id =:= ip_id_lsb(behavior, 8, 3) [ 8 ];
     ENFORCE((behavior == IP_ID_BEHAVIOR_SEQUENTIAL) ||
             (behavior == IP_ID_BEHAVIOR_SEQUENTIAL_SWAPPED));
     ENFORCE(indicator == 0);
   }

   COMPRESSED long {
     ip_id =:= irregular(16)  [ 16 ];
     ENFORCE((behavior == IP_ID_BEHAVIOR_SEQUENTIAL) ||
             (behavior == IP_ID_BEHAVIOR_SEQUENTIAL_SWAPPED));
     ENFORCE(indicator == 1);
   }
   COMPRESSED not_present {
     ENFORCE((behavior == IP_ID_BEHAVIOR_RANDOM) ||
             (behavior == IP_ID_BEHAVIOR_ZERO));
   }
 }

 dont_fragment(version)
 {
   UNCOMPRESSED {
     df [ 1 ];
   }

   COMPRESSED v4 {
     df =:= irregular(1) [ 1 ];
     ENFORCE(version == 4);
   }

   COMPRESSED v6 {
     df =:= compressed_value(1, 0) [ 1 ];
     ENFORCE(version == 6);
   }
 }

 //////////////////////////////////
 // Actual start of compressed packet formats
 // Important note:
 //   The base header is the compressed representation
 //   of the innermost IP header AND the TCP header.
 //////////////////////////////////

 // ttl_irregular_chain_flag is set by the user if the TTL/Hop Limit
 // of an outer header has changed.  The same value must be passed as
 // an argument to the ipv4/ipv6 encoding methods when extracting
 // the irregular chain items.
 co_baseheader(payload_size, ack_stride_value,
               ttl_irregular_chain_flag)
 {
   UNCOMPRESSED v4 {
     outer_headers  =:= baseheader_outer_headers        [ VARIABLE ];
     version        =:= uncompressed_value(4, 4)        [ 4 ];
     header_length  =:= uncompressed_value(4, 5)        [ 4 ];
     dscp                                               [ 6 ];
     ip_ecn_flags                                       [ 2 ];
     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 ];
     checksum                                           [ 16 ];
     src_addr                                           [ 32 ];
     dest_addr                                          [ 32 ];
     extension_headers =:= baseheader_extension_headers [ VARIABLE ];
     src_port                                           [ 16 ];
     dest_port                                          [ 16 ];
     seq_number                                         [ 32 ];
     ack_number                                         [ 32 ];
     data_offset                                        [ 4 ];
     tcp_res_flags                                      [ 4 ];
     tcp_ecn_flags                                      [ 2 ];
     urg_flag                                           [ 1 ];
     ack_flag                                           [ 1 ];
     psh_flag                                           [ 1 ];
     rsf_flags                                          [ 3 ];
     window                                             [ 16 ];
     tcp_checksum                                       [ 16 ];
     urg_ptr                                            [ 16 ];
     options                           [ (data_offset.UVALUE-5)*32 ];
   }

   UNCOMPRESSED v6 {
     outer_headers  =:= baseheader_outer_headers        [ VARIABLE ];
     version =:= uncompressed_value(4, 6)               [ 4 ];
     dscp                                               [ 6 ];
     ip_ecn_flags                                       [ 2 ];
     flow_label                                         [ 20 ];
     payload_length                                     [ 16 ];
     next_header                                        [ 8 ];
     ttl_hopl                                           [ 8 ];
     src_addr                                           [ 128 ];
     dest_addr                                          [ 128 ];
     extension_headers =:= baseheader_extension_headers [ VARIABLE ];
     src_port                                           [ 16 ];
     dest_port                                          [ 16 ];
     seq_number                                         [ 32 ];
     ack_number                                         [ 32 ];
     data_offset                                        [ 4 ];
     tcp_res_flags                                      [ 4 ];
     tcp_ecn_flags                                      [ 2 ];
     urg_flag                                           [ 1 ];
     ack_flag                                           [ 1 ];
     psh_flag                                           [ 1 ];
     rsf_flags                                          [ 3 ];
     window                                             [ 16 ];
     tcp_checksum                                       [ 16 ];
     urg_ptr                                            [ 16 ];
     options                           [ (data_offset.UVALUE-5)*32 ];
     ENFORCE(ip_id_behavior.UVALUE == IP_ID_BEHAVIOR_RANDOM);
   }

   CONTROL {

     ip_id_behavior                       [ 2 ];
     seq_number_scaled                    [ 32 ];
     seq_number_residue =:=
         field_scaling(payload_size, seq_number_scaled.UVALUE,
                       seq_number.UVALUE) [ 32 ];
     ack_stride                           [ 16 ];
     ack_number_scaled                    [ 32 ];
     ack_number_residue =:=
         field_scaling(ack_stride.UVALUE, ack_number_scaled.UVALUE,
                       ack_number.UVALUE) [ 32 ];
     ENFORCE(ack_stride_value == ack_stride.UVALUE);
   }

   INITIAL {
     ack_stride     =:= uncompressed_value(16, 0);
   }

   DEFAULT {
     tcp_ecn_flags  =:= static;
     data_offset    =:= inferred_offset;
     tcp_res_flags  =:= static;
     rsf_flags      =:= uncompressed_value(3, 0);
     dest_port      =:= static;
     dscp           =:= static;
     src_port       =:= static;
     urg_flag       =:= uncompressed_value(1, 0);
     window         =:= static;
     dest_addr      =:= static;
     version        =:= static;
     ttl_hopl       =:= static;
     src_addr       =:= static;
     df             =:= static;
     ack_number     =:= static;
     urg_ptr        =:= static;
     seq_number     =:= static;
     ack_flag       =:= uncompressed_value(1, 1);
     // The default for "options" is case 2) and 3) from
     // the list in section 6.3.1 (i.e. nothing present in the
     // baseheader itself).
     payload_length =:= inferred_ip_v6_length;
     checksum       =:= inferred_ip_v4_header_checksum;
     length         =:= inferred_ip_v4_length;
     flow_label     =:= static;
     next_header    =:= static;
     ip_ecn_flags   =:= static;
     // The tcp_checksum has no default,
     // it is considered a part of tcp_irregular
     ip_id_behavior =:= static;
     ecn_used       =:= static;

     // Default is to have no TTL in irregular chain
     // Can only be nonzero if co_common is used
     ENFORCE(ttl_irregular_chain_flag == 0);
   }

   ////////////////////////////////////////////
   // Common compressed packet format
   ////////////////////////////////////////////

   COMPRESSED co_common {
     discriminator        =:= '1111101'                [ 7 ];
     ttl_hopl_outer_flag  =:=
         compressed_value(1, ttl_irregular_chain_flag) [ 1 ];
     ack_flag             =:= irregular(1)             [ 1 ];
     psh_flag             =:= irregular(1)             [ 1 ];
     rsf_flags            =:= rsf_index_enc            [ 2 ];
     msn                  =:= lsb(4, 4)                [ 4 ];
     seq_indicator        =:= irregular(2)             [ 2 ];
     ack_indicator        =:= irregular(2)             [ 2 ];
     ack_stride_indicator =:= irregular(1)             [ 1 ];
     window_indicator     =:= irregular(1)             [ 1 ];
     ip_id_indicator      =:= irregular(1)             [ 1 ];
     urg_ptr_present      =:= irregular(1)             [ 1 ];
     reserved             =:= compressed_value(1, 0)   [ 1 ];
     ecn_used             =:= one_bit_choice           [ 1 ];
     dscp_present         =:= irregular(1)             [ 1 ];
     ttl_hopl_present     =:= irregular(1)             [ 1 ];
     list_present         =:= irregular(1)             [ 1 ];
     ip_id_behavior       =:= ip_id_behavior_choice(true)     [ 2 ];
     urg_flag             =:= irregular(1)             [ 1 ];
     df                   =:= dont_fragment(version.UVALUE)   [ 1 ];
     header_crc           =:= crc7(THIS.UVALUE, THIS.ULENGTH) [ 7 ];
     seq_number           =:=
       variable_length_32_enc(seq_indicator.CVALUE) [ 0, 8, 16, 32 ];
     ack_number           =:=
       variable_length_32_enc(ack_indicator.CVALUE) [ 0, 8, 16, 32 ];
     ack_stride           =:=
       static_or_irreg(ack_stride_indicator.CVALUE, 16)  [ 0, 16 ];
     window               =:=
       static_or_irreg(window_indicator.CVALUE, 16)      [ 0, 16 ];
     ip_id                =:=
       optional_ip_id_lsb(ip_id_behavior.UVALUE,
                          ip_id_indicator.CVALUE)      [ 0, 8, 16 ];
     urg_ptr              =:=
       static_or_irreg(urg_ptr_present.CVALUE, 16)     [ 0, 16 ];
     dscp                 =:=

       dscp_enc(dscp_present.CVALUE)                   [ 0, 8 ];
     ttl_hopl             =:=
       static_or_irreg(ttl_hopl_present.CVALUE, 8)     [ 0, 8 ];
     options              =:=
       tcp_list_presence_enc(list_present.CVALUE)      [ VARIABLE ];
   }

   // Send LSBs of sequence number
   COMPRESSED rnd_1 {
     discriminator =:= '101110'                        [ 6 ];
     seq_number    =:= lsb(18, 65535)                  [ 18 ];
     msn           =:= lsb(4, 4)                       [ 4 ];
     psh_flag      =:= irregular(1)                    [ 1 ];
     header_crc    =:= crc3(THIS.UVALUE, THIS.ULENGTH) [ 3 ];
     ENFORCE((ip_id_behavior.UVALUE == IP_ID_BEHAVIOR_RANDOM) ||
             (ip_id_behavior.UVALUE == IP_ID_BEHAVIOR_ZERO));
   }

   // Send scaled sequence number LSBs
   COMPRESSED rnd_2 {
     discriminator      =:= '1100'                          [ 4 ];
     seq_number_scaled  =:= lsb(4, 7)                       [ 4 ];
     msn                =:= lsb(4, 4)                       [ 4 ];
     psh_flag           =:= irregular(1)                    [ 1 ];
     header_crc         =:= crc3(THIS.UVALUE, THIS.ULENGTH) [ 3 ];
     ENFORCE(payload_size != 0);
     ENFORCE((ip_id_behavior.UVALUE == IP_ID_BEHAVIOR_RANDOM) ||
             (ip_id_behavior.UVALUE == IP_ID_BEHAVIOR_ZERO));
   }

   // Send acknowledgment number LSBs
   COMPRESSED rnd_3 {
     discriminator =:= '0'                             [ 1 ];
     ack_number    =:= lsb(15, 8191)                   [ 15 ];
     msn           =:= lsb(4, 4)                       [ 4 ];
     psh_flag      =:= irregular(1)                    [ 1 ];
     header_crc    =:= crc3(THIS.UVALUE, THIS.ULENGTH) [ 3 ];
     ENFORCE((ip_id_behavior.UVALUE == IP_ID_BEHAVIOR_RANDOM) ||
             (ip_id_behavior.UVALUE == IP_ID_BEHAVIOR_ZERO));
   }

   // Send acknowledgment number scaled
   COMPRESSED rnd_4 {
     discriminator      =:= '1101'                          [ 4 ];
     ack_number_scaled  =:= lsb(4, 3)                       [ 4 ];
     msn                =:= lsb(4, 4)                       [ 4 ];
     psh_flag           =:= irregular(1)                    [ 1 ];
     header_crc         =:= crc3(THIS.UVALUE, THIS.ULENGTH) [ 3 ];
     ENFORCE(ack_stride.UVALUE != 0);
     ENFORCE((ip_id_behavior.UVALUE == IP_ID_BEHAVIOR_RANDOM) ||
             (ip_id_behavior.UVALUE == IP_ID_BEHAVIOR_ZERO));
   }

   // Send ACK and sequence number
   COMPRESSED rnd_5 {
     discriminator =:= '100'                           [ 3 ];
     psh_flag      =:= irregular(1)                    [ 1 ];
     msn           =:= lsb(4, 4)                       [ 4 ];
     header_crc    =:= crc3(THIS.UVALUE, THIS.ULENGTH) [ 3 ];
     seq_number    =:= lsb(14, 8191)                   [ 14 ];
     ack_number    =:= lsb(15, 8191)                   [ 15 ];
     ENFORCE((ip_id_behavior.UVALUE == IP_ID_BEHAVIOR_RANDOM) ||
             (ip_id_behavior.UVALUE == IP_ID_BEHAVIOR_ZERO));
   }

okay the rnd crew is absolutely giving everything 💖✨ co_common is the main character here fr fr 👑

   // Send both ACK and scaled sequence number LSBs
   COMPRESSED rnd_6 {
     discriminator      =:= '1010'                          [ 4 ];
     header_crc         =:= crc3(THIS.UVALUE, THIS.ULENGTH) [ 3 ];
     psh_flag           =:= irregular(1)                    [ 1 ];
     ack_number         =:= lsb(16, 16383)                  [ 16 ];
     msn                =:= lsb(4, 4)                       [ 4 ];
     seq_number_scaled  =:= lsb(4, 7)                       [ 4 ];
     ENFORCE(payload_size != 0);
     ENFORCE((ip_id_behavior.UVALUE == IP_ID_BEHAVIOR_RANDOM) ||
             (ip_id_behavior.UVALUE == IP_ID_BEHAVIOR_ZERO));
   }

   // Send ACK and window
   COMPRESSED rnd_7 {
     discriminator =:= '101111'                        [ 6 ];
     ack_number    =:= lsb(18, 65535)                  [ 18 ];
     window        =:= irregular(16)                   [ 16 ];
     msn           =:= lsb(4, 4)                       [ 4 ];
     psh_flag      =:= irregular(1)                    [ 1 ];
     header_crc    =:= crc3(THIS.UVALUE, THIS.ULENGTH) [ 3 ];
     ENFORCE((ip_id_behavior.UVALUE == IP_ID_BEHAVIOR_RANDOM) ||
             (ip_id_behavior.UVALUE == IP_ID_BEHAVIOR_ZERO));
   }

   // An extended packet type for seldom-changing fields
   // Can send LSBs of TTL, RSF flags, change ECN behavior, and
   // options list
   COMPRESSED rnd_8 {
     discriminator =:= '10110'                         [ 5 ];
     rsf_flags     =:= rsf_index_enc                   [ 2 ];
     list_present  =:= irregular(1)                    [ 1 ];
     header_crc    =:= crc7(THIS.UVALUE, THIS.ULENGTH) [ 7 ];
     msn           =:= lsb(4, 4)                       [ 4 ];
     psh_flag      =:= irregular(1)                    [ 1 ];
     ttl_hopl      =:= lsb(3, 3)                       [ 3 ];
     ecn_used      =:= one_bit_choice                  [ 1 ];
     seq_number    =:= lsb(16, 65535)                  [ 16 ];
     ack_number    =:= lsb(16, 16383)                  [ 16 ];
     options       =:=
       tcp_list_presence_enc(list_present.CVALUE)      [ VARIABLE ];
     ENFORCE((ip_id_behavior.UVALUE == IP_ID_BEHAVIOR_RANDOM) ||
             (ip_id_behavior.UVALUE == IP_ID_BEHAVIOR_ZERO));
   }

   // Send LSBs of sequence number
   COMPRESSED seq_1 {
     discriminator =:= '1010'                                 [ 4 ];
     ip_id         =:= ip_id_lsb(ip_id_behavior.UVALUE, 4, 3) [ 4 ];
     seq_number    =:= lsb(16, 32767)                         [ 16 ];
     msn           =:= lsb(4, 4)                              [ 4 ];
     psh_flag      =:= irregular(1)                           [ 1 ];
     header_crc    =:= crc3(THIS.UVALUE, THIS.ULENGTH)        [ 3 ];
     ENFORCE((ip_id_behavior.UVALUE == IP_ID_BEHAVIOR_SEQUENTIAL) ||
             (ip_id_behavior.UVALUE ==
              IP_ID_BEHAVIOR_SEQUENTIAL_SWAPPED));
   }

   // Send scaled sequence number LSBs
   COMPRESSED seq_2 {
     discriminator      =:= '11010'                         [ 5 ];
     ip_id              =:=
       ip_id_lsb(ip_id_behavior.UVALUE, 7, 3)               [ 7 ];
     seq_number_scaled  =:= lsb(4, 7)                       [ 4 ];
     msn                =:= lsb(4, 4)                       [ 4 ];
     psh_flag           =:= irregular(1)                    [ 1 ];
     header_crc         =:= crc3(THIS.UVALUE, THIS.ULENGTH) [ 3 ];
     ENFORCE(payload_size != 0);
     ENFORCE((ip_id_behavior.UVALUE == IP_ID_BEHAVIOR_SEQUENTIAL) ||
             (ip_id_behavior.UVALUE ==
              IP_ID_BEHAVIOR_SEQUENTIAL_SWAPPED));
   }

   // Send acknowledgment number LSBs
   COMPRESSED seq_3 {
     discriminator =:= '1001'                                 [ 4 ];
     ip_id         =:= ip_id_lsb(ip_id_behavior.UVALUE, 4, 3) [ 4 ];
     ack_number    =:= lsb(16, 16383)                         [ 16 ];
     msn           =:= lsb(4, 4)                              [ 4 ];
     psh_flag      =:= irregular(1)                           [ 1 ];
     header_crc    =:= crc3(THIS.UVALUE, THIS.ULENGTH)        [ 3 ];
     ENFORCE((ip_id_behavior.UVALUE == IP_ID_BEHAVIOR_SEQUENTIAL) ||
             (ip_id_behavior.UVALUE ==
              IP_ID_BEHAVIOR_SEQUENTIAL_SWAPPED));
   }

   // Send scaled acknowledgment number scaled
   COMPRESSED seq_4 {
     discriminator     =:= '0'                             [ 1 ];
     ack_number_scaled =:= lsb(4, 3)                       [ 4 ];
     // Due to having very few ip_id bits, no negative offset
     ip_id      =:= ip_id_lsb(ip_id_behavior.UVALUE, 3, 1) [ 3 ];
     msn               =:= lsb(4, 4)                       [ 4 ];
     psh_flag          =:= irregular(1)                    [ 1 ];
     header_crc        =:= crc3(THIS.UVALUE, THIS.ULENGTH) [ 3 ];
     ENFORCE(ack_stride.UVALUE != 0);
     ENFORCE((ip_id_behavior.UVALUE == IP_ID_BEHAVIOR_SEQUENTIAL) ||
             (ip_id_behavior.UVALUE ==
              IP_ID_BEHAVIOR_SEQUENTIAL_SWAPPED));
   }

   // Send ACK and sequence number
   COMPRESSED seq_5 {
     discriminator =:= '1000'                                 [ 4 ];
     ip_id         =:= ip_id_lsb(ip_id_behavior.UVALUE, 4, 3) [ 4 ];
     ack_number    =:= lsb(16, 16383)                         [ 16 ];
     seq_number    =:= lsb(16, 32767)                         [ 16 ];
     msn           =:= lsb(4, 4)                              [ 4 ];
     psh_flag      =:= irregular(1)                           [ 1 ];
     header_crc    =:= crc3(THIS.UVALUE, THIS.ULENGTH)        [ 3 ];
     ENFORCE((ip_id_behavior.UVALUE == IP_ID_BEHAVIOR_SEQUENTIAL) ||
             (ip_id_behavior.UVALUE ==
              IP_ID_BEHAVIOR_SEQUENTIAL_SWAPPED));
   }

   // Send both ACK and scaled sequence number LSBs
   COMPRESSED seq_6 {
     discriminator      =:= '11011'                          [ 5 ];
     seq_number_scaled  =:= lsb(4, 7)                        [ 4 ];
     ip_id        =:= ip_id_lsb(ip_id_behavior.UVALUE, 7, 3) [ 7 ];
     ack_number         =:= lsb(16, 16383)                   [ 16 ];
     msn                =:= lsb(4, 4)                        [ 4 ];
     psh_flag           =:= irregular(1)                     [ 1 ];
     header_crc         =:= crc3(THIS.UVALUE, THIS.ULENGTH)  [ 3 ];
     ENFORCE(payload_size != 0);
     ENFORCE((ip_id_behavior.UVALUE == IP_ID_BEHAVIOR_SEQUENTIAL) ||
             (ip_id_behavior.UVALUE ==

              IP_ID_BEHAVIOR_SEQUENTIAL_SWAPPED));
   }

   // Send ACK and window
   COMPRESSED seq_7 {
     discriminator =:= '1100'                                 [ 4 ];
     window        =:= lsb(15, 16383)                         [ 15 ];
     ip_id         =:= ip_id_lsb(ip_id_behavior.UVALUE, 5, 3) [ 5 ];
     ack_number    =:= lsb(16, 32767)                         [ 16 ];
     msn           =:= lsb(4, 4)                              [ 4 ];
     psh_flag      =:= irregular(1)                           [ 1 ];
     header_crc    =:= crc3(THIS.UVALUE, THIS.ULENGTH)        [ 3 ];
     ENFORCE((ip_id_behavior.UVALUE == IP_ID_BEHAVIOR_SEQUENTIAL) ||
             (ip_id_behavior.UVALUE ==
              IP_ID_BEHAVIOR_SEQUENTIAL_SWAPPED));
   }

   // An extended packet type for seldom-changing fields
   // Can send LSBs of TTL, RSF flags, change ECN behavior, and
   // options list
   COMPRESSED seq_8 {
     discriminator =:= '1011'                                 [ 4 ];
     ip_id         =:= ip_id_lsb(ip_id_behavior.UVALUE, 4, 3) [ 4 ];
     list_present  =:= irregular(1)                           [ 1 ];
     header_crc    =:= crc7(THIS.UVALUE, THIS.ULENGTH)        [ 7 ];
     msn           =:= lsb(4, 4)                              [ 4 ];
     psh_flag      =:= irregular(1)                           [ 1 ];
     ttl_hopl      =:= lsb(3, 3)                              [ 3 ];
     ecn_used      =:= one_bit_choice                         [ 1 ];
     ack_number    =:= lsb(15, 8191)                          [ 15 ];
     rsf_flags     =:= rsf_index_enc                          [ 2 ];
     seq_number    =:= lsb(14, 8191)                          [ 14 ];
     options       =:=
       tcp_list_presence_enc(list_present.CVALUE)       [ VARIABLE ];
     ENFORCE((ip_id_behavior.UVALUE == IP_ID_BEHAVIOR_SEQUENTIAL) ||
             (ip_id_behavior.UVALUE ==
              IP_ID_BEHAVIOR_SEQUENTIAL_SWAPPED));
   }
 }

8.3. Feedback Formats and Options 🎀

8.3.1. Feedback Formats

okay so this section is all about the feedback formats for the ROHC-TCP profile 💅 and it builds on the general ROHC feedback format from Section 5.2.3 of [RFC4995] 🧚

every feedback format carries a field labeled MSN 🌸 the MSN field holds the LSBs of the MSN control field from Section 6.1.1. the sequence number to use is the MSN for the last header that was successfully CRC-8 validated or CRC verified ✨

FEEDBACK-1

     0   1   2   3   4   5   6   7
   +---+---+---+---+---+---+---+---+
   |              MSN              |
   +---+---+---+---+---+---+---+---+

   MSN: The LSB-encoded master sequence number.

a FEEDBACK-1 is an ACK 💖 if you want to send a NACK or a STATIC-NACK, you need FEEDBACK-2 instead

FEEDBACK-2

     0   1   2   3   4   5   6   7
   +---+---+---+---+---+---+---+---+
   |Acktype|          MSN          |
   +---+---+---+---+---+---+---+---+
   |              MSN              |
   +---+---+---+---+---+---+---+---+
   |              CRC              |
   +---+---+---+---+---+---+---+---+
   /       Feedback options        /
   +---+---+---+---+---+---+---+---+

the fields in FEEDBACK-2 work like this 🦋

  • Acktype:
    • 0 = ACK
    • 1 = NACK
    • 2 = STATIC-NACK
    • 3 is reserved (MUST NOT be used for parsability) 🙈
  • MSN: The LSB-encoded master sequence number.
  • CRC: 8-bit CRC computed over the entire feedback element (as defined in Section 5.3.1.1 of [RFC4995]). For the purpose of computing the CRC, the CRC field is zero. The CRC is calculated using the polynomial defined in [RFC4995].
  • Feedback options: A variable number of feedback options, see Section 8.3.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 the one matching the MSN of the feedback element, unless the MSN-NOT-VALID option (Section 8.3.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 the compressor gets FEEDBACK-2, it MUST verify the info 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 💀

8.3.2. Feedback Options

a ROHC-TCP feedback option has variable length and this general format 🍓

     0   1   2   3   4   5   6   7
   +---+---+---+---+---+---+---+---+
   |   Opt Type    |    Opt Len    |
   +---+---+---+---+---+---+---+---+
   /          option data          /  Opt Length (octets)
   +---+---+---+---+---+---+---+---+

each ROHC-TCP feedback option can appear at most once within a FEEDBACK-2 ✨

8.3.2.1. The REJECT Option

the REJECT option tells the compressor that the decompressor doesn't have enough resources to handle the flow 😭

   +---+---+---+---+---+---+---+---+
   |  Opt Type = 2 |  Opt Len = 0  |
   +---+---+---+---+---+---+---+---+

when the compressor gets a REJECT option, it MUST stop compressing the packet flow, and 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 🥹

8.3.2.2. The MSN-NOT-VALID Option ✨

Okay so the MSN-NOT-VALID option is the bestie that tells the compressor "hey, the MSN on this feedback is not valid" 🙅‍♀️💅

  +---+---+---+---+---+---+---+---+
  |  Opt Type = 3 |  Opt Len = 0  |
  +---+---+---+---+---+---+---+---+

A compressor MUST ignore the MSN of the feedback element when this option is present 🫶 So a NACK or a STATIC-NACK feedback type sent with the MSN-NOT-VALID option counts as a STATIC-NACK when it comes to what the feedback message means 🎀

The MSN-NOT-VALID option MUST NOT appear more than once in the FEEDBACK-2 format, and it MUST NOT show up in the same feedback element as the MSN option. If either of those happens, the compressor MUST discard the entire feedback element 💀

8.3.2.3. The MSN Option 🦋

The MSN option gives you 2 extra bits of MSN, so it's giving more room 😤

  +---+---+---+---+---+---+---+---+
  |  Opt Type = 4 |  Opt Len = 1  |
  +---+---+---+---+---+---+---+---+
  |  MSN  |        Reserved       |
  +---+---+---+---+---+---+---+---+

These 2 bits are the least significant bits of the MSN. They get glued onto the 14 bits that are already in the FEEDBACK-2 format 🧚

The MSN option MUST NOT appear more than once in the FEEDBACK-2 format. It also MUST NOT appear in the same feedback element as the MSN-NOT-VALID option. If it does, the compressor MUST discard the entire feedback element 🙈

8.3.2.4. The CONTEXT_MEMORY Feedback Option 🧠

The CONTEXT_MEMORY option is the decompressor saying "I literally do not have enough memory to handle the context of this packet flow the way it's being compressed right now" 😭

    0   1   2   3   4   5   6   7
  +---+---+---+---+---+---+---+---+
  |  Opt Type = 9 |  Opt Len = 0  |
  +---+---+---+---+---+---+---+---+

When a compressor receives a CONTEXT_MEMORY option, it SHOULD take actions to compress the packet flow in a way that needs less decompressor memory, or it SHOULD stop compressing the packet flow 🌸

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 💅

8.3.2.5. Unknown Option Types 🔍

If a compressor runs into an option type it doesn't know, it MUST keep parsing the rest of the FEEDBACK element. This works because the length of each option is explicit 🌈 The compressor MUST otherwise ignore the unknown option, so no drama 💖

9. Security Considerations 🔐

Okay, bestie, this section is serious, so read it carefully 😤

A malfunctioning or malicious header compressor could make the header decompressor rebuild packets that don't match the original packets, but still have valid IP and TCP headers, and possibly valid TCP checksums too 😱 Corruption like that may be caught by end-to-end authentication and integrity mechanisms, because header compression doesn't affect those. On top of that, this header compression scheme uses an internal checksum to verify reconstructed headers. That makes it less likely that decompressed headers which don't match the originals slip by without anyone noticing 🛡️

Denial-of-service attacks are possible if an intruder can inject (for example) bogus IR, CO, or FEEDBACK packets onto the link, and that can make compression efficiency drop 📉 But an intruder who can inject arbitrary packets at the link layer like that raises security issues that totally dwarf the ones related to using header compression 💀

10. IANA Considerations 🏷️

The ROHC profile identifier 0x0006 has been reserved by the IANA for the profile defined in this document ✨

A ROHC profile identifier has been reserved by the IANA for the profile defined in this document. Profiles 0x0000-0x0005 were reserved before this one, and this profile is 0x0006. Just like for earlier ROHC profiles, profile numbers 0xnn06 have been reserved for future updates of this profile 🎀

     Profile             Usage            Document
     identifier

     0x0006              ROHC TCP         [RFC4996]
     0xnn06              Reserved

11. Acknowledgments 🫶

The authors would like to thank Qian Zhang, Hong Bin Liao, Richard Price, and Fredrik Lindstroem for their work on early versions of this specification. Thanks also to Robert Finking and Carsten Bormann for valuable input 💜

Additional thanks go to committed reviewers Joe Touch and Ted Faber, who reviewed this document during working group last-call. Sally Floyd also gave a review, at the request of the Transport Area Directors 👑

12. References 📚

12.1. Normative References

[RFC0791]  Postel, J., "Internet Protocol", STD 5, RFC 791,
           September 1981.

[RFC0793]  Postel, J., "Transmission Control Protocol", STD 7,
           RFC 793, 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.

[RFC4164]  Pelletier, G., "RObust Header Compression (ROHC): Context
           Replication for ROHC Profiles", RFC 4164, August 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.

12.2. Informative References

[RFC1144]  Jacobson, V., "Compressing TCP/IP headers for low-speed
           serial links", RFC 1144, February 1990.

[RFC1323]  Jacobson, V., Braden, B., and D. Borman, "TCP Extensions
           for High Performance", RFC 1323, May 1992.

[RFC2018]  Mathis, M., Mahdavi, J., Floyd, S., and A. Romanow, "TCP
           Selective Acknowledgment Options", RFC 2018, October 1996.

[RFC2507]  Degermark, M., Nordgren, B., and S. Pink, "IP Header
           Compression", RFC 2507, February 1999.

[RFC2581]  Allman, M., Paxson, V., and W. Stevens, "TCP Congestion
           Control", RFC 2581, April 1999.

[RFC2883]  Floyd, S., Mahdavi, J., Mathis, M., and M. Podolsky, "An
           Extension to the Selective Acknowledgement (SACK) Option
           for TCP", RFC 2883, July 2000.

[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.

[RFC3168]  Ramakrishnan, K., Floyd, S., and D. Black, "The Addition
           of Explicit Congestion Notification (ECN) to IP",
           RFC 3168, September 2001.

[RFC3759]  Jonsson, L-E., "RObust Header Compression (ROHC):
           Terminology and Channel Mapping Examples", RFC 3759,
           April 2004.

[RFC4163]  Jonsson, L-E., "RObust Header Compression (ROHC):
           Requirements on TCP/IP Header Compression", RFC 4163,
           August 2005.

[RFC4224]  Pelletier, G., Jonsson, L-E., and K. Sandlund, "RObust
           Header Compression (ROHC): ROHC over Channels That Can
           Reorder Packets", RFC 4224, January 2006.

[RFC4413]  West, M. and S. McCann, "TCP/IP Field Behavior", RFC 4413,
           March 2006.

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


Lars-Erik Jonsson
Optand 737
Ostersund  SE-831 92
Sweden

Phone: +46 70 365 20 58
EMail: lars-erik@lejonsson.com


Mark A West
Siemens/Roke Manor
Roke Manor Research Ltd.
Romsey, Hampshire  SO51 0ZN
UK

Phone: +44 1794 833311
EMail: mark.a.west@roke.co.uk
URI:   http://www.roke.co.uk

Copyright (C) The IETF Trust (2007).

This document is subject to the rights, licenses and restrictions contained in BCP 78, and except as set forth therein, the authors retain all their rights.

This document and the information contained herein 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 takes no position regarding the validity or scope of any Intellectual Property Rights or other rights that might be claimed to pertain to the implementation or use of the technology described in this document, or the extent to which any license under such rights might or might not be available. Nor does it represent that it has made any independent effort to identify any such rights. Information on the procedures with respect to rights in RFC documents can be found in BCP 78 and BCP 79.

Copies of IPR disclosures made to the IETF Secretariat, and any assurances of licenses to be made available, or the result of an attempt made to obtain a general license or permission for the use of such proprietary rights by implementers or users of this specification, can be obtained from the IETF on-line IPR repository at http://www.ietf.org/ipr.

The IETF invites any interested party to bring to its attention any copyrights, patents or patent applications, or other proprietary rights that may cover technology that may be required to implement this standard. Please address the information to the IETF at ietf-ipr@ietf.org.

Acknowledgement 💖

Funding for the RFC Editor function is currently provided by the Internet Society. 🌷✨

📦 where this page comes from