goxdr - Go XDR compiler
goxdr [-b|-B] [-fmt] [-i import] [-p package]
[-o output.go] [file.x …]
goxdr compiles an RFC4506 XDR interface file to a set of go data structures that can be either marshaled to or unmarshaled from standard XDR binary format or traversed for other purposes such as pretty-printing. It does not rely on go’s reflection facilities, and so can be used to special-case handling of different XDR typedefs that represent identical go types.
Because go’s type system is not very extensible, goxdr uses a
dual type system. Every type T
declared in an XDR
file compiles to two corresponding go types, a native type
T
convenient for programming, and an XdrType,
XdrType_T
, with methods that facilitate generic traversal
and marshaling. XdrType_T
is equivalent to *T
when it is possible to implement appropriate methods on T
,
but this is not always the case. However, for all XDR types
T
, there is a method XDR_T(*T)XdrType_T
to
cast a value to its XdrType.
goxdr’s native types map to the most intuitive go equivalent: strings
map to strings, pointers map to pointers, fixed-size arrays map to
arrays, and variable-length arrays map to slices, without new type
declarations that might complicate assignment. E.g., the XDR
typedef string mystring<32>
is just a string, and so
can be assigned from a string. This does mean you can assign a string
longer than 32 bytes, but length limits are enforced during both
marshaling and unmarshaling.
To be consistent with go’s symbol export policy, all types, enum
constants, and struct/union fields defined in an XDR file are
capitalized in the corresponding go representation. Source XDR types are
mapped to their equivalent go types as follows (note that T
cannot be opaque
or string
in this table):
Source type Native type notes
-------------- ------------ ------------------------------
bool bool use capital TRUE and FALSE
int int32
unsigned int uint32
hyper int64
unsigned hyper uint64
float float32
double float64
quadruple float128 but float128 is not defined
string<n> string
opaque<n> []byte
opaque[n] [n]byte
enum T type T int32
T* *T for any XDR type T
T<n> []T for any XDR type T
T[n] [n]T for any XDR type T
Each XDR typedef
is compiled to a go type alias
(type Alias = Original
). However, each has its own XdrType,
allowing generic traversal code to special-case the use of type aliases.
Among other places, this is useful for pretty-printing code that
displays the same underlying type in different ways. For example, if
your XDR source specifies:
typedef int meters;
typedef int seconds;
it will compile to native types:
type Meters = int32
type Seconds = int32
But you can treat instances of each specially in your marshaling
function by testing for type XdrType_Meters
or
XdrType_Seconds
. Alternatively, you can also use the
XdrTypeName()string
method to obtain the string “Meters” or
“Seconds.”
Each XDR enum
declaration compiles to a defined type
whose representation is an int32
. The constants of the enum
are defined as go constants of the new defined type.
RFC4506 defines bool
in XDR source as equivalent to an
enum
with name identifiers TRUE
and
FALSE
. goxdr translates XDR’s bool
to go’s
native bool
type. However, following the RFC, goxdr still
requires you to use capital TRUE
and FALSE
.
(The identifiers true
and false
will get
translated to exported go identifiers True
and
False
, which is probably not what you want.)
An XDR struct
is compiled to a defined type represented
as a go struct containing each field of the XDR struct.
An XDR union
is compiled to a data structure with one
public field for the discriminant and one method for each non-void “arm
declaration” (i.e., declaration in a case statement) that returns a
pointer to a value of the appropriate type. There is no need to
initialize the union when setting the discriminant; changing its value
just causes the appropriate method to return a non-nil pointer. Invoking
the wrong method for the current discriminant value calls panic.
As an example, the following XDR source:
enum myenum {
= 1,
tag1 = 2,
tag2 = 3
tag3 };
union myunion switch (myenum discriminant) {
case tag1:
int one;
case tag2:
<>;
string twodefault:
void;
};
compiles to this go code:
type Myenum int32
const (
= 1
Tag1 Myenum = 2
Tag2 Myenum = 3
Tag3 Myenum )
func XDR_Myenum(v *Myenum) *Myenum { return v }
type Myunion struct {
Discriminant Myenum...
}
func (u *Myunion) One() *int32 {...}
func (u *Myunion) Two() *string {...}
func XDR_Myunion(x XDR) *Myunion { return v }
XDR union
types have an XdrValid() bool
method that returns whether the discriminant is in a valid state. If the
union
type does not have a default case, another method
XdrValidTags() map[uint32]bool
specifies the set of valid
discriminant values. For union
types that are not valid by
default, because go’s default 0 value does not correspond to a valid
discriminant, a method XdrInitialize()
places them in a
valid state. Similarly, enum
types that can’t be statically
verified to be valid with value 0 (either because no name is assigned to
constant 0, or because names are assigned to constants in a different
file), have an XdrInitialize()
method that sets them to the
first name in the enum
definition.
As previously mentioned, for every native type T
generated by goxdr (where T
is the capitalized go type),
including typedefs, there is a corresponding XdrType into which one can
cast the native type by means of a generated function:
func XDR_T(v *T) XdrType_T {...}
All XdrTypes support the XdrType
interface:
type XdrType interface {
() string
XdrTypeName() interface{}
XdrValue() interface{}
XdrPointer(XDR, string)
XdrMarshal}
The XdrType can be marshaled, unmarshaled, or otherwise traversed by
means of the XdrMarshal(x XDR, name string)
method. Note
the name
argument has no effect for RFC4506-compliant
binary marshaling, and can safely be supplied as the empty string
""
. However, when traversing an XDR type for other purposes
such as pretty-printing, name
will be set to the nested
name of the field (with components separated by period).
The argument x
implements the XDR interface and
determines what XDR_T actually does (i.e., marshal or unmarshal). It has
the following interface:
type XDR interface {
(name string, val XdrType)
Marshal(string, ...interface{}) string
Sprintf}
Sprintf
is expected to be a copy of
fmt.Sprintf
. However, XDR back-ends that do not make use of
the name
argument (notably marshaling to RFC4506 binary
format) can save some overhead by returning an empty string. Hence, the
two sensible implementations of Sprintf
are:
func (xp *MyXDR1) Sprintf(f string, args ...interface{}) string {
return fmt.Sprintf(f, args...)
}
func (xp *MyXDR2) Sprintf(f string, args ...interface{}) string {
return ""
}
Marshal
is the method that actually does whatever work
will be applied to the data structure. The second argument,
val
, will be the go value that must be
marshaled/unmarshaled. To simplify data structure traversal, XdrTypes
implement various more specific interfaces that extend
XdrType
allowing many different types to be handled
identically. Specifically:
For bool and all 32-bit numeric types (including the size of
variable-length arrays), The XdrType implements XdrNum32
,
which allows the value to be extracted and set as a
uint32
.
For all 64-bit numeric types, the XdrType imlements
XdrNum64
, which allows the value to be extracted and set as
a uint64
.
For struct
and union
types, the XdrType
is just a pointer to the type being marshaled. However, these types
implement the XdrAggregate
interface, which extends
XdrType
with the method
XdrRecurse(x XDR, name string)
that recursively marshals
every field of the type. union
types also implement the
XdrUnion
interface.
An enum
type T
also just uses
*T
as its XdrType, but enum
types implement
XdrNum32
instead of XdrAggregate
. They also
implement the XdrEnum
interface, which provides access to
symbolic names via the XdrEnumNames() map[int32]string
method.
Fixed-length arrays (other than opaque[]
) have a
generated XdrType that implements the XdrArray
interface,
which extends XdrAggregate
. Calling XdrRecurse
on an array iterates over the array to marshal each element
individually.
Variable-length arrays (other than opaque<>
)
also use a generated XdrType implementing the XdrVec
interface, which also extends XdrAggregate
. The
XdrRecurse
method first calls Marshal
on a
value of XdrSize
to get or set the size of the array, then
calls Marshal
on each element of the array as with
fixed-length arrays.
Similar to variable-length arrays, pointers use a generated
XdrType that implements the XdrPtr
interface, which extends
XdrAggregate
. The XdrRecurse
method first
calls Marshal
on another generated type that implements the
XdrNum32
interface (capable of containing the value 0 or 1
to indicate nil or value-present), then, if the pointer is non-nil, it
calls Marshal
on the underlying value.
string
has XdrType
of
XdrString
, which also encodes the size bound of the string
and implements the XdrVarBytes
and XdrBytes
interfaces that extend XdrType
.
opaque<>
has an XdrType
of
XdrVecOpaque
, which also implements the
XdrVarBytes
and XdrBytes
interfaces that
extend XdrType
.
opaque[]
is passed as a generated type implementing
XdrArrayOpaque
, which extends XdrBytes
but not
XdrVarBytes
.
For most types, you can recover the native type from the XdrType via
the the XdrPointer()
and XdrValue()
methods,
which return an interface{}
. One exception is arrays
(including opaque[]
), for which XdrValue()
returns a slice to avoid copying the entire array. Also, the fake
bool
on which Marshal
is called for a pointer
type supports XdrValue()
but returns nil
from
XdrPointer()
since there is no actual bool
to
point to.
The XdrTypeName()
method returns a string describing the
underlying type as declared in the XDR file, including any
typedef
aliases used. The string returned may have a suffix
of “*“,”?“,”<>“, or”[]” to indicate pointers, the boolean
associated with a pointer, a variable-length array, and a fixed-length
array, respectively. If you want the actual size or bound that would go
inside the “[]” or “<>”, you will need to obtain these from the
XdrArraySize()uint32
or XdrBound()uint32
method respectively.
The table below summarizes the (overlapping) interfaces implemented
by the the different XdrTypes generated. In the table, T
stands for a complete standalone XDR type (so not string
or
opaque
). Basic marshaling can be performed in a type switch
statement handling interfaces that cover all types, for instance
XdrNum32
, XdrNum64
, XdrBytes
, and
XdrAggregate
.
Interface Implemented for XDR source types
----------- --------------------------------------------
XdrNum32 bool, [unsigned] int, enums, float,
size, pointer present flag
XdrNum64 [unsigned] hyper, double
XdrArray T[n]
XdrVec T<n>
XdrPtr T*
XdrEnum enum T
XdrUnion union T
XdrBytes string<n>, opaque[n], opaque<n>
XdrVarBytes string<n>, opaque<n>
XdrArrayOpaque opaque[n]
XdrAggregate struct T, union T, T*, T<n>, T[n]
XdrTypedef typedef BaseT T
fmt.Stringer all types in XdrNum{32,64} and XdrBytes
fmt.Scanner all types in XdrNum{32,64} and XdrBytes
XdrType all XDR types
As previously mentioned, each (capitalized) type T
output by goxdr also has function XDR_T
that returns an
instance of XdrType
. For struct
,
union
, and enum
types, this function is the
identity function:
func XDR_T(v *T) XdrType_T { return v }
For other types, however, this returns a defined type implementing
the interfaces described in the previous subsection. As an example, the
following function in the pre-defined boilerplate casts an ordinary
*int32
into the defined type *XdrInt32
, which
implements the XdrNum32
interface:
type XdrInt32 int32
type XdrType_int32 = *XdrInt32
// ... implementation of XdrNum32 methods ...
func (XdrInt32) XdrTypeName() string { return "int32" }
func (v *XdrInt32) XdrPointer() interface{} { return (*int32)(v) }
func (v XdrInt32) XdrValue() interface{} { return int32(v) }
func (v *XdrInt32) XdrMarshal(x XDR, name string) { x.Marshal(name, v) }
func XDR_int32(v *int32) *XdrInt32 { return (*XdrInt32)(v) }
The following table lists the concrete types passed to the
Marshal
method. Note that types listed as
generated
get passed as a different defined type for each
underlying type T
. The defined type makes the size bound
availble via an XdrBound()
method, since that information
cannot conveniently be encoded as part of the go type.
XDR type Marshaled as notes
-------------- -------------- -------------------------------
bool *XdrBool
int *XdrInt32
unsigned int *XdrUint32
float *XdrFloat32
hyper *XdrInt64
unsigned hyper *XdrUint64
double *XdrFloat64
string<n> XdrString
opaque<n> XdrVecOpaque
opaque[n] generated
T *T for struct, enum, union
T[n] generated
T* generated
T<n> generated
size *XdrSize when recursing in T<n>
typedef generated XdrBaseType(v) gives inner type
Note that an XDR Marshal(name string, v XdrType)
method
can use a type switch to special-case certain interfaces and types. If
you test v
against interfaces (e.g.,
XdrNum32
), it will work regardless of typedefs. If you test
v
for specific types, such as XdrString
or
XdrType_int32
, it will not work for typedefs. You can,
however, switch on XdrBaseType(v)
to check the underlying
base type. You can also, of course, switch on the native type returned
by v.XdrPointer(), which is not affected by typedefs.
XdrTypeName()
is also useful to check for typedefs, but to
do so should be called on v
rather than
XdrBaseType(v)
.
XdrMarshal
methods panic with type XdrError
(a user-defined string) if the input is invalid or a value is out of
range.
The types XdrOut
, XdrIn
, and
XdrPrint
in the boilerplate code (by default package
"github.com/xdrpp/goxdr/xdr"
) implement the
XDR
interface and perform RFC4506 binary marshaling,
RFC4506 binary unmarshaling, and pretty-printing, respectively.
type XdrOut struct {
.Writer
Out io}
type XdrIn struct {
.Reader
In io}
type XdrPrint struct {
.Writer
Out io}
Each version declaration inside a program declaration gets compiled down to an interface with the same name as the version. For example this declaration
{
program my_prog {
version my_vers void null(void) = 1;
int Increment(int) = 2;
void MultiArg(int, int) = 3;
} = 1;
} = 0x20000000;
yields the following interface:
type My_vers interface {
()
Null(*int32) *int32
Increment(*int32, *int32)
MultiArg}
In addition, goxdr creates a type that implements the
My_vers
interface (for use in clients):
type My_vers_Client struct {
func(XdrProc) error
XdrSend }
func (c My_vers_Client) Null() {...}
func (c My_vers_Client) Increment(a1 *int32) *int32 {...}
func (c My_vers_Client) MultiArg(a1 *int32, a2 *int32) {...}
The methods all bundle their argument and result types into a type
implementing XdrProc
, and pass it to a function
XdrSend
. An XdrProc
instance contains all the
information necessary to marshal a remote procedure call and its result,
namely the program, version, and procedure numbers as well as both the
arguments and results ready to be marshaled in XdrType
format. GetArg()
returns the arguments supplied by the
user, while GetRes()
returns a result type expected to be
overwritten by the result of the RPC.
type XdrProc interface {
() uint32
Prog() uint32
Vers() uint32
Proc() string
ProgName() string
VersName() string
ProcName() XdrType
GetArg() XdrType
GetRes}
For the server side, goxdr generates a type
My_vers_Server
that takes an instance of
My_vers
and allows lookup of argument and result types by
procedure number. Specifically, My_vers_Server
just
requires an instance of My_vers
, and then generically
exposes it through the XdrSrv
interface.
type My_vers_Server struct {
Srv My_vers}
func (s My_vers_Server) GetProc(p uint32) XdrSrvProc {...}
var _ XdrSrv = My_vers_Server{} // implements XdrSrv interface
XdrSrv
provides everything an RFC5531 RPC library needs
to marshal and unmarshal arguments. The Do()
method of an
XdrSrvProc
calls the underlying method on
My_vers_Server
. Hence, program-independent RPC code can
call proc := GetProc()
to get the XdrSrvProc
,
then unmarshal proc.GetArg()
, then call
proc.Do()
to handle the call, and finally marshal the
result from proc.GetRes()
.
type XdrSrvProc interface {
XdrProc()
Do}
type XdrSrv interface {
() uint32
Prog() uint32
Vers() string
ProgName() string
VersName(uint32) XdrSrvProc
GetProc}
goxdr supports the following options:
-help
-b
"github.com/xdrpp/goxdr/xdr"
, a
module with boilerplate code to assist in marshaling and unmarshaling
values, including code for interfaces such as XDR
and
XdrNum32
as well as helper types implementing these
interfaces (XdrInt32
, XdrUint32
, etc.). This
option suppresses that default import. This can be useful if you are
importing another package that includes the boilerplate (see
-B
).
-B
-b
. Note only one copy of the
boilerplate should be included in a package. If you use goxdr to compile
all XDR input files to a single go file (the recommended usage), then
you will get only one copy of the boilerplate with -B
.
However, if you compile different XDR files into different go files, you
will need to specify -b
with each XDR input file to avoid
including the boilerplate, then run goxdr with no input files
(goxdr -B -o goxdr_boilerplate.go
) to get one copy of the
boilerplate. You should also use -b
if you are importing
another package that already includes the boilerplate using the
-i
option below.
-enum-comments
XdrEnumComments() map[int32]string
that contains the
comment turned into a string. The option is useful if, for instance, you
have an enum encoding various error conditions. In that case you can put
a human-readable description of the error condition as a comment in the
XDR source file, and access the text of that comment from your program.
-fmt
gofmt -s
to simplify and
format it.
-i
import_path-lax-discriminants
-o
output.go-p
packagepackage main
.
To serialize a data structure of type MyType
:
func serialize_Mytype(val *MyType) []byte {
:= &bytes.Buffer{}
buf (val).XdrMarshal(&XdrOut{ buf }, "")
XDR_MyTypereturn buf.Bytes()
}
To serialize/unserialize an arbitrary instance of
XdrType
:
func serialize(val XdrType) []byte {
:= &bytes.Buffer{}
buf .XdrMarshal(&XdrOut{ buf }, "")
valreturn buf.Bytes()
}
func deserialize(val XdrType, in []byte) (e error) {
defer func() {
switch i := recover().(type) {
case nil:
case XdrError:
= i
e default:
panic(i)
}
}()
.XdrMarshal(&XdrIn{ bytes.NewBuffer(in) }, "")
valreturn nil
}
To pretty-print an arbitrary XDR-defined data structure, but
special-case any fields of type MySpecialStruct
by
formatting them with a function called
MySpecialString(*MySpecialStruct)
, you can do the
following:
type XdrMyPrint struct {
.Writer
Out io}
func (xp *XdrMyPrint) Sprintf(f string, args ...interface{}) string {
return fmt.Sprintf(f, args...)
}
func (xp *XdrMyPrint) Marshal(name string, i XdrType) {
switch v := i.(type) {
case *MySpecialStruct:
.Fprintf(xp.Out, "%s: %s\n", name, MySpecialString(v))
fmtcase fmt.Stringer:
.Fprintf(xp.Out, "%s: %s\n", name, v.String())
fmtcase XdrPtr:
.Fprintf(xp.Out, "%s._present: %v\n", name, v.GetPresent())
fmt.XdrMarshalValue(xp, name)
vcase XdrVec:
.Fprintf(xp.Out, "%s.len: %d\n", name, v.GetVecLen())
fmt.XdrMarshalN(xp, name, v.GetVecLen())
vcase XdrAggregate:
.XdrRecurse(xp, name)
vdefault:
.Fprintf(xp.Out, "%s: %v\n", name, i)
fmt}
}
func MyXdrToString(t XdrType) string {
:= &strings.Builder{}
out .XdrMarshal(&XdrMyPrint{out}, "")
treturn out.String()
}
rpcgen(1), xdrc(1)
https://tools.ietf.org/html/rfc4506, https://tools.ietf.org/html/rfc5531
goxdr is not hygienic. Because it capitalizes symbols, it could
produce a name clash if two symbols differ only in the capitalization of
the first letter. Moreover, it introduces various helper types and
functions that begin XDR_
or Xdr
, so could
produce incorrect code if users employ such identifiers in XDR files.
Though RFC4506 disallows identifiers that start with underscore, goxdr
accepts them and produces code with inconsistent export semantics (since
underscore cannot be capitalized).
With -lax-discriminants
, when unions use type bool as a
discriminant, goxdr generates incorrect code unless it knows that the
discriminant is of type bool. (This is because go provides no uniform
syntax for converting both enums and bools to int32.) goxdr tries to
figure out when the union discriminant is of type bool by following
typedefs in the file, but this doesn’t work if you use type aliases
defined in a different file.
IEEE 754 floating point allows for many different NaN (not a number) values. The marshaling code simply takes whatever binary value go has sitting in memory, byteswapping on little-endian machines. Other languages and XDR implemenations may produce different NaN values from the same code. Hence, in the presence of floating point, the marshaled output of seemingly deterministic code may vary across implementations.