#xdr #codec #encode #rpc #onc

bin+lib fastxdr

Generate Rust types from XDR specs with fast, zero-copy deserialisation

3 stable releases

1.0.2 Aug 16, 2020
1.0.1 Aug 10, 2020
1.0.0 Aug 9, 2020

#117 in Build Utils

Download history 3/week @ 2022-03-05 8/week @ 2022-03-12 13/week @ 2022-03-19 3/week @ 2022-03-26 2/week @ 2022-04-02 4/week @ 2022-04-09 3/week @ 2022-04-16 5/week @ 2022-04-23 16/week @ 2022-04-30 14/week @ 2022-05-07 53/week @ 2022-05-14 12/week @ 2022-05-21 24/week @ 2022-05-28 20/week @ 2022-06-04 2/week @ 2022-06-11 6/week @ 2022-06-18

52 downloads per month

BSD-3-Clause

175KB
6K SLoC

fastxdr

Transpiles XDR specifications into Rust code.

  • Generates Rust types with fast, zero-copy deserialisation
  • Customisable derives for generated types
  • No panicking - returns generated Error variants for malformed data
  • Use as part of a build.rs or generate with a standalone binary
  • XDR unions mapped to Rust enums 1-to-1 for convince
  • XDR typedefs produce distinct Rust types (not type aliases)
  • Complies with rfc1014 / rfc1832 / rfc4506

Types containing opaque bytes are generic over AsRef<[u8]> implementations, and all types have TryFrom<Bytes> implemented for idiomatic, zero-copy deserialisation (see Bytes).

Speed

Deserialising the wire protocol is very fast, usually under 1 microsecond.

The examples below are NFS messages captured from a production network, of typical size and complexity for the protocol:

setclientid             time:   [719.93 ns 724.15 ns 728.76 ns]
mount                   time:   [661.15 ns 663.54 ns 665.85 ns]
lookup                  time:   [819.69 ns 823.80 ns 828.04 ns]

By avoiding the need to copy opaque bytes entirely, even XDR messages containing large amounts of data typically deserialise in 1us or less on a modern CPU in O(n) time and space.

Library functionality

This crate can be used as a library to implement custom code generation, or build XDR linters, etc.

The library tokenises the XDR specs using the Pest crate, constructs an abstract syntax tree, indexes and resolves type aliases and constants and generates code to calculate the on-wire size of the XDR serialised types - this information is exposed to users through the index and ast modules.

Usage

The generated code has dependencies that must be added to your Cargo.toml:

[dependencies]
thiserror = "1.0"
bytes = "0.5"

Then either generate the code as part of a build script (preferred), or manually using the CLI.

To view the generated types, either export the generated types in your application and use cargo doc, or use the CLI to produce the generated code directly for reading.

Build Script

To use it as part of a build.rs build script, first add fastxdr to the build dependencies:

[build-dependencies]
fastxdr = "1.0"

Then create a build.rs file at the crate root (not in src):

fn main() {
    // Tell Cargo to regenerate the types if the XDR spec changes
    println!("cargo:rerun-if-changed=src/xdr_spec.x");

    // Read from xdr_spec.x, writing the generated code to out.rs
    std::fs::write(
        std::path::Path::new(std::env::var("OUT_DIR").unwrap().as_str()).join("out.rs"),
        fastxdr::Generator::default()
            .generate(include_str!("src/xdr_spec.x"))
            .unwrap(),
    )
    .unwrap();
}

And include the generated content somewhere in your application:

// Where out.rs is the filename from above
include!(concat!(env!("OUT_DIR"), "/out.rs"));
use xdr::*;

The generated content is within a module named xdr which you may choose to re-export if needed.

CLI

You can also generate the code with the CLI:

cargo install fastxdr
fastxdr ./path/to/spec.x > generated.rs

This can get confusing if the spec is modified and the code is not regenerated, or the spec is not checked into source control and typically a build.rs script is the best way to go.

Orphan Rule

Because of the orphan rule it is not possible to implement TryFrom for types defined outside of generated code such as u32, etc. This is normally fine, except for relatively uncommon typedefs of variable length arrays containing primitive types.

Therefore any typedefs to arrays of primitive types must be modified slightly - this is not a breaking change and does not affect the serialised on-wire format:

typedef uint32_t        bitmap4<>;

As the orphan rule prevents a TryFrom implementation being added to the u32 typedef target, wrap it in a typedef to generate a new type:

typedef uint32_t        bitmap_inner;
typedef bitmap_inner    bitmap4<>;

The array now contains the bitmap_inner type that can have TryFrom implemented for it.

Dependencies

~1.4–2MB
~41K SLoC