Canopy supports a Nanopb-backed protobuf-compatible serialization path.
Nanopb exists in Canopy because the full Google C++ protobuf runtime is not a good fit for SGX enclaves. Enclave code has a constrained runtime, different compile and link flags, and a much smaller trusted computing base. Nanopb lets Canopy keep .proto schema compatibility and protobuf wire bytes while avoiding the full protobuf C++ library inside the enclave.
Nanopb is not a new wire format. It is an alternative implementation of protobuf encoding and decoding.
Canopy still generates .proto schemas from IDL. Those schemas remain the compatibility contract. The backend choice controls which runtime is used to encode and decode those messages:
| Backend | Runtime | Intended use |
|---|---|---|
protocol_buffers |
Google C++ protobuf runtime | Host processes that want the full generated C++ protobuf API |
nanopb |
Nanopb C runtime plus Canopy C++ adapters | SGX enclaves and small-runtime builds |
The two backends should remain wire-compatible for the subset of protobuf generated by Canopy.
CANOPY_BUILD_PROTOCOL_BUFFERS=ON # Build full Google C++ protobuf support
CANOPY_BUILD_NANOPB=ON # Build Nanopb supportThe two options are independent. Enabling Nanopb does not require enabling the full Google C++ protobuf runtime, and enabling full protobuf does not imply that Nanopb code is generated. Both may be enabled when host-side code wants the full protobuf API while embedded or enclave-facing targets also need the smaller Nanopb runtime.
CANOPY_BUILD_NANOPB=ON still requires protobuf tooling at build time because Canopy emits .proto files and Nanopb generation consumes them. That does not mean enclave targets link the full protobuf runtime.
For SGX presets, the intended release-style configuration is:
CANOPY_BUILD_ENCLAVE=ON
CANOPY_BUILD_NANOPB=ON
CANOPY_BUILD_PROTOCOL_BUFFERS=OFFThis gives enclave targets protobuf-compatible serialization without linking protobuf::libprotobuf.
Host-side code in an SGX build may still enable CANOPY_BUILD_PROTOCOL_BUFFERS;
Canopy removes full protobuf from enclave compile definitions and maps
rpc::encoding::protocol_buffers to Nanopb inside enclave targets.
IDL generation accepts nanopb directly:
CanopyGenerate(
my_service
my_service/my_service.idl
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_BINARY_DIR}/generated
""
yas_binary
nanopb)When an existing target requests protocol_buffers but full protobuf support is disabled, Canopy can satisfy the protobuf-compatible request with Nanopb if CANOPY_BUILD_NANOPB=ON. This is deliberate so existing protobuf-oriented IDL targets can still build in enclave-safe configurations.
The reverse alias is also supported for ordinary non-SGX targets: if
CANOPY_BUILD_NANOPB=OFF and CANOPY_BUILD_PROTOCOL_BUFFERS=ON, an
rpc::encoding::nanopb request is routed through the full protobuf backend.
When both backends are enabled, no aliasing is applied and each encoding uses
its matching implementation.
The IDL-level protocol_buffers request is handled by CMake policy in
CanopyGenerate.cmake. The generator executable itself treats --protobuf and
--nanopb as literal flags; it no longer turns a protobuf request into an
implicit Nanopb request. This keeps full protobuf and Nanopb generation
separable while still allowing CMake to use Nanopb as the stand-in for protobuf
wire support when full protobuf is disabled.
Generated Nanopb C++ adapters expose Canopy-facing methods such as:
void nanopb_serialise(std::vector<char>& buffer) const;
void nanopb_deserialise(const std::vector<char>& buffer);Application code normally uses the generic Canopy serializer path rather than calling those methods directly.
Enclave IDL libraries compile:
- the Nanopb runtime C files with enclave flags,
- Nanopb-generated
.pb.cfiles with enclave flags, - Canopy-generated Nanopb C++ adapter code with enclave flags.
They should not link the full Google protobuf runtime. The core RPC .proto descriptors are still needed by generated Nanopb code, so enclave builds link the generated rpc_types_idl_enclave pieces when Nanopb is enabled.
Use Nanopb when:
- the code runs inside an SGX enclave,
- the deployment cannot carry the full protobuf runtime,
- a small trusted runtime matters,
- protobuf-compatible wire bytes are still required.
Use full Protocol Buffers when:
- the code runs in a normal host process,
- you need the Google C++ generated message API,
- you use protobuf reflection, JSON mapping, dynamic messages, or other full-runtime features.
Use YAS when:
- both sides are Canopy C++ and maximum native performance matters more than language-neutral schemas.