RFC 2508
Compressing IP/UDP/RTP Headers for Low-Speed Serial Links
Compressing IP/UDP/RTP Headers for Low-Speed Serial Links ๐
okay bestie, this RFC is from February 1999 and it's a Standards Track doc (so it's a real protocol spec, not just vibes ๐ ). It was written by S. Casner and V. Jacobson, both at Cisco Systems, and it's published under the Network Working Group. It doesn't list anything it obsoletes or updates in its header. ๐ฆ
Status of This Memo โจ
ok so this doc specifies an Internet standards track protocol for the Internet community, and it's basically asking everyone to talk about it and send over suggestions for making it better ๐ซถ. If you want to know where this protocol stands officially, check the current edition of the "Internet Official Protocol Standards" (STD 1). And the distribution of this memo is unlimited, so share it with everyone ๐
Copyright Notice ๐
Copyright (C) The Internet Society (1999). All Rights Reserved.
Abstract ๐ธ
this doc explains a way to shrink the headers of IP/UDP/RTP datagrams so they don't eat up so much space on low-speed serial links ๐ญ. In lots of cases, all three headers can get squished down to just 2-4 bytes. iconic ๐
comments are wanted! send them to the working group mailing list at rem-conf@es.net and/or to the author(s) ๐ง
the key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119. ๐ฅ
1. Introduction ๐
ok so the Real-time Transport Protocol got published as an RFC [1], and ever since then people have been super into using RTP as one step toward getting different network audio/video apps to work together ๐. BUT there's a concern: the 12-byte RTP header is kind of huge for a 20-byte payload when you're running over slow lines like dial-up modems at 14.4 or 28.8 kb/s ๐. (Some apps already running in that environment use their own protocol with a header of just a few bytes, but it has less functionality than RTP. ๐)
header size can be cut down with compression, which worked really well for TCP [2] โจ. Here, the compression could be applied to the RTP header by itself on an end-to-end basis, or to the whole combo of IP, UDP and RTP headers on a link-by-link basis ๐ฆ. Compressing the 40 bytes of combined headers together gets you way more gain than compressing just the 12 bytes of RTP header, because the result ends up about the same size (2-4 bytes) either way ๐คฏ. Link-by-link also performs better since the delay and loss rate are lower ๐ซถ. So the method in this doc is for combined compression of IP, UDP and RTP headers on a link-by-link basis. main character energy ๐
this doc defines a compression scheme that can be used with IPv4, IPv6, or packets encapsulated with more than one IP header, though the main focus is IPv4 ๐. The IP/UDP/RTP compression here is meant to fit inside the bigger compression framework specified in [3], which works for both IPv6 and IPv4. That framework splits transports above IP into two classes: TCP and non-TCP. This spec creates IP/UDP/RTP as a third class pulled out of the non-TCP class ๐ .
2. Assumptions and Tradeoffs ๐
the goal here is to get the IP/UDP/RTP headers down to two bytes for most packets when no UDP checksums are being sent, or four bytes when checksums are included ๐ธ. The big motivation is sending audio and video over 14.4 and 28.8 dialup modems ๐. These links tend to be full-duplex, and the protocol takes advantage of that, though it can still work on simplex links with reduced performance ๐ง. This compression scheme does best on local links with a low round-trip-time โจ.
this spec doesn't deal with segmentation and preemption of large packets to cut down the delay on the slow link that small real-time packets experience. The one exception is that Section 4 points out some interactions between segmentation and compression that can happen ๐. Segmentation schemes can be defined separately and used together with the compression here ๐ซถ.
worth noting that implementation simplicity is a big deal when you're judging a compression scheme ๐ฅ. Communications servers might need to support compression across as many as 100 dial-up modem lines on a single processor ๐ญ. So it can make sense to simplify the design and give up some generality, or to build a flexible design that's general but can be subsetted for simplicity ๐. Higher compression gain could come from sending more complex models of the changing header fields from the compressor to the decompressor, but that complexity is considered unnecessary here ๐ โโ๏ธ. The next sections talk through some of the tradeoffs listed above ๐.
2.1. Simplex vs. Full Duplex ๐
if nothing else is a constraint, a compression scheme that works over simplex links is the one you'd pick over one that doesn't ๐ . BUT running over a simplex link means you need periodic refreshes with an uncompressed packet header to restore compression state if an error happens ๐ญ. If an explicit error signal can be sent back instead, recovery gets way faster, and the overhead in the no-error case goes down too ๐ฆ. To get those perf wins, this spec includes an explicit error indication sent on the reverse path โจ.
on a simplex link, you could use a periodic refresh instead ๐ง. Whenever the decompressor detects an error in a particular packet stream, it would just throw away all the packets in that stream until it gets an uncompressed header for that stream, and then it picks decompression back up ๐. The penalty is the potentially large number of packets that get discarded ๐. The periodic refresh method described in Section 3.3 of [3] applies to IP/UDP/RTP compression on simplex links or on links with high delay, and it also applies to other non-TCP packet streams ๐ธ.
2.2. Segmentation and Layering ๐
the delay from the time it takes to send a large packet over the slow link isn't a problem for one-way audio, for example, because the receiver can adapt to the variance in delay ๐ซถ. But for interactive conversations, keeping the end-to-end delay low is critical ๐ฅ. Segmenting large, non-real-time packets so small real-time packets can go out between the segments can cut down the delay ๐.
this spec only deals with compression and assumes that segmentation, if it's included, gets handled as a separate layer ๐ง. It would be a bad move to mash segmentation and compression together in a way where the compression couldn't be used by itself in situations where segmentation isn't necessary or isn't practical ๐ โโ๏ธ. Similarly, it's best to avoid requiring a reservation protocol ๐ฆ. The compression scheme can run locally on the two ends of a link, independent of any other mechanisms, except that the link layer has to provide some packet type codes, a packet length indication, and good error detection ๐ .
BUT separately compressing the IP/UDP and RTP layers loses too much of the compression gain you could get by treating them together ๐ญ. Crossing these protocol layer boundaries is fine here because the same function is being applied across all the layers โจ.
3. The Compression Algorithm ๐
the compression algorithm in this doc is heavily based on the design of TCP/IP header compression as described in RFC 1144 [2] ๐. Check that RFC for more info on the underlying motivations and general principles of header compression ๐.
3.1. The basic idea ๐
in TCP header compression, the first factor-of-two reduction in data rate comes from noticing that half of the bytes in the IP and TCP headers stay constant for the whole life of the connection ๐คฏ. After the uncompressed header is sent once, those fields can be left out of the compressed headers that follow ๐ซถ. The rest of the compression comes from differential coding on the changing fields to shrink them, and from getting rid of the changing fields entirely in common cases by calculating the changes from the length of the packet ๐ง. That length is indicated by the link-level protocol ๐.
for RTP header compression, some of the same techniques can be used ๐ธ. BUT the big win comes from noticing that even though several fields change in every packet, the difference from packet to packet is often constant, so the second-order difference is zero ๐. By keeping both the uncompressed header and the first-order differences in the session state shared between the compressor and decompressor, all that has to be communicated is an indication that the second-order difference was zero ๐ฆ. In that case, the decompressor can rebuild the original header with no loss of information, just by adding the first-order differences to the saved uncompressed header as each compressed packet comes in ๐ฅ.
just like TCP/IP header compression keeps shared state for multiple simultaneous TCP connections, this IP/UDP/RTP compression SHOULD maintain state for multiple session contexts ๐. A session context is defined by the combo of the IP source and destination addresses, the UDP source and destination ports, and the RTP SSRC field ๐. A compressor implementation might use a hash function on these fields to index a table of stored session contexts ๐ง. The compressed packet carries a small integer, called the session context identifier or CID, which says which session context that packet should be read against ๐ . The decompressor can use the CID to index its table of stored session contexts directly โจ.
because RTP compression is lossless, it can be applied to any UDP traffic that benefits from it ๐. Most likely the only packets that'll benefit are RTP packets, but it's totally acceptable to use heuristics to figure out whether a packet is RTP, because no harm is done if the heuristic gets it wrong ๐. The catch is this means running the compression algorithm on all UDP packets, or at least the ones with even port numbers (see section 3.4) ๐ซถ.
most compressor implementations will need to keep a "negative cache" of packet streams that have failed to compress as RTP packets after some number of attempts, so they don't keep trying ๐ โโ๏ธ. Failing to compress means some fields in the potential RTP header that are expected to stay constant most of the time, like the payload type field, keep changing ๐. Even if the other such fields stay constant, a packet stream with a constantly changing SSRC field SHOULD be entered in the negative cache so it doesn't eat up all the available session contexts ๐ฅ. The negative cache is indexed by the source and destination IP address and UDP port pairs, but NOT the RTP SSRC field, since the SSRC might be changing ๐ธ. When RTP compression fails, the IP and UDP headers MAY still be compressed ๐ฆ.
โ ๏ธ heads up, this one's serious: fragmented IP packets that aren't initial fragments, and packets that aren't long enough to contain a complete UDP header, MUST NOT be sent asFULL_HEADERpackets ๐ซ. Also, packets that don't additionally contain at least 12 bytes of UDP data MUST NOT be used to establish RTP context ๐. If a packet like that is sent as aFULL_HEADERpacket, it MAY be followed byCOMPRESSED_UDPpackets, but it MUST NOT be followed byCOMPRESSED_RTPpackets ๐ญ.
3.2. Header Compression for RTP Data Packets โจ
okay bestie, in the IPv4 header only the total length, packet ID, and header check-sum fields normally change ๐ . The total length is redundant with the length the link layer already gives you. This scheme has to depend on the link layer for good error detection (like PPP's CRC [4]), so the header checksum can be elided too ๐ซถ. That leaves just the packet ID, which wouldn't need to be sent if there's no IP fragmentation. BUT to keep compression lossless, changes in the packet ID still get transmitted ๐ญ. The packet ID usually goes up by one or a small number per packet. (Some systems increment the ID with the bytes swapped, which gives slightly less compression, rip ๐.) In the IPv6 base header there's no packet ID and no header checksum, so only the payload length field changes ๐ฆ.
In the UDP header, the length field is redundant with the IP total length field and the length the link layer indicates ๐ธ. The UDP check-sum field will be a constant zero if the source elects not to generate UDP checksums. Otherwise the checksum must be communicated intact so the compression stays lossless. Keeping end-to-end error detection for apps that need it is a super important principle ๐๐.
In the RTP header, the SSRC identifier stays constant within a given context, since it's literally part of what identifies that context โจ. For most packets, only the sequence number and the timestamp change from packet to packet ๐. If packets aren't lost or misordered upstream from the compressor, the sequence number goes up by one for each packet. For audio packets of constant duration, the timestamp goes up by the number of sample periods carried in each packet ๐. For video, the timestamp changes on the first packet of each frame, then stays constant for any extra packets in that frame ๐ฌ. If each video frame fits in only one packet, but the frames are generated at a constant rate, the change in timestamp from frame to frame is also constant ๐.
Here's the big brain part ๐ง ๐ฅ: in each of these cases, the second-order difference of the sequence number and timestamp fields is zero. So the next packet header can be built from the previous one by adding the first-order differences for these fields. Those differences are stored in the session context, along with the previous uncompressed header ๐ . When the second-order difference isn't zero, the change is usually way smaller than the full number of bits in the field. So you can shrink things by encoding the new first-order difference and sending that, instead of the absolute value ๐ญโจ.
The M bit gets set on the first packet of an audio talkspurt and on the last packet of a video frame ๐ค. If it were treated as a constant field, so every change required sending the full RTP header, the compression would drop a lot ๐. So one bit in the compressed header carries the M bit explicitly ๐ซถ.
If the packets are flowing through an RTP mixer (most commonly for audio), the CSRC list and CC count also change ๐ง. But the CSRC list usually stays the same during a talkspurt or even longer, so it only needs to be sent when it changes ๐.
3.3. The protocol ๐ง
The compression protocol must keep a collection of shared information in a consistent state between the compressor and decompressor ๐ค. There's a separate session context for each IP/UDP/RTP packet stream. Each one is defined by a particular combo of the IP source and destination addresses, the UDP source and destination ports, and the RTP SSRC field ๐. The number of session contexts to be maintained MAY be negotiated between the compressor and decompressor ๐ธ. Each context is identified by an 8- or 16-bit identifier, depending on how many contexts were negotiated, so the maximum number is 65536 ๐. Both uncompressed and compressed packets MUST carry the context ID and a 4-bit sequence number, which is used to detect packet loss between the compressor and decompressor ๐. Each context has its own separate sequence number space, so a single packet loss only needs to invalidate one context ๐.
The shared information in each context has these items:
- The full IP, UDP and RTP headers, possibly including a CSRC list, for the last packet sent by the compressor or reconstructed by the decompressor ๐ฆ.
- The first-order difference for the IPv4 ID field. It's initialized to 1 whenever an uncompressed IP header for this context is received, and updated each time a delta IPv4 ID field is received in a compressed packet ๐ฆ.
- The first-order difference for the RTP timestamp field. It's initialized to 0 whenever an uncompressed packet for this context is received, and updated each time a delta RTP timestamp field is received in a compressed packet โฐ.
- The last value of the 4-bit sequence number, which is used to detect packet loss between the compressor and decompressor ๐ข.
- The current generation number for non-differential coding of UDP packets with IPv6 (see [3]). For IPv4, the generation number may be set to zero if the
COMPRESSED_NON_TCPpacket type (defined below) is never used ๐. - A context-specific delta encoding table (see section 3.3.4) may optionally be negotiated for each context ๐ญ.
To communicate packets in the various uncompressed and compressed forms, this protocol depends on the link layer being able to indicate four new packet formats, on top of the normal IPv4 and IPv6 packet formats ๐:
FULL_HEADER: communicates the uncompressed IP header plus any following headers and data, to set up the uncompressed header state in the decompressor for a particular context ๐๏ธ. TheFULL_HEADERpacket also carries the 8- or 16-bit session context identifier and the 4-bit sequence number, so the compressor and decompressor stay in sync ๐ซถ. The format is shown in section 3.3.1.COMPRESSED_UDP: communicates the IP and UDP headers compressed to 6 or fewer bytes (often 2 if UDP checksums are disabled), followed by any subsequent headers (possibly RTP) in uncompressed form, plus data ๐๏ธ. This packet type is used when there are differences in the usually constant fields of the (potential) RTP header โจ. The RTP header can include a changed SSRC value, so this packet may redefine the session context ๐. The format is shown in section 3.3.3.COMPRESSED_RTP: indicates that the RTP header is compressed along with the IP and UDP headers ๐ซ. This header might still be just two bytes, or bigger if differences need to be communicated ๐. This packet type is used when the second-order difference (at least in the usually constant fields) is zero ๐งฎ. It includes delta encodings for the fields that changed by something other than the expected amount, so the first-order differences get set up after an uncompressed RTP header is sent, and again whenever they change ๐. The format is shown in section 3.3.2.CONTEXT_STATE: a special packet sent from the decompressor to the compressor, listing context IDs for which synchronization has been lost or might have been lost ๐จ. It's only sent across the point-to-point link, so it doesn't need an IP header ๐ฐ๏ธ. The format is shown in section 3.3.5.
When this compression scheme is used with IPv6 as part of the general header compression framework specified in [3], another packet type MAY be used ๐ง:
COMPRESSED_NON_TCP: communicates the compressed IP and UDP headers as defined in [3], without differential encoding ๐ง. If it were used for IPv4, it would need one or two extra bytes compared to theCOMPRESSED_UDPform above, in order to carry the IPv4 ID field ๐. For IPv6 there's no ID field, and this non-differential compression is more resilient to packet loss ๐ก๏ธโจ.
Assignments of numeric codes for these packet formats in the Point-to-Point Protocol [4] are to be made by the Internet Assigned Numbers Authority ๐ข๐.
3.3.1. FULL_HEADER (uncompressed) packet format โจ
okay so this is the FULL_HEADER packet, the uncompressed one that sends everything in full ๐ the definition here is meant to be consistent with the one in [3], and all the juicy design reasoning lives over there ๐ง
the FULL_HEADER packet looks just like the original packet it came from ๐ for IPv4 that's usually an IP header, then a UDP header, then the UDP payload, which might be an RTP header plus its payload. it can also carry IP-encapsulated packets, so you'd get two IP headers, then UDP and maybe RTP. with IPv6 the packet can be some mix of IPv6 and IPv4 headers ๐ฆ each header tells you what comes next through the type field of the header before it, as usual โจ
now the twist ๐ a FULL_HEADER packet has to also carry the compression context ID and the 4-bit sequence number, and it has to do that without making the header bigger ๐ so those values get tucked into the length fields of the IP and UDP headers, since the real length can be figured out from the link layer anyway. you need two 16-bit length fields, and they come from the first two headers in the packet. for an IPv4/UDP packet, the first one is the total length field of the IPv4 header and the second is the length field of the UDP header. for an IPv4 encapsulated packet, the first comes from the total length field of the first IP header and the second comes from the total length field of the second IP header ๐
as specified in Sections 5.3.2 of [3], where the context ID (CID) and 4-bit sequence number sit depends on whether you picked 8-bit or 16-bit context IDs ๐ (16 bits wide, with the most-significant bit is to the left):
For 8-bit context ID:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|0|1| Generation| CID | First length field
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0 | seq | Second length field
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
For 16-bit context ID:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|1|1| Generation| 0 | seq | First length field
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| CID | Second length field
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
the first bit in the first length field tells you the length of the CID ๐ the CID length MUST either be constant for all contexts, or two extra distinct packet types MUST be provided to separately indicate COMPRESSED_UDP and COMPRESSED_RTP packet formats with 8- and 16-bit CIDs. the second bit in the first length field is 1 to say the 4-bit sequence number is there, which is always the case for this IP/UDP/RTP compression scheme ๐ซถ
the generation field is used with IPv6 for COMPRESSED_NON_TCP packets, as described in [3] โจ if you're an IPv4-only implementation that doesn't use COMPRESSED_NON_TCP packets, the compressor SHOULD set the generation value to zero. for things to stay consistent between IPv4 and IPv6, the decompressor stores the generation value in the context when it arrives, and the most recent value gets sent back in the CONTEXT_STATE packet ๐
when a FULL_HEADER packet shows up, the decompressor stores the complete set of headers into the context picked by the context ID ๐ง the 4-bit sequence number gets stored there too, which resynchronizes the decompressor to the compressor. main character energy ๐
when COMPRESSED_NON_TCP packets are in use, the 4-bit sequence number goes into the "Data Field" of that packet, and the D bit gets set as described in Section 6 of [3] ๐ฆ when a COMPRESSED_NON_TCP packet is received, its generation number is compared to the value stored in the context. if they don't match, the context isn't up to date and it MUST be refreshed by a FULL_HEADER packet ๐ญ if the generation does match, then the compressed IP and UDP header information, the 4-bit sequence number, and the (potential) RTP header all get stored into the saved context ๐
the context's memory usage depends on how many encapsulating headers the FULL_HEADER packet includes ๐ and the compressor and decompressor MAY negotiate a maximum header size ๐
3.3.2. COMPRESSED_RTP packet format
okay so when the second-order difference of the RTP header is zero from packet to packet, the decompressor can rebuild the packet all by itself ๐ง it just adds the stored first-order differences to the stored uncompressed header from the previous packet ๐ the only things that need to be sent are the session context identifier and a small sequence number (this one is NOT the RTP sequence number, bestie) so the two ends stay in sync and can notice when packets go missing ๐ซถ
if the second-order difference isn't zero for some fields, only the new first-order difference for those fields gets sent, using a compact encoding โจ the decompressor adds those new values to the matching fields in its uncompressed header and also saves them in its session context, so they keep getting added on every later packet where the second-order difference is zero ๐ฆ whenever the first-order difference changes, it gets sent and saved again.
in practice, the only fields where it's actually useful to store the first-order difference are the IPv4 ID field and the RTP timestamp ๐ธ the RTP sequence number usually goes up by 1, so that's the default. if it changes by something other than 1, the difference has to be sent, but it does NOT become the expected difference for the next packet. the expected first-order difference stays fixed at 1, so if the next packet is in order, nothing extra needs to be sent for it ๐
for the RTP timestamp: when a FULL_HEADER, COMPRESSED_NON_TCP or COMPRESSED_UDP packet gets sent to refresh the RTP state, the stored first-order difference is set to zero ๐ if the timestamp is the same on the next packet (like when it's the same video frame), the second-order difference is zero, iykyk. otherwise, the difference between the two timestamps gets sent as the new first-order difference, to be added to the timestamp in the uncompressed header the decompressor has stored, and it's also saved as the first-order difference in that context. every time the first-order difference changes after that, the new one gets sent and used to update the context ๐
same vibe for the IPv4 ID field ๐ it usually goes up by one, so its first-order difference starts at one whenever the state is refreshed by a FULL_HEADER packet, or whenever a COMPRESSED_NON_TCP packet gets sent (because that one carries the ID field uncompressed). after that, any change to the first-order difference gets sent and stored in the context. it's giving consistency, periodt ๐
a bit mask shows which fields changed by something other than the expected difference ๐ on top of the small link sequence number, these are the items that can be conditionally sent in the compressed IP/UDP/RTP header:
I = IPv4 packet ID (always 0 if no IPv4 header)U = UDP checksumM = RTP marker bitS = RTP sequence numberT = RTP timestampL = RTP CSRC count and list
if you need 4 bits for the link sequence number to get a decent chance of spotting loss, there aren't enough bits left to give each item its own bit AND still squeeze everything into one byte next to the context ID ๐ญ kind of a tight squeeze, ngl.
the U bit doesn't need to be sent explicitly to say whether a UDP checksum is there, because a source usually puts checksums on every packet of a session or on none of them ๐ when the session state gets set up with an uncompressed header and the checksum is nonzero, an unencoded 16-bit checksum gets inserted into the compressed header of every later packet, until another uncompressed packet changes that setting.
of the items left over, the L bit for the CSRC count and list is probably the one used least often ๐ instead of giving it a whole bit in the mask, an unusual combination of the other bits gets used to signal it. this combination is called MSTI. when the bits for the IP packet ID, RTP marker bit, RTP sequence number and RTP timestamp are ALL set, that's a special case meaning an extended form of the compressed RTP header follows โจ that header has an extra byte holding the real values of those four bits plus the CC count, and then the CSRC list (as long as the CC count says) gets included exactly as it looks in the uncompressed RTP header. main character energy for sure ๐
the other RTP header fields (version, P bit, X bit, payload type and SSRC identifier) are assumed to stay pretty steady ๐ซถ the SSRC identifier is defined to be constant for a given context, since it's one of the things that picks the context in the first place. if any of the other fields change, the uncompressed RTP header MUST be sent as described in Section 3.3.3, no excuses ๐ฅ
here's the compressed IP/UDP/RTP header ๐ฆ dotted lines mean the field may or may not be there. the most significant bit is numbered 0, and multi-byte fields go out in network byte order (most significant byte first). the delta fields are usually a single byte like shown, but they can be two or three bytes depending on the delta value, as explained in Section 3.3.4.
0 1 2 3 4 5 6 7
+...............................+
: msb of session context ID : (if 16-bit CID)
+-------------------------------+
| lsb of session context ID |
+---+---+---+---+---+---+---+---+
| M | S | T | I | link sequence |
+---+---+---+---+---+---+---+---+
: :
+ UDP checksum + (if nonzero in context)
: :
+...............................+
: :
+ "RANDOM" fields + (if encapsulated)
: :
+...............................+
: M'| S'| T'| I'| CC : (if MSTI = 1111)
+...............................+
: delta IPv4 ID : (if I or I' = 1)
+...............................+
: delta RTP sequence : (if S or S' = 1)
+...............................+
: delta RTP timestamp : (if T or T' = 1)
+...............................+
: :
: CSRC list : (if MSTI = 1111
: : and CC nonzero)
: :
+...............................+
: :
: RTP header extension : (if X set in context)
: :
: :
+-------------------------------+
| |
| RTP data |
/ /
/ /
| |
+-------------------------------+
: padding : (if P set in context)
+...............................+
when the context set up by the FULL_HEADER packet has more than one IPv4 header, the IP ID fields of the encapsulating headers MUST be sent as absolute values, as described in [3] ๐ธ those fields are called "RANDOM" fields. they go into the COMPRESSED_RTP packet in the same order they show up in the original headers, right after the UDP checksum if there is one, or right after the MSTI byte if there isn't, just like the diagram shows. the IP ID of a UDP header only gets sent differentially if an IPv4 packet comes right before that UDP header. then it can be sent with no bits at all if the second difference is zero, or as a delta IPv4 ID field if it isn't. and if there's no IPv4 header right before the UDP header, the I bit MUST be 0 and no delta IPv4 ID field will be there ๐ญ๐
3.3.3. COMPRESSED_UDP packet format
if any of the RTP header fields that normally stay constant (like the payload type field) change, an uncompressed RTP header MUST be sent ๐ if the IP and UDP headers don't need updating too, that RTP header MAY be carried in a COMPRESSED_UDP packet instead of a FULL_HEADER packet โจ the COMPRESSED_UDP packet looks like COMPRESSED_RTP, EXCEPT the M, S and T bits are always 0 and their delta fields never show up:
0 1 2 3 4 5 6 7
+...............................+
: msb of session context ID : (if 16-bit CID)
+-------------------------------+
| lsb of session context ID |
+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | I | link sequence |
+---+---+---+---+---+---+---+---+
: :
+ UDP checksum + (if nonzero in context)
: :
+...............................+
: :
+ "RANDOM" fields + (if encapsulated)
: :
+...............................+
: delta IPv4 ID : (if I = 1)
+-------------------------------+
| UDP data |
: (uncompressed RTP header) :
note that this is a form of IP/UDP header compression that's different from the COMPRESSED_NON_TCP packet type defined in [3] ๐ฆ the goal is to hit the two-byte target when UDP checksums are turned off, which IPv4 allows ๐ the protocol in [3] doesn't do differential coding for UDP packets, so with IPv4, two bytes of IP ID plus two bytes of UDP checksum (if it's nonzero) would ALWAYS get sent on top of the two-byte compression prefix ๐ญ for IPv6, the COMPRESSED_NON_TCP packet type MAY be used instead ๐
3.3.4. Encoding of differences ๐
okay so the delta fields in COMPRESSED_RTP and COMPRESSED_UDP packets get squished down with a variable-length mapping, so the more common values take up less room ๐ฆ there's a default encoding below, but it is RECOMMENDED that implementations use a table-driven delta encoder and decoder. that way each session can negotiate its own table if it makes sense, maybe even an optimal Huffman encoding ๐
encodings that read the bit stream in order (the default table and Huffman are examples of this) keep the table size reasonable, and they may run faster than a version that has to explicitly test ranges of values โจ
the default delta encoding is in the table below. it was designed to handle the small changes that can show up in the IP ID and the RTP sequence number when packets get lost upstream of the compressor ๐ซถ it still manages to fit most audio and video deltas in two bytes ๐ the left column is the decimal value being encoded, and the right column is the bytes in hex, in the order they get sent (network byte order). only the first and last value of each contiguous range is shown, with ellipses in between:
Decimal Hex
-16384 C0 00 00
: :
-129 C0 3F 7F
-128 80 00
: :
-1 80 7F
0 00
: :
127 7F
128 80 80
: :
16383 BF FF
16384 C0 40 00
: :
4194303 FF FF FF
for positive values, a change of zero through 127 gets written directly as one byte ๐ธ if the two most significant bits of that byte are 10 or 11, that's a signal that it's extended to a two- or three-byte value, respectively. the six least significant bits of the first byte get combined, in decreasing order of significance, with the next one or two bytes to make a 14- or 22-bit value ๐ง
negative deltas can happen when packets get misordered, or with the intentionally out-of-order RTP timestamps on MPEG video [5]. those events are less likely, so a smaller range of negative values gets encoded using parts of the positive side of the table that would otherwise go unused ๐
a change in the RTP timestamp value less than -16384 or greater than 4194303 forces the RTP header to be sent uncompressed, using aFULL_HEADER,COMPRESSED_NON_TCPorCOMPRESSED_UDPpacket type. no cap, that's a big deal ๐ญ
the IP ID and RTP sequence number fields are only 16 bits, so negative deltas for those fields SHOULD be masked to 16 bits and then encoded (as large positive 16-bit numbers) ๐
3.3.5. Error Recovery ๐
so this part is about what happens when things go sideways. whenever the 4-bit sequence number for a particular context increments by anything other than 1, the decompressor MUST invalidate that context and send a CONTEXT_STATE packet back to the compressor saying the context has been invalidated ๐ the exception is when the sequence number was set by a FULL_HEADER or COMPRESSED_NON_TCP packet. all packets for the invalid context MUST be discarded until a FULL_HEADER or COMPRESSED_NON_TCP packet comes in for that context to re-establish consistent state (unless the "twice" algorithm is used, which we'll get to later in this section) ๐ฅ since several compressed packets might show up in the meantime, the decompressor SHOULD NOT retransmit the CONTEXT_STATE packet for every single compressed packet it gets. instead it SHOULD limit how often it retransmits, so it doesn't flood the reverse channel ๐
when an error happens on the link, the link layer usually throws out the damaged packet (if there was one), but it might give some indication that something went wrong ๐ฅน then it can take a while before another packet for the same context arrives, and the decompressor has to toss that one too once it notices it's out of sequence. that means at least two packets get lost ๐ฉ to recover faster when the link does give an explicit error indication, the decompressor MAY optionally send an advisory CONTEXT_STATE packet listing the last valid sequence number and generation number for one or more recently active contexts (not necessarily all of them) ๐ฆ for a given context, if the compressor hasn't sent a compressed packet with a higher sequence number, and the generation number matches the current generation, no corrective action is required ๐ otherwise, the compressor MAY choose to mark the context invalid so the next packet goes out as a FULL_HEADER or COMPRESSED_NON_TCP packet (a FULL_HEADER is required if the generation doesn't match). heads up though: if the link round-trip time is large compared to the spacing between packets, there could be several packets from multiple contexts in flight across the link. that makes it more likely the sequence numbers have already moved on by the time the CONTEXT_STATE packet reaches the compressor ๐ the result could be that some contexts get invalidated for no reason, which wastes extra bandwidth ๐
the CONTEXT_STATE packet is shown in the diagrams below. the first byte is a type code, so the CONTEXT_STATE packet type can be shared by multiple compression schemes within the general compression framework specified in [3]. what comes after depends on the compression scheme. for the IP/UDP/RTP compression scheme specified here, the rest of the CONTEXT_STATE packet is a list of blocks, so the state for multiple contexts can be indicated, and it's preceded by a one-byte count of the number of blocks ๐
there are two type code values for the IP/UDP/RTP compression scheme. the value 1 means 8-bit session context IDs are being used:
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 1 = IP/UDP/RTP with 8-bit CID |
+---+---+---+---+---+---+---+---+
| context count |
+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+
| session context ID |
+---+---+---+---+---+---+---+---+
| I | 0 | 0 | 0 | sequence |
+---+---+---+---+---+---+---+---+
| 0 | 0 | generation |
+---+---+---+---+---+---+---+---+
...
+---+---+---+---+---+---+---+---+
| session context ID |
+---+---+---+---+---+---+---+---+
| I | 0 | 0 | 0 | sequence |
+---+---+---+---+---+---+---+---+
| 0 | 0 | generation |
+---+---+---+---+---+---+---+---+
the value 2 means 16-bit session context IDs are being used. the session context ID gets sent in network byte order (most significant byte first) โจ
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 2 = IP/UDP/RTP with 16-bit CID|
+---+---+---+---+---+---+---+---+
| context count |
+---+---+---+---+---+---+---+---+
+---+---+---+---+---+---+---+---+
| |
+ session context ID +
| |
+---+---+---+---+---+---+---+---+
| I | 0 | 0 | 0 | sequence |
+---+---+---+---+---+---+---+---+
| 0 | 0 | generation |
+---+---+---+---+---+---+---+---+
...
+---+---+---+---+---+---+---+---+
| |
+ session context ID +
| |
+---+---+---+---+---+---+---+---+
| I | 0 | 0 | 0 | sequence |
+---+---+---+---+---+---+---+---+
| 0 | 0 | generation |
+---+---+---+---+---+---+---+---+
the bit labeled "I" gets set to one for contexts that have been marked invalid and need a FULL_HEADER or COMPRESSED_NON_TCP packet to be transmitted ๐จ if the I bit is zero, the context state is advisory. the I bit is set to zero to indicate advisory context state that MAY be sent following a link error indication ๐ธ
since the CONTEXT_STATE packet itself can get lost, retransmitting one or more blocks is allowed. it's expected that retransmission will only be triggered by receiving another packet, but if the line is near idle, retransmission MAY be triggered by a relatively long timer (on the order of 1 second) โญ
if a CONTEXT_STATE block for a given context gets retransmitted, it may cross paths with the FULL_HEADER or COMPRESSED_NON_TCP packet meant to refresh that context. in that case, the compressor MAY choose to ignore the error indication ๐
when UDP checksums are being transmitted, the decompressor MAY attempt to use the "twice" algorithm described in section 10.1 of [3]. the idea here is that the delta gets applied more than once, on the assumption that the delta may have been the same on the missing packet(s) and the one that arrived afterwards. success is indicated by a checksum match. for the scheme defined here, the difference in the 4-bit sequence number tells you how many times the delta must be applied ๐งฎ heads up, though: there's a nontrivial risk of an incorrect positive indication. it may be advisable to request a FULL_HEADER or COMPRESSED_NON_TCP packet even if the "twice" algorithm succeeds ๐ฌ
some errors might not get detected, for example if 16 packets are lost in a row and the link level doesn't give any error indication. in that case, the decompressor will generate packets that aren't valid. if UDP checksums are being transmitted, the receiver will probably notice the invalid packets and discard them, but the receiver has no way to signal the decompressor ๐ฅบ that's why it is RECOMMENDED that the decompressor verify the UDP checksum periodically, perhaps once every 16 packets. if an error is detected, the decompressor would invalidate the context and signal the compressor with a CONTEXT_STATE packet ๐ซถ
3.4. Compression of RTCP Control Packets
okay bestie, so RTP has this convention where the data rides on an even port number and the matching RTCP packets sit on the next odd one up ๐ฆ. That means you could build separate compression schemes for RTP and RTCP! For RTCP, compression could hit not just the header but also the "data", aka the contents of the different packet types. The numbers in Sender Report (SR) and Receiver Report (RR) packets wouldn't compress well ๐ญ, but the text in Source Description (SDES) packets could get squished down to a bit mask showing which items were there but compressed out (for timing on the SDES NOTE item, and so the end system can measure the average RTCP packet size for the interval calculation) ๐.
However, this scheme does NOT compress RTCP headers or "data" at all, for a few reasons ๐ (though compression SHOULD still be applied to the IP and UDP headers). The RTP spec suggests scaling the RTCP interval so the aggregate RTCP bandwidth used by all participants in a session is no more than 5% of the session bandwidth, so there's not much to gain from RTCP compression ๐คทโโ๏ธ. Compressing out the SDES items would need a way bigger pile of shared state stored for each context ID. And to allow compression when SDES info for several sources gets sent through an RTP "mixer", you'd have to keep a separate RTCP session context for every SSRC identifier. In a session with more than 255 participants, that causes perfect thrashing of the context cache, even when only one participant is sending data ๐.
Even though RTCP isn't compressed, the fraction of total bandwidth taken up by RTCP packets on the compressed link stays no more than 5% in most cases ๐ธ, assuming the RTCP packets are sent as COMPRESSED_UDP packets. Since uncompressed RTCP traffic eats no more than 5% of the total session bandwidth, a typical RTCP packet length of 90 bytes means RTCP will be no more than 5% of the compressed bandwidth if the RTP data payload is at least 108 bytes. If the RTP data payload is smaller, the fraction goes up, but it's still under 7% for a payload size of 37 bytes. For big data payloads, the compressed RTCP fraction is actually smaller than the uncompressed one (for example, 4% at 1000 bytes) ๐โจ.
3.5. Compression of non-RTP UDP Packets
as we covered earlier, the COMPRESSED_UDP packet MAY be used to compress UDP packets that don't carry RTP ๐ง. Whatever data follows the UDP header is unlikely to have constant values in the bits that line up with the usually constant fields in the RTP header. The SSRC field in particular would probably keep changing. So you have to keep track of these non-RTP UDP packet streams, otherwise they'd use up all the context slots as the "SSRC field" changes (since that field is part of what identifies a particular RTP context) ๐ตโ๐ซ. Each of those streams can get its own context, but the encoder sets a flag in the context saying the changing SSRC field should be ignored, and COMPRESSED_UDP packets should always be sent instead of COMPRESSED_RTP packets. Main character energy for the UDP streams ๐.
4. Interaction With Segmentation โจ
okay so a segmentation scheme can team up with RTP header compression to let small, real-time packets cut in line in front of big, presumably non-real-time packets, so the delay goes down ๐โโ๏ธ๐จ. It's assumed the large packets bypass the compressor and decompressor, because interleaving would change the sequencing of packets at the decompressor and make it look like there were errors ๐. Header compression should be less important for large packets, since the overhead ratio is smaller anyway.
If some packets from an RTP session context get picked for segmentation (maybe based on size) and some don't, re-ordering is possible ๐. That lowers compression efficiency, because the large packets would look like lost packets in the sequence space. But it shouldn't cause bigger problems, since the RTP sequence numbers should still be reconstructed correctly, and that lets the application fix the ordering ๐ซถ.
Link errors that the segmentation scheme spots using its own sequencing info MAY be reported to the compressor with an advisory CONTEXT_STATE message, the same way link errors spotted by the link layer itself are ๐ฃ.
The context ID byte goes first in the COMPRESSED_RTP header, so this byte MAY be shared with the segmentation layer if that sharing is feasible and has been negotiated ๐. Since the compressor can assign context ID values however it likes, the value can be set to match the context identifier from the segmentation layer. Neat, right? ๐
5. Negotiating Compression
IP/UDP/RTP compression over a particular link depends on the link-layer protocol ๐. Negotiation is expected to be defined separately for PPP [4], for example. The following items MAY be negotiated:
- The size of the context ID.
- The maximum size of the stack of headers in the context.
- A context-specific table for decoding of delta values.
6. Acknowledgments
a bunch of people helped design this compression scheme and related problems, and they deserve all the flowers ๐ท๐ซถ. Scott Petrack kicked off the discussion of RTP header compression in the AVT working group at Los Angeles in March, 1996. Carsten Bormann developed an overall architecture for compression combined with traffic control across a low-speed link, and made several specific contributions to the scheme described here ๐. David Oran independently developed a note based on similar ideas, and suggested using the PPP Multilink protocol for segmentation ๐ง . Mikael Degermark gave advice on integrating this compression scheme with the IPv6 compression framework ๐.
7. References:
[1] Schulzrinne, H., Casner, S., Frederick, R. and V. Jacobson, "RTP:
A Transport Protocol for real-time applications", RFC 1889,
January 1996.
[2] Jacobson, V., "TCP/IP Compression for Low-Speed Serial Links",
RFC 1144, February 1990.
[3] Degermark, M., Nordgren, B. and S. Pink, "Header Compression for
IPv6", RFC 2507, February 1999.
[4] Simpson, W., "The Point-to-Point Protocol (PPP)", STD 51, RFC
1661, July 1994.
[5] Hoffman, D., Fernando, G., Goyal, V. and M. Civanlar, "RTP
Payload Format for MPEG1/MPEG2 Video", RFC 2250, January 1998.
8. Security Considerations
okay this section is SERIOUS but also kind of iconic ๐ฅ๐. Encryption wipes out the redundancy this compression scheme tries to exploit, so there's some temptation to skip encryption just to get things working over a low-bandwidth link ๐ฌ. But if encrypting the data and not the headers is good enough for you, RTP does specify an alternative encryption method where only the RTP payload gets encrypted and the headers stay in the clear. That way compression can still be applied ๐โจ.
A malfunctioning or malicious compressor could make the decompressor rebuild packets that don't match the originals but still have valid IP, UDP and RTP headers, and maybe even valid UDP check-sums ๐จ. Corruption like that may be caught with end-to-end authentication and integrity mechanisms, which compression won't affect. Constant portions of authentication headers will be compressed as described in [3] ๐งฉ.
Nothing authenticates the CONTEXT_STATE control packet sent by this protocol ๐ โโ๏ธ. An attacker with access to the link between the decompressor and compressor could inject false CONTEXT_STATE packets and reduce compression efficiency, which would probably cause congestion on the link. But honestly, an attacker with link access could mess with the traffic in lots of other ways too ๐คทโโ๏ธ.
There's a potential denial-of-service threat with compression techniques that put a non-uniform computational load on the receiver ๐ฑ. An attacker can inject pathological datagrams into the stream that are complex to decompress, overloading the receiver and slowing down processing of other streams. Good news, though: this compression doesn't show any significant non-uniformity ๐.
A security review of this protocol found no additional security considerations ๐.
9. Authors' Addresses
Stephen L. Casner
Cisco Systems, Inc.
170 West Tasman Drive
San Jose, CA 95134-1706
United States
EMail: casner@cisco.com
Van Jacobson
Cisco Systems, Inc.
170 West Tasman Drive
San Jose, CA 95134-1706
United States
EMail: van@cisco.com
10. Full Copyright Statement
the legal bits stay exactly as written, no vibes-edits here ๐๐ :
Copyright (C) The Internet Society (1999). All Rights Reserved.
This document and translations of it may be copied and furnished to
others, and derivative works that comment on or otherwise explain it
or assist in its implementation may be prepared, copied, published
and distributed, in whole or in part, without restriction of any
kind, provided that the above copyright notice and this paragraph are
included on all such copies and derivative works. However, this
document itself may not be modified in any way, such as by removing
the copyright notice or references to the Internet Society or other
Internet organizations, except as needed for the purpose of
developing Internet standards in which case the procedures for
copyrights defined in the Internet Standards process must be
followed, or as required to translate it into languages other than
English.
The limited permissions granted above are perpetual and will not be
revoked by the Internet Society or its successors or assigns.
This document and the information contained herein is provided on an
"AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING
TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION
HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF
MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
๐ฆ where this page comes from
- ietf: rfc2508 1999-02
- ๐ girlified by claude-haiku-5-5 on : a language model's rewrite of the original, under its terms