This document is a description of the Fuchsia Interface Definition Language v2.0 (FIDL) implementation for C, including its libraries and code generator.
See FIDL 2.0: Overview for more information about FIDL's overall purpose, goals, and requirements, as well as links to related documents.
Goals
Support encoding and decoding FIDL objects with C11.
Generate headers which are compatible with C11 and C++14.
Small, fast, efficient, 64-bit only.
Depend only on a small subset of the standard library.
Minimize code expansion through table-driven encoding and decoding.
Support only one usage style: native.
Native Usage Style
Optimized to meet the needs of low-level systems programming.
Represent data structures whose memory layout coincides with the wire format.
Support in-place access and construction of FIDL objects.
Defer all memory allocation decisions to the client.
Code generator only produces type declarations, data tables, and simple inline functions.
Client is fully responsible for dispatching incoming method calls on interfaces (write their own switch statement and invoke argument decode functions).
Encoding Tables
To avoid generating any non-inline code whatsoever, the C language bindings instead produce encoding tables which describe how objects are encoded.
Introspection Tables
To allow for objects to be introspected (eg. printed), the C language bindings produce introspection tables which describe the name and type signature of each method of each interface and data structure.
Although small, introspection tables will be stripped out by the linker if unused.
Mapping Declarations
This is the mapping from FIDL types to C types which the code generator produces.
TODO: discuss reserved words, name mangling
TODO: discuss generated macros, enums, typedefs, encoding tables
Dependencies
Only depends on Zircon system headers and a portion of the C standard library.
Code Style
To be discussed.
The bindings library uses C standard library style, eg. function names are lower-case with underscores.
Data Types
typedef struct fidl_message_header { uint64_t transaction_id; uint32_t flags; uint32_t ordinal; } fidl_message_header_t; enum { // Specified that the message body is contained in the VMO which // was passed as the last handle in the handle table. FIDL_MSG_VMO = 0x00000001, };
Represents the initial part of every request or response message sent over a channel. The header is immediately followed by the body of the payload unless the FIDL_MSG_VMO flag is set, in which case the body is in the associated VMO.
typedef struct fidl_string { size_t size; // number of UTF-8 code units (bytes), must be 0 if |data| is null uint8_t* data; // pointer to UTF-8 code units (bytes), or null } fidl_string_t;
Holds a reference to a variable-length string.
When decoded, data points to the location within the buffer where the string content lives, or NULL if the reference is null.
When encoded, data is replaced by UINTPTR_MAX when the reference is non-null or NULL when the reference is null. The location of the string's content is determined by the depth-first traversal order of the message during decoding.
typedef struct fidl_vector { size_t size; // number of elements, must be 0 if |data| is null void* data; // pointer to element data, or null } fidl_vector_t;
Holds a reference to a variable-length vector of elements.
When decoded, data points to the location within the buffer where the elements live, or NULL if the reference is null.
When encoded, data is replaced by UINTPTR_MAX when the reference is non-null or NULL when the reference is null. The location of the vector's content is determined by the depth-first traversal order of the message during decoding.
Declared in system/ulib/fidl/include/lib/fidl/coding.h, defined in system/ulib/fidl/encoding.cpp.
zx_status_t fidl_encode( const fidl_type_t* type, void* bytes, uint32_t num_bytes, zx_handle_t* handles, uint32_t max_handles, uint32_t* actual_handles_out, const char** error_msg_out);
Encodes and validates exactly num_bytes of the object in bytes in-place by performing a depth-first traversal of the encoding data from type to fix up internal references. Replaces internal pointers references with FIDL_ALLOC_ABSENT
or FIDL_ALLOC_PRESENT
to indicate presence. Extracts non-zero internal handle references out of bytes, stores up to max_handles of them sequentially in handles, and replaces their location in bytes with FIDL_HANDLE_PRESENT
to indicate their presence. Sets actual_handles_out to the number of handles stored in handles.
To prevent handle leakage, this operation ensures that either all handles within bytes are moved into handles in case of success or they are all closed in case of an error.
If a recoverable error occurs, such as encountering a null pointer for a required sub-object, bytes remains in an unusable partially modified state.
All handles in bytes which were already been consumed up to the point of the error are closed and actual_handles_out is set to zero. Depth-first traversal of the object then continues to completion, closing all remaining handles in bytes.
If an unrecoverable error occurs, such as exceeding the bound of the buffer, exceeding the maximum nested complex object recursion depth, encountering invalid encoding table data, or a dangling pointer, the behavior is undefined.
On success, bytes and handles describe an encoded object ready to be sent using zx_channel_send()
.
If anything other than ZX_OK
is returned, error_msg_out will be set.
Result is…
ZX_OK
: successZX_ERR_INVALID_ARGS
:ZX_HANDLE_INVALID
FIDL_RECURSION_DEPTH
was exceeded (see wire format)This function is effectively a simple interpreter of the contents of the type. Unless the object encoding includes internal references which must be fixed up, the only work amounts to checking the object size and the ranges of data types such as enums and union tags.
Declared in system/ulib/fidl/include/lib/fidl/coding.h, defined in system/ulib/fidl/decoding.cpp.
zx_status_t fidl_decode( const fidl_type_t* type, void* bytes, uint32_t num_bytes, const zx_handle_t* handles, uint32_t num_handles, const char** error_msg_out);
Decodes and validates the object in bytes in-place by performing a depth-first traversal of the encoding data from type to fix up internal references. Patches internal pointers within bytes whose value is FIDL_ALLOC_PRESENT
to refer to the address of the out-of-line data they reference later in the buffer. Populates internal handles within bytes whose value is FIDL_HANDLE_PRESENT
to their corresponding handle taken sequentially from handles.
To prevent handle leakage, this operation ensures that either all handles in handles from handles[0] to handles[num_handles - 1] are moved into bytes in case of success or they are all closed in case of an error. After successfully decoding an object, you may use fidl_object_close_handles()
to close any handles which you did not consume from it.
The handles array is not modified by the operation.
If a recoverable error occurs, a result is returned, bytes remains in an unusable partially modified state, and all handles in handles are closed.
If an unrecoverable error occurs, such as encountering an invalid type, the behavior is undefined.
If anything other than ZX_OK
is returned, error_msg_out will be set.
Result is...
ZX_OK
: successZX_ERR_INVALID_ARGS
:FIDL_ALLOC_ABSENT
.ZX_HANDLE_INVALID
.FIDL_ALLOC_ABSENT
but has size > 0.FIDL_ALLOC_ABSENT
or FIDL_ALLOC_PRESENT
.ZX_HANDLE_INVALID
or FIDL_HANDLE_PRESENT
.FIDL_RECURSION_DEPTH
was exceeded (see wire format)This function is effectively a simple interpreter of the contents of the type. Unless the object encoding includes internal references which must be fixed up, the only work amounts to checking the object size and the ranges of data types such as enums and union tags.
zx_status_t fidl_object_close_handles( const fidl_encoding_table_t* encoding_table, const void* buf);
Releases all handles which appear in buf. Assumes that the object is valid. The contents of buf are not modified by the operation.
If buf is null, does nothing and returns success.
Result is…
zx_status_t fidl_object_count_handles( const fidl_encoding_table_t* encoding_table, const void* buf, size_t* num_handles);
Counts how many non-zero handles appear in buf. Assumes that the object is valid. The contents of buf are not modified by the operation.
If buf is null, sets num_handles to zero and returns success.
Result is...
NO_ERROR: success
ERR_INVALID_ARGS:
Introspection Functions
zx_status_t fidl_object_print( const fidl_introspection_table_t* introspection_table, const void* buf, char* output, size_t max_chars, size_t* num_chars);
Print a human-readable description of the object in buf to a zero-terminated string of up to max_chars characters (including the terminator) in output for debugging. Returns the number of characters produced in *num_chars. Assumes that the object is valid. The contents of buf are not modified by the operation.
If buf is null, sets num_chars to zero and returns success.
Result is...
NO_ERROR: success
ERR_INVALID_ARGS:
ERR_BUFFER_TOO_SMALL:
Encoding Table Declarations
Encoding tables are compact arrays of encoding entries which contain static metadata about FIDL types. This information is compiled into FIDL-based programs for the purposes of supporting efficient encoding and decoding of objects.
The encoding metadata contains:
One possible way to represent this information is as a sequence of entries which are used to drive an interpreter.
[API TBD: this is a quick sketch for flavor only]
// encoding entry typedef union fidl_encoding_entry { uint64_t value; // packed opcode and arguments fidl_encoding_entry* table; // reference to another table } fidl_encoding_entry_t; // encoding table typedef fidl_encoding_entry_t* fidl_encoding_table_t; // define a struct encoding table ##define FIDL_ENCODE_TABLE_STRUCT(obj_name, fields...) \ const fidl_encoding_table_t type##_encoding = { \ ??? stuff involving sizeof(obj_name) ???, \ ##fields, \ FIDL_ENCODE_END(), \ }; ##define FIDL_ENCODE_TABLE_UNION(obj_name, fields...) // record offsetof(obj_name, field_name) and type of field ##define FIDL_ENCODE_FIELD_ARRAY(field_name, size, element_encoding_table) ##define FIDL_ENCODE_FIELD_HANDLE(field_name, required) ##define FIDL_ENCODE_FIELD_STRING(field_name, required) ##define FIDL_ENCODE_FIELD_VECTOR(field_name, element_encoding_table, required) ##define FIDL_ENCODE_FIELD_OBJECT(field_name, referent_encoding_table, required) // mark end of encoding table ##define FIDL_ENCODE_END()
Introspection Table Declarations
Defined similarly to encoding tables but include names of methods and interfaces as well as sufficient field type information to allow for formatting of their contents.
TODO: It's possible we could generate both the encoding and introspection tables from the same set of macros, although we want to ensure that the encoding tables only contain the strict minimum information needed for validation.
Initialization Functions
void fidl_string_init( void** buf, fidl_string_t* ref, size_t size, const uint8_t* str);
Initializes the string at ref, optionally copying the contents of str into buf.
Sets ref->size to size. Sets ref->data to buf. Pads with zeros to the next multiple of 8 bytes (for alignment, not termination). Advances buf to point just beyond the padded region.
If str is non-null, copies size bytes from str to ref->data.
void fidl_vector_init( void** buf, fidl_vector_t* ref, size_t num_elements, size_t element_size, const void* data);
Initializes the vector at ref, optionally copying the contents of data into buf.
Sets ref->size to num_elements. Sets ref->data to *buf. Pads with zeros to the next multiple of 8 bytes (for alignment, not termination). Advances *buf to point just beyond the padded region.
If data is non-null, copies num_elements * element_size bytes from data to ref->data.
Macros
##define FIDL_ALIGN(x) (((x) | 7) & ~7)
Common Declarations used by Examples
The examples in this section use the following declarations.
library example; interface Animal { Say(string text, handle<event> token); Add(int32 a, int32 b) -> (int32 sum); };
// Ordinal indices for methods in Animal interface. enum { example_Animal_Say_ordinal = 1; example_Animal_Add_ordinal = 2; }; // Args for example::Animal::Say(). typedef struct example_Animal_Say_args { fidl_string_t text; zx_handle_t token; } example_Animal_Say_args_t; // Args and result example::Animal::Add(). typedef struct example_Animal_Add_args { int32_t a; int32_t b; } example_Animal_Add_args_t; typedef struct example_Animal_Add_result { int32_t sum; } example_Animal_Say_result_t;
// Encoding tables for example::Animal::Say(). FIDL_ENCODE_TABLE_STRUCT(example_Animal_Say_args, FIDL_ENCODE_FIELD_STRING(text, true), FIDL_ENCODE_FIELD_HANDLE(token, true) ); // Encoding tables for example::Animal::Add(). FIDL_ENCODE_TABLE_STRUCT(example_Animal_Add_args); FIDL_ENCODE_TABLE_STRUCT(example_Animal_Add_result);
// Introspection tables for example::Animal::Say(). FIDL_INTROSPECT_TABLE_STRUCT(example_Animal_Say_args, FIDL_INTROSPECT_FIELD_STRING(text, true), FIDL_INTROSPECT_FIELD_HANDLE(token, true) ); // Introspection tables for example::Animal::Call(). FIDL_INTROSPECT_TABLE_STRUCT(example_Animal_Add_args, FIDL_INTROSPECT_FIELD_INT32(a), FIDL_INTROSPECT_FIELD_INT32(b) ); FIDL_INTROSPECT_TABLE_STRUCT(example_Animal_Add_result, FIDL_INTROSPECT_FIELD_INT32(sum) ); // Introspection tables for example::Animal interface. FIDL_INTROSPECT_TABLE_INTERFACE(example_Animal, "example", "Animal", FIDL_INTROSPECT_METHOD("Say", Say, example_Animal_Say_args_introspection), FIDL_INTROSPECT_METHOD("Add", Add, example_Animal_Add_args_introspection, example_Animal_Add_result_introspection) );
Sending Messages
The client performs the following operations to send a message through a channel.
For especially simple messages, it may be possible to skip the encoding step altogether (or do it manually).
// Call Say("hello"). // This example could be tidied up a bit by expanding the FIDL API // for building messages to take care of more of the grunt work // or by generating helpers. zx_status_t say_hello( zx_handle_t channel, const char* text, zx_handle_t token) { assert(strlen(text) <= MAX_TEXT_SIZE); uint8_t buf[sizeof(fidl_message_header_t) + sizeof(example_animal_Say_args_t) + FIDL_ALIGN(MAX_TEXT_SIZE)]; memset(buf, 0, sizeof(buf)); void* ptr = buf; fidl_message_header_t* header = (fidl_message_header_t*)ptr; ptr = header + 1; header->transaction_id = 1; header->flags = 0; header->ordinal = example_Animal_Say_ordinal; example_Animal_Say_args_t* args = (example_Animal_Say_args_t*)ptr; ptr = request + 1; fidl_string_init(&ptr, &args->text, strlen(text), text); args->token = token; size_t num_bytes = (uint8_t*)ptr - buf; zx_handle_t handles[1]; size_t num_handles; const char* error_msg = nullptr; zx_status_t status = fidl_encode(example_Animal_Say_args_type, args, num_bytes - sizeof(fidl_message_header_t), handles, 1, &num_handles, &error_msg); if (status == ZX_OK) { status = zx_channel_write( channel, 0, msg, num_bytes, handles, num_handles); } else { FXL_LOG(WARNING) << "Failed to Say(\"hello\"): " << error_msg; } return status; }
Receiving Messages
The client performs the following operations to receive a message through a channel.
For especially simple messages, it may be possible to skip the encoding step altogether (or do it manually).
Dispatching Messages
The C language bindings do not provide any special affordances for dispatching interface method calls. The client should dispatch manually based on the interface method ordinal, such as by using a switch statement.
Introspection
The C language bindings provide a few functions to support table-based introspection of objects, assuming the tables have not been stripped out.
Print an object as a human-readable string for debugging.
Traverse fields within a structure.