% Regression tests for ISOTP

~ automotive_comm disabled

+ Configuration
~ conf

= Imports

from scapy.modules.six.moves.queue import Queue
from io import BytesIO
from scapy.contrib.isotp.isotp_scanner import get_isotp_packet

import scapy.modules.six as six

with open(scapy_path("test/contrib/automotive/interface_mockup.py")) as f:
    exec(f.read())

= Definition of constants, utility functions and mock classes


class MockCANSocket(SuperSocket):
    nonblocking_socket = True
    def __init__(self, rcvd_queue=None):
        self.rcvd_queue = Queue()
        self.sent_queue = Queue()
        if rcvd_queue is not None:
            for c in rcvd_queue:
                self.rcvd_queue.put(c)
    def recv_raw(self, x=MTU):
        pkt = bytes(self.rcvd_queue.get(True, 2))
        return CAN, pkt, None
    def send(self, p):
        self.sent_queue.put(p)
    @staticmethod
    def select(sockets, remain=None):
        return sockets


# utility function that waits on list l for n elements, timing out if nothing is added for 1 second
def list_wait(l, n):
    old_len = 0
    c = 0
    while len(l) < n:
        if c > 100:
            return False
        if len(l) == old_len:
            time.sleep(0.01)
            c += 1
        else:
            old_len = len(l)
            c = 0

# hexadecimal to bytes convenience function
if six.PY2:
    dhex = lambda s: "".join(s.split()).decode('hex')
else:
    dhex = bytes.fromhex


+ Syntax check

= Import isotp

conf.contribs['ISOTP'] = {'use-can-isotp-kernel-module': False}

if six.PY3:
    import importlib
    if "scapy.contrib.isotp" in sys.modules:
        importlib.reload(scapy.contrib.isotp)

load_contrib("isotp", globals_dict=globals())

ISOTPSocket = ISOTPSoftSocket

+ ISOTP packet check

= Creation of an empty ISOTP packet
p = ISOTP()
assert(p.data == b"")
assert(p.src is None and p.dst is None and p.exsrc is None and p.exdst is None)
assert(bytes(p) == b"")

= Creation of a simple ISOTP packet with src
p = ISOTP(b"eee", src=0x241)
assert(p.src == 0x241)
assert(p.data == b"eee")
assert(bytes(p) == b"eee")

= Creation of a simple ISOTP packet with exsrc
p = ISOTP(b"eee", exsrc=0x41)
assert(p.exsrc == 0x41)
assert(p.data == b"eee")
assert(bytes(p) == b"eee")

= Creation of a simple ISOTP packet with dst
p = ISOTP(b"eee", dst=0x241)
assert(p.dst == 0x241)
assert(p.data == b"eee")
assert(bytes(p) == b"eee")

= Creation of a simple ISOTP packet with exdst
p = ISOTP(b"eee", exdst=0x41)
assert(p.exdst == 0x41)
assert(p.data == b"eee")
assert(bytes(p) == b"eee")

= Creation of a simple ISOTP packet with src, dst, exsrc, exdst
p = ISOTP(b"eee", src=1, dst=2, exsrc=3, exdst=4)
assert(p.dst == 2)
assert(p.exdst == 4)
assert(p.src == 1)
assert(p.exsrc == 3)
assert(p.data == b"eee")
assert(bytes(p) == b"eee")

= ISOTP answers test
p = ISOTP()
r = ISOTP()
assert(p.data == b"")
assert(p.answers(r))
assert(not p.answers(Raw()))


= Creation of a simple ISOTP packet with src validation error
ex = False
try:
    p = ISOTP(b"eee", src=0x1000000000, dst=2, exsrc=3, exdst=4)
except Scapy_Exception:
    ex = True

assert ex

= Creation of a simple ISOTP packet with dst validation error
ex = False
try:
    p = ISOTP(b"eee", src=0x10, dst=0x20000000000, exsrc=3, exdst=4)
except Scapy_Exception:
    ex = True

assert ex

= Creation of a simple ISOTP packet with exsrc validation error
ex = False
try:
    p = ISOTP(b"eee", src=0x10, dst=2, exsrc=3000, exdst=4)
except Scapy_Exception:
    ex = True

assert ex


= Creation of a simple ISOTP packet with exdst validation error
ex = False
try:
    p = ISOTP(b"eee", src=0x10, dst=2, exsrc=30, exdst=400)
except Scapy_Exception:
    ex = True

assert ex

+ ISOTPFrame related checks

= Build a packet with extended addressing
pkt = CAN(identifier=0x123, data=b'\x42\x10\xff\xde\xea\xdd\xaa\xaa')
isotpex = ISOTPHeaderEA(bytes(pkt))
assert(isotpex.type == 1)
assert(isotpex.message_size == 0xff)
assert(isotpex.extended_address == 0x42)
assert(isotpex.identifier == 0x123)
assert(isotpex.length == 8)

= Build a packet with normal addressing
pkt = CAN(identifier=0x123, data=b'\x10\xff\xde\xea\xdd\xaa\xaa')
isotpno = ISOTPHeader(bytes(pkt))
assert(isotpno.type == 1)
assert(isotpno.message_size == 0xff)
assert(isotpno.identifier == 0x123)
assert(isotpno.length == 7)

= Compare both isotp payloads
assert(isotpno.data == isotpex.data)
assert(isotpno.message_size == isotpex.message_size)

= Dissect multiple packets
frames = \
    [b'\x00\x00\x00\x00\x08\x00\x00\x00\x10(\xde\xad\xbe\xef\xde\xad',
    b'\x00\x00\x00\x00\x08\x00\x00\x00!\xbe\xef\xde\xad\xbe\xef\xde',
    b'\x00\x00\x00\x00\x08\x00\x00\x00"\xad\xbe\xef\xde\xad\xbe\xef',
    b'\x00\x00\x00\x00\x08\x00\x00\x00#\xde\xad\xbe\xef\xde\xad\xbe',
    b'\x00\x00\x00\x00\x08\x00\x00\x00$\xef\xde\xad\xbe\xef\xde\xad',
    b'\x00\x00\x00\x00\x07\x00\x00\x00%\xbe\xef\xde\xad\xbe\xef']

isotpframes = [ISOTPHeader(x) for x in frames]

assert(isotpframes[0].type == 1)
assert(isotpframes[0].message_size == 40)
assert(isotpframes[0].length == 8)
assert(isotpframes[1].type == 2)
assert(isotpframes[1].index == 1)
assert(isotpframes[1].length == 8)
assert(isotpframes[2].type == 2)
assert(isotpframes[2].index == 2)
assert(isotpframes[2].length == 8)
assert(isotpframes[3].type == 2)
assert(isotpframes[3].index == 3)
assert(isotpframes[3].length == 8)
assert(isotpframes[4].type == 2)
assert(isotpframes[4].index == 4)
assert(isotpframes[4].length == 8)
assert(isotpframes[5].type == 2)
assert(isotpframes[5].index == 5)
assert(isotpframes[5].length == 7)

= Build SF frame with constructor, check for correct length assignments
p = ISOTPHeader(bytes(ISOTPHeader()/ISOTP_SF(data=b'\xad\xbe\xad\xff')))
assert(p.length == 5)
assert(p.message_size == 4)
assert(len(p.data) == 4)
assert(p.data == b'\xad\xbe\xad\xff')
assert(p.type == 0)
assert(p.identifier == 0)

= Build SF frame EA with constructor, check for correct length assignments
p = ISOTPHeaderEA(bytes(ISOTPHeaderEA()/ISOTP_SF(data=b'\xad\xbe\xad\xff')))
assert(p.extended_address == 0)
assert(p.length == 6)
assert(p.message_size == 4)
assert(len(p.data) == 4)
assert(p.data == b'\xad\xbe\xad\xff')
assert(p.type == 0)
assert(p.identifier == 0)

= Build FF frame with constructor, check for correct length assignments
p = ISOTPHeader(bytes(ISOTPHeader()/ISOTP_FF(message_size=10, data=b'\xad\xbe\xad\xff')))
assert(p.length == 6)
assert(p.message_size == 10)
assert(len(p.data) == 4)
assert(p.data == b'\xad\xbe\xad\xff')
assert(p.type == 1)
assert(p.identifier == 0)

= Build FF frame EA with constructor, check for correct length assignments
p = ISOTPHeaderEA(bytes(ISOTPHeaderEA()/ISOTP_FF(message_size=10, data=b'\xad\xbe\xad\xff')))
assert(p.extended_address == 0)
assert(p.length == 7)
assert(p.message_size == 10)
assert(len(p.data) == 4)
assert(p.data == b'\xad\xbe\xad\xff')
assert(p.type == 1)
assert(p.identifier == 0)

= Build FF frame EA, extended size, with constructor, check for correct length assignments
p = ISOTPHeaderEA(bytes(ISOTPHeaderEA()/ISOTP_FF(message_size=0,
                                                 extended_message_size=2000,
                                                 data=b'\xad')))
assert(p.extended_address == 0)
assert(p.length == 8)
assert(p.message_size == 0)
assert(p.extended_message_size == 2000)
assert(len(p.data) == 1)
assert(p.data == b'\xad')
assert(p.type == 1)
assert(p.identifier == 0)

= Build FF frame, extended size, with constructor, check for correct length assignments
p = ISOTPHeader(bytes(ISOTPHeader()/ISOTP_FF(message_size=0,
                                             extended_message_size=2000,
                                             data=b'\xad')))
assert(p.length == 7)
assert(p.message_size == 0)
assert(p.extended_message_size == 2000)
assert(len(p.data) == 1)
assert(p.data == b'\xad')
assert(p.type == 1)
assert(p.identifier == 0)

= Build CF frame with constructor, check for correct length assignments
p = ISOTPHeader(bytes(ISOTPHeader()/ISOTP_CF(data=b'\xad')))
assert(p.length == 2)
assert(p.index == 0)
assert(len(p.data) == 1)
assert(p.data == b'\xad')
assert(p.type == 2)
assert(p.identifier == 0)

= Build CF frame EA with constructor, check for correct length assignments
p = ISOTPHeaderEA(bytes(ISOTPHeaderEA()/ISOTP_CF(data=b'\xad')))
assert(p.length == 3)
assert(p.index == 0)
assert(len(p.data) == 1)
assert(p.data == b'\xad')
assert(p.type == 2)
assert(p.identifier == 0)

= Build FC frame EA with constructor, check for correct length assignments
p = ISOTPHeaderEA(bytes(ISOTPHeaderEA()/ISOTP_FC()))
assert(p.length == 4)
assert(p.block_size == 0)
assert(p.separation_time == 0)
assert(p.type == 3)
assert(p.identifier == 0)

= Build FC frame with constructor, check for correct length assignments
p = ISOTPHeader(bytes(ISOTPHeader()/ISOTP_FC()))
assert(p.length == 3)
assert(p.block_size == 0)
assert(p.separation_time == 0)
assert(p.type == 3)
assert(p.identifier == 0)

= Construct some single frames
p = ISOTPHeader(identifier=0x123, length=5)/ISOTP_SF(message_size=4, data=b'abcd')
assert(p.length == 5)
assert(p.identifier == 0x123)
assert(p.type == 0)
assert(p.message_size == 4)
assert(p.data == b'abcd')

= Construct some single frames EA
p = ISOTPHeaderEA(identifier=0x123, length=6, extended_address=42)/ISOTP_SF(message_size=4, data=b'abcd')
assert(p.length == 6)
assert(p.extended_address == 42)
assert(p.identifier == 0x123)
assert(p.type == 0)
assert(p.message_size == 4)
assert(p.data == b'abcd')

= Construct ISOTP_packet with extended can frame
p = get_isotp_packet(identifier=0x1234, extended=False, extended_can_id=True)
print(p)
assert (p.identifier == 0x1234)
assert (p.flags == "extended")

= Construct ISOTPEA_Packet with extended can frame
p = get_isotp_packet(identifier=0x1234, extended=True, extended_can_id=True)
print(p)
assert (p.identifier == 0x1234)
assert (p.flags == "extended")

+ ISOTP fragment and defragment checks

= Fragment an empty ISOTP message
fragments = ISOTP().fragment()
assert(len(fragments) == 1)
assert(fragments[0].data == b"\0")

= Fragment another empty ISOTP message
fragments = ISOTP(b"").fragment()
assert(len(fragments) == 1)
assert(fragments[0].data == b"\0")

= Fragment a 4 bytes long ISOTP message
fragments = ISOTP(b"data", src=0x241).fragment()
assert(len(fragments) == 1)
assert(isinstance(fragments[0], CAN))
fragment = CAN(bytes(fragments[0]))
assert(fragment.data == b"\x04data")
assert(fragment.flags == 0)
assert(fragment.length == 5)
assert(fragment.reserved == 0)

= Fragment a 4 bytes long ISOTP message extended
fragments = ISOTP(b"data", dst=0x1fff0000).fragment()
assert(len(fragments) == 1)
assert(isinstance(fragments[0], CAN))
fragment = CAN(bytes(fragments[0]))
assert(fragment.data == b"\x04data")
assert(fragment.length == 5)
assert(fragment.reserved == 0)
assert(fragment.flags == 4)

= Fragment a 8 bytes long ISOTP message extended
fragments = ISOTP(b"datadata", dst=0x1fff0000).fragment()
assert(len(fragments) == 2)
assert(isinstance(fragments[0], CAN))
fragment = CAN(bytes(fragments[0]))
assert(fragment.data == b"\x10\x08datada")
assert(fragment.length == 8)
assert(fragment.reserved == 0)
assert(fragment.flags == 4)
fragment = CAN(bytes(fragments[1]))
assert(fragment.data == b"\x21ta")
assert(fragment.length == 3)
assert(fragment.reserved == 0)
assert(fragment.flags == 4)

= Fragment a 7 bytes long ISOTP message
fragments = ISOTP(b"abcdefg").fragment()
assert(len(fragments) == 1)
assert(fragments[0].data == b"\x07abcdefg")

= Fragment a 8 bytes long ISOTP message
fragments = ISOTP(b"abcdefgh").fragment()
assert(len(fragments) == 2)
assert(fragments[0].data == b"\x10\x08abcdef")
assert(fragments[1].data == b"\x21gh")

= Fragment an ISOTP message with extended addressing
isotp = ISOTP(b"abcdef", exdst=ord('A'))
fragments = isotp.fragment()
assert(len(fragments) == 1)
assert(fragments[0].data == b"A\x06abcdef")

= Fragment a 7 bytes ISOTP message with destination identifier
isotp = ISOTP(b"abcdefg", dst=0x64f)
fragments = isotp.fragment()
assert(len(fragments)  == 1)
assert(fragments[0].data == b"\x07abcdefg")
assert(fragments[0].identifier == 0x64f)

= Fragment a 16 bytes ISOTP message with extended addressing
isotp = ISOTP(b"abcdefghijklmnop", dst=0x64f, exdst=ord('A'))
fragments = isotp.fragment()
assert(len(fragments) == 3)
assert(fragments[0].data == b"A\x10\x10abcde")
assert(fragments[1].data == b"A\x21fghijk")
assert(fragments[2].data == b"A\x22lmnop")
assert(fragments[0].identifier == 0x64f)
assert(fragments[1].identifier == 0x64f)
assert(fragments[2].identifier == 0x64f)

= Fragment a huge ISOTP message, 4997 bytes long
data = b"T" * 4997
isotp = ISOTP(b"T" * 4997, dst=0x345)
fragments = isotp.fragment()
assert(len(fragments) == 715)
assert(fragments[0].data == dhex("10 00 00 00 13 85") + b"TT")
assert(fragments[1].data == b"\x21TTTTTTT")
assert(fragments[-2].data == b"\x29TTTTTTT")
assert(fragments[-1].data == b"\x2ATTTT")

= Defragment a single-frame ISOTP message
fragments = [CAN(identifier=0x641, data=b"\x04test")]
isotp = ISOTP.defragment(fragments)
isotp.show()
assert(isotp.data == b"test")
assert(isotp.dst == 0x641)

= Defragment non ISOTP message
fragments = [CAN(identifier=0x641, data=b"\xa4test")]
isotp = ISOTP.defragment(fragments)
assert isotp is None

= Defragment ISOTP message with warning
fragments = [CAN(identifier=0x641, data=b"\x04test"), CAN(identifier=0x642, data=b"\x04test")]
isotp = ISOTP.defragment(fragments)
assert(isotp.data == b"test")
assert(isotp.dst == 0x641)

= Defragment exception
fragments = []
ex = False
try:
    isotp = ISOTP.defragment(fragments)
    isotp.show()
except Scapy_Exception:
    ex = True

assert ex

= Fragment exception
ex = False
try:
    fragments = ISOTP(b"a" * (1 << 32)).fragment()
except Scapy_Exception:
    ex = True

assert ex

= Defragment an ISOTP message composed of multiple CAN frames
fragments = [
    CAN(identifier=0x641, data=dhex("41 10 10 61 62 63 64 65")),
    CAN(identifier=0x641, data=dhex("41 21 66 67 68 69 6A 6B")),
    CAN(identifier=0x641, data=dhex("41 22 6C 6D 6E 6F 70 00"))
]
isotp = ISOTP.defragment(fragments)
isotp.show()
assert(isotp.data == dhex("61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70"))
assert(isotp.dst == 0x641)
assert(isotp.exdst == 0x41)

= Check if fragmenting a message and defragmenting it back yields the original message
isotp1 = ISOTP(b"abcdef", exdst=ord('A'))
fragments = isotp1.fragment()
isotp2 = ISOTP.defragment(fragments)
isotp2.show()
assert(isotp1 == isotp2)

isotp1 = ISOTP(b"abcdefghijklmnop")
fragments = isotp1.fragment()
isotp2 = ISOTP.defragment(fragments)
isotp2.show()
assert(isotp1 == isotp2)

isotp1 = ISOTP(b"abcdefghijklmnop", exdst=ord('A'))
fragments = isotp1.fragment()
isotp2 = ISOTP.defragment(fragments)
isotp2.show()
assert(isotp1 == isotp2)

isotp1 = ISOTP(b"T"*5000, exdst=ord('A'))
fragments = isotp1.fragment()
isotp2 = ISOTP.defragment(fragments)
isotp2.show()
assert(isotp1 == isotp2)

= Defragment an ambiguous CAN frame
fragments = [CAN(identifier=0x641, data=dhex("02 01 AA"))]
isotp = ISOTP.defragment(fragments, False)
isotp.show()
assert(isotp.data == dhex("01 AA"))
assert(isotp.exdst == None)
isotpex = ISOTP.defragment(fragments, True)
isotpex.show()
assert(isotpex.data == dhex("AA"))
assert(isotpex.exdst == 0x02)


+ Testing ISOTPMessageBuilder

= Create ISOTPMessageBuilder
m = ISOTPMessageBuilder()

= Feed packets to machine
ff = CAN(identifier=0x241, data=dhex("10 28 01 02 03 04 05 06"))
ff.time = 1000
m.feed(ff)
m.feed(CAN(identifier=0x641, data=dhex("30 03 00"               )))
m.feed(CAN(identifier=0x241, data=dhex("21 07 08 09 0A 0B 0C 0D")))
m.feed(CAN(identifier=0x241, data=dhex("22 0E 0F 10 11 12 13 14")))
m.feed(CAN(identifier=0x241, data=dhex("23 15 16 17 18 19 1A 1B")))
m.feed(CAN(identifier=0x641, data=dhex("30 03 00"               )))
m.feed(CAN(identifier=0x241, data=dhex("24 1C 1D 1E 1F 20 21 22")))
m.feed(CAN(identifier=0x241, data=dhex("25 23 24 25 26 27 28"   )))

= Verify there is a ready message in the machine
assert(m.count == 1)

= Extract the message from the machine
msg = m.pop()
assert(m.count == 0)
assert(msg.dst == 0x241)
assert(msg.exdst is None)
assert(msg.src == 0x641)
assert(msg.exsrc is None)
assert(msg.time == 1000)
expected = dhex("01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28")
assert(msg.data == expected)

= Verify that no error happens when there is not enough data
m = ISOTPMessageBuilder()
m.feed(CAN(identifier=0x241, data=dhex("04 AB CD EF")))
msg = m.pop()
assert(msg is None)

= Verify that no error happens when there is no data
m = ISOTPMessageBuilder()
m.feed(CAN(identifier=0x241, data=dhex("")))
msg = m.pop()
assert(msg is None)

= Verify a single frame without EA
m = ISOTPMessageBuilder()
m.feed(CAN(identifier=0x241, data=dhex("04 AB CD EF 04")))
msg = m.pop()
assert(msg.dst == 0x241)
assert(msg.exdst is None)
assert(msg.data == dhex("AB CD EF 04"))

= Single frame without EA, with excessive bytes in CAN frame
m = ISOTPMessageBuilder()
m.feed(CAN(identifier=0x241, data=dhex("03 AB CD EF AB CD EF AB")))
msg = m.pop()
assert(msg.dst == 0x241)
assert(msg.exdst is None)
assert(msg.data == dhex("AB CD EF"))

= Verify a single frame with EA
m = ISOTPMessageBuilder()
m.feed(CAN(identifier=0x241, data=dhex("E2 04 01 02 03 04")))
msg = m.pop()
assert(msg.dst == 0x241)
assert(msg.exdst is 0xE2)
assert(msg.data == dhex("01 02 03 04"))

= Single CAN frame that has 2 valid interpretations
m = ISOTPMessageBuilder()
m.feed(CAN(identifier=0x241, data=dhex("04 01 02 03 04")))
msg = m.pop(0x241, None)
assert(msg.dst == 0x241)
assert(msg.exdst is None)
assert(msg.data == dhex("01 02 03 04"))
msg = m.pop()
assert(msg.dst == 0x241)
assert(msg.exdst == 0x04)
assert(msg.data == dhex("02"))

= Verify multiple frames with EA
m = ISOTPMessageBuilder()
ff = CAN(identifier=0x241, data=dhex("EA 10 28 01 02 03 04 05"))
ff.time = 1005
m.feed(ff)
m.feed(CAN(identifier=0x641, data=dhex("EA 30 03 00"            )))
m.feed(CAN(identifier=0x241, data=dhex("EA 21 06 07 08 09 0A 0B")))
m.feed(CAN(identifier=0x241, data=dhex("EA 22 0C 0D 0E 0F 10 11")))
m.feed(CAN(identifier=0x241, data=dhex("EA 23 12 13 14 15 16 17")))
m.feed(CAN(identifier=0x641, data=dhex("EA 30 03 00"            )))
m.feed(CAN(identifier=0x241, data=dhex("EA 24 18 19 1A 1B 1C 1D")))
m.feed(CAN(identifier=0x241, data=dhex("EA 25 1E 1F 20 21 22 23")))
m.feed(CAN(identifier=0x241, data=dhex("EA 26 24 25 26 27 28"   )))
msg = m.pop()
assert(msg.dst == 0x241)
assert(msg.exdst is 0xEA)
assert(msg.src == 0x641)
assert(msg.exsrc is 0xEA)
assert(msg.time == 1005)
assert(msg.data == dhex("01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28"))

= Verify multiple frames with EA 2
m = ISOTPMessageBuilder()
m.feed(CAN(identifier=0x241, data=dhex("EA 10 28 01 02 03 04 05")))
m.feed(CAN(identifier=0x641, data=dhex("AE 30 03 00"            )))
m.feed(CAN(identifier=0x241, data=dhex("EA 21 06 07 08 09 0A 0B")))
m.feed(CAN(identifier=0x241, data=dhex("EA 22 0C 0D 0E 0F 10 11")))
m.feed(CAN(identifier=0x241, data=dhex("EA 23 12 13 14 15 16 17")))
m.feed(CAN(identifier=0x641, data=dhex("AE 30 03 00"            )))
m.feed(CAN(identifier=0x241, data=dhex("EA 24 18 19 1A 1B 1C 1D")))
m.feed(CAN(identifier=0x241, data=dhex("EA 25 1E 1F 20 21 22 23")))
m.feed(CAN(identifier=0x241, data=dhex("EA 26 24 25 26 27 28"   )))
msg = m.pop()
assert(msg.dst == 0x241)
assert(msg.exdst is 0xEA)
assert(msg.src == 0x641)
assert(msg.exsrc is 0xAE)
assert(msg.data == dhex("01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28"))

= Verify that an EA starting with 1 will still work
m = ISOTPMessageBuilder()
m.feed(CAN(identifier=0x241, data=dhex("1A 10 14 01 02 03 04 05")))
m.feed(CAN(identifier=0x641, data=dhex("1A 30 03 00"            )))
m.feed(CAN(identifier=0x241, data=dhex("1A 21 06 07 08 09 0A 0B")))
m.feed(CAN(identifier=0x241, data=dhex("1A 22 0C 0D 0E 0F 10 11")))
m.feed(CAN(identifier=0x241, data=dhex("1A 23 12 13 14 15 16 17")))
msg = m.pop()
assert(msg.dst == 0x241)
assert(msg.exdst is 0x1A)
assert(msg.src == 0x641)
assert(msg.exsrc is 0x1A)
assert(msg.data == dhex("01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14"))

= Verify that an EA of 07 will still work
m = ISOTPMessageBuilder()
m.feed(CAN(identifier=0x241, data=dhex("07 10 0A 01 02 03 04 05")))
m.feed(CAN(identifier=0x641, data=dhex("07 30 03 00"            )))
m.feed(CAN(identifier=0x241, data=dhex("07 21 06 07 08 09 0A 0B")))
msg = m.pop(0x241, 0x07)
assert(msg.dst == 0x241)
assert(msg.exdst is 0x07)
assert(msg.src == 0x641)
assert(msg.exsrc is 0x07)
assert(msg.data == dhex("01 02 03 04 05 06 07 08 09 0A"))

= Verify that three interleaved messages can be sniffed simultaneously on the same identifier and extended address (very unrealistic)
m = ISOTPMessageBuilder()
ff = CAN(identifier=0x241, data=dhex("EA 10 28 01 02 03 04 05"))
ff.time = 300
m.feed(ff) # start of message A
m.feed(CAN(identifier=0x641, data=dhex("EA 30 03 00"            )))
m.feed(CAN(identifier=0x241, data=dhex("EA 21 06 07 08 09 0A 0B")))
m.feed(CAN(identifier=0x241, data=dhex("EA 22 0C 0D 0E 0F 10 11")))
m.feed(CAN(identifier=0x241, data=dhex("EA 23 12 13 14 15 16 17")))
ff = CAN(identifier=0x241, data=dhex("EA 10 10 31 32 33 34 35"))
ff.time = 400
m.feed(ff) # start of message B
m.feed(CAN(identifier=0x641, data=dhex("EA 30 03 00"            )))
sf = CAN(identifier=0x241, data=dhex("EA 03 A6 A7 A8"         ))
sf.time = 200
m.feed(sf) # single-frame message C
m.feed(CAN(identifier=0x641, data=dhex("EA 30 03 00"            )))
m.feed(CAN(identifier=0x241, data=dhex("EA 24 18 19 1A 1B 1C 1D")))
m.feed(CAN(identifier=0x241, data=dhex("EA 21 36 37 38 39 3A 3B")))
m.feed(CAN(identifier=0x241, data=dhex("EA 22 3C 3D 3E 3F 40"   ))) # end of message B
m.feed(CAN(identifier=0x241, data=dhex("EA 25 1E 1F 20 21 22 23")))
m.feed(CAN(identifier=0x241, data=dhex("EA 26 24 25 26 27 28"   ))) # end of message A
msg = m.pop()
assert(msg.dst == 0x241)
assert(msg.exdst is 0xEA)
assert(msg.data == dhex("A6 A7 A8"))
assert(msg.time == 200)
msg = m.pop()
assert(msg.dst == 0x241)
assert(msg.exdst is 0xEA)
assert(msg.src == 0x641)
assert(msg.exsrc is 0xEA)
assert(msg.time == 400)
assert(msg.data == dhex("31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 40"))
msg = m.pop()
assert(msg.dst == 0x241)
assert(msg.exdst is 0xEA)
assert(msg.src == 0x641)
assert(msg.exsrc is 0xEA)
assert(msg.time == 300)
assert(msg.data == dhex("01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28"))


= Verify multiple frames with EA from list
m = ISOTPMessageBuilder()
msgs = [
    CAN(identifier=0x241, data=dhex("EA 10 28 01 02 03 04 05")),
    CAN(identifier=0x641, data=dhex("EA 30 03 00"            )),
    CAN(identifier=0x241, data=dhex("EA 21 06 07 08 09 0A 0B")),
    CAN(identifier=0x241, data=dhex("EA 22 0C 0D 0E 0F 10 11")),
    CAN(identifier=0x241, data=dhex("EA 23 12 13 14 15 16 17")),
    CAN(identifier=0x641, data=dhex("EA 30 03 00"            )),
    CAN(identifier=0x241, data=dhex("EA 24 18 19 1A 1B 1C 1D")),
    CAN(identifier=0x241, data=dhex("EA 25 1E 1F 20 21 22 23")),
    CAN(identifier=0x241, data=dhex("EA 26 24 25 26 27 28"   ))]
m.feed(msgs)
msg = m.pop()
assert(msg.dst == 0x241)
assert(msg.exdst is 0xEA)
assert(msg.src == 0x641)
assert(msg.exsrc is 0xEA)
assert(msg.data == dhex("01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28"))

= Verify multiple frames with EA from list and iterator
m = ISOTPMessageBuilder()
msgs = [
    CAN(identifier=0x241, data=dhex("EA 10 28 01 02 03 04 05")),
    CAN(identifier=0x641, data=dhex("EA 30 03 00"            )),
    CAN(identifier=0x241, data=dhex("EA 21 06 07 08 09 0A 0B")),
    CAN(identifier=0x241, data=dhex("EA 22 0C 0D 0E 0F 10 11")),
    CAN(identifier=0x241, data=dhex("EA 23 12 13 14 15 16 17")),
    CAN(identifier=0x641, data=dhex("EA 30 03 00"            )),
    CAN(identifier=0x241, data=dhex("EA 24 18 19 1A 1B 1C 1D")),
    CAN(identifier=0x241, data=dhex("EA 25 1E 1F 20 21 22 23")),
    CAN(identifier=0x241, data=dhex("EA 26 24 25 26 27 28"   )),
    CAN(identifier=0x241, data=dhex("EA 03 A6 A7 A8"         )),
    CAN(identifier=0x241, data=dhex("EA 03 A6 A7 A8"))]
m.feed(msgs)
assert m.count == 3
assert len(m) == 3

isotpmsgs = [x for x in m]
assert len(isotpmsgs) == 3
msg = isotpmsgs[0]
assert(msg.dst == 0x241)
assert(msg.exdst is 0xEA)
assert(msg.src == 0x641)
assert(msg.exsrc is 0xEA)
assert(msg.data == dhex("01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28"))

assert isotpmsgs[1] == isotpmsgs[2]

= Verify a single frame without EA and different basecls
m = ISOTPMessageBuilder(basecls=Raw)
m.feed(CAN(identifier=0x241, data=dhex("04 AB CD EF 04")))
msg = m.pop()
assert(msg.load == dhex("AB CD EF 04"))
assert(type(msg) == Raw)

+ Test sniffer
= Test sniffer with multiple frames
~ vcan_socket needs_root linux

test_frames = [
    (0x241, "EA 10 28 01 02 03 04 05"),
    (0x641, "EA 30 03 00"            ),
    (0x241, "EA 21 06 07 08 09 0A 0B"),
    (0x241, "EA 22 0C 0D 0E 0F 10 11"),
    (0x241, "EA 23 12 13 14 15 16 17"),
    (0x641, "EA 30 03 00"            ),
    (0x241, "EA 24 18 19 1A 1B 1C 1D"),
    (0x241, "EA 25 1E 1F 20 21 22 23"),
    (0x241, "EA 26 24 25 26 27 28"   ),
]

succ = False

def sender(args=None):
    global succ
    with new_can_socket(iface0) as tx_sock:
        for f in test_frames:
            tx_sock.send(CAN(identifier=f[0], data=dhex(f[1])))
        succ = True

with new_can_socket(iface0) as s:
    thread = threading.Thread(target=sender)
    sniffed = sniff(opened_socket=s, session=ISOTPSession, timeout=1, prn=lambda x: x.show2(), started_callback=thread.start, count=1)

assert sniffed[0]['ISOTP'].data == bytearray(range(1, 0x29))
assert(sniffed[0]['ISOTP'].src == 0x641)
assert(sniffed[0]['ISOTP'].exsrc is 0xEA)
assert(sniffed[0]['ISOTP'].dst == 0x241)
assert(sniffed[0]['ISOTP'].exdst is 0xEA)
thread.join(timeout=5)
assert(succ)

+ ISOTPSocket tests

= Single-frame receive
cans = MockCANSocket()
cans.rcvd_queue.put(CAN(identifier=0x241, data=dhex("05 01 02 03 04 05")))
with ISOTPSoftSocket(cans, sid=0x641, did=0x241) as s:
    msg = s.recv()

assert(msg.data == dhex("01 02 03 04 05"))
assert(cans.sent_queue.empty())

= Single-frame send
cans = MockCANSocket()
with ISOTPSoftSocket(cans, sid=0x641, did=0x241) as s:
    s.send(ISOTP(dhex("01 02 03 04 05")))

msg = cans.sent_queue.get(True, 1)
assert(msg.data == dhex("05 01 02 03 04 05"))
assert(cans.sent_queue.empty())
assert(cans.rcvd_queue.empty())

= Two frame receive
cans = MockCANSocket()
with ISOTPSoftSocket(cans, sid=0x641, did=0x241) as s:
    ready = threading.Event()
    exception = None
    succ = False
    def sender():
        global exception, succ
        try:
            cans.rcvd_queue.put(CAN(identifier=0x241, data=dhex("10 09 01 02 03 04 05 06")))
            ready.set()
            c = cans.sent_queue.get(True, 2)
            assert(c.data == dhex("30 00 00"))
            cans.rcvd_queue.put(CAN(identifier=0x241, data=dhex("21 07 08 09 00 00 00 00")))
            succ = True
        except Exception as ex:
            exception = ex
            raise ex
    thread = threading.Thread(target=sender, name="sender")
    thread.start()
    ready.wait(timeout=5)
    msg = s.recv()
    thread.join(timeout=5)

if exception is not None:
    raise exception

assert(succ)
assert(msg.data == dhex("01 02 03 04 05 06 07 08 09"))
assert(cans.sent_queue.empty())
assert(cans.rcvd_queue.empty())

= 20000 bytes receive
data = dhex("01 02 03 04 05")*4000
cans = MockCANSocket()

with ISOTPSoftSocket(cans, sid=0x641, did=0x241) as s:
    ready = threading.Event()
    exception = None
    succ = False
    def sender():
        global exception, succ
        try:
            cf = ISOTP(data, dst=0x241).fragment()
            ff = cf.pop(0)
            cans.rcvd_queue.put(ff)
            ready.set()
            c = cans.sent_queue.get(True, 2)
            assert(c.data == dhex("30 00 00"))
            for f in cf:
                cans.rcvd_queue.put(f)
            succ = True
        except Exception as ex:
            exception = ex
            raise ex
    thread = threading.Thread(target=sender, name="sender")
    thread.start()
    ready.wait(15)
    msg = s.recv()
    thread.join(15)

if exception is not None:
    raise exception

assert(succ)
assert(msg.data == data)
assert(cans.sent_queue.empty())
assert(cans.rcvd_queue.empty())

cans = MockCANSocket()
with ISOTPSoftSocket(cans, sid=0x641, did=0x241) as s:
    s.send(ISOTP(dhex("01 02 03 04 05")))

= 20000 bytes send
data = dhex("01 02 03 04 05")*4000
cans = MockCANSocket()
msg = ISOTP(data, dst=0x641)
succ = threading.Event()
ready = threading.Event()
fragments = msg.fragment()
ack = CAN(identifier=0x241, data=dhex("30 00 00"))

def acker():
    ready.set()
    ff = cans.sent_queue.get(True, 2)
    assert(ff == fragments[0])
    cans.rcvd_queue.put(ack)
    for fragment in fragments[1:]:
        cf = cans.sent_queue.get(True, 2)
        assert(fragment == cf)
    succ.set()

thread = threading.Thread(target=acker, name="acker")
thread.start()
ready.wait(timeout=5)

with ISOTPSoftSocket(cans, sid=0x641, did=0x241) as s:
    s.send(msg)

thread.join(15)
succ.wait(2)
assert(succ.is_set())

= Create and close ISOTP soft socket
with ISOTPSocket(MockCANSocket(), sid=0x641, did=0x241) as s:
    assert(s.impl.rx_thread.is_alive())
    s.close()
    assert(not s.impl.rx_thread.join(5))
    assert(not s.impl.rx_thread.is_alive())


= Verify that all threads will die when GC collects the socket
import gc
s = ISOTPSocket(MockCANSocket(), sid=0x641, did=0x241)
assert(s.impl.rx_thread.is_alive())
impl = s.impl
s = None
r = gc.collect()
impl.rx_thread.join(10)  # hope that the GC has made a pass
assert(not impl.rx_thread.is_alive())

= Test on_recv function with single frame
with ISOTPSocket(MockCANSocket(), sid=0x641, did=0x241) as s:
    s.ins.on_recv(CAN(identifier=0x241, data=dhex("05 01 02 03 04 05")))
    msg, ts = s.ins.rx_queue.get(True, 1)
    assert(msg == dhex("01 02 03 04 05"))

= Test on_recv function with empty frame
with ISOTPSocket(MockCANSocket(), sid=0x641, did=0x241) as s:
    s.ins.on_recv(CAN(identifier=0x241, data=b""))
    assert(s.ins.rx_queue.empty())

= Test on_recv function with single frame and extended addressing
with ISOTPSocket(MockCANSocket(), sid=0x641, did=0x241, extended_rx_addr=0xea) as s:
    cf = CAN(identifier=0x241, data=dhex("EA 05 01 02 03 04 05"))
    s.ins.on_recv(cf)
    msg, ts = s.ins.rx_queue.get(True, 1)
    assert(msg == dhex("01 02 03 04 05"))
    assert ts == cf.time

= CF is sent when first frame is received
cans = MockCANSocket()
with ISOTPSocket(cans, sid=0x641, did=0x241) as s:
    s.ins.on_recv(CAN(identifier=0x241, data=dhex("10 20 01 02 03 04 05 06")))
    can = cans.sent_queue.get(True, 1)
    assert(can.identifier == 0x641)
    assert(can.data == dhex("30 00 00"))

cans.close()

+ Testing ISOTPSocket with an actual CAN socket

= Verify that packets are not lost if they arrive before the sniff() is called
with new_can_socket(iface0) as ss, new_can_socket0() as sr:
    tx_func = lambda: ss.send(CAN(identifier=0x111, data=b"\x01\x23\x45\x67"))
    p = sr.sniff(count=1, timeout=0.2, started_callback=tx_func)
    assert(len(p)==1)
    tx_func = lambda: ss.send(CAN(identifier=0x111, data=b"\x89\xab\xcd\xef"))
    p = sr.sniff(count=1, timeout=0.2, started_callback=tx_func)
    assert(len(p)==1)

= Send single frame ISOTP message, using begin_send
with new_can_socket(iface0) as isocan, \
        ISOTPSocket(isocan, sid=0x641, did=0x241) as s, \
        new_can_socket0() as cans:
    can = cans.sniff(timeout=2, count=1, started_callback=lambda: s.begin_send(ISOTP(data=dhex("01 02 03 04 05"))))
    assert(can[0].identifier == 0x641)
    assert(can[0].data == dhex("05 01 02 03 04 05"))

= Send many single frame ISOTP messages, using begin_send

with new_can_socket0() as isocan, \
        ISOTPSocket(isocan, sid=0x641, did=0x241) as s, \
        new_can_socket0() as cans:
    for i in range(100):
        data = dhex("01 02 03 04 05") + struct.pack("B", i)
        expected = struct.pack("B", len(data)) + data
        can = cans.sniff(timeout=4, count=1, started_callback=lambda: s.begin_send(ISOTP(data=data)))
        assert(can[0].identifier == 0x641)
        print(can[0].data, data)
        assert(can[0].data == expected)


= Send two-frame ISOTP message, using begin_send
with new_can_socket0() as isocan, ISOTPSocket(isocan, sid=0x641, did=0x241) as s:
    with new_can_socket(iface0) as cans:
        can = cans.sniff(timeout=1, count=1, started_callback=lambda: s.begin_send(ISOTP(data=dhex("01 02 03 04 05 06 07 08"))))
        assert can[0].identifier == 0x641
        assert can[0].data == dhex("10 08 01 02 03 04 05 06")
        can = cans.sniff(timeout=1, count=1, started_callback=lambda: cans.send(CAN(identifier = 0x241, data=dhex("30 00 00"))))
        assert can[0].identifier == 0x641
        assert can[0].data == dhex("21 07 08")

cans.close()

= Send single frame ISOTP message
with new_can_socket(iface0) as cans:
    with new_can_socket0() as isocan, ISOTPSocket(isocan, sid=0x641, did=0x241) as s:
        s.send(ISOTP(data=dhex("01 02 03 04 05")))
        can = cans.sniff(timeout=1, count=1)
        assert(can[0].identifier == 0x641)
        assert(can[0].data == dhex("05 01 02 03 04 05"))


= Send two-frame ISOTP message
acker_ready = threading.Event()
def acker():
    with new_can_socket(iface0) as acks:
        acker_ready.set()
        can_pkt = acks.sniff(timeout=1, count=1)
        can = can_pkt[0]
        acks.send(CAN(identifier = 0x241, data=dhex("30 00 00")))

Thread(target=acker).start()
acker_ready.wait(timeout=5)
with new_can_socket0() as isocan, ISOTPSocket(isocan, sid=0x641, did=0x241) as s:
    with new_can_socket(iface0) as cans:
        s.send(ISOTP(data=dhex("01 02 03 04 05 06 07 08")))
        can = cans.sniff(timeout=1, count=1)[0]
        assert(can.identifier == 0x641)
        assert(can.data == dhex("10 08 01 02 03 04 05 06"))
        can = cans.sniff(timeout=1, count=1)[0]
        assert(can.identifier == 0x241)
        assert(can.data == dhex("30 00 00"))
        can = cans.sniff(timeout=1, count=1)[0]
        assert(can.identifier == 0x641)
        assert(can.data == dhex("21 07 08"))


= Send two-frame ISOTP message with bs

acker_ready = threading.Event()
def acker():
    with new_can_socket(iface0) as acks:
        acker_ready.set()
        can_pkt = acks.sniff(timeout=1, count=1)
        can = can_pkt[0]
        acks.send(CAN(identifier = 0x241, data=dhex("30 20 00")))

Thread(target=acker).start()
acker_ready.wait(timeout=5)
with new_can_socket0() as isocan, ISOTPSocket(isocan, sid=0x641, did=0x241) as s:
    with new_can_socket(iface0) as cans:
        s.send(ISOTP(data=dhex("01 02 03 04 05 06 07 08")))
        can = cans.sniff(timeout=1, count=1)[0]
        assert(can.identifier == 0x641)
        assert(can.data == dhex("10 08 01 02 03 04 05 06"))
        can = cans.sniff(timeout=1, count=1)[0]
        assert(can.identifier == 0x241)
        assert(can.data == dhex("30 20 00"))
        can = cans.sniff(timeout=1, count=1)[0]
        assert(can.identifier == 0x641)
        assert(can.data == dhex("21 07 08"))


= Send two-frame ISOTP message with ST
acker_ready = threading.Event()
def acker():
    with new_can_socket0() as acks:
        acker_ready.set()
        can_pkt = acks.sniff(timeout=1, count=1)
        can = can_pkt[0]
        acks.send(CAN(identifier = 0x241, data=dhex("30 00 10")))

Thread(target=acker).start()
acker_ready.wait(timeout=5)
with new_can_socket0() as isocan, ISOTPSocket(isocan, sid=0x641, did=0x241) as s:
    with new_can_socket(iface0) as cans:
        s.send(ISOTP(data=dhex("01 02 03 04 05 06 07 08")))
        can = cans.sniff(timeout=1, count=1)[0]
        assert(can.identifier == 0x641)
        assert(can.data == dhex("10 08 01 02 03 04 05 06"))
        can = cans.sniff(timeout=1, count=1)[0]
        assert(can.identifier == 0x241)
        assert(can.data == dhex("30 00 10"))
        can = cans.sniff(timeout=1, count=1)[0]
        assert(can.identifier == 0x641)
        assert(can.data == dhex("21 07 08"))


= Receive a single frame ISOTP message
with new_can_socket0() as isocan, ISOTPSocket(isocan, sid=0x641, did=0x241) as s:
    with new_can_socket(iface0) as cans:
        cans.send(CAN(identifier = 0x241, data = dhex("05 01 02 03 04 05")))
    isotp = s.recv()
    assert(isotp.data == dhex("01 02 03 04 05"))
    assert(isotp.src == 0x641)
    assert(isotp.dst == 0x241)
    assert(isotp.exsrc == None)
    assert(isotp.exdst == None)


= Receive a single frame ISOTP message, with extended addressing
with new_can_socket0() as isocan, ISOTPSocket(isocan, sid=0x641, did=0x241, extended_addr=0xc0, extended_rx_addr=0xea) as s:
    with new_can_socket(iface0) as cans:
        cans.send(CAN(identifier = 0x241, data = dhex("EA 05 01 02 03 04 05")))
    isotp = s.recv()
    assert(isotp.data == dhex("01 02 03 04 05"))
    assert(isotp.src == 0x641)
    assert(isotp.dst == 0x241)
    assert(isotp.exsrc == 0xc0)
    assert(isotp.exdst == 0xea)


= Receive frames from CandumpReader
candump_fd = BytesIO(b'''  vcan0  541   [8]  10 0A DE AD BE EF AA AA
  vcan0  241   [3]  30 00 00
  vcan0  541   [5]  21 AA AA AA AA
  vcan0  541   [8]  10 0A DE AD BE EF AA AA
  vcan0  541   [8]  10 0A DE AD BE EF AA AA
  vcan0  241   [3]  30 00 00
  vcan0  541   [5]  21 AA AA AA AA
  vcan0  541   [8]  10 0A DE AD BE EF AA AA
  vcan0  241   [3]  30 00 00
  vcan0  541   [5]  21 AA AA AA AA
  vcan0  541   [8]  10 0A DE AD BE EF AA AA
  vcan0  241   [3]  30 00 00
  vcan0  541   [5]  21 AA AA AA AA
  vcan0  541   [8]  10 0A DE AD BE EF AA AA
  vcan0  241   [3]  30 00 00
  vcan0  541   [5]  21 AA AA AA AA
  vcan0  541   [8]  10 0A DE AD BE EF AA AA
  vcan0  241   [3]  30 00 00
  vcan0  541   [5]  21 AA AA AA AA''')

with ISOTPSocket(CandumpReader(candump_fd), sid=0x241, did=0x541, listen_only=True) as s:
    pkts = s.sniff(timeout=2, count=6)
    assert(len(pkts) == 6)
    isotp = pkts[0]
    print(repr(isotp))
    print(hex(isotp.src))
    print(hex(isotp.dst))
    assert(isotp.data == dhex("DE AD BE EF AA AA AA AA AA AA"))
    assert(isotp.src == 0x241)
    assert(isotp.dst == 0x541)

= Receive frames from CandumpReader with ISOTPSniffer without extended addressing
candump_fd = BytesIO(b'''  vcan0  541   [8]  10 0A DE AD BE EF AA AA
  vcan0  241   [3]  30 00 00
  vcan0  541   [5]  21 AA AA AA AA
  vcan0  541   [8]  10 0A DE AD BE EF AA AA
  vcan0  541   [8]  10 0A DE AD BE EF AA AA
  vcan0  241   [3]  30 00 00
  vcan0  541   [5]  21 AA AA AA AA
  vcan0  541   [8]  10 0A DE AD BE EF AA AA
  vcan0  241   [3]  30 00 00
  vcan0  541   [5]  21 AA AA AA AA
  vcan0  541   [8]  10 0A DE AD BE EF AA AA
  vcan0  241   [3]  30 00 00
  vcan0  541   [5]  21 AA AA AA AA
  vcan0  541   [8]  10 0A DE AD BE EF AA AA
  vcan0  241   [3]  30 00 00
  vcan0  541   [5]  21 AA AA AA AA
  vcan0  541   [8]  10 0A DE AD BE EF AA AA
  vcan0  241   [3]  30 00 00
  vcan0  541   [5]  21 AA AA AA AA''')

pkts = sniff(opened_socket=CandumpReader(candump_fd), session=ISOTPSession, timeout=1, session_kwargs={"use_ext_addr": False})
assert(len(pkts) == 6)
isotp = pkts[0]
assert(isotp.data == dhex("DE AD BE EF AA AA AA AA AA AA"))
assert (isotp.dst == 0x541)

= Receive frames from CandumpReader with ISOTPSniffer
* all flow control frames are detected as single frame with extended address

candump_fd = BytesIO(b'''  vcan0  541   [8]  10 0A DE AD BE EF AA AA
  vcan0  241   [3]  30 00 00
  vcan0  541   [5]  21 AA AA AA AA
  vcan0  541   [8]  10 0A DE AD BE EF AA AA
  vcan0  541   [8]  10 0A DE AD BE EF AA AA
  vcan0  241   [3]  30 00 00
  vcan0  541   [5]  21 AA AA AA AA
  vcan0  541   [8]  10 0A DE AD BE EF AA AA
  vcan0  241   [3]  30 00 00
  vcan0  541   [5]  21 AA AA AA AA
  vcan0  541   [8]  10 0A DE AD BE EF AA AA
  vcan0  241   [3]  30 00 00
  vcan0  541   [5]  21 AA AA AA AA
  vcan0  541   [8]  10 0A DE AD BE EF AA AA
  vcan0  241   [3]  30 00 00
  vcan0  541   [5]  21 AA AA AA AA
  vcan0  541   [8]  10 0A DE AD BE EF AA AA
  vcan0  241   [3]  30 00 00
  vcan0  541   [5]  21 AA AA AA AA''')

pkts = sniff(opened_socket=CandumpReader(candump_fd), session=ISOTPSession, timeout=1)
assert(len(pkts) == 12)
isotp = pkts[1]
assert(isotp.data == dhex("DE AD BE EF AA AA AA AA AA AA"))
assert (isotp.dst == 0x541)
isotp = pkts[0]
assert(isotp.data == dhex(""))
assert (isotp.dst == 0x241)

= Receive frames from CandumpReader with ISOTPSniffer and count
* all flow control frames are detected as single frame with extended address

candump_fd = BytesIO(b'''  vcan0  541   [8]  10 0A DE AD BE EF AA AA
  vcan0  241   [3]  30 00 00
  vcan0  541   [5]  21 AA AA AA AA
  vcan0  541   [8]  10 0A DE AD BE EF AA AA
  vcan0  541   [8]  10 0A DE AD BE EF AA AA
  vcan0  241   [3]  30 00 00
  vcan0  541   [5]  21 AA AA AA AA
  vcan0  541   [8]  10 0A DE AD BE EF AA AA
  vcan0  241   [3]  30 00 00
  vcan0  541   [5]  21 AA AA AA AA
  vcan0  541   [8]  10 0A DE AD BE EF AA AA
  vcan0  241   [3]  30 00 00
  vcan0  541   [5]  21 AA AA AA AA
  vcan0  541   [8]  10 0A DE AD BE EF AA AA
  vcan0  241   [3]  30 00 00
  vcan0  541   [5]  21 AA AA AA AA
  vcan0  541   [8]  10 0A DE AD BE EF AA AA
  vcan0  241   [3]  30 00 00
  vcan0  541   [5]  21 AA AA AA AA''')

pkts = sniff(opened_socket=CandumpReader(candump_fd), session=ISOTPSession, timeout=1, count=2)
assert(len(pkts) == 2)
isotp = pkts[1]
assert(isotp.data == dhex("DE AD BE EF AA AA AA AA AA AA"))
assert (isotp.dst == 0x541)
isotp = pkts[0]
assert(isotp.data == dhex(""))
assert (isotp.dst == 0x241)

= ISOTPSession tests

ses = ISOTPSession()
ses.on_packet_received(None)
ses.on_packet_received([None, None])
assert True

= Receive a two-frame ISOTP message
with new_can_socket0() as isocan, ISOTPSocket(isocan, sid=0x641, did=0x241) as s:
    with new_can_socket(iface0) as cans:
        cans.send(CAN(identifier = 0x241, data = dhex("10 0B 01 02 03 04 05 06")))
        cans.send(CAN(identifier = 0x241, data = dhex("21 07 08 09 10 11")))
    isotp = s.recv()
    assert(isotp.data == dhex("01 02 03 04 05 06 07 08 09 10 11"))

= Check what happens when a CAN frame with wrong identifier gets received
with new_can_socket0() as isocan, ISOTPSocket(isocan, sid=0x641, did=0x241) as s:
    with new_can_socket(iface0) as cans:
        cans.send(CAN(identifier = 0x141, data = dhex("05 01 02 03 04 05")))
    assert(s.ins.rx_queue.empty())

+ Testing ISOTPSocket timeouts

= Check if not sending the last CF will make the socket timeout
with new_can_socket0() as isocan, ISOTPSocket(isocan, sid=0x641, did=0x241) as s:
    with new_can_socket(iface0) as cans:
        cans.send(CAN(identifier = 0x241, data = dhex("10 11 01 02 03 04 05 06")))
        cans.send(CAN(identifier = 0x241, data = dhex("21 07 08 09 0A 0B 0C 0D")))
    isotp = s.sniff(timeout=1)

assert(len(isotp) == 0)

= Check if not sending the first CF will make the socket timeout
with new_can_socket0() as isocan, ISOTPSocket(isocan, sid=0x641, did=0x241) as s:
    with new_can_socket(iface0) as cans:
        cans.send(CAN(identifier = 0x241, data = dhex("10 11 01 02 03 04 05 06")))
    isotp = s.sniff(timeout=1)

assert(len(isotp) == 0)

= Check if not sending the first FC will make the socket timeout
exception = None
isotp = ISOTP(data=dhex("01 02 03 04 05 06 07 08 09 0A"))

with new_can_socket0() as isocan, ISOTPSocket(isocan, sid=0x641, did=0x241) as s:
    try:
        s.send(isotp)
        assert(False)
    except Scapy_Exception as ex:
        exception = ex

assert(str(exception) == "TX state was reset due to timeout" or str(exception) == "ISOTP send not completed in 30s")

= Check if not sending the second FC will make the socket timeout
exception = None
isotp = ISOTP(data=b"\xa5" * 120)
test_sem = threading.Semaphore(0)
evt = threading.Event()

def acker():
    with new_can_socket(iface0) as cans:
        evt.set()
        can = cans.sniff(timeout=1, count=1)[0]
        cans.send(CAN(identifier = 0x241, data=dhex("30 04 00")))

thread = Thread(target=acker)
thread.start()
evt.wait(timeout=5)

with new_can_socket0() as isocan, ISOTPSocket(isocan, sid=0x641, did=0x241) as s:
    try:
        s.send(isotp)
    except Scapy_Exception as ex:
        exception = ex

cans.close()
thread.join(timeout=5)

assert(exception is not None)
print(exception)
assert(str(exception) == "TX state was reset due to timeout")

= Check if reception of an overflow FC will make a send fail
exception = None
isotp = ISOTP(data=b"\xa5" * 120)
test_sem = threading.Semaphore(0)
evt = threading.Event()

def acker():
    with new_can_socket(iface0) as cans:
        evt.set()
        can = cans.sniff(timeout=1, count=1)[0]
        cans.send(CAN(identifier = 0x241, data=dhex("32 00 00")))

thread = Thread(target=acker)
thread.start()
evt.wait(timeout=5)

with new_can_socket0() as isocan, ISOTPSocket(isocan, sid=0x641, did=0x241) as s:
    try:
        s.send(isotp)
    except Scapy_Exception as ex:
        exception = ex

thread.join(timeout=5)

assert(exception is not None)

assert(str(exception) == "Overflow happened at the receiver side")

= Close the Socket
with new_can_socket0() as isocan, ISOTPSocket(isocan, sid=0x641, did=0x241) as s:
    s.close()

+ More complex operations

= ISOTPSoftSocket sr1
drain_bus(iface0)
drain_bus(iface1)

evt = threading.Event()
msg = ISOTP(b'\x11\x22\x33\x11\x22\x33\x11\x22\x33\x11\x22\x33')
rx2 = None
evt2 = threading.Event()

def sender(sock):
    global evt, rx2, msg
    evt2.set()
    evt.wait(timeout=5)
    rx2 = sock.sr1(msg, timeout=3, verbose=True)

with new_can_socket0() as isocan_tx, ISOTPSoftSocket(isocan_tx, 0x123, 0x321) as sock_tx, \
    new_can_socket0() as isocan_rx, ISOTPSoftSocket(isocan_rx, 0x321, 0x123) as sock_rx:
    txThread = threading.Thread(target=sender, args=(sock_tx,))
    txThread.start()
    evt2.wait(timeout=1)
    rx = sock_rx.sniff(timeout=3, count=1, started_callback=evt.set)[0]
    sock_rx.send(msg)
    sent = True
    txThread.join(timeout=5)

assert(rx == msg)
assert(sent)
assert(rx2 is not None)
assert(rx2 == msg)

= ISOTPSoftSocket sr1 and ISOTP test vice versa
drain_bus(iface0)
drain_bus(iface1)

rx2 = None
sent = False
evt = threading.Event()
msg = ISOTP(b'\x11\x22\x33\x11\x22\x33\x11\x22\x33\x11\x22\x33')

with new_can_socket0() as isocan, ISOTPSoftSocket(isocan, 0x123, 0x321) as txSock:
    def receiver():
        global rx2, sent
        with new_can_socket0() as isocan, ISOTPSoftSocket(isocan, 0x321, 0x123) as rxSock:
            evt.set()
            rx2 = rxSock.sniff(count=1, timeout=3)
            rxSock.send(msg)
            sent = True
    rxThread = threading.Thread(target=receiver, name="receiver")
    rxThread.start()
    evt.wait(timeout=5)
    rx = txSock.sr1(msg, timeout=5,verbose=True)
    rxThread.join(timeout=5)

assert(rx is not None)
assert(rx == msg)
assert(len(rx2) == 1)
assert(rx2[0] == msg)
assert(sent)

= ISOTPSoftSocket sniff
evt = threading.Event()
succ = False

def receiver():
    global evt, succ, rx
    with new_can_socket0() as isocan, \
            ISOTPSoftSocket(isocan, 0x321, 0x123) as sock:
        rx = sock.sniff(count=5, timeout=5, started_callback=evt.set)
    succ = True

rxThread = threading.Thread(target=receiver)
rxThread.start()
evt.wait(timeout=5)

msg = ISOTP(b'\x11\x22\x33\x11\x22\x33\x11\x22\x33\x11\x22\x33')
with new_can_socket0() as isocan, \
        ISOTPSoftSocket(isocan, 0x123, 0x321) as sock:
    msg.data += b'0'
    sock.send(msg)
    time.sleep(0.1)
    msg.data += b'1'
    sock.send(msg)
    time.sleep(0.1)
    msg.data += b'2'
    sock.send(msg)
    time.sleep(0.1)
    msg.data += b'3'
    sock.send(msg)
    time.sleep(0.1)
    msg.data += b'4'
    sock.send(msg)
    time.sleep(0.1)

rxThread.join(timeout=5)
msg = ISOTP(b'\x11\x22\x33\x11\x22\x33\x11\x22\x33\x11\x22\x33')
msg.data += b'0'
assert(rx[0] == msg)
msg.data += b'1'
assert(rx[1] == msg)
msg.data += b'2'
assert(rx[2] == msg)
msg.data += b'3'
assert(rx[3] == msg)
msg.data += b'4'
assert(rx[4] == msg)
assert(succ)

+ ISOTPSoftSocket MITM attack tests

= bridge and sniff with isotp soft sockets set up vcan0 and vcan1 for package forwarding vcan1
~ needs_root
drain_bus(iface0)
drain_bus(iface1)

succ = False

with new_can_socket0() as can0_0, \
        new_can_socket0() as can0_1, \
        new_can_socket1() as can1_0, \
        new_can_socket1() as can1_1, \
        ISOTPSoftSocket(can0_0, sid=0x241, did=0x641) as isoTpSocket0, \
        ISOTPSoftSocket(can1_0, sid=0x541, did=0x141) as isoTpSocket1, \
        ISOTPSoftSocket(can0_1, sid=0x641, did=0x241) as bSocket0, \
        ISOTPSoftSocket(can1_1, sid=0x141, did=0x141) as bSocket1:
    evt = threading.Event()
    def forwarding(pkt):
        global forwarded
        forwarded += 1
        return pkt
    def bridge():
        global forwarded, succ
        forwarded = 0
        bridge_and_sniff(if1=bSocket0, if2=bSocket1, xfrm12=forwarding, xfrm21=forwarding, timeout=0.5,
                         started_callback=evt.set)
        succ = True
    threadBridge = threading.Thread(target=bridge)
    threadBridge.start()
    evt.wait(timeout=5)
    packetsVCan1 = isoTpSocket1.sniff(timeout=0.5, started_callback=lambda: isoTpSocket0.send(ISOTP(b'Request')))
    threadBridge.join(timeout=5)

assert forwarded == 1
assert len(packetsVCan1) == 1
assert succ

drain_bus(iface0)
drain_bus(iface1)

= bridge and sniff with isotp soft sockets and multiple long packets
~ needs_root
drain_bus(iface0)
drain_bus(iface1)

N = 3
T = 20

succ = False
with new_can_socket0() as can0_0, \
        new_can_socket0() as can0_1, \
        new_can_socket1() as can1_0, \
        new_can_socket1() as can1_1, \
        ISOTPSoftSocket(can0_0, sid=0x241, did=0x641) as isoTpSocket0, \
        ISOTPSoftSocket(can1_0, sid=0x541, did=0x141) as isoTpSocket1, \
        ISOTPSoftSocket(can0_1, sid=0x641, did=0x241) as bSocket0, \
        ISOTPSoftSocket(can1_1, sid=0x141, did=0x541) as bSocket1:
    evt = threading.Event()
    def forwarding(pkt):
        global forwarded
        forwarded += 1
        return pkt
    def bridge():
        global forwarded, succ
        forwarded = 0
        bridge_and_sniff(if1=bSocket0, if2=bSocket1, xfrm12=forwarding, xfrm21=forwarding,
                         timeout=T, count=N, started_callback=evt.set)
        succ = True
    def sendpkts():
        for _ in range(N):
            time.sleep(0.2)
            isoTpSocket0.send(ISOTP(b'RequestASDF1234567890'))
    threadBridge = threading.Thread(target=bridge)
    threadBridge.start()
    evt.wait(timeout=5)
    sender = threading.Thread(target=sendpkts)
    packetsVCan1 = isoTpSocket1.sniff(timeout=T, count=N, started_callback=sender.start)
    sender.join(timeout=5)
    threadBridge.join(timeout=5)

assert forwarded == N
assert len(packetsVCan1) == N
assert succ

drain_bus(iface0)
drain_bus(iface1)

= bridge and sniff with isotp soft sockets set up vcan0 and vcan1 for package change vcan1
~ needs_root
drain_bus(iface0)
drain_bus(iface1)

succ = False
with new_can_socket0() as can0_0, \
        new_can_socket0() as can0_1, \
        new_can_socket1() as can1_0, \
        new_can_socket1() as can1_1, \
        ISOTPSoftSocket(can0_0, sid=0x241, did=0x641) as isoTpSocket0, \
        ISOTPSoftSocket(can1_0, sid=0x641, did=0x241) as isoTpSocket1, \
        ISOTPSoftSocket(can0_1, sid=0x641, did=0x241) as bSocket0, \
        ISOTPSoftSocket(can1_1, sid=0x241, did=0x641) as bSocket1:
    evt = threading.Event()
    def forwarding(pkt):
        pkt.data = 'changed'
        return pkt
    def bridge():
        global succ
        bridge_and_sniff(if1=bSocket0, if2=bSocket1, xfrm12=forwarding, xfrm21=forwarding, timeout=5,
                         started_callback=evt.set)
        succ = True
    threadBridge = threading.Thread(target=bridge)
    threadBridge.start()
    evt.wait(timeout=5)
    packetsVCan1 = isoTpSocket1.sniff(timeout=2, started_callback=lambda: isoTpSocket0.send(ISOTP(b'Request')))
    threadBridge.join(timeout=5)

assert len(packetsVCan1) == 1
assert packetsVCan1[0].data == b'changed'
assert succ

drain_bus(iface0)
drain_bus(iface1)

= Two ISOTPSockets at the same time, sending and receiving

with new_can_socket0() as cs1, ISOTPSocket(cs1, sid=0x641, did=0x241) as s1, \
        new_can_socket0() as cs2, ISOTPSocket(cs2, sid=0x241, did=0x641) as s2:
    isotp = ISOTP(data=b"\x10\x25" * 43)
    def sender():
        s2.send(isotp)
    t = Thread(target=sender)
    result = s1.sniff(count=1, timeout=5, started_callback=t.start)
    t.join(timeout=5)

assert len(result) == 1
assert(result[0].data == isotp.data)


= Two ISOTPSockets at the same time, sending and receiving with tx_gap

with new_can_socket0() as cs1, ISOTPSocket(cs1, sid=0x641, did=0x241, rx_separation_time_min=1) as s1, \
        new_can_socket0() as cs2, ISOTPSocket(cs2, sid=0x241, did=0x641) as s2:
    isotp = ISOTP(data=b"\x10\x25" * 43)
    def sender():
        s2.send(isotp)
    t = Thread(target=sender)
    result = s1.sniff(count=1, timeout=5, started_callback=t.start)
    t.join(timeout=5)

assert len(result) == 1
assert(result[0].data == isotp.data)


= Two ISOTPSockets at the same time, multiple sends/receives
with new_can_socket0() as cs1, ISOTPSocket(cs1, sid=0x641, did=0x241) as s1, \
        new_can_socket0() as cs2, ISOTPSocket(cs2, sid=0x241, did=0x641) as s2:
    def sender(p):
        s2.send(p)
    for i in range(1, 40, 5):
        isotp = ISOTP(data=bytearray(range(i, i * 2)))
        t = Thread(target=sender, args=(isotp,))
        result = s1.sniff(count=1, timeout=5, started_callback=t.start)
        t.join(timeout=5)
        assert len(result)
        assert (result[0].data == isotp.data)


= Send a single frame ISOTP message with padding
with new_can_socket0() as cs1, ISOTPSocket(cs1, sid=0x641, did=0x241, padding=True) as s:
    with new_can_socket(iface0) as cans:
        s.send(ISOTP(data=dhex("01")))
        res = cans.sniff(timeout=1, count=1)[0]
    assert(res.length == 8)


= Send a two-frame ISOTP message with padding
acker_ready = threading.Event()
def acker():
    with new_can_socket(iface0) as acks:
        acker_ready.set()
        can = acks.sniff(timeout=1, count=1)[0]
        acks.send(CAN(identifier = 0x241, data=dhex("30 00 00")))

with new_can_socket(iface0) as cans:
    ack_thread = Thread(target=acker)
    ack_thread.start()
    acker_ready.wait(timeout=5)
    with new_can_socket0() as isocan, ISOTPSocket(isocan, sid=0x641, did=0x241, padding=True) as s:
        s.send(ISOTP(data=dhex("01 02 03 04 05 06 07 08")))
        can = cans.sniff(timeout=1, count=1)[0]
        assert(can.identifier == 0x641)
        assert(can.data == dhex("10 08 01 02 03 04 05 06"))
        can = cans.sniff(timeout=1, count=1)[0]
        assert(can.identifier == 0x241)
        assert(can.data == dhex("30 00 00"))
        can = cans.sniff(timeout=1, count=1)[0]
        assert(can.identifier == 0x641)
        assert(can.data == dhex("21 07 08 CC CC CC CC CC"))
    ack_thread.join(timeout=5)


= Receive a padded single frame ISOTP message with padding disabled
with new_can_socket0() as isocan, ISOTPSocket(isocan, sid=0x641, did=0x241, padding=False) as s:
    with new_can_socket(iface0) as cans:
        cans.send(CAN(identifier=0x241, data=dhex("02 05 06 00 00 00 00 00")))
        res = s.recv()
        assert(res.data == dhex("05 06"))


= Receive a padded single frame ISOTP message with padding enabled
with new_can_socket0() as isocan, ISOTPSocket(isocan, sid=0x641, did=0x241, padding=True) as s:
    with new_can_socket(iface0) as cans:
        cans.send(CAN(identifier=0x241, data=dhex("02 05 06 00 00 00 00 00")))
        res = s.recv()
        assert(res.data == dhex("05 06"))


= Receive a non-padded single frame ISOTP message with padding enabled
with new_can_socket0() as isocan, ISOTPSocket(isocan, sid=0x641, did=0x241, padding=True) as s:
    with new_can_socket(iface0) as cans:
        cans.send(CAN(identifier=0x241, data=dhex("02 05 06")))
        res = s.recv()
        assert(res.data == dhex("05 06"))


= Receive a padded two-frame ISOTP message with padding enabled
with new_can_socket0() as isocan, ISOTPSocket(isocan, sid=0x641, did=0x241, padding=True) as s:
    with new_can_socket(iface0) as cans:
        cans.send(CAN(identifier=0x241, data=dhex("10 09 01 02 03 04 05 06")))
        cans.send(CAN(identifier=0x241, data=dhex("21 07 08 09 00 00 00 00")))
        res = s.recv()
        assert(res.data == dhex("01 02 03 04 05 06 07 08 09"))


= Receive a padded two-frame ISOTP message with padding disabled
with new_can_socket0() as isocan, ISOTPSocket(isocan, sid=0x641, did=0x241, padding=False) as s:
    with new_can_socket(iface0) as cans:
        cans.send(CAN(identifier=0x241, data=dhex("10 09 01 02 03 04 05 06")))
        cans.send(CAN(identifier=0x241, data=dhex("21 07 08 09 00 00 00 00")))
        res = s.recv()
        res.show()
        assert(res.data == dhex("01 02 03 04 05 06 07 08 09"))


+ Compatibility with can-isotp linux kernel modules
~ vcan_socket needs_root linux

= Compatibility with isotpsend
exit_if_no_isotp_module()

message = "01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14"

with new_can_socket0() as isocan, ISOTPSocket(isocan, sid=0x642, did=0x242) as s:
    p = subprocess.Popen(["isotpsend", "-s", "242", "-d", "642", iface0], stdin=subprocess.PIPE, universal_newlines=True)
    p.communicate(message)
    r = p.returncode
    assert(r == 0)
    isotp = s.recv()
    assert(isotp.data == dhex(message))


= Compatibility with isotpsend - extended addresses
exit_if_no_isotp_module()
message = "01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14"

with new_can_socket0() as isocan, ISOTPSocket(isocan, sid=0x644, did=0x244, extended_addr=0xaa, extended_rx_addr=0xee) as s:
    p = subprocess.Popen(["isotpsend", "-s", "244", "-d", "644", "-x", "ee:aa", iface0], stdin=subprocess.PIPE, universal_newlines=True)
    p.communicate(message)
    r = p.returncode
    assert(r == 0)
    isotp = s.recv()
    assert(isotp.data == dhex(message))


= Compatibility with isotprecv
exit_if_no_isotp_module()

isotp = ISOTP(data=bytearray(range(1,20)))
cmd = ["isotprecv", "-s", "243", "-d", "643", "-b", "3", iface0]
p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
time.sleep(0.1)
with new_can_socket0() as isocan, ISOTPSocket(isocan, sid=0x643, did=0x243) as s:
    s.send(isotp)

threading.Timer(1, lambda: p.terminate() if p.poll() else p.wait()).start()  # Timeout the receiver after 1 second
r = p.wait()
assert(0 == r)

result = None
for i in range(10):
    time.sleep(0.1)
    if p.poll() is not None:
        result = p.stdout.readline().decode().strip()
        break

assert(result is not None)
result_data = dhex(result)
assert(result_data == isotp.data)


= Compatibility with isotprecv - extended addresses
exit_if_no_isotp_module()
isotp = ISOTP(data=bytearray(range(1,20)))
cmd = ["isotprecv", "-s245", "-d645", "-b3", "-x", "ee:aa", iface0]
p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
time.sleep(0.1)  # Give some time for starting reception
with new_can_socket0() as isocan, ISOTPSocket(isocan, sid=0x645, did=0x245, extended_addr=0xaa, extended_rx_addr=0xee) as s:
    s.send(isotp)

threading.Timer(1, lambda: p.terminate() if p.poll() else p.wait()).start()  # Timeout the receiver after 1 second
r = p.wait()
assert(0 == r)

result = None
for i in range(10):
    time.sleep(0.1)
    if p.poll() is not None:
        result = p.stdout.readline().decode().strip()
        break

assert(result is not None)
result_data = dhex(result)
assert(result_data == isotp.data)


+ ISOTPNativeSocket tests
~ python3_only not_pypy vcan_socket needs_root linux


= Overwrite definition for vcan_socket systems native sockets
~ conf
if six.PY3 and LINUX:
    conf.contribs['CANSocket'] = {'use-python-can': False}
    from scapy.contrib.cansocket_native import *
    new_can_socket = lambda iface: CANSocket(iface)
    new_can_socket0 = lambda: CANSocket(iface0)
    new_can_socket1 = lambda: CANSocket(iface1)



= Create ISOTP socket
exit_if_no_isotp_module()
s = ISOTPNativeSocket(iface0, sid=0x641, did=0x241)


= Send single frame ISOTP message
exit_if_no_isotp_module()

with new_can_socket(iface0) as cans:
    s = ISOTPNativeSocket(iface0, sid=0x641, did=0x241)
    s.send(ISOTP(data=dhex("01 02 03 04 05")))
    can = cans.sniff(timeout=1, count=1)[0]
    assert(can.identifier == 0x641)
    assert(can.data == dhex("05 01 02 03 04 05"))


= Send single frame ISOTP message Test init with CANSocket
exit_if_no_isotp_module()
cans = CANSocket(iface0)
s = ISOTPNativeSocket(cans, sid=0x641, did=0x241)
s.send(ISOTP(data=dhex("01 02 03 04 05")))
can = cans.sniff(timeout=1, count=1)[0]
assert(can.identifier == 0x641)
assert(can.data == dhex("05 01 02 03 04 05"))
cans.close()


= Test init with wrong type
exit_if_no_isotp_module()
exception_catched = False
try:
    s = ISOTPNativeSocket(42, sid=0x641, did=0x241)
except Scapy_Exception:
    exception_catched = True

assert exception_catched

= Send two-frame ISOTP message
exit_if_no_isotp_module()

evt = threading.Event()
def acker():
    with new_can_socket(iface0) as cans:
        evt.set()
        can = cans.sniff(timeout=1, count=1)[0]
        cans.send(CAN(identifier = 0x241, data=dhex("30 00 00")))


with new_can_socket(iface0) as cans:
    t = Thread(target=acker)
    t.start()
    s = ISOTPNativeSocket(iface0, sid=0x641, did=0x241)
    evt.wait(timeout=5)
    s.send(ISOTP(data=dhex("01 02 03 04 05 06 07 08")))
    can = cans.sniff(timeout=1, count=1)[0]
    assert(can.identifier == 0x641)
    assert(can.data == dhex("10 08 01 02 03 04 05 06"))
    can = cans.sniff(timeout=1, count=1)[0]
    assert(can.identifier == 0x241)
    assert(can.data == dhex("30 00 00"))
    can = cans.sniff(timeout=1, count=1)[0]
    assert(can.identifier == 0x641)
    assert(can.data == dhex("21 07 08"))
    t.join(timeout=5)

= Send a single frame ISOTP message with padding
exit_if_no_isotp_module()
s = ISOTPNativeSocket(iface0, sid=0x641, did=0x241, padding=True)

with new_can_socket(iface0) as cans:
    s.send(ISOTP(data=dhex("01")))
    can = cans.sniff(timeout=1, count=1)[0]
    assert(can.length == 8)


= Send a two-frame ISOTP message with padding
exit_if_no_isotp_module()

acker_ready = threading.Event()
def acker():
    with new_can_socket(iface0) as acks:
        acker_ready.set()
        can = acks.sniff(timeout=1, count=1)[0]
        acks.send(CAN(identifier = 0x241, data=dhex("30 00 00")))

with new_can_socket(iface0) as cans:
    Thread(target=acker).start()
    acker_ready.wait(timeout=5)
    s = ISOTPNativeSocket(iface0, sid=0x641, did=0x241, padding=True)
    s.send(ISOTP(data=dhex("01 02 03 04 05 06 07 08")))
    can = cans.sniff(timeout=1, count=1)[0]
    assert(can.identifier == 0x641)
    assert(can.data == dhex("10 08 01 02 03 04 05 06"))
    can = cans.sniff(timeout=1, count=1)[0]
    assert(can.identifier == 0x241)
    assert(can.data == dhex("30 00 00"))
    can = cans.sniff(timeout=1, count=1)[0]
    assert(can.identifier == 0x641)
    assert(can.data == dhex("21 07 08 CC CC CC CC CC"))


= Receive a padded single frame ISOTP message with padding disabled
exit_if_no_isotp_module()
s = ISOTPNativeSocket(iface0, sid=0x641, did=0x241, padding=False)
with new_can_socket(iface0) as cans:
    cans.send(CAN(identifier=0x241, data=dhex("02 05 06 00 00 00 00 00")))
    res = s.recv()
    assert(res.data == dhex("05 06"))


= Receive a padded single frame ISOTP message with padding enabled
exit_if_no_isotp_module()
s = ISOTPNativeSocket(iface0, sid=0x641, did=0x241, padding=True)
with new_can_socket(iface0) as cans:
    cans.send(CAN(identifier=0x241, data=dhex("02 05 06 00 00 00 00 00")))
    res = s.recv()
    assert(res.data == dhex("05 06"))


= Receive a non-padded single frame ISOTP message with padding enabled
exit_if_no_isotp_module()
s = ISOTPNativeSocket(iface0, sid=0x641, did=0x241, padding=True)
with new_can_socket(iface0) as cans:
    cans.send(CAN(identifier=0x241, data=dhex("02 05 06")))
    res = s.recv()
    assert(res.data == dhex("05 06"))


= Receive a padded two-frame ISOTP message with padding enabled
exit_if_no_isotp_module()
s = ISOTPNativeSocket(iface0, sid=0x641, did=0x241, padding=True)
with new_can_socket(iface0) as cans:
    cans.send(CAN(identifier=0x241, data=dhex("10 09 01 02 03 04 05 06")))
    cans.send(CAN(identifier=0x241, data=dhex("21 07 08 09 00 00 00 00")))
    res = s.recv()
    assert(res.data == dhex("01 02 03 04 05 06 07 08 09"))


= Receive a padded two-frame ISOTP message with padding disabled
exit_if_no_isotp_module()
s = ISOTPNativeSocket(iface0, sid=0x641, did=0x241, padding=False)
with new_can_socket(iface0) as cans:
    cans.send(CAN(identifier=0x241, data=dhex("10 09 01 02 03 04 05 06")))
    cans.send(CAN(identifier=0x241, data=dhex("21 07 08 09 00 00 00 00")))
    res = s.recv()
    assert(res.data == dhex("01 02 03 04 05 06 07 08 09"))


= Receive a single frame ISOTP message
exit_if_no_isotp_module()
s = ISOTPNativeSocket(iface0, sid=0x641, did=0x241)
with new_can_socket(iface0) as cans:
    cans.send(CAN(identifier = 0x241, data = dhex("05 01 02 03 04 05")))
    isotp = s.recv()
    assert(isotp.data == dhex("01 02 03 04 05"))
    assert(isotp.src == 0x641)
    assert(isotp.dst == 0x241)
    assert(isotp.exsrc == None)
    assert(isotp.exdst == None)


= Receive a single frame ISOTP message, with extended addressing
exit_if_no_isotp_module()
s = ISOTPNativeSocket(iface0, sid=0x641, did=0x241, extended_addr=0xc0, extended_rx_addr=0xea)
with new_can_socket(iface0) as cans:
    cans.send(CAN(identifier = 0x241, data = dhex("EA 05 01 02 03 04 05")))
    isotp = s.recv()
    assert(isotp.data == dhex("01 02 03 04 05"))
    assert(isotp.src == 0x641)
    assert(isotp.dst == 0x241)
    assert(isotp.exsrc == 0xc0)
    assert(isotp.exdst == 0xea)


= Receive a two-frame ISOTP message
exit_if_no_isotp_module()
s = ISOTPNativeSocket(iface0, sid=0x641, did=0x241)
with new_can_socket(iface0) as cans:
    cans.send(CAN(identifier = 0x241, data = dhex("10 0B 01 02 03 04 05 06")))
    cans.send(CAN(identifier = 0x241, data = dhex("21 07 08 09 10 11")))
    isotp = s.recv()
    assert(isotp.data == dhex("01 02 03 04 05 06 07 08 09 10 11"))

= Receive a two-frame ISOTP message and test python with statement
exit_if_no_isotp_module()
with ISOTPNativeSocket(iface0, sid=0x641, did=0x241) as s:
    with new_can_socket(iface0) as cans:
        cans.send(CAN(identifier = 0x241, data = dhex("10 0B 01 02 03 04 05 06")))
        cans.send(CAN(identifier = 0x241, data = dhex("21 07 08 09 10 11")))
    isotp = s.recv()
    assert(isotp.data == dhex("01 02 03 04 05 06 07 08 09 10 11"))


= ISOTP Socket sr1 test
exit_if_no_isotp_module()

txSock = ISOTPNativeSocket(iface0, sid=0x123, did=0x321, basecls=ISOTP)
txmsg = ISOTP(b'\x11\x22\x33')
rx2 = None

receiver_up = Event()

def sender():
    global receiver_up
    receiver_up.wait(timeout=5)
    global txmsg
    global rx2
    rx2 = txSock.sr1(txmsg, timeout=1, verbose=True)

def receiver():
    global receiver_up
    with new_can_socket(iface0) as cans:
        rx = cans.sniff(timeout=1, count=1, started_callback=receiver_up.set)[0]
        cans.send(CAN(identifier=0x321, length=4, data=b'\x03\x7f\x22\x33'))
    expectedrx = CAN(identifier=0x123, length=4, data=b'\x03\x11\x22\x33')
    assert(rx.length == expectedrx.length)
    assert(rx.data == expectedrx.data)
    assert(rx.identifier == expectedrx.identifier)

txThread = threading.Thread(target=sender)
txThread.start()
receiver()
txThread.join(timeout=5)

assert(rx2 is not None)
assert(rx2 == ISOTP(b'\x7f\x22\x33'))
assert(rx2.answers(txmsg))

= ISOTP Socket sr1 and ISOTP test
exit_if_no_isotp_module()
txSock = ISOTPNativeSocket(iface0, 0x123, 0x321, basecls=ISOTP)
rxSock = ISOTPNativeSocket(iface0, 0x321, 0x123, basecls=ISOTP)
msg = ISOTP(b'\x11\x22\x33\x11\x22\x33\x11\x22\x33\x11\x22\x33')
rx2 = None

receiver_up = Event()

def sender():
    receiver_up.wait(timeout=5)
    global rx2
    rx2 = txSock.sr1(msg, timeout=1, verbose=True)

def receiver():
    global rx
    receiver_up.set()
    rx = rxSock.recv()
    rxSock.send(msg)

txThread = threading.Thread(target=sender)
txThread.start()
receiver()
txThread.join(timeout=5)

assert(rx == msg)
assert(rxSock.send(msg))
assert(rx2 is not None)
assert(rx2 == msg)

= ISOTP Socket sr1 and ISOTP test vice versa
exit_if_no_isotp_module()

rxSock = ISOTPNativeSocket(iface0, 0x321, 0x123, basecls=ISOTP)
txSock = ISOTPNativeSocket(iface0, 0x123, 0x321, basecls=ISOTP)

msg = ISOTP(b'\x11\x22\x33\x11\x22\x33\x11\x22\x33\x11\x22\x33')

receiver_up = Event()

def receiver():
    global rx2, sent
    rx2 = rxSock.sniff(count=1, timeout=1, started_callback=receiver_up.set)
    sent = rxSock.send(msg)

def sender():
    global rx
    receiver_up.wait(timeout=5)
    rx = txSock.sr1(msg, timeout=1,verbose=True)

rx2 = None
sent = False
rxThread = threading.Thread(target=receiver)
rxThread.start()
sender()
rxThread.join(timeout=5)

assert(rx == msg)
assert(rx2[0] == msg)
assert(sent)

= ISOTP Socket sniff
exit_if_no_isotp_module()

rxSock = ISOTPNativeSocket(iface0, 0x321, 0x123, basecls=ISOTP)
txSock = ISOTPNativeSocket(iface0, 0x123, 0x321, basecls=ISOTP)
succ = False

receiver_up = Event()

def receiver():
    rx = rxSock.sniff(count=5, timeout=1, started_callback=receiver_up.set)
    msg = ISOTP(b'\x11\x22\x33\x11\x22\x33\x11\x22\x33\x11\x22\x33')
    msg.data += b'0'
    assert(rx[0] == msg)
    msg.data += b'1'
    assert(rx[1] == msg)
    msg.data += b'2'
    assert(rx[2] == msg)
    msg.data += b'3'
    assert(rx[3] == msg)
    msg.data += b'4'
    assert(rx[4] == msg)
    global succ
    succ = True

def sender():
    receiver_up.wait(timeout=5)
    msg = ISOTP(b'\x11\x22\x33\x11\x22\x33\x11\x22\x33\x11\x22\x33')
    msg.data += b'0'
    assert(txSock.send(msg))
    msg.data += b'1'
    assert(txSock.send(msg))
    msg.data += b'2'
    assert(txSock.send(msg))
    msg.data += b'3'
    assert(txSock.send(msg))
    msg.data += b'4'
    assert(txSock.send(msg))

rxThread = threading.Thread(target=receiver)
rxThread.start()
sender()
rxThread.join(timeout=5)

assert(succ)

+ ISOTPNativeSocket MITM attack tests
~ python3_only vcan_socket needs_root linux

= bridge and sniff with isotp native sockets set up vcan0 and vcan1 for package forwarding vcan1
exit_if_no_isotp_module()

isoTpSocket0 = ISOTPNativeSocket(iface0, sid=0x241, did=0x641)
isoTpSocket1 = ISOTPNativeSocket(iface1, sid=0x641, did=0x241)
bSocket0 = ISOTPNativeSocket(iface0, sid=0x641, did=0x241)
bSocket1 = ISOTPNativeSocket(iface1, sid=0x241, did=0x641)

bridgeStarted = threading.Event()
def bridge():
    global bridgeStarted
    def forwarding(pkt):
        return pkt
    bridge_and_sniff(if1=bSocket0, if2=bSocket1, xfrm12=forwarding, xfrm21=forwarding, timeout=2, count=1, started_callback=bridgeStarted.set)
    bSocket0.close()
    bSocket1.close()
    global bSucc
    bSucc = True

def RequestOnBus0():
    global rSucc
    isoTpSocket0.send(ISOTP(b'Request'))
    rSucc = True

bSucc = False
rSucc = False

threadBridge = threading.Thread(target=bridge)
threadBridge.start()
threadSender = threading.Thread(target=RequestOnBus0)
bridgeStarted.wait(timeout=5)

packetsVCan1 = isoTpSocket1.sniff(timeout=0.5, started_callback=threadSender.start)

len(packetsVCan1) == 1

isoTpSocket0.close()
isoTpSocket1.close()

threadSender.join(timeout=5)
threadBridge.join(timeout=5)

assert(bSucc)
assert(rSucc)

= bridge and sniff with isotp native sockets set up vcan0 and vcan1 for package change to vcan1
exit_if_no_isotp_module()

isoTpSocket0 = ISOTPNativeSocket(iface0, sid=0x241, did=0x641)
isoTpSocket1 = ISOTPNativeSocket(iface1, sid=0x641, did=0x241)
bSocket0 = ISOTPNativeSocket(iface0, sid=0x641, did=0x241)
bSocket1 = ISOTPNativeSocket(iface1, sid=0x241, did=0x641)

bSucc = False
rSucc = False

bridgeStarted = threading.Event()
def bridge():
    global bridgeStarted
    global bSucc
    def forwarding(pkt):
        pkt.data = 'changed'
        return pkt
    bridge_and_sniff(if1=bSocket0, if2=bSocket1, xfrm12=forwarding, xfrm21=forwarding, timeout=0.5, started_callback=bridgeStarted.set)
    bSocket0.close()
    bSocket1.close()
    bSucc = True

def RequestOnBus0():
    global rSucc
    isoTpSocket0.send(ISOTP(b'Request'))
    rSucc = True

threadBridge = threading.Thread(target=bridge)
threadBridge.start()
threadSender = threading.Thread(target=RequestOnBus0)
bridgeStarted.wait(timeout=5)
packetsVCan1 = isoTpSocket1.sniff(timeout=0.5, started_callback=threadSender.start)

packetsVCan1[0].data = b'changed'
len(packetsVCan1) == 1

isoTpSocket0.close()
isoTpSocket1.close()

threadSender.join(timeout=5)
threadBridge.join(timeout=5)

assert(bSucc)
assert(rSucc)

= bridge and sniff with isotp native sockets set up vcan0 and vcan1 for package forwarding in both directions
exit_if_no_isotp_module()

bSucc = False
rSucc = False

isoTpSocket0 = ISOTPNativeSocket(iface0, sid=0x241, did=0x641)
isoTpSocket1 = ISOTPNativeSocket(iface1, sid=0x641, did=0x241)
bSocket0 = ISOTPNativeSocket(iface0, sid=0x641, did=0x241)
bSocket1 = ISOTPNativeSocket(iface1, sid=0x241, did=0x641)

bridgeStarted = threading.Event()
def bridge():
    global bridgeStarted
    global bSucc
    def forwarding(pkt):
        return pkt
    bridge_and_sniff(if1=bSocket0, if2=bSocket1, xfrm12=forwarding, xfrm21=forwarding, timeout=0.5, started_callback=bridgeStarted.set)
    bSocket0.close()
    bSocket1.close()
    bSucc = True

def RequestBothVCans():
    global rSucc
    packetVcan0 = ISOTP(b'RequestVcan0')
    packetVcan1 = ISOTP(b'RequestVcan1')
    isoTpSocket0.send(packetVcan0)
    isoTpSocket1.send(packetVcan1)
    rSucc = True

threadBridge = threading.Thread(target=bridge)
threadBridge.start()
threadSender = threading.Thread(target=RequestOnBus0)
bridgeStarted.wait(timeout=5)

packetsVCan0 = isoTpSocket0.sniff(timeout=0.5, started_callback=threadSender.start)
packetsVCan1 = isoTpSocket1.sniff(timeout=0.5)

len(packetsVCan0) == 1
len(packetsVCan1) == 1

isoTpSocket0.close()
isoTpSocket1.close()

threadSender.join(timeout=5)
threadBridge.join(timeout=5)

assert(bSucc)
assert(rSucc)

= bridge and sniff with isotp native sockets set up vcan0 and vcan1 for package change in both directions
exit_if_no_isotp_module()

bSucc = False
rSucc = False

isoTpSocket0 = ISOTPNativeSocket(iface0, sid=0x241, did=0x641)
isoTpSocket1 = ISOTPNativeSocket(iface1, sid=0x641, did=0x241)
bSocket0 = ISOTPNativeSocket(iface0, sid=0x641, did=0x241)
bSocket1 = ISOTPNativeSocket(iface1, sid=0x241, did=0x641)

bridgeStarted = threading.Event()
def bridge():
    global bridgeStarted
    global bSucc
    def forwarding(pkt):
        pkt.data = 'changed'
        return pkt
    bridge_and_sniff(if1=bSocket0, if2=bSocket1, xfrm12=forwarding, xfrm21=forwarding, timeout=0.5, started_callback=bridgeStarted.set)
    bSocket0.close()
    bSocket1.close()
    bSucc = True

def RequestBothVCans():
    global rSucc
    packetVcan0 = ISOTP(b'RequestVcan0')
    packetVcan1 = ISOTP(b'RequestVcan1')
    isoTpSocket0.send(packetVcan0)
    isoTpSocket1.send(packetVcan1)
    rSucc = True

threadBridge = threading.Thread(target=bridge)
threadBridge.start()
threadSender = threading.Thread(target=RequestBothVCans)
bridgeStarted.wait(timeout=5)

packetsVCan0 = isoTpSocket0.sniff(timeout=0.5, started_callback=threadSender.start)
packetsVCan1 = isoTpSocket1.sniff(timeout=0.5)

packetsVCan0[0].data = b'changed'
assert len(packetsVCan0) == 1
packetsVCan1[0].data = b'changed'
assert len(packetsVCan1) == 1

isoTpSocket0.close()
isoTpSocket1.close()

threadSender.join(timeout=5)
threadBridge.join(timeout=5)

assert(bSucc)
assert(rSucc)

+ Cleanup

= Cleanup reference to ISOTPSoftSocket to let the thread end
s = None

= Delete vcan interfaces

assert cleanup_interfaces()
