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
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ members = [
"examples/discovery_client",
"examples/embassy_net_client",
"simple-someip-embassy-net",
"tools/size_probe",
Comment on lines 8 to +10
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.
]

[package]
Expand Down
23 changes: 23 additions & 0 deletions tests/data/vsomeip-offerer/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
cmake_minimum_required(VERSION 3.13)
project(vsomeip_offerer CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# vsomeip's exported config (installed by `make install` in the build
# stage of the Dockerfile) provides the imported targets we need.
find_package(vsomeip3 REQUIRED)

# Offerer binary: tiny, links libvsomeip3.
add_executable(offerer offerer.cpp)

target_link_libraries(offerer
PRIVATE
vsomeip3
)

# vsomeip publishes its headers under <vsomeip/...>.
target_include_directories(offerer
PRIVATE
${VSOMEIP_INCLUDE_DIRS}
)
94 changes: 94 additions & 0 deletions tests/data/vsomeip-offerer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# vsomeip 3.4.10 + a minimal offerer that advertises service 0x1234
# instance 0x0001 via SD multicast. Used by phase 20f's host-side
# conformance test (`tests/vsomeip_sd_compat.rs`).
#
# Build:
# docker build -t vsomeip-offerer tests/data/vsomeip-offerer/
#
# 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.
# vsomeip-offerer
#
# Verify it's emitting:
# docker logs vsomeip-offerer
#
# Stop:
# docker stop vsomeip-offerer
#
# Pinning to vsomeip 3.4.10 specifically because that's the version
# LumPDK / EnVision use (see LumPDK/packages/thirdparty/vsomeip/
# vsomeip.MODULE.bazel). Keeping wire-version-aligned with production
# avoids interop quirks during CI conformance testing.

FROM ubuntu:22.04 AS build

# vsomeip's CMake build needs: gcc, cmake, boost, and a few utilities
# for the patch-and-build flow. dlt is optional and we skip it.
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
cmake \
git \
ca-certificates \
wget \
libboost-system-dev \
libboost-filesystem-dev \
libboost-thread-dev \
libboost-log-dev \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /src

# Pull vsomeip 3.4.10 from upstream.
RUN wget -q https://github.com/COVESA/vsomeip/archive/refs/tags/3.4.10.tar.gz \
&& tar xzf 3.4.10.tar.gz \
&& rm 3.4.10.tar.gz

WORKDIR /src/vsomeip-3.4.10

# Build vsomeip as shared libs (default). Skip building the test/
# example trees — we'll compile our own offerer against the installed
# library.
RUN mkdir build && cd build \
&& cmake .. \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/usr/local \
&& make -j"$(nproc)" \
&& make install \
&& ldconfig

# Build our offerer. It links against the just-installed libvsomeip3.
COPY offerer.cpp /src/offerer/offerer.cpp
COPY CMakeLists.txt /src/offerer/CMakeLists.txt

WORKDIR /src/offerer
RUN mkdir build && cd build \
&& cmake .. \
-DCMAKE_BUILD_TYPE=Release \
&& make -j"$(nproc)"

# ── Runtime image ────────────────────────────────────────────────────
FROM ubuntu:22.04

RUN apt-get update && apt-get install -y --no-install-recommends \
libboost-system1.74.0 \
libboost-filesystem1.74.0 \
libboost-thread1.74.0 \
libboost-log1.74.0 \
&& rm -rf /var/lib/apt/lists/*

# Copy installed vsomeip libs + the offerer binary + the config + entrypoint.
COPY --from=build /usr/local/lib/libvsomeip3*.so* /usr/local/lib/
COPY --from=build /src/offerer/build/offerer /usr/local/bin/offerer
COPY offerer.json /etc/vsomeip-offerer.json
COPY entrypoint.sh /usr/local/bin/entrypoint.sh

RUN ldconfig && chmod +x /usr/local/bin/entrypoint.sh

# Entrypoint script templates VSOMEIP_UNICAST into the JSON config
# before launching the offerer. Caller MUST pass `-e VSOMEIP_UNICAST=
# <multicast-capable-iface-IP>` on `docker run`; the script exits
# loudly otherwise. See entrypoint.sh for the rationale (lo's lack of
# MULTICAST flag is the gotcha).
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
113 changes: 113 additions & 0 deletions tests/data/vsomeip-offerer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# vsomeip offerer for phase-20f conformance testing

A Docker image that builds vsomeip 3.4.10 (the version LumPDK /
EnVision pin) and runs a tiny C++ offerer advertising service
`0x1234` instance `0x0001` via SOME/IP-SD. The companion
`tests/vsomeip_sd_compat.rs` test on the host listens for that
broadcast.

## Build

```sh
docker build -t vsomeip-offerer tests/data/vsomeip-offerer/
```

First build pulls vsomeip from upstream and compiles it in the
container — expect 5–10 minutes on a typical workstation.
Subsequent builds use Docker's layer cache.

## Run

First, find a multicast-capable interface IP on your host:

```sh
ip route get 224.0.23.0
# Expected output:
# multicast 224.0.23.0 dev wlp0s20f3 src 192.168.1.42 uid 1000
# ^^^^^^^^^^^^
# That last IP is what you pass below. Lo (127.0.0.1) does NOT
# work — Linux's loopback interface lacks the MULTICAST flag by
# default, so SD multicast never leaves the host.
```

Then launch the offerer:

```sh
docker run --rm -d --name vsomeip-offerer --network host \
-e VSOMEIP_UNICAST=192.168.1.42 \
vsomeip-offerer
```

`--network host` is required so SD multicast (`224.0.23.0:30490`)
flows on the actual host interface. The `VSOMEIP_UNICAST` env var
gets templated into the JSON config at container start by
`entrypoint.sh`.

Verify it's up:

```sh
docker logs vsomeip-offerer
# Expected (debug level): "Joining to multicast group 224.0.23.0 from <your IP>"
# and "OFFER(1277): [1234.0001:1.0] (true)"
```

## Test against it

In another terminal:

```sh
SIMPLE_SOMEIP_TEST_INTERFACE=192.168.1.42 \
cargo test --features client-tokio,server-tokio \
--test vsomeip_sd_compat -- --ignored --nocapture
```

Use the **same IP** you passed via `VSOMEIP_UNICAST`. Expected:
`client_sees_vsomeip_offer_service ... ok` in well under a second
once vsomeip's first SD broadcast fires (~100 ms after offer
registration, then every 1 s thereafter).

## Stop

```sh
docker stop vsomeip-offerer
```

## Files

- `Dockerfile` — multi-stage: builds vsomeip + the offerer in stage 1,
copies the runtime artifacts into a slim runtime stage.
- `offerer.cpp` — ~50 LOC vsomeip-based offerer; calls
`application->offer_service(0x1234, 0x0001, 1, 0)` and idles
while vsomeip emits SD broadcasts.
- `CMakeLists.txt` — builds `offerer` against installed `libvsomeip3`.
- `offerer.json` — vsomeip configuration. `unicast` is templated
via `VSOMEIP_UNICAST` env var at container start (see
`entrypoint.sh`). Standard SD multicast `224.0.23.0:30490`.
- `entrypoint.sh` — substitutes `VSOMEIP_UNICAST` into the JSON
config before launching the offerer; bails loudly if the env
var isn't set.

## Why these specific values

- vsomeip 3.4.10: matches `LumPDK/packages/thirdparty/vsomeip/vsomeip.MODULE.bazel`
so CI conformance tests run against the same wire-version
production validation does.
- Service `0x1234` instance `0x0001`: hardcoded in both this
config and `tests/vsomeip_sd_compat.rs`. Change one, change the
other.
- Multicast `224.0.23.0:30490`: SOME/IP-SD spec default. (LumPDK's
production config uses `239.255.0.5:30491` but that's a
Luminar-network-specific choice; for the host-side conformance
test, sticking to spec defaults removes a configuration knob.)
- `unicast: "127.0.0.1"`: works under Docker host-network mode
because the host and container share the loopback interface.
For real-NIC testing, set this to the host's interface IP and
set `SIMPLE_SOMEIP_TEST_INTERFACE` to match.

## Future (phase 20g+)

- Wire this Dockerfile into CI via TestContainers-rs (or
equivalent) so `cargo test ... -- --ignored` runs in a
CI runner with Docker available.
- Apply LumPDK's vsomeip patches to the build (especially the
E2E Profile 5 patch) once we add E2E-conformance tests.
31 changes: 31 additions & 0 deletions tests/data/vsomeip-offerer/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/bin/sh
# Templates VSOMEIP_UNICAST into /etc/vsomeip-offerer.json then exec
# the offerer. The env var MUST be set on docker run; vsomeip 3.4.10
# does not honor a VSOMEIP_UNICAST_ADDRESS-style env var directly,
# and `unicast: 127.0.0.1` doesn't work on Linux (lo lacks the
# MULTICAST flag, so SD multicast never reaches the wire). Pick the
# IP of an actual multicast-capable interface on the host:
#
# ip route get 224.0.23.0
#
# returns "multicast 224.0.23.0 dev <iface> src <IP> ..." — use
# that <IP>.

set -eu

if [ -z "${VSOMEIP_UNICAST:-}" ]; then
echo "ERROR: set VSOMEIP_UNICAST=<iface-IP> on docker run." 1>&2
echo " e.g. 'docker run -e VSOMEIP_UNICAST=192.168.1.10 ...'" 1>&2
echo " Find your interface IP via 'ip route get 224.0.23.0'." 1>&2
exit 1
fi

# Templated config goes to a writable location since /etc/ in the
# image is read-only-ish from the build's COPY.
sed "s/VSOMEIP_UNICAST_PLACEHOLDER/${VSOMEIP_UNICAST}/" \
/etc/vsomeip-offerer.json > /tmp/vsomeip-offerer.json

export VSOMEIP_CONFIGURATION=/tmp/vsomeip-offerer.json
export VSOMEIP_APPLICATION_NAME=offerer

exec /usr/local/bin/offerer
95 changes: 95 additions & 0 deletions tests/data/vsomeip-offerer/offerer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Minimal vsomeip offerer for phase-20f conformance testing.
//
// Offers service 0x1234 instance 0x0001 via vsomeip's SD subsystem.
// vsomeip emits OfferService SD broadcasts on the configured
// multicast group/port (per offerer.json's "service-discovery"
// section) until the process exits. That's the broadcast our
// `tests/vsomeip_sd_compat.rs` test on the host listens for.
//
// Hardcoded service+instance to keep this trivial; if the test's
// constants change, change them here too. See
// tests/vsomeip_sd_compat.rs:SERVICE_ID / INSTANCE_ID.

#include <vsomeip/vsomeip.hpp>

#include <atomic>
#include <chrono>
#include <csignal>
#include <iostream>
#include <thread>

namespace {

constexpr vsomeip::service_t kServiceId = 0x1234;
constexpr vsomeip::instance_t kInstanceId = 0x0001;
// Major.Minor version vsomeip advertises in OfferService entries.
// Defaults; doesn't have to match anything specific test-side.
constexpr vsomeip::major_version_t kMajor = 1;
constexpr vsomeip::minor_version_t kMinor = 0;

std::atomic<bool> g_shutdown{false};

void on_signal(int /*signum*/) {
g_shutdown.store(true, std::memory_order_release);
}

} // namespace

int main() {
std::signal(SIGINT, on_signal);
std::signal(SIGTERM, on_signal);

auto runtime = vsomeip::runtime::get();
if (!runtime) {
std::cerr << "[offerer] vsomeip::runtime::get() returned null" << std::endl;
return 1;
}

// Application name matches "applications" / "routing" entries in
// offerer.json (and the VSOMEIP_APPLICATION_NAME env var the
// Dockerfile sets). vsomeip uses this to look up the routing
// configuration.
auto app = runtime->create_application("offerer");
if (!app) {
std::cerr << "[offerer] runtime->create_application() returned null" << std::endl;
return 1;
}

// init() reads the JSON config (VSOMEIP_CONFIGURATION) and
// registers the SD subsystem.
if (!app->init()) {
std::cerr << "[offerer] application->init() failed; "
<< "check VSOMEIP_CONFIGURATION and JSON validity" << std::endl;
return 1;
}

// Spawn vsomeip's main loop on a worker thread. start() blocks
// for the lifetime of the application; we drive it from a thread
// so this main loop can monitor the shutdown signal.
std::thread vsomeip_thread([&app]() { app->start(); });

// Wait for vsomeip to be ready, then advertise the service.
// 200 ms is more than enough for vsomeip's startup on any
// x86 host.
std::this_thread::sleep_for(std::chrono::milliseconds(200));

std::cout << "[offerer] offering service 0x" << std::hex << kServiceId
<< " instance 0x" << kInstanceId
<< " (major " << std::dec << static_cast<int>(kMajor)
<< ", minor " << kMinor << ")" << std::endl;

app->offer_service(kServiceId, kInstanceId, kMajor, kMinor);

// Spin until SIGINT/SIGTERM. vsomeip's SD subsystem emits
// periodic OfferService broadcasts in the background; we just
// need to keep the process alive.
while (!g_shutdown.load(std::memory_order_acquire)) {
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}

std::cout << "[offerer] shutdown requested; stopping vsomeip" << std::endl;
app->stop_offer_service(kServiceId, kInstanceId, kMajor, kMinor);
app->stop();
vsomeip_thread.join();
return 0;
}
Loading