% EtherCat test campaign

#
# execute test:
# $ test/run_tests -P "load_contrib('ethercat')" -t test/contrib/ethercat.uts
#

+ LEBitFields
= regression test

TEST_SAMPLE_ENUM = {
    0x01: 'one',
    0x02: 'two',
    0x03: 'three',
    0x04: 'four',
    0x05: 'five',
    0x06: 'six',
    0x07: 'seven'
}

class BitFieldUserExampleLE(Packet):

    fields_desc = [
        LEBitEnumField('a', 0, 2, TEST_SAMPLE_ENUM),
        LEBitField('b', 0, 18),
        LEBitField('c', 0, 5),
        LEBitField('d', 0, 23),
    ]

class BitFieldUserExample(Packet):

    fields_desc = [
        BitEnumField('a', 0, 2, TEST_SAMPLE_ENUM),
        BitField('b', 0, 18),
        BitField('c', 0, 5),
        BitField('d', 0, 23),
    ]

test_data = [
    {
        'a':0x01,
        'b':0x00,
        'c':0x00,
        'd':0x123456
    },
    {
        'a': 0x00,
        'b': 0b111111111111111111,
        'c': 0x00,
        'd': 0x112233
    },
    {
        'a': 0x00,
        'b': 0x00,
        'c': 0x01,
        'd': 0x00
    },
]

for data in test_data:
    bf_le = BitFieldUserExampleLE(**data)
    bf = BitFieldUserExample(**data)
    # rebuild big-endian and little-endian bitfields from its own binary expressions
    bf_le = BitFieldUserExampleLE(bf_le.do_build())
    bf = BitFieldUserExample(bf.do_build())
    ''' disabled as only required for 'visual debugging'
    from scapy.compat import raw
    # dump content for debugging
    bitstr = ''
    hexstr = ''
    for i in bytearray(raw(bf)):
        bitstr += '{:08b} '.format(i)
        hexstr += '{:02x} '.format(i)
    print('BE - BITS: {} HEX: {}  ({})'.format(bitstr, hexstr, data))
    bitstr = ''
    hexstr = ''
    for i in bytearray(raw(bf_le)):
        bitstr += '{:08b} '.format(i)
        hexstr += '{:02x} '.format(i)
    print('LE - BITS: {} HEX: {}  ({})'.format(bitstr, hexstr, data))
    '''
    # compare values
    for key in data:
        assert(getattr(bf,key) == data[key])
        assert (getattr(bf_le, key) == data[key])

= Avoid mix of LEBitFields and BitFields

TEST_SAMPLE_ENUM = {
    0x01: 'one',
    0x02: 'two',
    0x03: 'three',
    0x04: 'four',
    0x05: 'five',
    0x06: 'six',
    0x07: 'seven'
}

class MissingFieldSameLEFieldTypes(Packet):

    fields_desc = [
        LEBitEnumField('a', 0, 2, TEST_SAMPLE_ENUM),
        LEBitField('b', 0, 18),
    ]

try:
    frm = MissingFieldSameLEFieldTypes().build()
    assert False
except LEBitFieldSequenceException:
    pass


class MissingFieldDifferentLEFieldTypes(Packet):

    fields_desc = [
        LEBitEnumField('a', 0, 2, TEST_SAMPLE_ENUM),
        LEBitField('b', 0, 18),
    ]

try:
    frm = MissingFieldDifferentLEFieldTypes().build()
    assert False
except LEBitFieldSequenceException:
    pass


class MixedBitFieldTypesLEBE(Packet):

    fields_desc = [
        LEBitField('a', 0, 12),
        BitField('b', 0, 4),
    ]

try:
    frm = MixedBitFieldTypesLEBE().build()
    assert False
except LEBitFieldSequenceException:
    pass


class MixedBitFieldTypesBELE(Packet):

    fields_desc = [
        BitField('b', 0, 4),
        LEBitField('a', 0, 12),
    ]

try:
    frm = MixedBitFieldTypesBELE().build()
    assert False
except LEBitFieldSequenceException:
    pass

################################################
+ EtherCat header layer handling
= EtherCat and padding

frm = Ether() / EtherCat()
# even with padding the length must be zero
# the Ether(do_build()) forces the calculation of all (post_build generated) fields
frm = Ether(frm.do_build())
assert(frm[EtherCat].length == 0)
assert(len(frm) == 60)
frm = Ether()/Dot1Q()/Dot1Q()/EtherCat()
frm = Ether()/EtherCat()
assert(len(frm) == 60)
frm = Ether(frm.do_build())
assert(frm[EtherCat].length == 0)

= EtherCat and RawPayload

frm=Ether()/EtherCat()/Raw(b'0123456789')
assert(len(frm) == 60)
frm = Ether(frm.do_build())
assert(frm[EtherCat].length == 10)
frm = Ether()/EtherCat()/Raw(b'012345678901234567890123456789012345678901234567890123456789')
frm = Ether(frm.do_build())
assert(len(frm) == 76)
assert(frm[EtherCat].length == 60)

= EtherCat - test invalid length detection

nums_11_bits = [random.randint(0, 65535) & 0b11111111111 for dummy in range(0, 23)]
nums_4_bits = [random.randint(0, 16) & 0b1111 for dummy in range(0, 23)]

frm = Ether()/EtherCat()/EtherCatAPRD(adp=0x1234, ado=0x5678, irq=0xbad0, wkc=0xbeef, data=[1]*2035, c=1)
frm = Ether(frm.do_build())
assert(frm[EtherCat].length == 2047)
assert(len(frm[EtherCatAPRD].data) == 2035)
assert(frm[EtherCatAPRD].c == 1)

data_oversized = False
try:
    frm = Ether()/EtherCat()/EtherCatAPRD(adp=0x1234, ado=0x5678, irq=0xbad0, wkc=0xbeef, data=[2]*2048, c=1)
    frm = Ether(frm.do_build())
except ValueError as err:
    data_oversized = True
    assert('data size' in str(err))

assert(data_oversized == True)
dlpdu_oversized = False
try:
    frm = Ether()/EtherCat()/EtherCatAPRD(adp=0x1234, ado=0x5678, irq=0xbad0, wkc=0xbeef, data=[2]*2036, c=1)
    frm = Ether(frm.do_build())
except ValueError as err:
    dlpdu_oversized = True
    assert('EtherCat message' in str(err))

assert(dlpdu_oversized == True)

frm = Ether()/EtherCat(_reserved=1)/EtherCatAPRD(adp=0x1234, ado=0x5678, irq=0xbad0, wkc=0xbeef, data=[3], c=0)
frm = Ether(frm.do_build())
assert(frm[EtherCatAPRD].c == 0)


assert(frm[EtherCat]._reserved == 0)

= EtherCat and Type12 DLPDU layers

for type_id in EtherCat.ETHERCAT_TYPE12_DLPDU_TYPES:
    data = [random.randint(0, 255) for dummy in range(random.randint(1, 10))]
    frm = Ether() / EtherCat() / EtherCat.ETHERCAT_TYPE12_DLPDU_TYPES[type_id](data= data)
    frm = Ether(frm.do_build())
    # expect to have one layer of current Type12 DLPDU type
    dlpdu_lyr = frm[EtherCat.ETHERCAT_TYPE12_DLPDU_TYPES[type_id]]
    assert(dlpdu_lyr.data == data)

= EtherCat and Type12 DLPDU layer using structure used for physical and broadcast addressing

# the code is the same for all layer sharing this structure - no need to test em all
test_data = [121,99,110,104,114,109,58,41]
frm = Ether()/EtherCat()/EtherCatAPRD(adp=0x1234, ado=0x5678, irq=0xbad0, wkc=0xbeef, data=test_data)
frm = Ether(frm.do_build())
aprd_lyr = frm[EtherCatAPRD]
assert(aprd_lyr.adp == 0x1234)
assert(aprd_lyr.ado == 0x5678)
assert(aprd_lyr.irq == 0xbad0)
assert(aprd_lyr.wkc == 0xbeef)
assert(aprd_lyr.data == test_data)

= EtherCat and Type12 DLPDU layer using structure used for logical addressing

test_data = [116,104,101,116,97,111,105,115,103,114,101,97,116]
frm = Ether() / EtherCat() / EtherCatLRD(adr=0x11223344, irq=0xbad0, wkc=0xbeef, data=test_data)
frm = Ether(frm.do_build())
aprd_lyr = frm[EtherCatLRD]
assert (aprd_lyr.adr == 0x11223344)
assert (aprd_lyr.irq == 0xbad0)
assert (aprd_lyr.wkc == 0xbeef)
assert (aprd_lyr.data == test_data)

= EtherCat and randomly stacked Type12 DLPDU layers

for outer_dummy in range(10):
    frm = Ether()/EtherCat()
    layer_ids = []
    for inner_dummy in range(random.randint(1, 20)):
        layer_id = random.choice(list(EtherCat.ETHERCAT_TYPE12_DLPDU_TYPES))
        layer_ids.append(layer_id)
        frm = frm / EtherCat.ETHERCAT_TYPE12_DLPDU_TYPES[layer_id]()
    # build frame and convert back
    frm = Ether(frm.do_build())
    idx = 0
    for layer_id in layer_ids:
        assert(type(EtherCat.ETHERCAT_TYPE12_DLPDU_TYPES[layer_id]()) == type(frm[2 + idx]))
        idx += 1
