RFC 4997

Formal Notation for RObust Header Compression (ROHC-FN)

๐Ÿ“… published July 2007, from ietf

RFC 4997: Formal Notation for RObust Header Compression (ROHC-FN) ๐ŸŽ€

okay bestie, this is RFC 4997, and it's giving "we built a spec-writing language for compressed headers" ๐Ÿซถ It's an IETF Network Working Group doc from July 2007, and it's on the Standards Track โœจ so it's a real internet standards-track protocol doc, not just somebody's opinion ๐Ÿ’…

The authors are R. Finking from Siemens/Roke Manor Research and G. Pelletier from Ericsson ๐Ÿ‘‘ The title block doesn't list any RFCs it obsoletes or updates, so nothing is getting replaced or patched here ๐ŸŒธ


Status of This Memo ๐Ÿ“œ

So this doc is a standards track protocol spec for the whole Internet community, and it's literally asking you to chime in with discussion and suggestions for improvements ๐Ÿฆ‹ For the current standardization state and status, check the current edition of the "Internet Official Protocol Standards" (STD 1). And distribution of this memo is unlimited, so share away ๐Ÿ’œ

Copyright (C) The IETF Trust (2007). ๐ŸŒˆ

Abstract ๐ŸŒŸ

This doc defines Robust Header Compression - Formal Notation (ROHC-FN), which is a formal notation for specifying field encodings for compressed formats when you're defining new profiles in the ROHC framework ๐Ÿงš ROHC-FN comes with a library of encoding methods that show up a lot in ROHC profiles, so it can help make future profile work way less painful โœจ

Table of Contents ๐Ÿ—‚๏ธ

1.  Introduction . . . . . . . . . . . . . . . . . . . . . . . . .  4
2.  Terminology  . . . . . . . . . . . . . . . . . . . . . . . . .  4
3.  Overview of ROHC-FN  . . . . . . . . . . . . . . . . . . . . .  5
  3.1.  Scope of the Formal Notation . . . . . . . . . . . . . . .  6
  3.2.  Fundamentals of the Formal Notation  . . . . . . . . . . .  7
    3.2.1.  Fields and Encodings . . . . . . . . . . . . . . . . .  7
    3.2.2.  Formats and Encoding Methods . . . . . . . . . . . . .  9
  3.3.  Example Using IPv4 . . . . . . . . . . . . . . . . . . . . 11
4.  Normative Definition of ROHC-FN  . . . . . . . . . . . . . . . 13
  4.1.  Structure of a Specification . . . . . . . . . . . . . . . 13
  4.2.  Identifiers  . . . . . . . . . . . . . . . . . . . . . . . 14
  4.3.  Constant Definitions . . . . . . . . . . . . . . . . . . . 15
  4.4.  Fields . . . . . . . . . . . . . . . . . . . . . . . . . . 16
    4.4.1.  Attribute References . . . . . . . . . . . . . . . . . 17
    4.4.2.  Representation of Field Values . . . . . . . . . . . . 17

  4.5.  Grouping of Fields . . . . . . . . . . . . . . . . . . . . 17
  4.6.  "THIS" . . . . . . . . . . . . . . . . . . . . . . . . . . 18
  4.7.  Expressions  . . . . . . . . . . . . . . . . . . . . . . . 19
    4.7.1.  Integer Literals . . . . . . . . . . . . . . . . . . . 20
    4.7.2.  Integer Operators  . . . . . . . . . . . . . . . . . . 20
    4.7.3.  Boolean Literals . . . . . . . . . . . . . . . . . . . 20
    4.7.4.  Boolean Operators  . . . . . . . . . . . . . . . . . . 20
    4.7.5.  Comparison Operators . . . . . . . . . . . . . . . . . 21
  4.8.  Comments . . . . . . . . . . . . . . . . . . . . . . . . . 21
  4.9.  "ENFORCE" Statements . . . . . . . . . . . . . . . . . . . 22
  4.10. Formal Specification of Field Lengths  . . . . . . . . . . 23
  4.11. Library of Encoding Methods  . . . . . . . . . . . . . . . 24
    4.11.1. uncompressed_value . . . . . . . . . . . . . . . . . . 24
    4.11.2. compressed_value . . . . . . . . . . . . . . . . . . . 25
    4.11.3. irregular  . . . . . . . . . . . . . . . . . . . . . . 26
    4.11.4. static . . . . . . . . . . . . . . . . . . . . . . . . 27
    4.11.5. lsb  . . . . . . . . . . . . . . . . . . . . . . . . . 27
    4.11.6. crc  . . . . . . . . . . . . . . . . . . . . . . . . . 29
  4.12. Definition of Encoding Methods . . . . . . . . . . . . . . 29
    4.12.1. Structure  . . . . . . . . . . . . . . . . . . . . . . 30
    4.12.2. Arguments  . . . . . . . . . . . . . . . . . . . . . . 37
    4.12.3. Multiple Formats . . . . . . . . . . . . . . . . . . . 38
  4.13. Profile-Specific Encoding Methods  . . . . . . . . . . . . 40
5.  Security Considerations  . . . . . . . . . . . . . . . . . . . 41
6.  Contributors . . . . . . . . . . . . . . . . . . . . . . . . . 41
7.  Acknowledgements . . . . . . . . . . . . . . . . . . . . . . . 41
8.  References . . . . . . . . . . . . . . . . . . . . . . . . . . 42
  8.1.  Normative References . . . . . . . . . . . . . . . . . . . 42
  8.2.  Informative References . . . . . . . . . . . . . . . . . . 42
Appendix A.  Formal Syntax of ROHC-FN  . . . . . . . . . . . . . . 43
Appendix B.  Bit-level Worked Example  . . . . . . . . . . . . . . 45
  B.1.  Example Packet Format  . . . . . . . . . . . . . . . . . . 45
  B.2.  Initial Encoding . . . . . . . . . . . . . . . . . . . . . 46
  B.3.  Basic Compression  . . . . . . . . . . . . . . . . . . . . 47
  B.4.  Inter-Packet Compression . . . . . . . . . . . . . . . . . 48
  B.5.  Specifying Initial Values  . . . . . . . . . . . . . . . . 50
  B.6.  Multiple Packet Formats  . . . . . . . . . . . . . . . . . 51
  B.7.  Variable Length Discriminators . . . . . . . . . . . . . . 53
  B.8.  Default Encoding . . . . . . . . . . . . . . . . . . . . . 55
  B.9.  Control Fields . . . . . . . . . . . . . . . . . . . . . . 56
  B.10. Use of "ENFORCE" Statements as Conditionals  . . . . . . . 59

1. Introduction ๐Ÿฆ‹

ROHC-FN is a formal notation built to help define ROHC [RFC4995] header compression profiles ๐Ÿ’– Earlier profiles were written with English text plus ASCII Box notation, and that was sometimes unclear and ambiguous. It showed how hard it gets to define complex structures and encodings for compressed formats that way ๐Ÿ˜ญ The main goal of the Formal Notation is a more rigorous way to define header formats (compressed and uncompressed) and the relationships between them. No other formal notation exists that meets these requirements, so ROHC-FN is going for it ๐Ÿ’…

ROHC-FN also comes with a library of encoding methods that get used a lot in ROHC profiles. That means you can specify a new profile with the formal notation without rebuilding this whole library from scratch ๐Ÿงš Basically, an encoding method is a two-way mapping between uncompressed data and compressed data ๐Ÿ”ฅ

2. Terminology ๐Ÿ“š

  • Compressed format: A compressed format is a list of fields that binds encodings to the fields they compress. You can combine one or more compressed formats to represent an entire compressed header format ๐ŸŽ€
  • Context: Context is info about the current (de)compression state of the flow. A context for a specific field can be uninitialised, or it can hold a set of one or more values for the field's attributes that the compression algorithm defines, where a value may come from the field's attributes in a previous packet. See also the more generalised definition in Section 2.2 of [RFC4995] ๐ŸŒธ
  • Control field: Control fields get sent from a ROHC compressor to a ROHC decompressor, but they aren't part of the uncompressed header itself ๐Ÿซถ
  • Encoding method, encodings: Encoding methods are two-way relations that can be applied to compress and decompress fields of a protocol header ๐Ÿ’œ
  • Field: The protocol header is split into a set of contiguous bit patterns called fields. Each field is defined by a collection of attributes that say its value and its length in bits, for both the compressed and uncompressed headers. How the header gets divided into fields is specific to a profile's definition, and the field divisions don't have to match the ones in the specification(s) for the header being compressed ๐Ÿฆ‹
  • Library of encoding methods: The library of encoding methods holds a bunch of commonly used encoding methods for compressing header fields โœจ
  • Profile: A ROHC [RFC4995] profile describes how to compress a certain protocol stack. Each profile has a set of formats (like uncompressed and compressed formats) plus a set of rules that control compressor and decompressor behaviour ๐Ÿ‘‘
  • ROHC-FN specification: The specification of the set of formats of a ROHC profile, written using ROHC-FN ๐ŸŒˆ
  • Uncompressed format: An uncompressed format is a list of fields that gives the order of the fields to be compressed, for a contiguous set of bits whose bit layout matches the protocol header being compressed ๐Ÿ“

3. Overview of ROHC-FN ๐ŸŒŸ

This section gives you the overview of ROHC-FN ๐Ÿงš It also explains how ROHC-FN can be used to specify the compression of header fields as part of a ROHC profile ๐Ÿ’–

3.1. Scope of the Formal Notation ๐ŸŽ€

This section explains how the formal notation connects to the ROHC framework and to specifications of ROHC profiles ๐ŸŒธ

The ROHC framework [RFC4995] sets the general principles for robust header compression. It defines the concept of a profile, which makes ROHC a general platform for different compression schemes ๐Ÿฆ‹ It sets link layer requirements, and in particular negotiation requirements, for all ROHC profiles. It defines common functions like Context Identifiers (CIDs), padding, and segmentation โœจ It also defines common formats (IR, IR-DYN, Feedback, Add-CID, etc.), and finally it defines a generic, profile independent, feedback mechanism ๐Ÿ’œ

A ROHC profile describes how to compress a certain protocol stack. For example, ROHC profiles exist for RTP/UDP/IP and many other protocol stacks ๐Ÿ‘‘

At a high level, each ROHC profile has a set of formats (defining the bits to be transmitted) plus a set of rules that control compressor and decompressor behaviour ๐Ÿ”ฅ The formats are there to define how to compress and decompress headers. They define one or more compressed versions of each uncompressed header, and they also define the inverse: how to relate a compressed header back to the original uncompressed header ๐Ÿซถ

The set of formats will typically define compression of headers relative to a context of field values from previous headers in a flow. That way the overall compression gets better by using the redundancies between headers of successive packets ๐Ÿ“ So, on top of defining the formats, a profile has to:

  • specify how to manage the context for both the compressor and the decompressor,
  • define when and what to send in feedback messages, if any, from decompressor to compressor,
  • outline compression principles to make the profile robust against bit errors and dropped packets.

All of this is needed to keep the compressor and decompressor contexts consistent with each other, while still getting the best possible compression performance ๐ŸŒˆ

ROHC-FN is designed to help specify the compressed formats that, when put together based on the profile definition, make up the formats used in a ROHC profile ๐Ÿงš It offers a library of encoding methods for compressing fields, plus a mechanism for combining those methods to create compressed formats tailored to a specific protocol stack โœจ

The scope of ROHC-FN is limited to specifying the relationship between the compressed and uncompressed formats. To form a complete profile specification, the control logic for the profile behaviour has to be defined by other means ๐Ÿ’…


Part 1 of 12 is done ๐ŸŽ€ Part 2 coming up next ๐Ÿ‘€

3.2. Fundamentals of the Formal Notation ๐Ÿฆ‹

okay so this formal notation is built on two fundamental elements, and they're the core of the whole thing ๐Ÿ’…

  1. Fields and their encodings ๐Ÿ’–: these define the mapping between a header's uncompressed form and its compressed form.
  2. Encoding methods โœจ: these define the way headers get broken down into fields. They define lists of uncompressed fields and the lists of compressed fields they map onto.

we'll unpack both of these below, bestie ๐Ÿงš

3.2.1. Fields and Encodings ๐ŸŽ€

headers are made up of fields! version number, header length, and sequence number are all fields used in real protocols, no cap ๐ŸŒธ

fields have attributes, and attributes describe different things about the field. for example:

field.ULENGTH

that one tells you the uncompressed length of the field ๐Ÿ“. a field is said to have a value attribute (a compressed value or an uncompressed value) if the corresponding length attribute is greater than zero. see Section 4.4 for more details on field attributes ๐Ÿ’œ

the relationship between the compressed and uncompressed attributes of a field gets specified with encoding methods, using this notation:

field   =:=   encoding_method;

in that definition, =:= means "is encoded by" ๐Ÿฆ‹. but here's the twist: it is not an assignment from the right side to the left side! it's a two-way mapping between the compressed and uncompressed attributes. one definition covers both the compression and the decompression operation through two-way matching. iconic, honestly ๐Ÿ”ฅ

two-way matching is a binary operation that tries to make the operands match. it's kind of like unification in logic. the operands represent one unspecified data object and one specified object, and values can be matched from either operand ๐Ÿงฉ

here's how it plays out:

  • during compression, the uncompressed attributes are already defined. the given encoding matches the compressed attributes against them.
  • during decompression, the compressed attributes are already defined, so the uncompressed attributes get matched to them using the given encoding method.

so compression AND decompression are defined by a single field definition. main character energy ๐Ÿ‘‘

so an encoding method (including any parameters it's given) creates a reversible binding between the attributes of a field ๐Ÿ”. at the compressor, a format can be used if a set of bindings that works for all the attributes in all its fields can be found. at the decompressor, the operation gets reversed using the same bindings, and the attributes in each field are filled in according to those bindings. if the binding for an attribute fails, decoding fails โŒ

for example, the "static" encoding method creates a binding between the attribute for the uncompressed value of the field and the matching value of the field in the context ๐ŸŒˆ

  • for the compressor, the "static" binding is successful when both the context value and the uncompressed value are the same. if the two values differ, the binding fails.
  • for the decompressor, the "static" binding succeeds only if a valid context entry containing the value of the uncompressed field exists. otherwise, the binding fails.

both the compressed and uncompressed forms of each field are represented as a string of bits, most significant bit first, of the length given by the length attribute ๐Ÿ“. the bit string is the binary representation of the field's value attribute, modulo 2^length, where length is the length attribute of the field. BUT (and this is important, bestie ๐Ÿ‘€) that's only how the bits get exchanged between the compressor and the decompressor, designed for maximum compression efficiency. the FN itself uses the full range of integers. see Section 4.4.2 for further details โœจ

3.2.2. Formats and Encoding Methods ๐Ÿง

the ROHC-FN gives you a library of commonly used encoding methods ๐Ÿ“š. encoding methods can be defined in plain English, or with a formal definition made up of things like a collection of expressions (Section 4.7) and ENFORCE statements (Section 4.9) ๐Ÿ’…

ROHC-FN also has mechanisms for combining fields and their encoding methods into higher level encoding methods, following a well-defined structure. it's kind of like functions and procedures in a regular programming language ๐Ÿซถ. this lets complexity get broken down into manageable parts. new encoding methods are defined at the top level of a profile, and then they can be used inside the definition of other higher level encoding methods, and so on ๐ŸŒธ

here's an example:

new_encoding_method         // This block is an encoding method
{
  UNCOMPRESSED {            // This block is an uncompressed format
    field_1   [ 16 ];
    field_2   [ 32 ];
    field_3   [ 48 ];
  }

  CONTROL {                 // This block defines control fields
    ctrl_field_1;
    ctrl_field_2;
  }

  DEFAULT {                 // This block defines default encodings
                            // for specified fields
    ctrl_field_2 =:= encoding_method_2;
    field_1      =:= encoding_method_1;
  }

  COMPRESSED format_0 {     // This block is a compressed format
    field_1;
    field_2      =:= encoding_method_2;
    field_3      =:= encoding_method_3;
    ctrl_field_1 =:= encoding_method_4;
    ctrl_field_2;
  }
  COMPRESSED format_1 {     // This block is a compressed format
    field_1;
    field_2      =:= encoding_method_3;
    field_3      =:= encoding_method_4;
    ctrl_field_2 =:= encoding_method_5;
    ctrl_field_3 =:= encoding_method_6; // This is a control field
                                        // with no uncompressed value
  }
}

so in that example, the encoding method being defined is called new_encoding_method ๐ŸŽ€. the section headed UNCOMPRESSED shows the order of the fields in the uncompressed header (the uncompressed header format). the number of bits in each field is in square brackets. next up is the CONTROL section, which defines two control fields ๐ŸŽ›๏ธ. then comes the DEFAULT section, which sets default encoding methods for two of the fields (see below) ๐Ÿ’œ. last but not least, there are two alternative compressed formats, each in a section headed COMPRESSED ๐Ÿฆ‹

the fields that show up in the compressed formats are one of these:

  • fields that occur in the uncompressed format; or
  • control fields that have an uncompressed value and that occur in the CONTROL section; or
  • control fields that do not have an uncompressed value, and so are defined as part of the compressed format.

every one of these formats is built around a field list ๐Ÿ“. a field list defines the fields in the format and also the order they appear in. for the DEFAULT and CONTROL sections, field order is not significant ๐Ÿ™ˆ

on top of field order, the field list can also specify bindings for any or all of the fields in it. fields with no bindings defined for them get bound using the default bindings from the DEFAULT section (see Section 4.12.1.5) ๐ŸŒˆ

fields in the compressed format have the same names as they do in the uncompressed format. if there are any fields that exist ONLY in the compressed format but DO have an uncompressed value, they must be declared in the CONTROL section of the encoding method definition (see Section 4.12.1.3 for more details on defining control fields) โญ

fields with no uncompressed value don't appear in an UNCOMPRESSED field list, and they don't have to appear in the CONTROL field list either. instead, they just get declared in the compressed field lists where they're used ๐Ÿงš

in the example above, every field that appears in the compressed format is also found in the uncompressed format or in the control field list, EXCEPT for ctrl_field_3. that's allowed because ctrl_field_3 has no "uncompressed" value at all. fields like a checksum on the compressed information fall into this category ๐Ÿ”

3.3. Example Using IPv4 โœจ

okay so this section is a walkthrough example ๐Ÿ’– it builds the notation for an encoding method that can compress one well-known header, the IPv4 header [RFC791]. it's giving tutorial, bestie ๐ŸŽ€

first up, we need the overall shape of the IPv4 header. we call the encoding method "ipv4_header" (more on defining encoding methods in Section 4.12). it looks like this:

ipv4_header
{

the opening brace is where the definition starts (see Section 4.12). ๐Ÿงš

everything inside those braces is local to "ipv4_header". this scoping keeps track of which fields belong to which formats, and it's super useful for complex protocol stacks with several headers that reuse the same field names (see Section 4.2). main character organisation energy ๐Ÿ‘‘

next, we list the fields in the uncompressed IPv4 header. that's the uncompressed format the encoding method will use to define one or more compressed formats. we write it in ROHC-FN like this:

    UNCOMPRESSED {
      version         [  4 ];
      header_length   [  4 ];
      dscp            [  6 ];
      ecn             [  2 ];
      length          [ 16 ];
      id              [ 16 ];
      reserved        [  1 ];
      dont_frag       [  1 ];
      more_fragments  [  1 ];
      offset          [ 13 ];
      ttl             [  8 ];
      protocol        [  8 ];
      checksum        [ 16 ];
      src_addr        [ 32 ];
      dest_addr       [ 32 ];
    }

the number in square brackets is each field's width. this part is just there to help you follow along ๐Ÿ™ˆ indicating field lengths like this is optional, because the width can usually be worked out from the encoding used to compress and decompress the field in a given format. this bit is formally defined in Section 4.10.

now the compressed format ๐ŸŒธ this includes the encoding for each field that maps between the compressed and uncompressed forms. most of these encodings come from the ROHC-FN library (see Section 4.11). since this is about showing how the notation works, not the best possible way to compress IPv4 headers, the example only uses three encoding methods.

the first is "uncompressed_value" (defined in Section 4.11.1). it can compress any field whose uncompressed length and value are fixed, or can be worked out with an expression. no compressed bits have to be sent, because the decompressor can rebuild the field from its known size and value. this one is used on five fields in the IPv4 header:

    COMPRESSED {
      header_length  =:= uncompressed_value(4, 5);
      version        =:= uncompressed_value(4, 4);
      reserved       =:= uncompressed_value(1, 0);
      offset         =:= uncompressed_value(13, 0);
      more_fragments =:= uncompressed_value(1, 0);

the first parameter is the uncompressed field's length in bits, and the second is its integer value ๐Ÿ’…

the order of fields in the compressed format doesn't have to match the order in the uncompressed format. โœจ

the second is "irregular" (defined in Section 4.11.3). it can encode any field where both uncompressed attributes (ULENGTH and UVALUE) are defined, and where ULENGTH is either fixed or can be calculated with an expression. it's the fail-safe option for when no other encoding method fits. every bit of the uncompressed field also shows up in the compressed form, so this one doesn't compress anything at all ๐Ÿ˜ญ but it's always there when you need it ๐Ÿซถ

      src_addr       =:= irregular(32);
      dest_addr      =:= irregular(32);
      length         =:= irregular(16);
      id             =:= irregular(16);
      ttl            =:= irregular(8);
      protocol       =:= irregular(8);
      dscp           =:= irregular(6);
      ecn            =:= irregular(2);
      dont_frag      =:= irregular(1);

the third is only used with the uncompressed format defined above for the IPv4 header: "inferred_ip_v4_header_checksum".

      checksum       =:= inferred_ip_v4_header_checksum [ 0 ];
    }
  }

"inferred_ip_v4_header_checksum" is different from the other two because it isn't in the ROHC-FN library. its definition could go either in the formal notation as part of the profile definition itself (see Section 4.12), or in plain English text (see Section 4.13). ๐Ÿฆ‹

in this example, "inferred_ip_v4_header_checksum" is a specific encoding method that calculates the IP checksum from the rest of the header values. just like "uncompressed_value", it sends no compressed bits, since the decompressor can reconstruct the field value. that's shown explicitly by writing a length of 0 in square brackets for the checksum field in the compressed format. the notation is optional, since the encoding method itself is defined as sending zero compressed bits, but it helps the reader to include it (see Section 4.10 for details on this part of the notation). โœจ

finally, the format definition ends with a closing brace. the example now defines a compressed format that can represent the entire compressed IPv4 header. it also gives enough information for an implementation to build the compressed format from an uncompressed one (compression), and to go the other way (decompression). iconic ๐Ÿ’œ

4. Normative Definition of ROHC-FN ๐ŸŒˆ

this section is the normative definition of ROHC-FN. ROHC-FN is a declarative language that is referentially transparent, with no side effects. so whenever you evaluate an expression, obtaining its value has no other effects. the same expression is guaranteed to have the same value wherever it shows up in the notation, and you can always swap it for its value in any format it appears in (subject to the scope rules for identifiers in Section 4.2). ๐Ÿ”ฅ

the formal notation describes the structure of the formats and how their uncompressed and compressed forms relate. it doesn't describe how compression and decompression are actually carried out. ๐Ÿ“

in a few places in this section, text inside angle brackets is used as a descriptive placeholder. those brackets are only there for the reader. the brackets and their contents are not part of the notation. ๐Ÿ™ˆ

4.1. Structure of a Specification ๐Ÿ’…

the specification of a ROHC profile's compressed formats written in ROHC-FN is called a ROHC-FN specification. ROHC-FN specifications are case sensitive and are written in the 7-bit ASCII character set (as defined in [RFC2822]). they consist of a sequence of zero or more constant definitions (Section 4.3), an optional global control field list (Section 4.12.1.3), and one or more encoding method definitions (Section 4.12).

encoding methods can be defined using the formal notation, or they can be predefined encoding methods. โœจ

encoding methods defined with the formal notation give one or more uncompressed formats for the uncompressed header, plus one or more compressed formats. these formats are linked to each other by "fields", and each field describes a certain part of an uncompressed and/or compressed header. besides the formats, each encoding method may contain control fields, initial values, and default field encodings sections. a field's attributes are bound by using an encoding method on it and/or by using "ENFORCE" statements (Section 4.9) within the formats. each of these is terminated by a semi-colon. ๐Ÿฉท

predefined encoding methods aren't defined in the formal notation. instead, each gets a short textual reference saying where it's defined. it's not necessary to define the library of encoding methods in this document this way, because their definition is implicit in how the formal notation is used. ๐Ÿงš

4.2. Identifiers ๐ŸŽ€

in ROHC-FN, identifiers are used for any of the following:

  • encoding methods
  • formats
  • fields
  • parameters
  • constants

all identifiers can be any length and can contain any combination of alphanumeric characters and underscores, within the restrictions in this section.

all identifiers must start with an alphabetic character.

it's illegal to have two or more identifiers that differ only in capitalisation, in the same scope.

all letters in identifiers for constants must be upper case.

it's also illegal to use any of these as identifiers (including alternative capitalisations):

  • "false", "true"
  • "ENFORCE", "THIS", "VARIABLE"
  • "ULENGTH", "UVALUE"
  • "CLENGTH", "CVALUE"
  • "UNCOMPRESSED", "COMPRESSED", "CONTROL", "INITIAL", or "DEFAULT"

format names can't be referred to in the notation, even though they count as identifiers. (see Section 4.12.3.1 for more on format names.) ๐Ÿ’€

every identifier used in ROHC-FN has a scope. the scope defines the parts of the specification where the identifier applies and where you can refer to it from. if an identifier has a global scope, it applies throughout the specification that contains it and can be referred to from anywhere in it. if an identifier has a local scope, it only applies to the encoding method where it's defined, and it can't be referenced from outside that local scope. because local scopes are separate, the same identifier can be used in several local scopes to refer to different items.

every instance of an identifier within its scope refers to the same item. one identifier can never refer to different items within the same scope. for this reason, if an identifier has a global scope, it can't also be used separately in a local scope, since a globally-scoped identifier already applies in all local scopes.

the identifiers for each encoding method and each constant all have a global scope. each format and field also has an identifier. format and field identifiers have a local scope, except for global control fields, which have a global scope. so it's illegal for a format or field to share an identifier with another format or field in the same scope, or with an encoding method or a constant (since those have global scope). ๐Ÿซถ

note that format names (see Section 4.12.3.1) count as identifiers, but they aren't referred to in the notation. they're mainly for the reader's benefit. ๐ŸŒธ

4.3. Constant Definitions โœจ

okay so constants are the easy-mode values ๐Ÿ’… You make one with the "=" operator, and the name has to be ALL CAPS:

SOME_CONSTANT = 3;

The right-hand side is an expression (see Section 4.7), and it has to come out as a constant value. That means every term is either a constant or a literal, and it can't change depending on which header is being compressed ๐Ÿ™…

Constants are global ๐ŸŒธ Define them at the top level, outside any encoding method definition. They're completely the same as the value they point to, so you can swap one in anywhere. Field attributes can change from packet to packet, but constants keep the same value for every single packet ๐Ÿซถ

4.4. Fields ๐Ÿงฑ

Fields are the building blocks of a ROHC-FN specification. Headers get split up into fields, and each field can have two forms: a compressed form and an uncompressed form. Both forms get sent between the compressor and the decompressor the same way, as an unsigned string of bits, with the most significant bit first ๐Ÿฆ‹

The properties of the compressed form come from an encoding method and/or "ENFORCE" statements. Together, those fully define how the uncompressed and compressed forms relate, by spelling out how the field's attributes connect to each other โœจ

The notation has four field attributes, two for the uncompressed form and two for the compressed form. Here's the lineup:

uncompressed attributes of a field:

  • "UVALUE" and "ULENGTH",

compressed attributes of a field:

  • "CVALUE" and "CLENGTH".

The two value attributes hold the numerical values of the field. "UVALUE" is the numerical value of the uncompressed form, and "CVALUE" is the numerical value of the compressed form. Those numbers come from reading the bit-string representations as bit strings, most significant bit first ๐Ÿ”ข

The two length attributes tell you how many bits long the matching bit string is. "ULENGTH" is for the uncompressed form and "CLENGTH" is for the compressed form ๐Ÿ“

Attributes are undefined until they're bound to a value, and once they are, they become defined. If two conflicting bindings get given for the same field attribute, the bindings fail, along with the (combination of) formats where those bindings were defined. Yikes ๐Ÿ’€

Uncompressed attributes don't always match up with an aspect of the uncompressed header. Some fields don't come from the uncompressed header at all. They're control fields ๐ŸŽ€

4.4.1. Attribute References ๐Ÿ”—

To formally refer to an attribute of a field, you write the field's name, then a ".", then the attribute's identifier:

rtp_seq_number.UVALUE

That example gives you the uncompressed value of the rtp_seq_number field. The main reason you reference attributes is to use them in expressions, which get explained in Section 4.7 ๐Ÿ’–

4.4.2. Representation of Field Values ๐ŸŒˆ

Fields are represented as bit strings. The bit string gets calculated from the value attribute ("val") and the length attribute ("len"). It's the binary representation of val % (2 ^ len).

For example, say a field's "CLENGTH" is 8 and its "CVALUE" is -1. The compressed representation would be -1 % (2 ^ 8), which equals -1 % 256, which equals 255, or 11111111 in binary. Main character energy ๐Ÿ‘‘

ROHC-FN supports the full range of integers for use in expressions (see Section 4.7). The formats, meaning the bits actually exchanged between the compressor and the decompressor, are always in the form above ๐Ÿงš

4.5. Grouping of Fields ๐Ÿ“

The order of fields in a "COMPRESSED" field list (Section 4.12.1.2) doesn't have to match the order in an "UNCOMPRESSED" field list (Section 4.12.1.1). That means you can group together any number of fields that sit next to each other in a "COMPRESSED" format, so they all get encoded with a single encoding method. The group goes immediately to the left of "=:=", standing in for a single field name ๐Ÿ™Œ

You notate a group with a colon-separated list of the fields in it. Say an uncompressed header has two non-contiguous fields that are really two halves of one sequence number, here's how that looks:

grouping_example
{
  UNCOMPRESSED {
    minor_seq_num;  // 12 bits
    other_field;    //  8 bits
    major_seq_num;  //  4 bits
  }

  COMPRESSED {
    other_field     =:= irregular(8);
    major_seq_num
    : minor_seq_num =:= lsb(3, 0);
  }
}

The grouped fields get handed to the encoding method as one contiguous group of bits, made by concatenating the fields in the order they're listed in the group. The most significant bit of the combined field is the most significant bit of the first field in the list, and the least significant bit of the combined field is the least significant bit of the last field in the list. Bit math, but make it iconic ๐Ÿฆ‹

Last part: the length attributes of the combined field equal the sum of the matching length attributes for all the fields in the group ๐Ÿงฎ

4.6. "THIS" ๐Ÿชž

Inside the definition of an encoding method, you can refer to the field the method is encoding (the group of contiguous bits) with the keyword "THIS" ๐Ÿ‘€

It's super handy for getting at the attributes of the field being encoded. For example, it's often useful to know the total uncompressed length of the uncompressed format being encoded:

THIS.ULENGTH

4.7. Expressions ๐Ÿ’…

ROHC-FN includes the usual infix style of expressions, with parentheses "(" and ")" for grouping. Expressions can be made up of any of the components described in the subsections that follow ๐ŸŽ€

The semantics of expressions are generally similar to the expressions in the ANSI-C programming language [C90]. The definitive list of expressions in ROHC-FN comes in the next subsections, but here are some examples of how ROHC-FN expressions differ from ANSI-C:

  • There is no limit on the range of integers.
  • "x ^ y" evaluates to x raised to the power of y. It has a precedence higher than *, / and %, but lower than unary -, and it's right to left associative.
  • There is no comma operator.
  • There are no "modify" operators (no assignment operators and no increment or decrement).
  • There are no bitwise operators.

Expressions can refer to any of the attributes of a field (as described in Section 4.4), to any defined constant (see Section 4.3), and to encoding method parameters, if any are in scope (see Section 4.12) ๐Ÿง

If any of the attributes, constants, or parameters used in an expression are undefined, the value of the expression is undefined. Undefined expressions make the environment they're used in (for example, the compressed format) fail whenever a defined value is needed. Defined values are required for all compressed attributes of fields that appear in the compressed format. Defined values are not required for all uncompressed attributes of fields that appear in the uncompressed format. It's up to the profile creator to decide what happens to unbound field attributes in this case. Worth noting: in that situation, transparency of the compression process is lost. In other words, the decompressor won't be able to reproduce the original header ๐Ÿ˜ญ

Expressions can't be used as encoding methods directly, because they don't completely characterise a field. An expression only specifies a single value, but a field is made up of several values, which are its attributes. For example, the following is illegal:
tcp_list_length =:= (data_offset + 20) / 4;
There's only enough information there to define a single attribute of "tcp_list_length". Formally it makes no sense, but you could intuitively read it as defining the "UVALUE" attribute. That would still leave the length of the uncompressed field undefined at the decompressor, though. So that usage is prohibited ๐Ÿšซ

4.7.1. Integer Literals ๐Ÿ”ข

Integers can be written as decimal values, binary values (prefixed by "0b"), or hexadecimal values (prefixed by "0x"). Negative integers get a "-" sign in front. For example, "10", "0b1010", and "-0x0a" are all valid integer literals, with the values 10, 10, and -10 respectively ๐Ÿ“

4.7.2. Integer Operators ๐Ÿงฎ

These "integer" operators are available. They take integer arguments and return an integer result:

  • ^, for exponentiation. "x ^ y" returns the value of "x" to the power of "y".
  • *, / for multiplication and division. "x * y" returns the product of "x" and "y". "x / y" returns the quotient, rounded down to the next integer (the next one towards negative infinity).
  • +, - for addition and subtraction. "x + y" returns the sum of "x" and "y". "x - y" returns the difference.
  • % for modulo. "x % y" returns "x" modulo "y"; x - y * (x / y).

4.7.3. Boolean Literals โœ…

The boolean literals are "false" and "true" โŒโœ…

4.7.4. Boolean Operators ๐Ÿ”€

These "boolean" operators are available. They take boolean arguments and return a boolean result:

  • &&, for logical "and". Returns true if both arguments are true. Returns false otherwise.
  • ||, for logical "or". Returns true if at least one argument is true. Returns false otherwise.
  • !, for logical "not". Returns true if its argument is false. Returns false otherwise.

4.7.5. Comparison Operators ๐Ÿ‘ฏ

These "comparison" operators are available. They take integer arguments and return a boolean result:

  • ==, !=, for equality and its negative. "x == y" returns true if x is equal to y. Returns false otherwise. "x != y" returns true if x is not equal to y. Returns false otherwise.
  • <, >, for less than and greater than. "x < y" returns true if x is less than y. Returns false otherwise. "x > y" returns true if x is greater than y. Returns false otherwise.
  • >=, <=, for greater than or equal and less than or equal, the inverse functions of <, >. "x >= y" returns false if x is less than y. Returns true otherwise. "x <= y" returns false if x is greater than y. Returns true otherwise. ๐Ÿ’œ

4.8. Comments โœจ

okay so ROHC-FN specs can have free English text thrown in to explain why something was done a certain way, clarify what the notation means, or just elaborate on a point. it's giving "leave yourself notes" energy ๐ŸŽ€

The FN uses end-of-line comments, marked with //. Anything between the // and the end of the line has no formal meaning, so it's basically a sticky note ๐Ÿฆ‹ For example:

//-----------------------------------------------------------------
//    IR-REPLICATE header formats
//-----------------------------------------------------------------

// The following fields are included in all of the IR-REPLICATE
// header formats:
//
UNCOMPRESSED {
  discriminator;    //  8 bits
  tcp_seq_number;   // 32 bits
  tcp_flags_ecn;    //  2 bits

Comments don't change what the notation formally means, but they make it way easier to read ๐Ÿ’… Using them is optional, totally your call.

Comments can clear things up for the reader, and they serve different purposes for implementers. So don't treat them as an afterthought when you write a ROHC-FN spec ๐Ÿงš They should match the normative part of the spec, no contradictions allowed, bestie ๐Ÿ˜ญ

4.9. "ENFORCE" Statements ๐Ÿ”ฅ

The ENFORCE statement lets you attach predicates to a format. All of them must be satisfied for the format to succeed. ENFORCE kinda has a vibe with encoding methods: an encoding method binds several field attributes at once, while an ENFORCE usually binds just one. Honestly, every binding an encoding method makes can be written as a bunch of ENFORCE statements ๐Ÿคฏ Here's one that binds the UVALUE attribute of a field to 5:

ENFORCE(field.UVALUE == 5);

An ENFORCE statement must only be used inside a field list (see Section 4.12). It tries to make the expression true for the format it belongs to.

There's a shortcut form for binding length attributes with [ and ], see Section 4.10 โœจ

Like an encoding method, an ENFORCE only works in a format if the binding is actually achievable. The example above would be unusable if the same format also bound the field with uncompressed_value encoding that gave it a UVALUE other than 5. Major clash ๐Ÿ’€

An ENFORCE takes a boolean expression. You can use it to assert that the expression is true, so a particular format gets picked from a list of possible formats in an encoding method (see Section 4.12), or just to bind an expression like the one above. The general form is therefore:

ENFORCE(<boolean expression>);

The expression can end up in one of three conditions:

  1. The boolean expression evaluates to false, in which case the local scope of the format containing the ENFORCE can't be used.
  2. The boolean expression evaluates to true, in which case the binding is created and successful.
  3. The value of the boolean expression is undefined. In this case, the binding is also created and successful.

In all three cases, any undefined term gets bound by the expression ๐ŸŒธ Basically, an ENFORCE is either acting as an assignment (condition 3) or testing whether a format is usable (conditions 1 and 2). Iconic multitasking.

4.10. Formal Specification of Field Lengths ๐Ÿฆ‹

In a lot of the examples, each field is followed by a comment saying how long it is. Adding that is optional, but it's super helpful for the reader ๐Ÿซถ Just remember that comments have no formal meaning, however useful they are.

One of the most common uses of ENFORCE (see Section 4.9) is pinning down the length of a field in a header. Doing that with ENFORCE is formal but kinda clunky to read, so there's an abbreviated form that's easy to read and has formal meaning โœจ

You can put an expression for a field's length in square brackets right after the field in a format. If the field can have several alternative lengths, list the expressions as a comma separated list inside the brackets. For example:

field_1                  [ 4 ];
field_2                  [ a+b, 2 ];
field_3 =:= lsb(16, 16)  [ 26 ];

Which attribute gets bound depends on whether the field is in a COMPRESSED, UNCOMPRESSED, or CONTROL field list (see Section 4.12.1 and its subsections). In a COMPRESSED field list, the field's CLENGTH attribute is bound. In UNCOMPRESSED and CONTROL field lists, the field's ULENGTH attribute is bound. Abbreviated ENFORCE statements are not allowed in DEFAULT sections (see Section 4.12.1.5), so the notation above can't go in a DEFAULT section ๐Ÿ™…โ€โ™€๏ธ But if it appeared in an UNCOMPRESSED or CONTROL section, it would be the same as:

field_1;                 ENFORCE(field_1.ULENGTH == 4);
field_2;                 ENFORCE((field_2.ULENGTH == 2)
                              || (field_2.ULENGTH == a+b));
field_3 =:= lsb(16, 16); ENFORCE(field_3.ULENGTH == 26);

There's a special case for fields whose length is variable and the notator either doesn't want to, or can't, write as an expression. Use the keyword VARIABLE for that:

variable_length_field  [ VARIABLE ];

Formally, this puts no restriction on the field length. It maps onto any positive integer or to zero ๐ŸŒˆ So the length has to be defined somewhere else (see the final paragraphs of Section 4.12.1.1 and Section 4.12.1.2). That could be in the notation or in the English text of the profile the FN lives in. Inside the square brackets, VARIABLE can be used as a term in an expression, just like any other term. For example:

field  [ 8 * (5 + VARIABLE) ];

This defines a field whose length is a whole number of octets and at least 40 bits (5 octets). Mathy and cute ๐Ÿงฎ

4.11. Library of Encoding Methods ๐Ÿ’œ

There's a bunch of common techniques for compressing header fields built into the ROHC-FN library, so you can reuse them when you make new ROHC-FN specs. Their notation is described below ๐ŸŽ€

You can also, or instead, define your own encoding methods in a ROHC-FN spec, either with the formal notation (see Section 4.12) or with a textual definition (see Section 4.13).

4.11.1. uncompressed_value ๐ŸŒธ

The uncompressed_value encoding method is for header fields whose uncompressed value can be defined with a mathematical expression (constant values count too). It's defined like this:

uncompressed_value(len, val) {
  UNCOMPRESSED {
    field;
    ENFORCE(field.ULENGTH == len);
    ENFORCE(field.UVALUE == val);
  }
  COMPRESSED {
    field;
    ENFORCE(field.CLENGTH == 0);
  }
}

For example, the IPv6 header version number is a 4-bit field that's always 6:

version   =:=   uncompressed_value(4, 6);

Here's another one, using an expression to calculate the length:

padding =:= uncompressed_value(nbits - 8, 0);

That expression uses an encoding method parameter called nbits, which in this example says how many significant bits there are in the data, and that tells you how many pad bits to use. See Section 4.12.2 for more on encoding method parameters ๐Ÿง 

4.11.2. compressed_value ๐Ÿ“

The compressed_value encoding method is for defining fields in compressed formats that have no counterpart in the uncompressed format (i.e., control fields). It can specify compressed fields whose value can be defined with a mathematical expression (constant values included). It's defined like this:

compressed_value(len, val) {
  UNCOMPRESSED {
    field;
    ENFORCE(field.ULENGTH == 0);
  }
  COMPRESSED {
    field;
    ENFORCE(field.CLENGTH == len);
    ENFORCE(field.CVALUE == val);
  }
}

One use is padding in a compressed format:

pad_to_octet_boundary      =:=   compressed_value(3, 0);

A more common use is a discriminator field, so you can tell apart different compressed formats inside an encoding method (see Section 4.12). For convenience, the notation has a shortcut for compressed_value in the form of a binary string. You put the binary string in single quotes, the field's CLENGTH binds to the number of bits in the string, and its CVALUE binds to the value the string represents. For example:

discriminator     =:=   '01101';

That has exactly the same meaning as:

discriminator     =:=   compressed_value(5, 13);

4.11.3. irregular ๐Ÿ”ฅ

The irregular encoding method is for encoding a field in the compressed format with a bit pattern identical to the uncompressed field. It's defined like this:

irregular(len) {
  UNCOMPRESSED {
    field;
    ENFORCE(field.ULENGTH == len);
  }
  COMPRESSED {
    field;
    ENFORCE(field.CLENGTH == len);
    ENFORCE(field.CVALUE == field.UVALUE);
  }
}

For example, the TCP header checksum is a 16-bit field that doesn't follow any predictable pattern from one header to the next, so it can't be compressed ๐Ÿ˜ค

tcp_checksum  =:=   irregular(16);

Note that the length doesn't have to be constant. An expression can derive the length of the field from the value of another field, which is very giga-brain of it ๐Ÿงš

4.11.4. static ๐Ÿ‘‘

The static encoding method compresses a field whose length and value are the same as in a previous header in the flow. In other words, the field completely matches an existing entry in the context:

field            =:=   static;

The field's UVALUE and ULENGTH attributes bind to their respective values in the context, and the CLENGTH attribute is bound to zero.

Since the field value matches a previous field value, the whole field can be rebuilt from the context. So it gets compressed to zero bits and doesn't show up in the compressed format at all ๐Ÿฆ‹

For example, the TCP source port is a field whose value doesn't change from one packet to the next within a given flow:

src_port  =:=   static;

4.11.5. lsb โœจ

okay so the least significant bits method, "lsb", is giving efficiency ๐Ÿ’… It compresses a field whose value is only a little bit different from the value already stored in the context. Instead of sending the whole field value, you just send its least significant bits ๐Ÿฆ‹

field  =:=   lsb(<num_lsbs_param>, <offset_param>);

Here, "num_lsbs_param" is how many least significant bits you're using, and "offset_param" is the interpretation interval offset, which we'll get to in a sec ๐Ÿงš

The "num_lsbs_param" parameter binds to the "CLENGTH" attribute. The "UVALUE" attribute binds to whatever value inside the interval has least significant bits that match the "CVALUE" attribute ๐ŸŒธ The "ULENGTH" can be worked out from info already stored in the context, so you don't have to send it.

Here's a real-life example, the TCP sequence number:

tcp_sequence_number   =:=   lsb(14, 8192);

This takes up 14 bits, and it can communicate any value between 8192 lower than the value stored in context and 8191 above it. The way that works is so clever ๐Ÿคฏ

The interpretation interval can be described as a function of a value stored in the context, ref_value, and of num_lsbs_param:

f(context_value, num_lsbs_param) = [ref_value - offset_param,
           ref_value + (2^num_lsbs_param - 1) - offset_param]

where offset_param is an integer.

      <-- interpretation interval (size is 2^num_lsbs_param) -->
      |---------------------------+----------------------------|
    lower                     ref_value                      upper
    bound                                                    bound

where:

    lower bound = ref_value - offset_param
    upper bound = ref_value + (2^num_lsbs_param-1) - offset_param

So the "lsb" method can compress any field whose value sits between the lower and upper bounds of the interpretation interval, inclusive ๐Ÿ’œ Now the offset is the secret sauce. If offset_param = 0, the field value can only stay the same or go up compared to the reference value ref_value. If offset_param = -1, it can only go up. And if offset_param = 2^num_lsbs_param, it can only go down. Iconic range control, no cap ๐Ÿ‘‘

The compressed field takes up the number of bits you specified in the compressed format, which is num_lsbs_param ๐ŸŽ€

Now here's the tricky part ๐Ÿ™ˆ The compressor might not know the exact reference value that the decompressor has stored in its context, because some packets that would have updated the context might have gotten lost or damaged along the way. BUT, from feedback it gets back, or from making assumptions, the compressor can narrow down a candidate set of values. Then it picks a format that uses "lsb" encoding, with suitable values for num_lsbs_param and offset_param, so that no matter which context value in that candidate set the decompressor ends up using, the decompression still comes out correct. Main character problem-solving โœจ If that's not possible, the "lsb" method just fails, and the compressor usually ends up picking a less efficient compressed format instead ๐Ÿ˜ญ How the compressor decides which reference values to store and keep in its candidate set is outside the scope of this notation, so we're not getting into that one today ๐Ÿซถ

4.11.6. crc

The "crc" encoding method gives you a CRC calculated over a block of data ๐Ÿ”ฅ The algorithm used to calculate the CRC is the one specified in [RFC4995]. The "crc" method takes a few parameters:

  • the number of bits for the CRC (crc_bits),
  • the bit-pattern for the polynomial (bit_pattern),
  • the initial value for the CRC register (initial_value),
  • the value of the block of data, represented using either the "UVALUE" or "CVALUE" attribute of a field (block_data_value); and
  • the size in octets of the block of data (block_data_length).

So it looks like this:

field   =:=   crc(<num_bits>, <bit_pattern>, <initial_value>,
                  <block_data_value>, <block_data_length>);

When you're specifying the bit pattern for the polynomial, each bit represents the coefficient for the matching term in the polynomial. The highest order term is always there (by definition), so you don't need to specify it in the bit pattern ๐ŸŒˆ That means a CRC polynomial with n terms in it gets represented by a bit pattern with n-1 bits set. Little trick, big payoff ๐Ÿ’–

The CRC is calculated in least significant bit (LSB) order.

For example:

// 3 bit CRC, C(x) = x^0 + x^1 + x^3
crc_field =:= crc(3, 0x6, 0xF, THIS.CVALUE, THIS.CLENGTH);

Using the "THIS" keyword (see Section 4.6) like in the example above is typical when you're using "crc" encoding. For instance, when it's used in the encoding method for an entire header, it makes the CRC get calculated over all the fields in the header ๐Ÿฆ‹

4.12. Definition of Encoding Methods

New encoding methods can be defined in a formal specification ๐ŸŽ€ These compose groups of individual fields into one contiguous block.

Encoding methods have names and may have parameters. They can also be used in the same way as any other encoding method from the library of encoding methods. Since they can contain references to other encoding methods, you can break complicated formats down into manageable pieces in a hierarchical fashion. Very organised, very bestie ๐Ÿงš

This section describes the various features used to define new encoding methods.

4.12.1. Structure

This is the simplest form of defining an encoding method: you specify a single encoding. For example:

compound_encoding_method
{
  UNCOMPRESSED {
    field_1;  //  4 bits
    field_2;  // 12 bits
  }

  COMPRESSED {
    field_2 =:= uncompressed_value(12, 9); //  0 bits
    field_1 =:= irregular(4);              //  4 bits
  }
}

The above begins with the new method's identifier, "compound_encoding_method". The definition of the method then follows inside curly brackets, "{" and "}". The first item in the definition is the "UNCOMPRESSED" field list, which gives the order of the fields in the uncompressed format. After that comes the compressed format field list ("COMPRESSED"). This list gives the order of the fields in the compressed format and also gives the encoding method for each field ๐Ÿ’…

In the example, both formats list each field exactly once. However, sometimes you need to specify more than one binding for a given field, which means it shows up more than once in the field list. In that case, it's the first occurrence of the field in the list that tells you its position in the field order. The later occurrences only specify binding information, not field order information ๐Ÿ™ˆ

The different components of this example are described in more detail below. Other components that can be used in defining encoding methods are also defined after that ๐ŸŒธ

4.12.1.1. Uncompressed Format - "UNCOMPRESSED"

The uncompressed field list is defined by "UNCOMPRESSED", which specifies the fields of the uncompressed format in the order they appear in the uncompressed header ๐Ÿ’œ The sum of the lengths of each individual uncompressed field in the list must be equal to the length of the field being encoded. Finally, the representation of the uncompressed format, described using the list of fields in the "UNCOMPRESSED" section (for which compressed formats are being defined), always consists of one single contiguous block of bits ๐Ÿซถ

In the example above in Section 4.12.1, the uncompressed field list is "field_1", followed by "field_2". This means a field being encoded by this method gets divided into two subfields, "field_1" and "field_2". The total uncompressed length of these two fields therefore equals the length of the field being encoded:

field_1.ULENGTH + field_2.ULENGTH == THIS.ULENGTH

The example only has two fields, but any number of fields may be used. This relationship applies to however many fields you actually use. Any arrangement of fields that efficiently describes the content of the uncompressed header may be chosen. It doesn't have to match the one in the specifications for the protocol header being compressed โœจ

For example, a protocol might have a header with a 16-bit sequence number, but its sessions tend to be short-lived. That would mean the high bits of the sequence number are almost always constant. The "UNCOMPRESSED" format could reflect this by splitting the original uncompressed field into two fields: one to represent the almost-always-zero part of the sequence number, and a second to represent the salient part ๐Ÿงš

An "UNCOMPRESSED" field list may specify encoding methods in the same way as the "COMPRESSED" field list in the example. Encoding methods specified there are used whenever a packet with that uncompressed format is being encoded. The encoding of a packet with a given uncompressed format can only succeed if all of its encoding methods and "ENFORCE" statements succeed (see Section 4.9) ๐Ÿ”ฅ

The total length of each uncompressed format must always be defined. The length of each of the fields in an uncompressed format must also be defined. This means the bindings in the "UNCOMPRESSED", "COMPRESSED" (see Section 4.12.1.2 below), "CONTROL" (see Section 4.12.1.3 below), "INITIAL" (see Section 4.12.1.4 below), and "DEFAULT" (see Section 4.12.1.5 below) field lists must, between them, define the "ULENGTH" attribute of every field in an uncompressed format. That way there's an unambiguous mapping from the bits in the uncompressed format to the fields listed in the "UNCOMPRESSED" field list. Not optional, bestie ๐ŸŒˆ

4.12.1.2. Compressed Format - "COMPRESSED"

okay so the compressed header works kinda like the uncompressed field list ๐Ÿ’… the fields show up in whatever order the compressed field list for that format says, and each one gets encoded with its own method. the total compressed length is just all the compressed lengths added together โœจ in the example from Section 4.12.1, the encoding methods make the fields zero and 4 bits long, so the total is 4 bits ๐ŸŽ€

the order in a "COMPRESSED" field list does not have to match the "UNCOMPRESSED" one ๐Ÿฆ‹ you might wanna reorder things so the compressed header lines up on an octet boundary, or just for other reasons. in the example above it's literally the reverse of the uncompressed order ๐Ÿ™ƒ iconic

the compressed field list says field_1 uses irregular(4), so it's 4 bits in both the compressed and uncompressed formats. field_2 uses uncompressed_value(12, 9), which means it's a fixed value. it's 12 bits wide uncompressed, but its value is always 9, so it compresses down to zero bits ๐ŸŒธ

fields like field_2, the ones that compress to zero bits, can sit anywhere in the list without changing the compressed format, because their position doesn't matter ๐Ÿ’œ and if the encoding method was defined somewhere else (like in the "UNCOMPRESSED" section), this field could be left out of the "COMPRESSED" section entirely ๐Ÿ˜ญ

  compound_encoding_method
  {
    UNCOMPRESSED {
      field_1;                                //  4 bits
      field_2 =:= uncompressed_value(12, 9);  // 12 bits
    }

    COMPRESSED {
      field_1 =:= irregular(4);               //  4 bits
    }
  }

the total length of each compressed format must always be defined โœจ and the length of each field in a compressed format has to be defined too ๐Ÿซถ so the bindings across the "UNCOMPRESSED", "COMPRESSED", "CONTROL" (see Section 4.12.1.3 below), "INITIAL" (see Section 4.12.1.4 below), and "DEFAULT" (see Section 4.12.1.5 below) field lists have to work together to define the CLENGTH attribute of every field. that way there's one clear mapping from the bits in the compressed format to the fields in the "COMPRESSED" field list. no guesswork, bestie ๐Ÿ‘‘


4.12.1.3. Control Fields - "CONTROL"

control fields are defined with the "CONTROL" field list ๐Ÿงš this list covers all the fields that aren't in the uncompressed format but still have an uncompressed value (specifically, ones with a ULENGTH greater than zero). they're used to compress the uncompressed fields more efficiently. a control field can show off some commonality between a bunch of uncompressed fields, or track info about the flow that isn't actually in the protocol headers ๐Ÿ’–

for example, in IPv4, the IP-ID field can behave differently depending on how the endpoints handle IP-IDs. sometimes it's basically random, and sometimes it follows a predictable sequence ๐ŸŒˆ the type of IP-ID behaviour is never sent explicitly in the uncompressed header, so it's kinda hidden tea โ˜•

the good news is a profile can still figure out the behaviour and tweak its compression strategy to match, which makes compression way better โœจ to do that, the ROHC-FN specification can add an explicit field that communicates the IP-ID behaviour in the compressed format. that's done by introducing a control field:

  ipv4
  {
    UNCOMPRESSED {
      version;       // 4 bits
      hdr_length;    // 4 bits
      protocol;      // 8 bits
      dscp;          // 6 bits
      ip_ecn_flags;  // 2 bits
      ttl_hopl;      // 8 bits
      df;            // 1 bit
      mf;            // 1 bit
      rf;            // 1 bit
      frag_offset;   // 13 bits
      ip_id;         // 16 bits
      src_addr;      // 32 bits
      dst_addr;      // 32 bits
      checksum;      // 16 bits
      length;        // 16 bits
    }

    CONTROL {
      ip_id_behavior; // 1 bit
         :
         :

the "CONTROL" field list is basically the same as the "UNCOMPRESSED" field list, but for fields that don't show up in the uncompressed format ๐Ÿ’… it defines a field with the same properties (the same defined attributes, etc.) as the fields that do appear in the uncompressed format ๐Ÿฆ‹

control fields get initialised with the right encoding methods and/or with ENFORCE statements ๐ŸŒธ and you can do that inside the "CONTROL" field list too โœจ

for example:

  example_encoding_method_definition
  {
    UNCOMPRESSED {
      field_1 =:= some_encoding;
    }

    CONTROL {
      scaled_field;
      ENFORCE(scaled_field.UVALUE == field_1.UVALUE / 8);
      ENFORCE(scaled_field.ULENGTH == field_1.ULENGTH - 3);
    }

    COMPRESSED {
      scaled_field =:= lsb(4, 0);
    }
  }

this control field scales a field from the uncompressed format down by a factor of 8 before it gets encoded with lsb. scaling it down makes the lsb encoding way more efficient ๐Ÿ”ฅ main character energy

control fields can also have a global scope! in that case, the declaration has to live outside of any encoding method definition ๐Ÿ‘‘ then they're visible inside any encoding method, which means encoding methods can share info directly with each other ๐Ÿซถ


4.12.1.4. Initial Values - "INITIAL"

so the first packet in a specific format might need to be compressed with static, lsb, or other context-dependent encoding methods, but there's no context yet ๐Ÿ˜ญ that's where "INITIAL" comes in. it lets you specify initial bindings for those fields. it looks like this:

  INITIAL {
     field =:= uncompressed_value(4, 6);
  }

that initialises the UVALUE of field to 6, and its ULENGTH to 4 โœจ the sneaky part is that unlike every other binding in the formal notation, these ones get applied to the field's context, but only if that context is undefined ๐Ÿ™ˆ it's especially useful for encoding methods that need context to exist, like static or lsb, on the first packet in a flow ๐Ÿงš

because the "INITIAL" field list only binds the context, it makes zero sense to give it initial bindings that rely on the context themselves (like lsb). that kind of usage is not allowed ๐Ÿ’€ don't do it bestie


4.12.1.5. Default Field Bindings - "DEFAULT"

default bindings can be specified for each field or attribute ๐Ÿ“ the default encoding methods tell you which method to use for a field if no binding is given anywhere else for that field's value. this keeps the format definitions concise, since you don't have to repeat the same encoding method in every format when you're defining multiple formats (see Section 4.12.3) ๐Ÿ’–

default bindings are optional and can be given for any combination of fields and attributes that are in scope ๐ŸŒˆ

the syntax is kinda like what you'd use for a compressed or uncompressed format. the difference is that the order of the fields in the field list doesn't affect the order of the fields in either the compressed or uncompressed format ๐Ÿฆ‹ that's because the field order gets specified separately for each "COMPRESSED" format and each "UNCOMPRESSED" format โœจ

here's an example:

    DEFAULT {
      field_1 =:= uncompressed_value(4, 1);
      field_2 =:= uncompressed_value(4, 2);
      field_3 =:= lsb(3, -1);
      ENFORCE(field_4.ULENGTH == 4);

    }

so here default bindings are set for fields 1 to 3, and there's also a default binding for the ULENGTH attribute of field_4 ๐Ÿ’…

fields that have a default encoding method don't need their bindings written out in any format's field list that uses that default method ๐Ÿซถ but any format that doesn't use the default method has to explicitly specify a binding for that field's attribute values ๐Ÿ™ƒ

if no binding is specified elsewhere for a field's attributes, the default encoding method gets used ๐ŸŒธ and if the default method always squishes the field down to zero bits, the field can be left out of the compressed format's field list ๐ŸŽ€ same as any other zero-bit field, its position in the list doesn't matter ๐Ÿงš

the "DEFAULT" field list can hold default bindings for individual attributes too, using ENFORCE statements ๐Ÿ”ฅ a default for an individual attribute only gets used if there's no binding for that attribute or for the field it belongs to anywhere else. if somewhere else there's an ENFORCE statement binding that attribute, or an encoding method binding the field it belongs to, the default for the attribute is ignored ๐Ÿ’€ that's true even if the encoding method doesn't bind that particular attribute from the "DEFAULT" section ๐Ÿ˜ญ

one plot twist: an ENFORCE statement elsewhere that only binds the field's length still lets the default bindings get used, except for default ENFORCE statements that bind nothing but the field's length ๐Ÿ™ˆ

to make it clearer, using the default bindings from the example above, the first three of these four compressed formats would not use the default binding for field_4.ULENGTH ๐Ÿ‘‘

    COMPRESSED format1 {
      ENFORCE(field_4.ULENGTH == 3); // set ULENGTH to 3
      ENFORCE(field_4.UVALUE == 7);  // set UVALUE to 7
    }

    COMPRESSED format2 {
      field_4 =:= irregular(3);      // set ULENGTH to 3
    }

    COMPRESSED format3 {
      field_4 =:= '1010';            // set ULENGTH to zero
    }
    COMPRESSED format4 {
      ENFORCE(field_4.UVALUE == 12); // use default ULENGTH
    }

the fourth format is the only one that uses the default binding for field_4.ULENGTH ๐Ÿ“

in short, an encoding method's default bindings only get used for formats that don't already specify a binding for the value of all of their fields ๐ŸŒˆ for the formats that do use defaults, only the fields and attributes without a binding get looked up in the "DEFAULT" field list โœจ and that's the tea, periodt ๐Ÿ’…

4.12.2. Arguments

Encoding methods can take arguments that control how compressed fields map to uncompressed ones, bestie ๐Ÿ’… They go right after the method's name, inside parentheses, as a comma-separated list ๐ŸŽ€

For example:

  poor_mans_lsb(variable_length)
  {
    UNCOMPRESSED {
      constant_bits;
      variable_bits;
    }

    COMPRESSED {
      variable_bits =:= irregular(variable_length);
      constant_bits =:= static;
    }
  }

Okay so, every argument takes an individual value, like an integer literal or a field attribute, never a whole field ๐Ÿ™ˆ You can't pass entire fields as arguments, but you can pass each of their attributes, and that works out to the same thing โœจ

Also, every binding is two-way ๐Ÿฆ‹ So the arguments aren't really "inputs" to the encoding method. The result of an encoding method can end up binding the parameters passed to it, which is so iconic ๐Ÿ‘‘

For example:

  set_to_double(arg1, arg2)
  {
    CONTROL {
      ENFORCE(arg1 == 2 * arg2);
    }
  }

This encoding method will try to bind the first argument to twice the value of the second ๐Ÿ“ Honestly, this "encoding" method is pathological ๐Ÿ’€ Since it doesn't define any fields, it doesn't actually encode anything. A "CONTROL" section is the better tool for this job than "UNCOMPRESSED" ๐Ÿงš

4.12.3. Multiple Formats

Encoding methods can also define multiple formats for a single header ๐ŸŒˆ That lets the compressor use different methods depending on which one squeezes a particular header the best ๐Ÿ’œ

For example, a field might have a fixed value most of the time but change every now and then. With a single format, you'd have to encode that field using "irregular" (see Section 4.11.3), even though the value only rarely changes ๐Ÿ˜ญ But if you define multiple formats, you get two alternative encodings: one for when the value stays fixed, and one for when it changes โœจ

The following sub-sections cover this in more detail ๐Ÿซถ

4.12.3.1. Naming Convention

When compressed formats are defined, they must be defined with the reserved word "COMPRESSED" ๐ŸŽ€ Uncompressed formats must be defined with the reserved word "UNCOMPRESSED" ๐ŸŒธ After each of these keywords, you can give the format a name. If you don't give the format a name, its name is empty ๐Ÿฆ‹

Format names follow the syntactic rules for identifiers from Section 4.2, except when the name is empty ๐Ÿ’…

Format names must be unique within the scope of the encoding method they belong to ๐Ÿ“ The one exception is the empty name, which can be used for one "COMPRESSED" format and one "UNCOMPRESSED" format ๐Ÿงš

4.12.3.2. Format Discrimination

Each compressed format has its own field list โœจ A compressor can pick any of these alternative formats to compress a header, as long as the field bindings it uses also work with the uncompressed format ๐Ÿ’– For example, the compressor couldn't choose a compressed format that had a "static" encoding for a field whose "UVALUE" attribute differs from its matching value in the context ๐Ÿ™ˆ

More formally, the compressor can choose any combination of an uncompressed format and a compressed format where none of the field's attribute bindings "fail". That means the encoding methods and "ENFORCE" statements (see Section 4.9) that bind their compressed attributes all succeed ๐Ÿ’… If there's more than one successful combination, the compressor can pick any one of them ๐ŸŒˆ But if there are no successful combinations, the encoding method "fails" ๐Ÿ’€ A format never fails just because it doesn't define the "UVALUE" attribute of a field ๐Ÿซถ A format only fails if it doesn't define one of the compressed attributes of one of the fields in the compressed format, or if it leaves the length of the uncompressed format undefined ๐Ÿ˜ญ

Here's the catch: because the compressor gets to choose, the decompressor has to be able to tell apart the different compressed formats the compressor could have picked ๐Ÿ‘‘ A simple way to do this is to give each compressed format a "discriminator" that uniquely identifies that particular "COMPRESSED" format โœจ A discriminator is a control field, not something derived from any of the uncompressed field values (see Section 4.11.2) ๐ŸŽ€

4.12.3.3. Example of Multiple Formats

Putting it all together, here's a complete example of an encoding method with multiple compressed formats ๐Ÿฆ‹

  example_multiple_formats
  {
    UNCOMPRESSED {
      field_1;  //  4 bits
      field_2;  //  4 bits
      field_3;  // 24 bits
    }

    DEFAULT {
      field_1 =:= static;
      field_2 =:= uncompressed_value(4, 2);
      field_3 =:= lsb(4, 0);
    }
    COMPRESSED format0 {
      discriminator =:= '0'; // 1 bit
      field_3;               // 4 bits
    }

    COMPRESSED format1 {
      discriminator =:= '1';           //  1 bit
      field_1       =:= irregular(4);  //  4 bits
      field_3       =:= irregular(24); // 24 bits
    }
  }

A few things to notice, babe ๐Ÿ‘€

  • field_1 and field_3 both have default encoding methods, which get used in format0 but are overridden in format1 ๐Ÿ’– The default encoding method for field_2 isn't overridden, though ๐ŸŒธ
  • field_1 and field_2 have default encoding methods that compress to zero bits ๐Ÿ™ˆ When these are used in format0, their field names don't show up in the field list ๐Ÿงš
  • field_3 has an encoding method that doesn't compress to zero bits ๐Ÿ˜ญ So even though format0 doesn't specify an encoding for it in the field list, field_3 still has to appear there to show where it goes in the compressed format ๐Ÿซถ
  • In this example, every field in the uncompressed format has a default encoding method, but that's not a requirement โœจ You can specify defaults for only some of the fields, or for none of them at all ๐Ÿ’…
  • In this example, all the default encoding methods are on fields from the uncompressed format, but that's not a requirement either ๐ŸŒˆ Default encoding methods can also be specified for control fields ๐Ÿ‘‘

4.13. Profile-Specific Encoding Methods

The library of encoding methods that ROHC-FN defines in Section 4.11 is basic and generic ๐ŸŒธ When you use a ROHC-FN specification in a ROHC profile, though, you may need some extra encodings that are specific to the particular protocol header being compressed ๐Ÿฆ‹ Things like methods that infer the value of a field from other values, for example โœจ

These methods depend on the properties of the protocol being compressed, so they have to be defined inside the profile specification itself ๐Ÿ’… Profile-specific encoding methods can be defined either in ROHC-FN syntax or rigorously in plain text ๐Ÿ“ Either way, the ROHC-FN specification of the profile's formats can refer to them just like any method in the ROHC-FN library ๐Ÿงš

Encoding methods that aren't defined in the formal notation get specified by giving their name, followed by a short description of where they're defined, in double quotes, and then a semi-colon ๐ŸŽ€

For example:

  inferred_ip_v4_header_checksum "defined in RFCxxxx Section 6.4.1";

5. Security Considerations

This document describes a formal notation similar to ABNF [RFC4234], and so it isn't believed to raise any security issues ๐Ÿ˜Œ (Note that ABNF has a completely separate purpose to the ROHC formal notation ๐Ÿซข Don't mix them up, bestie.)

6. Contributors

Richard Price did much of the foundational work on the formal notation ๐Ÿ‘‘ He wrote the initial document describing a formal notation, and this document is based on it ๐Ÿ’œ

Kristofer Sandlund contributed by applying new ideas to the ROHC-TCP profile, by giving feedback, and by helping resolve different issues throughout the entire development of the notation ๐Ÿซถ

Carsten Bormann provided the translation of the formal notation syntax into ABNF in Appendix A ๐ŸŒˆ Carsten also contributed feedback and reviews to validate the notation's completeness and correctness โœจ

7. Acknowledgements

A bunch of important concepts and ideas were borrowed from ROHC [RFC3095] ๐Ÿฆ‹

Huge thanks to Mark West, Eilert Brinkmann, Alan Ford, and Lars-Erik Jonsson for their contributions, reviews, and feedback, which led to major improvements in the notation's readability, completeness, and overall quality ๐ŸŽ€

Thanks to Stewart Sadler, Caroline Daniels, Alan Finney, and David Findlay for their reviews and comments ๐Ÿ’– Thanks to Rob Hancock and Stephen McCann for their early work on the formal notation ๐ŸŒธ The authors would also like to thank Christian Schmidt, Qian Zhang, Hongbin Liao, and Max Riegel for their comments and valuable input ๐Ÿงš

Additional thanks: this document was reviewed during working group last-call by the committed reviewers Mark West, Carsten Bormann, and Joe Touch ๐Ÿ”ฅ Sally Floyd also gave a review at the request of the Transport Area Directors ๐Ÿ‘‘ Thanks as well to Magnus Westerlund for his feedback in preparation for the IESG review ๐Ÿซถ

8. References

8.1. Normative References

  • [C90] ISO/IEC, "ISO/IEC 9899:1990 Information technology -- Programming Language C", ISO 9899:1990, April 1990. ๐Ÿ“š
  • [RFC2822] Resnick, P., Ed., "STANDARD FOR THE FORMAT OF ARPA INTERNET TEXT MESSAGES", RFC 2822, April 2001. ๐Ÿ“š
  • [RFC4234] Crocker, D., Ed. and P. Overell, "Augmented BNF for Syntax Specifications: ABNF", RFC 4234, October 2005. ๐Ÿ“š
  • [RFC4995] Jonsson, L-E., Pelletier, G., and K. Sandlund, "The RObust Header Compression (ROHC) Framework", RFC 4995, July 2007. ๐Ÿ“š

8.2. Informative References

  • [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. ๐Ÿ“–
  • [RFC791] University of Southern California, "DARPA INTERNET PROGRAM PROTOCOL SPECIFICATION", RFC 791, September 1981. ๐Ÿ“–

Appendix A. Formal Syntax of ROHC-FN โœจ

okay so this section is the official grammar for ROHC-FN, written in ABNF [RFC4234]. the start rule is "fnspec", so that's where you begin reading ๐Ÿงš

; overall structure
fnspec     = S *(constdef S) [globctl S] 1*(methdef S)
constdef   = constname S "=" S expn S ";"
globctl    = CONTROL S formbody
methdef    = id S [parmlist S] "{" S 1*(formatdef S) "}"
           / id S [parmlist S] STRQ *STRCHAR STRQ S ";"
parmlist   = "(" S id S *( "," S id S ) ")"
formatdef  = formhead S formbody
formhead   = UNCOMPRESSED [ 1*WS id ]
           / COMPRESSED [ 1*WS id ]
           / CONTROL / INITIAL / DEFAULT
formbody   = "{" S *((fielddef/enforcer) S) "}"
fielddef   = fieldgroup S ["=:=" S encspec S] [lenspec S] ";"
fieldgroup = fieldname *( S ":" S fieldname )
fieldname  = id
encspec    = "'" *("0"/"1") "'"
           / id [ S "(" S expn S *( "," S expn S ) ")"]
lenspec    = "[" S expn S *("," S expn S) "]"
enforcer   = ENFORCE S "(" S expn S ")" S ";"


; expressions
expn  = *(expnb S "||" S) expnb
expnb = *(expna S "&&" S) expna
expna = *(expn7 S ("=="/"!=") S) expn7
expn7 = *(expn6 S ("<"/"<="/">"/">=") S) expn6
expn6 = *(expn4 S ("+"/"-") S) expn4
expn4 = *(expn3 S ("*"/"/"/"%") S) expn3
expn3 = expn2 [S "^" S expn3]
expn2 = ["!" S] expn1
expn1 = expn0 / attref / constname / litval / id
expn0 = "(" S expn S ")" / VARIABLE
attref       = fieldnameref "." attname
fieldnameref = fieldname / THIS
attname      = ( U / C ) ( LENGTH / VALUE )
litval       = ["-"] "0b" 1*("0"/"1")
             / ["-"] "0x" 1*(DIGIT/"a"/"b"/"c"/"d"/"e"/"f")
             / ["-"] 1*DIGIT
             / false / true

; lexical categories
constname = UPCASE *(UPCASE / DIGIT / "_")
id        = ALPHA *(ALPHA / DIGIT / "_")
ALPHA     = %x41-5A / %x61-7A
UPCASE    = %x41-5A
DIGIT     = %x30-39
COMMENT   = "//" *(SP / HTAB / VCHAR) CRLF
SP        = %x20
HTAB      = %x09
VCHAR     = %x21-7E
CRLF      = %x0A / %x0D.0A
NL        = COMMENT / CRLF
WS        = SP / HTAB / NL
S         = *WS
STRCHAR   = SP / HTAB / %x21 / %x23-7E
STRQ      = %x22


; case-sensitive literals
C            = %d67
COMPRESSED   = %d67.79.77.80.82.69.83.83.69.68
CONTROL      = %d67.79.78.84.82.79.76
DEFAULT      = %d68.69.70.65.85.76.84
ENFORCE      = %d69.78.70.79.82.67.69
INITIAL      = %d73.78.73.84.73.65.76
LENGTH       = %d76.69.78.71.84.72
THIS         = %d84.72.73.83
U            = %d85
UNCOMPRESSED = %d85.78.67.79.77.80.82.69.83.83.69.68
VALUE        = %d86.65.76.85.69
VARIABLE     = %d86.65.82.73.65.66.76.69
false        = %d102.97.108.115.101
true         = %d116.114.117.101

Appendix B. Bit-level Worked Example ๐Ÿ“

okay bestie, this appendix is a worked example at the bit level. it shows how a simple ROHC-FN spec describes the compression of real data from an imaginary protocol header ๐Ÿฆ‹ I kept it pretty simple, but it still shows off some of the tricky bits you'll hit when you use the notation. the fields are short so you can actually read the binary without your brain melting ๐Ÿซ 

B.1. Example Packet Format

our imaginary header is just 16 bits long and has these fields:

  1. version number -- 2 bits
  2. type -- 2 bits
  3. flow id -- 4 bits
  4. sequence number -- 4 bits
  5. flag bits -- 4 bits

so for example, 0101000100010000 means a header with version number one, type one, flow id one, sequence number one, and all flag bits set to zero. iconic ๐ŸŽ€

here's an ASCII box diagram of the imaginary header:

   0   1   2   3   4   5   6   7
 +---+---+---+---+---+---+---+---+
 |version| type  |    flow_id    |
 +---+---+---+---+---+---+---+---+
 |  sequence_no  |   flag_bits   |
 +---+---+---+---+---+---+---+---+

B.2. Initial Encoding

this is the first definition, based only on the info above ๐Ÿ’…

eg_header
{
  UNCOMPRESSED {
    version_no   [ 2 ];
    type         [ 2 ];
    flow_id      [ 4 ];
    sequence_no  [ 4 ];
    flag_bits    [ 4 ];
  }

  COMPRESSED initial_definition {
    version_no  =:= irregular(2);
    type        =:= irregular(2);
    flow_id     =:= irregular(4);
    sequence_no =:= irregular(4);
    flag_bits   =:= irregular(4);
  }
}

it defines the format really nicely, but it doesn't actually compress anything yet ๐Ÿ™ˆ when we use it on the header above, we get:

Uncompressed header: 0101000100010000
Compressed header:   0101000100010000

that's because every field is marked "irregular", which just means we haven't said anything about how it behaves ๐Ÿคทโ€โ™€๏ธ

also, since we only have one compressed format and one uncompressed format, it doesn't matter whether the encoding methods are written in the compressed or the uncompressed format. honestly it would make zero difference if we wrote this instead:

eg_header
{
  UNCOMPRESSED {
    version_no  =:= irregular(2);
    type        =:= irregular(2);
    flow_id     =:= irregular(4);
    sequence_no =:= irregular(4);
    flag_bits   =:= irregular(4);
  }
  COMPRESSED initial_definition {
    version_no   [ 2 ];
    type         [ 2 ];
    flow_id      [ 4 ];
    sequence_no  [ 4 ];
    flag_bits    [ 4 ];
  }
}

B.3. Basic Compression

to get any compression at all, we need to write down more knowledge about the header and how it behaves in a flow โœจ for example, we might know these facts about it:

  1. version number -- indicates which version of the protocol this is: always one for this version of the protocol.
  2. type -- may take any value.
  3. flow id -- may take any value.
  4. sequence number -- can take any value.
  5. flag bits -- contains three flags, a, b, and c, each of which may be set or clear, and a reserved flag bit, which is always clear (i.e., zero).

we could write that knowledge down like this:

eg_header
{
  UNCOMPRESSED {
    version_no     [ 2 ];
    type           [ 2 ];
    flow_id        [ 4 ];
    sequence_no    [ 4 ];
    abc_flag_bits  [ 3 ];
    reserved_flag  [ 1 ];
  }

  COMPRESSED basic {
    version_no    =:= uncompressed_value(2, 1)  [ 0 ];
    type          =:= irregular(2)              [ 2 ];
    flow_id       =:= irregular(4)              [ 4 ];
    sequence_no   =:= irregular(4)              [ 4 ];
    abc_flag_bits =:= irregular(3)              [ 3 ];
    reserved_flag =:= uncompressed_value(1, 0)  [ 0 ];

  }
}

with this simple scheme, we've managed to encode that one field is permanently fixed at one, so it has no useful info in it at all ๐Ÿซถ we've also encoded that the last flag bit is always zero, which is equally useless info. both of these facts use the "uncompressed_value" encoding method (see Section 4.11.1).

using this new encoding on the header above, we get:

Uncompressed header: 0101000100010000
Compressed header:   0100010001000

that cuts the amount of data we need to send by roughly 20% ๐Ÿ“‰ but this encoding doesn't take advantage of how a field's value in one packet relates to its value in later packets ๐Ÿ˜ค for example, every header in this sequence gets compressed by the same amount, even though they're clearly similar:

Uncompressed header: 0101000100010000
Compressed header:   0100010001000


Uncompressed header: 0101000101000000
Compressed header:   0100010100000


Uncompressed header: 0110000101110000
Compressed header:   1000010111000

B.4. Inter-Packet Compression

the profile we've defined so far doesn't compress the sequence number or flow ID fields at all, since they can take any value ๐Ÿฅฒ but the value of each of these fields has a really simple relationship to its value in earlier headers:

  • the sequence number is unusual -- it increases by three each time,
  • the flow_id stays the same -- it always has the same value that it did in the previous header in the flow,
  • the abc_flag_bits stay the same most of the time -- they usually have the same value that they did in the previous header in the flow.

the obvious way to write this down looks like this:

// This obvious encoding will not work (correct encoding below)
eg_header
{
  UNCOMPRESSED {
    version_no     [ 2 ];
    type           [ 2 ];
    flow_id        [ 4 ];
    sequence_no    [ 4 ];
    abc_flag_bits  [ 3 ];
    reserved_flag  [ 1 ];
  }

  COMPRESSED obvious {
    version_no    =:= uncompressed_value(2, 1);
    type          =:= irregular(2);
    flow_id       =:= static;
    sequence_no   =:= lsb(0, -3);
    abc_flag_bits =:= irregular(3);
    reserved_flag =:= uncompressed_value(1, 0);
  }
}

this uses the "static" and "lsb" encoding methods to express the dependency on previous packets (see Section 4.11.4 and Section 4.11.5 respectively). BUT there are a few problems with it, so let's talk through them ๐Ÿ™ƒ

firstly, and this is the most important one, the "flow_id" field is marked as "static", which means it doesn't change from packet to packet. however, the notation never says how the value gets communicated in the first place. there's no point saying "it's the same value as last time" if there was never a first time where we defined that value so it can be referred back to. the notation above has no way to do that. the sequence number has the same issue, since there needs to be a way to communicate its initial value too. honestly, apart from the explicit notation for their lengths, even the lengths of these two fields are left undefined ๐Ÿ˜ญ this gets solved below, in Appendix B.5.

secondly, the sequence number is sent super efficiently in zero bits, but it's not at all robust against packet loss. if a packet goes missing, there's no way to handle the missing sequence number. when you're communicating sequence numbers, or any other field using "lsb" encoding, a really important thing for the notator to think about is how robust the compressed protocol should be against packet loss. this changes a lot from one protocol stack to another. for this example protocol, we'll assume short, low-overhead flows and say we need to survive the loss of just one packet. we can do that with two bits of "lsb" encoding (one bit isn't enough, since the sequence number increases by three each time -- see Section 4.11.5). this gets addressed below in Appendix B.5 ๐Ÿ’–

finally, even though the flag bits are usually the same as in the previous header in the flow, the profile doesn't use that fact at all. since they're sometimes different from the previous header, it isn't safe to say they're always the same, so "static" encoding can't be used on its own. this gets solved later on, through multiple formats, in Appendix B.6 ๐Ÿ‘‘

B.5. Specifying Initial Values โœจ

okay so, to tell the decompressor the starting values for fields that get compressed with a context dependent encoding like "static" or "lsb", we use an INITIAL field list ๐ŸŽ€ this is super handy for fields whose starting value is fixed and known. like, if we knew the flow always starts with flow_id at 1 and sequence_no at 0, we could write it down like this:

  // This encoding will not work either (correct encoding below)
eg_header
{
  UNCOMPRESSED {
    version_no     [ 2 ];
    type           [ 2 ];
    flow_id        [ 4 ];
    sequence_no    [ 4 ];
    abc_flag_bits  [ 3 ];
    reserved_flag  [ 1 ];
  }

  INITIAL {
    // set initial values of fields before flow starts
    flow_id     =:= uncompressed_value(4, 1);
    sequence_no =:= uncompressed_value(4, 0);
  }

  COMPRESSED obvious {
    version_no    =:= uncompressed_value(2, 1);
    type          =:= irregular(2);
    flow_id       =:= static;
    sequence_no   =:= lsb(2, -3);
    abc_flag_bits =:= irregular(3);
    reserved_flag =:= uncompressed_value(1, 0);
  }

}

bestie, though, this use of INITIAL is no good ๐Ÿ˜ญ the starting values of both flow_id and sequence_no change from flow to flow, so hardcoding them just doesn't work. INITIAL is only for fields whose starting value is fixed, which is often the case with control fields ๐Ÿ’œ


B.6. Multiple Packet Formats ๐Ÿฆ‹

so, to send the starting values for the sequence number and flow ID fields correctly, and to take advantage of the fact that the flag bits are usually the same as in the previous header, we have to stop using a single format and switch to multiple formats ๐Ÿ’… here we've written the encodings for two of the fields in the uncompressed format, since they'll always be true for uncompressed headers of that format. the rest of the fields, whose encoding can depend on exactly how the header is being compressed, get their encodings in the compressed formats.

eg_header
{
  UNCOMPRESSED {
    version_no    =:= uncompressed_value(2, 1) [ 2 ];
    type                                       [ 2 ];
    flow_id                                    [ 4 ];
    sequence_no                                [ 4 ];
    abc_flag_bits                              [ 3 ];
    reserved_flag =:= uncompressed_value(1, 0) [ 1 ];
  }


  COMPRESSED irregular_format {
    discriminator =:= '0'          [ 1 ];
    version_no                     [ 0 ];
    type          =:= irregular(2) [ 2 ];
    flow_id       =:= irregular(4) [ 4 ];
    sequence_no   =:= irregular(4) [ 4 ];
    abc_flag_bits =:= irregular(3) [ 3 ];
    reserved_flag                  [ 0 ];
  }

  COMPRESSED compressed_format {
    discriminator =:= '1'          [ 1 ];
    version_no                     [ 0 ];
    type          =:= irregular(2) [ 2 ];
    flow_id       =:= static       [ 0 ];
    sequence_no   =:= lsb(2, -3)   [ 2 ];
    abc_flag_bits =:= static       [ 0 ];
    reserved_flag                  [ 0 ];
  }
}

we've added a discriminator field, so the decompressor can tell which format the compressor picked โœจ the format with a "static" flow ID and an "lsb" encoded sequence number is now 5 bits long. and even though we had to add the discriminator, this format is still the same size as the original wrong "obvious" format, because it takes advantage of the fact that the abc flag bits rarely change ๐ŸŒธ

BUT the original "basic" format has grown by one bit too, thanks to the new discriminator (that's irregular_format now) ๐Ÿ˜ฉ an important thing to think about when making multiple formats is whether each one shows up often enough that the average compressed header gets shorter because of it. like, if the flag bits always changed between packets, the compressed_format could never be used, and all we'd have done is make the "basic" format one bit longer ๐Ÿ’€

using the notation above, we now get:

Uncompressed header: 0101000100010000
Compressed header:   00100010001000


Uncompressed header: 0101000101000000
Compressed header:   10100 ; 00100010100000


Uncompressed header: 0110000101110000
Compressed header:   11011 ; 01000010111000

the first header in the stream gets compressed the same way as before, except it now has the extra 1-bit discriminator at the start (0). when a second header shows up with the same flow ID as the first and a sequence number three higher, it can be compressed in one of two ways: either with compressed_format, or the same way as before, with irregular_format ๐Ÿงš

fyi, we show every theoretically possible encoding of a header as defined by the ROHC-FN specification, separated by semi-colons. either of the encodings above for each header could come from a valid implementation, but a good implementation would always aim to pick the one that gives the best compression. a good implementation would also think about robustness, so on the second packet it probably wouldn't assume the decompressor has the context it needs to decompress the shorter compressed_format form ๐Ÿซถ

last thing: the fields whose encodings are set in the uncompressed format have zero length when compressed. so their position in the compressed format doesn't matter, and there's no need to write them down when defining the compressed formats. in the next part of the example, they're removed from the compressed formats altogether ๐Ÿ‘‘


B.7. Variable Length Discriminators ๐ŸŒˆ

okay so suppose we analyze flows of our example protocol and find out that, while successive packets usually have the same flags, when they don't, the packet is almost always a "flags set" packet where all three of the abc flags are set ๐Ÿ”ฅ to encode the flow more efficiently, we need to write a format that reflects this.

now we have three formats in total, which means we need three discriminators to tell them apart. the obvious move is to make the discriminator two bits long and use 00, 01, and 10, for example. but we can do slightly better than that โœจ

any uniquely identifiable discriminator will do, so we can use 00, 01, and 1. if the discriminator starts with 1, that's the whole thing. if it starts with 0, the decompressor knows it has to check one more bit to figure out which format it is ๐Ÿ’–

be careful with variable length discriminators though ๐Ÿ™ˆ for example, it would be wrong to use 0, 01, and 10 as discriminators, because after reading an initial 0, the decompressor couldn't tell whether the next bit is a second bit of the discriminator or the first bit of the next field in the format. but 0, 10, and 11 would be correct, since the first bit still tells you whether more discriminator bits follow.

this gives us the following:

eg_header
{
  UNCOMPRESSED {
    version_no    =:= uncompressed_value(2, 1) [ 2 ];
    type                                       [ 2 ];
    flow_id                                    [ 4 ];
    sequence_no                                [ 4 ];
    abc_flag_bits                              [ 3 ];
    reserved_flag =:= uncompressed_value(1, 0) [ 1 ];
  }

  COMPRESSED irregular_format {
    discriminator =:= '00'         [ 2 ];
    type          =:= irregular(2) [ 2 ];
    flow_id       =:= irregular(4) [ 4 ];
    sequence_no   =:= irregular(4) [ 4 ];
    abc_flag_bits =:= irregular(3) [ 3 ];
  }

  COMPRESSED flags_set {
    discriminator =:= '01'                     [ 2 ];
    type          =:= irregular(2)             [ 2 ];
    flow_id       =:= static                   [ 0 ];
    sequence_no   =:= lsb(2, -3)               [ 2 ];
    abc_flag_bits =:= uncompressed_value(3, 7) [ 0 ];
  }

  COMPRESSED flags_static {
    discriminator =:= '1'          [ 1 ];
    type          =:= irregular(2) [ 2 ];
    flow_id       =:= static       [ 0 ];
    sequence_no   =:= lsb(2, -3)   [ 2 ];
    abc_flag_bits =:= static       [ 0 ];
  }
}

here's some example output:

Uncompressed header: 0101000100010000
Compressed header:   000100010001000


Uncompressed header: 0101000101000000
Compressed header:   10100 ; 000100010100000
Uncompressed header: 0110000101110000
Compressed header:   11011 ; 001000010111000


Uncompressed header: 0111000110101110
Compressed header:   011110 ; 001100011010111

so this is a lot like last time, except there's now an extra message at the end that has the flag bits set ๐Ÿฆ‹ the encoding for the first message in the stream is now one bit larger, while the encoding for the next two messages stays the same as before, since that format hasn't grown. thanks to variable length discriminators, that's the win ๐Ÿ’œ finally, the packet that comes through with all the flag bits set can be encoded in just six bits, only one bit more than the most common format. without the extra format, that last packet would have had to use the longest format and would have taken up 14 bits ๐Ÿ˜ญโœจ

B.8. Default Encoding โœจ

okay so some of the encoding methods we've been using got "factored out" into the uncompressed format definition, which means we don't have to spell them out for every compressed format ๐Ÿ’…. but ngl there's still some repetition. a bunch of fields use the same encoding in lots of different formats (not always all of them), and we keep redefining it each time ๐Ÿ˜ญ. so if one of those encodings ever changed, every single format using it would need an edit. the fix is to set default encoding methods for those fields, which also makes the profile way more concise ๐Ÿซถ

  eg_header
  {
    UNCOMPRESSED {
      version_no    =:= uncompressed_value(2, 1) [ 2 ];
      type                                       [ 2 ];
      flow_id                                    [ 4 ];
      sequence_no                                [ 4 ];
      abc_flag_bits                              [ 3 ];
      reserved_flag =:= uncompressed_value(1, 0) [ 1 ];
    }

    DEFAULT {
      type          =:= irregular(2);
      flow_id       =:= static;
      sequence_no   =:= lsb(2, -3);
    }

    COMPRESSED irregular_format {
      discriminator =:= '00'         [ 2 ];
      type                           [ 2 ]; // Uses default
      flow_id       =:= irregular(4) [ 4 ]; // Overrides default
      sequence_no   =:= irregular(4) [ 4 ]; // Overrides default
      abc_flag_bits =:= irregular(3) [ 3 ];
    }

    COMPRESSED flags_set {
      discriminator =:= '01' [ 2 ];
      type                   [ 2 ]; // Uses default
      sequence_no            [ 2 ]; // Uses default
      abc_flag_bits =:= uncompressed_value(3, 7);
    }

    COMPRESSED flags_static {
      discriminator =:= '1' [ 1 ];
      type                  [ 2 ]; // Uses default
      sequence_no           [ 2 ]; // Uses default
      abc_flag_bits =:= static;
    }
  }

this profile does exactly the same thing as the one from before, because it means the same thing ๐ŸŽ€ the cool part is that once the defaults are pulled out, each format only shows the encodings that are unique to it, so you can actually see what makes each one different ๐Ÿ‘€ and any default encoding that squishes down to zero bits just becomes totally implicit. for example, the compressed formats using the default for "flow_id" don't even mention it, since the default is "static" encoding, which takes zero bits ๐Ÿงš


B.9. Control Fields ๐Ÿฆ‹

okay so our compression scheme still has a little inefficiency ๐Ÿ™ˆ right now it spends two bits on the "lsb" encoded sequence number so it can survive losing one packet, but honestly, in theory we only need one bit. the reason is the weird sequence number the protocol uses, which counts up by three each time. to encode it super efficiently, we need to turn it into a field that goes up by one every time, and we do that with a control field โœจ

a control field is extra data sent in the compressed format that isn't a direct encoding of any part of the uncompressed header. control fields let us send extra info in the compressed format so that other fields can be compressed more efficiently. iconic, right? ๐Ÿ‘‘

the control field we're adding scales the sequence number down by a factor of three. so instead of encoding the original sequence number in the compressed packet, we encode the scaled one. that means we still get robustness to losing one packet, but now we only need one bit of "lsb" encoding ๐Ÿ’…

  eg_header
  {
    UNCOMPRESSED {
      version_no    =:= uncompressed_value(2, 1) [ 2 ];
      type                                       [ 2 ];
      flow_id                                    [ 4 ];
      sequence_no                                [ 4 ];
      abc_flag_bits                              [ 3 ];
      reserved_flag =:= uncompressed_value(1, 0) [ 1 ];
    }

    CONTROL {
      // need modulo maths to calculate scaling correctly,
      // due to 4 bit wrap around
      scaled_seq_no   [ 4 ];
      ENFORCE(sequence_no.UVALUE
                == (scaled_seq_no.UVALUE * 3) % 16);
    }

    DEFAULT {
      type          =:= irregular(2);
      flow_id       =:= static;
      scaled_seq_no =:= lsb(1, -1);
    }

    COMPRESSED irregular_format {
      discriminator =:= '00'         [ 2 ];
      type                           [ 2 ];
      flow_id       =:= irregular(4) [ 4 ];
      scaled_seq_no =:= irregular(4) [ 4 ]; // Overrides default
      abc_flag_bits =:= irregular(3) [ 3 ];
    }

    COMPRESSED flags_set {
      discriminator =:= '01' [ 2 ];
      type                   [ 2 ];
      scaled_seq_no          [ 1 ]; // Uses default
      abc_flag_bits =:= uncompressed_value(3, 7);
    }

    COMPRESSED flags_static {
      discriminator =:= '1' [ 1 ];
      type                  [ 2 ];
      scaled_seq_no         [ 1 ]; // Uses default
      abc_flag_bits =:= static;
    }
  }

normally, the encoding method on a field tells you how long that field is. but here, nothing encodes "sequence_no" directly, so its length has to be spelled out with an "ENFORCE" statement ๐Ÿค“ we're using the abbreviated syntax for consistency and readability. this is kind of a plot twist, because most length indications are redundant and optional, but this one is NOT optional ๐Ÿ˜ญ if you took it out, the length of the "sequence_no" field would just be undefined. oops.

here's some example output:

  Uncompressed header: 0101000100010000
  Compressed header:   000100011011000


  Uncompressed header: 0101000101000000
  Compressed header:   1010 ; 000100011100000


  Uncompressed header: 0110000101110000
  Compressed header:   1101 ; 001000011101000


  Uncompressed header: 0111000110101110
  Compressed header:   01110 ; 001100011110111

and see? we save another bit in most packets ๐ŸŽ‰ if most of a flow is "flags_static" headers, the average header in a compressed flow ends up being just over a quarter the size of its uncompressed version. that's a whole vibe โœจ


B.10. Use of "ENFORCE" Statements as Conditionals ๐Ÿ’œ

so earlier we made a brand new format, "flags_set", for packets where all three flag bits are set. here's the tea ๐Ÿ“ those three flags are ALWAYS all set for "type 3" packets and never all set for any other type. (a "type 3" packet is just one where the type field is three.)

that gives us a chance for extra efficiency. since we know the type is three, we don't have to send the type field in the compressed header at all ๐Ÿ™Œ the type field used to be "irregular(2)", which is two bits, so dropping it shrinks the "flags_set" format from five bits down to three. that makes it the smallest format in our encoding method definition, lowkey iconic.

now, to say that "flags_set" should only be used for "type 3" headers, and "flags_static" only when the type isn't three, we have to write those conditions inside each format. we can do that with an "ENFORCE" statement:

  eg_header
  {
    UNCOMPRESSED {
      version_no    =:= uncompressed_value(2, 1) [ 2 ];
      type                                       [ 2 ];
      flow_id                                    [ 4 ];
      sequence_no                                [ 4 ];
      abc_flag_bits                              [ 3 ];
      reserved_flag =:= uncompressed_value(1, 0) [ 1 ];
    }

    CONTROL {
      // need modulo maths to calculate scaling correctly,
      // due to 4 bit wrap around
      scaled_seq_no   [ 4 ];
      ENFORCE(sequence_no.UVALUE
                == (scaled_seq_no.UVALUE * 3) % 16);
    }

    DEFAULT {
      type          =:= irregular(2);
      scaled_seq_no =:= lsb(1, -1);
      flow_id       =:= static;
    }

    COMPRESSED irregular_format {
      discriminator =:= '00'         [ 2 ];
      type                           [ 2 ];
      flow_id       =:= irregular(4) [ 4 ];
      scaled_seq_no =:= irregular(4) [ 4 ];
      abc_flag_bits =:= irregular(3) [ 3 ];
    }

    COMPRESSED flags_set {
      ENFORCE(type.UVALUE == 3); // redundant condition
      discriminator =:= '01'                      [ 2 ];
      type          =:= uncompressed_value(2, 3)  [ 0 ];
      scaled_seq_no                               [ 1 ];
      abc_flag_bits =:= uncompressed_value(3, 7)  [ 0 ];
    }

    COMPRESSED flags_static {
      ENFORCE(type.UVALUE != 3);
      discriminator =:= '1'    [ 1 ];
      type                     [ 2 ];
      scaled_seq_no            [ 1 ];
      abc_flag_bits =:= static [ 0 ];
    }
  }

the two "ENFORCE" statements in the last two formats work as "guards" ๐Ÿ›ก๏ธ a guard stops a format from being used in the wrong situation. fun twist: the "ENFORCE" in "flags_set" is actually redundant ๐Ÿ™ˆ the new encoding method on the "type" field already enforces that same condition. "uncompressed_value(2,3)" binds the "UVALUE" attribute to three, which is exactly what that "ENFORCE" does, so it can go without changing anything. the "uncompressed_value" encoding method, though, is NOT redundant. it sets other bindings on the type field on top of the one the "ENFORCE" sets, so you couldn't get rid of the encoding method and keep only the "ENFORCE" ๐Ÿ˜ญ

guards are preventative only โš ๏ธ a guard can never force the compressor to pick a format. a format is only guaranteed to be chosen in a situation when no other format could be used instead. the example below shows this. the compressor is still free to choose the "irregular" format if it wants to:
  Uncompressed header: 0101000100010000
  Compressed header:   000100011011000


  Uncompressed header: 0101000101000000
  Compressed header:   1010 ; 000100011100000
  Uncompressed header: 0110000101110000
  Compressed header:   1101 ; 001000011101000


  Uncompressed header: 0111000110101110
  Compressed header:   010 ; 001100011110111

in this example flow, that saves just two extra bits, which is a 7% saving ๐Ÿ’–


Authors' Addresses ๐ŸŽ€

   Robert Finking
   Siemens/Roke Manor Research
   Old Salisbury Lane
   Romsey, Hampshire  SO51 0ZN
   UK

   Phone: +44 (0)1794 833189
   EMail: robert.finking@roke.co.uk
   URI:   http://www.roke.co.uk


   Ghyslain Pelletier
   Ericsson
   Box 920
   Lulea  SE-971 28
   Sweden

   Phone: +46 (0) 8 404 29 43
   EMail: ghyslain.pelletier@ericsson.com

Okay bestie, the legal bit ๐Ÿซถ The copyright belongs to The IETF Trust (2007), and this document is subject to the rights, licenses, and restrictions in BCP 78. Other than what BCP 78 says, the authors keep all their rights ๐Ÿ’œ

This document and everything in it is provided "AS IS" ๐Ÿ™ˆ 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. That includes, but isn't limited to, any warranty that using the information won't infringe any rights, and any implied warranties of merchantability or fitness for a particular purpose. Nobody is promising it'll be perfect ๐Ÿ’€

Heads up: that disclaimer is the official, serious bit, so read it as written and don't treat it like a vibe check.

Intellectual Property

The IETF doesn't take a side on whether any Intellectual Property Rights (or other rights) that someone claims about the tech in this document are valid, or how far they reach ๐ŸŒธ It also doesn't say it has checked for any such rights itself. Anything about how rights work for RFC documents lives in BCP 78 and BCP 79 ๐Ÿ“š

If you want copies of IPR disclosures sent to the IETF Secretariat, or the assurances of licenses that are available, or the results of trying to get a general license for implementers or users, you can check the IETF online IPR repository at http://www.ietf.org/ipr ๐Ÿ”ฅ

The IETF also invites anyone who knows about copyrights, patents, patent applications, or other proprietary rights that might cover technology needed to implement this standard to tell them. Send that info to ietf-ipr@ietf.org ๐Ÿ’…


Acknowledgement

And last but not least ๐ŸŽ€ funding for the RFC Editor function is currently provided by the Internet Society. Thank you, Internet Society ๐Ÿฆ‹๐ŸŒˆ

๐Ÿ“ฆ where this page comes from