diff --git a/README.md b/README.md index 1d0efa7..343b2b6 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ pip3 install someipy ## Example Applications -In the directory [example_apps](./example_apps/), examples including explanations, can be found for using the someipy library. In [temperature_msg.py](./example_apps/temperature_msg.py), a payload interface "TemperatureMsg" is defined, which can be serialized and deserialized. In [send_events.py](./example_apps/send_events.py), the service discovery and two services are instantiated. The "TemperatureMsg" is serialized and used as the payload for sending events. +In the directory [example_apps](./example_apps/), examples including explanations, can be found for using the someipy library. ## Supported Features, Limitations and Deviations @@ -64,7 +64,6 @@ The library is still under development. The current major limitations and deviat - Configuration and load balancing options in SOME/IP SD messages are not supported. - TTL of Service Discovery entries is not checked yet. - The Initial Wait Phase and Repetition Phase of the Service Discovery specification are skipped. For simplification, the Main Phase is directly entered, i.e. SD Offer Entries are immediately sent cyclically. -- Multiple Service Discovery entries are not packed together in a single SD message, which is sent via UDP. ### De-/Serialization diff --git a/example_apps/call_method_tcp.py b/example_apps/call_method_tcp.py index 2042b96..df7b925 100644 --- a/example_apps/call_method_tcp.py +++ b/example_apps/call_method_tcp.py @@ -3,9 +3,8 @@ import logging import sys -from someipy import TransportLayerProtocol +from someipy import TransportLayerProtocol, ReturnCode, MessageType from someipy.client_service_instance import ( - MethodResult, construct_client_service_instance, ) from someipy.service import ServiceBuilder @@ -65,31 +64,42 @@ async def main(): try: while True: - method_parameter = Addends(addend1=1, addend2=2) - # The call method function returns a tuple with the first element being a MethodResult enum - method_success, method_result = await client_instance_addition.call_method( - SAMPLE_METHOD_ID, method_parameter.serialize() - ) - # Check the result of the method call and handle it accordingly - if method_success == MethodResult.SUCCESS: - print( - f"Received result for method: {' '.join(f'0x{b:02x}' for b in method_result)}" + try: + # You can query if the service offering the method was already found via SOME/IP service discovery + print(f"Service found: {client_instance_addition.service_found()}") + + while not client_instance_addition.service_found(): + print("Waiting for service..") + await asyncio.sleep(0.5) + + # The call_method function can raise an error, e.g. if no TCP connection to the server can be established + # In case there is an application specific error in the server, the server still returns a response and the + # message_type and return_code are evaluated. + method_result = await client_instance_addition.call_method( + SAMPLE_METHOD_ID, method_parameter.serialize() ) - try: - sum = Sum().deserialize(method_result) - print(f"Sum: {sum.value.value}") - except Exception as e: - print(f"Error during deserialization of method's result: {e}") - elif method_success == MethodResult.ERROR: - print("Method call failed..") - elif method_success == MethodResult.TIMEOUT: - print("Method call timed out..") - elif method_success == MethodResult.SERVICE_NOT_FOUND: - print("Service not yet available..") - - await asyncio.sleep(2) + + if method_result.message_type == MessageType.RESPONSE: + print( + f"Received result for method: {' '.join(f'0x{b:02x}' for b in method_result.payload)}" + ) + if method_result.return_code == ReturnCode.E_OK: + sum = Sum().deserialize(method_result.payload) + print(f"Sum: {sum.value.value}") + else: + print( + f"Method call returned an error: {method_result.return_code}" + ) + elif method_result.message_type == MessageType.ERROR: + print("Server returned an error..") + # In case the server includes an error message in the payload, it can be deserialized and printed + + except Exception as e: + print(f"Error during method call: {e}") + + await asyncio.sleep(1) # When the application is canceled by the user, the asyncio.CancelledError is raised except asyncio.CancelledError: diff --git a/example_apps/call_method_udp.py b/example_apps/call_method_udp.py index 1cbdc38..7c20d77 100644 --- a/example_apps/call_method_udp.py +++ b/example_apps/call_method_udp.py @@ -4,7 +4,7 @@ import logging import sys -from someipy import TransportLayerProtocol +from someipy import TransportLayerProtocol, MessageType, ReturnCode from someipy.client_service_instance import ( MethodResult, construct_client_service_instance, @@ -36,8 +36,6 @@ async def main(): interface_ip = sys.argv[i + 1] break - print(interface_ip) - # Since the construction of the class ServiceDiscoveryProtocol is not trivial and would require an async __init__ function # use the construct_service_discovery function # The local interface IP address needs to be passed so that the src-address of all SD UDP packets is correctly set @@ -69,26 +67,42 @@ async def main(): try: while True: method_parameter = Addends(addend1=1, addend2=2) - method_success, method_result = await client_instance_addition.call_method( - SAMPLE_METHOD_ID, method_parameter.serialize() - ) - if method_success == MethodResult.SUCCESS: - print( - f"Received result for method: {' '.join(f'0x{b:02x}' for b in method_result)}" + + try: + # You can query if the service offering the method was already found via SOME/IP service discovery + print(f"Service found: {client_instance_addition.service_found()}") + + while not client_instance_addition.service_found(): + print("Waiting for service..") + await asyncio.sleep(0.5) + + # The call_method function can raise an error, e.g. if no TCP connection to the server can be established + # In case there is an application specific error in the server, the server still returns a response and the + # message_type and return_code are evaluated. + method_result = await client_instance_addition.call_method( + SAMPLE_METHOD_ID, method_parameter.serialize() ) - try: - sum = Sum().deserialize(method_result) - print(f"Sum: {sum.value.value}") - except Exception as e: - print(f"Error during deserialization of method's result: {e}") - elif method_success == MethodResult.ERROR: - print("Method call failed..") - elif method_success == MethodResult.TIMEOUT: - print("Method call timed out..") - elif method_success == MethodResult.SERVICE_NOT_FOUND: - print("Service not yet available..") + + if method_result.message_type == MessageType.RESPONSE: + print( + f"Received result for method: {' '.join(f'0x{b:02x}' for b in method_result.payload)}" + ) + if method_result.return_code == ReturnCode.E_OK: + sum = Sum().deserialize(method_result.payload) + print(f"Sum: {sum.value.value}") + else: + print( + f"Method call returned an error: {method_result.return_code}" + ) + elif method_result.message_type == MessageType.ERROR: + print("Server returned an error..") + # In case the server includes an error message in the payload, it can be deserialized and printed + + except Exception as e: + print(f"Error during method call: {e}") await asyncio.sleep(1) + except asyncio.CancelledError: print("Shutdown..") finally: diff --git a/example_apps/offer_method_tcp.py b/example_apps/offer_method_tcp.py index f1d4856..b07acad 100644 --- a/example_apps/offer_method_tcp.py +++ b/example_apps/offer_method_tcp.py @@ -4,7 +4,7 @@ import sys from typing import Tuple -from someipy import TransportLayerProtocol +from someipy import TransportLayerProtocol, MethodResult, MessageType, ReturnCode from someipy.service import ServiceBuilder, Method from someipy.service_discovery import construct_service_discovery from someipy.server_service_instance import construct_server_service_instance @@ -21,29 +21,34 @@ SAMPLE_METHOD_ID = 0x0123 -def add_method_handler(input_data: bytes, addr: Tuple[str, int]) -> Tuple[bool, bytes]: - # Process the data and return True/False indicating the success of the operation - # and the result of the method call in serialized form (bytes object) - # If False is returned an error message will be sent back to the client. In that case - # the payload can be an empty bytes-object, e.g. return False, b"" - +def add_method_handler(input_data: bytes, addr: Tuple[str, int]) -> MethodResult: print( f"Received data: {' '.join(f'0x{b:02x}' for b in input_data)} from IP: {addr[0]} Port: {addr[1]}" ) + result = MethodResult() + try: # Deserialize the input data addends = Addends() addends.deserialize(input_data) except Exception as e: print(f"Error during deserialization: {e}") - return False, b"" + + # Set the return code to E_MALFORMED_MESSAGE and return + result.message_type = MessageType.RESPONSE + result.return_code = ReturnCode.E_MALFORMED_MESSAGE + return result # Perform the addition sum = Sum() sum.value = Sint32(addends.addend1.value + addends.addend2.value) print(f"Send back: {' '.join(f'0x{b:02x}' for b in sum.serialize())}") - return True, sum.serialize() + + result.message_type = MessageType.RESPONSE + result.return_code = ReturnCode.E_OK + result.payload = sum.serialize() + return result async def main(): diff --git a/example_apps/offer_method_udp.py b/example_apps/offer_method_udp.py index 728ff3d..e8a0731 100644 --- a/example_apps/offer_method_udp.py +++ b/example_apps/offer_method_udp.py @@ -4,7 +4,7 @@ import sys from typing import Tuple -from someipy import TransportLayerProtocol +from someipy import TransportLayerProtocol, MethodResult, ReturnCode, MessageType from someipy.service import ServiceBuilder, Method from someipy.service_discovery import construct_service_discovery from someipy.server_service_instance import construct_server_service_instance @@ -21,29 +21,34 @@ SAMPLE_METHOD_ID = 0x0123 -def add_method_handler(input_data: bytes, addr: Tuple[str, int]) -> Tuple[bool, bytes]: - # Process the data and return True/False indicating the success of the operation - # and the result of the method call in serialized form (bytes object) - # If False is returned an error message will be sent back to the client. In that case - # the payload can be an empty bytes-object, e.g. return False, b"" - +def add_method_handler(input_data: bytes, addr: Tuple[str, int]) -> MethodResult: print( f"Received data: {' '.join(f'0x{b:02x}' for b in input_data)} from IP: {addr[0]} Port: {addr[1]}" ) + result = MethodResult() + try: # Deserialize the input data addends = Addends() addends.deserialize(input_data) except Exception as e: print(f"Error during deserialization: {e}") - return False, b"" + + # Set the return code to E_MALFORMED_MESSAGE and return + result.message_type = MessageType.RESPONSE + result.return_code = ReturnCode.E_MALFORMED_MESSAGE + return result # Perform the addition sum = Sum() sum.value = Sint32(addends.addend1.value + addends.addend2.value) print(f"Send back: {' '.join(f'0x{b:02x}' for b in sum.serialize())}") - return True, sum.serialize() + + result.message_type = MessageType.RESPONSE + result.return_code = ReturnCode.E_OK + result.payload = sum.serialize() + return result async def main(): diff --git a/example_apps/receive_events_udp.py b/example_apps/receive_events_udp.py index 23588e9..27d09ba 100644 --- a/example_apps/receive_events_udp.py +++ b/example_apps/receive_events_udp.py @@ -30,7 +30,9 @@ def temperature_callback(someip_message: SomeIpMessage) -> None: None: This function does not return anything. """ try: - print(f"Received {len(someip_message.payload)} bytes. Try to deserialize..") + print( + f"Received {len(someip_message.payload)} bytes for event {someip_message.header.method_id}. Try to deserialize.." + ) temperature_msg = TemparatureMsg().deserialize(someip_message.payload) print(temperature_msg) except Exception as e: @@ -69,7 +71,6 @@ async def main(): ServiceBuilder() .with_service_id(SAMPLE_SERVICE_ID) .with_major_version(1) - .with_eventgroup(temperature_eventgroup) .build() ) diff --git a/setup.cfg b/setup.cfg index 74c88c9..95b8a18 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = someipy -version = 0.0.7 +version = 0.0.8 author = Christian H. author_email = someipy.package@gmail.com description = A Python package implementing the SOME/IP protocol diff --git a/src/someipy/__init__.py b/src/someipy/__init__.py index 8de8f51..624de8a 100644 --- a/src/someipy/__init__.py +++ b/src/someipy/__init__.py @@ -11,3 +11,6 @@ ) # noqa: F401 from ._internal.someip_message import SomeIpMessage # noqa: F401 +from ._internal.method_result import MethodResult # noqa: F401 +from ._internal.return_codes import ReturnCode # noqa: F401 +from ._internal.message_types import MessageType # noqa: F401 diff --git a/src/someipy/_internal/message_types.py b/src/someipy/_internal/message_types.py index 4a0c74a..81fce54 100644 --- a/src/someipy/_internal/message_types.py +++ b/src/someipy/_internal/message_types.py @@ -1,20 +1,21 @@ # Copyright (C) 2024 Christian H. -# +# # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program. If not, see . from enum import Enum + class MessageType(Enum): REQUEST = 0x00 REQUEST_NO_RETURN = 0x01 @@ -24,19 +25,5 @@ class MessageType(Enum): TP_REQUEST = 0x20 TP_REQUEST_NO_RETURN = 0x21 TP_NOTIFICATION = 0x22 - TP_RESPONSE = 0xa0 - TP_ERROR = 0xa1 - -# PRS_SOMEIP_00191 -class ReturnCode(Enum): - E_OK = 0x00 # No error occurred - E_NOT_OK = 0x01 # An unspecified error occurred - E_UNKNOWN_SERVICE = 0x02 # The requested Service ID is unknown. - E_UNKNOWN_METHOD = 0x03 # The requested Method ID is unknown. - E_NOT_READY = 0x04 # Service ID and Method ID are known. Application not running. - E_NOT_REACHABLE = 0x05 # System running the service is not reachable (inter-nal error code only). - E_TIMEOUT = 0x06 # A timeout occurred (internal error code only). - E_WRONG_PROTOCOL_VERSION = 0x07 # Version of SOME/IP protocol not supported - E_WRONG_INTERFACE_VERSION = 0x08 # Interface version mismatch - E_MALFORMED_MESSAGE = 0x09 # Deserialization error, so that payload cannot be de-serialized. - E_WRONG_MESSAGE_TYPE = 0x0a # An unexpected message type was received (e.g. REQUEST_NO_RETURN for a method defined as REQUEST). + TP_RESPONSE = 0xA0 + TP_ERROR = 0xA1 diff --git a/src/someipy/_internal/method_result.py b/src/someipy/_internal/method_result.py new file mode 100644 index 0000000..e3b643e --- /dev/null +++ b/src/someipy/_internal/method_result.py @@ -0,0 +1,13 @@ +from .message_types import MessageType +from .return_codes import ReturnCode + + +class MethodResult: + message_type: MessageType + return_code: ReturnCode + payload: bytes + + def __init__(self): + self.message_type = MessageType.RESPONSE + self.return_code = ReturnCode.E_OK + self.payload = b"" diff --git a/src/someipy/_internal/return_codes.py b/src/someipy/_internal/return_codes.py new file mode 100644 index 0000000..5ac3ef8 --- /dev/null +++ b/src/someipy/_internal/return_codes.py @@ -0,0 +1,35 @@ +# Copyright (C) 2024 Christian H. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from enum import Enum + + +# PRS_SOMEIP_00191 +class ReturnCode(Enum): + E_OK = 0x00 # No error occurred + E_NOT_OK = 0x01 # An unspecified error occurred + E_UNKNOWN_SERVICE = 0x02 # The requested Service ID is unknown. + E_UNKNOWN_METHOD = 0x03 # The requested Method ID is unknown. + E_NOT_READY = 0x04 # Service ID and Method ID are known. Application not running. + E_NOT_REACHABLE = ( + 0x05 # System running the service is not reachable (internal error code only). + ) + E_TIMEOUT = 0x06 # A timeout occurred (internal error code only). + E_WRONG_PROTOCOL_VERSION = 0x07 # Version of SOME/IP protocol not supported + E_WRONG_INTERFACE_VERSION = 0x08 # Interface version mismatch + E_MALFORMED_MESSAGE = ( + 0x09 # Deserialization error, so that payload cannot be de-serialized. + ) + E_WRONG_MESSAGE_TYPE = 0x0A # An unexpected message type was received (e.g. REQUEST_NO_RETURN for a method defined as REQUEST). diff --git a/src/someipy/client_service_instance.py b/src/someipy/client_service_instance.py index c0195cd..b2dce58 100644 --- a/src/someipy/client_service_instance.py +++ b/src/someipy/client_service_instance.py @@ -19,6 +19,7 @@ from typing import Iterable, Tuple, Callable, Set, List from someipy import Service +from someipy._internal.method_result import MethodResult from someipy._internal.someip_data_processor import SomeipDataProcessor from someipy._internal.someip_sd_header import ( SdService, @@ -40,6 +41,7 @@ ) from someipy._internal.logging import get_logger from someipy._internal.message_types import MessageType +from someipy._internal.return_codes import ReturnCode from someipy._internal.someip_endpoint import ( SomeipEndpoint, UDPSomeipEndpoint, @@ -50,13 +52,6 @@ _logger_name = "client_service_instance" -class MethodResult(Enum): - SUCCESS = 0 - TIMEOUT = 1 - SERVICE_NOT_FOUND = 2 - ERROR = 3 - - class ExpectedAck: def __init__(self, eventgroup_id: int) -> None: self.eventgroup_id = eventgroup_id @@ -139,10 +134,10 @@ def register_callback(self, callback: Callable[[SomeIpMessage], None]) -> None: """ self._callback = callback - async def call_method( - self, method_id: int, payload: bytes - ) -> Tuple[MethodResult, bytes]: - get_logger(_logger_name).debug(f"Try to call method 0x{method_id:04X}") + def service_found(self) -> bool: + """ + Returns whether the service instance represented by the ClientServiceInstance has been offered by a server and was found. + """ has_service = False for s in self._found_services: if ( @@ -151,12 +146,33 @@ async def call_method( ): has_service = True break + return has_service + + async def call_method(self, method_id: int, payload: bytes) -> MethodResult: + """ + Calls a method on the service instance represented by the ClientServiceInstance. + + Args: + method_id (int): The ID of the method to call. + payload (bytes): The payload to send with the method call. + + Returns: + MethodResult: The result of the method call which can contain an error or a successfull result including the response payload. + + Raises: + RuntimeError: If the TCP connection to the server cannot be established or if the server service has not been found yet. + asyncio.TimeoutError: If the method call times out, i.e. the server does not send back a response within one second. + """ - if not has_service: - get_logger(_logger_name).warn( - f"Do not execute method call. Service 0x{self._service.id:04X} with instance 0x{self._instance_id:04X} not found." + get_logger(_logger_name).debug(f"Try to call method 0x{method_id:04X}") + + if not self.service_found(): + get_logger(_logger_name).warning( + f"Method 0x{method_id:04x} called, but service 0x{self._service.id:04X} with instance 0x{self._instance_id:04X} not found yet." + ) + raise RuntimeError( + f"Method 0x{method_id:04x} called, but service 0x{self._service.id:04X} with instance 0x{self._instance_id:04X} not found yet." ) - return MethodResult.SERVICE_NOT_FOUND, b"" header = SomeIpHeader( service_id=self._service.id, @@ -195,17 +211,21 @@ async def call_method( await asyncio.wait_for(self._tcp_connection_established_event.wait(), 2) except asyncio.TimeoutError: get_logger(_logger_name).error( - "Could not establish TCP connection for method call." + f"Cannot establish TCP connection to {dst_address}:{dst_port}." + ) + raise RuntimeError( + f"Cannot establish TCP connection to {dst_address}:{dst_port}." ) - return MethodResult.ERROR, b"" if self._tcp_connection.is_open(): self._tcp_connection.writer.write(someip_message.serialize()) else: get_logger(_logger_name).error( - "TCP connection for method call is not open." + f"TCP connection to {dst_address}:{dst_port} is not opened." + ) + raise RuntimeError( + f"TCP connection to {dst_address}:{dst_port} is not opened." ) - return MethodResult.ERROR, b"" else: # In case of UDP, just send out the datagram and wait for the response @@ -221,9 +241,9 @@ async def call_method( get_logger(_logger_name).error( f"Waiting on response for method call 0x{method_id:04X} timed out." ) - return MethodResult.TIMEOUT, b"" + raise - return MethodResult.SUCCESS, self._method_call_future.result() + return self._method_call_future.result() def someip_message_received( self, someip_message: SomeIpMessage, addr: Tuple[str, int] @@ -231,25 +251,22 @@ def someip_message_received( if ( someip_message.header.client_id == 0x00 and someip_message.header.message_type == MessageType.NOTIFICATION.value - and someip_message.header.return_code == 0x00 + and someip_message.header.return_code == ReturnCode.E_OK.value ): if self._callback is not None and self._subscription_active: self._callback(someip_message) - - if ( - someip_message.header.message_type == MessageType.RESPONSE.value - and someip_message.header.return_code == 0x00 - ): # E_OK - if self._method_call_future is not None: - self._method_call_future.set_result(someip_message.payload) return if ( - someip_message.header.message_type == MessageType.ERROR.value - and someip_message.header.return_code == 0x01 - ): # E_NOT_OK + someip_message.header.message_type == MessageType.RESPONSE.value + or someip_message.header.message_type == MessageType.ERROR.value + ): if self._method_call_future is not None: - self._method_call_future.set_result(b"") + result = MethodResult() + result.message_type = MessageType(someip_message.header.message_type) + result.return_code = ReturnCode(someip_message.header.return_code) + result.payload = someip_message.payload + self._method_call_future.set_result(result) return def subscribe_eventgroup(self, eventgroup_id: int): diff --git a/src/someipy/serialization.py b/src/someipy/serialization.py index 96b0515..b244699 100644 --- a/src/someipy/serialization.py +++ b/src/someipy/serialization.py @@ -1,15 +1,15 @@ # Copyright (C) 2024 Christian H. -# +# # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program. If not, see . @@ -440,10 +440,10 @@ def serialize(obj) -> bytes: """ Serializes an object into bytes by iterating over its attributes, excluding those starting with double underscores or underscores. For each attribute, it calls the `serialize` method of the attribute and appends the returned bytes to the output. - + Parameters: obj (object): The object to be serialized. - + Returns: bytes: The serialized representation of the object as bytes. """ @@ -460,7 +460,7 @@ def serialize(obj) -> bytes: class SomeIpPayload: """ - A base class for defining a custom Some/IP payload ("structs"). It can be recursively nested, i.e. a SomeIpPayload object may contain other SomeIpPayload objects. + A base class for defining a custom SOME/IP payload ("structs"). It can be recursively nested, i.e. a SomeIpPayload object may contain other SomeIpPayload objects. """ def __len__(self) -> int: @@ -514,11 +514,13 @@ def deserialize(self, payload: bytes): pos += type_length return self + T = TypeVar("T") + class SomeIpFixedSizeArray(Generic[T]): """ - A datatype for a SOME/IP fixed size array. This type shall be used with someipy datatypes that support serialization and deserialization. + A datatype for a SOME/IP fixed size array. This type shall be used with someipy datatypes that support serialization and deserialization. """ data: List[T] @@ -583,7 +585,7 @@ def serialize(self) -> bytes: """ Serialize the object into bytes by iterating over its attributes, excluding those starting with double underscores or underscores. For each attribute, it calls the `serialize` method of the attribute and appends the returned bytes to the output. - + Returns: bytes: The serialized representation of the object as bytes. """ diff --git a/src/someipy/server_service_instance.py b/src/someipy/server_service_instance.py index c67fff1..79b3295 100644 --- a/src/someipy/server_service_instance.py +++ b/src/someipy/server_service_instance.py @@ -20,7 +20,8 @@ from someipy.service import Service from someipy._internal.tcp_client_manager import TcpClientManager, TcpClientProtocol -from someipy._internal.message_types import MessageType, ReturnCode +from someipy._internal.message_types import MessageType +from someipy._internal.return_codes import ReturnCode from someipy._internal.someip_sd_builder import ( build_stop_offer_service_sd_header, build_subscribe_eventgroup_ack_entry, @@ -168,7 +169,7 @@ def send_response(): ) if header.service_id != self._service.id: - get_logger(_logger_name).warn( + get_logger(_logger_name).warning( f"Unknown service ID received from {addr}: ID 0x{header.service_id:04X}" ) header_to_return.message_type = MessageType.RESPONSE.value @@ -177,7 +178,7 @@ def send_response(): return if header.method_id not in self._service.methods.keys(): - get_logger(_logger_name).warn( + get_logger(_logger_name).warning( f"Unknown method ID received from {addr}: ID 0x{header.method_id:04X}" ) header_to_return.message_type = MessageType.RESPONSE.value @@ -192,17 +193,15 @@ def send_response(): and header.return_code == 0x00 ): method_handler = self._service.methods[header.method_id].method_handler - success, payload_result = method_handler(message.payload, addr) - if not success: - header_to_return.message_type = MessageType.ERROR.value - else: - header_to_return.message_type = MessageType.RESPONSE.value - payload_to_return = payload_result + result = method_handler(message.payload, addr) + header_to_return.message_type = result.message_type.value + header_to_return.return_code = result.return_code.value + payload_to_return = result.payload send_response() else: - get_logger(_logger_name).warn( + get_logger(_logger_name).warning( f"Unknown message type received from {addr}: Type 0x{header.message_type:04X}" ) diff --git a/src/someipy/service.py b/src/someipy/service.py index e7bc79f..58d08c8 100644 --- a/src/someipy/service.py +++ b/src/someipy/service.py @@ -1,29 +1,35 @@ # Copyright (C) 2024 Christian H. -# +# # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. -# +# # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -# +# # You should have received a copy of the GNU General Public License # along with this program. If not, see . from dataclasses import dataclass from typing import List, Callable, Tuple, Dict +from someipy._internal.method_result import MethodResult + + +MethodHandler = Callable[[bytes, Tuple[str, int]], MethodResult] + @dataclass class Method: """ Class representing a SOME/IP method with a method id and a method handler. """ + id: int - method_handler: Callable[[bytes, Tuple[str, int]], Tuple[bool, bytes]] + method_handler: MethodHandler def __eq__(self, __value: object) -> bool: return self.method_id == __value.method_id @@ -34,6 +40,7 @@ class EventGroup: """ Class representing a SOME/IP eventgroup with an eventgroup id and a list of event ids. """ + id: int event_ids: List[int] @@ -43,6 +50,7 @@ class Service: """ Class representing a SOME/IP service. A service has an id, major and minor version and 0 or more methods and/or eventgroups. """ + id: int major_version: int minor_version: int