Repository: secdev/scapy

Issue:
[WIP] Adding more BTLE PDUs
Hello @gpotter2 and @guedou I'm doing this preliminary PR to add more PDUs for BTLE layer and fix some problems I've found for CRC and BTLE_CONNECT_REQ. Keep in mind that this is a way preliminary PR just to sync what changes needs to be done, that's why I didn't even run flake8 and the tests will probably fail as of now.
Here's the summary of new additions, fixes and observations for this PR:

- Added most data PDUs up to BLE 4.2;
- Fixed connection request endianness. AA and crc_init should be little endian;
- Fixed the CtrlPDU layer For some reason it was the same as LL_VERSION_IND layer;
-  Added more BTLE_Versions
- LL_FEATURE_RSP is not ready, somewhat I had to brak the bitfield in 16 bits as it was no working well with an odd bit field number;
- The CRC only corresponds to wireshark, if it's in little endian. The bits reversed is fine though. 
- The Data channel PDUs name was bases on the BLE standard, but let me know if you'd prefer different and simpler names.


Discussion:

Resolving patch:
diff --git a/scapy/layers/bluetooth4LE.py b/scapy/layers/bluetooth4LE.py
--- a/scapy/layers/bluetooth4LE.py
+++ b/scapy/layers/bluetooth4LE.py
@@ -14,9 +14,10 @@
     PPI_BTLE
 from scapy.packet import Packet, bind_layers
 from scapy.fields import BitEnumField, BitField, ByteEnumField, ByteField, \
-    Field, LEIntField, LEShortEnumField, LEShortField, \
+    Field, FlagsField, LEIntField, LEShortEnumField, LEShortField, \
     MACField, PacketListField, SignedByteField, X3BytesField, XBitField, \
-    XByteField, XIntField, XShortField, XLEIntField, XLEShortField
+    XByteField, XIntField, XShortField, XLEIntField, XLELongField, \
+    XLEShortField
 from scapy.contrib.ethercat import LEBitEnumField, LEBitField
 
 from scapy.layers.bluetooth import EIR_Hdr, L2CAP_Hdr
@@ -128,6 +129,37 @@ def getfield(self, pkt, s):
         return s[5:], self.m2i(pkt, struct.unpack(self.fmt, s[:5] + b"\x00\x00\x00")[0])  # noqa: E501
 
 
+class BTLEFeatureField(FlagsField):
+    def __init__(self, name, default):
+        super(BTLEFeatureField, self).__init__(
+            name, default, -64,
+            ['le_encryption',
+             'conn_par_req_proc',
+             'ext_reject_ind',
+             'slave_init_feat_exch',
+             'le_ping',
+             'le_data_len_ext',
+             'll_privacy',
+             'ext_scan_filter',
+             'le_2m_phy',
+             'tx_mod_idx',
+             'rx_mod_idx',
+             'le_coded_phy',
+             'le_ext_adv',
+             'le_periodic_adv',
+             'ch_sel_alg',
+             'le_pwr_class']
+        )
+
+
+class BTLEPhysField(FlagsField):
+    def __init__(self, name, default):
+        super(BTLEPhysField, self).__init__(
+            name, default, -8,
+            ['phy_1m', 'phy_2m', 'phy_coded']
+        )
+
+
 ##########
 # Layers #
 ##########
@@ -300,23 +332,270 @@ class BTLE_CONNECT_REQ(Packet):
 
 
 BTLE_Versions = {
-    7: '4.1'
+    6: '4.0',
+    7: '4.1',
+    8: '4.2',
+    9: '5.0',
+    10: '5.1',
+    11: '5.2',
 }
+
+
 BTLE_Corp_IDs = {
-    0xf: 'Broadcom Corporation'
+    0xf: 'Broadcom Corporation',
+    0x59: 'Nordic Semiconductor ASA'
 }
 
 
-class CtrlPDU(Packet):
-    name = "CtrlPDU"
+BTLE_BTLE_CTRL_opcode = {
+    0x00: 'LL_CONNECTION_UPDATE_REQ',
+    0x01: 'LL_CHANNEL_MAP_REQ',
+    0x02: 'LL_TERMINATE_IND',
+    0x03: 'LL_ENC_REQ',
+    0x04: 'LL_ENC_RSP',
+    0x05: 'LL_START_ENC_REQ',
+    0x06: 'LL_START_ENC_RSP',
+    0x07: 'LL_UNKNOWN_RSP',
+    0x08: 'LL_FEATURE_REQ',
+    0x09: 'LL_FEATURE_RSP',
+    0x0A: 'LL_PAUSE_ENC_REQ',
+    0x0B: 'LL_PAUSE_ENC_RSP',
+    0x0C: 'LL_VERSION_IND',
+    0x0D: 'LL_REJECT_IND',
+    0x0E: 'LL_SLAVE_FEATURE_REQ',
+    0x0F: 'LL_CONNECTION_PARAM_REQ',
+    0x10: 'LL_CONNECTION_PARAM_RSP',
+    0x14: 'LL_LENGTH_REQ',
+    0x15: 'LL_LENGTH_RSP',
+    0x16: 'LL_PHY_REQ',
+    0x17: 'LL_PHY_RSP',
+    0x18: 'LL_PHY_UPDATE_IND',
+}
+
+
+class BTLE_EMPTY_PDU(Packet):
+    name = "Empty data PDU"
+
+
+class BTLE_CTRL(Packet):
+    name = "BTLE_CTRL"
+    fields_desc = [
+        ByteEnumField("opcode", 0, BTLE_BTLE_CTRL_opcode)
+    ]
+
+
+class LL_CONNECTION_UPDATE_IND(Packet):
+    name = 'LL_CONNECTION_UPDATE_IND'
+    fields_desc = [
+        XByteField("win_size", 0),
+        XLEShortField("win_offset", 0),
+        XLEShortField("interval", 6),
+        XLEShortField("latency", 0),
+        XLEShortField("timeout", 50),
+        XLEShortField("instant", 6),
+    ]
+
+
+class LL_CHANNEL_MAP_IND(Packet):
+    name = 'LL_CHANNEL_MAP_IND'
+    fields_desc = [
+        BTLEChanMapField("chM", 0xFFFFFFFFFE),
+        XLEShortField("instant", 0),
+    ]
+
+
+class LL_TERMINATE_IND(Packet):
+    name = 'LL_TERMINATE_IND'
+    fields_desc = [
+        XByteField("code", 0x0),
+    ]
+
+
+class LL_ENC_REQ(Packet):
+    name = 'LL_ENC_REQ'
+    fields_desc = [
+        XLELongField("rand", 0),
+        XLEShortField("ediv", 0),
+        XLELongField("skdm", 0),
+        XLEIntField("ivm", 0),
+    ]
+
+
+class LL_ENC_RSP(Packet):
+    name = 'LL_ENC_RSP'
+    fields_desc = [
+        XLELongField("skds", 0),
+        XLEIntField("ivs", 0),
+    ]
+
+
+class LL_START_ENC_REQ(Packet):
+    name = 'LL_START_ENC_REQ'
+    fields_desc = []
+
+
+class LL_START_ENC_RSP(Packet):
+    name = 'LL_START_ENC_RSP'
+
+
+class LL_UNKNOWN_RSP(Packet):
+    name = 'LL_UNKNOWN_RSP'
+    fields_desc = [
+        XByteField("code", 0x0),
+    ]
+
+
+class LL_FEATURE_REQ(Packet):
+    name = "LL_FEATURE_REQ"
+    fields_desc = [
+        BTLEFeatureField("feature_set", 0)
+    ]
+
+
+class LL_FEATURE_RSP(Packet):
+    name = "LL_FEATURE_RSP"
+    fields_desc = [
+        BTLEFeatureField("feature_set", 0)
+    ]
+
+
+class LL_PAUSE_ENC_REQ(Packet):
+    name = "LL_PAUSE_ENC_REQ"
+
+
+class LL_PAUSE_ENC_RSP(Packet):
+    name = "LL_PAUSE_ENC_RSP"
+
+
+class LL_VERSION_IND(Packet):
+    name = "LL_VERSION_IND"
     fields_desc = [
-        XByteField("optcode", 0),
-        ByteEnumField("version", 0, BTLE_Versions),
-        LEShortEnumField("Company", 0, BTLE_Corp_IDs),
+        ByteEnumField("version", 8, BTLE_Versions),
+        LEShortEnumField("company", 0, BTLE_Corp_IDs),
         XShortField("subversion", 0)
     ]
 
 
+class LL_REJECT_IND(Packet):
+    name = "LL_REJECT_IND"
+    fields_desc = [
+        XByteField("code", 0x0),
+    ]
+
+
+class LL_SLAVE_FEATURE_REQ(Packet):
+    name = "LL_SLAVE_FEATURE_REQ"
+    fields_desc = [
+        BTLEFeatureField("feature_set", 0)
+    ]
+
+
+class LL_CONNECTION_PARAM_REQ(Packet):
+    name = "LL_CONNECTION_PARAM_REQ"
+    fields_desc = [
+        XShortField("interval_min", 0x6),
+        XShortField("interval_max", 0x6),
+        XShortField("latency", 0x0),
+        XShortField("timeout", 0x0),
+        XByteField("preferred_periodicity", 0x0),
+        XShortField("reference_conn_evt_count", 0x0),
+        XShortField("offset0", 0x0),
+        XShortField("offset1", 0x0),
+        XShortField("offset2", 0x0),
+        XShortField("offset3", 0x0),
+        XShortField("offset4", 0x0),
+        XShortField("offset5", 0x0),
+    ]
+
+
+class LL_CONNECTION_PARAM_RSP(Packet):
+    name = "LL_CONNECTION_PARAM_RSP"
+    fields_desc = [
+        XShortField("interval_min", 0x6),
+        XShortField("interval_max", 0x6),
+        XShortField("latency", 0x0),
+        XShortField("timeout", 0x0),
+        XByteField("preferred_periodicity", 0x0),
+        XShortField("reference_conn_evt_count", 0x0),
+        XShortField("offset0", 0x0),
+        XShortField("offset1", 0x0),
+        XShortField("offset2", 0x0),
+        XShortField("offset3", 0x0),
+        XShortField("offset4", 0x0),
+        XShortField("offset5", 0x0),
+    ]
+
+
+class LL_REJECT_EXT_IND(Packet):
+    name = "LL_REJECT_EXT_IND"
+    fields_desc = [
+        XByteField("reject_opcode", 0x0),
+        XByteField("error_code", 0x0),
+    ]
+
+
+class LL_PING_REQ(Packet):
+    name = "LL_PING_REQ"
+
+
+class LL_PING_RSP(Packet):
+    name = "LL_PING_RSP"
+
+
+class LL_LENGTH_REQ(Packet):
+    name = ' LL_LENGTH_REQ'
+    fields_desc = [
+        XLEShortField("max_rx_bytes", 251),
+        XLEShortField("max_rx_time", 2120),
+        XLEShortField("max_tx_bytes", 251),
+        XLEShortField("max_tx_time", 2120),
+    ]
+
+
+class LL_LENGTH_RSP(Packet):
+    name = ' LL_LENGTH_RSP'
+    fields_desc = [
+        XLEShortField("max_rx_bytes", 251),
+        XLEShortField("max_rx_time", 2120),
+        XLEShortField("max_tx_bytes", 251),
+        XLEShortField("max_tx_time", 2120),
+    ]
+
+
+class LL_PHY_REQ(Packet):
+    name = "LL_PHY_REQ"
+    fields_desc = [
+        BTLEPhysField('tx_phys', 0),
+        BTLEPhysField('rx_phys', 0),
+    ]
+
+
+class LL_PHY_RSP(Packet):
+    name = "LL_PHY_RSP"
+    fields_desc = [
+        BTLEPhysField('tx_phys', 0),
+        BTLEPhysField('rx_phys', 0),
+    ]
+
+
+class LL_PHY_UPDATE_IND(Packet):
+    name = "LL_PHY_UPDATE_IND"
+    fields_desc = [
+        BTLEPhysField('tx_phy', 0),
+        BTLEPhysField('rx_phy', 0),
+        XShortField("instant", 0x0),
+    ]
+
+
+class LL_MIN_USED_CHANNELS_IND(Packet):
+    name = "LL_MIN_USED_CHANNELS_IND"
+    fields_desc = [
+        BTLEPhysField('phys', 0),
+        ByteField("min_used_channels", 2),
+    ]
+
+
+# Advertisement (37-39) channel PDUs
 bind_layers(BTLE, BTLE_ADV, access_addr=0x8E89BED6)
 bind_layers(BTLE, BTLE_DATA)
 bind_layers(BTLE_ADV, BTLE_ADV_IND, PDU_type=0)
@@ -327,9 +606,38 @@ class CtrlPDU(Packet):
 bind_layers(BTLE_ADV, BTLE_CONNECT_REQ, PDU_type=5)
 bind_layers(BTLE_ADV, BTLE_ADV_SCAN_IND, PDU_type=6)
 
-bind_layers(BTLE_DATA, L2CAP_Hdr, LLID=2)  # BTLE_DATA / L2CAP_Hdr / ATT_Hdr
+# Data channel (0-36) PDUs
 # LLID=1 -> Continue
-bind_layers(BTLE_DATA, CtrlPDU, LLID=3)
+bind_layers(BTLE_DATA, L2CAP_Hdr, LLID=2)
+bind_layers(BTLE_DATA, BTLE_CTRL, LLID=3)
+bind_layers(BTLE_DATA, BTLE_EMPTY_PDU, {'len': 0, 'LLID': 1})
+bind_layers(BTLE_CTRL, LL_CONNECTION_UPDATE_IND, opcode=0x00)
+bind_layers(BTLE_CTRL, LL_CHANNEL_MAP_IND, opcode=0x01)
+bind_layers(BTLE_CTRL, LL_TERMINATE_IND, opcode=0x02)
+bind_layers(BTLE_CTRL, LL_ENC_REQ, opcode=0x03)
+bind_layers(BTLE_CTRL, LL_ENC_RSP, opcode=0x04)
+bind_layers(BTLE_CTRL, LL_START_ENC_REQ, opcode=0x05)
+bind_layers(BTLE_CTRL, LL_START_ENC_RSP, opcode=0x06)
+bind_layers(BTLE_CTRL, LL_UNKNOWN_RSP, opcode=0x07)
+bind_layers(BTLE_CTRL, LL_FEATURE_REQ, opcode=0x08)
+bind_layers(BTLE_CTRL, LL_FEATURE_RSP, opcode=0x09)
+bind_layers(BTLE_CTRL, LL_PAUSE_ENC_REQ, opcode=0x0A)
+bind_layers(BTLE_CTRL, LL_PAUSE_ENC_RSP, opcode=0x0B)
+bind_layers(BTLE_CTRL, LL_VERSION_IND, opcode=0x0C)
+bind_layers(BTLE_CTRL, LL_REJECT_IND, opcode=0x0D)
+bind_layers(BTLE_CTRL, LL_SLAVE_FEATURE_REQ, opcode=0x0E)
+bind_layers(BTLE_CTRL, LL_CONNECTION_PARAM_REQ, opcode=0x0F)
+bind_layers(BTLE_CTRL, LL_CONNECTION_PARAM_RSP, opcode=0x10)
+bind_layers(BTLE_CTRL, LL_REJECT_EXT_IND, opcode=0x11)
+bind_layers(BTLE_CTRL, LL_PING_REQ, opcode=0x12)
+bind_layers(BTLE_CTRL, LL_PING_RSP, opcode=0x13)
+bind_layers(BTLE_CTRL, LL_LENGTH_REQ, opcode=0x14)
+bind_layers(BTLE_CTRL, LL_LENGTH_RSP, opcode=0x15)
+bind_layers(BTLE_CTRL, LL_PHY_REQ, opcode=0x16)
+bind_layers(BTLE_CTRL, LL_PHY_RSP, opcode=0x17)
+bind_layers(BTLE_CTRL, LL_PHY_UPDATE_IND, opcode=0x18)
+bind_layers(BTLE_CTRL, LL_MIN_USED_CHANNELS_IND, opcode=0x19)
+
 
 conf.l2types.register(DLT_BLUETOOTH_LE_LL, BTLE)
 conf.l2types.register(DLT_BLUETOOTH_LE_LL_WITH_PHDR, BTLE_RF)


Test patch:
diff --git a/test/scapy/layers/bluetooth4LE.uts b/test/scapy/layers/bluetooth4LE.uts
--- a/test/scapy/layers/bluetooth4LE.uts
+++ b/test/scapy/layers/bluetooth4LE.uts
@@ -30,11 +30,238 @@ test1 = BTLE() / BTLE_ADV() / BTLE_ADV_IND() / EIR_Hdr() / EIR_ShortenedLocalNam
 test1e = BTLE(raw(test1))
 assert test1e[EIR_ShortenedLocalName].local_name == b"wussa"
 
-= BTLE_DATA + CtrlPDU
+= LL_CONNECTION_UPDATE_IND
 
-test2 = BTLE(access_addr=1) / BTLE_DATA() / CtrlPDU(version=7)
-test2e = BTLE(raw(test2))
-assert test2e[CtrlPDU].version == 7
+test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \
+        LL_CONNECTION_UPDATE_IND(win_size=2, win_offset=5, interval=0x400, timeout=500, instant=0xFEFE)
+test = BTLE(raw(test))
+assert test[LL_CONNECTION_UPDATE_IND].win_size == 2
+assert test[LL_CONNECTION_UPDATE_IND].win_offset == 5
+assert test[LL_CONNECTION_UPDATE_IND].interval == 0x400
+assert test[LL_CONNECTION_UPDATE_IND].timeout == 500
+assert test[LL_CONNECTION_UPDATE_IND].instant == 0xFEFE
+
+= LL_CHANNEL_MAP_IND
+
+test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \
+        LL_CHANNEL_MAP_IND(chM=0x1A1B1C1D1E, instant=0xFEFE)
+test = BTLE(raw(test))
+assert test[LL_CHANNEL_MAP_IND].chM == 0x1A1B1C1D1E
+assert test[LL_CHANNEL_MAP_IND].instant == 0xFEFE
+
+= LL_TERMINATE_IND
+
+test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \
+        LL_TERMINATE_IND(code=0x16)
+test = BTLE(raw(test))
+assert test[LL_TERMINATE_IND].code == 0x16
+
+= LL_ENC_REQ
+
+test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \
+        LL_ENC_REQ(rand=0x1112131415161718, ediv=0x4321, 
+                   skdm=0x1817161514131211, ivm=0x87654321)
+test = BTLE(raw(test))
+assert test[LL_ENC_REQ].rand == 0x1112131415161718
+assert test[LL_ENC_REQ].ediv == 0x4321
+assert test[LL_ENC_REQ].skdm == 0x1817161514131211
+assert test[LL_ENC_REQ].ivm == 0x87654321
+
+= LL_ENC_RSP
+
+test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \
+        LL_ENC_RSP(skds=0x1817161514131211, ivs=0x87654321)
+test = BTLE(raw(test))
+assert test[LL_ENC_RSP].skds == 0x1817161514131211
+assert test[LL_ENC_RSP].ivs == 0x87654321
+
+= LL_START_ENC_REQ
+
+test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / LL_START_ENC_REQ()
+test = BTLE(raw(test))
+assert test[BTLE_CTRL].opcode == 5
+
+= LL_START_ENC_RSP
+
+test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / LL_START_ENC_RSP()
+test = BTLE(raw(test))
+assert test[BTLE_CTRL].opcode == 6
+
+= LL_UNKNOWN_RSP
+
+test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / LL_UNKNOWN_RSP(code=4)
+test = BTLE(raw(test))
+assert test[LL_UNKNOWN_RSP].code == 4
+
+= LL_FEATURE_REQ
+
+test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / LL_FEATURE_REQ(feature_set=0x1234)
+test = BTLE(raw(test))
+assert test[LL_FEATURE_REQ].feature_set == \
+    "ext_reject_ind+le_ping+le_data_len_ext+tx_mod_idx+le_ext_adv"
+
+= LL_FEATURE_RSP
+
+test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / LL_FEATURE_RSP(feature_set=0x4321)
+test = BTLE(raw(test))
+assert test[LL_FEATURE_RSP].feature_set == \
+    "le_encryption+le_data_len_ext+le_2m_phy+tx_mod_idx+ch_sel_alg"
+
+= LL_PAUSE_ENC_REQ
+
+test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / LL_PAUSE_ENC_REQ()
+test = BTLE(raw(test))
+assert test[BTLE_CTRL].opcode == 10
+
+= LL_PAUSE_ENC_RSP
+
+test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / LL_PAUSE_ENC_RSP()
+test = BTLE(raw(test))
+assert test[BTLE_CTRL].opcode == 11
+
+= LL_VERSION_IND
+
+test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \
+    LL_VERSION_IND(version=7, company=0x59, subversion=1)
+test = BTLE(raw(test))
+assert test[LL_VERSION_IND].version == 7
+assert test[LL_VERSION_IND].company == 0x59
+assert test[LL_VERSION_IND].subversion == 1
+
+= LL_REJECT_IND
+
+test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / LL_REJECT_IND(code=4)
+test = BTLE(raw(test))
+assert test[LL_REJECT_IND].code == 4
+
+= LL_SLAVE_FEATURE_REQ
+
+test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / LL_SLAVE_FEATURE_REQ(feature_set=0x1234)
+test = BTLE(raw(test))
+assert test[LL_SLAVE_FEATURE_REQ].feature_set == \
+    "ext_reject_ind+le_ping+le_data_len_ext+tx_mod_idx+le_ext_adv"
+
+= LL_CONNECTION_PARAM_REQ
+
+test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \
+    LL_CONNECTION_PARAM_REQ(interval_min=10, interval_max=12, latency=1, timeout=2,
+                            preferred_periodicity=3, reference_conn_evt_count=4,
+                            offset0=5, offset1=6, offset2=7, offset3=8, offset4=9, offset5=10)
+test = BTLE(raw(test))
+assert test[LL_CONNECTION_PARAM_REQ].interval_min == 10
+assert test[LL_CONNECTION_PARAM_REQ].interval_max == 12
+assert test[LL_CONNECTION_PARAM_REQ].latency == 1
+assert test[LL_CONNECTION_PARAM_REQ].timeout == 2
+assert test[LL_CONNECTION_PARAM_REQ].preferred_periodicity == 3
+assert test[LL_CONNECTION_PARAM_REQ].reference_conn_evt_count == 4
+assert test[LL_CONNECTION_PARAM_REQ].offset0 == 5
+assert test[LL_CONNECTION_PARAM_REQ].offset1 == 6
+assert test[LL_CONNECTION_PARAM_REQ].offset2 == 7
+assert test[LL_CONNECTION_PARAM_REQ].offset3 == 8
+assert test[LL_CONNECTION_PARAM_REQ].offset4 == 9
+assert test[LL_CONNECTION_PARAM_REQ].offset5 == 10
+
+= LL_CONNECTION_PARAM_RSP
+
+test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \
+    LL_CONNECTION_PARAM_RSP(interval_min=10, interval_max=12, latency=1, timeout=2,
+                            preferred_periodicity=3, reference_conn_evt_count=4,
+                            offset0=5, offset1=6, offset2=7, offset3=8, offset4=9, offset5=10)
+test = BTLE(raw(test))
+assert test[LL_CONNECTION_PARAM_RSP].interval_min == 10
+assert test[LL_CONNECTION_PARAM_RSP].interval_max == 12
+assert test[LL_CONNECTION_PARAM_RSP].latency == 1
+assert test[LL_CONNECTION_PARAM_RSP].timeout == 2
+assert test[LL_CONNECTION_PARAM_RSP].preferred_periodicity == 3
+assert test[LL_CONNECTION_PARAM_RSP].reference_conn_evt_count == 4
+assert test[LL_CONNECTION_PARAM_RSP].offset0 == 5
+assert test[LL_CONNECTION_PARAM_RSP].offset1 == 6
+assert test[LL_CONNECTION_PARAM_RSP].offset2 == 7
+assert test[LL_CONNECTION_PARAM_RSP].offset3 == 8
+assert test[LL_CONNECTION_PARAM_RSP].offset4 == 9
+assert test[LL_CONNECTION_PARAM_RSP].offset5 == 10
+
+= LL_REJECT_EXT_IND
+
+test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \
+    LL_REJECT_EXT_IND(reject_opcode=2, error_code=4)
+test = BTLE(raw(test))
+assert test[LL_REJECT_EXT_IND].reject_opcode == 2
+assert test[LL_REJECT_EXT_IND].error_code == 4
+
+= LL_PING_REQ
+
+test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / LL_PING_REQ()
+test = BTLE(raw(test))
+assert test[BTLE_CTRL].opcode == 18
+
+= LL_PING_RSP
+
+test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / LL_PING_RSP()
+test = BTLE(raw(test))
+assert test[BTLE_CTRL].opcode == 19
+
+= LL_LENGTH_REQ
+
+test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \
+    LL_LENGTH_REQ(max_rx_bytes=28, max_rx_time=329, max_tx_bytes=29, max_tx_time=330)
+test = BTLE(raw(test))
+assert test[LL_LENGTH_REQ].max_rx_bytes == 28
+assert test[LL_LENGTH_REQ].max_rx_time == 329
+assert test[LL_LENGTH_REQ].max_tx_bytes == 29
+assert test[LL_LENGTH_REQ].max_tx_time == 330
+
+= LL_LENGTH_RSP
+
+test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \
+    LL_LENGTH_RSP(max_tx_bytes=28, max_tx_time=329, max_rx_bytes=29, max_rx_time=330)
+test = BTLE(raw(test))
+assert test[LL_LENGTH_RSP].max_tx_bytes == 28
+assert test[LL_LENGTH_RSP].max_tx_time == 329
+assert test[LL_LENGTH_RSP].max_rx_bytes == 29
+assert test[LL_LENGTH_RSP].max_rx_time == 330
+
+= LL_PHY_REQ
+
+test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \
+    LL_PHY_REQ(tx_phys="phy_1m+phy_2m", rx_phys="phy_coded")
+test = BTLE(raw(test))
+assert test[LL_PHY_REQ].tx_phys == "phy_1m+phy_2m"
+assert test[LL_PHY_REQ].rx_phys == "phy_coded"
+
+= LL_PHY_RSP
+
+test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \
+    LL_PHY_RSP(tx_phys="phy_1m+phy_2m", rx_phys="phy_coded")
+test = BTLE(raw(test))
+assert test[LL_PHY_RSP].tx_phys == "phy_1m+phy_2m"
+assert test[LL_PHY_RSP].rx_phys == "phy_coded"
+
+= LL_PHY_UPDATE_IND
+
+test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \
+    LL_PHY_UPDATE_IND(tx_phy="phy_2m", rx_phy="phy_coded", instant=1234)
+test = BTLE(raw(test))
+assert test[LL_PHY_UPDATE_IND].tx_phy == "phy_2m"
+assert test[LL_PHY_UPDATE_IND].rx_phy == "phy_coded"
+assert test[LL_PHY_UPDATE_IND].instant == 1234
+
+# LL_MIN_USED_CHANNELS_IND
+
+test = BTLE(access_addr=1) / BTLE_DATA() / BTLE_CTRL() / \
+    LL_MIN_USED_CHANNELS_IND(phys="phy_1m+phy_2m", min_used_channels=3)
+test = BTLE(raw(test))
+assert test[LL_MIN_USED_CHANNELS_IND].phys == "phy_1m+phy_2m"
+assert test[LL_MIN_USED_CHANNELS_IND].min_used_channels == 3
+
+= BTLE_DATA + BTLE_EMPTY_PDU
+
+test = BTLE(access_addr=1)/BTLE_DATA(LLID=1, len=0)/BTLE_EMPTY_PDU()
+a = BTLE(raw(test))
+print(dir(a))
+print(a.layers)
+print(a[BTLE_DATA].len, a[BTLE_DATA].LLID)
+assert a[BTLE_DATA].len == 0
 
 = BTLE_DATA + ATT_PrepareWriteReq