Skip to content

Commit c82aaed

Browse files
jakubkristaJakub Krista
andauthored
Add vm_specific parameter to DoIPClient initialization (#60)
* Remove old 'log_level' parameter docstring from DoIPClient.__init__; add 'vm_specific' as an init argument and store it as a static value for the entire instance. Add .idea/ to .gitignore * Add tests; bugfix * increase version * cleanup * Move vm_specific validation to _validate_vm_specific_value; check vm_specific value also in request_activation; add tests --------- Co-authored-by: Jakub Krista <jakub.krista@digiteqautomotive.com>
1 parent 00c9718 commit c82aaed

File tree

4 files changed

+107
-6
lines changed

4 files changed

+107
-6
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,6 @@ dmypy.json
127127

128128
# Pyre type checker
129129
.pyre/
130+
131+
# Pycharm project settings
132+
.idea/

doipclient/client.py

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -145,10 +145,10 @@ class DoIPClient:
145145
:param use_secure: Enables TLS. If set to True, a default SSL context is used. For more control, a preconfigured
146146
SSL context can be passed directly. Untested. Should be combined with changing tcp_port to 3496.
147147
:type use_secure: Union[bool,ssl.SSLContext]
148-
:param log_level: Logging level
149-
:type log_level: int
150148
:param auto_reconnect_tcp: Attempt to automatically reconnect TCP sockets that were closed by peer
151149
:type auto_reconnect_tcp: bool
150+
:param vm_specific: Optional 4 byte long int
151+
:type vm_specific: int, optional
152152
153153
:raises ConnectionRefusedError: If the activation request fails
154154
:raises ValueError: If the IPAddress is neither an IPv4 nor an IPv6 address
@@ -166,6 +166,7 @@ def __init__(
166166
client_ip_address=None,
167167
use_secure=False,
168168
auto_reconnect_tcp=False,
169+
vm_specific=None
169170
):
170171
self._ecu_logical_address = ecu_logical_address
171172
self._client_logical_address = client_logical_address
@@ -180,6 +181,7 @@ def __init__(
180181
self._protocol_version = protocol_version
181182
self._auto_reconnect_tcp = auto_reconnect_tcp
182183
self._tcp_close_detected = False
184+
self.vm_specific = vm_specific
183185

184186
# Check the ECU IP type to determine socket family
185187
# Will raise ValueError if neither a valid IPv4, nor IPv6 address
@@ -587,16 +589,19 @@ def request_activation(
587589
:type disable_retry: bool, optional
588590
:return: The resulting activation response object
589591
:rtype: RoutingActivationResponse
592+
:raises ValueError: vm_specific is invalid or out of range
590593
"""
591594
message = RoutingActivationRequest(
592-
self._client_logical_address, activation_type, vm_specific=vm_specific
595+
self._client_logical_address,
596+
activation_type,
597+
vm_specific=self._validate_vm_specific_value(vm_specific) if vm_specific else self.vm_specific,
593598
)
594599
self.send_doip_message(message, disable_retry=disable_retry)
595600
while True:
596601
result = self.read_doip()
597-
if type(result) == RoutingActivationResponse:
602+
if isinstance(result, RoutingActivationResponse):
598603
return result
599-
elif result:
604+
if result:
600605
logger.warning(
601606
"Received unexpected DoIP message type {}. Ignoring".format(
602607
type(result)
@@ -824,3 +829,39 @@ def reconnect(self, close_delay=A_PROCESSING_TIME):
824829
raise ConnectionRefusedError(
825830
f"Activation Request failed with code {result.response_code}"
826831
)
832+
833+
@staticmethod
834+
def _validate_vm_specific_value(value):
835+
"""Validate the VM specific value (must be > 0 and <= 0xffffffff) or None.
836+
If the conditions are not fulfilled, raises an exception.
837+
838+
:param value: The value to check.
839+
:type value: int, optional
840+
:return: The input value if valid.
841+
:rtype: int or None
842+
:raises ValueError: If the value is invalid or out of range.
843+
"""
844+
if not isinstance(value, int) and value is not None:
845+
raise ValueError("Invalid vm_specific type must be int or None")
846+
if isinstance(value, int) and (value < 0 or value > 0xffffffff):
847+
raise ValueError("Invalid vm_specific value must be > 0 and <= 0xffffffff")
848+
return value
849+
850+
@property
851+
def vm_specific(self):
852+
"""Get the optional OEM specific field value if set.
853+
854+
:return: vm_specific value
855+
:rtype: int, optional
856+
"""
857+
return self._vm_specific
858+
859+
@vm_specific.setter
860+
def vm_specific(self, value):
861+
"""Set the optional OEM specific field value. If you do not need to send this item, set it to None.
862+
863+
:param value: The vm_specific value (must be > 0 and <= 0xffffffff) or None
864+
:type value: int, optional
865+
:raises ValueError: Value is invalid or out of range
866+
"""
867+
self._vm_specific = self._validate_vm_specific_value(value)

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
setuptools.setup(
99
name="doipclient",
10-
version="1.1.6",
10+
version="1.1.7.dev1",
1111
description="A Diagnostic over IP (DoIP) client implementing ISO-13400-2.",
1212
long_description=long_description,
1313
long_description_content_type="text/x-rst",

tests/test_client.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -750,3 +750,60 @@ def test_use_secure_with_external_ssl_context(mock_socket, mocker):
750750

751751
mocked_external_wrap_socket = mocked_external_context.wrap_socket
752752
mocked_external_wrap_socket.assert_called_once_with(mock_socket)
753+
754+
755+
@pytest.mark.parametrize(
756+
"vm_specific, exc",
757+
((None, False), (0, False), (-1, True), (0xFFFFFFFF, False), (0x100000000, True), ("0x1", True), (10.0, True)))
758+
def test_vm_specific_setter(mock_socket, mocker, vm_specific, exc):
759+
sut = DoIPClient(test_ip, test_logical_address, auto_reconnect_tcp=True)
760+
if exc:
761+
with pytest.raises(ValueError):
762+
sut.vm_specific = vm_specific
763+
else:
764+
sut.vm_specific = vm_specific
765+
assert sut.vm_specific == vm_specific
766+
767+
768+
def test_vm_specific_static_value(mock_socket, mocker):
769+
request_activation_spy = mocker.spy(DoIPClient, "request_activation")
770+
mock_socket.rx_queue.append(successful_activation_response_with_vm)
771+
772+
sut = DoIPClient(
773+
test_ip,
774+
test_logical_address,
775+
auto_reconnect_tcp=True,
776+
activation_type=None,
777+
vm_specific=0x01020304,
778+
)
779+
sut.request_activation(activation_type=RoutingActivationRequest.ActivationType.Default)
780+
assert mock_socket.tx_queue[-1] == activation_request_with_vm
781+
assert request_activation_spy.call_count == 1
782+
783+
784+
def test_vm_specific_request_activation_bad_value(mock_socket, mocker):
785+
request_activation_spy = mocker.spy(DoIPClient, "request_activation")
786+
mock_socket.rx_queue.append(successful_activation_response_with_vm)
787+
788+
sut = DoIPClient(
789+
test_ip,
790+
test_logical_address,
791+
auto_reconnect_tcp=True,
792+
activation_type=None,
793+
vm_specific=0x01020304,
794+
)
795+
with pytest.raises(ValueError):
796+
sut.request_activation(activation_type=RoutingActivationRequest.ActivationType.Default, vm_specific=-1)
797+
798+
799+
def test_vm_specific_verification_in_init(mock_socket, mocker):
800+
request_activation_spy = mocker.spy(DoIPClient, "request_activation")
801+
mock_socket.rx_queue.append(successful_activation_response_with_vm)
802+
with pytest.raises(ValueError):
803+
sut = DoIPClient(
804+
test_ip,
805+
test_logical_address,
806+
auto_reconnect_tcp=True,
807+
activation_type=None,
808+
vm_specific=-1,
809+
)

0 commit comments

Comments
 (0)