Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ ARG PROTO_GEN_VALIDATE_VERSION=7b06248484ceeaa947e93ca2747eccf336a88ecc # v1.2.1
ARG GOOGLEAPIS_VERSION=376467058c288ad34dd7aafa892a95883e4acd0c # master
ARG PROTO_DEPS_DIR=/proto/.proto_deps

RUN apk add --no-cache bash curl git make graphviz ttf-freefont
RUN apk add --no-cache bash curl git make graphviz ttf-freefont gcc musl-dev


RUN arch="$(uname -m)" && \
Expand Down Expand Up @@ -70,3 +70,7 @@ RUN mkdir -p $(go env GOPATH)
RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@v${PROTOC_GEN_GO_VERSION} && mv /root/go/bin/protoc-gen-go /usr/local/bin/protoc-gen-go
RUN go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@${OPENAPI_VERSION} && mv /root/go/bin/protoc-gen-openapiv2 /usr/local/bin/protoc-gen-openapiv2
RUN go install github.com/envoyproxy/protoc-gen-validate@${PROTO_GEN_VALIDATE_VERSION} && mv /root/go/bin/protoc-gen-validate /usr/local/bin/protoc-gen-validate

# Install Rust toolchain (for prost-build based protobuf bindings)
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal --component clippy,rustfmt
ENV PATH="/root/.cargo/bin:${PATH}"
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
.cache/
swagger/
rust/target/
rust/proto-deps/
24 changes: 21 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ help:
@echo " proto Build protobuf files (default target)"
@echo " proto-validate-go Generate Go validation rules for protos (should be run after proto)."
@echo " swagger Generate Swagger/OpenAPI documentation"
@echo " rust Build Rust protobuf bindings"
@echo " rust-test Run Rust tests"
@echo " rust-check Run Rust clippy, fmt check"
@echo " rust-clean Clean Rust build artifacts"

proto-container:
docker build -f .devcontainer/Dockerfile --build-arg PROTO_DEPS_DIR=$(PROTO_DEPS_DIR) -t eve-api-builder .
Expand All @@ -21,17 +25,17 @@ proto-diagram:
dot ./images/devconfig.dot -Tsvg -o ./images/devconfig.svg
echo generated ./images/devconfig.*

.PHONY: proto-api-% proto proto-container proto-local swagger swagger-local
.PHONY: proto-api-% proto proto-container proto-local swagger swagger-local rust rust-test rust-clean rust-check

proto: proto-container
docker run --rm --env HOME=/tmp -v $(PWD):/src -w /src -u $$(id -u) eve-api-builder make proto-local

proto-validate-go: proto-container
docker run --rm --env HOME=/tmp -v $(PWD):/src -w /src -u $$(id -u) eve-api-builder make proto-validate-go-local

proto-local: go go-vet python proto-diagram
proto-local: go go-vet python rust proto-diagram
@echo Done building protobuf, you may want to vendor it into your packages, e.g. pkg/pillar.
@echo See ./go/README.md for more information.
@echo See ./go/README.md and ./rust/README.md for more information.

go: PROTOC_OUT_OPTS=paths=source_relative:
go: proto-api-go
Expand All @@ -41,6 +45,20 @@ go-vet:

python: proto-api-python

rust:
cd rust && PROTO_DEPS_DIR=$(PROTO_DEPS_DIR) cargo build --release
@echo Rust bindings built successfully

rust-test:
cd rust && PROTO_DEPS_DIR=$(PROTO_DEPS_DIR) cargo test

rust-clean:
cd rust && cargo clean

rust-check:
cd rust && PROTO_DEPS_DIR=$(PROTO_DEPS_DIR) cargo clippy -- -D warnings
cd rust && cargo fmt --check

proto-api-%:
rm -rf $*/*/; mkdir -p $* # building $@
protoc -I./proto --$(*)_out=$(PROTOC_OUT_OPTS)./$* \
Expand Down
26 changes: 26 additions & 0 deletions rust/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[package]
name = "eve-api"
version = "0.1.0"
edition = "2021"
description = "Rust protobuf bindings for the EVE Device API"
repository = "https://github.com/lf-edge/eve-api"
license = "Apache-2.0"
keywords = ["eve", "api", "protobuf", "iot", "edge"]
categories = ["api-bindings"]

[dependencies]
prost = "0.13"
prost-types = "0.13"
bytes = "1"
thiserror = "2"
base64 = "0.22"

[build-dependencies]
prost-build = "0.13"

[lints.clippy]
# Allow warnings from generated protobuf code
empty_docs = "allow"
doc_lazy_continuation = "allow"
doc_overindented_list_items = "allow"
large_enum_variant = "allow"
105 changes: 105 additions & 0 deletions rust/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# EVE API — Rust Bindings

Rust protobuf bindings for the [EVE Device API v2](https://github.com/lf-edge/eve-api),
auto-generated at build time from `.proto` definitions using
[prost](https://github.com/tokio-rs/prost).

This is a **pure data types** crate — it contains only generated protobuf message
structs and enums. No business logic, no crypto, no HTTP client. Higher-level
functionality belongs in companion crates (e.g., `crypto-provider`, `eve-api-client`).

## Modules

Each module corresponds to a protobuf package from the EVE API:

| Module | Protobuf package | Key types |
|---|---|---|
| `common` | `org.lfedge.eve.common` | `HashAlgorithm`, `CipherBlock`, `CipherContext` |
| `auth` | `org.lfedge.eve.auth` | `AuthBody`, `AuthContainer` |
| `register` | `org.lfedge.eve.register` | `ZRegisterMsg` |
| `certs` | `org.lfedge.eve.certs` | `ZControllerCert`, `ZCert` |
| `config` | `org.lfedge.eve.config` | `EdgeDevConfig`, `ConfigRequest`, `ConfigResponse` |
| `attest` | `org.lfedge.eve.attest` | `ZAttestReq`, `ZAttestResp` |
| `info` | `org.lfedge.eve.info` | `ZInfoMsg` |
| `metrics` | `org.lfedge.eve.metrics` | `ZMetricMsg` |
| `logs` | `org.lfedge.eve.logs` | `LogBundle`, `LogEntry` |
| `flowlog` | `org.lfedge.eve.flowlog` | `FlowMessage` |
| `uuid` | `org.lfedge.eve.uuid` | `UuidRequest`, `UuidResponse` |
| `hardwarehealth` | `org.lfedge.eve.hardwarehealth` | `ZHardwareHealth` |
| `profile` | `org.lfedge.eve.profile` | `LocalProfile` |
| `nestedappinstancemetrics` | `org.lfedge.eve.nestedappinstancemetrics` | nested app inventory, logs, metrics |

## Usage

```rust
use eve_api::register::ZRegisterMsg;
use prost::Message;

let msg = ZRegisterMsg {
pem_cert: bytes::Bytes::from_static(b"my-cert"),
serial: "DEVICE-001".to_string(),
soft_serial: "SOFT-001".to_string(),
..Default::default()
};

// Serialize to protobuf wire format
let encoded = msg.encode_to_vec();

// Deserialize
let decoded = ZRegisterMsg::decode(encoded.as_slice()).unwrap();
assert_eq!(decoded.serial, "DEVICE-001");
```

## Convenience re-exports

Commonly used types are re-exported from the crate root:

```rust
use eve_api::{AuthBody, AuthContainer, HashAlgorithm, EdgeDevConfig, ZRegisterMsg};
```

Constants:

```rust
assert_eq!(eve_api::API_VERSION, "v2");
assert_eq!(eve_api::API_PATH_PREFIX, "/api/v2/edgedevice");
```

## How bindings are generated

There are no pre-generated `.rs` files. The `build.rs` script compiles all
`.proto` files from `../proto/` via `prost-build` during `cargo build`. The
generated code lands in `$OUT_DIR` and is included via `include!()` macros
in `src/lib.rs`.

If upstream adds a new `.proto` file or package, update `build.rs` and
`src/lib.rs` accordingly.

## Building

```sh
cargo build # generates and compiles bindings
cargo test # roundtrip and smoke tests
cargo clippy # lint (generated code warnings are suppressed in Cargo.toml)
```

Or from the repository root:

```sh
make rust # build release
make rust-test # run tests
make rust-check # clippy + fmt check
```

## Dependencies

| Crate | Purpose |
|---|---|
| `prost` | Protobuf runtime (derive `Message`) |
| `prost-types` | Well-known protobuf types |
| `bytes` | Zero-copy `Bytes` for binary fields |
| `prost-build` | Build-time proto compilation |

## License

Apache-2.0 — see [LICENSE](../LICENSE).
135 changes: 135 additions & 0 deletions rust/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
use prost_build::Config;
use std::path::Path;
use std::process::Command;

fn main() -> Result<(), Box<dyn std::error::Error>> {
let proto_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("../proto");

if !proto_dir.exists() {
return Err(format!(
"Proto directory not found at {}. Make sure this crate lives inside the eve-api repo.",
proto_dir.display()
)
.into());
}

let proto_dir_str = proto_dir.to_str().unwrap();

// Extra include path for third-party proto deps (e.g., validate/validate.proto
// from protoc-gen-validate, imported by register.proto).
//
// Resolution order:
// 1. PROTO_DEPS_DIR env var (set inside devcontainer → /proto/.proto_deps)
// 2. Local proto-deps/ directory (auto-fetched for local dev)
let proto_deps_dir = if let Ok(d) = std::env::var("PROTO_DEPS_DIR") {
Path::new(&d).join("protoc-gen-validate").to_path_buf()
} else {
let local = Path::new(env!("CARGO_MANIFEST_DIR")).join("proto-deps");
let validate = local.join("validate").join("validate.proto");
if !validate.exists() {
eprintln!("Fetching validate.proto for local build...");
std::fs::create_dir_all(local.join("validate")).expect("create proto-deps/validate/");
let status = Command::new("curl")
.args([
"-sSfL",
"-o",
validate.to_str().unwrap(),
"https://raw.githubusercontent.com/bufbuild/protoc-gen-validate/v1.2.1/validate/validate.proto",
])
.status()
.expect("failed to run curl — install curl or use the devcontainer");
if !status.success() {
panic!("failed to download validate.proto (exit {status})");
}
}
local
};
let proto_deps_str = proto_deps_dir.to_str().unwrap().to_owned();

let mut config = Config::new();

// Use Bytes for all bytes fields for zero-copy efficiency
config.bytes(["."]);

// AuthContainer contains large binary payloads, but we still need Debug
// for prost::Message trait bound. We let prost derive Debug normally and
// accept the verbose output — users can use the {:?} alternate form or
// wrap in a custom Display if needed.

// Proto files grouped by package, ordered so dependencies come first.
//
// The include path is the proto root directory; file paths passed to
// compile_protos are relative to that root.
let proto_files: Vec<String> = [
// --- evecommon (no deps on other eve protos) ---
"evecommon/evecommon.proto",
"evecommon/devmodelcommon.proto",
"evecommon/acipherinfo.proto",
"evecommon/netcmn.proto",
// --- eveuuid ---
"eveuuid/eveuuid.proto",
// --- auth (depends on evecommon) ---
"auth/auth.proto",
// --- certs (depends on evecommon) ---
"certs/certs.proto",
// --- register ---
"register/register.proto",
// --- attest (depends on certs) ---
"attest/attest.proto",
// --- config (depends on evecommon, certs, auth) ---
"config/devcommon.proto",
"config/storage.proto",
"config/vm.proto",
"config/fw.proto",
"config/scep.proto",
"config/netconfig.proto",
"config/netinst.proto",
"config/baseosconfig.proto",
"config/appconfig.proto",
"config/edgeview.proto",
"config/edge_node_cluster.proto",
"config/patch_envelope.proto",
"config/devmodel.proto",
"config/compound_devconfig.proto",
"config/devconfig.proto",
// --- info (depends on evecommon, config) ---
"info/cert.proto",
"info/hardware.proto",
"info/pnac.proto",
"info/ntpsources.proto",
"info/patch_envelope.proto",
"info/edge_node_cluster.proto",
"info/info.proto",
// --- metrics (depends on evecommon) ---
"metrics/nestedappruntimemetrics.proto",
"metrics/metrics.proto",
// --- logs ---
"logs/log.proto",
// --- flowlog ---
"flowlog/flowlog.proto",
// --- hardwarehealth ---
"hardwarehealth/hardware_health.proto",
// --- profile (depends on info, metrics, config) ---
"profile/local_profile.proto",
"profile/network.proto",
// --- proxy (depends on evecommon) ---
"proxy/scep.proto",
// --- nestedappinstancemetrics ---
"nestedappinstancemetrics/nestedappinstanceinventory.proto",
"nestedappinstancemetrics/nestedappinstancelog.proto",
"nestedappinstancemetrics/nestedappinstancemetrics.proto",
]
.iter()
.map(|f| format!("{proto_dir_str}/{f}"))
.collect();

config.compile_protos(&proto_files, &[proto_dir_str, &proto_deps_str])?;

// Rerun if any proto file changes
println!("cargo:rerun-if-changed={proto_dir_str}");
println!("cargo:rerun-if-changed={proto_deps_str}");
println!("cargo:rerun-if-env-changed=PROTO_DEPS_DIR");
println!("cargo:rerun-if-changed=build.rs");

Ok(())
}
Loading