BARE is a simple binary representation for structured application data.
NOTICE: The BARE encoding is not finalized. Feedback is welcome. draft-devault-bare has been filed with the IETF as an Internet-Draft and represents the latest authoritative draft of the specification.
Here is a sample schema:
type PublicKey data<128> type Time string # ISO 8601 enum Department { ACCOUNTING ADMINISTRATION CUSTOMER_SERVICE DEVELOPMENT # Reserved for the CEO JSMITH = 99 } type Customer { name: string email: string address: Address orders: []{ orderId: i64 quantity: i32 } metadata: map[string]data } type Employee { name: string email: string address: Address department: Department hireDate: Time publicKey: optional<PublicKey> metadata: map[string]data } type Person (Customer | Employee) type Address { address: [4]string city: string state: string country: string }
Binary Application Record Encoding (BARE) is, as the name implies, a simple binary representation for structured application data.
BARE messages omit type information, and are not self-describing. The structure of a message must be established out of band, generally by prior agreement and context - for example, if a BARE message is returned from /api/user/info, it can be inferred from context that the message represents user information, and the structure of such messages is available in the documentation for this API.
A BARE message is a single value of a pre-defined type, though the type and its encoded value may be an aggregate type.
Signed integers are mapped to unsigned integers using "zig-zag" encoding: positive values x are written as 2*x + 0, negative values are written as 2*(^x) + 1; that is, negative numbers are complemented and whether to complement is encoded in bit 0.
The maximum precision of a varint is 64 bit.
A user-defined type gives a name to a built-in type, or aliases another type. This creates a distinct type, whose underlying storage is equivalent to the type it names.
The following invariants must be upheld in a BARE schema:
The use of a schema language is optional, and implementations should support decoding arbitrary BARE messages without such a document, or by defining the schema in a manner utilizing more native tools available from the language or runtime environment.
However, it may be useful to have a schema language, for use with code generation, documentation, or interoperability. A domain-specific language is provided for this purpose.
During lexical analysis, whitespace may be used to separate tokens, and is then discarded. Additionally, "#" is used for comments; if encountered, the "#" character and any subsequent characters are discarded until a LF is found. The syntax of this language is represented by the following ABNF grammar (see RFC5234):
schema = 1*user-type user-type = "type" user-type-name non-enum-type user-type /= "enum" user-type-name enum-type type = non-enum-type / enum-type non-enum-type = primitive-type / aggregate-type / user-type-name user-type-name = UPPER *(ALPHA / DIGIT) ; First letter is uppercase primitive-type = "int" / "i8" / "i16" / "i32" / "i64" primitive-type /= "uint" / "u8" / "u16" / "u32" / "u64" primitive-type /= "f32" / "f64" primitive-type /= "bool" primitive-type /= "string" primitive-type /= "data" / ("data" "<" integer ">") primitive-type /= "void" enum-type = "{" enum-values "}" enum-values = enum-value / (enum-values enum-value) enum-value = enum-value-name / (enum-value-name "=" integer) enum-value-name = UPPER *(UPPER / DIGIT / "_") aggregate-type = optional-type aggregate-type /= array-type aggregate-type /= map-type aggregate-type /= union-type aggregate-type /= struct-type optional-type = "optional" "<" type ">" array-type = "[" [integer] "]" type integer = 1*DIGIT map-type = "map" "[" type "]" type union-type = "(" union-members ")" union-members = union-member / (union-members "|" union-member) union-member = type ["=" integer] struct-type = "{" fields "}" fields = field / (fields field) field = 1*ALPHA ":" type UPPER = %x41-5A ; uppercase ASCII letters
Here is a simple example schema using this language:
type PublicKey data<128> type Time string # ISO 8601 enum Department { ACCOUNTING ADMINISTRATION CUSTOMER_SERVICE DEVELOPMENT # Reserved for the CEO JSMITH = 99 } type Customer { name: string email: string address: Address orders: []{ orderId: i64 quantity: i32 } metadata: map[string]data } type Employee { name: string email: string address: Address department: Department hireDate: Time publicKey: optional<PublicKey> metadata: map[string]data } type Person (Customer | Employee) type Address { address: [4]string city: string state: string country: string }
The names of fields and user-defined types are informational: they are not represented in BARE messages, but they may be used for code generation or to provide meaningful names for readers of the schema.
Enum values are also informational. Values without an assigned integer are assigned automatically in the order that they appear, starting from zero and incrementing for each subsequent unassigned value. If an enum value is explicitly specified, automatic assignment continues from that value plus one for subsequent enum values.
Union type members are assigned a tag in the order that they appear, starting from zero and incrementing for each subsequent type. If a tag value is explicitly specified, automatic assignment continues from that value plus one for subsequent values.
This section is informative.
The recommended approach for message versioning is with the use of union types. Adding new types to a union is backwards compatible with previous messages. For example, the following schema provides several versions of a message:
type Message (MessageV1 | MessageV2 | MessageV3) type MessageV1 { ... } type MessageV2 { ... } type MessageV3 { ... }
An updated schema which added a MessageV4 would still be able to decode versions 1, 2, and 3. However, you must make the decision to use versioning in advance. Replacing a struct type with a union type that contains the same struct is NOT backwards compatible.
If you later decide to deprecate MessageV1, you may remove it and specify the initial tag explicitly:
type Message (MessageV2 = 1 | MessageV3) type MessageV2 { ... } type MessageV3 { ... }
Implementations must take care when decoding types with an unbounded length (e.g. []int, map, data), as a malicious message can be created with an excessive length and cause a naive implementation to enable denial-of-service attacks, failed allocations, or other security faults.
This specification text is licensed with CC-BY-SA.