This repo houses a collection of utilities for XDR / RPC programs.
LANL software release number: O4912. See the LICENSE file for copyright and license.
xdr_codegen/-- this directory houses the library used to compile an XDR language specification into a Rust library that implements the [de]serialization of the defined data types.test_xdr/-- this directory holds tests for thexdr_codegenlibrary.rpc_protocol/-- this directory holds a library that implements the RPC protocol.rpcbind/-- binaries that implement the rpcbind protocol, both client and server side. These are effectively (currently incomplete) clones of the standardrpcbindandrpcinfobinaries.nfs3/-- programs and libraries related to the NFS v3 protocol.
See nfs3/README.md for information on the NFS v3 programs.
The xdr_codegen library works by taking in a specification in the XDR language (defined in RFC 4506)
and the RPC language extension (defined in RFC 5531) and outputs Rust code that implements
serialization and deserialization routines for the data types defined in the XDR spec.
For example, given the following XDR code:
struct File {
unsigned int size;
string name<>;
};xdr_codegen will generate a Rust module that defines the following struct and methods:
#[derive(Debug, PartialEq, Clone)]
pub struct File {
pub size: u32,
pub name: std::ffi::OsString,
}
impl File {
pub fn serialize_alloc(&self) -> Vec<u8>;
pub fn deserialize(&mut self, input: &mut &[u8]) -> Result<(), xdr_lib::DeserializeError>;
}You can run xdr_codegen via the command line:
$ echo "struct foo { int bar; };" | cargo run --bin xdr_codegen
#[allow(non_camel_case_types, non_snake_case)]
pub mod XdrInterface {
#[derive(Debug, PartialEq, Clone)]
pub struct foo {
pub bar: i32,
}
...
}or in build.rs:
fn main() {
xdr_codegen::Compiler::new()
.file("protocol_spec.x")
.run()
.expect("Generating code failed");
}The primitive types translate from XDR to Rust in the predictable way, e.g., int => i32
unsigned hyper => u64, etc.
XDR Arrays are encoded as Rust arrays (for fixed-length arrays) or Vectors (for variable length
arrays, both limited and unlimited). Opaque arrays are encoded as u8s.
XDR Strings are represented as ffi::OsStrings.
| XDR | Rust |
|---|---|
struct Arrays {
/* Strings: */
string lim<10>;
string unlim<>;
/* Byte arrays: */
opaque fixed[4];
opaque byte_lim<5>;
opaque byte_unlim<>;
}; |
pub struct Arrays {
pub lim: std::ffi::OsString /* max length: 10 */,
pub unlim: std::ffi::OsString,
pub fixed: [u8; 4],
pub byte_lim: Vec<u8> /* max length: 5 */,
pub byte_unlim: Vec<u8>,
} |
XDR uses "optional" types to implement a "linked list"-like structure. XDR's "pointer" syntax suggests that this type of structure be implemented as a literal linked list. This might be natural in C, but it certainly is not in Rust. Therefore, this library represents optionals using a Rust Vector.
The implementation requires that the self-referential structure has a container type, and that container type--NOT the self-referential type itself--is the one that contains the vector. It is also required that the optional field be the last field of the self-referential struct. For example:
| XDR | Rust |
|---|---|
struct Node {
string name<>;
Node *next; /* must be the last field ! */
};
struct NodeList {
Node *list;
}; |
pub struct Node {
pub name: std::ffi::OsString,
}
pub struct NodeList {
pub list: Vec<Node>,
} |
If the self-referential type does not have a container type, then this library will not serialize it properly! In practice, most/all of the real protocol definitions I have looked at do use container types for lists. For example, in the following types from the NFSv3 spec:
struct entry3 {
fileid3 fileid;
filename3 name;
cookie3 cookie;
entry3 *nextentry;
};
struct dirlist3 {
entry3 *entries;
bool eof;
};dirlist3 is the needed container type.
Although non-recursive optionals seem to be rare or non-existent in practice, they are possible. A
non-recursive optional is simply encoded as a Rust Option:
| XDR | Rust |
|---|---|
struct MyOption {
int *maybe;
}; |
pub struct MyOption {
pub maybe: Option<i32>,
} |
XDR enums are simply represented as Rust enums:
| XDR | Rust |
|---|---|
enum Cases {
one = 1,
two = 2,
three = 3
}; |
pub enum Cases {
one,
two,
three,
} |
XDR unions come in multiple flavors depending on the type of the discriminant. Bool-discriminated
unions are the simplest, represented in Rust as Options:
| XDR | Rust |
|---|---|
union MyOption switch (bool yes) {
case TRUE:
int data;
case FALSE:
void;
}; |
pub struct MyOption {
pub inner: Option<i32>,
} |
When the discriminant is a bool, this library requires the False arm to be void.
Enum-discriminated unions are represented as Rust enums:
| XDR | Rust |
|---|---|
enum Cases {
One = 1,
Two = 2
};
union Things switch (Cases blah) {
case One:
int a;
case Two:
bool b;
default:
void;
}; |
pub enum Cases {
One,
Two,
}
pub enum Things {
One(i32),
Two(bool),
Default,
} |
Note that when the union has a default arm, that is represented by Default enum variant in Rust,
which should be distinguished from the default() method on the enum!
The XDR spec allows for int-discriminated enums, which are effectively equivalent to enum-discriminated unions in terms of their encoding. The only difference is whether the arms are given meaningful names or not. Int-discriminated unions do not appear to be used in practice, so this library does not support them.
XDR normally uses snake_case for type names, while Rust uses CamelCase. This library makes no attempt to rename types to conform to the Rust style. I recommend using CamelCase names in the XDR specs that you write, so that the generated Rust code will be idiomatic.
Here are some relevant RFCs:
- RFC 4506 - XDR Serialization Protocol
- RFC 5531 - RPC Protocol
- RFC 1813 - RPCBIND and PortMapper Protocols
- RFC 1813 - NFS v3 Protocol
- RFC 7530 - NFS v4.0 Protocol
- RFC 7531 - NFS v4.0 XDR/RPC Specification
- RFC 5661 - NFS v4.1 Protocol
- RFC 5662 - NFS v4.1 XDR/RPC Specification
- RFC 7862 - NFS v4.2 Protocol
- RFC 7863 - NFS v4.2 XDR/RPC Specification
- RFC 8434 - Requirements for PNFS Layout Types
- RFC 8435 - PNFS Flex Files Layout Specification