From 765d724304fcf1d8f5c8e28f50cd96eca9a03bf5 Mon Sep 17 00:00:00 2001 From: Johannes Schrimpf Date: Wed, 1 Jul 2026 18:06:50 +0200 Subject: [PATCH 1/3] Add Rust protobuf binding Add a Rust crate that compiles the proto definitions at build time using prost + protox (no protoc required), exposing the generated types via rust/src/lib.rs. - rust/build.rs: build-time codegen with protox - rust/tests/roundtrip.rs: round-trip and WKT integration tests - README.md, CLAUDE.md: document the Rust crate - .editorconfig: Rust override --- .editorconfig | 5 +- CLAUDE.md | 9 +- README.md | 18 +- rust/.gitignore | 1 + rust/Cargo.lock | 524 ++++++++++++++++++++++++++++++++++++++++ rust/Cargo.toml | 18 ++ rust/build.rs | 17 ++ rust/src/lib.rs | 16 ++ rust/tests/roundtrip.rs | 38 +++ 9 files changed, 643 insertions(+), 3 deletions(-) create mode 100644 rust/.gitignore create mode 100644 rust/Cargo.lock create mode 100644 rust/Cargo.toml create mode 100644 rust/build.rs create mode 100644 rust/src/lib.rs create mode 100644 rust/tests/roundtrip.rs diff --git a/.editorconfig b/.editorconfig index c94a3496..53eb361a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,4 +1,7 @@ [*] indent_style = space indent_size = 2 -trim_trailing_whitespace = true \ No newline at end of file +trim_trailing_whitespace = true + +[*.rs] +indent_size = 4 \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index 3f3ae27d..50b698a4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project Overview -Protocol definition repository for Blueye Robotics underwater drones. The core definitions live in `protobuf_definitions/*.proto` (proto3 syntax) and are compiled into language-specific libraries for C++, Python, TypeScript, and C#/.NET. +Protocol definition repository for Blueye Robotics underwater drones. The core definitions live in `protobuf_definitions/*.proto` (proto3 syntax) and are compiled into language-specific libraries for C++, Python, TypeScript, C#/.NET, and Rust. Published packages: - **npm**: `@blueyerobotics/protocol-definitions` @@ -39,6 +39,13 @@ pnpm run build dotnet pack Blueye.Protocol.Protobuf.csproj -c Release -o out ``` +### Rust +``` +cargo build --manifest-path rust/Cargo.toml +cargo test --manifest-path rust/Cargo.toml +``` +Code is generated at build time by `rust/build.rs` (prost + protox, no protoc needed). + ### Linting ``` # Via Docker: diff --git a/README.md b/README.md index dc5bcd4f..3a3da4f6 100644 --- a/README.md +++ b/README.md @@ -12,10 +12,26 @@ Drones running a Blunux version \< 3.0 will need to use the legacy protocol. [bl Version 3 of the Blueye communication protocol is based on [Protocol Buffers](https://developers.google.com/protocol-buffers). -The protobuf definitions are compiled to language specific libraries, and are available as a [NuGet package](https://github.com/BluEye-Robotics/ProtocolDefinitions/packages/1239508), [Python package](https://pypi.org/project/blueye.protocol/) and a [npm package](https://www.npmjs.com/package/@blueyerobotics/protocol-definitions). +The protobuf definitions are compiled to language specific libraries, and are available as a [NuGet package](https://github.com/BluEye-Robotics/ProtocolDefinitions/packages/1239508), [Python package](https://pypi.org/project/blueye.protocol/) and a [npm package](https://www.npmjs.com/package/@blueyerobotics/protocol-definitions); a Rust crate is available as a git dependency (see below). Automatically generated documentation for the protocol format can be found [here](https://blueyebuildserver.blob.core.windows.net/protocoldefinitions/docs/protocol.html). +### Rust + +The `rust/` directory contains the `blueye-protocol` crate. Code is generated at build time from `protobuf_definitions/*.proto` using [prost](https://crates.io/crates/prost) and [protox](https://crates.io/crates/protox) — no `protoc` installation required. + +```sh +cargo build --manifest-path rust/Cargo.toml +cargo test --manifest-path rust/Cargo.toml +``` + +Use it from another project as a git dependency: + +```toml +[dependencies] +blueye-protocol = { git = "https://github.com/BluEye-Robotics/ProtocolDefinitions" } +``` + ### Installation #### OS X diff --git a/rust/.gitignore b/rust/.gitignore new file mode 100644 index 00000000..ea8c4bf7 --- /dev/null +++ b/rust/.gitignore @@ -0,0 +1 @@ +/target diff --git a/rust/Cargo.lock b/rust/Cargo.lock new file mode 100644 index 00000000..c63fbb8c --- /dev/null +++ b/rust/Cargo.lock @@ -0,0 +1,524 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "beef" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" + +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + +[[package]] +name = "blueye-protocol" +version = "0.1.0" +dependencies = [ + "prost", + "prost-build", + "prost-types", + "protox", +] + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "log" +version = "0.4.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" + +[[package]] +name = "logos" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff472f899b4ec2d99161c51f60ff7075eeb3097069a36050d8037a6325eb8154" +dependencies = [ + "logos-derive 0.15.1", +] + +[[package]] +name = "logos" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2c55a318a87600ea870ff8c2012148b44bf18b74fad48d0f835c38c7d07c5f" +dependencies = [ + "logos-derive 0.16.1", +] + +[[package]] +name = "logos-codegen" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "192a3a2b90b0c05b27a0b2c43eecdb7c415e29243acc3f89cc8247a5b693045c" +dependencies = [ + "beef", + "fnv", + "lazy_static", + "proc-macro2", + "quote", + "regex-syntax", + "rustc_version", + "syn", +] + +[[package]] +name = "logos-codegen" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58b3ffaa284e1350d017a57d04ada118c4583cf260c8fb01e0fe28a2e9cf8970" +dependencies = [ + "fnv", + "proc-macro2", + "quote", + "regex-automata", + "regex-syntax", + "syn", +] + +[[package]] +name = "logos-derive" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "605d9697bcd5ef3a42d38efc51541aa3d6a4a25f7ab6d1ed0da5ac632a26b470" +dependencies = [ + "logos-codegen 0.15.1", +] + +[[package]] +name = "logos-derive" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d3a9855747c17eaf4383823f135220716ab49bea5fbea7dd42cc9a92f8aa31" +dependencies = [ + "logos-codegen 0.16.1", +] + +[[package]] +name = "memchr" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" + +[[package]] +name = "miette" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f98efec8807c63c752b5bd61f862c165c115b0a35685bdcfd9238c7aeb592b7" +dependencies = [ + "cfg-if", + "miette-derive", + "unicode-width", +] + +[[package]] +name = "miette-derive" +version = "7.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset", + "hashbrown 0.15.5", + "indexmap", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03da047801ff44bb6a4d407d4860c05fd70bb81714e6b2f3812603d5b145b042" +dependencies = [ + "heck", + "itertools", + "log", + "multimap", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-reflect" +version = "0.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "590aa145fee8f7a26b5a6055365e7c5e89a5c1caae9869de76ec0ee73181a2f9" +dependencies = [ + "logos 0.16.1", + "miette", + "prost", + "prost-types", +] + +[[package]] +name = "prost-types" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f94967dc7688f3054c7fac87473ffae4cc4c3904800e2d9f5b857246d8963b0a" +dependencies = [ + "prost", +] + +[[package]] +name = "protox" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f25a07a73c6717f0b9bbbd685918f5df9815f7efba450b83d9c9dea41f0e3a1" +dependencies = [ + "bytes", + "miette", + "prost", + "prost-reflect", + "prost-types", + "protox-parse", + "thiserror", +] + +[[package]] +name = "protox-parse" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "072eee358134396a4643dff81cfff1c255c9fbd3fb296be14bdb6a26f9156366" +dependencies = [ + "logos 0.15.1", + "miette", + "prost-types", + "thiserror", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "once_cell", + "rustix", + "windows-sys", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] diff --git a/rust/Cargo.toml b/rust/Cargo.toml new file mode 100644 index 00000000..020b03c1 --- /dev/null +++ b/rust/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "blueye-protocol" +version = "0.1.0" +edition = "2021" +license = "LGPL-3.0-only" +description = "Protocol definitions for Blueye Robotics underwater drones" +repository = "https://github.com/BluEye-Robotics/ProtocolDefinitions" + +[lib] +name = "blueye_protocol" + +[dependencies] +prost = "0.14" +prost-types = "0.14" + +[build-dependencies] +prost-build = "0.14" +protox = "0.9" diff --git a/rust/build.rs b/rust/build.rs new file mode 100644 index 00000000..be2acdab --- /dev/null +++ b/rust/build.rs @@ -0,0 +1,17 @@ +use std::path::PathBuf; + +fn main() -> Result<(), Box> { + let proto_dir = PathBuf::from("../protobuf_definitions"); + println!("cargo:rerun-if-changed=../protobuf_definitions"); + + let mut protos: Vec = std::fs::read_dir(&proto_dir) + .map_err(|e| format!("failed to read {}: {e}", proto_dir.display()))? + .filter_map(|entry| entry.ok().map(|e| e.path())) + .filter(|path| path.extension().is_some_and(|ext| ext == "proto")) + .collect(); + protos.sort(); + + let file_descriptor_set = protox::compile(&protos, [&proto_dir])?; + prost_build::Config::new().compile_fds(file_descriptor_set)?; + Ok(()) +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs new file mode 100644 index 00000000..a4b98566 --- /dev/null +++ b/rust/src/lib.rs @@ -0,0 +1,16 @@ +//! Rust bindings for the Blueye Robotics protocol definitions. +//! +//! Generated at build time from the repository's `protobuf_definitions/*.proto` +//! files via [`prost`]. All messages live in the `blueye.protocol` proto +//! package and are re-exported flat from the crate root. + +mod generated { + include!(concat!(env!("OUT_DIR"), "/blueye.protocol.rs")); +} + +pub use generated::*; + +/// Re-export of the [`prost::Message`] trait for encoding/decoding. +pub use prost::Message; +/// Re-export of well-known types (`Timestamp`, `Duration`, `Any`). +pub use prost_types; diff --git a/rust/tests/roundtrip.rs b/rust/tests/roundtrip.rs new file mode 100644 index 00000000..d64fc6d8 --- /dev/null +++ b/rust/tests/roundtrip.rs @@ -0,0 +1,38 @@ +use blueye_protocol::{Attitude, Message, SystemTime}; + +#[test] +fn attitude_roundtrip() { + let original = Attitude { + roll: 1.5, + pitch: -10.25, + yaw: 179.0, + }; + + let bytes = original.encode_to_vec(); + let decoded = Attitude::decode(bytes.as_slice()).expect("decode failed"); + + assert_eq!(original, decoded); +} + +#[test] +fn timestamp_roundtrip() { + let original = SystemTime { + unix_timestamp: Some(blueye_protocol::prost_types::Timestamp { + seconds: 1_700_000_000, + nanos: 123_456_789, + }), + }; + + let bytes = original.encode_to_vec(); + let decoded = SystemTime::decode(bytes.as_slice()).expect("decode failed"); + + assert_eq!(original, decoded); +} + +#[test] +fn decode_rejects_garbage() { + // Tag 0x08 claims field 1 is varint-encoded, but Attitude.roll is a + // float (fixed32) — wire-type mismatch must error, not panic. + let garbage: &[u8] = &[0x08]; + assert!(Attitude::decode(garbage).is_err()); +} From ac4640eec934db40857837b922b3b52bbb863b2e Mon Sep 17 00:00:00 2001 From: Johannes Schrimpf Date: Wed, 1 Jul 2026 18:36:23 +0200 Subject: [PATCH 2/3] Watch each .proto file in build.rs for rebuild triggering Emit cargo:rerun-if-changed per .proto file so edits to an existing proto retrigger codegen. Directory mtime alone doesn't change when a file inside it is edited, so generated code could go stale. The directory watch is kept so added/removed files still retrigger. Co-Authored-By: Claude Opus 4.8 (1M context) --- rust/build.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rust/build.rs b/rust/build.rs index be2acdab..2001d129 100644 --- a/rust/build.rs +++ b/rust/build.rs @@ -2,6 +2,7 @@ use std::path::PathBuf; fn main() -> Result<(), Box> { let proto_dir = PathBuf::from("../protobuf_definitions"); + // Watch the directory so added/removed .proto files retrigger the build. println!("cargo:rerun-if-changed=../protobuf_definitions"); let mut protos: Vec = std::fs::read_dir(&proto_dir) @@ -11,6 +12,12 @@ fn main() -> Result<(), Box> { .collect(); protos.sort(); + // Watch each file individually so edits to an existing .proto retrigger codegen + // (directory mtime alone doesn't change when a file inside it is edited). + for proto in &protos { + println!("cargo:rerun-if-changed={}", proto.display()); + } + let file_descriptor_set = protox::compile(&protos, [&proto_dir])?; prost_build::Config::new().compile_fds(file_descriptor_set)?; Ok(()) From 3cc07cf267be900750a5ba0b62787e20e4d0a838 Mon Sep 17 00:00:00 2001 From: Johannes Schrimpf Date: Wed, 1 Jul 2026 18:56:10 +0200 Subject: [PATCH 3/3] Propagate per-entry read_dir errors in build.rs Collect directory entries into a Result instead of dropping per-entry errors via entry.ok(). An unreadable entry would otherwise silently shrink the proto list and produce codegen with missing types; now it fails the build with a useful error. Co-Authored-By: Claude Opus 4.8 (1M context) --- rust/build.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/rust/build.rs b/rust/build.rs index 2001d129..0d01018f 100644 --- a/rust/build.rs +++ b/rust/build.rs @@ -5,9 +5,13 @@ fn main() -> Result<(), Box> { // Watch the directory so added/removed .proto files retrigger the build. println!("cargo:rerun-if-changed=../protobuf_definitions"); - let mut protos: Vec = std::fs::read_dir(&proto_dir) + let entries = std::fs::read_dir(&proto_dir) .map_err(|e| format!("failed to read {}: {e}", proto_dir.display()))? - .filter_map(|entry| entry.ok().map(|e| e.path())) + .collect::, _>>() + .map_err(|e| format!("failed to read entry in {}: {e}", proto_dir.display()))?; + let mut protos: Vec = entries + .into_iter() + .map(|entry| entry.path()) .filter(|path| path.extension().is_some_and(|ext| ext == "proto")) .collect(); protos.sort();