Elixir XDR

Build Badge Coverage Status Version Badge Downloads Badge License badge

Process XDR types based on the RFC4506. Extend with ease to other XDR types.

Installation

Available in Hex, Add elixir_xdr to your list of dependencies in mix.exs:

def deps do
[
{:elixir_xdr, "~> 0.1.0"}
]
end

Implemented types

The following XDR types are completely implemented in this library:

# Basic types
XDR.Int # Section 4.1
XDR.UInt # Section 4.2
XDR.Bool # Section 4.4
XDR.HyperInt # Section 4.5
XDR.HyperUInt # Section 4.5
XDR.Float # Section 4.6
XDR.DoubleFloat # Section 4.7
XDR.Void # Section 4.16
# Complex types
XDR.Enum # Section 4.3
XDR.FixedOpaque # Section 4.9
XDR.VariableOpaque # Section 4.10
XDR.String # Section 4.11
XDR.FixedArray # Section 4.12
XDR.VariableArray # Section 4.13
XDR.Struct # Section 4.14
XDR.Union # Section 4.15
XDR.Optional # Section 4.19

The following types were not implemented

XDR.QuadFloat # Section 4.8, not supported for 128-byte size.
XDR.Const # Section 4.17, can be replaced with elixir constants.
XDR.Typedef # Section 4.18, may be implemented with elixir modules. More info bellow in this guide.

Better without macros

It is an Open Source project, not a code that only I understand

Macros are harder to write than ordinary Elixir functions, implementing them increases the code complexity which is not good especially if you are planning to build an Open Source code easy to understand to everyone. We decided to go without macros, we want to let everyone to expand or implement their own XDR types with a clear model based on Elixir functions.

How to implement an XDR type?

Behavior is the key. When implementing a new XDR type follow this Behavior's Declaration.

Decoding output

Encoded binaries may overflow the byte(s) size. That's why the returning value for decoding functions is set to be a tuple. Second element holds the remaining binary after decoding. This applies to all XDR types.

iex> XDR.Int.decode_xdr!(<<0, 0, 4, 210, 5>>)
{%XDR.Int{datum: 1234}, <<5>>}

Usage examples

As mentioned before all the XDR types follow the same Behavior's Declaration

Integer

For encoding integers use encode_xdr/2 or use the raising version of the function encode_xdr!/2.

iex> XDR.Int.new(1234) |> XDR.Int.encode_xdr()
{:ok, <<0, 0, 4, 210>>}
iex> XDR.Int.new(1234) |> XDR.Int.encode_xdr!()
<<0, 0, 4, 210>>

For decoding use decode_xdr/2 or decode_xdr!/2.

iex> XDR.Int.decode_xdr(<<0, 0, 4, 210>>)
{:ok, {%XDR.Int{datum: 1234}, <<>>}}
iex> XDR.Int.decode_xdr!(<<0, 0, 4, 210>>)
{%XDR.Int{datum: 1234}, <<>>}

Unsigned Integer

Represents integer values in a range of [0, 4294967295].

For encoding

iex> XDR.UInt.new(564) |> XDR.UInt.encode_xdr()
{:ok, <<0, 0, 2, 52>>}
iex> XDR.UInt.new(564) |> XDR.UInt.encode_xdr!()
<<0, 0, 2, 52>>

For decoding

iex> XDR.UInt.decode_xdr(<<0, 0, 2, 52>>)
{:ok, {%XDR.UInt{datum: 564}, <<>>}}
iex> XDR.UInt.decode_xdr!(<<0, 0, 2, 52>>)
{%XDR.UInt{datum: 564}, <<>>}

Enumeration

Represents subsets of integers.

Implementation

Enums are keywords lists containing a set of declarations (statically defined) and a identifier with the key of the selected declaration. The XDR.Bool is a clear example of an Enum implementation.

declarations = [false: 0, true: 1]

Now, you could decide the key to select

For encoding

iex> XDR.Enum.new([false: 0, true: 1], :false) |> XDR.Enum.encode_xdr()
{:ok, <<0, 0, 0, 0>>}
iex> XDR.Enum.new([false: 0, true: 1], :true) |> XDR.Enum.encode_xdr!()
<<0, 0, 0, 1>>

For decoding

iex> XDR.Enum.decode_xdr(<<0, 0, 0, 1>>, %{declarations: [false: 0, true: 1]})
{:ok, {%XDR.Enum{declarations: [false: 0, true: 1], identifier: true}, <<>>}}
iex> XDR.Enum.decode_xdr!(<<0, 0, 0, 1>>, %{declarations: [false: 0, true: 1]})
{%XDR.Enum{declarations: [false: 0, true: 1], identifier: true}, <<>>}

Boolean

Boolean is an Enum implementation that allows us to create boolean types

iex> XDR.Bool.new(true) |> XDR.Bool.encode_xdr()
{:ok, <<0, 0, 0, 0>>}
iex> XDR.Bool.new(true) |> XDR.Bool.encode_xdr!()
<<0, 0, 0, 0>>

For decoding the binary use decode_xdr/2 or decode_xdr!/2..

iex> XDR.Bool.decode_xdr(<<0, 0, 0, 1>>)
{:ok, {%XDR.Bool{declarations: [false: 0, true: 1], identifier: true}, ""}}
iex> XDR.Bool.decode_xdr!(<<0, 0, 0, 1>>)
{%XDR.Bool{declarations: [false: 0, true: 1], identifier: true}, ""}

Hyper Integer

Represents integer values in a range of [-9223372036854775808, 9223372036854775807]

For encoding

iex> XDR.HyperInt.new(258963) |> XDR.HyperInt.encode_xdr()
{:ok, <<0, 0, 0, 0, 0, 3, 243, 147>>}
iex> XDR.HyperInt.new(258963) |> XDR.HyperInt.encode_xdr!()
<<0, 0, 0, 0, 0, 3, 243, 147>>

For encoding

iex> XDR.HyperInt.decode_xdr(<<0, 0, 0, 0, 0, 3, 243, 147>>)
{:ok, {%XDR.HyperInt{datum: 258963}, <<>>}}
iex> XDR.HyperInt.decode_xdr!(<<0, 0, 0, 0, 0, 3, 243, 147>>)
{%XDR.HyperInt{datum: 258963}, <<>>}

Unsigned Hyper Integer

Represents integer values in a range of [0, 18446744073709551615]

For encoding

iex> XDR.HyperUInt.new(258963) |> XDR.HyperUInt.encode_xdr()
{:ok, <<0, 0, 0, 0, 0, 3, 243, 147>>}
iex> XDR.HyperUInt.new(258963) |> XDR.HyperUInt.encode_xdr!()
<<0, 0, 0, 0, 0, 3, 243, 147>>

For decoding

iex> XDR.HyperUInt.decode_xdr(<<0, 0, 0, 0, 0, 3, 243, 147>>)
{:ok, {%XDR.HyperUInt{datum: 258963}, <<>>}}
iex> XDR.HyperUInt.decode_xdr!(<<0, 0, 0, 0, 0, 3, 243, 147>>)
{%XDR.HyperUInt{datum: 258963}, <<>>}

Floating Point

Represents single-precision float values (32 bits, 4 bytes)

For encoding

iex> XDR.Float.new(3.46) |> XDR.Float.encode_xdr()
{:ok, <<64, 93, 112, 164>>}
iex> XDR.Float.new(258963) |> XDR.Float.encode_xdr!()
<<64, 93, 112, 164>>

For decoding

iex> XDR.Float.decode_xdr(<<64, 93, 112, 164>>)
{:ok, {%XDR.Float{float: 3.4600000381469727}, <<>>}}
iex> XDR.Float.decode_xdr!(<<64, 93, 112, 164>>)
{%XDR.Float{float: 3.4600000381469727}, <<>>}

Double-Floating Point

Represents Double-precision float values (64 bits, 8 bytes)

For encoding

iex> XDR.DoubleFloat.new(3.46) |> XDR.DoubleFloat.encode_xdr()
{:ok, <<64, 11, 174, 20, 122, 225, 71, 174>>}
iex> XDR.DoubleFloat.new(258963) |> XDR.DoubleFloat.encode_xdr!()
<<64, 11, 174, 20, 122, 225, 71, 174>>

For decoding

iex> XDR.DoubleFloat.decode_xdr(<<64, 11, 174, 20, 122, 225, 71, 174>>)
{:ok, {%XDR.DoubleFloat{float: 3.46}, <<>>}}
iex> XDR.DoubleFloat.decode_xdr!(<<64, 11, 174, 20, 122, 225, 71, 174>>)
{:ok, {%XDR.DoubleFloat{float: 3.46}, <<>>}}

Fixed-Length Opaque

FixedOpaque is used for fixed-length uninterpreted data that needs to be passed among machines, in other words, let's think on a string that must match a fixed length.

iex> ComplementBinarySize.new(<<1,2,3,4,5>>) |> ComplementBinarySize.encode_xdr()
{:ok, <<1, 2, 3, 4, 5, 0, 0, 0>>}

An example is available here: FixedOpaque Type.

Variable-Length Opaque

Represents a sequence of n (numbered 0 through n-1) arbitrary bytes to be the number n encoded as an unsigned integer

For encoding

iex> XDR.VariableOpaque.new(<<1, 2, 3, 4, 5>>) |> XDR.VariableOpaque.encode_xdr()
{:ok, <<0, 0, 0, 5, 1, 2, 3, 4, 5, 0, 0, 0>>}
iex> XDR.VariableOpaque.new(<<1, 2, 3>>, 3) |> XDR.VariableOpaque.encode_xdr!()
<<0, 0, 0, 3, 1, 2, 3, 0>>

For decoding

iex> XDR.VariableOpaque.decode_xdr(<<0, 0, 0, 5, 1, 2, 3, 4, 5, 0, 0, 0>>, %{max_size: 5})
{:ok, {%XDR.VariableOpaque{max_size: 5, opaque: <<1, 2, 3, 4, 5>>}, <<>>}}
iex> XDR.VariableOpaque.decode_xdr!(<<0, 0, 0, 5, 1, 2, 3, 4, 5, 0, 0, 0>>, %{max_size: 5})
{%XDR.VariableOpaque{max_size: 5, opaque: <<1, 2, 3, 4, 5>>}, <<>>}

String

For econding strings.

iex> XDR.String.new("The little prince") |> XDR.String.encode_xdr()
{:ok,
<<0, 0, 0, 17, 84, 104, 101, 32, 108, 105, 116, 116, 108, 101, 32, 112, 114,
105, 110, 99, 101, 0, 0, 0>>}
iex> XDR.String.new("The little prince") |> XDR.String.encode_xdr!()
<<0, 0, 0, 17, 84, 104, 101, 32, 108, 105, 116, 116, 108, 101, 32, 112, 114,
105, 110, 99, 101, 0, 0, 0>>

For decoding strings.

iex> XDR.String.decode_xdr(<<0, 0, 0, 17, 84, 104, 101, 32, 108, 105, 116, 116, 108, 101, 32, 112, 114,
105, 110, 99, 101, 0, 0, 0>>)
{:ok, {%XDR.String{max_length: 4294967295, string: "The little prince"}, ""}}
iex> XDR.String.decode_xdr!(<<0, 0, 0, 17, 84, 104, 101, 32, 108, 105, 116, 116, 108, 101, 32, 112, 114,
105, 110, 99, 101, 0, 0, 0>>)
{%XDR.String{max_length: 4294967295, string: "The little prince"}, ""}

Fixed-Length Array

Represents a Fixed-Length array that contains the same type of elements

For encoding

iex> XDR.FixedArray.new([1,2,3], XDR.Int, 3) |> XDR.FixedArray.encode_xdr()
{:ok, <<0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3>>}
iex> XDR.FixedArray.new(["The", "little", "prince"], XDR.String, 3) |> XDR.FixedArray.encode_xdr!()
<<0, 0, 0, 3, 84, 104, 101, 0, 0, 0, 0, 6, 108, 105, 116, 116, 108, 101, 0, 0,
0, 0, 0, 6, 112, 114, 105, 110, 99, 101, 0, 0>>

For decoding

iex> XDR.FixedArray.decode_xdr(<<0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3>>, %{type: XDR.Int, length: 3})
{:ok, {[%XDR.Int{datum: 1}, %XDR.Int{datum: 2}, %XDR.Int{datum: 3}], <<>>}}
iex> XDR.FixedArray.decode_xdr!(<<0, 0, 0, 3, 84, 104, 101, 0, 0, 0, 0, 6, 108, 105, 116, 116, 108,
101, 0, 0, 0, 0, 0, 6, 112, 114, 105, 110, 99, 101, 0, 0>>, %{type: XDR.String, length: 3})
{[
%XDR.String{max_length: 4294967295, string: "The"},
%XDR.String{max_length: 4294967295, string: "little"},
%XDR.String{max_length: 4294967295, string: "prince"}
], <<>>}

Variable-Length Array

Represents a variable-length array which contains the same type of elements

For encoding

iex> XDR.VariableArray.new([1,2,3], XDR.Int) |> XDR.VariableArray.encode_xdr()
{:ok, <<0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3>>}
iex> XDR.VariableArray.new(["The", "little", "prince"], XDR.String) |> XDR.VariableArray.encode_xdr!()
<<0, 0, 0, 3, 0, 0, 0, 3, 84, 104, 101, 0, 0, 0, 0, 6, 108, 105, 116, 116, 108,
101, 0, 0, 0, 0, 0, 6, 112, 114, 105, 110, 99, 101, 0, 0>>

For decoding

iex> XDR.VariableArray.decode_xdr(<<0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3>>,
...> %{type: XDR.Int, max_length: 3})
{:ok, {[%XDR.Int{datum: 1}, %XDR.Int{datum: 2}, %XDR.Int{datum: 3}], <<>>}}
iex> XDR.VariableArray.decode_xdr!(<<0, 0, 0, 3, 0, 0, 0, 3, 84, 104, 101, 0, 0, 0, 0, 6, 108, 105,
...> 116, 116, 108, 101, 0, 0, 0, 0, 0, 6, 112, 114, 105, 110, 99, 101, 0, 0>>,
...> %{type: XDR.String, length: 3})
{[
%XDR.String{max_length: 4294967295, string: "The"},
%XDR.String{max_length: 4294967295, string: "little"},
%XDR.String{max_length: 4294967295, string: "prince"}
], <<>>}

Structure

iex(1)> name = XDR.String.new("The little prince")
%XDR.String{max_length: 4294967295, string: "The little prince"}
iex(2)> size = XDR.Int.new(298)
%XDR.Int{datum: 298}
iex(3)> Book.new(name, size) |> Book.encode_xdr()
{:ok,
<<0, 0, 0, 17, 84, 104, 101, 32, 108, 105, 116, 116, 108, 101, 32, 112, 114,
105, 110, 99, 101, 0, 0, 0, 0, 0, 1, 42>>}

An example is available here: Struct Type.

Union

A union is a type composed of a discriminant (Statement) followed by a type selected from a set of prearranged types (UnionStatement). The type of discriminant is either "Int", "Unsigned Int", or an Enumerated type, such as "Bool". The (UnionStatement) types are called "arms" of the union and are preceded by the value of the discriminant that implies their encoding.

iex(1)> XDR.UnionStatement.new(:ST_NOMINATE) |> XDR.UnionStatement.encode_xdr()
{:ok, <<0, 0, 0, 3, 64, 93, 112, 164>>}
iex(3)> XDR.UnionStatement.decode_xdr(<<0, 0, 0, 3, 64, 93, 112, 164>>)
{:ok, {{:ST_NOMINATE, %XDR.Float{float: 3.4600000381469727}}, ""}}

An example is available here: Union Example

Void

Represents the void types or nil in elixir case

For encoding

iex> XDR.Void.new(nil) |> XDR.Void.encode_xdr()
{:ok, <<>>}
iex> XDR.Void.new(nil) |> XDR.Void.encode_xdr!()
<<>>

For decoding

iex> XDR.Void.decode_xdr(<<>>)
{:ok, {nil, <<>>}}
iex> XDR.Void.decode_xdr!(<<>>)
{nil, <<>>}

Optional

Think that you are filling out a form and it has optional fields such as the phone number if you do not want to fill this field you can leave it empty and the field will have a nil value, on the contrary, if you want to fill it out you can do it and it will take the indicated value

iex(1)> XDR.String.new("phone number") |> OptionalString.new() |> OptionalString.encode_xdr()
{:ok, <<0, 0, 0, 1, 0, 0, 0, 12, 112, 104, 111, 110, 101, 32, 110, 117, 109, 98, 101, 114>>}
iex(2)> OptionalString.decode_xdr(<<0, 0, 0, 1, 0, 0, 0, 12, 112, 104, 111, 110, 101, 32, 110, 117, 109, 98, 101, 114>>)
{:ok,
{%XDR.Optional{type: %XDR.String{max_length: 4294967295, string: "phone number"}}, ""}}
iex(3)> OptionalString.new(nil) |> OptionalString.encode_xdr()
{:ok, <<0, 0, 0, 0>>}
iex(4)> OptionalString.decode_xdr(<<0, 0, 0, 0>>)
{:ok, {nil, ""}}

An example is available here: Optional Type Example

Contributing and Development

See CONTRIBUTING.md for guidance on how to develop for this library.

Bug reports and pull requests are welcome on GitHub at https://github.com/kommitters/elixir_xdr.

Everyone is welcome to participate in the project.

Changelog

See the CHANGELOG for versions details.

License

See LICENSE for details.

Credits

Made with 💙 from kommit