Skip to content

bug: RpcClient.call() and Transport.send() silently return fake data when C++ extension is missing #17

@vtz

Description

@vtz

Summary

When the C++ extension (_opensomeip) fails to load, RpcClient.call() returns a synthetic Message with return_code=E_OK and empty payload instead of raising an error. Transport.send() silently drops the message. This makes the library appear to work while doing nothing on the network, causing silent data loss for all downstream consumers (e.g. jumpstarter-driver-someip).

Reported downstream at jumpstarter-dev/jumpstarter#628.

Affected code

src/opensomeip/rpc.pyRpcClient.call()

The stub fallthrough path fabricates a success response:

# When self._cpp is None (no C++ extension):
request = Message(...)
self._transport.send(request)   # no-op — no socket opened
return Message(
    message_id=method_id,
    request_id=request.request_id,
    message_type=MessageType.RESPONSE,
    return_code=ReturnCode.E_OK,
    # payload defaults to b"" — caller thinks RPC succeeded
)

Additionally, the C++ code path has a blanket except Exception: pass that silently swallows all errors and falls through to the same stub:

if self._cpp is not None:
    try:
        result = self._cpp.call_method_sync(...)
        if int(result.result) == 0:
            return Message(...)  # only returns on success
    except Exception:
        pass  # ← ALL errors silently swallowed, falls through to stub

src/opensomeip/transport.pyTransport.send()

When the C++ extension is unavailable, send() is a silent no-op:

def send(self, message, endpoint=None):
    if not self._running:
        raise TransportError("Transport is not running")
    if self._cpp is not None:
        # ... sends via native transport
    # When cpp is None: does absolutely nothing, no error

src/opensomeip/events.pyEventSubscriber.subscribe()

Without the C++ extension, subscribing only records the eventgroup ID locally — no actual subscription happens over the network. The notification MessageReceiver never gets any messages, causing all receive_event() callers to time out.

Observed impact

All of these produce identical symptoms regardless of whether the extension is missing or the network is broken:

Operation Observed Expected
client.call(method_id, payload) Message(return_code=E_OK, payload=b"") Message with actual response payload, or RpcError
transport.send(message) Silent no-op TransportError
event_subscriber.subscribe(eg_id) Local-only, no network Actual SD subscription
receive_event(timeout=5) Always times out Event notifications from the ECU

The ImportWarning emitted by _bridge.py is filtered by Python's default warning filters, making the degradation invisible in production.

Proposed fix

1. RpcClient.call() — raise instead of faking a response

def call(self, method_id, payload=b"", *, timeout=5.0):
    if not self._running:
        raise RpcError("RPC client is not running")

    if self._cpp is not None:
        try:
            ...
            result = self._cpp.call_method_sync(...)
            if int(result.result) == 0:
                return Message(...)
            raise RpcError(f"RPC call failed with result code {result.result}")
        except RpcError:
            raise
        except Exception as exc:
            raise RpcError(f"Native RPC call failed: {exc}") from exc

    raise RpcError(
        "Cannot perform RPC: opensomeip C++ extension is not available. "
        "See https://github.com/vtz/opensomeip-python#troubleshooting"
    )

2. Transport.send() — raise when no native transport

def send(self, message, endpoint=None):
    if not self._running:
        raise TransportError("Transport is not running")
    if self._cpp is None:
        raise TransportError(
            "Cannot send: native transport is not available. "
            "See https://github.com/vtz/opensomeip-python#troubleshooting"
        )
    ...

3. EventSubscriber.subscribe() — raise when no native events

Same pattern: raise ConfigurationError or RuntimeError when self._cpp is None and a real subscription is attempted.

4. Documentation update for macOS users

The existing Troubleshooting section in README.md documents the macOS libc++ ABI issue and the "Silent no-op transport" behavior. However:

  • The workaround (CC=/usr/bin/clang CXX=/usr/bin/clang++ pip install ...) should be made more prominent (e.g. a callout/admonition at the top of the Quick Start section)
  • Add a "Verify your installation" section:
### Verify native extension

After installation, confirm the C++ extension loads:

\`\`\`bash
python3 -c "from opensomeip._bridge import get_ext; ext = get_ext(); print('native:', ext is not None)"
\`\`\`

If this prints `native: False`, see the Troubleshooting section below.
\`\`\`

## Backward compatibility

Code that relied on the silent stub behavior (e.g. unit tests that import opensomeip without the C++ extension) will now get exceptions. This is intentional — the stubs were never producing correct results. Tests that need stub behavior should mock the transport/RPC layer explicitly.

## Related

- [jumpstarter-dev/jumpstarter#628](https://github.com/jumpstarter-dev/jumpstarter/issues/628) — downstream report of empty payloads
- Troubleshooting section in README.md — documents the macOS extension loading issue

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions