Sign in

    moonxdr

    Bounded XDR schema interpreter, binary interchange and ONC RPC wire toolkit in pure MoonBit

    xdr
    onc-rpc
    rfc4506
    serialization
    schema
    Download zip
    Author
    Version
    0.2.0
    License
    MIT
    Last updated
    6 days ago
    Downloads
    46

    No dependencies

    #MoonXDR

    Pure MoonBit schema-driven XDR interchange and transport-independent ONC RPCv2 wire tools. Module: kekeshuo/moonxdr@0.2.0 · MIT · only official MoonBit core dependencies.

    #Purpose and standard target

    Protocol authors compile .x declarations and interchange typed values. Legacy integration tools inspect RPC envelopes and reassemble records. Fixture authors convert exact integers, binary128, opaque bytes and optional values between a local JSON convention and XDR. These are reusable APIs, not an NFS-specific reader or a full RPC client/server.

    The project targets all data types and declaration rules of RFC 4506, not a permanent subset. Version 0.2.0 adds typed quadruple precision and correct unsigned case/bound semantics. The clause matrix maps implemented behavior to tests and separates protocol rules from resource budgets and extensions. This is tested coverage, not independent certification or organizer acceptance. Any remaining standard discrepancy is a bug to address, not a permanently excluded standard feature. RFC 5531 support is separately limited to wire envelopes/framing.

    #Install and quick start

    moon add kekeshuo/moonxdr@0.2.0

    Add "kekeshuo/moonxdr" @xdr to your package imports. Calling functions must allow raise or catch XdrError::Invalid(offset, message).

    let schema = @xdr.parse_schema(
    "typedef quadruple Q; union Measurement switch(unsigned int tag) { case 4294967295: Q reading; default: void; };",
    )
    let q = @xdr.Quadruple::from_hex("3fff0000000000000000000000000000")
    let value = @xdr.VUnion(4294967295L, @xdr.VQuadruple(q))
    let wire = @xdr.encode(schema, "Measurement", value)
    assert_eq(@xdr.to_hex(wire), "ffffffff3fff0000000000000000000000000000")
    assert_true(@xdr.encode(schema, "Measurement", @xdr.decode(schema, "Measurement", wire)) == wire)

    This exact declaration, expected bytes and JSON round trip run in moon run examples/standard --deny-warn. The generated pkg.generated.mbti is the public API, including Reader/Writer, parse_schema, encode/decode, from_json/to_json, exact decimal64 and hex helpers, RPC and record APIs.

    #Supported standard behavior

    • const, typedef, named/inline enums, structs, discriminated unions; decimal, octal and hexadecimal constants stored as exact BigInt, then range-checked at use.
    • All XDR scalar types: int/uint, hyper/uhyper, bool, float, double, quadruple.
    • ASCII strings (including embedded NUL), fixed/variable opaque, fixed/variable arrays, recursive optional data, void union arms and typedef discriminants.
    • Enum values and int discriminants are signed32; uint discriminants and length bounds cover 0..4294967295. Unsigned case -1 is invalid, not an alias of MAX.
    • Omitted <> has protocol bound 4294967295U. Declared bounds are independent of operation limits; schema acceptance does not promise allocating the maximum.
    • Field order, union selection/default, unique names/cases, prior const bounds, boolean/enum membership, zero padding and complete input consumption are checked.
    • Quadruple stores sign/exponent/112 fraction bits in two UInt64 words, preserving zero, subnormals, infinity and NaN payloads. from_double widens binary64 exactly; it never substitutes a Double for a 128-bit value. XDR interchange does not require implementing IEEE128 arithmetic or decimal rounding; those are not claimed here.

    #Four executable downstream workflows

    moon run examples/schema --deny-warn moon run examples/rpc --deny-warn moon run examples/interchange --deny-warn moon run examples/standard --deny-warn

    1. Data producers parse sensor declarations, encode/decode exact64 records and assert identical bytes.
    2. Integration tooling attaches AUTH_SYS to a procedure payload, fragments an RPC CALL, reconstructs bytewise and decodes a schema-selected reply. No socket service.
    3. Fixture authors exchange exact64 JSON, opaque bytes and explicit optional values.
    4. Standard interoperability authors combine binary128 with a literal UINT32_MAX union case and verify exact network bytes and JSON round trips.

    #Project-defined JSON (not specified by RFC 4506)

    XDRJSON
    int / uint / enumchecked integral number; enum also accepts a name
    hyper / uhyperexact decimal string
    quadrupleexactly 32 hex digits in network word order; all bits retained
    float / doublefinite JSON number; use binary APIs for NaN/payload/negative-zero fidelity
    opaqueeven-length hex string
    optionalnull or {"some": VALUE}; nested optionals remain distinguishable
    struct / arrayexact-key object / array
    union{"tag": NUMBER, "value": VALUE}; signedness follows schema
    voidnull

    Integer number lexemes containing a fraction/exponent are rejected, including 1.0 and 1e0, to prevent underlying JSON conversion from truncating inputs. Struct Values are ordered (field_name, Value) arrays in declaration order.

    #Limits and language extensions

    Default operation limits: 1 MiB bytes, 65,536 aggregate items/tokens, depth 64. Allowed configuration maxima: 64 MiB, 1,048,576 items/tokens, depth 128. A constant literal has a 4,096-character resource limit; it is not restricted to signed32 or signed64. Type graph analysis has a work budget of four times the token limit. These caps can reject standard-valid large inputs; they do not change the schema's declared sizes or label domains. type_names() uses MoonBit shortlex order.

    Documented convenience extensions include // comments, 0X and negative hex/octal literals, prior-constant aliases and forward type references. These do not replace standard syntax. C preprocessing, expressions, rpcgen service generation and RPC program syntax are not RFC 4506 requirements. Full RPC services, sockets, rpcbind, NFS, retransmission, credentials verification and RPCSEC_GSS are separate work. AUTH_SYS is an untrusted assertion, not authentication.

    Readers own their input copies. Do not mutate public Type/Value arrays during use. RecordDecoder is a bounded single-stream state machine; errors poison it, and the caller must discard it and impose transport timeouts. See SECURITY.md.

    #Verify and version migration

    Pinned official development compiler: v0.10.14+7d59c7ec9.

    moon info && moon fmt moon fmt --check moon check --deny-warn moon build --deny-warn moon test --deny-warn python scripts/reference_vectors.py --check python scripts/reference128.py --check python scripts/audit.py

    46 tests include 64 independent Python struct vectors (384 scalar fields), 176 binary128 word vectors, 140 exact rational binary64-widening vectors, the RFC section 7 example bytes, UINT32_MAX union regressions, malformed lengths, recursion and record-stream boundaries. Fixtures are synthetic; tests/generated data do not count toward production source size. CI performs checks/build/tests and all four workflows on wasm-gc, wasm, js and native. Local Windows execution covers the first three; native execution is checked by the actual Linux CI job.

    0.2.0 intentionally changes public schema bounds to UInt, schema constants to BigInt, and arm labels / VUnion tags to Int64. Use an L suffix for explicit tags. parse_constant_number remains a signed32 compatibility helper only; new schema parsing uses parse_integer_constant. Version 0.1.0 is not overwritten.

    #License and provenance

    Original MIT implementation, not a port. See ATTRIBUTION.md for RFC4506/5531, CPython xdrlib (PSF), go-xdr (ISC) behavior references and core (Apache-2.0). No upstream implementation/tests are bundled. The independent Python oracles are original project code. AI-USE.md discloses AI assistance; architecture and bounded duplicate review describe implementation and ecosystem boundaries. No competition approval claim.

    XdrError

    pub suberror XdrError {
    Invalid(Int, String)
    }

    AcceptStatus

    pub(all) enum AcceptStatus {
    Success(Array[Byte])
    ProgramUnavailable
    ProgramMismatch(UInt, UInt)
    ProcedureUnavailable
    GarbageArguments
    SystemError
    }

    Arm

    pub(all) struct Arm {
    labels : Array[Int64]
    ty : Type
    }

    AuthSys

    pub(all) struct AuthSys {
    stamp : UInt
    machine : String
    uid : UInt
    gid : UInt
    groups : Array[UInt]
    }

    AUTH_SYS is an unauthenticated assertion, not proof of identity.

    AuthSys::from_auth

    fn AuthSys::from_auth(auth : OpaqueAuth) -> AuthSys raise XdrError

    AuthSys::to_auth

    fn AuthSys::to_auth(self : AuthSys) -> OpaqueAuth raise XdrError

    Field

    pub(all) struct Field {
    name : String
    ty : Type
    }

    Limits

    pub(all) struct Limits {
    max_bytes : Int
    max_items : Int
    max_depth : Int
    max_schema_tokens : Int
    }

    All limits are per operation, never trusted from wire lengths.

    Limits::default

    fn Limits::default() -> Limits

    Limits::validate

    fn Limits::validate(self : Limits) -> Unit raise XdrError

    OpaqueAuth

    pub(all) struct OpaqueAuth {
    flavor : UInt
    body : Array[Byte]
    }

    Wire authentication only; these bytes do not establish a trusted identity.

    OpaqueAuth::none

    fn OpaqueAuth::none() -> OpaqueAuth

    Parser

    pub struct Parser {
    tokens : Array[Token]
    cursor : Int
    schema : Schema
    }

    Parser::add_constant

    fn Parser::add_constant(self : Parser, name : String, value :
    BigInt
    , definition? : Bool) -> Unit raise XdrError

    Parser::add_type

    fn Parser::add_type(self : Parser, name : String, ty : Type) -> Unit raise XdrError

    Parser::arm_type

    fn Parser::arm_type(self : Parser, depth : Int, names? : Map[String, Bool]) -> Type raise XdrError

    Parser::base_type

    fn Parser::base_type(self : Parser, depth : Int) -> Type raise XdrError

    Parser::bound

    fn Parser::bound(self : Parser, closing : String) -> UInt raise XdrError

    Parser::compound

    fn Parser::compound(self : Parser, kind : String, depth : Int) -> Type raise XdrError

    Parser::declaration

    fn Parser::declaration(self : Parser, depth : Int) -> Field raise XdrError

    Parser::expect

    fn Parser::expect(self : Parser, expected : String) -> Unit raise XdrError

    Parser::identifier

    fn Parser::identifier(self : Parser) -> String raise XdrError

    Parser::new

    fn Parser::new(source : String, limits? : Limits) -> Parser raise XdrError

    Parser::offset

    fn Parser::offset(self : Parser) -> Int

    Parser::peek

    fn Parser::peek(self : Parser) -> String

    Parser::take

    fn Parser::take(self : Parser) -> String raise XdrError

    Parser::value

    Quadruple

    pub(all) struct Quadruple {
    high : UInt64
    low : UInt64
    } derive(Eq,
    Debug
    )

    RFC 4506 section 4.8 binary128, retained as two words on every backend. This is a numeric wire type, not a Double approximation or a software arithmetic library.

    Quadruple::classify

    fn Quadruple::classify(self : Quadruple) -> QuadrupleClass

    Quadruple::equal

    fn Quadruple::equal(Quadruple, Quadruple) -> Bool

    Quadruple::exponent

    fn Quadruple::exponent(self : Quadruple) -> Int

    Quadruple::fraction

    fn Quadruple::fraction(self : Quadruple) -> (UInt64, UInt64)

    Quadruple::from_double

    fn Quadruple::from_double(value : Double) -> Quadruple

    Exact widening from binary64. No arithmetic or conversion through decimal strings. NaN sign, quiet/signaling bit and payload are widened without canonicalization.

    Quadruple::from_hex

    fn Quadruple::from_hex(text : String) -> Quadruple raise XdrError

    Quadruple::not_equal

    fn Quadruple::not_equal(x : Quadruple, y : Quadruple) -> Bool

    Quadruple::sign

    fn Quadruple::sign(self : Quadruple) -> Bool

    Quadruple::to_bytes

    fn Quadruple::to_bytes(self : Quadruple) -> Array[Byte]

    Quadruple::to_hex

    fn Quadruple::to_hex(self : Quadruple) -> String

    QuadrupleClass

    pub(all) enum QuadrupleClass {
    Zero
    Subnormal
    Normal
    Infinite
    QuietNaN
    SignalingNaN
    } derive(Eq,
    Debug
    )

    QuadrupleClass::equal

    QuadrupleClass::not_equal

    fn QuadrupleClass::not_equal(x : QuadrupleClass, y : QuadrupleClass) -> Bool

    Reader

    pub struct Reader {
    data : Array[Byte]
    pos : Int
    limits : Limits
    }

    Reader::finish

    fn Reader::finish(self : Reader) -> Unit raise XdrError

    Reader::new

    fn Reader::new(data : Array[Byte], limits? : Limits) -> Reader raise XdrError

    Reader::position

    fn Reader::position(self : Reader) -> Int

    Reader::read_auth

    fn Reader::read_auth(self : Reader) -> OpaqueAuth raise XdrError

    Reader::read_bool

    fn Reader::read_bool(self : Reader) -> Bool raise XdrError

    Reader::read_double

    fn Reader::read_double(self : Reader) -> Double raise XdrError

    Reader::read_fixed

    fn Reader::read_fixed(self : Reader, length : Int) -> Array[Byte] raise XdrError

    Reader::read_float

    fn Reader::read_float(self : Reader) -> Float raise XdrError

    Reader::read_hyper

    fn Reader::read_hyper(self : Reader) -> Int64 raise XdrError

    Reader::read_int

    fn Reader::read_int(self : Reader) -> Int raise XdrError

    Reader::read_length

    fn Reader::read_length(self : Reader, bound : UInt) -> Int raise XdrError

    Reader::read_opaque

    fn Reader::read_opaque(self : Reader, bound? : UInt) -> Array[Byte] raise XdrError

    Reader::read_quadruple

    fn Reader::read_quadruple(self : Reader) -> Quadruple raise XdrError

    Reader::read_raw

    fn Reader::read_raw(self : Reader, count : Int) -> Array[Byte] raise XdrError

    Reader::read_string

    fn Reader::read_string(self : Reader, bound? : UInt) -> String raise XdrError

    Reader::read_uhyper

    fn Reader::read_uhyper(self : Reader) -> UInt64 raise XdrError

    Reader::read_uint

    fn Reader::read_uint(self : Reader) -> UInt raise XdrError

    Reader::remaining

    fn Reader::remaining(self : Reader) -> Int

    RecordDecoder

    pub struct RecordDecoder {
    limits : Limits
    record : Array[Byte]
    marker : UInt
    header_bytes : Int
    body_left : Int
    last : Bool
    fragments : Int
    poisoned : Bool
    }

    Incremental, owned record assembly. A malformed feed permanently poisons this decoder. feed returns records only on success; discard the stream on error.

    RecordDecoder::feed

    fn RecordDecoder::feed(self : RecordDecoder, chunk : Array[Byte]) -> Array[Array[Byte]] raise XdrError

    RecordDecoder::finish

    fn RecordDecoder::finish(self : RecordDecoder) -> Unit raise XdrError

    End of transport: incomplete headers, bodies and non-final records are errors.

    RecordDecoder::new

    fn RecordDecoder::new(limits? : Limits) -> RecordDecoder raise XdrError

    RejectStatus

    pub(all) enum RejectStatus {
    RpcMismatch(UInt, UInt)
    AuthError(UInt)
    }

    ReplyBody

    pub(all) enum ReplyBody {
    Accepted(OpaqueAuth, AcceptStatus)
    Rejected(RejectStatus)
    }

    RpcCall

    pub(all) struct RpcCall {
    xid : UInt
    program : UInt
    version : UInt
    procedure : UInt
    credential : OpaqueAuth
    verifier : OpaqueAuth
    arguments : Array[Byte]
    }

    RpcReply

    pub(all) struct RpcReply {
    xid : UInt
    body : ReplyBody
    }

    Schema

    pub struct Schema {
    types : Map[String, Type]
    constants : Map[String,
    BigInt
    ]
    constant_defs : Map[String, Bool]
    limits : Limits
    }

    Schema::constant

    fn Schema::constant(self : Schema, name : String) ->
    BigInt
    ?

    Schema::empty

    fn Schema::empty(limits? : Limits) -> Schema raise XdrError

    Schema::lookup

    fn Schema::lookup(self : Schema, name : String) -> Type raise XdrError

    Schema::resolve

    fn Schema::resolve(self : Schema, ty : Type) -> Type raise XdrError

    Schema::type_names

    fn Schema::type_names(self : Schema) -> Array[String]

    Schema::validate

    fn Schema::validate(self : Schema) -> Unit raise XdrError

    Fixed-point inhabitation detects mandatory recursive cycles while permitting pointers.

    Token

    pub(all) struct Token {
    text : String
    offset : Int
    }

    Type

    pub(all) enum Type {
    TVoid
    TInt
    TUInt
    THyper
    TUHyper
    TBool
    TFloat
    TDouble
    TQuadruple
    TString(UInt)
    TOpaque(UInt, Bool)
    TArray(Type, UInt, Bool)
    TOptional(Type)
    TRef(String)
    TEnum(Array[(String, Int)])
    TStruct(Array[Field])
    TUnion(Type, Array[Arm], Type?)
    }

    RFC 4506 types. Bounds are nonnegative and checked before allocation.

    Value

    pub(all) enum Value {
    VVoid
    VInt(Int)
    VUInt(UInt)
    VHyper(Int64)
    VUHyper(UInt64)
    VBool(Bool)
    VFloat(Float)
    VDouble(Double)
    VQuadruple(Quadruple)
    VString(String)
    VOpaque(Array[Byte])
    VArray(Array[Value])
    VOptional(Value?)
    VEnum(Int)
    VStruct(Array[(String, Value)])
    VUnion(Int64, Value)
    }

    Values preserve signed/unsigned 64-bit and IEEE representations without JSON rounding.

    Writer

    pub struct Writer {
    data : Array[Byte]
    limits : Limits
    }

    Writer::bytes

    fn Writer::bytes(self : Writer) -> Array[Byte]

    Writer::length

    fn Writer::length(self : Writer) -> Int

    Writer::new

    fn Writer::new(limits? : Limits) -> Writer raise XdrError

    Writer::write_auth

    fn Writer::write_auth(self : Writer, auth : OpaqueAuth) -> Unit raise XdrError

    Writer::write_bool

    fn Writer::write_bool(self : Writer, b : Bool) -> Unit raise XdrError

    Writer::write_double

    fn Writer::write_double(self : Writer, d : Double) -> Unit raise XdrError

    Writer::write_fixed

    fn Writer::write_fixed(self : Writer, bytes : Array[Byte]) -> Unit raise XdrError

    Writer::write_float

    fn Writer::write_float(self : Writer, f : Float) -> Unit raise XdrError

    Writer::write_hyper

    fn Writer::write_hyper(self : Writer, n : Int64) -> Unit raise XdrError

    Writer::write_int

    fn Writer::write_int(self : Writer, n : Int) -> Unit raise XdrError

    Writer::write_opaque

    fn Writer::write_opaque(self : Writer, bytes : Array[Byte], bound? : UInt) -> Unit raise XdrError

    Writer::write_quadruple

    fn Writer::write_quadruple(self : Writer, value : Quadruple) -> Unit raise XdrError

    Writer::write_raw

    fn Writer::write_raw(self : Writer, bytes : Array[Byte]) -> Unit raise XdrError

    Writer::write_string

    fn Writer::write_string(self : Writer, text : String, bound? : UInt) -> Unit raise XdrError

    RFC 4506 strings are ASCII; arbitrary bytes use opaque instead.

    Writer::write_uhyper

    fn Writer::write_uhyper(self : Writer, n : UInt64) -> Unit raise XdrError

    Writer::write_uint

    fn Writer::write_uint(self : Writer, n : UInt) -> Unit raise XdrError

    decode

    fn decode(schema : Schema, type_name : String, bytes : Array[Byte]) -> Value raise XdrError

    Decode exactly one datum; trailing bytes are rejected, not silently discarded.

    decode_call

    fn decode_call(bytes : Array[Byte], limits? : Limits) -> RpcCall raise XdrError

    decode_reply

    fn decode_reply(bytes : Array[Byte], limits? : Limits) -> RpcReply raise XdrError

    encode

    fn encode(schema : Schema, type_name : String, value : Value) -> Array[Byte] raise XdrError

    encode_call

    fn encode_call(call : RpcCall, limits? : Limits) -> Array[Byte] raise XdrError

    encode_reply

    fn encode_reply(reply : RpcReply, limits? : Limits) -> Array[Byte] raise XdrError

    fragment_record

    fn fragment_record(record : Array[Byte], fragment_size : Int, limits? : Limits) -> Array[Byte] raise XdrError

    RFC 5531 record markers; fragment size need not be XDR-word aligned. The returned framed buffer, including headers, must fit max_bytes.

    from_hex

    fn from_hex(text : String, limits? : Limits) -> Array[Byte] raise XdrError

    from_json

    fn from_json(schema : Schema, type_name : String, json : Json) -> Value raise XdrError

    Local JSON convention, not an RFC-defined JSON wire format.

    ident_continue

    fn ident_continue(c : Char) -> Bool

    ident_start

    fn ident_start(c : Char) -> Bool

    parse_constant_number

    fn parse_constant_number(text : String, offset? : Int) -> Int raise XdrError

    Compatibility signed32 literal utility from 0.1.0. New schema parsing uses parse_integer_constant, with context-sensitive range validation.

    parse_integer_constant

    fn parse_integer_constant(text : String, offset? : Int) ->
    BigInt
    raise XdrError

    Parse RFC 4506 decimal/octal/hex integer literals exactly, without a machine-word cap. The 4096-character ceiling is an operation budget, not an integer type restriction.

    parse_schema

    fn parse_schema(source : String, limits? : Limits) -> Schema raise XdrError

    parse_signed_decimal

    fn parse_signed_decimal(text : String) -> Int64 raise XdrError

    parse_unsigned_decimal

    fn parse_unsigned_decimal(text : String) -> UInt64 raise XdrError

    Exact decimal conversion used for JSON hyper integers; never passes through Double.

    to_hex

    fn to_hex(bytes : Array[Byte]) -> String

    to_json

    fn to_json(schema : Schema, type_name : String, value : Value) -> Json raise XdrError

    Binary validation precedes JSON output, including ASCII, collection and total byte limits.

    tokenize

    fn tokenize(source : String, limits? : Limits) -> Array[Token] raise XdrError

    Lexer preserves source character offsets and rejects unsupported directives.

    union_arm

    fn union_arm(arms : Array[Arm], fallback : Type?, tag : Int64) -> Type raise XdrError

    validate_tag

    fn validate_tag(tag : Type, value : Int64) -> Unit raise XdrError