Skip to content

Commit d90acfd

Browse files
authored
Fix broken isotp socket connection (#201)
1 parent 1622f25 commit d90acfd

File tree

5 files changed

+160
-93
lines changed

5 files changed

+160
-93
lines changed

doc/source/udsoncan/questions_answers.rst

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,4 +77,24 @@ What makes a DTC permanent?
7777
How can I contribute?
7878
---------------------
7979

80-
Create a Github issue, fork the project, propose a pull request and I will review it; that's the best way.
80+
.. epigraph::
81+
82+
Create a Github issue, fork the project, propose a pull request and I will review it; that's the best way.
83+
84+
-----
85+
86+
My IsoTPSocketConnection raises an error after updating udsoncan
87+
----------------------------------------------------------------
88+
89+
.. epigraph::
90+
91+
With a breaking change of the isotp v2 socket module, it was necessary to change the signature of the :class:`IsoTPSocketConnection<udsoncan.connections.IsoTPSocketConnection>`.
92+
The change has been carried in v1.21.2. It is not possible to pass ``rxid`` and ``txid`` parameter. A full ``isotp.Address`` must be provided.
93+
94+
.. code-block:: python
95+
96+
# Before 1.21
97+
IsoTPSocketConnection('vcan0', rxid=123, txid=456)
98+
99+
# After 1.21
100+
IsoTPSocketConnection('vcan0', isotp.Address(isotp.AddressingMode.Normal_11bits, rxid=123, txid=456))

test/stub.py

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,49 +3,72 @@
33
import queue
44
import logging
55
import socket
6+
from dataclasses import dataclass
7+
68

79
class StubbedIsoTPSocket(object):
810
conns = {}
911

1012
def __init__(self, name=None, timeout=1):
1113
self.bound = False
12-
self.interface=None
13-
self.rxid=None
14-
self.txid=None
14+
self.interface = None
15+
self.address = None
1516
self.timeout = timeout
1617

17-
self.queue_in = queue.Queue() # Client reads from this queue. Other end is simulated
18+
self.queue_in = queue.Queue() # Client reads from this queue. Other end is simulated
1819

19-
def bind(self, interface, rxid, txid):
20+
def bind(self, interface, address):
2021
self.interface = interface
21-
self.rxid = rxid
22-
self.txid = txid
22+
self.address = address
2323
self.bound = True
24-
sockkey = (self.interface, self.rxid, self.txid)
24+
sockkey = (self.interface, self.address)
2525
if sockkey not in StubbedIsoTPSocket.conns:
2626
StubbedIsoTPSocket.conns[sockkey] = dict()
2727
StubbedIsoTPSocket.conns[sockkey][id(self)] = self
2828

2929
def close(self):
30-
self.bound=False
31-
sockkey = (self.interface, self.rxid, self.txid)
30+
self.bound = False
31+
sockkey = (self.interface, self.address)
3232
if sockkey in StubbedIsoTPSocket.conns:
3333
if id(self) in StubbedIsoTPSocket.conns[sockkey]:
3434
del StubbedIsoTPSocket.conns[sockkey][id(self)]
3535
while not self.queue_in.empty():
3636
self.queue_in.get()
3737

38+
def must_receive(self, dst_interface: str, srcaddr: "isotp.Address", dstaddr: "isotp.Address"):
39+
if dst_interface != self.interface:
40+
return False
41+
42+
@dataclass
43+
class StubbedCanMsg:
44+
is_extended_id: bool
45+
arbitration_id: int
46+
data: bytes
47+
48+
# Simulate an isotp.CanMessage to work with addresses
49+
msg = StubbedCanMsg(
50+
is_extended_id=srcaddr.is_tx_29bits(),
51+
arbitration_id=srcaddr.get_tx_arbitration_id(),
52+
data=bytes([srcaddr.get_tx_extension_byte()]) if srcaddr.requires_tx_extension_byte() else bytes()
53+
)
54+
55+
if dstaddr.is_for_me(msg):
56+
return True
57+
58+
return False
59+
3860
def send(self, payload):
39-
target_sockkey = (self.interface, self.txid, self.rxid)
40-
if target_sockkey in StubbedIsoTPSocket.conns:
41-
for sockid in StubbedIsoTPSocket.conns[target_sockkey]:
42-
StubbedIsoTPSocket.conns[target_sockkey][sockid].queue_in.put(payload)
61+
# if target_sockkey in StubbedIsoTPSocket.conns:
62+
for target_sockkey in StubbedIsoTPSocket.conns:
63+
dst_interface = target_sockkey[0]
64+
dstaddr = target_sockkey[1]
65+
if self.must_receive(dst_interface, self.address, dstaddr):
66+
for sockid in StubbedIsoTPSocket.conns[target_sockkey]:
67+
StubbedIsoTPSocket.conns[target_sockkey][sockid].queue_in.put(payload)
4368

4469
def recv(self):
4570
try:
4671
payload = self.queue_in.get(block=True, timeout=self.timeout)
4772
except queue.Empty:
48-
raise socket.timeout
73+
raise socket.timeout
4974
return payload
50-
51-

test/test_connection.py

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import isotp
1313
import can
1414
s = isotp.socket()
15-
s.bind(_interface_name,rxid=1,txid=2)
15+
s.bind(_interface_name, isotp.Address(rxid=1, txid=2))
1616
s.close()
1717
_STACK_POSSIBLE = True
1818
except Exception as e:
@@ -25,34 +25,39 @@
2525
except Exception as e:
2626
_AISOTP_POSSIBLE = False
2727

28+
2829
class TestIsoTPSocketConnection(UdsTest):
2930

3031
def setUp(self):
3132
self.tpsock1 = StubbedIsoTPSocket(timeout=0.1)
3233
self.tpsock2 = StubbedIsoTPSocket(timeout=0.1)
3334

3435
def test_open(self):
35-
conn = IsoTPSocketConnection(interface='vcan0', rxid=0x001, txid=0x002, tpsock=self.tpsock1, name='unittest')
36+
addr = isotp.Address(isotp.AddressingMode.Normal_11bits, rxid=0x001, txid=0x002)
37+
conn = IsoTPSocketConnection(interface='vcan0', address=addr, tpsock=self.tpsock1, name='unittest')
3638
self.assertFalse(conn.is_open())
3739
conn.open()
3840
self.assertTrue(conn.is_open())
3941
conn.close()
4042
self.assertFalse(conn.is_open())
4143

4244
def test_transmit(self):
43-
conn1 = IsoTPSocketConnection(interface='vcan0', rxid=0x100, txid=0x101, tpsock=self.tpsock1, name='unittest')
44-
conn2 = IsoTPSocketConnection(interface='vcan0', rxid=0x101, txid=0x100, tpsock=self.tpsock2, name='unittest')
45+
addr1 = isotp.Address(isotp.AddressingMode.Normal_11bits, rxid=0x100, txid=0x101)
46+
addr2 = isotp.Address(isotp.AddressingMode.Normal_11bits, rxid=0x101, txid=0x100)
47+
conn1 = IsoTPSocketConnection(interface='vcan0', address=addr1, tpsock=self.tpsock1, name='unittest')
48+
conn2 = IsoTPSocketConnection(interface='vcan0', address=addr2, tpsock=self.tpsock2, name='unittest')
4549

4650
with conn1.open():
4751
with conn2.open():
4852
payload1 = b"\x00\x01\x02\x03\x04"
4953
conn1.send(payload1)
50-
payload2 = conn2.wait_frame(timeout=0.3)
54+
payload2 = conn2.wait_frame(timeout=0.3, exception=True)
5155
self.assertEqual(payload1, payload2)
5256

57+
5358
class TestSocketConnection(UdsTest):
5459
def server_sock_thread_task(self):
55-
self.thread_started=True
60+
self.thread_started = True
5661
self.sock1, addr = self.server_sock.accept()
5762

5863
def setUp(self):
@@ -112,6 +117,7 @@ def test_transmit(self):
112117
payload2 = conn2.wait_frame(timeout=1, exception=True)
113118
self.assertEqual(payload1, payload2)
114119

120+
115121
class TestQueueConnection(UdsTest):
116122
def setUp(self):
117123
self.conn = QueueConnection(name='unittest')
@@ -136,7 +142,7 @@ def test_send(self):
136142
self.assertEqual(frame, payload)
137143

138144
def test_truncate(self):
139-
payload = b"\x00\x01\x02\x03"*5000
145+
payload = b"\x00\x01\x02\x03" * 5000
140146
self.conn.send(payload)
141147
frame = self.conn.touserqueue.get()
142148
self.assertEqual(len(frame), 4095)
@@ -161,48 +167,48 @@ def test_reopen(self):
161167

162168
self.assertTrue(self.conn.touserqueue.empty())
163169

170+
164171
@unittest.skipIf(_STACK_POSSIBLE == False, 'Cannot test TestPythonIsoTpConnection. %s' % _STACK_UNVAILABLE_REASON)
165172
class TestPythonIsoTpConnection(UdsTest):
166173
def __init__(self, *args, **kwargs):
167174
UdsTest.__init__(self, *args, **kwargs)
168175
if not hasattr(self.__class__, '_next_id'):
169-
self.__class__._next_id=1
176+
self.__class__._next_id = 1
170177

171178
self.stack_txid = self.__class__._next_id
172-
self.stack_rxid = self.__class__._next_id +1
179+
self.stack_rxid = self.__class__._next_id + 1
173180
self.__class__._next_id += 2
174181

175182
def make_bus(self):
176183
return can.interface.Bus(bustype='socketcan', channel='vcan0', bitrate=500000, receive_own_messages=True)
177184

178185
def setUp(self):
179186
self.vcan0_bus = self.make_bus()
187+
self.reader = can.BufferedReader()
188+
self.notifier = can.Notifier(self.vcan0_bus, [self.reader])
180189
addr = isotp.Address(isotp.AddressingMode.Normal_11bits, rxid=self.stack_rxid, txid=self.stack_txid)
181-
self.conn = PythonIsoTpConnection(isotp.CanStack(bus=self.vcan0_bus, address=addr), name='unittest')
190+
self.conn = PythonIsoTpConnection(isotp.NotifierBasedCanStack(bus=self.vcan0_bus, notifier=self.notifier, address=addr), name='unittest')
182191
self.conn.open()
183192

184193
def test_open(self):
185194
self.assertTrue(self.conn.is_open())
186195

187196
def test_receive(self):
188-
self.vcan0_bus.send(can.Message(arbitration_id = self.stack_rxid, data = b"\x03\x01\x02\x03", is_extended_id = False))
197+
self.vcan0_bus.send(can.Message(arbitration_id=self.stack_rxid, data=b"\x03\x01\x02\x03", is_extended_id=False))
189198
frame = self.conn.wait_frame(timeout=1)
190199
self.assertEqual(frame, b"\x01\x02\x03")
191200

192201
def test_send(self):
193202
self.conn.send(b"\xAA\xBB\xCC\xDD\xEE\xFF")
194-
t1 = time.time()
195-
msg = self.vcan0_bus.recv(1)
203+
msg = self.reader.get_message(1)
196204
self.assertIsNotNone(msg)
197205
self.assertEqual(msg.data, b'\x06\xAA\xBB\xCC\xDD\xEE\xFF')
198206

199207
def test_reopen(self):
200208
self.conn.send(b"\x0A\x0B\x0C\x0D")
201-
self.vcan0_bus.send(can.Message(arbitration_id = self.stack_rxid, data = b"\x03\x01\x02\x03", is_extended_id = False))
209+
self.vcan0_bus.send(can.Message(arbitration_id=self.stack_rxid, data=b"\x03\x01\x02\x03", is_extended_id=False))
202210
self.conn.close()
203-
self.vcan0_bus.shutdown()
204-
self.vcan0_bus = self.make_bus()
205-
self.conn.open(bus=self.vcan0_bus)
211+
self.conn.open()
206212

207213
with self.assertRaises(TimeoutException):
208214
self.conn.wait_frame(timeout=0.05, exception=True)
@@ -211,8 +217,10 @@ def test_reopen(self):
211217

212218
def tearDown(self):
213219
self.conn.close()
220+
self.notifier.stop()
214221
self.vcan0_bus.shutdown()
215222

223+
216224
@unittest.skipIf(_AISOTP_POSSIBLE == False, "aisotp module is not present.")
217225
class TestSyncAioIsotpConnection(UdsTest):
218226

@@ -232,7 +240,7 @@ def test_open(self):
232240
def test_transmit(self):
233241
conn0 = SyncAioIsotpConnection(interface="virtual", channel=0, bitrate=500000, rx_id=0x123, tx_id=0x456, name="unittest")
234242
conn1 = SyncAioIsotpConnection(interface="virtual", channel=0, bitrate=500000, rx_id=0x456, tx_id=0x123, name="unittest")
235-
243+
236244
with conn0.open():
237245
with conn1.open():
238246
tx_data = bytes([i for i in range(256)])

test/test_stubbed_isotpsock.py

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,34 @@
1+
import unittest
12
from test.UdsTest import UdsTest
23
from test.stub import StubbedIsoTPSocket
34
from udsoncan.exceptions import *
45
import socket
56

7+
try:
8+
import isotp
9+
_isotp_available = True
10+
except ImportError:
11+
_isotp_available = False
12+
13+
14+
@unittest.skipIf(_isotp_available == False, "isotp module not available")
615
class TestStubbedIsoTPSocket(UdsTest):
716
def test_open(self):
817
tpsock = StubbedIsoTPSocket()
918
self.assertFalse(tpsock.bound)
10-
tpsock.bind(interface='vcan0', rxid=0x100, txid=0x101)
19+
addr = isotp.Address(isotp.AddressingMode.Normal_11bits, rxid=0x100, txid=0x101)
20+
tpsock.bind(interface='vcan0', address=addr)
1121
self.assertTrue(tpsock.bound)
1222
tpsock.close()
1323
self.assertFalse(tpsock.bound)
1424

1525
def test_transmit(self):
1626
tpsock1 = StubbedIsoTPSocket()
1727
tpsock2 = StubbedIsoTPSocket(timeout=0.5)
18-
tpsock1.bind(interface='vcan0', rxid=0x200, txid=0x201)
19-
tpsock2.bind(interface='vcan0', rxid=0x201, txid=0x200)
28+
addr1 = isotp.Address(isotp.AddressingMode.Normal_11bits, rxid=0x200, txid=0x201)
29+
addr2 = isotp.Address(isotp.AddressingMode.Normal_11bits, rxid=0x201, txid=0x200)
30+
tpsock1.bind(interface='vcan0', address=addr1)
31+
tpsock2.bind(interface='vcan0', address=addr2)
2032

2133
payload1 = b"\x01\x02\x03\x04"
2234
tpsock1.send(payload1)
@@ -27,9 +39,13 @@ def test_multicast(self):
2739
tpsock1 = StubbedIsoTPSocket()
2840
tpsock2 = StubbedIsoTPSocket(timeout=0.5)
2941
tpsock3 = StubbedIsoTPSocket(timeout=0.5)
30-
tpsock1.bind(interface='vcan0', rxid=0x300, txid=0x301)
31-
tpsock2.bind(interface='vcan0', rxid=0x301, txid=0x300)
32-
tpsock3.bind(interface='vcan0', rxid=0x301, txid=0x300)
42+
addr1 = isotp.Address(isotp.AddressingMode.Normal_11bits, rxid=0x300, txid=0x301)
43+
addr2 = isotp.Address(isotp.AddressingMode.Normal_11bits, rxid=0x301, txid=0x300)
44+
addr3 = isotp.Address(isotp.AddressingMode.Normal_11bits, rxid=0x301, txid=0x300)
45+
46+
tpsock1.bind(interface='vcan0', address=addr1)
47+
tpsock2.bind(interface='vcan0', address=addr2)
48+
tpsock3.bind(interface='vcan0', address=addr3)
3349

3450
payload1 = b"\x01\x02\x03\x04"
3551
tpsock1.send(payload1)
@@ -41,8 +57,10 @@ def test_multicast(self):
4157
def test_empty_on_close(self):
4258
tpsock1 = StubbedIsoTPSocket()
4359
tpsock2 = StubbedIsoTPSocket(timeout=0.2)
44-
tpsock1.bind(interface='vcan0', rxid=0x400, txid=0x401)
45-
tpsock2.bind(interface='vcan0', rxid=0x401, txid=0x400)
60+
addr1 = isotp.Address(isotp.AddressingMode.Normal_11bits, rxid=0x400, txid=0x401)
61+
addr2 = isotp.Address(isotp.AddressingMode.Normal_11bits, rxid=0x401, txid=0x400)
62+
tpsock1.bind(interface='vcan0', address=addr1)
63+
tpsock2.bind(interface='vcan0', address=addr2)
4664

4765
payload = b"\x01\x02\x03\x04"
4866
tpsock1.send(payload)
@@ -54,12 +72,13 @@ def test_empty_on_close(self):
5472
def test_no_listener(self):
5573
tpsock1 = StubbedIsoTPSocket()
5674
tpsock2 = StubbedIsoTPSocket(timeout=0.2)
57-
tpsock1.bind(interface='vcan0', rxid=0x400, txid=0x401)
75+
addr1 = isotp.Address(isotp.AddressingMode.Normal_11bits, rxid=0x400, txid=0x401)
76+
addr2 = isotp.Address(isotp.AddressingMode.Normal_11bits, rxid=0x401, txid=0x400)
77+
tpsock1.bind(interface='vcan0', address=addr1)
5878

5979
payload = b"\x01\x02\x03\x04"
6080
tpsock1.send(payload)
61-
tpsock2.bind(interface='vcan0', rxid=0x401, txid=0x400)
81+
tpsock2.bind(interface='vcan0', address=addr2)
6282

6383
with self.assertRaises(socket.timeout):
6484
tpsock2.recv()
65-

0 commit comments

Comments
 (0)