From c7534f07bfbc37d16b4acb595e61af3f8172a1d0 Mon Sep 17 00:00:00 2001 From: Vinicius Zein Date: Sat, 18 Apr 2026 08:23:08 -0400 Subject: [PATCH] feat: support static remote endpoint in ClientConfig (no SD) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add an optional `remote_endpoint` field to `ClientConfig` and forward it to `UdpTransport` / `TcpTransport` during `SomeIpClient` construction. This enables communication with SOME/IP servers that do not run Service Discovery (e.g. Zephyr-based MCU firmware with multicast TX disabled). Both transports already accept the parameter — it just wasn't wired through the high-level config. The field defaults to `None`, preserving existing SD-based behavior. Closes #13 Made-with: Cursor --- src/opensomeip/client.py | 7 +++++ tests/unit/test_client.py | 55 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/src/opensomeip/client.py b/src/opensomeip/client.py index 71b9ff0..0f0b03c 100644 --- a/src/opensomeip/client.py +++ b/src/opensomeip/client.py @@ -47,12 +47,17 @@ class ClientConfig: - feat_req_someip_850-854: TCP transport configuration - feat_req_someiptp_403: TP segment size negotiation - feat_req_someip_102-103: E2E protection configuration + + When ``remote_endpoint`` is set, the transport is configured with a + static remote peer address, bypassing Service Discovery. This is + useful for ECUs that do not run SOME/IP-SD. """ local_endpoint: Endpoint sd_config: SdConfig transport_mode: TransportMode = TransportMode.UDP multicast_group: str | None = None + remote_endpoint: Endpoint | None = None enable_tp: bool = False tp_mtu: int = 1400 e2e_config: E2EConfig | None = None @@ -86,10 +91,12 @@ def __init__(self, config: ClientConfig) -> None: if config.transport_mode == TransportMode.TCP: self._transport: Transport = TcpTransport( config.local_endpoint, + config.remote_endpoint, ) else: self._transport = UdpTransport( config.local_endpoint, + config.remote_endpoint, multicast_group=config.multicast_group, ) diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index 4a833e8..ffc904e 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -210,6 +210,61 @@ def test_send_via_tp(self, tp_client_config: ClientConfig) -> None: client.send(msg) +class TestStaticRemoteEndpoint: + """Static remote endpoint support (no Service Discovery).""" + + def test_remote_endpoint_defaults_to_none(self, client_config: ClientConfig) -> None: + assert client_config.remote_endpoint is None + + def test_udp_remote_endpoint_forwarded(self) -> None: + remote = Endpoint("192.168.100.10", 30490) + cfg = ClientConfig( + local_endpoint=Endpoint("0.0.0.0", 0), + sd_config=SdConfig( + multicast_endpoint=Endpoint("239.1.1.1", 30490), + unicast_endpoint=Endpoint("192.168.1.200", 30490), + ), + remote_endpoint=remote, + ) + client = SomeIpClient(cfg) + assert isinstance(client.transport, UdpTransport) + assert client.transport.remote_endpoint == remote + + def test_tcp_remote_endpoint_forwarded(self) -> None: + remote = Endpoint("192.168.100.10", 30490) + cfg = ClientConfig( + local_endpoint=Endpoint("0.0.0.0", 0), + sd_config=SdConfig( + multicast_endpoint=Endpoint("239.1.1.1", 30490), + unicast_endpoint=Endpoint("192.168.1.200", 30490), + ), + transport_mode=TransportMode.TCP, + remote_endpoint=remote, + ) + client = SomeIpClient(cfg) + assert isinstance(client.transport, TcpTransport) + assert client.transport.remote_endpoint == remote + + def test_no_remote_endpoint_preserves_none(self, client_config: ClientConfig) -> None: + client = SomeIpClient(client_config) + assert client.transport.remote_endpoint is None + + def test_lifecycle_with_remote_endpoint(self) -> None: + remote = Endpoint("192.168.100.10", 30490) + cfg = ClientConfig( + local_endpoint=Endpoint("0.0.0.0", 0), + sd_config=SdConfig( + multicast_endpoint=Endpoint("239.1.1.1", 30490), + unicast_endpoint=Endpoint("192.168.1.200", 30490), + ), + remote_endpoint=remote, + ) + with SomeIpClient(cfg) as client: + assert client.is_running is True + assert client.transport.remote_endpoint == remote + assert client.is_running is False + + class TestE2EIntegration: """feat_req_someip_102-103: E2E protection integration."""