Skip to content

Phase20g vsomeip docker setup#110

Draft
JustinKovacich wants to merge 2 commits into
feature/phase20f_vsomeip_sd_conformancefrom
feature/phase20g_vsomeip_docker_setup
Draft

Phase20g vsomeip docker setup#110
JustinKovacich wants to merge 2 commits into
feature/phase20f_vsomeip_sd_conformancefrom
feature/phase20g_vsomeip_docker_setup

Conversation

@JustinKovacich
Copy link
Copy Markdown
Contributor

Makes phase 20f's tests/vsomeip_sd_compat.rs actually
runnable. Adds tests/data/vsomeip-offerer/:

  • Dockerfile — multi-stage Ubuntu 22.04 base. Stage 1 builds
    vsomeip 3.4.10 (the LumPDK / EnVision pinned version per
    LumPDK/packages/thirdparty/vsomeip/vsomeip.MODULE.bazel)
    from upstream tarball, plus our minimal C++ offerer. Stage 2
    is a slim runtime image with just libvsomeip3 + the offerer
    binary + entrypoint. ~463 MB final image.
  • offerer.cpp — ~85 LOC. Calls application->offer_service(0x1234, 0x0001, 1, 0) and idles while vsomeip's SD subsystem emits
    cyclic OfferService broadcasts.
  • offerer.json — vsomeip configuration. Standard SD multicast
    224.0.23.0:30490 per spec defaults; cyclic_offer_delay=1000ms;
    ttl=5s. unicast is templated at container start (see below).
  • entrypoint.sh — substitutes VSOMEIP_UNICAST env var into the
    JSON before exec'ing the offerer. Bails loudly if the env var
    isn't set. The substitution exists because unicast: 127.0.0.1
    doesn't work on Linux — lo lacks the MULTICAST flag by
    default, so SD multicast never actually leaves the host. Caller
    must pick a real interface IP via ip route get 224.0.23.0.
  • CMakeLists.txt — builds offerer against find_package(vsomeip3).
  • README.md — full build + run + test invocation flow with the
    multicast-on-lo gotcha documented.

Test file (tests/vsomeip_sd_compat.rs) module docs updated to
match the new harness shape. The #[ignore]'d test itself is
unchanged from 20f.

Verified end-to-end on 2026-04-29:

docker build --network=host -t vsomeip-offerer tests/data/vsomeip-offerer/
docker run --rm -d --name vsomeip-offerer --network host \
    -e VSOMEIP_UNICAST=172.20.21.206 vsomeip-offerer
SIMPLE_SOMEIP_TEST_INTERFACE=172.20.21.206 \
    cargo test --features client-tokio,server-tokio \
    --test vsomeip_sd_compat -- --ignored --nocapture
# client_sees_vsomeip_offer_service ... ok in 0.59s

This is the FIRST wire-level conformance signal in the project.
Every prior test ran simple-someip on both sides of the wire and
couldn't catch protocol non-compliance against an external
reference. Today: simple-someip's Client successfully decoded a
real vsomeip-emitted SD OfferService entry — service ID,
instance ID, TTL, major/minor version, source address all
matched the spec.

What this proves:

  • vsomeip 3.4.10 builds + runs from upstream source in our docker
  • simple-someip's SD-receive code path is wire-conformant against
    vsomeip's SD-emit path for OfferService entries (one rung)

What this does NOT prove (worth being explicit about):

  • Anything on TC4D — all of this is x86_64 Linux + native upstream
    Rust + tokio. No proxy LLVM-IR-TriCore exercise.
  • Bidirectional wire compatibility — we only tested vsomeip ->
    simple-someip. The reverse (simple-someip emits SD that vsomeip
    parses) is the next test (phase 20h).
  • Other SD entry types — FindService, SubscribeEventGroup,
    SubscribeAck, SubscribeNack are all separate code paths.
  • Anything stateful — request/response correlation, subscription
    state, event publishing, E2E protect/check on real payloads.
  • The lwip transport story — vsomeip uses its own UDP socket; nothing
    about Halo's planned lwip integration was tested.
  • The Option-A FFI shape — doesn't exist yet. This test went
    through simple-someip's existing tokio/Client API, which
    Halo won't use in production.

CI integration deferred. The test stays #[ignore]'d by default;
flipping it on cargo test would fail until a CI runner has
docker + the harness available. That's the next phase (20i?)
once we have the full conformance test set built out.

What this leaves:

  • 20h: bidirectional SD test (simple-someip emits, vsomeip
    subscribes; proves TX wire format).
  • 20i+: SubscribeEventGroup roundtrip, request/response, E2E
    conformance.
  • Eventual CI: TestContainers-rs (or equivalent) to bring up
    this docker on every PR.

Adds a `tools/size_probe/` workspace member that mirrors halo PR
#4429's `rust_simple_someip` C-callable FFI surface (header
encode/decode + E2E Profile 4/5 round-trips) and builds as a
`staticlib` for `thumbv7em-none-eabihf`. Used during phase
20-pre to estimate simple-someip's flash footprint.

Build + measure:

    cargo build -p size_probe --release --target thumbv7em-none-eabihf
    llvm-size target/thumbv7em-none-eabihf/release/libsize_probe.a

(rustup toolchain ships `llvm-size` under
`~/.rustup/toolchains/.../bin/`).

Why a probe instead of measuring simple-someip's rlib directly:
rlibs include compiler metadata that bloats them ~60×. A
staticlib with `extern "C"` entry points lets post-link
dead-code elimination strip everything an actual FFI consumer
wouldn't reach, giving a closer-to-real-world flash number.

First measurement (default release profile, no `opt-level=z`,
no LTO at the probe level): ~12 KB of simple-someip-specific
text + 14 KB of transitive dep code (heapless, thiserror,
tracing). Compiler-rt builtins and `core::fmt` chains aren't
simple-someip-unique — they're amortized firmware-wide — and
were excluded from the per-component breakdown.

NOT a production crate. Pure measurement tool. Includes a
panic-on-alloc stub `GlobalAlloc` to satisfy the link-target
requirement on builds where some transitive dep pulls
`extern crate alloc` even though the codec FFI surface itself
is alloc-free.

Why thumbv7em-none-eabihf and not the actual TC4D target:
halo's TriCore build pipeline uses an in-house LLVM-IR-to-
TriCore proxy + a private Docker image we don't have local
access to. cortex-m4f is the closest upstream-Rust-supported
target with similar code-density characteristics; gives a
defensible bracket for the real TC4D flash cost (likely within
±50% on the proxy toolchain).

Future use: when the Option-A stateful FFI surface lands,
re-add equivalent `extern "C"` shims for the new entry points
(`rust_handle_udp_rx`, `rust_tick`, etc.) and re-measure.
Lets us track the flash-cost delta from codec-only → full
state machines as that work progresses.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Makes phase 20f's `tests/vsomeip_sd_compat.rs` actually
runnable. Adds `tests/data/vsomeip-offerer/`:

- `Dockerfile` — multi-stage Ubuntu 22.04 base. Stage 1 builds
  vsomeip 3.4.10 (the LumPDK / EnVision pinned version per
  `LumPDK/packages/thirdparty/vsomeip/vsomeip.MODULE.bazel`)
  from upstream tarball, plus our minimal C++ offerer. Stage 2
  is a slim runtime image with just libvsomeip3 + the offerer
  binary + entrypoint. ~463 MB final image.
- `offerer.cpp` — ~85 LOC. Calls `application->offer_service(0x1234,
  0x0001, 1, 0)` and idles while vsomeip's SD subsystem emits
  cyclic OfferService broadcasts.
- `offerer.json` — vsomeip configuration. Standard SD multicast
  `224.0.23.0:30490` per spec defaults; cyclic_offer_delay=1000ms;
  ttl=5s. `unicast` is templated at container start (see below).
- `entrypoint.sh` — substitutes `VSOMEIP_UNICAST` env var into the
  JSON before exec'ing the offerer. Bails loudly if the env var
  isn't set. The substitution exists because `unicast: 127.0.0.1`
  doesn't work on Linux — `lo` lacks the `MULTICAST` flag by
  default, so SD multicast never actually leaves the host. Caller
  must pick a real interface IP via `ip route get 224.0.23.0`.
- `CMakeLists.txt` — builds offerer against `find_package(vsomeip3)`.
- `README.md` — full build + run + test invocation flow with the
  multicast-on-lo gotcha documented.

Test file (`tests/vsomeip_sd_compat.rs`) module docs updated to
match the new harness shape. The `#[ignore]`'d test itself is
unchanged from 20f.

Verified end-to-end on 2026-04-29:

    docker build --network=host -t vsomeip-offerer tests/data/vsomeip-offerer/
    docker run --rm -d --name vsomeip-offerer --network host \
        -e VSOMEIP_UNICAST=172.20.21.206 vsomeip-offerer
    SIMPLE_SOMEIP_TEST_INTERFACE=172.20.21.206 \
        cargo test --features client-tokio,server-tokio \
        --test vsomeip_sd_compat -- --ignored --nocapture
    # client_sees_vsomeip_offer_service ... ok in 0.59s

This is the FIRST wire-level conformance signal in the project.
Every prior test ran simple-someip on both sides of the wire and
couldn't catch protocol non-compliance against an external
reference. Today: simple-someip's Client successfully decoded a
real vsomeip-emitted SD `OfferService` entry — service ID,
instance ID, TTL, major/minor version, source address all
matched the spec.

What this proves:
- vsomeip 3.4.10 builds + runs from upstream source in our docker
- simple-someip's SD-receive code path is wire-conformant against
  vsomeip's SD-emit path for OfferService entries (one rung)

What this does NOT prove (worth being explicit about):
- Anything on TC4D — all of this is x86_64 Linux + native upstream
  Rust + tokio. No proxy LLVM-IR-TriCore exercise.
- Bidirectional wire compatibility — we only tested vsomeip ->
  simple-someip. The reverse (simple-someip emits SD that vsomeip
  parses) is the next test (phase 20h).
- Other SD entry types — FindService, SubscribeEventGroup,
  SubscribeAck, SubscribeNack are all separate code paths.
- Anything stateful — request/response correlation, subscription
  state, event publishing, E2E protect/check on real payloads.
- The lwip transport story — vsomeip uses its own UDP socket; nothing
  about Halo's planned lwip integration was tested.
- The Option-A FFI shape — doesn't exist yet. This test went
  through simple-someip's existing tokio/`Client` API, which
  Halo won't use in production.

CI integration deferred. The test stays `#[ignore]`'d by default;
flipping it on `cargo test` would fail until a CI runner has
docker + the harness available. That's the next phase (20i?)
once we have the full conformance test set built out.

What this leaves:
- 20h: bidirectional SD test (simple-someip emits, vsomeip
  subscribes; proves TX wire format).
- 20i+: SubscribeEventGroup roundtrip, request/response, E2E
  conformance.
- Eventual CI: TestContainers-rs (or equivalent) to bring up
  this docker on every PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@JustinKovacich JustinKovacich force-pushed the feature/phase20g_vsomeip_docker_setup branch from 09c9e00 to 886e2fe Compare April 29, 2026 20:28
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a runnable local harness for the ignored vsomeip_sd_compat integration test by introducing a vsomeip-based Docker offerer container, and updates the test’s module docs accordingly. Also introduces a new tools/size_probe workspace crate intended for no-std flash-size probing via an exported C ABI surface.

Changes:

  • Add tests/data/vsomeip-offerer/ Docker image (vsomeip 3.4.10 build + minimal C++ offerer + entrypoint templating) to emit SOME/IP-SD OfferService broadcasts.
  • Update tests/vsomeip_sd_compat.rs “Running locally” documentation to use the new container flow and document the multicast/loopback gotcha.
  • Add a new tools/size_probe no-std staticlib crate to measure post-link size of an FFI-like surface, and register it in the workspace.

Reviewed changes

Copilot reviewed 10 out of 11 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
Cargo.toml Adds tools/size_probe to workspace members.
Cargo.lock Records the new size_probe package in the lockfile.
tools/size_probe/Cargo.toml Defines a no-std staticlib crate configured for size-optimized builds.
tools/size_probe/src/lib.rs Implements exported C ABI shims for SOME/IP header encode + E2E profile round-trips.
tests/vsomeip_sd_compat.rs Updates local-run documentation to match the new dockerized vsomeip offerer harness.
tests/data/vsomeip-offerer/Dockerfile Multi-stage build: compiles vsomeip 3.4.10 + offerer; runtime image packages libs + binary + entrypoint.
tests/data/vsomeip-offerer/offerer.cpp Minimal vsomeip app that offers a fixed service/instance and stays alive to emit SD offers.
tests/data/vsomeip-offerer/offerer.json vsomeip configuration template with SD multicast defaults and a templated unicast address.
tests/data/vsomeip-offerer/entrypoint.sh Templates VSOMEIP_UNICAST into config then runs offerer; errors if env var missing.
tests/data/vsomeip-offerer/CMakeLists.txt Builds the offerer against vsomeip3.
tests/data/vsomeip-offerer/README.md Step-by-step build/run/test instructions for the harness.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +82 to +85
let Ok(msg_type_raw) = MessageType::try_from(h.message_type & 0xBF) else {
return 0;
};
let msg_type = MessageTypeField::new(msg_type_raw, (h.message_type & 0x20) != 0);
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MessageType::try_from(h.message_type & 0xBF) masks off bit 0x40 before validation. That can turn otherwise-invalid on-wire message_type values (with reserved bits set) into valid ones (e.g., 0x40 becomes Request), which is incorrect for a header encode shim. Prefer validating the raw byte and constructing the field from it (e.g., MessageTypeField::try_from(h.message_type)), rather than clearing bits first.

Suggested change
let Ok(msg_type_raw) = MessageType::try_from(h.message_type & 0xBF) else {
return 0;
};
let msg_type = MessageTypeField::new(msg_type_raw, (h.message_type & 0x20) != 0);
let Ok(msg_type) = MessageTypeField::try_from(h.message_type) else {
return 0;
};

Copilot uses AI. Check for mistakes.
Comment on lines +58 to +97
pub length: u32,
pub client_id: u16,
pub session_id: u16,
pub protocol_version: u8,
pub interface_version: u8,
pub message_type: u8,
pub return_code: u8,
}

/// # Safety
/// Caller must ensure `header` points to a valid `CSomeIpHeader` and
/// `buf` points to at least `buf_len` writable bytes.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn someip_header_encode(
header: *const CSomeIpHeader,
buf: *mut u8,
buf_len: usize,
) -> usize {
if header.is_null() || buf.is_null() || buf_len < 16 {
return 0;
}
let h = unsafe { &*header };
let message_id = MessageId::new_from_service_and_method(h.service_id, h.method_id);
let request_id = (u32::from(h.client_id) << 16) | u32::from(h.session_id);
let Ok(msg_type_raw) = MessageType::try_from(h.message_type & 0xBF) else {
return 0;
};
let msg_type = MessageTypeField::new(msg_type_raw, (h.message_type & 0x20) != 0);
let Ok(ret_code) = ReturnCode::try_from(h.return_code) else {
return 0;
};
let header = Header::new(
message_id,
request_id,
h.protocol_version,
h.interface_version,
msg_type,
ret_code,
0,
);
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CSomeIpHeader includes a length field, but someip_header_encode ignores it and always constructs the Header with payload_len = 0, forcing the encoded length to 8. If the intent is to encode an arbitrary on-wire header (as the FFI shape suggests), use Header::from_fields(..., h.length, ...) or otherwise derive the payload length from h.length and pass that through so the encoded header matches the caller-provided value.

Copilot uses AI. Check for mistakes.
Comment on lines +20 to +34
/// Stub allocator. Some transitive dep pulls `extern crate alloc`
/// even with simple-someip's `default-features = false`, requiring a
/// `#[global_allocator]` link target. The codec-only FFI surface
/// (header encode + E2E protect/check) never actually allocates, so
/// this stub returning null on alloc is sound for the probe; if any
/// path it fronts ever does allocate, that's an explicit FFI-design
/// bug surfaced at link time, not silent corruption at runtime.
struct PanicAllocator;

unsafe impl GlobalAlloc for PanicAllocator {
unsafe fn alloc(&self, _: Layout) -> *mut u8 {
ptr::null_mut()
}
unsafe fn dealloc(&self, _: *mut u8, _: Layout) {}
}
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The allocator docs say this stub returning null will surface allocation attempts "at link time", but a null-returning GlobalAlloc::alloc would instead be a runtime OOM path (and depending on how allocation is triggered, could end up in an abort or an alloc error handler). Also, the type name PanicAllocator is misleading since it never panics. Consider updating the comment to describe the actual behavior and/or renaming the allocator, or making alloc explicitly abort/panic if you want allocation attempts to fail loudly.

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,34 @@
{
"_comment": "vsomeip configuration for the phase-20f host-side conformance test offerer. Defaults follow the SOME/IP-SD spec (multicast 224.0.23.0:30490) so simple-someip's test-side Client picks up the broadcast without any non-default routing. The 'unicast' field is the vsomeip-side IP — 127.0.0.1 works on Linux Docker host-network mode because both sides share the loopback. For real-NIC testing, set unicast to the host interface IP and adjust the test's SIMPLE_SOMEIP_TEST_INTERFACE env var to match.",
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _comment text claims unicast: 127.0.0.1 works in host-network mode, but the rest of this harness (entrypoint.sh + test docs) explicitly says loopback doesn't work due to lo lacking MULTICAST. This is confusing for users; update/remove the 127.0.0.1 claim so the JSON docs match the required VSOMEIP_UNICAST behavior.

Suggested change
"_comment": "vsomeip configuration for the phase-20f host-side conformance test offerer. Defaults follow the SOME/IP-SD spec (multicast 224.0.23.0:30490) so simple-someip's test-side Client picks up the broadcast without any non-default routing. The 'unicast' field is the vsomeip-side IP — 127.0.0.1 works on Linux Docker host-network mode because both sides share the loopback. For real-NIC testing, set unicast to the host interface IP and adjust the test's SIMPLE_SOMEIP_TEST_INTERFACE env var to match.",
"_comment": "vsomeip configuration for the phase-20f host-side conformance test offerer. Defaults follow the SOME/IP-SD spec (multicast 224.0.23.0:30490) so simple-someip's test-side Client picks up the broadcast without any non-default routing. The 'unicast' field is the vsomeip-side IP and must be set to a non-loopback host interface IP with MULTICAST support. Set unicast via VSOMEIP_UNICAST and adjust the test's SIMPLE_SOMEIP_TEST_INTERFACE env var to match.",

Copilot uses AI. Check for mistakes.
# Run (host network mode so SD multicast 224.0.23.0:30490 reaches the
# host's listener — required for the cargo test to receive the
# OfferService broadcast):
# docker run --rm -d --name vsomeip-offerer --network host \
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Dockerfile's header comment shows a docker run ... vsomeip-offerer invocation without -e VSOMEIP_UNICAST=..., but the image ENTRYPOINT requires VSOMEIP_UNICAST and exits with an error if it's missing. Update the example run command in the comment block to include -e VSOMEIP_UNICAST=<iface-ip> so the documented usage matches actual behavior.

Suggested change
# docker run --rm -d --name vsomeip-offerer --network host \
# docker run --rm -d --name vsomeip-offerer --network host \
# -e VSOMEIP_UNICAST=<iface-ip> \

Copilot uses AI. Check for mistakes.
Comment thread Cargo.toml
Comment on lines 8 to +10
"examples/embassy_net_client",
"simple-someip-embassy-net",
"tools/size_probe",
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description focuses on making tests/vsomeip_sd_compat.rs runnable via the new vsomeip Docker harness, but this PR also adds a new workspace member tools/size_probe (and lockfile entries) that isn't mentioned. If size_probe is intentional here, please call it out in the PR description; otherwise consider splitting it into a separate PR to keep scope focused.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants