Skip to content
Merged
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
9 changes: 5 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -834,10 +834,7 @@ bench-gate:
@printf "$(GREEN)Checking connector allocation counts...$(NC)\n"
cargo bench --package aimdb-bench --bench b0_alloc_connector

## Drift guards
# The README quickstart is compiled as examples/readme-quickstart; this target
# fails when the README code block and the example diverge, or when the
# example no longer compiles (design 038 §2.6/§3.13).
## Interop
# ros2:// against a real ROS 2 distribution (rmw_zenohd and the ros2 CLI in
# Docker, host networking). DISTRO is a ros:<distro> image tag.
DISTRO ?= lyrical
Expand All @@ -846,6 +843,10 @@ ros2-interop:
cargo build --package aimdb-zenoh-connector --features "std" --examples
./aimdb-zenoh-connector/tests/interop/ros2_interop.sh $(DISTRO)

## Drift guards
# The README quickstart is compiled as examples/readme-quickstart; this target
# fails when the README code block and the example diverge, or when the
# example no longer compiles (design 038 §2.6/§3.13).
readme-check:
@printf "$(GREEN)Checking README quickstart against examples/readme-quickstart...$(NC)\n"
@awk '/^```rust$$/{f=1;next} f&&/^```$$/{exit} f' README.md \
Expand Down
7 changes: 7 additions & 0 deletions aimdb-zenoh-connector/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,13 @@ builder.configure::<Temperature>("cell4.temperature", |reg| {
subscription on the session; one that panics drops its sample, logged.
- **Keys are at most 65,535 bytes**, the Zenoh wire format's limit: a longer
fixed key fails the build, a longer written key is skipped.
- **An inner `**` is exposed to
[zenoh#2836](https://github.com/eclipse-zenoh/zenoh/issues/2836).** Zenoh
checks each delivered sample against every subscription with `intersects`,
which recurses once per chunk under a `**` that more chunks follow. On an
inbound link such as `zenoh://site/{path..}/state`, one sample with a long
matching key (about 64 KB) overflows a stack and aborts the process. A `**`
or `{name..}` at the end of a link is not affected.
- **Transport compression stays off.** zenoh 1.10.1 decompresses through
`lz4_flex` 0.10.0, which has RUSTSEC-2026-0041; this crate never enables it.
An application whose own dependencies enable `zenoh`'s compression, for
Expand Down
11 changes: 7 additions & 4 deletions docs/aimdb-usage-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -443,15 +443,18 @@ aimdb-data-contracts = { version = "0.2", features = ["ros2"] } # #[derive(Ros
```rust
use aimdb_zenoh_connector::{Ros2Connector, Ros2Node, ZenohConnector};

// Plain Zenoh
// Plain Zenoh, and ROS 2 on the same session: register both
let zenoh = ZenohConnector::new("tcp/192.168.10.5:7447");
let ros2 = zenoh.ros2(Ros2Node::new("cell4_gateway")).register::<Temperature>();
let mut builder = AimDbBuilder::new()
.runtime(runtime)
.with_connector(zenoh)
.with_connector(ros2);

builder.configure::<MachineState>("cell4.state", |reg| {
reg.buffer(BufferCfg::SingleLatest)
.linked_to_with("zenoh://aimdb/cell4/state", Postcard::<128>);
});

// ROS 2, on the same session
let ros2 = zenoh.ros2(Ros2Node::new("cell4_gateway")).register::<Temperature>();
builder.configure::<Temperature>("cell4.temperature", |reg| {
reg.buffer(BufferCfg::SpmcRing { capacity: 16 })
.linked_to("ros2://cell4/temperature");
Expand Down
7 changes: 7 additions & 0 deletions docs/design/053-zenoh-connector.md
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,13 @@ from a Zenoh or rmw_zenoh config work unchanged. The native backend also accepts
subscriber. Subscriber `i`'s callback therefore dispatches only if no
earlier subscription matches the key, so each route still sees it once
(s08, tested). 055 §5.7's MQTT 5 duplicate does not occur on Zenoh.
- zenoh's `Session` still checks each delivered sample against every
subscriber's key with `intersects`, which recurses once per chunk of the
sample's key under a `**` that chunks follow (zenoh#2836). A filter
such as `aimdb/{path..}/state` is therefore exposed: one long matching
key aborts the process. **[verified]** on zenoh 1.10.1 with a
64,001-byte key and a client subscribed to `a/**/b` through a router.
A trailing `**` is not affected.
- An inbound `with_topic_resolver` is honoured: its result goes through
the same grammar.
- **`ZenohGrammar`** implements `TopicGrammar` (055 §5.1). `{name}` compiles
Expand Down
28 changes: 23 additions & 5 deletions docs/upstream/zenoh-2836-keyexpr-intersects-stack-overflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,29 @@

## Why it matters to AimDB

`ZenohGrammar` does its own matching and does not call `intersects`, so the
connector is not affected (053 §4.3). Its matcher handles a 64 KB key in about
a millisecond (`long_keys_match_in_linear_time`). The issue matters if the
connector ever delegates matching to `zenoh-keyexpr`, and for the grammar
oracle, whose corpus keeps keys short.
`ZenohGrammar` does its own matching and does not call `intersects`; it
handles a 64 KB key in about a millisecond (`long_keys_match_in_linear_time`).
zenoh calls it, though: `Session` matches every sample it delivers against
each subscriber's key expression with `intersects` (`subscriber_callbacks` in
`zenoh/src/api/session.rs`, 1.10.1). So an inbound `zenoh://` link whose
filter has a `**` that other chunks follow, such as
`zenoh://site/{path..}/state`, is reachable from the network: any client that
can publish a matching key through the router can abort the process.

Measured on zenoh 1.10.1, release build. A client subscribed to `a/**/b`
through a router received one `put` on a matching key of 32,000 chunks
(64,001 bytes, inside the wire format's limit). Its receive thread (`rx-0`)
overflowed its stack and the process aborted; the router survived. Peer to
peer, `**/b` and `a/*/**/b` aborted the same way. A trailing `**` is not
affected: `a/**` received a key of 32,000 chunks, because `intersects`
returns at once when the `**` is last.

The connector has no guard yet; its README lists this under Limits. A guard
could declare such a filter only up to its first `**` and match the filter
itself in the callback, except a filter with a verbatim chunk after that
`**`, which no wider key covers. The issue also matters if the connector ever
delegates matching to `zenoh-keyexpr`, and for the grammar oracle, whose
corpus keeps keys short.

## Issue as filed

Expand Down