diff --git a/extern/opensomeip b/extern/opensomeip index c66051f..23ff711 160000 --- a/extern/opensomeip +++ b/extern/opensomeip @@ -1 +1 @@ -Subproject commit c66051facd4d01c5c8ccf8d70fc5bb7f903c10c9 +Subproject commit 23ff71157c6b7a6b040884f7b17470adea60c6d6 diff --git a/pyproject.toml b/pyproject.toml index b523647..eb18505 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -145,4 +145,4 @@ name = "Miscellaneous" showcontent = false [tool.opensomeip] -opensomeip-version = "0.1.0" +opensomeip-version = "0.2.0" diff --git a/src/_opensomeip/e2e_bind.cpp b/src/_opensomeip/e2e_bind.cpp index f1dfeb1..e6ce36f 100644 --- a/src/_opensomeip/e2e_bind.cpp +++ b/src/_opensomeip/e2e_bind.cpp @@ -45,7 +45,10 @@ void init_e2e(py::module_& m) { .def_readwrite("profile_id", &E2EConfig::profile_id) .def_readwrite("profile_name", &E2EConfig::profile_name) .def_readwrite("data_id", &E2EConfig::data_id) - .def_readwrite("offset", &E2EConfig::offset) + .def_readwrite("offset_bits", &E2EConfig::offset_bits) + .def_property_readonly_static( + "DEFAULT_OFFSET_BITS", + [](py::object /*cls*/) { return E2EConfig::DEFAULT_OFFSET_BITS; }) .def_readwrite("enable_crc", &E2EConfig::enable_crc) .def_readwrite("enable_counter", &E2EConfig::enable_counter) .def_readwrite("enable_freshness", &E2EConfig::enable_freshness) diff --git a/src/_opensomeip/events_bind.cpp b/src/_opensomeip/events_bind.cpp index acbe434..779b046 100644 --- a/src/_opensomeip/events_bind.cpp +++ b/src/_opensomeip/events_bind.cpp @@ -112,9 +112,19 @@ void init_events(py::module_& m) { py::call_guard()) .def("set_default_client_endpoint", &EventPublisher::set_default_client_endpoint, py::arg("address"), py::arg("port")) - .def("handle_subscription", &EventPublisher::handle_subscription, + .def("handle_subscription", + static_cast&)>( + &EventPublisher::handle_subscription), py::arg("eventgroup_id"), py::arg("client_id"), py::arg("filters") = std::vector{}) + .def("handle_subscription", + static_cast&)>( + &EventPublisher::handle_subscription), + py::arg("eventgroup_id"), py::arg("client_id"), + py::arg("ttl_seconds"), + py::arg("filters") = std::vector{}) .def("handle_unsubscription", &EventPublisher::handle_unsubscription, py::arg("eventgroup_id"), py::arg("client_id")) .def("get_registered_events", &EventPublisher::get_registered_events) diff --git a/src/_opensomeip/message_bind.cpp b/src/_opensomeip/message_bind.cpp index 17d117c..cc70ab4 100644 --- a/src/_opensomeip/message_bind.cpp +++ b/src/_opensomeip/message_bind.cpp @@ -52,6 +52,11 @@ void init_message(py::module_& m) { std::vector vec(sv.begin(), sv.end()); return msg.deserialize(vec); }, py::arg("data")) + .def("try_deserialize", [](Message& msg, py::bytes data) { + auto sv = static_cast(data); + std::vector vec(sv.begin(), sv.end()); + return msg.try_deserialize(vec); + }, py::arg("data")) // Validation .def("is_valid", &Message::is_valid) diff --git a/src/_opensomeip/rpc_bind.cpp b/src/_opensomeip/rpc_bind.cpp index abdafb7..8e29869 100644 --- a/src/_opensomeip/rpc_bind.cpp +++ b/src/_opensomeip/rpc_bind.cpp @@ -20,8 +20,13 @@ void init_rpc(py::module_& m) { .value("INVALID_PARAMETERS", RpcResult::INVALID_PARAMETERS) .value("METHOD_NOT_FOUND", RpcResult::METHOD_NOT_FOUND) .value("SERVICE_NOT_AVAILABLE", RpcResult::SERVICE_NOT_AVAILABLE) + .value("WRONG_INTERFACE_VERSION", RpcResult::WRONG_INTERFACE_VERSION) .value("INTERNAL_ERROR", RpcResult::INTERNAL_ERROR); + py::enum_(rpc, "MethodSemantics") + .value("REQUEST_RESPONSE", MethodSemantics::REQUEST_RESPONSE) + .value("FIRE_AND_FORGET", MethodSemantics::FIRE_AND_FORGET); + py::class_(rpc, "RpcTimeout") .def(py::init<>()) .def_readwrite("request_timeout", &RpcTimeout::request_timeout) @@ -65,18 +70,52 @@ void init_rpc(py::module_& m) { .def_readonly("timeout_calls", &RpcClient::Statistics::timeout_calls) .def_readonly("average_response_time", &RpcClient::Statistics::average_response_time); rpc_client - .def(py::init(), py::arg("client_id")) + .def(py::init(), + py::arg("client_id"), + py::arg("interface_version") = static_cast(0x01), + py::arg("local_bind") = someip::transport::Endpoint("0.0.0.0", 0)) .def("initialize", &RpcClient::initialize) .def("shutdown", &RpcClient::shutdown, py::call_guard()) - .def("call_method_sync", &RpcClient::call_method_sync, + .def("set_remote_endpoint", &RpcClient::set_remote_endpoint, py::arg("endpoint")) + .def("get_local_endpoint", &RpcClient::get_local_endpoint) + .def("call_method_sync", + static_cast( + &RpcClient::call_method_sync), py::arg("service_id"), py::arg("method_id"), py::arg("parameters"), py::arg("timeout") = RpcTimeout(), py::call_guard()) - .def("call_method_async", &RpcClient::call_method_async, + .def("call_method_sync", + static_cast( + &RpcClient::call_method_sync), + py::arg("service_id"), py::arg("method_id"), + py::arg("parameters"), py::arg("server_endpoint"), + py::arg("timeout") = RpcTimeout(), + py::call_guard()) + .def("call_method_async", + static_cast( + &RpcClient::call_method_async), py::arg("service_id"), py::arg("method_id"), py::arg("parameters"), py::arg("callback"), py::arg("timeout") = RpcTimeout(), py::call_guard()) + .def("call_method_async", + static_cast( + &RpcClient::call_method_async), + py::arg("service_id"), py::arg("method_id"), + py::arg("parameters"), py::arg("callback"), + py::arg("server_endpoint"), py::arg("timeout") = RpcTimeout(), + py::call_guard()) + .def("send_request_no_return", &RpcClient::send_request_no_return, + py::arg("service_id"), py::arg("method_id"), + py::arg("parameters"), py::arg("dest"), + py::call_guard()) .def("cancel_call", &RpcClient::cancel_call, py::arg("handle")) .def("is_ready", &RpcClient::is_ready) .def("get_statistics", &RpcClient::get_statistics); @@ -90,11 +129,17 @@ void init_rpc(py::module_& m) { .def_readonly("method_not_found_errors", &RpcServer::Statistics::method_not_found_errors) .def_readonly("average_processing_time", &RpcServer::Statistics::average_processing_time); rpc_server - .def(py::init(), py::arg("service_id")) + .def(py::init(), + py::arg("service_id"), + py::arg("interface_version") = static_cast(0x01), + py::arg("bind_endpoint") = someip::transport::Endpoint( + "127.0.0.1", SOMEIP_DEFAULT_RPC_PORT)) .def("initialize", &RpcServer::initialize) .def("shutdown", &RpcServer::shutdown, py::call_guard()) .def("register_method", &RpcServer::register_method, - py::arg("method_id"), py::arg("handler")) + py::arg("method_id"), py::arg("handler"), + py::arg("semantics") = MethodSemantics::REQUEST_RESPONSE) + .def("get_local_endpoint", &RpcServer::get_local_endpoint) .def("unregister_method", &RpcServer::unregister_method, py::arg("method_id")) .def("is_method_registered", &RpcServer::is_method_registered, py::arg("method_id")) .def("get_registered_methods", &RpcServer::get_registered_methods) diff --git a/src/_opensomeip/sd_bind.cpp b/src/_opensomeip/sd_bind.cpp index 9fb0bdc..aff077a 100644 --- a/src/_opensomeip/sd_bind.cpp +++ b/src/_opensomeip/sd_bind.cpp @@ -118,6 +118,9 @@ void init_sd(py::module_& m) { py::arg("service_id"), py::arg("instance_id"), py::arg("eventgroup_id")) .def("get_available_services", &SdClient::get_available_services, py::arg("service_id") = 0) + .def("get_eventgroup_subscription_state", + &SdClient::get_eventgroup_subscription_state, + py::arg("service_id"), py::arg("instance_id"), py::arg("eventgroup_id")) .def("is_ready", &SdClient::is_ready) .def("get_statistics", &SdClient::get_statistics); diff --git a/src/_opensomeip/tp_bind.cpp b/src/_opensomeip/tp_bind.cpp index 72bb1a2..98a04c3 100644 --- a/src/_opensomeip/tp_bind.cpp +++ b/src/_opensomeip/tp_bind.cpp @@ -58,13 +58,32 @@ void init_tp(py::module_& m) { .def_readwrite("segment_offset", &TpSegmentHeader::segment_offset) .def_readwrite("segment_length", &TpSegmentHeader::segment_length) .def_readwrite("sequence_number", &TpSegmentHeader::sequence_number) - .def_readwrite("message_type", &TpSegmentHeader::message_type); + .def_readwrite("message_type", &TpSegmentHeader::message_type) + .def_readwrite("service_id", &TpSegmentHeader::service_id) + .def_readwrite("method_id", &TpSegmentHeader::method_id) + .def_readwrite("client_id", &TpSegmentHeader::client_id) + .def_readwrite("session_id", &TpSegmentHeader::session_id) + .def_readwrite("protocol_version", &TpSegmentHeader::protocol_version) + .def_readwrite("interface_version", &TpSegmentHeader::interface_version); py::class_(tp, "TpSegment") .def(py::init<>()) .def_readwrite("header", &TpSegment::header) .def_readwrite("payload", &TpSegment::payload) - .def_readwrite("retransmit_count", &TpSegment::retransmit_count); + .def_readwrite("retransmit_count", &TpSegment::retransmit_count) + .def_readwrite("sender_ipv4", &TpSegment::sender_ipv4) + .def_readwrite("sender_port", &TpSegment::sender_port); + + py::class_(tp, "TpReassemblyKey") + .def(py::init<>()) + .def_readwrite("message_id", &TpReassemblyKey::message_id) + .def_readwrite("protocol_version", &TpReassemblyKey::protocol_version) + .def_readwrite("interface_version", &TpReassemblyKey::interface_version) + .def_readwrite("message_type", &TpReassemblyKey::message_type) + .def_readwrite("request_id", &TpReassemblyKey::request_id) + .def_readwrite("sender_ipv4", &TpReassemblyKey::sender_ipv4) + .def_readwrite("sender_port", &TpReassemblyKey::sender_port) + .def("__eq__", &TpReassemblyKey::operator==); py::class_(tp, "TpStatistics") .def(py::init<>()) @@ -84,10 +103,27 @@ void init_tp(py::module_& m) { py::class_(tp, "TpReassembler") .def(py::init(), py::arg("config") = TpConfig()) - .def("process_segment", &TpReassembler::process_segment, + .def("process_segment", + [](TpReassembler& reassembler, const TpSegment& segment, + someip::platform::ByteBuffer& complete_message) { + return reassembler.process_segment(segment, complete_message, nullptr); + }, py::arg("segment"), py::arg("complete_message")) - .def("is_reassembling", &TpReassembler::is_reassembling, py::arg("message_id")) - .def("cancel_reassembly", &TpReassembler::cancel_reassembly, py::arg("message_id")) + .def("is_reassembling", + static_cast( + &TpReassembler::is_reassembling), + py::arg("message_id")) + .def("is_reassembling", + static_cast( + &TpReassembler::is_reassembling), + py::arg("key")) + .def("cancel_reassembly", + static_cast(&TpReassembler::cancel_reassembly), + py::arg("message_id")) + .def("cancel_reassembly", + static_cast( + &TpReassembler::cancel_reassembly), + py::arg("key")) .def("process_timeouts", &TpReassembler::process_timeouts) .def("get_active_reassemblies", &TpReassembler::get_active_reassemblies) .def("update_config", &TpReassembler::update_config, py::arg("config")); @@ -118,6 +154,7 @@ void init_tp(py::module_& m) { .def("set_progress_callback", &TpManager::set_progress_callback, py::arg("callback")) .def("set_message_callback", &TpManager::set_message_callback, py::arg("callback")) .def("process_timeouts", &TpManager::process_timeouts) - .def("get_statistics", &TpManager::get_statistics) + .def("get_sender_statistics", &TpManager::get_sender_statistics) + .def("get_receiver_statistics", &TpManager::get_receiver_statistics) .def("update_config", &TpManager::update_config, py::arg("config")); } diff --git a/src/_opensomeip/transport_bind.cpp b/src/_opensomeip/transport_bind.cpp index a9d9830..a9c2eb9 100644 --- a/src/_opensomeip/transport_bind.cpp +++ b/src/_opensomeip/transport_bind.cpp @@ -3,6 +3,7 @@ #include #include "transport/endpoint.h" +#include "transport/message_rejection.h" #include "transport/transport.h" #include "transport/udp_transport.h" #include "transport/tcp_transport.h" @@ -33,6 +34,11 @@ struct PyTransportListener : ITransportListener { py::gil_scoped_acquire gil; PYBIND11_OVERRIDE_PURE(void, ITransportListener, on_error, error); } + + void on_message_rejected(const MessageRejectionInfo& info) override { + py::gil_scoped_acquire gil; + PYBIND11_OVERRIDE(void, ITransportListener, on_message_rejected, info); + } }; void init_transport(py::module_& m) { @@ -65,12 +71,31 @@ void init_transport(py::module_& m) { .value("CONNECTED", TcpConnectionState::CONNECTED) .value("DISCONNECTING", TcpConnectionState::DISCONNECTING); + py::enum_(m, "MessageRejectionStage") + .value("DESERIALIZE", MessageRejectionStage::DESERIALIZE) + .value("TCP_FRAMING", MessageRejectionStage::TCP_FRAMING) + .value("TP_REASSEMBLY", MessageRejectionStage::TP_REASSEMBLY) + .value("E2E_INTEGRITY", MessageRejectionStage::E2E_INTEGRITY); + + py::class_(m, "MessageRejectionInfo") + .def(py::init<>()) + .def_readwrite("sender", &MessageRejectionInfo::sender) + .def_readwrite("result", &MessageRejectionInfo::result) + .def_readwrite("stage", &MessageRejectionInfo::stage) + .def_readwrite("has_message_id", &MessageRejectionInfo::has_message_id) + .def_readwrite("message_id", &MessageRejectionInfo::message_id) + .def_readwrite("has_request_id", &MessageRejectionInfo::has_request_id) + .def_readwrite("request_id", &MessageRejectionInfo::request_id); + py::class_(m, "ITransportListener") .def(py::init<>()) - .def("on_message_received", &ITransportListener::on_message_received) + .def("on_message_received", + static_cast( + &ITransportListener::on_message_received)) .def("on_connection_lost", &ITransportListener::on_connection_lost) .def("on_connection_established", &ITransportListener::on_connection_established) - .def("on_error", &ITransportListener::on_error); + .def("on_error", &ITransportListener::on_error) + .def("on_message_rejected", &ITransportListener::on_message_rejected); py::class_(m, "UdpTransportConfig") .def(py::init<>()) @@ -82,7 +107,14 @@ void init_transport(py::module_& m) { .def_readwrite("enable_broadcast", &UdpTransportConfig::enable_broadcast) .def_readwrite("multicast_interface", &UdpTransportConfig::multicast_interface) .def_readwrite("multicast_ttl", &UdpTransportConfig::multicast_ttl) - .def_readwrite("max_message_size", &UdpTransportConfig::max_message_size); + .def_readwrite("max_message_size", &UdpTransportConfig::max_message_size) + .def_readwrite("enable_tp", &UdpTransportConfig::enable_tp); + + py::class_(m, "MulticastError") + .def(py::init<>()) + .def_readwrite("group_address", &MulticastError::group_address) + .def_readwrite("interface_address", &MulticastError::interface_address) + .def_readwrite("system_error", &MulticastError::system_error); py::class_(m, "UdpTransport") .def(py::init(), @@ -94,6 +126,11 @@ void init_transport(py::module_& m) { py::arg("message"), py::arg("endpoint"), py::call_guard()) .def("receive_message", &UdpTransport::receive_message) + .def("receive_message_with_sender", [](UdpTransport& transport) { + Endpoint sender; + MessagePtr message = transport.receive_message_with_sender(sender); + return py::make_tuple(std::move(message), sender); + }) .def("is_running", &UdpTransport::is_running) .def("is_connected", &UdpTransport::is_connected) .def("get_local_endpoint", &UdpTransport::get_local_endpoint) @@ -104,7 +141,8 @@ void init_transport(py::module_& m) { py::call_guard()) .def("leave_multicast_group", &UdpTransport::leave_multicast_group, py::arg("multicast_address"), - py::call_guard()); + py::call_guard()) + .def("last_multicast_error", &UdpTransport::last_multicast_error); py::class_(m, "TcpTransportConfig") .def(py::init<>()) @@ -133,6 +171,11 @@ void init_transport(py::module_& m) { py::arg("message"), py::arg("endpoint"), py::call_guard()) .def("receive_message", &TcpTransport::receive_message) + .def("receive_message_with_sender", [](TcpTransport& transport) { + Endpoint sender; + MessagePtr message = transport.receive_message_with_sender(sender); + return py::make_tuple(std::move(message), sender); + }) .def("is_running", &TcpTransport::is_running) .def("is_connected", &TcpTransport::is_connected) .def("get_local_endpoint", &TcpTransport::get_local_endpoint) @@ -142,7 +185,10 @@ void init_transport(py::module_& m) { .def("enable_server_mode", &TcpTransport::enable_server_mode, py::arg("backlog") = 5, py::call_guard()) - .def("accept_connection", &TcpTransport::accept_connection, + .def("connection_count", &TcpTransport::connection_count) + .def("max_connections", &TcpTransport::max_connections) + .def("is_peer_connected", &TcpTransport::is_peer_connected, py::arg("peer")) + .def("disconnect_peer", &TcpTransport::disconnect_peer, py::arg("peer"), py::call_guard()) .def_static("is_magic_cookie", &TcpTransport::is_magic_cookie, py::arg("data"), py::arg("offset") = 0) diff --git a/src/_opensomeip/types_bind.cpp b/src/_opensomeip/types_bind.cpp index 1681dde..2b794f2 100644 --- a/src/_opensomeip/types_bind.cpp +++ b/src/_opensomeip/types_bind.cpp @@ -50,6 +50,7 @@ void init_types(py::module_& m) { .value("CONNECTION_REFUSED", Result::CONNECTION_REFUSED) .value("TIMEOUT", Result::TIMEOUT) .value("INVALID_ENDPOINT", Result::INVALID_ENDPOINT) + .value("MULTICAST_ERROR", Result::MULTICAST_ERROR) .value("INVALID_MESSAGE", Result::INVALID_MESSAGE) .value("INVALID_MESSAGE_TYPE", Result::INVALID_MESSAGE_TYPE) .value("INVALID_SERVICE_ID", Result::INVALID_SERVICE_ID) diff --git a/src/opensomeip/events.py b/src/opensomeip/events.py index 2085253..080090b 100644 --- a/src/opensomeip/events.py +++ b/src/opensomeip/events.py @@ -163,6 +163,11 @@ def __init__( ext = get_ext() if ext is not None: self._cpp = ext.events.EventSubscriber(client_id) + # v0.2.0 no longer falls back to 127.0.0.1:30490 when the + # service endpoint was not resolved. + if transport.remote_endpoint is not None: + remote = transport.remote_endpoint + self._cpp.set_default_endpoint(remote.ip, remote.port) @property def is_running(self) -> bool: diff --git a/src/opensomeip/rpc.py b/src/opensomeip/rpc.py index 8db1ced..c4d2ca2 100644 --- a/src/opensomeip/rpc.py +++ b/src/opensomeip/rpc.py @@ -14,7 +14,7 @@ if TYPE_CHECKING: from typing_extensions import Self -from opensomeip._bridge import from_cpp_message, get_ext, to_cpp_message +from opensomeip._bridge import from_cpp_message, get_ext, to_cpp_endpoint, to_cpp_message from opensomeip.exceptions import RpcError from opensomeip.message import Message from opensomeip.receiver import MessageReceiver @@ -58,6 +58,9 @@ def __init__(self, transport: Transport, *, client_id: int = 0x0001) -> None: ext = get_ext() if ext is not None: self._cpp = ext.rpc.RpcClient(client_id) + # v0.2.0 no longer defaults application traffic to 127.0.0.1:30490. + if transport.remote_endpoint is not None: + self._cpp.set_remote_endpoint(to_cpp_endpoint(transport.remote_endpoint)) @property def is_running(self) -> bool: @@ -221,7 +224,9 @@ def __init__(self, transport: Transport, *, service_id: int = 0x0000) -> None: self._cpp: Any = None ext = get_ext() if ext is not None: - self._cpp = ext.rpc.RpcServer(service_id) + # v0.2.0 defaults this socket to 127.0.0.1:30501. Use an ephemeral + # port so it does not occupy an address the application chose. + self._cpp = ext.rpc.RpcServer(service_id, 0x01, ext.Endpoint("127.0.0.1", 0)) @property def is_running(self) -> bool: diff --git a/src/opensomeip/sd.py b/src/opensomeip/sd.py index 31de5c9..413b8f8 100644 --- a/src/opensomeip/sd.py +++ b/src/opensomeip/sd.py @@ -204,14 +204,17 @@ def find( if self._cpp is not None: receiver = self._find_receiver - def _on_found(cpp_svc: Any) -> None: - py_svc = from_cpp_service_instance(cpp_svc) - from opensomeip.message import Message - - msg = Message(payload=repr(py_svc).encode()) - receiver.put(msg) - if callback is not None: - callback(py_svc) + def _on_found(found: Any) -> None: + # Native find_service passes a sequence of ServiceInstance. + services = found if isinstance(found, (list, tuple)) else [found] + for cpp_svc in services: + py_svc = from_cpp_service_instance(cpp_svc) + from opensomeip.message import Message + + msg = Message(payload=repr(py_svc).encode()) + receiver.put(msg) + if callback is not None: + callback(py_svc) try: self._cpp.find_service(service.service_id, _on_found) diff --git a/src/opensomeip/tp.py b/src/opensomeip/tp.py index 599a314..b6ecb82 100644 --- a/src/opensomeip/tp.py +++ b/src/opensomeip/tp.py @@ -129,12 +129,31 @@ def reassembled(self) -> MessageReceiver: """Return a :class:`MessageReceiver` yielding fully reassembled messages.""" return self._reassembly_receiver - def get_statistics(self) -> Any: - """Return TP statistics (native only).""" + def get_sender_statistics(self) -> Any: + """Return sender-path TP statistics (native only).""" + if self._cpp is not None: + return self._cpp.get_sender_statistics() + return None + + def get_receiver_statistics(self) -> Any: + """Return receiver-path TP statistics (native only).""" if self._cpp is not None: - return self._cpp.get_statistics() + return self._cpp.get_receiver_statistics() return None + def get_statistics(self) -> Any: + """Return TP statistics (native only). + + opensomeip v0.2.0 reports sender and receiver counters separately. + This helper returns both snapshots. + """ + if self._cpp is None: + return None + return { + "sender": self.get_sender_statistics(), + "receiver": self.get_receiver_statistics(), + } + def __enter__(self) -> Self: self.start() return self diff --git a/tests/unit/test_coverage_gaps.py b/tests/unit/test_coverage_gaps.py index 821ed7b..335103a 100644 --- a/tests/unit/test_coverage_gaps.py +++ b/tests/unit/test_coverage_gaps.py @@ -272,6 +272,7 @@ def test_get_statistics_returns_none(self) -> None: manager = TpManager(t) with patch.object(manager, "_cpp", None): - result = manager.get_statistics() - assert result is None + assert manager.get_statistics() is None + assert manager.get_sender_statistics() is None + assert manager.get_receiver_statistics() is None t.stop()