RFC 4506
XDR: External Data Representation Standard
XDR: External Data Representation Standard β¨
RFC 4506 π is an Internet Standards Track doc (STD 67) by M. Eisler, Ed. from Network Appliance, Inc., dated May 2006. It obsoletes RFC 1832, and there's no Updates entry listed. Let's go! π
Status of This Memo
This document specifies an Internet standards track protocol for the Internet community, and requests discussion and suggestions for improvements. π«Ά For the standardization state and status of this protocol, check the current edition of the "Internet Official Protocol Standards" (STD 1). Distribution of this memo is unlimited. π
Copyright Notice
Copyright (C) The Internet Society (2006). π₯
Abstract
Okay so this doc describes the External Data Representation Standard (XDR) protocol the way it's actually deployed and accepted out in the wild πΈ. It also obsoletes RFC 1832, so that one's officially retired π .
Table of Contents
1. Introduction ....................................................3
2. Changes from RFC 1832 ...........................................3
3. Basic Block Size ................................................3
4. XDR Data Types ..................................................4
4.1. Integer ....................................................4
4.2. Unsigned Integer ...........................................4
4.3. Enumeration ................................................5
4.4. Boolean ....................................................5
4.5. Hyper Integer and Unsigned Hyper Integer ...................5
4.6. Floating-Point .............................................6
4.7. Double-Precision Floating-Point ............................7
4.8. Quadruple-Precision Floating-Point .........................8
4.9. Fixed-Length Opaque Data ...................................9
4.10. Variable-Length Opaque Data ...............................9
4.11. String ...................................................10
4.12. Fixed-Length Array .......................................11
4.13. Variable-Length Array ....................................11
4.14. Structure ................................................12
4.15. Discriminated Union ......................................12
4.16. Void .....................................................13
4.17. Constant .................................................13
4.18. Typedef ..................................................13
4.19. Optional-Data ............................................14
4.20. Areas for Future Enhancement .............................16
5. Discussion .....................................................16
6. The XDR Language Specification .................................17
6.1. Notational Conventions ....................................17
6.2. Lexical Notes .............................................18
6.3. Syntax Information ........................................18
6.4. Syntax Notes ..............................................20
7. An Example of an XDR Data Description ..........................21
8. Security Considerations ........................................22
9. IANA Considerations ............................................23
10. Trademarks and Owners .........................................23
11. ANSI/IEEE Standard 754-1985 ...................................24
12. Normative References ..........................................25
13. Informative References ........................................25
14. Acknowledgements ..............................................26
1. Introduction π§
Okay bestie, XDR is a standard for describing and encoding data π. It's super useful for moving data between totally different computer architectures, and it's been used to talk between machines as diverse as the SUN WORKSTATION*, VAX*, IBM-PC*, and Cray* π₯οΈβ¨. XDR fits into the ISO presentation layer and is roughly analogous in purpose to X.409, ISO Abstract Syntax Notation. The major difference is that XDR uses implicit typing, while X.409 uses explicit typing π.
XDR uses a language to describe data formats. Lowkey important: the language can only be used to describe data. It is not a programming language π
. It lets you describe intricate data formats in a concise way, because the alternative (graphical representations, which are themselves an informal language) gets totally incomprehensible once things get complex π. The XDR language itself is similar to the C language [KERN], just as Courier [COUR] is similar to Mesa. Protocols like ONC RPC (Remote Procedure Call) and the NFS* (Network File System) use XDR to describe the format of their data π¦.
The XDR standard makes one big assumption: that bytes (or octets) are portable, where a byte is 8 bits of data π. A given hardware device should encode bytes onto the various media in such a way that other hardware devices can decode them without losing meaning β¨. For example, the Ethernet* standard suggests bytes be encoded in "little-endian" style [COHE], or least significant bit first π.
2. Changes from RFC 1832 π
Here's the tea: this document makes no technical changes to RFC 1832 π. It's published just to note IANA considerations, augment security considerations, and separate normative references from informative ones π.
3. Basic Block Size π§©
The representation of all items requires a multiple of four bytes (or 32 bits) of data π. The bytes are numbered 0 through n-1. They're read or written to some byte stream so that byte m always comes before byte m+1 π«Ά. If the n bytes needed to hold the data aren't a multiple of four, then the n bytes get followed by enough (0 to 3) residual zero bytes, r, to make the total byte count a multiple of 4 π.
We include the familiar graphic box notation for illustration and comparison π¦. In most illustrations, each box (delimited by a plus sign at the 4 corners and vertical bars and dashes) depicts a byte π .
Ellipses (...) between boxes show zero or more additional bytes where required β¨.
+--------+--------+...+--------+--------+...+--------+
| byte 0 | byte 1 |...|byte n-1| 0 |...| 0 | BLOCK
+--------+--------+...+--------+--------+...+--------+
|<-----------n bytes---------->|<------r bytes------>|
|<-----------n+r (where (n+r) mod 4 = 0)>----------->|
4. XDR Data Types πΈ
Each section that follows describes a data type defined in the XDR standard, shows how it's declared in the language, and includes a graphic illustration of its encoding π.
For each data type in the language we show a general paradigm declaration π. Note that angle brackets (< and >) denote variable-length sequences of data, and square brackets ([ and ]) denote fixed-length sequences of data π. n, m, and r denote integers π’. For the full language specification and more formal definitions of terms like "identifier" and "declaration", refer to Section 6, "The XDR Language Specification" π.
For some data types, more specific examples are included. A more extensive example of a data description is in Section 7, "An Example of an XDR Data Description" π§.
4.1. Integer π
An XDR signed integer is a 32-bit datum that encodes an integer in the range [-2147483648,2147483647]. The integer is represented in two's complement notation. The most and least significant bytes are 0 and 3, respectively. Integers are declared like this π«Ά:
int identifier;
(MSB) (LSB)
+-------+-------+-------+-------+
|byte 0 |byte 1 |byte 2 |byte 3 | INTEGER
+-------+-------+-------+-------+
<------------32 bits------------>
4.2. Unsigned Integer β¨
An XDR unsigned integer is a 32-bit datum that encodes a non-negative integer in the range [0,4294967295] π. It's represented by an unsigned binary number whose most and least significant bytes are 0 and 3, respectively. An unsigned integer is declared like this π
:
unsigned int identifier;
(MSB) (LSB)
+-------+-------+-------+-------+
|byte 0 |byte 1 |byte 2 |byte 3 | UNSIGNED INTEGER
+-------+-------+-------+-------+
<------------32 bits------------>
4.3. Enumeration π¦
Enumerations have the same representation as signed integers π. They're handy for describing subsets of the integers π. Enumerated data is declared like this:
enum { name-identifier = constant, ... } identifier;
For example, the three colors red, yellow, and blue could be described by an enumerated type π¨:
enum { RED = 2, YELLOW = 3, BLUE = 5 } colors;
It is an error to encode as an enum any integer other than those that have been given assignments in the enum declaration π ββοΈ.
4.4. Boolean π
Booleans are important enough, and show up often enough, to get their own explicit type in the standard π«Ά. They're declared like this:
bool identifier;
This is equivalent to:
enum { FALSE = 0, TRUE = 1 } identifier;
4.5. Hyper Integer and Unsigned Hyper Integer π§
The standard also defines 64-bit (8-byte) numbers called hyper integers and unsigned hyper integers π. Their representations are the obvious extensions of integer and unsigned integer, defined above β¨. They're represented in two's complement notation. The most and least significant bytes are 0 and 7, respectively. Their declarations:
hyper identifier; unsigned hyper identifier;
(MSB) (LSB)
+-------+-------+-------+-------+-------+-------+-------+-------+
|byte 0 |byte 1 |byte 2 |byte 3 |byte 4 |byte 5 |byte 6 |byte 7 |
+-------+-------+-------+-------+-------+-------+-------+-------+
<----------------------------64 bits---------------------------->
HYPER INTEGER
UNSIGNED HYPER INTEGER
to be continued in part 2 π
4.6. Floating-Point π¦
okay so the standard gives us the float type, and it's 32 bits or 4 bytes π The encoding is the IEEE standard for normalized single-precision floating-point numbers [IEEE]. Three fields describe the number:
S: the sign. 0 means positive and 1 means negative. One bit πΈE: the exponent, base 2. This one gets 8 bits, and it's biased by 127 β¨F: the fractional part of the mantissa, base 2. This gets 23 bits π
So the number works out to:
(-1)**S * 2**(E-Bias) * 1.F
and you declare it like this:
float identifier;
Here's the layout, bestie π
+-------+-------+-------+-------+
|byte 0 |byte 1 |byte 2 |byte 3 | SINGLE-PRECISION
S| E | F | FLOATING-POINT NUMBER
+-------+-------+-------+-------+
1|<- 8 ->|<-------23 bits------>|
<------------32 bits------------>
Just like the most and least significant bytes are 0 and 3, the most and least significant bits of a single-precision float are 0 and 31. The beginning bit offsets (and most significant bit offsets) for S, E, and F are 0, 1, and 9. Just know these numbers are the mathematical positions of the bits, and they are not their actual physical locations, which change from medium to medium π
For signed zero, signed infinity (overflow), and denormalized numbers (underflow), go check the IEEE specs [IEEE]. According to IEEE, "NaN" (not a number) is system dependent, and within XDR it should not be read as anything other than "NaN" π
4.7. Double-Precision Floating-Point π
the double type is 64 bits or 8 bytes, and it's encoded with the IEEE standard for normalized double-precision floating-point numbers [IEEE]. It has the same three fields, just bigger:
S: the sign. 0 is positive, 1 is negative. One bit πE: the exponent, base 2. 11 bits go here, biased by 1023 π§F: the fractional part of the mantissa, base 2. 52 bits go here π
The math is the same:
(-1)**S * 2**(E-Bias) * 1.F
Declare it with:
double identifier;
And the layout is:
+------+------+------+------+------+------+------+------+
|byte 0|byte 1|byte 2|byte 3|byte 4|byte 5|byte 6|byte 7|
S| E | F |
+------+------+------+------+------+------+------+------+
1|<--11-->|<-----------------52 bits------------------->|
<-----------------------64 bits------------------------->
DOUBLE-PRECISION FLOATING-POINT
Same deal with the bits: the most and least significant bits are 0 and 63, and the beginning bit offsets for S, E, and F are 0, 1, and 12. Again, those are mathematical positions, not where the bits physically live π¦
The rules for signed zero, signed infinity (overflow), and denormalized numbers (underflow) are in the IEEE specs [IEEE]. And "NaN" is system dependent here too, so within XDR it should not be treated as anything other than "NaN" β¨
4.8. Quadruple-Precision Floating-Point π₯
the quadruple type is 128 bits or 16 bytes. The encoding is designed to be a simple analog of the single- and double-precision encodings, using one form of IEEE double extended precision. Its fields are:
S: the sign. 0 is positive and 1 is negative. One bit πE: the exponent, base 2. 15 bits, biased by 16383 πF: the fractional part of the mantissa, base 2. 112 bits πΈ
The formula stays the same:
(-1)**S * 2**(E-Bias) * 1.F
Declaration:
quadruple identifier;
Layout:
+------+------+------+------+------+------+-...--+------+
|byte 0|byte 1|byte 2|byte 3|byte 4|byte 5| ... |byte15|
S| E | F |
+------+------+------+------+------+------+-...--+------+
1|<----15---->|<-------------112 bits------------------>|
<-----------------------128 bits------------------------>
QUADRUPLE-PRECISION FLOATING-POINT
The most and least significant bits are 0 and 127. The beginning bit offsets for S, E, and F are 0, 1, and 16. These are mathematical positions, not physical locations, which can vary from medium to medium π§
For signed zero, signed infinity (overflow), and denormalized numbers, the encodings are analogs of the single- and double-precision ones [SPAR], [HPRE]. And the "NaN" encoding for quadruple-precision numbers is system dependent too, so within XDR it should not be interpreted as anything other than "NaN" π
4.9. Fixed-Length Opaque Data π«Ά
sometimes fixed-length data that nobody interprets needs to get passed between machines. That's called "opaque" data, and you declare it like this:
opaque identifier[n];
Here n is the (static) number of bytes needed to hold the opaque data. If n isn't a multiple of four, the n bytes get followed by enough residual zero bytes, r (anywhere from 0 to 3), so the total byte count is a multiple of four π
0 1 ...
+--------+--------+...+--------+--------+...+--------+
| byte 0 | byte 1 |...|byte n-1| 0 |...| 0 |
+--------+--------+...+--------+--------+...+--------+
|<-----------n bytes---------->|<------r bytes------>|
|<-----------n+r (where (n+r) mod 4 = 0)------------>|
FIXED-LENGTH OPAQUE
4.10. Variable-Length Opaque Data π
this one's the counted version. It's a sequence of n arbitrary bytes, numbered 0 through n-1. The number n gets encoded as an unsigned integer (like we covered above), and then the n bytes follow it π
Byte m of the sequence always comes before byte m+1, and byte 0 always comes right after the length (count). Same padding rule as before: if n isn't a multiple of four, you add 0 to 3 residual zero bytes, r, so the total is a multiple of four β¨
You can declare it in either of these ways:
opaque identifier<m>;
or
opaque identifier<>;
The constant m is an upper bound on how many bytes the sequence can hold. If you skip m like in the second one, it's assumed to be (2**32) - 1, the maximum length π¦
The constant m usually comes from a protocol spec. For example, a filing protocol might say the max data transfer size is 8192 bytes, so you'd write:
opaque filedata<8192>;
0 1 2 3 4 5 ...
+-----+-----+-----+-----+-----+-----+...+-----+-----+...+-----+
| length n |byte0|byte1|...| n-1 | 0 |...| 0 |
+-----+-----+-----+-----+-----+-----+...+-----+-----+...+-----+
|<-------4 bytes------->|<------n bytes------>|<---r bytes--->|
|<----n+r (where (n+r) mod 4 = 0)---->|
VARIABLE-LENGTH OPAQUE
And if you encode a length that's bigger than the max in the spec, that's an error π
4.11. String π
a string is basically the same deal. It's a string of n ASCII bytes, numbered 0 through n-1. The number n gets encoded as an unsigned integer (described above), and then the n bytes follow it π
Byte m of the string always comes before byte m+1, and byte 0 always follows the string's length. If n isn't a multiple of four, you add 0 to 3 residual zero bytes, r, to make the total byte count a multiple of four π
Counted strings get declared like this:
string object<m>;
or
string object<>;
Same rules as before: m is an upper bound on the bytes the string can contain. Skip it, like in the second form, and it's assumed to be (2**32) - 1, the maximum length. The constant m would normally come from a protocol spec. Say a filing protocol says file names can be no longer than 255 bytes. You'd write:
string filename<255>;
0 1 2 3 4 5 ...
+-----+-----+-----+-----+-----+-----+...+-----+-----+...+-----+
| length n |byte0|byte1|...| n-1 | 0 |...| 0 |
+-----+-----+-----+-----+-----+-----+...+-----+-----+...+-----+
|<-------4 bytes------->|<------n bytes------>|<---r bytes--->|
|<----n+r (where (n+r) mod 4 = 0)---->|
STRING
And encoding a length above the max in the spec is an error, same as before π
4.12. Fixed-Length Array π§
fixed-length arrays of homogeneous elements are declared like this:
type-name identifier[n];
The elements, numbered 0 through n-1, get encoded one at a time in their natural order, 0 through n-1. Each element's size is a multiple of four bytes β¨
Here's the twist: all the elements are the same type, but they don't all have to be the same size. Like, in a fixed-length array of strings, every element is type "string", but each one can have a different length πΈ
+---+---+---+---+---+---+---+---+...+---+---+---+---+
| element 0 | element 1 |...| element n-1 |
+---+---+---+---+---+---+---+---+...+---+---+---+---+
|<--------------------n elements------------------->|
FIXED-LENGTH ARRAY
4.13. Variable-Length Array β¨
Okay bestie, counted arrays are how you send a list of same-type things when you don't know how many there'll be π¦. The encoding starts with the element count n (an unsigned integer), then each element in order, starting at element 0 and going all the way to element n-1. The declaration comes in two flavours:
type-name identifier<m>;
or
type-name identifier<>;
The constant m is the max element count you'll accept for the array. If you leave it out like in the second line, it's assumed to be (2**32) - 1, which is a lot of elements π
0 1 2 3
+--+--+--+--+--+--+--+--+--+--+--+--+...+--+--+--+--+
| n | element 0 | element 1 |...|element n-1|
+--+--+--+--+--+--+--+--+--+--+--+--+...+--+--+--+--+
|<-4 bytes->|<--------------n elements------------->|
COUNTED ARRAY
It's an error to encode an n that goes over the maximum from the spec, so don't even try π
ββοΈ
4.14. Structure π
Structures are declared like this:
struct {
component-declaration-A;
component-declaration-B;
...
} identifier;
The components get encoded in the same order they're declared in the structure. Each component's size is a multiple of four bytes, but the components can be different sizes from each other, which is giving flexibility πΈ
+-------------+-------------+...
| component A | component B |... STRUCTURE
+-------------+-------------+...
4.15. Discriminated Union π¦
A discriminated union is a type made of a discriminant followed by a type picked from a set of prearranged types, depending on what the discriminant's value is. The discriminant has to be either "int", "unsigned int", or an enumerated type like "bool". The possible types are called "arms" of the union, and each arm is preceded by the discriminant value that tells you how to encode it. Here's the declaration:
union switch (discriminant-declaration) {
case discriminant-value-A:
arm-declaration-A;
case discriminant-value-B:
arm-declaration-B;
...
default: default-declaration;
} identifier;
Every case keyword gets followed by a legal discriminant value. The default arm is optional π«Ά If you skip it, then a valid encoding of the union can't have discriminant values that weren't specified. Also, the size of the arm you end up with is always a multiple of four bytes.
The union gets encoded as the discriminant, then the encoding of the arm it implies:
0 1 2 3
+---+---+---+---+---+---+---+---+
| discriminant | implied arm | DISCRIMINATED UNION
+---+---+---+---+---+---+---+---+
|<---4 bytes--->|
4.16. Void π
An XDR void is a 0-byte quantity, so it's literally nothing but iconic π Voids are useful for operations that take no data as input, or no data as output. They also come in handy inside unions, where some arms have data and others don't. The declaration is just:
void;
And here's what a void looks like:
++
|| VOID
++
--><-- 0 bytes
4.17. Constant π
The declaration for a constant looks like this:
const name-identifier = n;
const gives a symbolic name to a constant, but it doesn't declare any data, so it's not a variable π§ You can use the symbolic constant anywhere a regular constant would work. For example, this one defines a symbolic constant called DOZEN, which equals 12:
const DOZEN = 12;
4.18. Typedef π
typedef doesn't declare any data either. It just defines new identifiers you can use when declaring data. The syntax is:
typedef declaration;
The new type name is actually the variable name sitting in the declaration part of the typedef. For example, this defines a new type called eggbox, built from an existing type called egg:
typedef egg eggbox[DOZEN];
A variable declared with the new type name has the same type that the new name would have if you treated it as a variable. So these two declarations are equivalent when it comes to declaring the variable fresheggs:
eggbox fresheggs;
egg fresheggs[DOZEN];
When a typedef involves a struct, enum, or union definition, there's another syntax that's preferred for defining the same type. Generally, a typedef like this:
typedef <<struct, union, or enum definition>> identifier;
can be rewritten by dropping the typedef part and putting the identifier right after the struct, union, or enum keyword, instead of at the end. For example, here are the two ways of defining the bool type:
typedef enum { /* using typedef */
FALSE = 0,
TRUE = 1
} bool;
enum bool { /* preferred alternative */
FALSE = 0,
TRUE = 1
};
The preferred version is nicer because you don't have to wait until the very end of a declaration to find out what the new type is called β¨
4.19. Optional-Data πΈ
Optional-data is a kind of union that shows up so often that it gets its own special syntax. It's declared like this:
type-name *identifier;
That's equivalent to this union:
union switch (bool opted) {
case TRUE:
type-name element;
case FALSE:
void;
} identifier;
It's also equivalent to this variable-length array declaration, since the boolean opted can be read as the array's length:
type-name identifier<1>;
On its own, optional-data isn't super exciting, but it's really useful for recursive data structures like linked lists and trees π³ For example, this defines a type stringlist that encodes lists of zero or more arbitrary-length strings:
struct stringentry {
string item<>;
stringentry *next;
};
typedef stringentry *stringlist;
It could have been declared the same way as this union:
union stringlist switch (bool opted) {
case TRUE:
struct {
string item<>;
stringlist next;
} element;
case FALSE:
void;
};
or as a variable-length array:
struct stringentry {
string item<>;
stringentry next<1>;
};
typedef stringentry stringlist<1>;
Both of those declarations hide what the stringlist type is really for, so the optional-data declaration is the one that's preferred over both of them π The optional-data type also lines up nicely with how recursive data structures get represented in high-level languages like Pascal or C using pointers. Honestly, the syntax is the same as C's syntax for pointers, which is iconic π
4.20. Areas for Future Enhancement π
XDR is missing representations for bit fields and bitmaps, since the standard is based on bytes. Packed (or binary-coded) decimals aren't in there either π€·ββοΈ
The point of XDR was never to describe every kind of data people have ever sent or will ever want to send between machines. It only covers the most commonly used data types of high-level languages like Pascal or C, so that apps written in those languages can talk to each other easily over some medium π
You could imagine extending XDR so it could describe almost any existing protocol, like TCP. The minimum you'd need for that is support for different block sizes and byte-orders. The XDR we're talking about here could then be thought of as the 4-byte big-endian member of a bigger XDR family π¦
5. Discussion π¬
(1) Why use a language for describing data? What's wrong with diagrams?
There are lots of advantages to using a data-description language like XDR instead of diagrams π Languages are more formal than diagrams, so they lead to less ambiguous descriptions of data. They're also easier to understand, and they let you think about other issues instead of getting lost in low-level bit encoding details π§ There's also a close analogy between XDR's types and a high-level language like C or Pascal, which makes implementing XDR encoding and decoding modules way easier. And finally, the language specification is itself an ASCII string, so it can be passed from machine to machine to do on-the-fly data interpretation β¨
(2) Why is there only one byte-order for an XDR unit?
Supporting two byte-orderings would require a higher-level protocol to figure out which byte-order the data is encoded in. Since XDR isn't a protocol, that can't happen π The upside though? Data in XDR format can be written to a magnetic tape, for example, and any machine will be able to read it, since no higher-level protocol is needed to figure out the byte-order π§²
(3) Why is the XDR byte-order big-endian instead of little-endian? Isn't this unfair to little-endian machines such as the VAX(r), which has to convert from one form to the other?
Yes, it's unfair, but having only one byte-order means you have to be unfair to somebody π Lots of architectures, like the Motorola 68000* and IBM 370*, support the big-endian byte-order.
(4) Why is the XDR unit four bytes wide?
It's a tradeoff π₯Ή Picking a small size, like two, keeps the encoded data small, but causes alignment problems for machines that aren't aligned on those boundaries. A large size, like eight, means the data will be aligned on virtually every machine, but makes the encoded data grow too big. So we went with four as a compromise. Four is big enough to support most architectures efficiently, except for rare machines like the eight-byte-aligned Cray*. And four is small enough to keep the encoded data at a reasonable size π
(5) Why must variable-length data be padded with zeros?
It's important that the same data encodes into the same thing on all machines, so encoded data can be meaningfully compared or checksummed π Forcing the padded bytes to be zero is what makes that happen π«Ά
(6) Why is there no explicit data-typing?
Data-typing costs a lot for the small advantages it might give π€ One cost is the expansion of data from the inserted type fields. Another is the extra work of interpreting those type fields and acting on them. And most protocols already know what type they expect, so data-typing only supplies redundant information. That said, you can still get the benefits of data-typing with XDR π One way is to encode two things: first a string that's the XDR data description of the encoded data, and then the encoded data itself. Another way is to assign a value to all the types in XDR, then define a universal type that takes this value as its discriminant and, for each value, describes the corresponding data type π§
6. The XDR Language Specification β¨
6.1. Notational Conventions
Okay so XDR's language gets described with an extended Backus-Naur Form notation π§ β¨ Here's the quick rundown:
- The characters
|,(,),[,],", and*are special. π - Terminal symbols are strings of any characters surrounded by double quotes. π
- Non-terminal symbols are strings of non-special characters. πΈ
- Alternative items are separated by a vertical bar (
|). π¦ - Optional items are enclosed in brackets. π
- Items are grouped together by enclosing them in parentheses. π§
- A
*following an item means 0 or more occurrences of that item. π
Here's an example pattern to make it click:
"a " "very" (", " "very")* [" cold " "and "] " rainy "
("day" | "night")
An infinite number of strings match this pattern, and a few of them are:
"a very rainy day"
"a very, very rainy day"
"a very cold and rainy day"
"a very, very, very cold and rainy night"
6.2. Lexical Notes
Lexical notes, bestie, let's gooo π₯
- Comments begin with
/*and terminate with*/. π - White space serves to separate items and is otherwise ignored. π«Ά
- An identifier is a letter followed by an optional sequence of letters, digits, or underbar (
_). The case of identifiers is not ignored, so capital letters matter! π - A decimal constant expresses a number in base 10 and is a sequence of one or more decimal digits, where the first digit is not a zero, and is optionally preceded by a minus-sign (
-). β¨ - A hexadecimal constant expresses a number in base 16, and must be preceded by
0x, followed by one or hexadecimal digits ('A', 'B', 'C', 'D', E', 'F', 'a', 'b', 'c', 'd', 'e', 'f', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'). π - An octal constant expresses a number in base 8, always leads with digit 0, and is a sequence of one or more octal digits (
'0', '1', '2', '3', '4', '5', '6', '7'). π
6.3. Syntax Information
Here's the whole grammar in one place, and honestly it's giving very organised π
declaration:
type-specifier identifier
| type-specifier identifier "[" value "]"
| type-specifier identifier "<" [ value ] ">"
| "opaque" identifier "[" value "]"
| "opaque" identifier "<" [ value ] ">"
| "string" identifier "<" [ value ] ">"
| type-specifier "*" identifier
| "void"
value:
constant
| identifier
constant:
decimal-constant | hexadecimal-constant | octal-constant
type-specifier:
[ "unsigned" ] "int"
| [ "unsigned" ] "hyper"
| "float"
| "double"
| "quadruple"
| "bool"
| enum-type-spec
| struct-type-spec
| union-type-spec
| identifier
enum-type-spec:
"enum" enum-body
enum-body:
"{"
( identifier "=" value )
( "," identifier "=" value )*
"}"
struct-type-spec:
"struct" struct-body
struct-body:
"{"
( declaration ";" )
( declaration ";" )*
"}"
union-type-spec:
"union" union-body
union-body:
"switch" "(" declaration ")" "{"
case-spec
case-spec *
[ "default" ":" declaration ";" ]
"}"
case-spec:
( "case" value ":")
( "case" value ":") *
declaration ";"
constant-def:
"const" identifier "=" constant ";"
type-def:
"typedef" declaration ";"
| "enum" identifier enum-body ";"
| "struct" identifier struct-body ";"
| "union" identifier union-body ";"
definition:
type-def
| constant-def
specification:
definition *
6.4. Syntax Notes
Now the rules that the grammar can't express on its own. These are the fine print, and they matter π
- The following are keywords and cannot be used as identifiers:
"bool", "case", "const", "default", "double", "quadruple", "enum", "float", "hyper", "int", "opaque", "string", "struct", "switch", "typedef", "union", "unsigned", and "void". π« - Only unsigned constants may be used as size specifications for arrays. If an identifier is used, it must have been declared previously as an unsigned constant in a
"const"definition. π - Constant and type identifiers within the scope of a specification are in the same name space and must be declared uniquely within this scope. π
- Similarly, variable names must be unique within the scope of struct and union declarations. Nested struct and union declarations create new scopes. π§
- The discriminant of a union must be of a type that evaluates to an integer. That is,
"int","unsigned int","bool", an enumerated type, or any typedefed type that evaluates to one of these is legal. Also, the case values must be one of the legal values of the discriminant. Finally, a case value may not be specified more than once within the scope of a union declaration. π¦
7. An Example of an XDR Data Description
Time for the fun part π Here's a short XDR description of a thing called a "file," the kind of thing you'd use to send files from one machine to another.
const MAXUSERNAME = 32; /* max length of a user name */
const MAXFILELEN = 65535; /* max length of a file */
const MAXNAMELEN = 255; /* max length of a file name */
/*
* Types of files:
*/
enum filekind {
TEXT = 0, /* ascii data */
DATA = 1, /* raw data */
EXEC = 2 /* executable */
};
/*
* File information, per kind of file:
*/
union filetype switch (filekind kind) {
case TEXT:
void; /* no extra information */
case DATA:
string creator<MAXNAMELEN>; /* data creator */
case EXEC:
string interpretor<MAXNAMELEN>; /* program interpretor */
};
/*
* A complete file:
*/
struct file {
string filename<MAXNAMELEN>; /* name of file */
filetype type; /* info about file */
string owner<MAXUSERNAME>; /* owner of file */
opaque data<MAXFILELEN>; /* file data */
};
Okay so picture a user named "john" who wants to store his lisp program "sillyprog," which contains just the data "(quit)". π§ Here's how his file gets encoded, byte by byte:
OFFSET HEX BYTES ASCII COMMENTS
------ --------- ----- --------
0 00 00 00 09 .... -- length of filename = 9
4 73 69 6c 6c sill -- filename characters
8 79 70 72 6f ypro -- ... and more characters ...
12 67 00 00 00 g... -- ... and 3 zero-bytes of fill
16 00 00 00 02 .... -- filekind is EXEC = 2
20 00 00 00 04 .... -- length of interpretor = 4
24 6c 69 73 70 lisp -- interpretor characters
28 00 00 00 04 .... -- length of owner = 4
32 6a 6f 68 6e john -- owner characters
36 00 00 00 06 .... -- length of file data = 6
40 28 71 75 69 (qui -- file data bytes ...
44 74 29 00 00 t).. -- ... and 2 zero-bytes of fill
8. Security Considerations
Serious talk time, bestie π«Ά XDR is a data description language, not a protocol. So it doesn't carry its own particular security considerations by itself. Protocols that carry XDR-formatted data, like NFSv4, are responsible for providing whatever security services are needed to protect the data they transport π
Still, encoding and decoding have to be done properly to avoid attacks π Here are the known and avoidable risks:
- Buffer overflow attacks. Where feasible, protocols should be defined with explicit limits (via the
"<" [ value ] ">"notation instead of"<" ">") on elements with variable-length data types. Regardless of whether an explicit limit is feasible for a given protocol, decoders need to ensure the incoming size does not exceed the length of any provisioned receiver buffers. π - Nul octets embedded in an encoded value of type string. If the decoder's native string format uses nul-terminated strings, then the apparent size of the decoded object will be less than the amount of memory allocated for the string. Some memory deallocation interfaces take a size argument. The caller of the deallocation interface would likely determine the size of the string by counting to the location of the nul octet and adding one. This discrepancy can cause memory leakage (because less memory is actually returned to the free pool than allocated), leading to system failure and a denial of service attack. π
- Decoding of characters in strings that are legal ASCII characters but nonetheless are illegal for the intended application. For example, some operating systems treat the
/character as a component separator in path names. For a protocol that encodes a string in the argument to a file creation operation, the decoder needs to ensure that/is not inside the component name. Otherwise, a file with an illegal/in its name will be created, making it difficult to remove, and is therefore a denial of service attack. π₯ - Denial of service caused by recursive decoder or encoder subroutines. A recursive decoder or encoder might process data that has a structured type with a member of type optional data that directly or indirectly refers to the structured type (i.e., a linked list). For example:
struct m {
int x;
struct m *next;
};
An encoder or decoder subroutine might be written to recursively call itself each time another element of type "struct m" is found. An attacker could construct a long linked list of "struct m" elements in the request or response, which then causes a stack overflow on the decoder or encoder. Decoders and encoders should be written non-recursively or impose a limit on list length. π
9. IANA Considerations
It's possible, if not likely, that new data types will be added to XDR in the future. β¨ New types get added through a standards track RFC, not by registering them with IANA. Standards track RFCs that update or replace this document should be documented as such in the RFC Editor's database of RFCs.
10. Trademarks and Owners
Last but not least, the trademark credits π
SUN WORKSTATION Sun Microsystems, Inc.
VAX Hewlett-Packard Company
IBM-PC International Business Machines Corporation
Cray Cray Inc.
NFS Sun Microsystems, Inc.
Ethernet Xerox Corporation.
Motorola 68000 Motorola, Inc.
IBM 370 International Business Machines Corporation
11. ANSI/IEEE Standard 754-1985 β¨
okay so this section is where the floating point stuff lives π§ the definitions of NaNs, signed zero, infinity, and denormalized numbers are copied over from [IEEE] just so you have them handy. bestie, the quad-precision ones are basically the same vibe as single and double, just bigger π
we're using 'S' for the sign bit, 'E' for the exponent, and 'F' for the fractional part. the 'u' means an undefined bit, so it can be a 0 or a 1 and we don't care π€·ββοΈ
For single-precision floating point numbers:
Type S (1 bit) E (8 bits) F (23 bits)
---- --------- ---------- -----------
signalling NaN u 255 (max) .0uuuuu---u
(with at least
one 1 bit)
quiet NaN u 255 (max) .1uuuuu---u
negative infinity 1 255 (max) .000000---0
positive infinity 0 255 (max) .000000---0
negative zero 1 0 .000000---0
positive zero 0 0 .000000---0
For double-precision floating point numbers:
Type S (1 bit) E (11 bits) F (52 bits)
---- --------- ----------- -----------
signalling NaN u 2047 (max) .0uuuuu---u
(with at least
one 1 bit)
quiet NaN u 2047 (max) .1uuuuu---u
negative infinity 1 2047 (max) .000000---0
positive infinity 0 2047 (max) .000000---0
negative zero 1 0 .000000---0
positive zero 0 0 .000000---0
For quadruple-precision floating point numbers:
Type S (1 bit) E (15 bits) F (112 bits)
---- --------- ----------- ------------
signalling NaN u 32767 (max) .0uuuuu---u
(with at least
one 1 bit)
quiet NaN u 32767 (max) .1uuuuu---u
negative infinity 1 32767 (max) .000000---0
positive infinity 0 32767 (max) .000000---0
negative zero 1 0 .000000---0
positive zero 0 0 .000000---0
Subnormal numbers are represented as follows:
Precision Exponent Value
--------- -------- -----
Single 0 (-1)**S * 2**(-126) * 0.F
Double 0 (-1)**S * 2**(-1022) * 0.F
Quadruple 0 (-1)**S * 2**(-16382) * 0.F
12. Normative References π
okay the normative stuff is short and sweet π this is the one reference you actually need to follow:
[IEEE] "IEEE Standard for Binary Floating-Point Arithmetic", ANSI/IEEE Standard 754-1985, Institute of Electrical and Electronics Engineers, August 1985.
13. Informative References πΈ
these ones are the extra reading, nice to know but not required π€
[KERN] Brian W. Kernighan & Dennis M. Ritchie, "The C Programming Language", Bell Laboratories, Murray Hill, New Jersey, 1978.
[COHE] Danny Cohen, "On Holy Wars and a Plea for Peace", IEEE Computer, October 1981.
[COUR] "Courier: The Remote Procedure Call Protocol", XEROX Corporation, XSIS 038112, December 1981.
[SPAR] "The SPARC Architecture Manual: Version 8", Prentice Hall, ISBN 0-13-825001-4.
[HPRE] "HP Precision Architecture Handbook", June 1987, 5954-9906.
14. Acknowledgements π«Ά
the people who made this possible, we love them π
Bob Lyon was Sun's visible force behind ONC RPC in the 1980s. Sun Microsystems, Inc., is listed as the author of RFC 1014. Raj Srinivasan and the rest of the old ONC RPC working group edited RFC 1014 into RFC 1832, from which this document is derived. Mike Eisler and Bill Janssen submitted the implementation reports for this standard. Kevin Coffman, Benny Halevy, and Jon Peterson reviewed this document and gave feedback. Peter Astrand and Bryan Olson pointed out several errors in RFC 1832 which are corrected in this document.
Editor's Address π¬
Mike Eisler 5765 Chase Point Circle Colorado Springs, CO 80919 USA
Phone: 719-599-9026 EMail: email2mre-rfc4506@yahoo.com
Please address comments to: nfsv4@ietf.org
Full Copyright Statement βοΈ
Copyright (C) The Internet Society (2006).
ngl the legal part is kind of a mood, but here's the deal: this document is subject to the rights, licenses and restrictions in BCP 78, and except as set forth there, the authors keep all their rights.
The document and the information in it are provided on an "AS IS" basis. THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY AND THE INTERNET ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
Intellectual Property π
The IETF doesn't take a side on whether any Intellectual Property Rights or other rights that might be claimed to apply to the technology in this document are valid or how far they reach. It also doesn't say whether a license for those rights is available, and it hasn't done any independent digging to find out what rights exist. You can find the procedures for rights in RFC documents in BCP 78 and BCP 79.
Copies of IPR disclosures made to the IETF Secretariat, along with any assurances of licenses that might be offered, or the results of an attempt to get a general license or permission to use those proprietary rights for implementers or users of this specification, can be found in the IETF on-line IPR repository at http://www.ietf.org/ipr.
The IETF invites anyone interested to tell it about any copyrights, patents, patent applications, or other proprietary rights that may cover technology needed to implement this standard. Send that information to the IETF at ietf-ipr@ietf.org.
Acknowledgement π
Funding for the RFC Editor function is provided by the IETF Administrative Support Activity (IASA).
π¦ and that's a wrap on RFC 4506, bestie. you made it through the whole thing π
π¦ where this page comes from
- ietf: rfc4506 2006-05
- π girlified by claude-haiku-5-5 on : a language model's rewrite of the original, under its terms