The BSDF format specification

This document applies to BSDF format VERSION = 2.2.

Purpose and features

The purpose of BSDF is to provide a data format that is ...

This has resulted in the following features:

Also see how BSDF compares to other formats.

Minimal implementation

A minimal BSDF implementation must support:

Implementations are encouraged to support:

Further, implementations can be made more powerful by supporting:

The format

Each data value is identified using a 1 byte character in the ASCII range. If this identifier is a capital letter (smaller than ASCII 95), it means that it's a value to be converted via an extension. If so, the next item is a string (see below for its encoding) representing the extension name. Next is the data itself. All words are stored in little endian format.

Encoding of size

Sizes (of e.g. lists, mappings, strings, and blobs) are encoded as follows: if the size is smaller than 251, a single byte (uint8) is used. Otherwise, the first byte is 253, and the next 8 bytes represent the size using an unsigned 64bit integer. (The bytes 254 and 255 are used to identify (closed and unclosed) streams, and 251-252 are reserved.)


Data encoded with BSDF starts with the following 6-byte header:


The value null/nil/none is identified by v (for void), and has no data.


The values false and true are identified by n for no, and y for yes, respectively. These values have no data.


Integer values come it two flavours:


Floats values follow the IEEE 754 standard, can be NaN and inf and come in two flavours:


String values are identified by s (for string), and consists of a size item (1 or 9 bytes), followed by the bytes that represents the UTF-8 encoded string.


Binary data is encoded as follows:

Note: at this moment, some implementations can write checksums, but none actually use it to validate the data. A policy w.r.t. checksums will have to be made and implementations will have to implement this.


List values consist of the identifier l (for list), followed by a size item that represents the length of the list n. After that, n values follow, which can be of any type.


Mappings, a.k.a. dictionaries or structs, consists of the identifier m (for mapping), followed by a size item that represents the length of the mapping n. After that, n items follow, each time a combination of a string (the key) and the value.


Streams allow data to be written and read in a "lazy" fashion. Implementations are not required to support streaming itself, but must be able to read data that contains (closed and unclosed) streams.

Data that is "streaming" must always be the last object in the file (except for its sub items). BSDF currently specifies that streaming is only supported for lists. It will likely also be supported for blobs.

Streams are identified by the size encoding which starts with 254 or 255, followed by an unsigned 64 bit integer. For closed streams (254), the integer represents the number of items in the stream. For unclosed streams (255) the 64 bit integer must be ignored.

Encoder implementations can thus close a stream by changing the 255 to 254 and writing the real size in the next 8 bytes. Alternatively, an implementaion can turn it into a regular encoded list (not streamed) by writing 253 instead. Note that in the latter case the list can not be read as a stream anymore.