4. Protocol Buffers to DDS-XTYPES Mapping
Every .proto file passed to the idl4 plugin will be converted into an .idl file containing
DDS-XTYPES data types equivalent to the Protocol Buffers ones passed as input.
The generated IDL4 files will contain type definitions for all Protocol Buffers messages, enumerations, and other constructs which can be mapped to IDL4 constructs.
The definitions will be guarded by a C preprocessor guard, to ensure that the generated file can be included by multiple files without redefinition errors.
Protocol Buffers |
DDS-XTYPES/IDL4 |
|---|---|
|
|
message MyMessage {
}
|
#ifndef message_proto_IDL4_
#define message_proto_IDL4_
@mutable
struct MyMessage {
};
#endif // message_proto_IDL4_
|
|
|
package myapp;
message MyMessage {
}
|
#ifndef myapp_message_proto_IDL4_
#define myapp_message_proto_IDL4_
module myapp {
@mutable
struct MyMessage {
};
}; // module myapp
#endif // message_proto_IDL4_
|
4.1. User Types
Protocol Buffers supports two types of user-defined types:
4.1.1. Messages
Protocol Buffers messages are mapped to IDL4 struct’s, with @mutable extensibility.
The use of the @mutable extensibility allows modifications to the types
without breaking communication, as well as declaration of @optional members.
This makes the XCDR serialization of these types behave similarly to their Protocol Buffers
serialization.
The extensibility of the mapped IDL4 types can be changed with option .omg.dds.type.extensibility.
IDL4 does not support nested type declarations, therefore Protocol Buffers messages declared inside other messages, are mapped to top-level IDL4 types using a special naming convention and dedicated annotations.
The name of the IDL4 type associated with a nested message will encode the names of all containing types, concatenated by underscores. This naming convention matches the one followed by Protocol Buffers in several programming languages (e.g. C++) when deriving the names of types associated with nested messages.
Nested types are marked as @nested, to prevent them from being used as top-level types in
DDS topics. This choice is made to better match the visibility of nested types in Protocol Buffers, which
is limited to the .proto file where they are declared.
Nested types are also marked with @containing_type, to further encode their relationship to
the containing type.
Protocol Buffers |
DDS-XTYPES/IDL4 |
|---|---|
message MyMessage { }
|
@mutable
struct MyMessage { };
|
message MyMessage {
message NestedMessage { }
}
|
@nested
@containing_type("MyMessage")
@mutable
struct MyMessage_NestedMessage {
};
|
4.1.2. Enumerations
Protocol Buffers enumerations are mapped to IDL4 enum types.
The value of each enumeration literal is propagated to IDL4 using the @value annotation.
The default literal, which Protocol Buffers typically defines as the first value, is always
marked in IDL4 with @default_literal, for greater robustness.
The literals are exposed as top-level symbols without any prefix, unless the enumeration is nested inside a message.
Nested enumerations follow the same naming conventions as nested messages, with the only difference being that enumeration literals are also prefixed with the nested type’s name.
Similar to the handling of messages, this naming choice matches how Protocol Buffers “mangles” the names of nested enumerations when translating them to programming languages such as C++.
Protocol Buffers |
DDS-XTYPES/IDL4 |
|---|---|
enum MyEnum {
HELLO = 0;
WORLD = 1;
}
|
enum MyEnum {
@value(0)
@default_literal
HELLO,
@value(1)
WORLD
};
|
message MyMessage {
enum NestedEnum {
HELLO = 0;
WORLD = 1;
}
}
|
@containing_type("MyMessage")
enum MyMessage_NestedEnum {
@value(0)
@default_literal
MyMessage_NestedEnum_HELLO,
@value(1)
MyMessage_NestedEnum_WORLD
};
|
4.2. Primitive Types
All Protocol Buffers primitive (or “scalar”) types are mapped to their IDL4 counterparts, which results in very similar representations in both Protocol Buffers, and the many other “language bindings” supported by Connext.
The only considerable deviation is with the Protocol Buffers type bytes, which is mapped to a sequence
of octests in IDL4 (i.e. sequence<octet>), clearly not a primitive. bytes is typically represented
as a string by Protocol Buffers, which may cause some confusion when accessing these values
from different language bindings. For example, in C++, the bytes type will be mapped to:
std::string, when using the Protocol Buffers/C++ API.std::vector<uint8_t>, when using the standard DDS C++ API.
Protocol Buffers |
DDS-XTYPES/IDL4 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4.3. Collections
Protocol Buffers offers two ways to represent collections of data:
4.3.1. Repeated Fields
Protocol Buffers repeated fields are mapped to unbounded IDL4 sequences.
The corresponding IDL4 member may be marked as @optional by
using option .omg.dds.member.optional.
When a message includes a repeated field of type bytes, the
generated IDL4 will include an alias for sequence<octet>
scoped within the message type. The alias will be used in the
definition of the associated struct member, because IDL4
does not allow defining sequences of “anonymous” sequences
(i.e. sequence<sequence<T>> is not valid IDL4).
Protocol Buffers |
DDS-XTYPES/IDL4 |
|---|---|
repeated TYPE my_field ... ;
|
sequence< TYPE > my_field;
|
message MyMessage {
repeated bytes my_field = 1;
}
|
typedef sequence<octet> MyMessage_OctetSeq;
struct MyMessage {
@id(1)
sequence<MyMessage_OctetSeq> my_field;
};
|
4.3.2. Map Fields
Protocol Buffers map fields are mapped to IDL4 sequences of key and value pairs represented
using an automatically-generated “MapPair” struct type.
A “MapPair” type will be defined for each pair of key and value types used by map fields
in a Protocol Buffers message, following the naming scheme <message>_MapPair_<key-type>_<value-type>.
The same type will be reused by all map fields in a message which share the same key and value types,
The “MapPair” struct types are marked with @map_pair to indicate their use, and with
@containing_type to trace their relationship to the containing message type.
The @nested annotation is also used to prevent their use as top-level types for DDS topics.
The members associated with a map field will be annotated with @map to
indicate that they should be mapped to map constructs in those language bindings
that support them.
Protocol Buffers |
DDS-XTYPES/IDL4 |
|---|---|
message MyMessage {
map<K, V> my_field = 1;
}
|
@nested
@final
@map_pair
@containing_type("MyMessage")
struct MyMessage_MapPair_K_V {
K key;
V value;
};
struct MyMessage {
@id(1)
@map
sequence< MyMessage_MapPair_K_V >
my_field;
};
|
Warning
The mapping for map fields will be updated to the native IDL4 map type
once support for this construct has been introduced by a future release of Connext.
The @map and @map_pair annotations are a temporary solutition to allow
the Protocol Buffers/C++ langauge binding to support map fields. The annotations will be
ignored by all other language bindings, where maps must be accessed as linear
collections, rather than as associative containers.
4.4. Packages
Protocol Buffers packages are mapped to IDL4 module’s.
The name of the Protocol Buffers package is split into segments by the dot (.) character,
and each segment is mapped to a nested IDL4 module.
Qualified Protocol Buffers package names can be converted to IDL4 module names by replacing
dots (.) with double colons (::).
The name of the Protocol Buffers package will also be used as part of the C preprocesor guard used to wrap all definitions in the generated IDL4 file.
Protocol Buffers |
DDS-XTYPES/IDL4 |
|---|---|
package my.messages.package;
// Type definitions...
|
#ifndef my_messages_package_proto_IDL4_
#define my_messages_package_proto_IDL4_
module my {
module messages {
module package {
// Type definitions...
}; // module package
}; // module messages
}; // module my
#endif // my_messages_package_proto_IDL4_
|
4.5. Imported Files
Each import statement in a Protocol Buffers file will be converted to an #include statement
in the generated IDL4 file, using the same path, but with the .idl extension instead of .proto.
All imported .proto files must be independently converted to .idl
for the generated #include statements to become valid.
Any “built-in” Protocol Buffers file imported by a .proto file (e.g. google/protobuf/timestamp/proto)
will also need to be explicitly converted to IDL4.
Typically, Protocol Buffers applications do not need to generate artifacts from these files, because the associated code is already part of the Protocol Buffers binary distribution.
Protocol Buffers Extension does not include a pre-generated IDL4 version of the “built-in” Protocol Buffers files in order to allow users to precisely match the interfaces included in their preferred version of Protocol Buffers.
Protocol Buffers |
DDS-XTYPES/IDL4 |
|---|---|
import "another.proto";
|
#include "another.idl"
|
4.6. Field Presence
To quote the Protocol Buffers documentation about Field Presence:
Field presence is the notion of whether a protobuf field has a value.
There are two different manifestations of presence for protobufs:
implicit presence, where the generated message API stores field values (only).
explicit presence, where the API also stores whether or not a field has been set.
Based on whether a field has “implicit” or “explicit” presence, the Protocol Buffers compiler will generate a different API for the message type:
Fields with explicit presence have an associated “has” method, which can be used to check whether the field has been set.
Fields with implicit presence do not have an associated “has” method, and their value can only be checked against the default value for their type (or the default value assigned via options).
Protocol Buffers Extension relies on this classification to determine whether the field should be mapped to an @optional member in IDL4, or not.
Every field with a “has” method (i.e. which explicit presence), which is not marked as “required”, will be mapped to
an @optional member in IDL4.
Refer to the Protocol Buffers documentation about Field Presence for a detailed summary of which fields have explicit presence, which changes depending on the syntax/edition used.
It is possible to override the default mapping of the @optional annotation using option .omg.dds.member.optional.
4.6.1. Required Fields
The proto2 syntax allows the declaration of required fields. This is also possible when using
the edition = "2023" syntax, by using the features.field_presence = LEGACY_REQUIRED option.
A field marked as “required” must be present in every serialization of the message. As such, Protocol Buffers Extension
maps “required” fields to members of IDL4 struct’s without the @optional annotation.
File Syntax |
Protocol Buffers |
DDS-XTYPES/IDL4 |
|---|---|---|
|
required TYPE my_field ... ;
|
TYPE my_field;
|
|
N/A |
N/A |
|
TYPE my_field ... [
features.field_presence = LEGACY_REQUIRED
];
|
TYPE my_field;
|
4.6.2. Optional Fields
All Protocol Buffers syntaxes allow the declaration of “optional” fields, which are singular values that may or may not appear in the network serialization of a message.
proto2 and proto3 provide the optional keyword to denote such fields,
while edition = "2023" automatically marks every singular field as “optional”.
With edition = "2023", fields can be futher controlled using
the features.field_presence = EXPLICIT option.
Any field with “optional” semantics will be mapped to an IDL4 member with the @optional annotation.
File Syntax |
Protocol Buffers |
DDS-XTYPES/IDL4 |
|---|---|---|
|
optional TYPE my_field ... ;
|
@optional
TYPE my_field;
|
|
optional TYPE my_field ... ;
MESSAGE_TYPE my_msg_field;
|
@optional
TYPE my_field;
@optional
MESSAGE_TYPE my_msg_field;
|
|
TYPE my_field ... ;
TYPE my_field ... [
features.field_presence = EXPLICIT
];
|
@optional
TYPE my_field;
@optional
TYPE my_field;
|
4.6.3. Implicit Fields
proto3 introduced the concept of implicit presence, by not providing “hazzer” methods for any
singular field of a non-message type, which is not explicitly marked as optional.
The same implicit presence can be achieved in edition = "2023" by using the
features.field_presence = IMPLICIT option.
Beside not being marked as @optional, IDL4 members derived from fields with implicit presence will
be annotated with @field_presence(implicit) to encode this peculiar API choice in the IDL4 data model.
Warning
The use of implicit presence is discouraged by Protocol Buffers.
File Syntax |
Protocol Buffers |
DDS-XTYPES/IDL4 |
|---|---|---|
|
N/A |
N/A |
|
NON_MESSAGE_TYPE my_field ... ;
|
@field_presence(implicit)
NON_MESSAGE_TYPE my_field;
|
|
NON_MESSAGE_TYPE my_field ... [
features.field_presence = IMPLICIT
];
|
@field_presence(implicit)
NON_MESSAGE_TYPE my_field;
|
4.6.4. Field Presence Examples
Example Element |
Protocol Buffers |
DDS-XTYPES/IDL4 |
|---|---|---|
Explicitly mark any field as |
TYPE my_field ... [
.omg.dds.optional = true
];
|
@optional
TYPE my_field;
|
Prevent default mapping to |
optional TYPE my_field ... [
.omg.dds.optional = false
];
|
TYPE my_field;
|
Mark a |
repeated TYPE my_field ... [
.omg.dds.optional = true
];
|
@optional
sequence< TYPE > my_field;
|
Mark a |
map<K, V> my_field ... [
.omg.dds.optional = true
];
|
@map
@optional
sequence<
MyMessage_MapPair_K_V
> my_field;
|
4.7. OneOf Fields
Protocol Buffers provides the oneof construct to group multiple optional fields together,
allowing only one of them to be set at a time.
Every field that is part of a oneof will be treated as an “optional” field of the message.
The associated IDL4 members will be annotated with @optional, and with
@oneof to encode the name of the oneof group.
The @oneof annotation is most useful when the types are used with Protocol Buffers/C++ language
binding. Applications using the types with other language bindings must enforce the “oneof” semantics
by manually setting/resetting fields in the same group.
Protocol Buffers |
DDS-XTYPES/IDL4 |
|---|---|
message MyMessage {
// other fields...
oneof oneof_field {
A oneof_a ...;
B oneof_b ...;
C oneof_c ...;
}
}
|
@mutable
struct MyMessage {
// other fields...
@optional
@oneof("oneof_field")
A oneof_a;
@optional
@oneof("oneof_field")
B oneof_b;
@optional
@oneof("oneof_field")
C oneof_c;
};
|
4.8. Field Groups
Field groups are a Protocol Buffers feature that allows grouping of fields within a message without the need to declare another dedicated message.
In IDL4, each field group is mapped to a nested struct associated
with the declaring message type.
Warning
Field groups are deprecated by Protocol Buffers, and they are only available when using the older proto2 syntax.
File Syntax |
Protocol Buffers |
DDS-XTYPES/IDL4 |
|---|---|---|
|
message MyMessage {
required group my_req_group = 1 { }
optional group my_opt_group = 2 { }
repeated group my_rep_group = 3 { }
}
|
@nested
@containing_type("MyMessage")
@mutable
struct MyMessage_MyReqGroup { };
@nested
@containing_type("MyMessage")
@mutable
struct MyMessage_MyOptGroup { };
@nested
@containing_type("MyMessage")
@mutable
struct MyMessage_MyRepGroup { };
struct MyMessage {
@id(1)
MyMessage_MyReqGroup my_req_group;
@id(2)
@optional
MyMessage_MyOptGroup my_opt_group;
@id(3)
sequence<MyMessage_MyRepGroup> my_rep_group;
};
|
|
N/A |
N/A |
|
N/A |
N/A |
4.9. Unsupported Features
Message extensions.
Self-referencing messages.