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
14 changes: 14 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,20 @@ jobs:
cargo doc --no-deps --all-features
- name: No-alloc witness (explicit gate)
run: cargo test --features client,bare_metal --test no_alloc_witness
- name: SD wire-format conformance (TX direction)
# `tx_announcement_loop_emits_wire_format_offer` is `#[ignore]`'d by
# default because it needs an interface with the `MULTICAST` link
# flag. CI's `lo` lacks it; flip it on, point the test at
# 127.0.0.1, and run just this one test (the rest of the file's
# ignored tests need an external vsomeip docker container — they
# stay skipped).
run: |
sudo ip link set lo multicast on
SIMPLE_SOMEIP_TEST_INTERFACE=127.0.0.1 \
cargo test --features client-tokio,server-tokio \
--test vsomeip_sd_compat \
tx_announcement_loop_emits_wire_format_offer \
-- --ignored --exact --nocapture
- run: cargo llvm-cov nextest --all-features --lcov --output-path ./target/lcov.info
- name: Upload Coverage report
uses: codecov/codecov-action@v5
Expand Down
19 changes: 8 additions & 11 deletions tests/data/vsomeip-offerer/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,13 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
# stage of the Dockerfile) provides the imported targets we need.
find_package(vsomeip3 REQUIRED)

# Offerer binary: tiny, links libvsomeip3.
# Two binaries: offerer (advertises the service) and subscriber
# (consumes it via SD availability handler). The Dockerfile builds
# both; the entrypoint dispatches based on `VSOMEIP_ROLE`.
add_executable(offerer offerer.cpp)
add_executable(subscriber subscriber.cpp)

target_link_libraries(offerer
PRIVATE
vsomeip3
)

# vsomeip publishes its headers under <vsomeip/...>.
target_include_directories(offerer
PRIVATE
${VSOMEIP_INCLUDE_DIRS}
)
foreach(target offerer subscriber)
target_link_libraries(${target} PRIVATE vsomeip3)
target_include_directories(${target} PRIVATE ${VSOMEIP_INCLUDE_DIRS})
endforeach()
21 changes: 13 additions & 8 deletions tests/data/vsomeip-offerer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,10 @@ RUN mkdir build && cd build \
&& make install \
&& ldconfig

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

WORKDIR /src/offerer
Expand All @@ -78,17 +80,20 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
libboost-log1.74.0 \
&& rm -rf /var/lib/apt/lists/*

# Copy installed vsomeip libs + the offerer binary + the config + entrypoint.
# Copy installed vsomeip libs + both binaries + both configs + entrypoint.
COPY --from=build /usr/local/lib/libvsomeip3*.so* /usr/local/lib/
COPY --from=build /src/offerer/build/offerer /usr/local/bin/offerer
COPY --from=build /src/offerer/build/subscriber /usr/local/bin/subscriber
COPY offerer.json /etc/vsomeip-offerer.json
COPY subscriber.json /etc/vsomeip-subscriber.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 script templates VSOMEIP_UNICAST into the chosen
# role's JSON config and execs offerer or subscriber based on
# VSOMEIP_ROLE (default: 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"]
48 changes: 36 additions & 12 deletions tests/data/vsomeip-offerer/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
#!/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:
# Templates VSOMEIP_UNICAST into the role-specific JSON and execs
# the chosen vsomeip role. Two roles:
# VSOMEIP_ROLE=offerer (default) — advertises service 0x1234
# VSOMEIP_ROLE=subscriber — requests + watches for it
#
# VSOMEIP_UNICAST is required (vsomeip 3.4.10 doesn't honor any
# unicast-override 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
#
Expand All @@ -20,12 +24,32 @@ if [ -z "${VSOMEIP_UNICAST:-}" ]; then
exit 1
fi

# Templated config goes to a writable location since /etc/ in the
# image is read-only-ish from the build's COPY.
ROLE="${VSOMEIP_ROLE:-offerer}"
case "${ROLE}" in
offerer)
SRC_JSON=/etc/vsomeip-offerer.json
DST_JSON=/tmp/vsomeip-offerer.json
BINARY=/usr/local/bin/offerer
APP_NAME=offerer
;;
subscriber)
SRC_JSON=/etc/vsomeip-subscriber.json
DST_JSON=/tmp/vsomeip-subscriber.json
BINARY=/usr/local/bin/subscriber
APP_NAME=subscriber
;;
*)
echo "ERROR: VSOMEIP_ROLE='${ROLE}' invalid; use 'offerer' or 'subscriber'." 1>&2
exit 1
;;
esac

# Templated config goes to /tmp because /etc is read-only-ish from
# the image's COPY layer.
sed "s/VSOMEIP_UNICAST_PLACEHOLDER/${VSOMEIP_UNICAST}/" \
/etc/vsomeip-offerer.json > /tmp/vsomeip-offerer.json
"${SRC_JSON}" > "${DST_JSON}"

export VSOMEIP_CONFIGURATION=/tmp/vsomeip-offerer.json
export VSOMEIP_APPLICATION_NAME=offerer
export VSOMEIP_CONFIGURATION="${DST_JSON}"
export VSOMEIP_APPLICATION_NAME="${APP_NAME}"

exec /usr/local/bin/offerer
exec "${BINARY}"
3 changes: 2 additions & 1 deletion tests/data/vsomeip-offerer/offerer.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"routing": "offerer",
"service-discovery": {
"enable": "true",
"multicast": "224.0.23.0",
"_multicast_comment": "simple-someip's default SD multicast is hardcoded to 239.255.0.255 (see src/protocol/sd/mod.rs:MULTICAST_IP — Luminar-internal-network style, predates spec-default alignment). vsomeip's default would be 224.0.23.0 (the SOME/IP-SD spec value). For these conformance tests we match simple-someip; future work should make simple-someip's multicast group configurable so we can test against spec-default vsomeip too.",
"multicast": "239.255.0.255",
"port": "30490",
"protocol": "udp",
"initial_delay_min": "10",
Expand Down
94 changes: 94 additions & 0 deletions tests/data/vsomeip-offerer/subscriber.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// vsomeip subscriber for phase-20h's TX-direction conformance test.
//
// Reverse of `offerer.cpp`: registers as a *requester* of service
// 0x1234 instance 0x0001, sets up an availability handler, and
// prints a stable [subscriber] AVAILABLE / UNAVAILABLE marker
// whenever vsomeip's SD subsystem decides the service is on/off
// the wire. The Rust test (`tests/vsomeip_sd_compat.rs`) drives
// `Server::announcement_loop` and scrapes our docker logs for the
// AVAILABLE marker as the assertion.
//
// Same hardcoded service+instance as the offerer — change one,
// change the other (see tests/vsomeip_sd_compat.rs constants).

#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;

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 << "[subscriber] vsomeip::runtime::get() returned null" << std::endl;
return 1;
}

auto app = runtime->create_application("subscriber");
if (!app) {
std::cerr << "[subscriber] runtime->create_application() returned null" << std::endl;
return 1;
}

if (!app->init()) {
std::cerr << "[subscriber] application->init() failed; "
<< "check VSOMEIP_CONFIGURATION and JSON validity" << std::endl;
return 1;
}

// The availability handler fires whenever the routing manager's
// view of the service changes (offered <-> stopped). Print a
// distinct prefix so the Rust test can grep with low noise.
app->register_availability_handler(
kServiceId, kInstanceId,
[](vsomeip::service_t srv, vsomeip::instance_t inst, bool available) {
std::cout << "[subscriber] "
<< (available ? "AVAILABLE" : "UNAVAILABLE")
<< " service=0x" << std::hex << srv
<< " instance=0x" << inst
<< std::dec << std::endl
<< std::flush;
});

// Drive vsomeip on a worker thread, the same shape as the offerer.
std::thread vsomeip_thread([&app]() { app->start(); });

// Brief warmup so vsomeip's SD subsystem is fully initialized
// before we issue the request. Without this the request can
// race past the SD-init code path on slower hosts and miss the
// first round of incoming offers.
std::this_thread::sleep_for(std::chrono::milliseconds(200));

std::cout << "[subscriber] requesting service 0x" << std::hex << kServiceId
<< " instance 0x" << kInstanceId << std::dec << std::endl;

app->request_service(kServiceId, kInstanceId);

while (!g_shutdown.load(std::memory_order_acquire)) {
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}

std::cout << "[subscriber] shutdown requested; stopping vsomeip" << std::endl;
app->release_service(kServiceId, kInstanceId);
app->stop();
vsomeip_thread.join();
return 0;
}
35 changes: 35 additions & 0 deletions tests/data/vsomeip-offerer/subscriber.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"_comment": "vsomeip configuration for the phase-20h conformance subscriber. Mirror of offerer.json but registers as a `clients` consumer of service 0x1234 instance 0x0001 instead of `services` provider. Used to verify simple-someip's TX-direction SD wire format: simple-someip's Server::announcement_loop emits OfferService broadcasts, vsomeip subscribes, and its availability handler fires when the offer is recognized.",

"_unicast_comment": "Templated at container start by entrypoint.sh from VSOMEIP_UNICAST env var (same gotcha as offerer.json: Linux's lo lacks the MULTICAST flag, so SD multicast can't traverse it; pick a real interface IP).",
"unicast": "VSOMEIP_UNICAST_PLACEHOLDER",
"netmask": "255.255.255.0",
"logging": {
"level": "debug",
"console": "true"
},
"applications": [
{ "name": "subscriber", "id": "0x1278" }
],
"clients": [
{
"service": "0x1234",
"instance": "0x0001",
"unreliable": [30509]
}
],
"routing": "subscriber",
"service-discovery": {
"enable": "true",
"_multicast_comment": "Must match offerer.json and simple-someip's hardcoded MULTICAST_IP (239.255.0.255 in src/protocol/sd/mod.rs). vsomeip's spec default is 224.0.23.0 — we override here so the subscriber joins the group simple-someip actually broadcasts to. Future: make simple-someip's multicast group configurable so we can test against spec-default vsomeip.",
"multicast": "239.255.0.255",
"port": "30490",
"protocol": "udp",
"initial_delay_min": "10",
"initial_delay_max": "100",
"repetitions_base_delay": "200",
"repetitions_max": "3",
"ttl": "5",
"cyclic_offer_delay": "1000"
}
}
Loading