Introduction
nanopb is a plain-C implementation of Protocol Buffers designed for embedded systems and memory-constrained environments. It generates small, static C structs and codec functions from .proto files, avoiding dynamic memory allocation entirely.
What nanopb Does
- Generates compact C structs and codec functions from .proto files via a protoc plugin
- Encodes and decodes messages without dynamic memory allocation, using stack or caller-provided buffers
- Supports proto2 and proto3 including nested messages, repeated fields, oneof, and extensions
- Provides a stream-based API for incremental encoding and decoding on RAM-limited devices
- Allows per-field size limits via .options files at generation time
Architecture Overview
nanopb consists of a Python-based protoc plugin and a small C runtime (pb_encode.c, pb_decode.c, pb_common.c). The generator produces .pb.h and .pb.c files with struct definitions and field descriptors. The runtime uses these descriptors for serialization with fully deterministic memory behavior. The generated code and runtime typically add only a few kilobytes to the binary.
Self-Hosting & Configuration
- Install via pip or build from source; requires Python 3 and optionally protoc
- Use .options files to set maximum string lengths and array counts for generated code
- Link the three runtime C files; no external library dependencies are required
- Use callbacks instead of static arrays for fields with unknown maximum sizes
- Integrate with CMake, Make, or Bazel using provided build system support
Key Features
- Runtime library adds roughly 2-5 KB to the compiled binary
- No calls to malloc or free during encoding or decoding
- Wire-compatible with standard Protocol Buffers in any language
- Stream-based API can process messages larger than available RAM
- Runs on bare-metal targets, FreeRTOS, Zephyr, and any C89 compiler
Comparison with Similar Tools
- protobuf-c: More complete C implementation with dynamic allocation, but uses significantly more memory and code space
- MessagePack: Schema-less binary format that is simpler to adopt, but lacks type safety and code generation
- FlatBuffers: Zero-copy deserialization for high performance, but larger code and more RAM for buffer management
- tinycbor: Compact self-describing binary format, but no structured code generation or compile-time type checking
FAQ
Q: Does nanopb support all protobuf features? A: It supports most proto2 and proto3 features. gRPC services and reflection are excluded as they target server use cases.
Q: How much code space does nanopb add? A: The runtime adds 2-5 KB. Generated codecs are proportional to proto complexity, usually a few hundred bytes per message type.
Q: Can nanopb talk to standard protobuf libraries? A: Yes, nanopb output is wire-compatible. Messages can be decoded by any standard protobuf library in Python, Go, Java, etc.
Q: Does nanopb require an OS? A: No, it runs on bare-metal targets with only a C89 compiler required.
Sources
- Repository: https://github.com/nanopb/nanopb
- Documentation: https://jpa.kapsi.fi/nanopb/