Snap for 11025555 from db1c42dd56e7c581a306da3eb3bd469d11108226 to 24Q1-release

Change-Id: I151b4008541d222300cc53399e05d31c11e5e148
diff --git a/.github/workflows/python-avatar.yml b/.github/workflows/python-avatar.yml
new file mode 100644
index 0000000..eb1b270
--- /dev/null
+++ b/.github/workflows/python-avatar.yml
@@ -0,0 +1,43 @@
+name: Python Avatar
+
+on:
+  push:
+    branches: [ main ]
+  pull_request:
+    branches: [ main ]
+
+permissions:
+  contents: read
+
+jobs:
+  test:
+    name: Avatar [${{ matrix.shard }}]
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        shard: [
+           1/24,  2/24,  3/24,  4/24,
+           5/24,  6/24,  7/24,  8/24,
+           9/24, 10/24, 11/24, 12/24,
+          13/24, 14/24, 15/24, 16/24,
+          17/24, 18/24, 19/24, 20/24,
+          21/24, 22/24, 23/24, 24/24,
+        ]
+    steps:
+      - uses: actions/checkout@v3
+      - name: Set Up Python 3.11
+        uses: actions/setup-python@v4
+        with:
+          python-version: 3.11
+      - name: Install
+        run: |
+          python -m pip install --upgrade pip
+          python -m pip install .[avatar]
+      - name: Rootcanal
+        run: nohup python -m rootcanal > rootcanal.log &
+      - name: Test
+        run: |
+          avatar --list | grep -Ev '^=' > test-names.txt
+          timeout 5m avatar --test-beds bumble.bumbles --tests $(split test-names.txt -n l/${{ matrix.shard }})
+      - name: Rootcanal Logs
+        run: cat rootcanal.log
diff --git a/apps/bench.py b/apps/bench.py
index 4708403..8b37883 100644
--- a/apps/bench.py
+++ b/apps/bench.py
@@ -24,6 +24,7 @@
 
 import click
 
+from bumble import l2cap
 from bumble.core import (
     BT_BR_EDR_TRANSPORT,
     BT_LE_TRANSPORT,
@@ -85,6 +86,7 @@
 
 DEFAULT_RFCOMM_CHANNEL = 8
 
+
 # -----------------------------------------------------------------------------
 # Utils
 # -----------------------------------------------------------------------------
@@ -197,6 +199,7 @@
 
 PACKET_FLAG_LAST = 1
 
+
 # -----------------------------------------------------------------------------
 # Sender
 # -----------------------------------------------------------------------------
@@ -659,17 +662,19 @@
         self.mps = mps
         self.ready = asyncio.Event()
 
-    async def on_connection(self, connection):
+    async def on_connection(self, connection: Connection) -> None:
         connection.on('disconnection', self.on_disconnection)
 
         # Connect a new L2CAP channel
         print(color(f'>>> Opening L2CAP channel on PSM = {self.psm}', 'yellow'))
         try:
-            l2cap_channel = await connection.open_l2cap_channel(
-                psm=self.psm,
-                max_credits=self.max_credits,
-                mtu=self.mtu,
-                mps=self.mps,
+            l2cap_channel = await connection.create_l2cap_channel(
+                spec=l2cap.LeCreditBasedChannelSpec(
+                    psm=self.psm,
+                    max_credits=self.max_credits,
+                    mtu=self.mtu,
+                    mps=self.mps,
+                )
             )
             print(color('*** L2CAP channel:', 'cyan'), l2cap_channel)
         except Exception as error:
@@ -695,7 +700,7 @@
 class L2capServer(StreamedPacketIO):
     def __init__(
         self,
-        device,
+        device: Device,
         psm=DEFAULT_L2CAP_PSM,
         max_credits=DEFAULT_L2CAP_MAX_CREDITS,
         mtu=DEFAULT_L2CAP_MTU,
@@ -706,12 +711,11 @@
         self.ready = asyncio.Event()
 
         # Listen for incoming L2CAP CoC connections
-        device.register_l2cap_channel_server(
-            psm=psm,
-            server=self.on_l2cap_channel,
-            max_credits=max_credits,
-            mtu=mtu,
-            mps=mps,
+        device.create_l2cap_server(
+            spec=l2cap.LeCreditBasedChannelSpec(
+                psm=psm, mtu=mtu, mps=mps, max_credits=max_credits
+            ),
+            handler=self.on_l2cap_channel,
         )
         print(color(f'### Listening for CoC connection on PSM {psm}', 'yellow'))
 
diff --git a/apps/gg_bridge.py b/apps/gg_bridge.py
index 88ebdc5..12d16e4 100644
--- a/apps/gg_bridge.py
+++ b/apps/gg_bridge.py
@@ -21,6 +21,7 @@
 import logging
 import click
 
+from bumble import l2cap
 from bumble.colors import color
 from bumble.device import Device, Peer
 from bumble.core import AdvertisingData
@@ -204,7 +205,7 @@
 
 # -----------------------------------------------------------------------------
 class GattlinkNodeBridge(GattlinkL2capEndpoint, Device.Listener):
-    def __init__(self, device):
+    def __init__(self, device: Device):
         super().__init__()
         self.device = device
         self.peer = None
@@ -218,7 +219,12 @@
 
         # Listen for incoming L2CAP CoC connections
         psm = 0xFB
-        device.register_l2cap_channel_server(0xFB, self.on_coc)
+        device.create_l2cap_server(
+            spec=l2cap.LeCreditBasedChannelSpec(
+                psm=0xFB,
+            ),
+            handler=self.on_coc,
+        )
         print(f'### Listening for CoC connection on PSM {psm}')
 
         # Setup the Gattlink service
diff --git a/apps/l2cap_bridge.py b/apps/l2cap_bridge.py
index 83379a0..14bd759 100644
--- a/apps/l2cap_bridge.py
+++ b/apps/l2cap_bridge.py
@@ -20,6 +20,7 @@
 import os
 import click
 
+from bumble import l2cap
 from bumble.colors import color
 from bumble.transport import open_transport_or_link
 from bumble.device import Device
@@ -47,14 +48,13 @@
         self.tcp_host = tcp_host
         self.tcp_port = tcp_port
 
-    async def start(self, device):
+    async def start(self, device: Device) -> None:
         # Listen for incoming L2CAP CoC connections
-        device.register_l2cap_channel_server(
-            psm=self.psm,
-            server=self.on_coc,
-            max_credits=self.max_credits,
-            mtu=self.mtu,
-            mps=self.mps,
+        device.create_l2cap_server(
+            spec=l2cap.LeCreditBasedChannelSpec(
+                psm=self.psm, mtu=self.mtu, mps=self.mps, max_credits=self.max_credits
+            ),
+            handler=self.on_coc,
         )
         print(color(f'### Listening for CoC connection on PSM {self.psm}', 'yellow'))
 
@@ -195,11 +195,13 @@
             # Connect a new L2CAP channel
             print(color(f'>>> Opening L2CAP channel on PSM = {self.psm}', 'yellow'))
             try:
-                l2cap_channel = await connection.open_l2cap_channel(
-                    psm=self.psm,
-                    max_credits=self.max_credits,
-                    mtu=self.mtu,
-                    mps=self.mps,
+                l2cap_channel = await connection.create_l2cap_channel(
+                    spec=l2cap.LeCreditBasedChannelSpec(
+                        psm=self.psm,
+                        max_credits=self.max_credits,
+                        mtu=self.mtu,
+                        mps=self.mps,
+                    )
                 )
                 print(color('*** L2CAP channel:', 'cyan'), l2cap_channel)
             except Exception as error:
diff --git a/apps/pair.py b/apps/pair.py
index 2cc8188..39ee4fe 100644
--- a/apps/pair.py
+++ b/apps/pair.py
@@ -306,6 +306,7 @@
         # Expose a GATT characteristic that can be used to trigger pairing by
         # responding with an authentication error when read
         if mode == 'le':
+            device.le_enabled = True
             device.add_service(
                 Service(
                     '50DB505C-8AC4-4738-8448-3B1D9CC09CC5',
@@ -326,7 +327,6 @@
         # Select LE or Classic
         if mode == 'classic':
             device.classic_enabled = True
-            device.le_enabled = False
             device.classic_smp_enabled = ctkd
 
         # Get things going
diff --git a/apps/speaker/speaker.py b/apps/speaker/speaker.py
index e451c04..84e05a0 100644
--- a/apps/speaker/speaker.py
+++ b/apps/speaker/speaker.py
@@ -641,7 +641,7 @@
             self.device.on('connection', self.on_bluetooth_connection)
 
             # Create a listener to wait for AVDTP connections
-            self.listener = Listener(Listener.create_registrar(self.device))
+            self.listener = Listener.for_device(self.device)
             self.listener.on('connection', self.on_avdtp_connection)
 
             print(f'Speaker ready to play, codec={color(self.codec, "cyan")}')
diff --git a/bumble/avdtp.py b/bumble/avdtp.py
index 3988f30..9a332f4 100644
--- a/bumble/avdtp.py
+++ b/bumble/avdtp.py
@@ -20,8 +20,24 @@
 import struct
 import time
 import logging
+import enum
+import warnings
 from pyee import EventEmitter
-from typing import Dict, Type
+from typing import (
+    Any,
+    Awaitable,
+    Dict,
+    Type,
+    Tuple,
+    Optional,
+    Callable,
+    List,
+    AsyncGenerator,
+    Iterable,
+    Union,
+    SupportsBytes,
+    cast,
+)
 
 from .core import (
     BT_ADVANCED_AUDIO_DISTRIBUTION_SERVICE,
@@ -38,7 +54,7 @@
     SbcMediaCodecInformation,
     VendorSpecificMediaCodecInformation,
 )
-from . import sdp
+from . import sdp, device, l2cap
 from .colors import color
 
 # -----------------------------------------------------------------------------
@@ -206,7 +222,9 @@
 
 
 # -----------------------------------------------------------------------------
-async def find_avdtp_service_with_sdp_client(sdp_client):
+async def find_avdtp_service_with_sdp_client(
+    sdp_client: sdp.Client,
+) -> Optional[Tuple[int, int]]:
     '''
     Find an AVDTP service, using a connected SDP client, and return its version,
     or None if none is found
@@ -227,10 +245,13 @@
                     avdtp_version_major = profile_descriptor.value[1].value >> 8
                     avdtp_version_minor = profile_descriptor.value[1].value & 0xFF
                     return (avdtp_version_major, avdtp_version_minor)
+    return None
 
 
 # -----------------------------------------------------------------------------
-async def find_avdtp_service_with_connection(device, connection):
+async def find_avdtp_service_with_connection(
+    device: device.Device, connection: device.Connection
+) -> Optional[Tuple[int, int]]:
     '''
     Find an AVDTP service, for a connection, and return its version,
     or None if none is found
@@ -246,17 +267,17 @@
 
 # -----------------------------------------------------------------------------
 class RealtimeClock:
-    def now(self):
+    def now(self) -> float:
         return time.time()
 
-    async def sleep(self, duration):
+    async def sleep(self, duration: float) -> None:
         await asyncio.sleep(duration)
 
 
 # -----------------------------------------------------------------------------
 class MediaPacket:
     @staticmethod
-    def from_bytes(data):
+    def from_bytes(data: bytes) -> MediaPacket:
         version = (data[0] >> 6) & 0x03
         padding = (data[0] >> 5) & 0x01
         extension = (data[0] >> 4) & 0x01
@@ -286,17 +307,17 @@
 
     def __init__(
         self,
-        version,
-        padding,
-        extension,
-        marker,
-        sequence_number,
-        timestamp,
-        ssrc,
-        csrc_list,
-        payload_type,
-        payload,
-    ):
+        version: int,
+        padding: int,
+        extension: int,
+        marker: int,
+        sequence_number: int,
+        timestamp: int,
+        ssrc: int,
+        csrc_list: List[int],
+        payload_type: int,
+        payload: bytes,
+    ) -> None:
         self.version = version
         self.padding = padding
         self.extension = extension
@@ -308,7 +329,7 @@
         self.payload_type = payload_type
         self.payload = payload
 
-    def __bytes__(self):
+    def __bytes__(self) -> bytes:
         header = bytes(
             [
                 self.version << 6
@@ -322,7 +343,7 @@
             header += struct.pack('>I', csrc)
         return header + self.payload
 
-    def __str__(self):
+    def __str__(self) -> str:
         return (
             f'RTP(v={self.version},'
             f'p={self.padding},'
@@ -339,12 +360,16 @@
 
 # -----------------------------------------------------------------------------
 class MediaPacketPump:
-    def __init__(self, packets, clock=RealtimeClock()):
+    pump_task: Optional[asyncio.Task]
+
+    def __init__(
+        self, packets: AsyncGenerator, clock: RealtimeClock = RealtimeClock()
+    ) -> None:
         self.packets = packets
         self.clock = clock
         self.pump_task = None
 
-    async def start(self, rtp_channel):
+    async def start(self, rtp_channel: l2cap.ClassicChannel) -> None:
         async def pump_packets():
             start_time = 0
             start_timestamp = 0
@@ -376,7 +401,7 @@
         # Pump packets
         self.pump_task = asyncio.create_task(pump_packets())
 
-    async def stop(self):
+    async def stop(self) -> None:
         # Stop the pump
         if self.pump_task:
             self.pump_task.cancel()
@@ -385,32 +410,37 @@
 
 
 # -----------------------------------------------------------------------------
-class MessageAssembler:  # pylint: disable=attribute-defined-outside-init
-    def __init__(self, callback):
+class MessageAssembler:
+    message: Optional[bytes]
+
+    def __init__(self, callback: Callable[[int, Message], Any]) -> None:
         self.callback = callback
         self.reset()
 
-    def reset(self):
+    def reset(self) -> None:
         self.transaction_label = 0
         self.message = None
-        self.message_type = 0
+        self.message_type = Message.MessageType.COMMAND
         self.signal_identifier = 0
         self.number_of_signal_packets = 0
         self.packet_count = 0
 
-    def on_pdu(self, pdu):
+    def on_pdu(self, pdu: bytes) -> None:
         self.packet_count += 1
 
         transaction_label = pdu[0] >> 4
-        packet_type = (pdu[0] >> 2) & 3
-        message_type = pdu[0] & 3
+        packet_type = Protocol.PacketType((pdu[0] >> 2) & 3)
+        message_type = Message.MessageType(pdu[0] & 3)
 
         logger.debug(
             f'transaction_label={transaction_label}, '
-            f'packet_type={Protocol.packet_type_name(packet_type)}, '
-            f'message_type={Message.message_type_name(message_type)}'
+            f'packet_type={packet_type.name}, '
+            f'message_type={message_type.name}'
         )
-        if packet_type in (Protocol.SINGLE_PACKET, Protocol.START_PACKET):
+        if packet_type in (
+            Protocol.PacketType.SINGLE_PACKET,
+            Protocol.PacketType.START_PACKET,
+        ):
             if self.message is not None:
                 # The previous message has not been terminated
                 logger.warning(
@@ -423,13 +453,16 @@
             self.signal_identifier = pdu[1] & 0x3F
             self.message_type = message_type
 
-            if packet_type == Protocol.SINGLE_PACKET:
+            if packet_type == Protocol.PacketType.SINGLE_PACKET:
                 self.message = pdu[2:]
                 self.on_message_complete()
             else:
                 self.number_of_signal_packets = pdu[2]
                 self.message = pdu[3:]
-        elif packet_type in (Protocol.CONTINUE_PACKET, Protocol.END_PACKET):
+        elif packet_type in (
+            Protocol.PacketType.CONTINUE_PACKET,
+            Protocol.PacketType.END_PACKET,
+        ):
             if self.packet_count == 0:
                 logger.warning('unexpected continuation')
                 return
@@ -448,9 +481,9 @@
                 )
                 return
 
-            self.message += pdu[1:]
+            self.message = (self.message or b'') + pdu[1:]
 
-            if packet_type == Protocol.END_PACKET:
+            if packet_type == Protocol.PacketType.END_PACKET:
                 if self.packet_count != self.number_of_signal_packets:
                     logger.warning(
                         'incomplete fragmented message: '
@@ -471,24 +504,25 @@
                     self.reset()
                     return
 
-    def on_message_complete(self):
+    def on_message_complete(self) -> None:
         message = Message.create(
-            self.signal_identifier, self.message_type, self.message
+            self.signal_identifier, self.message_type, self.message or b''
         )
-
         try:
             self.callback(self.transaction_label, message)
         except Exception as error:
             logger.warning(color(f'!!! exception in callback: {error}'))
-
         self.reset()
 
 
 # -----------------------------------------------------------------------------
 class ServiceCapabilities:
     @staticmethod
-    def create(service_category, service_capabilities_bytes):
+    def create(
+        service_category: int, service_capabilities_bytes: bytes
+    ) -> ServiceCapabilities:
         # Select the appropriate subclass
+        cls: Type[ServiceCapabilities]
         if service_category == AVDTP_MEDIA_CODEC_SERVICE_CATEGORY:
             cls = MediaCodecCapabilities
         else:
@@ -503,7 +537,7 @@
         return instance
 
     @staticmethod
-    def parse_capabilities(payload):
+    def parse_capabilities(payload: bytes) -> List[ServiceCapabilities]:
         capabilities = []
         while payload:
             service_category = payload[0]
@@ -518,7 +552,7 @@
         return capabilities
 
     @staticmethod
-    def serialize_capabilities(capabilities):
+    def serialize_capabilities(capabilities: Iterable[ServiceCapabilities]) -> bytes:
         serialized = b''
         for item in capabilities:
             serialized += (
@@ -527,21 +561,23 @@
             )
         return serialized
 
-    def init_from_bytes(self):
+    def init_from_bytes(self) -> None:
         pass
 
-    def __init__(self, service_category, service_capabilities_bytes=b''):
+    def __init__(
+        self, service_category: int, service_capabilities_bytes: bytes = b''
+    ) -> None:
         self.service_category = service_category
         self.service_capabilities_bytes = service_capabilities_bytes
 
-    def to_string(self, details=[]):  # pylint: disable=dangerous-default-value
+    def to_string(self, details: List[str] = []) -> str:
         attributes = ','.join(
             [name_or_number(AVDTP_SERVICE_CATEGORY_NAMES, self.service_category)]
             + details
         )
         return f'ServiceCapabilities({attributes})'
 
-    def __str__(self):
+    def __str__(self) -> str:
         if self.service_capabilities_bytes:
             details = [self.service_capabilities_bytes.hex()]
         else:
@@ -551,7 +587,11 @@
 
 # -----------------------------------------------------------------------------
 class MediaCodecCapabilities(ServiceCapabilities):
-    def init_from_bytes(self):
+    media_codec_information: Union[bytes, SupportsBytes]
+    media_type: int
+    media_codec_type: int
+
+    def init_from_bytes(self) -> None:
         self.media_type = self.service_capabilities_bytes[0]
         self.media_codec_type = self.service_capabilities_bytes[1]
         self.media_codec_information = self.service_capabilities_bytes[2:]
@@ -571,7 +611,12 @@
                 )
             )
 
-    def __init__(self, media_type, media_codec_type, media_codec_information):
+    def __init__(
+        self,
+        media_type: int,
+        media_codec_type: int,
+        media_codec_information: Union[bytes, SupportsBytes],
+    ) -> None:
         super().__init__(
             AVDTP_MEDIA_CODEC_SERVICE_CATEGORY,
             bytes([media_type, media_codec_type]) + bytes(media_codec_information),
@@ -580,7 +625,7 @@
         self.media_codec_type = media_codec_type
         self.media_codec_information = media_codec_information
 
-    def __str__(self):
+    def __str__(self) -> str:
         codec_info = (
             self.media_codec_information.hex()
             if isinstance(self.media_codec_information, bytes)
@@ -598,17 +643,17 @@
 # -----------------------------------------------------------------------------
 class EndPointInfo:
     @staticmethod
-    def from_bytes(payload):
+    def from_bytes(payload: bytes) -> EndPointInfo:
         return EndPointInfo(
             payload[0] >> 2, payload[0] >> 1 & 1, payload[1] >> 4, payload[1] >> 3 & 1
         )
 
-    def __bytes__(self):
+    def __bytes__(self) -> bytes:
         return bytes(
             [self.seid << 2 | self.in_use << 1, self.media_type << 4 | self.tsep << 3]
         )
 
-    def __init__(self, seid, in_use, media_type, tsep):
+    def __init__(self, seid: int, in_use: int, media_type: int, tsep: int) -> None:
         self.seid = seid
         self.in_use = in_use
         self.media_type = media_type
@@ -617,24 +662,16 @@
 
 # -----------------------------------------------------------------------------
 class Message:  # pylint:disable=attribute-defined-outside-init
-    COMMAND = 0
-    GENERAL_REJECT = 1
-    RESPONSE_ACCEPT = 2
-    RESPONSE_REJECT = 3
-
-    MESSAGE_TYPE_NAMES = {
-        COMMAND: 'COMMAND',
-        GENERAL_REJECT: 'GENERAL_REJECT',
-        RESPONSE_ACCEPT: 'RESPONSE_ACCEPT',
-        RESPONSE_REJECT: 'RESPONSE_REJECT',
-    }
+    class MessageType(enum.IntEnum):
+        COMMAND = 0
+        GENERAL_REJECT = 1
+        RESPONSE_ACCEPT = 2
+        RESPONSE_REJECT = 3
 
     # Subclasses, by signal identifier and message type
     subclasses: Dict[int, Dict[int, Type[Message]]] = {}
-
-    @staticmethod
-    def message_type_name(message_type):
-        return name_or_number(Message.MESSAGE_TYPE_NAMES, message_type)
+    message_type: MessageType
+    signal_identifier: int
 
     @staticmethod
     def subclass(subclass):
@@ -643,23 +680,23 @@
         if name == 'General_Reject':
             subclass.signal_identifier = 0
             signal_identifier_str = None
-            message_type = Message.COMMAND
+            message_type = Message.MessageType.COMMAND
         elif name.endswith('_Command'):
             signal_identifier_str = name[:-8]
-            message_type = Message.COMMAND
+            message_type = Message.MessageType.COMMAND
         elif name.endswith('_Response'):
             signal_identifier_str = name[:-9]
-            message_type = Message.RESPONSE_ACCEPT
+            message_type = Message.MessageType.RESPONSE_ACCEPT
         elif name.endswith('_Reject'):
             signal_identifier_str = name[:-7]
-            message_type = Message.RESPONSE_REJECT
+            message_type = Message.MessageType.RESPONSE_REJECT
         else:
             raise ValueError('invalid class name')
 
         subclass.message_type = message_type
 
         if signal_identifier_str is not None:
-            for (name, signal_identifier) in AVDTP_SIGNAL_IDENTIFIERS.items():
+            for name, signal_identifier in AVDTP_SIGNAL_IDENTIFIERS.items():
                 if name.lower().endswith(signal_identifier_str.lower()):
                     subclass.signal_identifier = signal_identifier
                     break
@@ -674,7 +711,9 @@
     # Factory method to create a subclass based on the signal identifier and message
     # type
     @staticmethod
-    def create(signal_identifier, message_type, payload):
+    def create(
+        signal_identifier: int, message_type: MessageType, payload: bytes
+    ) -> Message:
         # Look for a registered subclass
         subclasses = Message.subclasses.get(signal_identifier)
         if subclasses:
@@ -686,7 +725,7 @@
                 return instance
 
         # Instantiate the appropriate class based on the message type
-        if message_type == Message.RESPONSE_REJECT:
+        if message_type == Message.MessageType.RESPONSE_REJECT:
             # Assume a simple reject message
             instance = Simple_Reject(payload)
             instance.init_from_payload()
@@ -696,16 +735,16 @@
         instance.message_type = message_type
         return instance
 
-    def init_from_payload(self):
+    def init_from_payload(self) -> None:
         pass
 
-    def __init__(self, payload=b''):
+    def __init__(self, payload: bytes = b'') -> None:
         self.payload = payload
 
-    def to_string(self, details):
+    def to_string(self, details: Union[str, Iterable[str]]) -> str:
         base = color(
             f'{name_or_number(AVDTP_SIGNAL_NAMES, self.signal_identifier)}_'
-            f'{Message.message_type_name(self.message_type)}',
+            f'{self.message_type.name}',
             'yellow',
         )
 
@@ -721,7 +760,7 @@
 
         return base
 
-    def __str__(self):
+    def __str__(self) -> str:
         return self.to_string(self.payload.hex())
 
 
@@ -738,7 +777,7 @@
         super().__init__(payload=bytes([seid << 2]))
         self.acp_seid = seid
 
-    def __str__(self):
+    def __str__(self) -> str:
         return self.to_string([f'ACP SEID: {self.acp_seid}'])
 
 
@@ -755,7 +794,7 @@
         super().__init__(payload=bytes([error_code]))
         self.error_code = error_code
 
-    def __str__(self):
+    def __str__(self) -> str:
         details = [f'error_code: {name_or_number(AVDTP_ERROR_NAMES, self.error_code)}']
         return self.to_string(details)
 
@@ -775,6 +814,8 @@
     See Bluetooth AVDTP spec - 8.6.2 Stream End Point Discovery Response
     '''
 
+    endpoints: List[EndPointInfo]
+
     def init_from_payload(self):
         self.endpoints = []
         endpoint_count = len(self.payload) // 2
@@ -787,7 +828,7 @@
         super().__init__(payload=b''.join([bytes(endpoint) for endpoint in endpoints]))
         self.endpoints = endpoints
 
-    def __str__(self):
+    def __str__(self) -> str:
         details = []
         for endpoint in self.endpoints:
             details.extend(
@@ -826,7 +867,7 @@
         )
         self.capabilities = capabilities
 
-    def __str__(self):
+    def __str__(self) -> str:
         details = [str(capability) for capability in self.capabilities]
         return self.to_string(details)
 
@@ -875,7 +916,9 @@
         self.int_seid = self.payload[1] >> 2
         self.capabilities = ServiceCapabilities.parse_capabilities(self.payload[2:])
 
-    def __init__(self, acp_seid, int_seid, capabilities):
+    def __init__(
+        self, acp_seid: int, int_seid: int, capabilities: Iterable[ServiceCapabilities]
+    ) -> None:
         super().__init__(
             payload=bytes([acp_seid << 2, int_seid << 2])
             + ServiceCapabilities.serialize_capabilities(capabilities)
@@ -884,7 +927,7 @@
         self.int_seid = int_seid
         self.capabilities = capabilities
 
-    def __str__(self):
+    def __str__(self) -> str:
         details = [f'ACP SEID: {self.acp_seid}', f'INT SEID: {self.int_seid}'] + [
             str(capability) for capability in self.capabilities
         ]
@@ -915,7 +958,7 @@
         self.service_category = service_category
         self.error_code = error_code
 
-    def __str__(self):
+    def __str__(self) -> str:
         details = [
             (
                 'service_category: '
@@ -947,13 +990,13 @@
     def init_from_payload(self):
         self.capabilities = ServiceCapabilities.parse_capabilities(self.payload)
 
-    def __init__(self, capabilities):
+    def __init__(self, capabilities: Iterable[ServiceCapabilities]) -> None:
         super().__init__(
             payload=ServiceCapabilities.serialize_capabilities(capabilities)
         )
         self.capabilities = capabilities
 
-    def __str__(self):
+    def __str__(self) -> str:
         details = [str(capability) for capability in self.capabilities]
         return self.to_string(details)
 
@@ -978,7 +1021,7 @@
         self.acp_seid = self.payload[0] >> 2
         self.capabilities = ServiceCapabilities.parse_capabilities(self.payload[1:])
 
-    def __str__(self):
+    def __str__(self) -> str:
         details = [
             f'ACP SEID: {self.acp_seid}',
         ] + [str(capability) for capability in self.capabilities]
@@ -1035,11 +1078,11 @@
     def init_from_payload(self):
         self.acp_seids = [x >> 2 for x in self.payload]
 
-    def __init__(self, seids):
+    def __init__(self, seids: Iterable[int]) -> None:
         super().__init__(payload=bytes([seid << 2 for seid in seids]))
         self.acp_seids = seids
 
-    def __str__(self):
+    def __str__(self) -> str:
         return self.to_string([f'ACP SEIDs: {self.acp_seids}'])
 
 
@@ -1067,7 +1110,7 @@
         self.acp_seid = acp_seid
         self.error_code = error_code
 
-    def __str__(self):
+    def __str__(self) -> str:
         details = [
             f'acp_seid:   {self.acp_seid}',
             f'error_code: {name_or_number(AVDTP_ERROR_NAMES, self.error_code)}',
@@ -1186,7 +1229,7 @@
         self.acp_seid = self.payload[0] >> 2
         self.delay = (self.payload[1] << 8) | (self.payload[2])
 
-    def __str__(self):
+    def __str__(self) -> str:
         return self.to_string([f'ACP_SEID: {self.acp_seid}', f'delay:    {self.delay}'])
 
 
@@ -1208,32 +1251,36 @@
 
 # -----------------------------------------------------------------------------
 class Protocol(EventEmitter):
-    SINGLE_PACKET = 0
-    START_PACKET = 1
-    CONTINUE_PACKET = 2
-    END_PACKET = 3
+    local_endpoints: List[LocalStreamEndPoint]
+    remote_endpoints: Dict[int, DiscoveredStreamEndPoint]
+    streams: Dict[int, Stream]
+    transaction_results: List[Optional[asyncio.Future[Message]]]
+    channel_connector: Callable[[], Awaitable[l2cap.ClassicChannel]]
 
-    PACKET_TYPE_NAMES = {
-        SINGLE_PACKET: 'SINGLE_PACKET',
-        START_PACKET: 'START_PACKET',
-        CONTINUE_PACKET: 'CONTINUE_PACKET',
-        END_PACKET: 'END_PACKET',
-    }
+    class PacketType(enum.IntEnum):
+        SINGLE_PACKET = 0
+        START_PACKET = 1
+        CONTINUE_PACKET = 2
+        END_PACKET = 3
 
     @staticmethod
     def packet_type_name(packet_type):
         return name_or_number(Protocol.PACKET_TYPE_NAMES, packet_type)
 
     @staticmethod
-    async def connect(connection, version=(1, 3)):
-        connector = connection.create_l2cap_connector(AVDTP_PSM)
-        channel = await connector()
+    async def connect(
+        connection: device.Connection, version: Tuple[int, int] = (1, 3)
+    ) -> Protocol:
+        channel = await connection.create_l2cap_channel(
+            spec=l2cap.ClassicChannelSpec(psm=AVDTP_PSM)
+        )
         protocol = Protocol(channel, version)
-        protocol.channel_connector = connector
 
         return protocol
 
-    def __init__(self, l2cap_channel, version=(1, 3)):
+    def __init__(
+        self, l2cap_channel: l2cap.ClassicChannel, version: Tuple[int, int] = (1, 3)
+    ) -> None:
         super().__init__()
         self.l2cap_channel = l2cap_channel
         self.version = version
@@ -1243,7 +1290,6 @@
         self.transaction_semaphore = asyncio.Semaphore(16)
         self.transaction_count = 0
         self.channel_acceptor = None
-        self.channel_connector = None
         self.local_endpoints = []  # Local endpoints, with contiguous seid values
         self.remote_endpoints = {}  # Remote stream endpoints, by seid
         self.streams = {}  # Streams, by seid
@@ -1253,27 +1299,31 @@
         l2cap_channel.on('open', self.on_l2cap_channel_open)
         l2cap_channel.on('close', self.on_l2cap_channel_close)
 
-    def get_local_endpoint_by_seid(self, seid):
+    def get_local_endpoint_by_seid(self, seid: int) -> Optional[LocalStreamEndPoint]:
         if 0 < seid <= len(self.local_endpoints):
             return self.local_endpoints[seid - 1]
 
         return None
 
-    def add_source(self, codec_capabilities, packet_pump):
+    def add_source(
+        self, codec_capabilities: MediaCodecCapabilities, packet_pump: MediaPacketPump
+    ) -> LocalSource:
         seid = len(self.local_endpoints) + 1
         source = LocalSource(self, seid, codec_capabilities, packet_pump)
         self.local_endpoints.append(source)
 
         return source
 
-    def add_sink(self, codec_capabilities):
+    def add_sink(self, codec_capabilities: MediaCodecCapabilities) -> LocalSink:
         seid = len(self.local_endpoints) + 1
         sink = LocalSink(self, seid, codec_capabilities)
         self.local_endpoints.append(sink)
 
         return sink
 
-    async def create_stream(self, source, sink):
+    async def create_stream(
+        self, source: LocalStreamEndPoint, sink: StreamEndPointProxy
+    ) -> Stream:
         # Check that the source isn't already used in a stream
         if source.in_use:
             raise InvalidStateError('source already in use')
@@ -1290,10 +1340,10 @@
 
         return stream
 
-    async def discover_remote_endpoints(self):
+    async def discover_remote_endpoints(self) -> Iterable[DiscoveredStreamEndPoint]:
         self.remote_endpoints = {}
 
-        response = await self.send_command(Discover_Command())
+        response: Discover_Response = await self.send_command(Discover_Command())
         for endpoint_entry in response.endpoints:
             logger.debug(
                 f'getting endpoint capabilities for endpoint {endpoint_entry.seid}'
@@ -1311,7 +1361,9 @@
 
         return self.remote_endpoints.values()
 
-    def find_remote_sink_by_codec(self, media_type, codec_type):
+    def find_remote_sink_by_codec(
+        self, media_type: int, codec_type: int
+    ) -> Optional[DiscoveredStreamEndPoint]:
         for endpoint in self.remote_endpoints.values():
             if (
                 not endpoint.in_use
@@ -1330,9 +1382,10 @@
                         capabilities.service_category
                         == AVDTP_MEDIA_CODEC_SERVICE_CATEGORY
                     ):
+                        codec_capabilities = cast(MediaCodecCapabilities, capabilities)
                         if (
-                            capabilities.media_type == AVDTP_AUDIO_MEDIA_TYPE
-                            and capabilities.media_codec_type == codec_type
+                            codec_capabilities.media_type == AVDTP_AUDIO_MEDIA_TYPE
+                            and codec_capabilities.media_codec_type == codec_type
                         ):
                             has_codec = True
                 if has_media_transport and has_codec:
@@ -1340,10 +1393,10 @@
 
         return None
 
-    def on_pdu(self, pdu):
+    def on_pdu(self, pdu: bytes) -> None:
         self.message_assembler.on_pdu(pdu)
 
-    def on_message(self, transaction_label, message):
+    def on_message(self, transaction_label: int, message: Message) -> None:
         logger.debug(
             f'{color("<<< Received AVDTP message", "magenta")}: '
             f'[{transaction_label}] {message}'
@@ -1362,7 +1415,7 @@
             logger.warning('!!! invalid signal identifier')
             self.send_message(transaction_label, General_Reject())
 
-        if message.message_type == Message.COMMAND:
+        if message.message_type == Message.MessageType.COMMAND:
             # Command
             signal_name = (
                 AVDTP_SIGNAL_NAMES.get(message.signal_identifier, "")
@@ -1407,7 +1460,7 @@
         logger.debug(color('<<< L2CAP channel close', 'magenta'))
         self.emit('close')
 
-    def send_message(self, transaction_label, message):
+    def send_message(self, transaction_label: int, message: Message) -> None:
         logger.debug(
             f'{color(">>> Sending AVDTP message", "magenta")}: '
             f'[{transaction_label}] {message}'
@@ -1418,9 +1471,9 @@
         payload = message.payload
         if len(payload) + 2 <= self.l2cap_channel.mtu:
             # Fits in a single packet
-            packet_type = self.SINGLE_PACKET
+            packet_type = self.PacketType.SINGLE_PACKET
         else:
-            packet_type = self.START_PACKET
+            packet_type = self.PacketType.START_PACKET
 
         done = False
         while not done:
@@ -1428,9 +1481,9 @@
                 transaction_label << 4 | packet_type << 2 | message.message_type
             )
 
-            if packet_type == self.SINGLE_PACKET:
+            if packet_type == self.PacketType.SINGLE_PACKET:
                 header = bytes([first_header_byte, message.signal_identifier])
-            elif packet_type == self.START_PACKET:
+            elif packet_type == self.PacketType.START_PACKET:
                 packet_count = (
                     max_fragment_size - 1 + len(payload)
                 ) // max_fragment_size
@@ -1447,14 +1500,14 @@
             payload = payload[max_fragment_size:]
             if payload:
                 packet_type = (
-                    self.CONTINUE_PACKET
-                    if payload > max_fragment_size
-                    else self.END_PACKET
+                    self.PacketType.CONTINUE_PACKET
+                    if len(payload) > max_fragment_size
+                    else self.PacketType.END_PACKET
                 )
             else:
                 done = True
 
-    async def send_command(self, command):
+    async def send_command(self, command: Message):
         # TODO: support timeouts
         # Send the command
         (transaction_label, transaction_result) = await self.start_transaction()
@@ -1464,12 +1517,16 @@
         response = await transaction_result
 
         # Check for errors
-        if response.message_type in (Message.GENERAL_REJECT, Message.RESPONSE_REJECT):
+        if response.message_type in (
+            Message.MessageType.GENERAL_REJECT,
+            Message.MessageType.RESPONSE_REJECT,
+        ):
+            assert hasattr(response, 'error_code')
             raise ProtocolError(response.error_code, 'avdtp')
 
         return response
 
-    async def start_transaction(self):
+    async def start_transaction(self) -> Tuple[int, asyncio.Future[Message]]:
         # Wait until we can start a new transaction
         await self.transaction_semaphore.acquire()
 
@@ -1484,34 +1541,38 @@
 
         assert False  # Should never reach this
 
-    async def get_capabilities(self, seid):
+    async def get_capabilities(
+        self, seid: int
+    ) -> Union[Get_Capabilities_Response, Get_All_Capabilities_Response,]:
         if self.version > (1, 2):
             return await self.send_command(Get_All_Capabilities_Command(seid))
 
         return await self.send_command(Get_Capabilities_Command(seid))
 
-    async def set_configuration(self, acp_seid, int_seid, capabilities):
+    async def set_configuration(
+        self, acp_seid: int, int_seid: int, capabilities: Iterable[ServiceCapabilities]
+    ) -> Set_Configuration_Response:
         return await self.send_command(
             Set_Configuration_Command(acp_seid, int_seid, capabilities)
         )
 
-    async def get_configuration(self, seid):
+    async def get_configuration(self, seid: int) -> Get_Configuration_Response:
         response = await self.send_command(Get_Configuration_Command(seid))
         return response.capabilities
 
-    async def open(self, seid):
+    async def open(self, seid: int) -> Open_Response:
         return await self.send_command(Open_Command(seid))
 
-    async def start(self, seids):
+    async def start(self, seids: Iterable[int]) -> Start_Response:
         return await self.send_command(Start_Command(seids))
 
-    async def suspend(self, seids):
+    async def suspend(self, seids: Iterable[int]) -> Suspend_Response:
         return await self.send_command(Suspend_Command(seids))
 
-    async def close(self, seid):
+    async def close(self, seid: int) -> Close_Response:
         return await self.send_command(Close_Command(seid))
 
-    async def abort(self, seid):
+    async def abort(self, seid: int) -> Abort_Response:
         return await self.send_command(Abort_Command(seid))
 
     def on_discover_command(self, _command):
@@ -1653,26 +1714,46 @@
 
 # -----------------------------------------------------------------------------
 class Listener(EventEmitter):
-    @staticmethod
-    def create_registrar(device):
-        return device.create_l2cap_registrar(AVDTP_PSM)
+    servers: Dict[int, Protocol]
 
-    def set_server(self, connection, server):
+    @staticmethod
+    def create_registrar(device: device.Device):
+        warnings.warn("Please use Listener.for_device()", DeprecationWarning)
+
+        def wrapper(handler: Callable[[l2cap.ClassicChannel], None]) -> None:
+            device.create_l2cap_server(l2cap.ClassicChannelSpec(psm=AVDTP_PSM), handler)
+
+        return wrapper
+
+    def set_server(self, connection: device.Connection, server: Protocol) -> None:
         self.servers[connection.handle] = server
 
-    def remove_server(self, connection):
+    def remove_server(self, connection: device.Connection) -> None:
         if connection.handle in self.servers:
             del self.servers[connection.handle]
 
-    def __init__(self, registrar, version=(1, 3)):
+    def __init__(self, registrar=None, version=(1, 3)):
         super().__init__()
         self.version = version
         self.servers = {}  # Servers, by connection handle
 
         # Listen for incoming L2CAP connections
-        registrar(self.on_l2cap_connection)
+        if registrar:
+            warnings.warn("Please use Listener.for_device()", DeprecationWarning)
+            registrar(self.on_l2cap_connection)
 
-    def on_l2cap_connection(self, channel):
+    @classmethod
+    def for_device(
+        cls, device: device.Device, version: Tuple[int, int] = (1, 3)
+    ) -> Listener:
+        listener = Listener(registrar=None, version=version)
+        l2cap_server = device.create_l2cap_server(
+            spec=l2cap.ClassicChannelSpec(psm=AVDTP_PSM)
+        )
+        l2cap_server.on('connection', listener.on_l2cap_connection)
+        return listener
+
+    def on_l2cap_connection(self, channel: l2cap.ClassicChannel) -> None:
         logger.debug(f'{color("<<< incoming L2CAP connection:", "magenta")} {channel}')
 
         if channel.connection.handle in self.servers:
@@ -1701,18 +1782,21 @@
     Pair of a local and a remote stream endpoint that can stream from one to the other
     '''
 
+    rtp_channel: Optional[l2cap.ClassicChannel]
+
     @staticmethod
-    def state_name(state):
+    def state_name(state: int) -> str:
         return name_or_number(AVDTP_STATE_NAMES, state)
 
-    def change_state(self, state):
+    def change_state(self, state: int) -> None:
         logger.debug(f'{self} state change -> {color(self.state_name(state), "cyan")}')
         self.state = state
 
-    def send_media_packet(self, packet):
+    def send_media_packet(self, packet: MediaPacket) -> None:
+        assert self.rtp_channel
         self.rtp_channel.send_pdu(bytes(packet))
 
-    async def configure(self):
+    async def configure(self) -> None:
         if self.state != AVDTP_IDLE_STATE:
             raise InvalidStateError('current state is not IDLE')
 
@@ -1721,7 +1805,7 @@
         )
         self.change_state(AVDTP_CONFIGURED_STATE)
 
-    async def open(self):
+    async def open(self) -> None:
         if self.state != AVDTP_CONFIGURED_STATE:
             raise InvalidStateError('current state is not CONFIGURED')
 
@@ -1731,9 +1815,13 @@
         self.change_state(AVDTP_OPEN_STATE)
 
         # Create a channel for RTP packets
-        self.rtp_channel = await self.protocol.channel_connector()
+        self.rtp_channel = (
+            await self.protocol.l2cap_channel.connection.create_l2cap_channel(
+                l2cap.ClassicChannelSpec(psm=AVDTP_PSM)
+            )
+        )
 
-    async def start(self):
+    async def start(self) -> None:
         # Auto-open if needed
         if self.state == AVDTP_CONFIGURED_STATE:
             await self.open()
@@ -1749,7 +1837,7 @@
 
         self.change_state(AVDTP_STREAMING_STATE)
 
-    async def stop(self):
+    async def stop(self) -> None:
         if self.state != AVDTP_STREAMING_STATE:
             raise InvalidStateError('current state is not STREAMING')
 
@@ -1761,7 +1849,7 @@
 
         self.change_state(AVDTP_OPEN_STATE)
 
-    async def close(self):
+    async def close(self) -> None:
         if self.state not in (AVDTP_OPEN_STATE, AVDTP_STREAMING_STATE):
             raise InvalidStateError('current state is not OPEN or STREAMING')
 
@@ -1905,7 +1993,12 @@
         else:
             logger.warning('unexpected channel close while not CLOSING or ABORTING')
 
-    def __init__(self, protocol, local_endpoint, remote_endpoint):
+    def __init__(
+        self,
+        protocol: Protocol,
+        local_endpoint: LocalStreamEndPoint,
+        remote_endpoint: StreamEndPointProxy,
+    ) -> None:
         '''
         remote_endpoint must be a subclass of StreamEndPointProxy
 
@@ -1919,7 +2012,7 @@
         local_endpoint.stream = self
         local_endpoint.in_use = 1
 
-    def __str__(self):
+    def __str__(self) -> str:
         return (
             f'Stream({self.local_endpoint.seid} -> '
             f'{self.remote_endpoint.seid} {self.state_name(self.state)})'
@@ -1928,14 +2021,21 @@
 
 # -----------------------------------------------------------------------------
 class StreamEndPoint:
-    def __init__(self, seid, media_type, tsep, in_use, capabilities):
+    def __init__(
+        self,
+        seid: int,
+        media_type: int,
+        tsep: int,
+        in_use: int,
+        capabilities: Iterable[ServiceCapabilities],
+    ) -> None:
         self.seid = seid
         self.media_type = media_type
         self.tsep = tsep
         self.in_use = in_use
         self.capabilities = capabilities
 
-    def __str__(self):
+    def __str__(self) -> str:
         media_type = f'{name_or_number(AVDTP_MEDIA_TYPE_NAMES, self.media_type)}'
         tsep = f'{name_or_number(AVDTP_TSEP_NAMES, self.tsep)}'
         return '\n'.join(
@@ -1955,40 +2055,58 @@
 
 # -----------------------------------------------------------------------------
 class StreamEndPointProxy:
-    def __init__(self, protocol, seid):
+    def __init__(self, protocol: Protocol, seid: int) -> None:
         self.seid = seid
         self.protocol = protocol
 
-    async def set_configuration(self, int_seid, configuration):
+    async def set_configuration(
+        self, int_seid: int, configuration: Iterable[ServiceCapabilities]
+    ) -> Set_Configuration_Response:
         return await self.protocol.set_configuration(self.seid, int_seid, configuration)
 
-    async def open(self):
+    async def open(self) -> Open_Response:
         return await self.protocol.open(self.seid)
 
-    async def start(self):
+    async def start(self) -> Start_Response:
         return await self.protocol.start([self.seid])
 
-    async def stop(self):
+    async def stop(self) -> Suspend_Response:
         return await self.protocol.suspend([self.seid])
 
-    async def close(self):
+    async def close(self) -> Close_Response:
         return await self.protocol.close(self.seid)
 
-    async def abort(self):
+    async def abort(self) -> Abort_Response:
         return await self.protocol.abort(self.seid)
 
 
 # -----------------------------------------------------------------------------
 class DiscoveredStreamEndPoint(StreamEndPoint, StreamEndPointProxy):
-    def __init__(self, protocol, seid, media_type, tsep, in_use, capabilities):
+    def __init__(
+        self,
+        protocol: Protocol,
+        seid: int,
+        media_type: int,
+        tsep: int,
+        in_use: int,
+        capabilities: Iterable[ServiceCapabilities],
+    ) -> None:
         StreamEndPoint.__init__(self, seid, media_type, tsep, in_use, capabilities)
         StreamEndPointProxy.__init__(self, protocol, seid)
 
 
 # -----------------------------------------------------------------------------
 class LocalStreamEndPoint(StreamEndPoint, EventEmitter):
+    stream: Optional[Stream]
+
     def __init__(
-        self, protocol, seid, media_type, tsep, capabilities, configuration=None
+        self,
+        protocol: Protocol,
+        seid: int,
+        media_type: int,
+        tsep: int,
+        capabilities: Iterable[ServiceCapabilities],
+        configuration: Optional[Iterable[ServiceCapabilities]] = None,
     ):
         StreamEndPoint.__init__(self, seid, media_type, tsep, 0, capabilities)
         EventEmitter.__init__(self)
@@ -2043,7 +2161,13 @@
 
 # -----------------------------------------------------------------------------
 class LocalSource(LocalStreamEndPoint):
-    def __init__(self, protocol, seid, codec_capabilities, packet_pump):
+    def __init__(
+        self,
+        protocol: Protocol,
+        seid: int,
+        codec_capabilities: MediaCodecCapabilities,
+        packet_pump: MediaPacketPump,
+    ) -> None:
         capabilities = [
             ServiceCapabilities(AVDTP_MEDIA_TRANSPORT_SERVICE_CATEGORY),
             codec_capabilities,
@@ -2058,13 +2182,13 @@
         )
         self.packet_pump = packet_pump
 
-    async def start(self):
-        if self.packet_pump:
+    async def start(self) -> None:
+        if self.packet_pump and self.stream and self.stream.rtp_channel:
             return await self.packet_pump.start(self.stream.rtp_channel)
 
         self.emit('start')
 
-    async def stop(self):
+    async def stop(self) -> None:
         if self.packet_pump:
             return await self.packet_pump.stop()
 
@@ -2079,7 +2203,9 @@
 
 # -----------------------------------------------------------------------------
 class LocalSink(LocalStreamEndPoint):
-    def __init__(self, protocol, seid, codec_capabilities):
+    def __init__(
+        self, protocol: Protocol, seid: int, codec_capabilities: MediaCodecCapabilities
+    ) -> None:
         capabilities = [
             ServiceCapabilities(AVDTP_MEDIA_TRANSPORT_SERVICE_CATEGORY),
             codec_capabilities,
diff --git a/bumble/company_ids.py b/bumble/company_ids.py
index 1c40951..af99b01 100644
--- a/bumble/company_ids.py
+++ b/bumble/company_ids.py
@@ -14,12 +14,12 @@
 
 # -----------------------------------------------------------------------------
 #  The contents of this file are generated by copy/pasting the output of
-#  the `generate_company_id_list.py` script
+#  the `tools/generate_company_id_list.py` script
 # -----------------------------------------------------------------------------
 
 # pylint: disable=line-too-long
 COMPANY_IDENTIFIERS = {
-    0x0000: "Ericsson Technology Licensing",
+    0x0000: "Ericsson AB",
     0x0001: "Nokia Mobile Phones",
     0x0002: "Intel Corp.",
     0x0003: "IBM Corp.",
@@ -40,7 +40,7 @@
     0x0012: "Zeevo, Inc.",
     0x0013: "Atmel Corporation",
     0x0014: "Mitsubishi Electric Corporation",
-    0x0015: "RTX Telecom A/S",
+    0x0015: "RTX A/S",
     0x0016: "KC Technology Inc.",
     0x0017: "Newlogic",
     0x0018: "Transilica, Inc.",
@@ -56,7 +56,7 @@
     0x0022: "NEC Corporation",
     0x0023: "WavePlus Technology Co., Ltd.",
     0x0024: "Alcatel",
-    0x0025: "NXP Semiconductors (formerly Philips Semiconductors)",
+    0x0025: "NXP B.V.",
     0x0026: "C Technologies",
     0x0027: "Open Interface",
     0x0028: "R F Micro Devices",
@@ -77,9 +77,9 @@
     0x0037: "Mobilian Corporation",
     0x0038: "Syntronix Corporation",
     0x0039: "Integrated System Solution Corp.",
-    0x003A: "Panasonic Corporation (formerly Matsushita Electric Industrial Co., Ltd.)",
+    0x003A: "Panasonic Holdings Corporation",
     0x003B: "Gennum Corporation",
-    0x003C: "BlackBerry Limited  (formerly Research In Motion)",
+    0x003C: "BlackBerry Limited",
     0x003D: "IPextreme, Inc.",
     0x003E: "Systems and Chips, Inc",
     0x003F: "Bluetooth SIG, Inc",
@@ -120,9 +120,9 @@
     0x0062: "Gibson Guitars",
     0x0063: "MiCommand Inc.",
     0x0064: "Band XI International, LLC",
-    0x0065: "Hewlett-Packard Company",
+    0x0065: "HP, Inc.",
     0x0066: "9Solutions Oy",
-    0x0067: "GN Netcom A/S",
+    0x0067: "GN Audio A/S",
     0x0068: "General Motors",
     0x0069: "A&D Engineering, Inc.",
     0x006A: "MindTree Ltd.",
@@ -138,7 +138,7 @@
     0x0074: "Zomm, LLC",
     0x0075: "Samsung Electronics Co. Ltd.",
     0x0076: "Creative Technology Ltd.",
-    0x0077: "Laird Technologies",
+    0x0077: "Laird Connectivity LLC",
     0x0078: "Nike, Inc.",
     0x0079: "lesswire AG",
     0x007A: "MStar Semiconductor, Inc.",
@@ -149,20 +149,20 @@
     0x007F: "Autonet Mobile",
     0x0080: "DeLorme Publishing Company, Inc.",
     0x0081: "WuXi Vimicro",
-    0x0082: "Sennheiser Communications A/S",
+    0x0082: "DSEA A/S",
     0x0083: "TimeKeeping Systems, Inc.",
     0x0084: "Ludus Helsinki Ltd.",
     0x0085: "BlueRadios, Inc.",
     0x0086: "Equinux AG",
     0x0087: "Garmin International, Inc.",
     0x0088: "Ecotest",
-    0x0089: "GN ReSound A/S",
+    0x0089: "GN Hearing A/S",
     0x008A: "Jawbone",
     0x008B: "Topcon Positioning Systems, LLC",
-    0x008C: "Gimbal Inc. (formerly Qualcomm Labs, Inc. and Qualcomm Retail Solutions, Inc.)",
+    0x008C: "Gimbal Inc.",
     0x008D: "Zscan Software",
     0x008E: "Quintic Corp",
-    0x008F: "Telit Wireless Solutions GmbH (formerly Stollmann E+V GmbH)",
+    0x008F: "Telit Wireless Solutions GmbH",
     0x0090: "Funai Electric Co., Ltd.",
     0x0091: "Advanced PANMOBIL systems GmbH & Co. KG",
     0x0092: "ThinkOptics, Inc.",
@@ -188,7 +188,7 @@
     0x00A6: "Panda Ocean Inc.",
     0x00A7: "Visteon Corporation",
     0x00A8: "ARP Devices Limited",
-    0x00A9: "MARELLI EUROPE S.P.A. (formerly Magneti Marelli S.p.A.)",
+    0x00A9: "MARELLI EUROPE S.P.A.",
     0x00AA: "CAEN RFID srl",
     0x00AB: "Ingenieur-Systemgruppe Zahn GmbH",
     0x00AC: "Green Throttle Games",
@@ -205,7 +205,7 @@
     0x00B7: "TreLab Ltd",
     0x00B8: "Qualcomm Innovation Center, Inc. (QuIC)",
     0x00B9: "Johnson Controls, Inc.",
-    0x00BA: "Starkey Laboratories Inc.",
+    0x00BA: "Starkey Hearing Technologies",
     0x00BB: "S-Power Electronics Limited",
     0x00BC: "Ace Sensor Inc",
     0x00BD: "Aplix Corporation",
@@ -225,7 +225,7 @@
     0x00CB: "Binauric SE",
     0x00CC: "Beats Electronics",
     0x00CD: "Microchip Technology Inc.",
-    0x00CE: "Elgato Systems GmbH",
+    0x00CE: "Eve Systems GmbH",
     0x00CF: "ARCHOS SA",
     0x00D0: "Dexcom, Inc.",
     0x00D1: "Polar Electro Europe B.V.",
@@ -238,7 +238,7 @@
     0x00D8: "Qualcomm Connected Experiences, Inc.",
     0x00D9: "Voyetra Turtle Beach",
     0x00DA: "txtr GmbH",
-    0x00DB: "Biosentronics",
+    0x00DB: "Snuza (Pty) Ltd",
     0x00DC: "Procter & Gamble",
     0x00DD: "Hosiden Corporation",
     0x00DE: "Muzik LLC",
@@ -247,13 +247,13 @@
     0x00E1: "Danlers Ltd",
     0x00E2: "Semilink Inc",
     0x00E3: "inMusic Brands, Inc",
-    0x00E4: "Laird Connectivity, Inc. formerly L.S. Research Inc.",
+    0x00E4: "L.S. Research, Inc.",
     0x00E5: "Eden Software Consultants Ltd.",
     0x00E6: "Freshtemp",
     0x00E7: "KS Technologies",
     0x00E8: "ACTS Technologies",
     0x00E9: "Vtrack Systems",
-    0x00EA: "Nielsen-Kellerman Company",
+    0x00EA: "www.vtracksystems.com",
     0x00EB: "Server Technology Inc.",
     0x00EC: "BioResearch Associates",
     0x00ED: "Jolly Logic, LLC",
@@ -269,7 +269,7 @@
     0x00F7: "VSN Technologies, Inc.",
     0x00F8: "AceUni Corp., Ltd.",
     0x00F9: "StickNFind",
-    0x00FA: "Crystal Code AB",
+    0x00FA: "Crystal Alarm AB",
     0x00FB: "KOUKAAM a.s.",
     0x00FC: "Delphi Corporation",
     0x00FD: "ValenceTech Limited",
@@ -282,13 +282,13 @@
     0x0104: "PLUS Location Systems Pty Ltd",
     0x0105: "Ubiquitous Computing Technology Corporation",
     0x0106: "Innovative Yachtter Solutions",
-    0x0107: "William Demant Holding A/S",
+    0x0107: "Demant A/S",
     0x0108: "Chicony Electronics Co., Ltd.",
     0x0109: "Atus BV",
     0x010A: "Codegate Ltd",
     0x010B: "ERi, Inc",
     0x010C: "Transducers Direct, LLC",
-    0x010D: "DENSO TEN LIMITED (formerly Fujitsu Ten LImited)",
+    0x010D: "DENSO TEN Limited",
     0x010E: "Audi AG",
     0x010F: "HiSilicon Technologies CO., LIMITED",
     0x0110: "Nippon Seiki Co., Ltd.",
@@ -371,24 +371,24 @@
     0x015D: "Estimote, Inc.",
     0x015E: "Unikey Technologies, Inc.",
     0x015F: "Timer Cap Co.",
-    0x0160: "Awox formerly AwoX",
+    0x0160: "AwoX",
     0x0161: "yikes",
     0x0162: "MADSGlobalNZ Ltd.",
     0x0163: "PCH International",
     0x0164: "Qingdao Yeelink Information Technology Co., Ltd.",
-    0x0165: "Milwaukee Tool (Formally Milwaukee Electric Tools)",
+    0x0165: "Milwaukee Electric Tools",
     0x0166: "MISHIK Pte Ltd",
     0x0167: "Ascensia Diabetes Care US Inc.",
     0x0168: "Spicebox LLC",
     0x0169: "emberlight",
-    0x016A: "Cooper-Atkins Corporation",
+    0x016A: "Emerson Digital Cold Chain, Inc.",
     0x016B: "Qblinks",
     0x016C: "MYSPHERA",
     0x016D: "LifeScan Inc",
     0x016E: "Volantic AB",
     0x016F: "Podo Labs, Inc",
     0x0170: "Roche Diabetes Care AG",
-    0x0171: "Amazon.com Services, LLC (formerly Amazon Fulfillment Service)",
+    0x0171: "Amazon.com Services LLC",
     0x0172: "Connovate Technology Private Limited",
     0x0173: "Kocomojo, LLC",
     0x0174: "Everykey Inc.",
@@ -396,10 +396,10 @@
     0x0176: "SentriLock",
     0x0177: "I-SYST inc.",
     0x0178: "CASIO COMPUTER CO., LTD.",
-    0x0179: "LAPIS Technology Co., Ltd. formerly LAPIS Semiconductor Co., Ltd.",
+    0x0179: "LAPIS Semiconductor Co.,Ltd",
     0x017A: "Telemonitor, Inc.",
     0x017B: "taskit GmbH",
-    0x017C: "Daimler AG",
+    0x017C: "Mercedes-Benz Group AG",
     0x017D: "BatAndCat",
     0x017E: "BluDotz Ltd",
     0x017F: "XTel Wireless ApS",
@@ -429,7 +429,7 @@
     0x0197: "WiSilica Inc.",
     0x0198: "VENGIT Korlatolt Felelossegu Tarsasag",
     0x0199: "SALTO SYSTEMS S.L.",
-    0x019A: "TRON Forum (formerly T-Engine Forum)",
+    0x019A: "TRON Forum",
     0x019B: "CUBETECH s.r.o.",
     0x019C: "Cokiya Incorporated",
     0x019D: "CVS Health",
@@ -439,14 +439,14 @@
     0x01A1: "FIAMM",
     0x01A2: "GIGALANE.CO.,LTD",
     0x01A3: "EROAD",
-    0x01A4: "Mine Safety Appliances",
+    0x01A4: "MSA Innovation, LLC",
     0x01A5: "Icon Health and Fitness",
-    0x01A6: "Wille Engineering (formely as Asandoo GmbH)",
+    0x01A6: "Wille Engineering",
     0x01A7: "ENERGOUS CORPORATION",
     0x01A8: "Taobao",
     0x01A9: "Canon Inc.",
     0x01AA: "Geophysical Technology Inc.",
-    0x01AB: "Facebook, Inc.",
+    0x01AB: "Meta Platforms, Inc.",
     0x01AC: "Trividia Health, Inc.",
     0x01AD: "FlightSafety International",
     0x01AE: "Earlens Corporation",
@@ -466,7 +466,7 @@
     0x01BC: "SenionLab AB",
     0x01BD: "Syszone Co., Ltd",
     0x01BE: "Pulsate Mobile Ltd.",
-    0x01BF: "Hong Kong HunterSun Electronic Limited",
+    0x01BF: "Hongkong OnMicro Electronics Limited",
     0x01C0: "pironex GmbH",
     0x01C1: "BRADATECH Corp.",
     0x01C2: "Transenergooil AG",
@@ -496,7 +496,7 @@
     0x01DA: "Logitech International SA",
     0x01DB: "Innblue Consulting",
     0x01DC: "iParking Ltd.",
-    0x01DD: "Koninklijke Philips Electronics N.V.",
+    0x01DD: "Koninklijke Philips N.V.",
     0x01DE: "Minelab Electronics Pty Limited",
     0x01DF: "Bison Group Ltd.",
     0x01E0: "Widex A/S",
@@ -574,7 +574,7 @@
     0x0228: "Twocanoes Labs, LLC",
     0x0229: "Muoverti Limited",
     0x022A: "Stamer Musikanlagen GMBH",
-    0x022B: "Tesla Motors",
+    0x022B: "Tesla, Inc.",
     0x022C: "Pharynks Corporation",
     0x022D: "Lupine",
     0x022E: "Siemens AG",
@@ -600,7 +600,7 @@
     0x0242: "16Lab Inc",
     0x0243: "Masimo Corp",
     0x0244: "Iotera Inc",
-    0x0245: "Endress+Hauser ",
+    0x0245: "Endress+Hauser",
     0x0246: "ACKme Networks, Inc.",
     0x0247: "FiftyThree Inc.",
     0x0248: "Parker Hannifin Corp",
@@ -664,7 +664,7 @@
     0x0282: "Sonova AG",
     0x0283: "Maven Machines, Inc.",
     0x0284: "Synapse Electronics",
-    0x0285: "Standard Innovation Inc.",
+    0x0285: "WOWTech Canada Ltd.",
     0x0286: "RF Code, Inc.",
     0x0287: "Wally Ventures S.L.",
     0x0288: "Willowbank Electronics Ltd",
@@ -693,10 +693,10 @@
     0x029F: "Areus Engineering GmbH",
     0x02A0: "Impossible Camera GmbH",
     0x02A1: "InventureTrack Systems",
-    0x02A2: "LockedUp",
+    0x02A2: "Sera4 Ltd.",
     0x02A3: "Itude",
     0x02A4: "Pacific Lock Company",
-    0x02A5: "Tendyron Corporation ( 天地融科技股份有限公司 )",
+    0x02A5: "Tendyron Corporation",
     0x02A6: "Robert Bosch GmbH",
     0x02A7: "Illuxtron international B.V.",
     0x02A8: "miSport Ltd.",
@@ -709,7 +709,7 @@
     0x02AF: "Technicolor USA Inc.",
     0x02B0: "Bestechnic(Shanghai),Ltd",
     0x02B1: "Raden Inc",
-    0x02B2: "JouZen Oy",
+    0x02B2: "Oura Health Oy",
     0x02B3: "CLABER S.P.A.",
     0x02B4: "Hyginex, Inc.",
     0x02B5: "HANSHIN ELECTRIC RAILWAY CO.,LTD.",
@@ -728,10 +728,10 @@
     0x02C2: "Guillemot Corporation",
     0x02C3: "Techtronic Power Tools Technology Limited",
     0x02C4: "Wilson Sporting Goods",
-    0x02C5: "Lenovo (Singapore) Pte Ltd. ( 联想(新加坡) )",
+    0x02C5: "Lenovo (Singapore) Pte Ltd.",
     0x02C6: "Ayatan Sensors",
     0x02C7: "Electronics Tomorrow Limited",
-    0x02C8: "VASCO Data Security International, Inc.",
+    0x02C8: "OneSpan",
     0x02C9: "PayRange Inc.",
     0x02CA: "ABOV Semiconductor",
     0x02CB: "AINA-Wireless Inc.",
@@ -760,7 +760,7 @@
     0x02E2: "NTT docomo",
     0x02E3: "Carmanah Technologies Corp.",
     0x02E4: "Bytestorm Ltd.",
-    0x02E5: "Espressif Incorporated ( 乐鑫信息科技(上海)有限公司 )",
+    0x02E5: "Espressif Systems (Shanghai) Co., Ltd.",
     0x02E6: "Unwire",
     0x02E7: "Connected Yard, Inc.",
     0x02E8: "American Music Environments",
@@ -768,10 +768,10 @@
     0x02EA: "Fujitsu Limited",
     0x02EB: "Ardic Technology",
     0x02EC: "Delta Systems, Inc",
-    0x02ED: "HTC Corporation ",
-    0x02EE: "Citizen Holdings Co., Ltd. ",
+    0x02ED: "HTC Corporation",
+    0x02EE: "Citizen Holdings Co., Ltd.",
     0x02EF: "SMART-INNOVATION.inc",
-    0x02F0: "Blackrat Software ",
+    0x02F0: "Blackrat Software",
     0x02F1: "The Idea Cave, LLC",
     0x02F2: "GoPro, Inc.",
     0x02F3: "AuthAir, Inc",
@@ -780,7 +780,7 @@
     0x02F6: "Intemo Technologies",
     0x02F7: "DreamVisions co., Ltd.",
     0x02F8: "Runteq Oy Ltd",
-    0x02F9: "IMAGINATION TECHNOLOGIES LTD ",
+    0x02F9: "IMAGINATION TECHNOLOGIES LTD",
     0x02FA: "CoSTAR TEchnologies",
     0x02FB: "Clarius Mobile Health Corp.",
     0x02FC: "Shanghai Frequen Microelectronics Co., Ltd.",
@@ -793,7 +793,7 @@
     0x0303: "IACA electronique",
     0x0304: "Proxy Technologies, Inc.",
     0x0305: "Swipp ApS",
-    0x0306: "Life Laboratory Inc. ",
+    0x0306: "Life Laboratory Inc.",
     0x0307: "FUJI INDUSTRIAL CO.,LTD.",
     0x0308: "Surefire, LLC",
     0x0309: "Dolby Labs",
@@ -805,101 +805,101 @@
     0x030F: "Shortcut Labs",
     0x0310: "SGL Italia S.r.l.",
     0x0311: "PEEQ DATA",
-    0x0312: "Ducere Technologies Pvt Ltd ",
-    0x0313: "DiveNav, Inc. ",
+    0x0312: "Ducere Technologies Pvt Ltd",
+    0x0313: "DiveNav, Inc.",
     0x0314: "RIIG AI Sp. z o.o.",
-    0x0315: "Thermo Fisher Scientific ",
-    0x0316: "AG Measurematics Pvt. Ltd. ",
-    0x0317: "CHUO Electronics CO., LTD. ",
-    0x0318: "Aspenta International ",
-    0x0319: "Eugster Frismag AG ",
-    0x031A: "Amber wireless GmbH ",
-    0x031B: "HQ Inc ",
-    0x031C: "Lab Sensor Solutions ",
-    0x031D: "Enterlab ApS ",
+    0x0315: "Thermo Fisher Scientific",
+    0x0316: "AG Measurematics Pvt. Ltd.",
+    0x0317: "CHUO Electronics CO., LTD.",
+    0x0318: "Aspenta International",
+    0x0319: "Eugster Frismag AG",
+    0x031A: "Wurth Elektronik eiSos GmbH & Co. KG",
+    0x031B: "HQ Inc",
+    0x031C: "Lab Sensor Solutions",
+    0x031D: "Enterlab ApS",
     0x031E: "Eyefi, Inc.",
-    0x031F: "MetaSystem S.p.A. ",
-    0x0320: "SONO ELECTRONICS. CO., LTD ",
-    0x0321: "Jewelbots ",
-    0x0322: "Compumedics Limited ",
-    0x0323: "Rotor Bike Components ",
-    0x0324: "Astro, Inc. ",
-    0x0325: "Amotus Solutions ",
-    0x0326: "Healthwear Technologies (Changzhou)Ltd ",
-    0x0327: "Essex Electronics ",
+    0x031F: "MetaSystem S.p.A.",
+    0x0320: "SONO ELECTRONICS. CO., LTD",
+    0x0321: "Jewelbots",
+    0x0322: "Compumedics Limited",
+    0x0323: "Rotor Bike Components",
+    0x0324: "Astro, Inc.",
+    0x0325: "Amotus Solutions",
+    0x0326: "Healthwear Technologies (Changzhou)Ltd",
+    0x0327: "Essex Electronics",
     0x0328: "Grundfos A/S",
-    0x0329: "Eargo, Inc. ",
-    0x032A: "Electronic Design Lab ",
-    0x032B: "ESYLUX ",
+    0x0329: "Eargo, Inc.",
+    0x032A: "Electronic Design Lab",
+    0x032B: "ESYLUX",
     0x032C: "NIPPON SMT.CO.,Ltd",
-    0x032D: "BM innovations GmbH ",
+    0x032D: "BM innovations GmbH",
     0x032E: "indoormap",
-    0x032F: "OttoQ Inc ",
-    0x0330: "North Pole Engineering ",
+    0x032F: "OttoQ Inc",
+    0x0330: "North Pole Engineering",
     0x0331: "3flares Technologies Inc.",
-    0x0332: "Electrocompaniet A.S. ",
+    0x0332: "Electrocompaniet A.S.",
     0x0333: "Mul-T-Lock",
-    0x0334: "Corentium AS ",
+    0x0334: "Airthings ASA",
     0x0335: "Enlighted Inc",
     0x0336: "GISTIC",
     0x0337: "AJP2 Holdings, LLC",
-    0x0338: "COBI GmbH ",
-    0x0339: "Blue Sky Scientific, LLC ",
+    0x0338: "COBI GmbH",
+    0x0339: "Blue Sky Scientific, LLC",
     0x033A: "Appception, Inc.",
-    0x033B: "Courtney Thorne Limited ",
+    0x033B: "Courtney Thorne Limited",
     0x033C: "Virtuosys",
-    0x033D: "TPV Technology Limited ",
+    0x033D: "TPV Technology Limited",
     0x033E: "Monitra SA",
-    0x033F: "Automation Components, Inc. ",
-    0x0340: "Letsense s.r.l. ",
-    0x0341: "Etesian Technologies LLC ",
-    0x0342: "GERTEC BRASIL LTDA. ",
+    0x033F: "Automation Components, Inc.",
+    0x0340: "Letsense s.r.l.",
+    0x0341: "Etesian Technologies LLC",
+    0x0342: "GERTEC BRASIL LTDA.",
     0x0343: "Drekker Development Pty. Ltd.",
-    0x0344: "Whirl Inc ",
-    0x0345: "Locus Positioning ",
-    0x0346: "Acuity Brands Lighting, Inc ",
-    0x0347: "Prevent Biometrics ",
+    0x0344: "Whirl Inc",
+    0x0345: "Locus Positioning",
+    0x0346: "Acuity Brands Lighting, Inc",
+    0x0347: "Prevent Biometrics",
     0x0348: "Arioneo",
-    0x0349: "VersaMe ",
-    0x034A: "Vaddio ",
-    0x034B: "Libratone A/S ",
-    0x034C: "HM Electronics, Inc. ",
+    0x0349: "VersaMe",
+    0x034A: "Vaddio",
+    0x034B: "Libratone A/S",
+    0x034C: "HM Electronics, Inc.",
     0x034D: "TASER International, Inc.",
-    0x034E: "SafeTrust Inc. ",
-    0x034F: "Heartland Payment Systems ",
-    0x0350: "Bitstrata Systems Inc. ",
-    0x0351: "Pieps GmbH ",
+    0x034E: "SafeTrust Inc.",
+    0x034F: "Heartland Payment Systems",
+    0x0350: "Bitstrata Systems Inc.",
+    0x0351: "Pieps GmbH",
     0x0352: "iRiding(Xiamen)Technology Co.,Ltd.",
-    0x0353: "Alpha Audiotronics, Inc. ",
-    0x0354: "TOPPAN FORMS CO.,LTD. ",
-    0x0355: "Sigma Designs, Inc. ",
-    0x0356: "Spectrum Brands, Inc. ",
-    0x0357: "Polymap Wireless ",
+    0x0353: "Alpha Audiotronics, Inc.",
+    0x0354: "TOPPAN FORMS CO.,LTD.",
+    0x0355: "Sigma Designs, Inc.",
+    0x0356: "Spectrum Brands, Inc.",
+    0x0357: "Polymap Wireless",
     0x0358: "MagniWare Ltd.",
-    0x0359: "Novotec Medical GmbH ",
-    0x035A: "Medicom Innovation Partner a/s ",
-    0x035B: "Matrix Inc. ",
-    0x035C: "Eaton Corporation ",
+    0x0359: "Novotec Medical GmbH",
+    0x035A: "Phillips-Medisize A/S",
+    0x035B: "Matrix Inc.",
+    0x035C: "Eaton Corporation",
     0x035D: "KYS",
-    0x035E: "Naya Health, Inc. ",
-    0x035F: "Acromag ",
-    0x0360: "Insulet Corporation ",
-    0x0361: "Wellinks Inc. ",
+    0x035E: "Naya Health, Inc.",
+    0x035F: "Acromag",
+    0x0360: "Insulet Corporation",
+    0x0361: "Wellinks Inc.",
     0x0362: "ON Semiconductor",
-    0x0363: "FREELAP SA ",
-    0x0364: "Favero Electronics Srl ",
-    0x0365: "BioMech Sensor LLC ",
+    0x0363: "FREELAP SA",
+    0x0364: "Favero Electronics Srl",
+    0x0365: "BioMech Sensor LLC",
     0x0366: "BOLTT Sports technologies Private limited",
-    0x0367: "Saphe International ",
-    0x0368: "Metormote AB ",
-    0x0369: "littleBits ",
-    0x036A: "SetPoint Medical ",
-    0x036B: "BRControls Products BV ",
-    0x036C: "Zipcar ",
-    0x036D: "AirBolt Pty Ltd ",
-    0x036E: "KeepTruckin Inc ",
-    0x036F: "Motiv, Inc. ",
-    0x0370: "Wazombi Labs OÜ ",
+    0x0367: "Saphe International",
+    0x0368: "Metormote AB",
+    0x0369: "littleBits",
+    0x036A: "SetPoint Medical",
+    0x036B: "BRControls Products BV",
+    0x036C: "Zipcar",
+    0x036D: "AirBolt Pty Ltd",
+    0x036E: "MOTIVE TECHNOLOGIES, INC.",
+    0x036F: "Motiv, Inc.",
+    0x0370: "Wazombi Labs OÜ",
     0x0371: "ORBCOMM",
     0x0372: "Nixie Labs, Inc.",
     0x0373: "AppNearMe Ltd",
@@ -914,7 +914,7 @@
     0x037C: "Cronologics Corporation",
     0x037D: "MICRODIA Ltd.",
     0x037E: "lulabytes S.L.",
-    0x037F: "Société des Produits Nestlé S.A. (formerly Nestec S.A.)",
+    0x037F: "Société des Produits Nestlé S.A.",
     0x0380: "LLC \"MEGA-F service\"",
     0x0381: "Sharp Corporation",
     0x0382: "Precision Outcomes Ltd",
@@ -1022,7 +1022,7 @@
     0x03E8: "Reiner Kartengeraete GmbH & Co. KG.",
     0x03E9: "SHENZHEN LEMONJOY TECHNOLOGY CO., LTD.",
     0x03EA: "Hello Inc.",
-    0x03EB: "Evollve Inc.",
+    0x03EB: "Ozo Edu, Inc.",
     0x03EC: "Jigowatts Inc.",
     0x03ED: "BASIC MICRO.COM,INC.",
     0x03EE: "CUBE TECHNOLOGIES",
@@ -1040,7 +1040,7 @@
     0x03FA: "Vyassoft Technologies Inc",
     0x03FB: "Nox Medical",
     0x03FC: "Kimberly-Clark",
-    0x03FD: "Trimble Navigation Ltd.",
+    0x03FD: "Trimble Inc.",
     0x03FE: "Littelfuse",
     0x03FF: "Withings",
     0x0400: "i-developer IT Beratung UG",
@@ -1052,8 +1052,8 @@
     0x0406: "Airtago",
     0x0407: "Swiss Audio SA",
     0x0408: "ToGetHome Inc.",
-    0x0409: "AXIS",
-    0x040A: "Openmatics",
+    0x0409: "RYSE INC.",
+    0x040A: "ZF OPENMATICS s.r.o.",
     0x040B: "Jana Care Inc.",
     0x040C: "Senix Corporation",
     0x040D: "NorthStar Battery Company, LLC",
@@ -1092,7 +1092,7 @@
     0x042E: "MemCachier Inc.",
     0x042F: "Danfoss A/S",
     0x0430: "SnapStyk Inc.",
-    0x0431: "Amway Corporation",
+    0x0431: "Alticor Inc.",
     0x0432: "Silk Labs, Inc.",
     0x0433: "Pillsy Inc.",
     0x0434: "Hatch Baby, Inc.",
@@ -1126,7 +1126,7 @@
     0x0450: "Teenage Engineering AB",
     0x0451: "Tunstall Nordic AB",
     0x0452: "Svep Design Center AB",
-    0x0453: "Qorvo Utrecht B.V. formerly GreenPeak Technologies BV",
+    0x0453: "Qorvo Utrecht B.V.",
     0x0454: "Sphinx Electronics GmbH & Co KG",
     0x0455: "Atomation",
     0x0456: "Nemik Consulting Inc",
@@ -1202,7 +1202,7 @@
     0x049C: "DyOcean",
     0x049D: "Uhlmann & Zacher GmbH",
     0x049E: "AND!XOR LLC",
-    0x049F: "tictote AB",
+    0x049F: "Popper Pay AB",
     0x04A0: "Vypin, LLC",
     0x04A1: "PNI Sensor Corporation",
     0x04A2: "ovrEngineered, LLC",
@@ -1260,13 +1260,13 @@
     0x04D6: "LUGLOC LLC",
     0x04D7: "Blincam, Inc.",
     0x04D8: "FUJIFILM Corporation",
-    0x04D9: "RandMcNally",
+    0x04D9: "RM Acquisition LLC",
     0x04DA: "Franceschi Marina snc",
     0x04DB: "Engineered Audio, LLC.",
     0x04DC: "IOTTIVE (OPC) PRIVATE LIMITED",
     0x04DD: "4MOD Technology",
     0x04DE: "Lutron Electronics Co., Inc.",
-    0x04DF: "Emerson",
+    0x04DF: "Emerson Electric Co.",
     0x04E0: "Guardtec, Inc.",
     0x04E1: "REACTEC LIMITED",
     0x04E2: "EllieGrid",
@@ -1290,7 +1290,7 @@
     0x04F4: "ZanCompute Inc.",
     0x04F5: "Pirelli Tyre S.P.A.",
     0x04F6: "McLear Limited",
-    0x04F7: "Shenzhen Huiding Technology Co.,Ltd.",
+    0x04F7: "Shenzhen Goodix Technology Co., Ltd",
     0x04F8: "Convergence Systems Limited",
     0x04F9: "Interactio",
     0x04FA: "Androtec GmbH",
@@ -1304,11 +1304,11 @@
     0x0502: "Specifi-Kali LLC",
     0x0503: "Locoroll, Inc",
     0x0504: "PHYPLUS Inc",
-    0x0505: "Inplay Technologies LLC",
+    0x0505: "InPlay, Inc.",
     0x0506: "Hager",
     0x0507: "Yellowcog",
     0x0508: "Axes System sp. z o. o.",
-    0x0509: "myLIFTER Inc.",
+    0x0509: "Garage Smart, Inc.",
     0x050A: "Shake-on B.V.",
     0x050B: "Vibrissa Inc.",
     0x050C: "OSRAM GmbH",
@@ -1322,7 +1322,7 @@
     0x0514: "FIBRO GmbH",
     0x0515: "RB Controls Co., Ltd.",
     0x0516: "Footmarks",
-    0x0517: "Amtronic Sverige AB (formerly Amcore AB)",
+    0x0517: "Amtronic Sverige AB",
     0x0518: "MAMORIO.inc",
     0x0519: "Tyto Life LLC",
     0x051A: "Leica Camera AG",
@@ -1370,7 +1370,7 @@
     0x0544: "OrthoSensor, Inc.",
     0x0545: "Candy Hoover Group s.r.l",
     0x0546: "Apexar Technologies S.A.",
-    0x0547: "LOGICDATA d.o.o.",
+    0x0547: "LOGICDATA Electronic & Software Entwicklungs GmbH",
     0x0548: "Knick Elektronische Messgeraete GmbH & Co. KG",
     0x0549: "Smart Technologies and Investment Limited",
     0x054A: "Linough Inc.",
@@ -1395,7 +1395,7 @@
     0x055D: "Valve Corporation",
     0x055E: "Hekatron Vertriebs GmbH",
     0x055F: "PROTECH S.A.S. DI GIRARDI ANDREA & C.",
-    0x0560: "Sarita CareTech APS (formerly Sarita CareTech IVS)",
+    0x0560: "Sarita CareTech APS",
     0x0561: "Finder S.p.A.",
     0x0562: "Thalmic Labs Inc.",
     0x0563: "Steinel Vertrieb GmbH",
@@ -1439,9 +1439,9 @@
     0x0589: "Star Technologies",
     0x058A: "START TODAY CO.,LTD.",
     0x058B: "Maxim Integrated Products",
-    0x058C: "MERCK Kommanditgesellschaft auf Aktien",
+    0x058C: "Fracarro Radioindustrie SRL",
     0x058D: "Jungheinrich Aktiengesellschaft",
-    0x058E: "Oculus VR, LLC",
+    0x058E: "Meta Platforms Technologies, LLC",
     0x058F: "HENDON SEMICONDUCTORS PTY LTD",
     0x0590: "Pur3 Ltd",
     0x0591: "Viasat Group S.p.A.",
@@ -1474,7 +1474,7 @@
     0x05AC: "GoerTek Dynaudio Co., Ltd.",
     0x05AD: "INIA",
     0x05AE: "CARMATE MFG.CO.,LTD",
-    0x05AF: "OV LOOP, INC. (formerly ONvocal)",
+    0x05AF: "OV LOOP, INC.",
     0x05B0: "NewTec GmbH",
     0x05B1: "Medallion Instrumentation Systems",
     0x05B2: "CAREL INDUSTRIES S.P.A.",
@@ -1537,7 +1537,7 @@
     0x05EB: "Bayerische Motoren Werke AG",
     0x05EC: "Gycom Svenska AB",
     0x05ED: "Fuji Xerox Co., Ltd",
-    0x05EE: "Glide Inc.",
+    0x05EE: "Wristcam Inc.",
     0x05EF: "SIKOM AS",
     0x05F0: "beken",
     0x05F1: "The Linux Foundation",
@@ -1570,7 +1570,7 @@
     0x060C: "Vuzix Corporation",
     0x060D: "TDK Corporation",
     0x060E: "Blueair AB",
-    0x060F: "Signify Netherlands",
+    0x060F: "Signify Netherlands B.V.",
     0x0610: "ADH GUARDIAN USA LLC",
     0x0611: "Beurer GmbH",
     0x0612: "Playfinity AS",
@@ -1619,7 +1619,7 @@
     0x063D: "Xradio Technology Co.,Ltd.",
     0x063E: "The Indoor Lab, LLC",
     0x063F: "LDL TECHNOLOGY",
-    0x0640: "Parkifi",
+    0x0640: "Dish Network LLC",
     0x0641: "Revenue Collection Systems FRANCE SAS",
     0x0642: "Bluetrum Technology Co.,Ltd",
     0x0643: "makita corporation",
@@ -1709,7 +1709,7 @@
     0x0697: "Stemco Products Inc",
     0x0698: "Wood IT Security, LLC",
     0x0699: "RandomLab SAS",
-    0x069A: "Adero, Inc. (formerly as TrackR, Inc.)",
+    0x069A: "Adero, Inc.",
     0x069B: "Dragonchip Limited",
     0x069C: "Noomi AB",
     0x069D: "Vakaros LLC",
@@ -1719,7 +1719,7 @@
     0x06A1: "Cardo Systems, Ltd",
     0x06A2: "Globalworx GmbH",
     0x06A3: "Nymbus, LLC",
-    0x06A4: "Sanyo Techno Solutions Tottori Co., Ltd.",
+    0x06A4: "LIMNO Co. Ltd.",
     0x06A5: "TEKZITEL PTY LTD",
     0x06A6: "Roambee Corporation",
     0x06A7: "Chipsea Technologies (ShenZhen) Corp.",
@@ -1778,8 +1778,8 @@
     0x06DC: "Industrial Network Controls, LLC",
     0x06DD: "Intellithings Ltd.",
     0x06DE: "Navcast, Inc.",
-    0x06DF: "Hubbell Lighting, Inc.",
-    0x06E0: "Avaya ",
+    0x06DF: "HLI Solutions Inc.",
+    0x06E0: "Avaya Inc.",
     0x06E1: "Milestone AV Technologies LLC",
     0x06E2: "Alango Technologies Ltd",
     0x06E3: "Spinlock Ltd",
@@ -1799,7 +1799,7 @@
     0x06F1: "Shibutani Co., Ltd.",
     0x06F2: "Trapper Data AB",
     0x06F3: "Alfred International Inc.",
-    0x06F4: "Near Field Solutions Ltd",
+    0x06F4: "Touché Technology Ltd",
     0x06F5: "Vigil Technologies Inc.",
     0x06F6: "Vitulo Plus BV",
     0x06F7: "WILKA Schliesstechnik GmbH",
@@ -1832,11 +1832,11 @@
     0x0712: "Bull Group Company Limited",
     0x0713: "Respiri Limited",
     0x0714: "MindPeace Safety LLC",
-    0x0715: "Vgyan Solutions",
+    0x0715: "MBARC LABS Inc",
     0x0716: "Altonics",
     0x0717: "iQsquare BV",
     0x0718: "IDIBAIX enginneering",
-    0x0719: "ECSG",
+    0x0719: "COREIOT PTY LTD",
     0x071A: "REVSMART WEARABLE HK CO LTD",
     0x071B: "Precor",
     0x071C: "F5 Sports, Inc",
@@ -1865,7 +1865,7 @@
     0x0733: "Iguanavation, Inc.",
     0x0734: "DiUS Computing Pty Ltd",
     0x0735: "UpRight Technologies LTD",
-    0x0736: "FrancisFund, LLC",
+    0x0736: "Luna XIO, Inc.",
     0x0737: "LLC Navitek",
     0x0738: "Glass Security Pte Ltd",
     0x0739: "Jiangsu Qinheng Co., Ltd.",
@@ -1948,6 +1948,7 @@
     0x0786: "HLP Controls Pty Limited",
     0x0787: "Pangaea Solution",
     0x0788: "BubblyNet, LLC",
+    0x0789: "PCB Piezotronics, Inc.",
     0x078A: "The Wildflower Foundation",
     0x078B: "Optikam Tech Inc.",
     0x078C: "MINIBREW HOLDING B.V",
@@ -1956,7 +1957,7 @@
     0x078F: "Hanna Instruments, Inc.",
     0x0790: "KOMPAN A/S",
     0x0791: "Scosche Industries, Inc.",
-    0x0792: "Provo Craft",
+    0x0792: "Cricut, Inc.",
     0x0793: "AEV spol. s r.o.",
     0x0794: "The Coca-Cola Company",
     0x0795: "GASTEC CORPORATION",
@@ -2030,17 +2031,17 @@
     0x07D9: "Micro-Design, Inc.",
     0x07DA: "STARLITE Co., Ltd.",
     0x07DB: "Remedee Labs",
-    0x07DC: "ThingOS GmbH",
+    0x07DC: "ThingOS GmbH & Co KG",
     0x07DD: "Linear Circuits",
     0x07DE: "Unlimited Engineering SL",
     0x07DF: "Snap-on Incorporated",
     0x07E0: "Edifier International Limited",
     0x07E1: "Lucie Labs",
     0x07E2: "Alfred Kaercher SE & Co. KG",
-    0x07E3: "Audiowise Technology Inc.",
+    0x07E3: "Airoha Technology Corp.",
     0x07E4: "Geeksme S.L.",
     0x07E5: "Minut, Inc.",
-    0x07E6: "Autogrow Systems Limited",
+    0x07E6: "Waybeyond Limited",
     0x07E7: "Komfort IQ, Inc.",
     0x07E8: "Packetcraft, Inc.",
     0x07E9: "Häfele GmbH & Co KG",
@@ -2051,7 +2052,7 @@
     0x07EE: "KidzTek LLC",
     0x07EF: "Aktiebolaget Sandvik Coromant",
     0x07F0: "e-moola.com Pty Ltd",
-    0x07F1: "GSM Innovations Pty Ltd",
+    0x07F1: "Zimi Innovations Pty Ltd",
     0x07F2: "SERENE GROUP, INC",
     0x07F3: "DIGISINE ENERGYTECH CO. LTD.",
     0x07F4: "MEDIRLAB Orvosbiologiai Fejleszto Korlatolt Felelossegu Tarsasag",
@@ -2107,7 +2108,7 @@
     0x0826: "Hyundai Motor Company",
     0x0827: "Kickmaker",
     0x0828: "Shanghai Suisheng Information Technology Co., Ltd.",
-    0x0829: "HEXAGON",
+    0x0829: "HEXAGON METROLOGY DIVISION ROMER",
     0x082A: "Mitutoyo Corporation",
     0x082B: "shenzhen fitcare electronics Co.,Ltd",
     0x082C: "INGICS TECHNOLOGY CO., LTD.",
@@ -2151,20 +2152,20 @@
     0x0852: "Cognosos, Inc.",
     0x0853: "Pektron Group Limited",
     0x0854: "Tap Sound System",
-    0x0855: "Helios Hockey, Inc.",
+    0x0855: "Helios Sports, Inc.",
     0x0856: "Canopy Growth Corporation",
     0x0857: "Parsyl Inc",
     0x0858: "SOUNDBOKS",
     0x0859: "BlueUp",
     0x085A: "DAKATECH",
-    0x085B: "RICOH ELECTRONIC DEVICES CO., LTD.",
+    0x085B: "Nisshinbo Micro Devices Inc.",
     0x085C: "ACOS CO.,LTD.",
     0x085D: "Guilin Zhishen Information Technology Co.,Ltd.",
     0x085E: "Krog Systems LLC",
     0x085F: "COMPEGPS TEAM,SOCIEDAD LIMITADA",
     0x0860: "Alflex Products B.V.",
     0x0861: "SmartSensor Labs Ltd",
-    0x0862: "SmartDrive Inc.",
+    0x0862: "SmartDrive",
     0x0863: "Yo-tronics Technology Co., Ltd.",
     0x0864: "Rafaelmicro",
     0x0865: "Emergency Lighting Products Limited",
@@ -2179,7 +2180,7 @@
     0x086E: "Vorwerk Elektrowerke GmbH & Co. KG",
     0x086F: "Trackunit A/S",
     0x0870: "Wyze Labs, Inc",
-    0x0871: "Dension Elektronikai Kft. (formerly: Dension Audio Systems Ltd.)",
+    0x0871: "Dension Elektronikai Kft.",
     0x0872: "11 Health & Technologies Limited",
     0x0873: "Innophase Incorporated",
     0x0874: "Treegreen Limited",
@@ -2199,8 +2200,8 @@
     0x0882: "PSYONIC, Inc.",
     0x0883: "Wintersteiger AG",
     0x0884: "Controlid Industria, Comercio de Hardware e Servicos de Tecnologia Ltda",
-    0x0885: "LEVOLOR, INC.",
-    0x0886: "Xsens Technologies B.V.",
+    0x0885: "LEVOLOR INC",
+    0x0886: "Movella Technologies B.V.",
     0x0887: "Hydro-Gear Limited Partnership",
     0x0888: "EnPointe Fencing Pty Ltd",
     0x0889: "XANTHIO",
@@ -2288,7 +2289,7 @@
     0x08DB: "Tertium Technology",
     0x08DC: "SHENZHEN AUKEY E BUSINESS CO., LTD",
     0x08DD: "code-Q",
-    0x08DE: "Tyco Electronics Corporation a TE Connectivity Ltd Company",
+    0x08DE: "TE Connectivity Corporation",
     0x08DF: "IRIS OHYAMA CO.,LTD.",
     0x08E0: "Philia Technology",
     0x08E1: "KOZO KEIKAKU ENGINEERING Inc.",
@@ -2297,14 +2298,14 @@
     0x08E4: "Rashidov ltd",
     0x08E5: "Crowd Connected Ltd",
     0x08E6: "Eneso Tecnologia de Adaptacion S.L.",
-    0x08E7: "Barrot Technology Limited",
+    0x08E7: "Barrot Technology Co.,Ltd.",
     0x08E8: "Naonext",
     0x08E9: "Taiwan Intelligent Home Corp.",
     0x08EA: "COWBELL ENGINEERING CO.,LTD.",
     0x08EB: "Beijing Big Moment Technology Co., Ltd.",
     0x08EC: "Denso Corporation",
     0x08ED: "IMI Hydronic Engineering International SA",
-    0x08EE: "ASKEY",
+    0x08EE: "Askey Computer Corp.",
     0x08EF: "Cumulus Digital Systems, Inc",
     0x08F0: "Joovv, Inc.",
     0x08F1: "The L.S. Starrett Company",
@@ -2389,7 +2390,7 @@
     0x0940: "Hero Workout GmbH",
     0x0941: "Rivian Automotive, LLC",
     0x0942: "TRANSSION HOLDINGS LIMITED",
-    0x0943: "Inovonics Corp.",
+    0x0943: "Reserved",
     0x0944: "Agitron d.o.o.",
     0x0945: "Globe (Jiangsu) Co., Ltd",
     0x0946: "AMC International Alfa Metalcraft Corporation AG",
@@ -2415,7 +2416,7 @@
     0x095A: "Selekt Bilgisayar, lletisim Urunleri lnsaat Sanayi ve Ticaret Limited Sirketi",
     0x095B: "Lismore Instruments Limited",
     0x095C: "LogiLube, LLC",
-    0x095D: "ETC",
+    0x095D: "Electronic Theatre Controls",
     0x095E: "BioEchoNet inc.",
     0x095F: "NUANCE HEARING LTD",
     0x0960: "Sena Technologies Inc.",
@@ -2490,7 +2491,7 @@
     0x09A5: "Security Enhancement Systems, LLC",
     0x09A6: "BEIJING ELECTRIC VEHICLE CO.,LTD",
     0x09A7: "Paybuddy ApS",
-    0x09A8: "KHN Solutions Inc",
+    0x09A8: "KHN Solutions LLC",
     0x09A9: "Nippon Ceramic Co.,Ltd.",
     0x09AA: "PHOTODYNAMIC INCORPORATED",
     0x09AB: "DashLogic, Inc.",
@@ -2618,7 +2619,7 @@
     0x0A25: "Eran Financial Services LLC",
     0x0A26: "Louis Vuitton",
     0x0A27: "AYU DEVICES PRIVATE LIMITED",
-    0x0A28: "NanoFlex",
+    0x0A28: "NanoFlex Power Corporation",
     0x0A29: "Worthcloud Technology Co.,Ltd",
     0x0A2A: "Yamaha Corporation",
     0x0A2B: "PaceBait IVS",
@@ -2697,13 +2698,652 @@
     0x0A74: "MICROSON S.A.",
     0x0A75: "Delta Cycle Corporation",
     0x0A76: "Synaptics Incorporated",
-    0x0A77: "JMD PACIFIC PTE. LTD.",
+    0x0A77: "AXTRO PTE. LTD.",
     0x0A78: "Shenzhen Sunricher Technology Limited",
     0x0A79: "Webasto SE",
     0x0A7A: "Emlid Limited",
     0x0A7B: "UniqAir Oy",
     0x0A7C: "WAFERLOCK",
     0x0A7D: "Freedman Electronics Pty Ltd",
-    0x0A7E: "Keba AG",
+    0x0A7E: "KEBA Handover Automation GmbH",
     0x0A7F: "Intuity Medical",
+    0x0A80: "Cleer Limited",
+    0x0A81: "Universal Biosensors Pty Ltd",
+    0x0A82: "Corsair",
+    0x0A83: "Rivata, Inc.",
+    0x0A84: "Greennote Inc,",
+    0x0A85: "Snowball Technology Co., Ltd.",
+    0x0A86: "ALIZENT International",
+    0x0A87: "Shanghai Smart System Technology Co., Ltd",
+    0x0A88: "PSA Peugeot Citroen",
+    0x0A89: "SES-Imagotag",
+    0x0A8A: "HAINBUCH GMBH SPANNENDE TECHNIK",
+    0x0A8B: "SANlight GmbH",
+    0x0A8C: "DelpSys, s.r.o.",
+    0x0A8D: "JCM TECHNOLOGIES S.A.",
+    0x0A8E: "Perfect Company",
+    0x0A8F: "TOTO LTD.",
+    0x0A90: "Shenzhen Grandsun Electronic Co.,Ltd.",
+    0x0A91: "Monarch International Inc.",
+    0x0A92: "Carestream Dental LLC",
+    0x0A93: "GiPStech S.r.l.",
+    0x0A94: "OOBIK Inc.",
+    0x0A95: "Pamex Inc.",
+    0x0A96: "Lightricity Ltd",
+    0x0A97: "SensTek",
+    0x0A98: "Foil, Inc.",
+    0x0A99: "Shanghai high-flying electronics technology Co.,Ltd",
+    0x0A9A: "TEMKIN ASSOCIATES, LLC",
+    0x0A9B: "Eello LLC",
+    0x0A9C: "Xi'an Fengyu Information Technology Co., Ltd.",
+    0x0A9D: "Canon Finetech Nisca Inc.",
+    0x0A9E: "LifePlus, Inc.",
+    0x0A9F: "ista International GmbH",
+    0x0AA0: "Loy Tec electronics GmbH",
+    0x0AA1: "LINCOGN TECHNOLOGY CO. LIMITED",
+    0x0AA2: "Care Bloom, LLC",
+    0x0AA3: "DIC Corporation",
+    0x0AA4: "FAZEPRO LLC",
+    0x0AA5: "Shenzhen Uascent Technology Co., Ltd",
+    0x0AA6: "Realityworks, inc.",
+    0x0AA7: "Urbanista AB",
+    0x0AA8: "Zencontrol Pty Ltd",
+    0x0AA9: "Spintly, Inc.",
+    0x0AAA: "Computime International Ltd",
+    0x0AAB: "Anhui Listenai Co",
+    0x0AAC: "OSM HK Limited",
+    0x0AAD: "Adevo Consulting AB",
+    0x0AAE: "PS Engineering, Inc.",
+    0x0AAF: "AIAIAI ApS",
+    0x0AB0: "Visiontronic s.r.o.",
+    0x0AB1: "InVue Security Products Inc",
+    0x0AB2: "TouchTronics, Inc.",
+    0x0AB3: "INNER RANGE PTY. LTD.",
+    0x0AB4: "Ellenby Technologies, Inc.",
+    0x0AB5: "Elstat Electronics Ltd.",
+    0x0AB6: "Xenter, Inc.",
+    0x0AB7: "LogTag North America Inc.",
+    0x0AB8: "Sens.ai Incorporated",
+    0x0AB9: "STL",
+    0x0ABA: "Open Bionics Ltd.",
+    0x0ABB: "R-DAS, s.r.o.",
+    0x0ABC: "KCCS Mobile Engineering Co., Ltd.",
+    0x0ABD: "Inventas AS",
+    0x0ABE: "Robkoo Information & Technologies Co., Ltd.",
+    0x0ABF: "PAUL HARTMANN AG",
+    0x0AC0: "Omni-ID USA, INC.",
+    0x0AC1: "Shenzhen Jingxun Technology Co., Ltd.",
+    0x0AC2: "RealMega Microelectronics technology (Shanghai) Co. Ltd.",
+    0x0AC3: "Kenzen, Inc.",
+    0x0AC4: "CODIUM",
+    0x0AC5: "Flexoptix GmbH",
+    0x0AC6: "Barnes Group Inc.",
+    0x0AC7: "Chengdu Aich Technology Co.,Ltd",
+    0x0AC8: "Keepin Co., Ltd.",
+    0x0AC9: "Swedlock AB",
+    0x0ACA: "Shenzhen CoolKit Technology Co., Ltd",
+    0x0ACB: "ise Individuelle Software und Elektronik GmbH",
+    0x0ACC: "Nuvoton",
+    0x0ACD: "Visuallex Sport International Limited",
+    0x0ACE: "KOBATA GAUGE MFG. CO., LTD.",
+    0x0ACF: "CACI Technologies",
+    0x0AD0: "Nordic Strong ApS",
+    0x0AD1: "EAGLE KINGDOM TECHNOLOGIES LIMITED",
+    0x0AD2: "Lautsprecher Teufel GmbH",
+    0x0AD3: "SSV Software Systems GmbH",
+    0x0AD4: "Zhuhai Pantum Electronisc Co., Ltd",
+    0x0AD5: "Streamit B.V.",
+    0x0AD6: "nymea GmbH",
+    0x0AD7: "AL-KO Geraete GmbH",
+    0x0AD8: "Franz Kaldewei GmbH&Co KG",
+    0x0AD9: "Shenzhen Aimore. Co.,Ltd",
+    0x0ADA: "Codefabrik GmbH",
+    0x0ADB: "Reelables, Inc.",
+    0x0ADC: "Duravit AG",
+    0x0ADD: "Boss Audio",
+    0x0ADE: "Vocera Communications, Inc.",
+    0x0ADF: "Douglas Dynamics L.L.C.",
+    0x0AE0: "Viceroy Devices Corporation",
+    0x0AE1: "ChengDu ForThink Technology Co., Ltd.",
+    0x0AE2: "IMATRIX SYSTEMS, INC.",
+    0x0AE3: "GlobalMed",
+    0x0AE4: "DALI Alliance",
+    0x0AE5: "unu GmbH",
+    0x0AE6: "Hexology",
+    0x0AE7: "Sunplus Technology Co., Ltd.",
+    0x0AE8: "LEVEL, s.r.o.",
+    0x0AE9: "FLIR Systems AB",
+    0x0AEA: "Borda Technology",
+    0x0AEB: "Square, Inc.",
+    0x0AEC: "FUTEK ADVANCED SENSOR TECHNOLOGY, INC",
+    0x0AED: "Saxonar GmbH",
+    0x0AEE: "Velentium, LLC",
+    0x0AEF: "GLP German Light Products GmbH",
+    0x0AF0: "Leupold & Stevens, Inc.",
+    0x0AF1: "CRADERS,CO.,LTD",
+    0x0AF2: "Shanghai All Link Microelectronics Co.,Ltd",
+    0x0AF3: "701x Inc.",
+    0x0AF4: "Radioworks Microelectronics PTY LTD",
+    0x0AF5: "Unitech Electronic Inc.",
+    0x0AF6: "AMETEK, Inc.",
+    0x0AF7: "Irdeto",
+    0x0AF8: "First Design System Inc.",
+    0x0AF9: "Unisto AG",
+    0x0AFA: "Chengdu Ambit Technology Co., Ltd.",
+    0x0AFB: "SMT ELEKTRONIK GmbH",
+    0x0AFC: "Cerebrum Sensor Technologies Inc.",
+    0x0AFD: "Weber Sensors, LLC",
+    0x0AFE: "Earda Technologies Co.,Ltd",
+    0x0AFF: "FUSEAWARE LIMITED",
+    0x0B00: "Flaircomm Microelectronics Inc.",
+    0x0B01: "RESIDEO TECHNOLOGIES, INC.",
+    0x0B02: "IORA Technology Development Ltd. Sti.",
+    0x0B03: "Precision Triathlon Systems Limited",
+    0x0B04: "I-PERCUT",
+    0x0B05: "Marquardt GmbH",
+    0x0B06: "FAZUA GmbH",
+    0x0B07: "Workaround Gmbh",
+    0x0B08: "Shenzhen Qianfenyi Intelligent Technology Co., LTD",
+    0x0B09: "soonisys",
+    0x0B0A: "Belun Technology Company Limited",
+    0x0B0B: "Sanistaal A/S",
+    0x0B0C: "BluPeak",
+    0x0B0D: "SANYO DENKO Co.,Ltd.",
+    0x0B0E: "Honda Lock Mfg. Co.,Ltd.",
+    0x0B0F: "B.E.A. S.A.",
+    0x0B10: "Alfa Laval Corporate AB",
+    0x0B11: "ThermoWorks, Inc.",
+    0x0B12: "ToughBuilt Industries LLC",
+    0x0B13: "IOTOOLS",
+    0x0B14: "Olumee",
+    0x0B15: "NAOS JAPAN K.K.",
+    0x0B16: "Guard RFID Solutions Inc.",
+    0x0B17: "SIG SAUER, INC.",
+    0x0B18: "DECATHLON SE",
+    0x0B19: "WBS PROJECT H PTY LTD",
+    0x0B1A: "Roca Sanitario, S.A.",
+    0x0B1B: "Enerpac Tool Group Corp.",
+    0x0B1C: "Nanoleq AG",
+    0x0B1D: "Accelerated Systems",
+    0x0B1E: "PB INC.",
+    0x0B1F: "Beijing ESWIN Computing Technology Co., Ltd.",
+    0x0B20: "TKH Security B.V.",
+    0x0B21: "ams AG",
+    0x0B22: "Hygiene IQ, LLC.",
+    0x0B23: "iRhythm Technologies, Inc.",
+    0x0B24: "BeiJing ZiJie TiaoDong KeJi Co.,Ltd.",
+    0x0B25: "NIBROTECH LTD",
+    0x0B26: "Baracoda Daily Healthtech.",
+    0x0B27: "Lumi United Technology Co., Ltd",
+    0x0B28: "CHACON",
+    0x0B29: "Tech-Venom Entertainment Private Limited",
+    0x0B2A: "ACL Airshop B.V.",
+    0x0B2B: "MAINBOT",
+    0x0B2C: "ILLUMAGEAR, Inc.",
+    0x0B2D: "REDARC ELECTRONICS PTY LTD",
+    0x0B2E: "MOCA System Inc.",
+    0x0B2F: "Duke Manufacturing Co",
+    0x0B30: "ART SPA",
+    0x0B31: "Silver Wolf Vehicles Inc.",
+    0x0B32: "Hala Systems, Inc.",
+    0x0B33: "ARMATURA LLC",
+    0x0B34: "CONZUMEX INDUSTRIES PRIVATE LIMITED",
+    0x0B35: "BH SENS",
+    0x0B36: "SINTEF",
+    0x0B37: "Omnivoltaic Energy Solutions Limited Company",
+    0x0B38: "WISYCOM S.R.L.",
+    0x0B39: "Red 100 Lighting Co., ltd.",
+    0x0B3A: "Impact Biosystems, Inc.",
+    0x0B3B: "AIC semiconductor (Shanghai) Co., Ltd.",
+    0x0B3C: "Dodge Industrial, Inc.",
+    0x0B3D: "REALTIMEID AS",
+    0x0B3E: "ISEO Serrature S.p.a.",
+    0x0B3F: "MindRhythm, Inc.",
+    0x0B40: "Havells India Limited",
+    0x0B41: "Sentrax GmbH",
+    0x0B42: "TSI",
+    0x0B43: "INCITAT ENVIRONNEMENT",
+    0x0B44: "nFore Technology Co., Ltd.",
+    0x0B45: "Electronic Sensors, Inc.",
+    0x0B46: "Bird Rides, Inc.",
+    0x0B47: "Gentex Corporation",
+    0x0B48: "NIO USA, Inc.",
+    0x0B49: "SkyHawke Technologies",
+    0x0B4A: "Nomono AS",
+    0x0B4B: "EMS Integrators, LLC",
+    0x0B4C: "BiosBob.Biz",
+    0x0B4D: "Adam Hall GmbH",
+    0x0B4E: "ICP Systems B.V.",
+    0x0B4F: "Breezi.io, Inc.",
+    0x0B50: "Mesh Systems LLC",
+    0x0B51: "FUN FACTORY GmbH",
+    0x0B52: "ZIIP Inc",
+    0x0B53: "SHENZHEN KAADAS INTELLIGENT TECHNOLOGY CO.,Ltd",
+    0x0B54: "Emotion Fitness GmbH & Co. KG",
+    0x0B55: "H G M Automotive Electronics, Inc.",
+    0x0B56: "BORA - Vertriebs GmbH & Co KG",
+    0x0B57: "CONVERTRONIX TECHNOLOGIES AND SERVICES LLP",
+    0x0B58: "TOKAI-DENSHI INC",
+    0x0B59: "Versa Group B.V.",
+    0x0B5A: "H.P. Shelby Manufacturing, LLC.",
+    0x0B5B: "Shenzhen ImagineVision Technology Limited",
+    0x0B5C: "Exponential Power, Inc.",
+    0x0B5D: "Fujian Newland Auto-ID Tech. Co., Ltd.",
+    0x0B5E: "CELLCONTROL, INC.",
+    0x0B5F: "Rivieh, Inc.",
+    0x0B60: "RATOC Systems, Inc.",
+    0x0B61: "Sentek Pty Ltd",
+    0x0B62: "NOVEA ENERGIES",
+    0x0B63: "Innolux Corporation",
+    0x0B64: "NingBo klite Electric Manufacture Co.,LTD",
+    0x0B65: "The Apache Software Foundation",
+    0x0B66: "MITSUBISHI ELECTRIC AUTOMATION (THAILAND) COMPANY LIMITED",
+    0x0B67: "CleanSpace Technology Pty Ltd",
+    0x0B68: "Quha oy",
+    0x0B69: "Addaday",
+    0x0B6A: "Dymo",
+    0x0B6B: "Samsara Networks, Inc",
+    0x0B6C: "Sensitech, Inc.",
+    0x0B6D: "SOLUM CO., LTD",
+    0x0B6E: "React Mobile",
+    0x0B6F: "Shenzhen Malide Technology Co.,Ltd",
+    0x0B70: "JDRF Electromag Engineering Inc",
+    0x0B71: "lilbit ODM AS",
+    0x0B72: "Geeknet, Inc.",
+    0x0B73: "HARADA INDUSTRY CO., LTD.",
+    0x0B74: "BQN",
+    0x0B75: "Triple W Japan Inc.",
+    0x0B76: "MAX-co., ltd",
+    0x0B77: "Aixlink(Chengdu) Co., Ltd.",
+    0x0B78: "FIELD DESIGN INC.",
+    0x0B79: "Sankyo Air Tech Co.,Ltd.",
+    0x0B7A: "Shenzhen KTC Technology Co.,Ltd.",
+    0x0B7B: "Hardcoder Oy",
+    0x0B7C: "Scangrip A/S",
+    0x0B7D: "FoundersLane GmbH",
+    0x0B7E: "Offcode Oy",
+    0x0B7F: "ICU tech GmbH",
+    0x0B80: "AXELIFE",
+    0x0B81: "SCM Group",
+    0x0B82: "Mammut Sports Group AG",
+    0x0B83: "Taiga Motors Inc.",
+    0x0B84: "Presidio Medical, Inc.",
+    0x0B85: "VIMANA TECH PTY LTD",
+    0x0B86: "Trek Bicycle",
+    0x0B87: "Ampetronic Ltd",
+    0x0B88: "Muguang (Guangdong) Intelligent Lighting Technology Co., Ltd",
+    0x0B89: "Rotronic AG",
+    0x0B8A: "Seiko Instruments Inc.",
+    0x0B8B: "American Technology Components, Incorporated",
+    0x0B8C: "MOTREX",
+    0x0B8D: "Pertech Industries Inc",
+    0x0B8E: "Gentle Energy Corp.",
+    0x0B8F: "Senscomm Semiconductor Co., Ltd.",
+    0x0B90: "Ineos Automotive Limited",
+    0x0B91: "Alfen ICU B.V.",
+    0x0B92: "Citisend Solutions, SL",
+    0x0B93: "Hangzhou BroadLink Technology Co., Ltd.",
+    0x0B94: "Dreem SAS",
+    0x0B95: "Netwake GmbH",
+    0x0B96: "Telecom Design",
+    0x0B97: "SILVER TREE LABS, INC.",
+    0x0B98: "Gymstory B.V.",
+    0x0B99: "The Goodyear Tire & Rubber Company",
+    0x0B9A: "Beijing Wisepool Infinite Intelligence Technology Co.,Ltd",
+    0x0B9B: "GISMAN",
+    0x0B9C: "Komatsu Ltd.",
+    0x0B9D: "Sensoria Holdings LTD",
+    0x0B9E: "Audio Partnership Plc",
+    0x0B9F: "Group Lotus Limited",
+    0x0BA0: "Data Sciences International",
+    0x0BA1: "Bunn-O-Matic Corporation",
+    0x0BA2: "TireCheck GmbH",
+    0x0BA3: "Sonova Consumer Hearing GmbH",
+    0x0BA4: "Vervent Audio Group",
+    0x0BA5: "SONICOS ENTERPRISES, LLC",
+    0x0BA6: "Nissan Motor Co., Ltd.",
+    0x0BA7: "hearX Group (Pty) Ltd",
+    0x0BA8: "GLOWFORGE INC.",
+    0x0BA9: "Allterco Robotics ltd",
+    0x0BAA: "Infinitegra, Inc.",
+    0x0BAB: "Grandex International Corporation",
+    0x0BAC: "Machfu Inc.",
+    0x0BAD: "Roambotics, Inc.",
+    0x0BAE: "Soma Labs LLC",
+    0x0BAF: "NITTO KOGYO CORPORATION",
+    0x0BB0: "Ecolab Inc.",
+    0x0BB1: "Beijing ranxin intelligence technology Co.,LTD",
+    0x0BB2: "Fjorden Electra AS",
+    0x0BB3: "Flender GmbH",
+    0x0BB4: "New Cosmos USA, Inc.",
+    0x0BB5: "Xirgo Technologies, LLC",
+    0x0BB6: "Build With Robots Inc.",
+    0x0BB7: "IONA Tech LLC",
+    0x0BB8: "INNOVAG PTY. LTD.",
+    0x0BB9: "SaluStim Group Oy",
+    0x0BBA: "Huso, INC",
+    0x0BBB: "SWISSINNO SOLUTIONS AG",
+    0x0BBC: "T2REALITY SOLUTIONS PRIVATE LIMITED",
+    0x0BBD: "ETHEORY PTY LTD",
+    0x0BBE: "SAAB Aktiebolag",
+    0x0BBF: "HIMSA II K/S",
+    0x0BC0: "READY FOR SKY LLP",
+    0x0BC1: "Miele & Cie. KG",
+    0x0BC2: "EntWick Co.",
+    0x0BC3: "MCOT INC.",
+    0x0BC4: "TECHTICS ENGINEERING B.V.",
+    0x0BC5: "Aperia Technologies, Inc.",
+    0x0BC6: "TCL COMMUNICATION EQUIPMENT CO.,LTD.",
+    0x0BC7: "Signtle Inc.",
+    0x0BC8: "OTF Distribution, LLC",
+    0x0BC9: "Neuvatek Inc.",
+    0x0BCA: "Perimeter Technologies, Inc.",
+    0x0BCB: "Divesoft s.r.o.",
+    0x0BCC: "Sylvac sa",
+    0x0BCD: "Amiko srl",
+    0x0BCE: "Neurosity, Inc.",
+    0x0BCF: "LL Tec Group LLC",
+    0x0BD0: "Durag GmbH",
+    0x0BD1: "Hubei Yuan Times Technology Co., Ltd.",
+    0x0BD2: "IDEC",
+    0x0BD3: "Procon Analytics, LLC",
+    0x0BD4: "ndd Medizintechnik AG",
+    0x0BD5: "Super B Lithium Power B.V.",
+    0x0BD6: "Shenzhen Injoinic Technology Co., Ltd.",
+    0x0BD7: "VINFAST TRADING AND PRODUCTION JOINT STOCK COMPANY",
+    0x0BD8: "PURA SCENTS, INC.",
+    0x0BD9: "Elics Basis Ltd.",
+    0x0BDA: "Aardex Ltd.",
+    0x0BDB: "CHAR-BROIL, LLC",
+    0x0BDC: "Ledworks S.r.l.",
+    0x0BDD: "Coroflo Limited",
+    0x0BDE: "Yale",
+    0x0BDF: "WINKEY ENTERPRISE (HONG KONG) LIMITED",
+    0x0BE0: "Koizumi Lighting Technology corp.",
+    0x0BE1: "Back40 Precision",
+    0x0BE2: "OTC engineering",
+    0x0BE3: "Comtel Systems Ltd.",
+    0x0BE4: "Deepfield Connect GmbH",
+    0x0BE5: "ZWILLING J.A. Henckels Aktiengesellschaft",
+    0x0BE6: "Puratap Pty Ltd",
+    0x0BE7: "Fresnel Technologies, Inc.",
+    0x0BE8: "Sensormate AG",
+    0x0BE9: "Shindengen Electric Manufacturing Co., Ltd.",
+    0x0BEA: "Twenty Five Seven, prodaja in storitve, d.o.o.",
+    0x0BEB: "Luna Health, Inc.",
+    0x0BEC: "Miracle-Ear, Inc.",
+    0x0BED: "CORAL-TAIYI Co. Ltd.",
+    0x0BEE: "LINKSYS USA, INC.",
+    0x0BEF: "Safetytest GmbH",
+    0x0BF0: "KIDO SPORTS CO., LTD.",
+    0x0BF1: "Site IQ LLC",
+    0x0BF2: "Angel Medical Systems, Inc.",
+    0x0BF3: "PONE BIOMETRICS AS",
+    0x0BF4: "ER Lab LLC",
+    0x0BF5: "T5 tek, Inc.",
+    0x0BF6: "greenTEG AG",
+    0x0BF7: "Wacker Neuson SE",
+    0x0BF8: "Innovacionnye Resheniya",
+    0x0BF9: "Alio, Inc",
+    0x0BFA: "CleanBands Systems Ltd.",
+    0x0BFB: "Dodam Enersys Co., Ltd",
+    0x0BFC: "T+A elektroakustik GmbH & Co.KG",
+    0x0BFD: "Esmé Solutions",
+    0x0BFE: "Media-Cartec GmbH",
+    0x0BFF: "Ratio Electric BV",
+    0x0C00: "MQA Limited",
+    0x0C01: "NEOWRK SISTEMAS INTELIGENTES S.A.",
+    0x0C02: "Loomanet, Inc.",
+    0x0C03: "Puff Corp",
+    0x0C04: "Happy Health, Inc.",
+    0x0C05: "Montage Connect, Inc.",
+    0x0C06: "LED Smart Inc.",
+    0x0C07: "CONSTRUKTS, INC.",
+    0x0C08: "limited liability company \"Red\"",
+    0x0C09: "Senic Inc.",
+    0x0C0A: "Automated Pet Care Products, LLC",
+    0x0C0B: "aconno GmbH",
+    0x0C0C: "Mendeltron, Inc.",
+    0x0C0D: "Mereltron bv",
+    0x0C0E: "ALEX DENKO CO.,LTD.",
+    0x0C0F: "AETERLINK",
+    0x0C10: "Cosmed s.r.l.",
+    0x0C11: "Gordon Murray Design Limited",
+    0x0C12: "IoSA",
+    0x0C13: "Scandinavian Health Limited",
+    0x0C14: "Fasetto, Inc.",
+    0x0C15: "Geva Sol B.V.",
+    0x0C16: "TYKEE PTY. LTD.",
+    0x0C17: "SomnoMed Limited",
+    0x0C18: "CORROHM",
+    0x0C19: "Arlo Technologies, Inc.",
+    0x0C1A: "Catapult Group International Ltd",
+    0x0C1B: "Rockchip Electronics Co., Ltd.",
+    0x0C1C: "GEMU",
+    0x0C1D: "OFF Line Japan Co., Ltd.",
+    0x0C1E: "EC sense co., Ltd",
+    0x0C1F: "LVI Co.",
+    0x0C20: "COMELIT GROUP S.P.A.",
+    0x0C21: "Foshan Viomi Electrical Technology Co., Ltd",
+    0x0C22: "Glamo Inc.",
+    0x0C23: "KEYTEC,Inc.",
+    0x0C24: "SMARTD TECHNOLOGIES INC.",
+    0x0C25: "JURA Elektroapparate AG",
+    0x0C26: "Performance Electronics, Ltd.",
+    0x0C27: "Pal Electronics",
+    0x0C28: "Embecta Corp.",
+    0x0C29: "DENSO AIRCOOL CORPORATION",
+    0x0C2A: "Caresix Inc.",
+    0x0C2B: "GigaDevice Semiconductor Inc.",
+    0x0C2C: "Zeku Technology (Shanghai) Corp., Ltd.",
+    0x0C2D: "OTF Product Sourcing, LLC",
+    0x0C2E: "Easee AS",
+    0x0C2F: "BEEHERO, INC.",
+    0x0C30: "McIntosh Group Inc",
+    0x0C31: "KINDOO LLP",
+    0x0C32: "Xian Yisuobao Electronic Technology Co., Ltd.",
+    0x0C33: "Exeger Operations AB",
+    0x0C34: "BYD Company Limited",
+    0x0C35: "Thermokon-Sensortechnik GmbH",
+    0x0C36: "Cosmicnode BV",
+    0x0C37: "SignalQuest, LLC",
+    0x0C38: "Noritz Corporation.",
+    0x0C39: "TIGER CORPORATION",
+    0x0C3A: "Equinosis, LLC",
+    0x0C3B: "ORB Innovations Ltd",
+    0x0C3C: "Classified Cycling",
+    0x0C3D: "Wrmth Corp.",
+    0x0C3E: "BELLDESIGN Inc.",
+    0x0C3F: "Stinger Equipment, Inc.",
+    0x0C40: "HORIBA, Ltd.",
+    0x0C41: "Control Solutions LLC",
+    0x0C42: "Heath Consultants Inc.",
+    0x0C43: "Berlinger & Co. AG",
+    0x0C44: "ONCELABS LLC",
+    0x0C45: "Brose Verwaltung SE, Bamberg",
+    0x0C46: "Granwin IoT Technology (Guangzhou) Co.,Ltd",
+    0x0C47: "Epsilon Electronics,lnc",
+    0x0C48: "VALEO MANAGEMENT SERVICES",
+    0x0C49: "twopounds gmbh",
+    0x0C4A: "atSpiro ApS",
+    0x0C4B: "ADTRAN, Inc.",
+    0x0C4C: "Orpyx Medical Technologies Inc.",
+    0x0C4D: "Seekwave Technology Co.,ltd.",
+    0x0C4E: "Tactile Engineering, Inc.",
+    0x0C4F: "SharkNinja Operating LLC",
+    0x0C50: "Imostar Technologies Inc.",
+    0x0C51: "INNOVA S.R.L.",
+    0x0C52: "ESCEA LIMITED",
+    0x0C53: "Taco, Inc.",
+    0x0C54: "HiViz Lighting, Inc.",
+    0x0C55: "Zintouch B.V.",
+    0x0C56: "Rheem Sales Company, Inc.",
+    0x0C57: "UNEEG medical A/S",
+    0x0C58: "Hykso Inc.",
+    0x0C59: "CYBERDYNE Inc.",
+    0x0C5A: "Lockswitch Sdn Bhd",
+    0x0C5B: "Alban Giacomo S.P.A.",
+    0x0C5C: "MGM WIRELESSS HOLDINGS PTY LTD",
+    0x0C5D: "StepUp Solutions ApS",
+    0x0C5E: "BlueID GmbH",
+    0x0C5F: "Nanjing Linkpower Microelectronics Co.,Ltd",
+    0x0C60: "KEBA Energy Automation GmbH",
+    0x0C61: "NNOXX, Inc",
+    0x0C62: "Phiaton Corporation",
+    0x0C63: "phg Peter Hengstler GmbH + Co. KG",
+    0x0C64: "dormakaba Holding AG",
+    0x0C65: "WAKO CO,.LTD",
+    0x0C66: "DEN Smart Home B.V.",
+    0x0C67: "TRACKTING S.R.L.",
+    0x0C68: "Emerja Corporation",
+    0x0C69: "BLITZ electric motors. LTD",
+    0x0C6A: "CONSORCIO TRUST CONTROL - NETTEL",
+    0x0C6B: "GILSON SAS",
+    0x0C6C: "SNIFF LOGIC LTD",
+    0x0C6D: "Fidure Corp.",
+    0x0C6E: "Sensa LLC",
+    0x0C6F: "Parakey AB",
+    0x0C70: "SCARAB SOLUTIONS LTD",
+    0x0C71: "BitGreen Technolabz (OPC) Private Limited",
+    0x0C72: "StreetCar ORV, LLC",
+    0x0C73: "Truma Gerätetechnik GmbH & Co. KG",
+    0x0C74: "yupiteru",
+    0x0C75: "Embedded Engineering Solutions LLC",
+    0x0C76: "Shenzhen Gwell Times Technology Co. , Ltd",
+    0x0C77: "TEAC Corporation",
+    0x0C78: "CHARGTRON IOT PRIVATE LIMITED",
+    0x0C79: "Zhuhai Smartlink Technology Co., Ltd",
+    0x0C7A: "Triductor Technology (Suzhou), Inc.",
+    0x0C7B: "PT SADAMAYA GRAHA TEKNOLOGI",
+    0x0C7C: "Mopeka Products LLC",
+    0x0C7D: "3ALogics, Inc.",
+    0x0C7E: "BOOMING OF THINGS",
+    0x0C7F: "Rochester Sensors, LLC",
+    0x0C80: "CARDIOID - TECHNOLOGIES, LDA",
+    0x0C81: "Carrier Corporation",
+    0x0C82: "NACON",
+    0x0C83: "Watchdog Systems LLC",
+    0x0C84: "MAXON INDUSTRIES, INC.",
+    0x0C85: "Amlogic, Inc.",
+    0x0C86: "Qingdao Eastsoft Communication Technology Co.,Ltd",
+    0x0C87: "Weltek Technologies Company Limited",
+    0x0C88: "Nextivity Inc.",
+    0x0C89: "AGZZX OPTOELECTRONICS TECHNOLOGY CO., LTD",
+    0x0C8A: "ARTISTIC&CO.GLOBAL Ltd.",
+    0x0C8B: "Heavys Inc",
+    0x0C8C: "T-Mobile USA",
+    0x0C8D: "tonies GmbH",
+    0x0C8E: "Technocon Engineering Ltd.",
+    0x0C8F: "Radar Automobile Sales(Shandong)Co.,Ltd.",
+    0x0C90: "WESCO AG",
+    0x0C91: "Yashu Systems",
+    0x0C92: "Kesseböhmer Ergonomietechnik GmbH",
+    0x0C93: "Movesense Oy",
+    0x0C94: "Baxter Healthcare Corporation",
+    0x0C95: "Gemstone Lights Canada Ltd.",
+    0x0C96: "H+B Hightech GmbH",
+    0x0C97: "Deako",
+    0x0C98: "MiX Telematics International (PTY) LTD",
+    0x0C99: "Vire Health Oy",
+    0x0C9A: "ALF Inc.",
+    0x0C9B: "NTT sonority, Inc.",
+    0x0C9C: "Sunstone-RTLS Ipari Szolgaltato Korlatolt Felelossegu Tarsasag",
+    0x0C9D: "Ribbiot, INC.",
+    0x0C9E: "ECCEL CORPORATION SAS",
+    0x0C9F: "Dragonfly Energy Corp.",
+    0x0CA0: "BIGBEN",
+    0x0CA1: "YAMAHA MOTOR CO.,LTD.",
+    0x0CA2: "XSENSE LTD",
+    0x0CA3: "MAQUET GmbH",
+    0x0CA4: "MITSUBISHI ELECTRIC LIGHTING CO, LTD",
+    0x0CA5: "Princess Cruise Lines, Ltd.",
+    0x0CA6: "Megger Ltd",
+    0x0CA7: "Verve InfoTec Pty Ltd",
+    0x0CA8: "Sonas, Inc.",
+    0x0CA9: "Mievo Technologies Private Limited",
+    0x0CAA: "Shenzhen Poseidon Network Technology Co., Ltd",
+    0x0CAB: "HERUTU ELECTRONICS CORPORATION",
+    0x0CAC: "Shenzhen Shokz Co.,Ltd.",
+    0x0CAD: "Shenzhen Openhearing Tech CO., LTD .",
+    0x0CAE: "Evident Corporation",
+    0x0CAF: "NEURINNOV",
+    0x0CB0: "SwipeSense, Inc.",
+    0x0CB1: "RF Creations",
+    0x0CB2: "SHINKAWA Sensor Technology, Inc.",
+    0x0CB3: "janova GmbH",
+    0x0CB4: "Eberspaecher Climate Control Systems GmbH",
+    0x0CB5: "Racketry, d. o. o.",
+    0x0CB6: "THE EELECTRIC MACARON LLC",
+    0x0CB7: "Cucumber Lighting Controls Limited",
+    0x0CB8: "Shanghai Proxy Network Technology Co., Ltd.",
+    0x0CB9: "seca GmbH & Co. KG",
+    0x0CBA: "Ameso Tech (OPC) Private Limited",
+    0x0CBB: "Emlid Tech Kft.",
+    0x0CBC: "TROX GmbH",
+    0x0CBD: "Pricer AB",
+    0x0CBF: "Forward Thinking Systems LLC.",
+    0x0CC0: "Garnet Instruments Ltd.",
+    0x0CC1: "CLEIO Inc.",
+    0x0CC2: "Anker Innovations Limited",
+    0x0CC3: "HMD Global Oy",
+    0x0CC4: "ABUS August Bremicker Soehne Kommanditgesellschaft",
+    0x0CC5: "Open Road Solutions, Inc.",
+    0x0CC6: "Serial Technology Corporation",
+    0x0CC7: "SB C&S Corp.",
+    0x0CC8: "TrikThom",
+    0x0CC9: "Innocent Technology Co., Ltd.",
+    0x0CCA: "Cyclops Marine Ltd",
+    0x0CCB: "NOTHING TECHNOLOGY LIMITED",
+    0x0CCC: "Kord Defence Pty Ltd",
+    0x0CCD: "YanFeng Visteon(Chongqing) Automotive Electronic Co.,Ltd",
+    0x0CCE: "SENOSPACE LLC",
+    0x0CCF: "Shenzhen CESI Information Technology Co., Ltd.",
+    0x0CD0: "MooreSilicon Semiconductor Technology (Shanghai) Co., LTD.",
+    0x0CD1: "Imagine Marketing Limited",
+    0x0CD2: "EQOM SSC B.V.",
+    0x0CD3: "TechSwipe",
+    0x0CD4: "Shenzhen Zhiduotun IoT Technology Co., Ltd",
+    0x0CD5: "Numa Products, LLC",
+    0x0CD6: "HHO (Hangzhou) Digital Technology Co., Ltd.",
+    0x0CD7: "Maztech Industries, LLC",
+    0x0CD8: "SIA Mesh Group",
+    0x0CD9: "Minami acoustics Limited",
+    0x0CDA: "Wolf Steel ltd",
+    0x0CDB: "Circus World Displays Limited",
+    0x0CDC: "Ypsomed AG",
+    0x0CDD: "Alif Semiconductor, Inc.",
+    0x0CDE: "RESPONSE TECHNOLOGIES, LTD.",
+    0x0CDF: "SHENZHEN CHENYUN ELECTRONICS  CO., LTD",
+    0x0CE0: "VODALOGIC PTY LTD",
+    0x0CE1: "Regal Beloit America, Inc.",
+    0x0CE2: "CORVENT MEDICAL, INC.",
+    0x0CE3: "Taiwan Fuhsing",
+    0x0CE4: "Off-Highway Powertrain Services Germany GmbH",
+    0x0CE5: "Amina Distribution AS",
+    0x0CE6: "McWong International, Inc.",
+    0x0CE7: "TAG HEUER SA",
+    0x0CE8: "Dongguan Yougo Electronics Co.,Ltd.",
+    0x0CE9: "PEAG, LLC dba JLab Audio",
+    0x0CEA: "HAYWARD INDUSTRIES, INC.",
+    0x0CEB: "Shenzhen Tingting Technology Co. LTD",
+    0x0CEC: "Pacific Coast Fishery Services (2003) Inc.",
+    0x0CED: "CV. NURI TEKNIK",
+    0x0CEE: "MadgeTech, Inc",
+    0x0CEF: "POGS B.V.",
+    0x0CF0: "THOTAKA TEKHNOLOGIES INDIA PRIVATE LIMITED",
+    0x0CF1: "Midmark",
+    0x0CF2: "BestSens AG",
+    0x0CF3: "Radio Sound",
+    0x0CF4: "SOLUX PTY LTD",
+    0x0CF5: "BOS Balance of Storage Systems AG",
+    0x0CF6: "OJ Electronics A/S",
+    0x0CF7: "TVS Motor Company Ltd.",
+    0x0CF8: "core sensing GmbH",
+    0x0CF9: "Tamblue Oy",
+    0x0CFA: "Protect Animals With Satellites LLC",
+    0x0CFB: "Tyromotion GmbH",
+    0x0CFC: "ElectronX design",
+    0x0CFD: "Wuhan Woncan Construction Technologies Co., Ltd.",
+    0x0CFE: "Thule Group AB",
+    0x0CFF: "Ergodriven Inc",
 }
diff --git a/bumble/device.py b/bumble/device.py
index b01dc58..7f11012 100644
--- a/bumble/device.py
+++ b/bumble/device.py
@@ -33,6 +33,8 @@
     Tuple,
     Type,
     Union,
+    cast,
+    overload,
     TYPE_CHECKING,
 )
 
@@ -151,6 +153,7 @@
     CompositeEventEmitter,
     setup_event_forwarding,
     composite_listener,
+    deprecated,
 )
 from .keys import (
     KeyStore,
@@ -670,9 +673,7 @@
     def send_l2cap_pdu(self, cid: int, pdu: bytes) -> None:
         self.device.send_l2cap_pdu(self.handle, cid, pdu)
 
-    def create_l2cap_connector(self, psm):
-        return self.device.create_l2cap_connector(self, psm)
-
+    @deprecated("Please use create_l2cap_channel()")
     async def open_l2cap_channel(
         self,
         psm,
@@ -682,6 +683,23 @@
     ):
         return await self.device.open_l2cap_channel(self, psm, max_credits, mtu, mps)
 
+    @overload
+    async def create_l2cap_channel(
+        self, spec: l2cap.ClassicChannelSpec
+    ) -> l2cap.ClassicChannel:
+        ...
+
+    @overload
+    async def create_l2cap_channel(
+        self, spec: l2cap.LeCreditBasedChannelSpec
+    ) -> l2cap.LeCreditBasedChannel:
+        ...
+
+    async def create_l2cap_channel(
+        self, spec: Union[l2cap.ClassicChannelSpec, l2cap.LeCreditBasedChannelSpec]
+    ) -> Union[l2cap.ClassicChannel, l2cap.LeCreditBasedChannel]:
+        return await self.device.create_l2cap_channel(connection=self, spec=spec)
+
     async def disconnect(
         self, reason: int = HCI_REMOTE_USER_TERMINATED_CONNECTION_ERROR
     ) -> None:
@@ -829,6 +847,9 @@
         self.connectable = config.get('connectable', self.connectable)
         self.discoverable = config.get('discoverable', self.discoverable)
         self.gatt_services = config.get('gatt_services', self.gatt_services)
+        self.address_resolution_offload = config.get(
+            'address_resolution_offload', self.address_resolution_offload
+        )
 
         # Load or synthesize an IRK
         irk = config.get('irk')
@@ -1180,15 +1201,11 @@
 
         return None
 
-    def create_l2cap_connector(self, connection, psm):
-        return lambda: self.l2cap_channel_manager.connect(connection, psm)
-
-    def create_l2cap_registrar(self, psm):
-        return lambda handler: self.register_l2cap_server(psm, handler)
-
+    @deprecated("Please use create_l2cap_server()")
     def register_l2cap_server(self, psm, server) -> int:
         return self.l2cap_channel_manager.register_server(psm, server)
 
+    @deprecated("Please use create_l2cap_server()")
     def register_l2cap_channel_server(
         self,
         psm,
@@ -1201,6 +1218,7 @@
             psm, server, max_credits, mtu, mps
         )
 
+    @deprecated("Please use create_l2cap_channel()")
     async def open_l2cap_channel(
         self,
         connection,
@@ -1213,6 +1231,74 @@
             connection, psm, max_credits, mtu, mps
         )
 
+    @overload
+    async def create_l2cap_channel(
+        self,
+        connection: Connection,
+        spec: l2cap.ClassicChannelSpec,
+    ) -> l2cap.ClassicChannel:
+        ...
+
+    @overload
+    async def create_l2cap_channel(
+        self,
+        connection: Connection,
+        spec: l2cap.LeCreditBasedChannelSpec,
+    ) -> l2cap.LeCreditBasedChannel:
+        ...
+
+    async def create_l2cap_channel(
+        self,
+        connection: Connection,
+        spec: Union[l2cap.ClassicChannelSpec, l2cap.LeCreditBasedChannelSpec],
+    ) -> Union[l2cap.ClassicChannel, l2cap.LeCreditBasedChannel]:
+        if isinstance(spec, l2cap.ClassicChannelSpec):
+            return await self.l2cap_channel_manager.create_classic_channel(
+                connection=connection, spec=spec
+            )
+        if isinstance(spec, l2cap.LeCreditBasedChannelSpec):
+            return await self.l2cap_channel_manager.create_le_credit_based_channel(
+                connection=connection, spec=spec
+            )
+
+    @overload
+    def create_l2cap_server(
+        self,
+        spec: l2cap.ClassicChannelSpec,
+        handler: Optional[Callable[[l2cap.ClassicChannel], Any]] = None,
+    ) -> l2cap.ClassicChannelServer:
+        ...
+
+    @overload
+    def create_l2cap_server(
+        self,
+        spec: l2cap.LeCreditBasedChannelSpec,
+        handler: Optional[Callable[[l2cap.LeCreditBasedChannel], Any]] = None,
+    ) -> l2cap.LeCreditBasedChannelServer:
+        ...
+
+    def create_l2cap_server(
+        self,
+        spec: Union[l2cap.ClassicChannelSpec, l2cap.LeCreditBasedChannelSpec],
+        handler: Union[
+            Callable[[l2cap.ClassicChannel], Any],
+            Callable[[l2cap.LeCreditBasedChannel], Any],
+            None,
+        ] = None,
+    ) -> Union[l2cap.ClassicChannelServer, l2cap.LeCreditBasedChannelServer]:
+        if isinstance(spec, l2cap.ClassicChannelSpec):
+            return self.l2cap_channel_manager.create_classic_server(
+                spec=spec,
+                handler=cast(Callable[[l2cap.ClassicChannel], Any], handler),
+            )
+        elif isinstance(spec, l2cap.LeCreditBasedChannelSpec):
+            return self.l2cap_channel_manager.create_le_credit_based_server(
+                handler=cast(Callable[[l2cap.LeCreditBasedChannel], Any], handler),
+                spec=spec,
+            )
+        else:
+            raise ValueError(f'Unexpected mode {spec}')
+
     def send_l2cap_pdu(self, connection_handle: int, cid: int, pdu: bytes) -> None:
         self.host.send_l2cap_pdu(connection_handle, cid, pdu)
 
@@ -1425,10 +1511,10 @@
             check_result=True,
         )
 
-        self.advertising_own_address_type = own_address_type
-        self.auto_restart_advertising = auto_restart
         self.advertising_type = advertising_type
+        self.advertising_own_address_type = own_address_type
         self.advertising = True
+        self.auto_restart_advertising = auto_restart
 
     async def stop_advertising(self) -> None:
         # Disable advertising
@@ -1438,9 +1524,9 @@
                 check_result=True,
             )
 
+            self.advertising_type = None
             self.advertising_own_address_type = None
             self.advertising = False
-            self.advertising_type = None
             self.auto_restart_advertising = False
 
     @property
@@ -2630,7 +2716,6 @@
                 own_address_type = self.advertising_own_address_type
 
             # We are no longer advertising
-            self.advertising_own_address_type = None
             self.advertising = False
 
             if own_address_type in (
@@ -2687,7 +2772,6 @@
             and self.advertising
             and self.advertising_type.is_directed
         ):
-            self.advertising_own_address_type = None
             self.advertising = False
 
         # Notify listeners
diff --git a/bumble/hci.py b/bumble/hci.py
index 41deed2..057c04a 100644
--- a/bumble/hci.py
+++ b/bumble/hci.py
@@ -121,6 +121,7 @@
 HCI_VERSION_BLUETOOTH_CORE_5_1     = 10
 HCI_VERSION_BLUETOOTH_CORE_5_2     = 11
 HCI_VERSION_BLUETOOTH_CORE_5_3     = 12
+HCI_VERSION_BLUETOOTH_CORE_5_4     = 13
 
 HCI_VERSION_NAMES = {
     HCI_VERSION_BLUETOOTH_CORE_1_0B:    'HCI_VERSION_BLUETOOTH_CORE_1_0B',
@@ -135,7 +136,8 @@
     HCI_VERSION_BLUETOOTH_CORE_5_0:     'HCI_VERSION_BLUETOOTH_CORE_5_0',
     HCI_VERSION_BLUETOOTH_CORE_5_1:     'HCI_VERSION_BLUETOOTH_CORE_5_1',
     HCI_VERSION_BLUETOOTH_CORE_5_2:     'HCI_VERSION_BLUETOOTH_CORE_5_2',
-    HCI_VERSION_BLUETOOTH_CORE_5_3:     'HCI_VERSION_BLUETOOTH_CORE_5_3'
+    HCI_VERSION_BLUETOOTH_CORE_5_3:     'HCI_VERSION_BLUETOOTH_CORE_5_3',
+    HCI_VERSION_BLUETOOTH_CORE_5_4:     'HCI_VERSION_BLUETOOTH_CORE_5_4',
 }
 
 # LMP Version
diff --git a/bumble/hid.py b/bumble/hid.py
new file mode 100644
index 0000000..e4d6a77
--- /dev/null
+++ b/bumble/hid.py
@@ -0,0 +1,332 @@
+# Copyright 2021-2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# -----------------------------------------------------------------------------
+# Imports
+# -----------------------------------------------------------------------------
+from __future__ import annotations
+from dataclasses import dataclass
+import logging
+import asyncio
+import enum
+
+from pyee import EventEmitter
+from typing import Optional, Tuple, Callable, Dict, Union, TYPE_CHECKING
+
+from . import core, l2cap  # type: ignore
+from .colors import color  # type: ignore
+from .core import BT_BR_EDR_TRANSPORT, InvalidStateError, ProtocolError  # type: ignore
+
+if TYPE_CHECKING:
+    from bumble.device import Device, Connection
+
+
+# -----------------------------------------------------------------------------
+# Logging
+# -----------------------------------------------------------------------------
+logger = logging.getLogger(__name__)
+
+
+# -----------------------------------------------------------------------------
+# Constants
+# -----------------------------------------------------------------------------
+# fmt: on
+HID_CONTROL_PSM = 0x0011
+HID_INTERRUPT_PSM = 0x0013
+
+
+class Message:
+    message_type: MessageType
+    # Report types
+    class ReportType(enum.IntEnum):
+        OTHER_REPORT = 0x00
+        INPUT_REPORT = 0x01
+        OUTPUT_REPORT = 0x02
+        FEATURE_REPORT = 0x03
+
+    # Handshake parameters
+    class Handshake(enum.IntEnum):
+        SUCCESSFUL = 0x00
+        NOT_READY = 0x01
+        ERR_INVALID_REPORT_ID = 0x02
+        ERR_UNSUPPORTED_REQUEST = 0x03
+        ERR_UNKNOWN = 0x0E
+        ERR_FATAL = 0x0F
+
+    # Message Type
+    class MessageType(enum.IntEnum):
+        HANDSHAKE = 0x00
+        CONTROL = 0x01
+        GET_REPORT = 0x04
+        SET_REPORT = 0x05
+        GET_PROTOCOL = 0x06
+        SET_PROTOCOL = 0x07
+        DATA = 0x0A
+
+    # Protocol modes
+    class ProtocolMode(enum.IntEnum):
+        BOOT_PROTOCOL = 0x00
+        REPORT_PROTOCOL = 0x01
+
+    # Control Operations
+    class ControlCommand(enum.IntEnum):
+        SUSPEND = 0x03
+        EXIT_SUSPEND = 0x04
+        VIRTUAL_CABLE_UNPLUG = 0x05
+
+    # Class Method to derive header
+    @classmethod
+    def header(cls, lower_bits: int = 0x00) -> bytes:
+        return bytes([(cls.message_type << 4) | lower_bits])
+
+
+# HIDP messages
+@dataclass
+class GetReportMessage(Message):
+    report_type: int
+    report_id: int
+    buffer_size: int
+    message_type = Message.MessageType.GET_REPORT
+
+    def __bytes__(self) -> bytes:
+        packet_bytes = bytearray()
+        packet_bytes.append(self.report_id)
+        packet_bytes.extend(
+            [(self.buffer_size & 0xFF), ((self.buffer_size >> 8) & 0xFF)]
+        )
+        if self.report_type == Message.ReportType.OTHER_REPORT:
+            return self.header(self.report_type) + packet_bytes
+        else:
+            return self.header(0x08 | self.report_type) + packet_bytes
+
+
+@dataclass
+class SetReportMessage(Message):
+    report_type: int
+    data: bytes
+    message_type = Message.MessageType.SET_REPORT
+
+    def __bytes__(self) -> bytes:
+        return self.header(self.report_type) + self.data
+
+
+@dataclass
+class GetProtocolMessage(Message):
+    message_type = Message.MessageType.GET_PROTOCOL
+
+    def __bytes__(self) -> bytes:
+        return self.header()
+
+
+@dataclass
+class SetProtocolMessage(Message):
+    protocol_mode: int
+    message_type = Message.MessageType.SET_PROTOCOL
+
+    def __bytes__(self) -> bytes:
+        return self.header(self.protocol_mode)
+
+
+@dataclass
+class Suspend(Message):
+    message_type = Message.MessageType.CONTROL
+
+    def __bytes__(self) -> bytes:
+        return self.header(Message.ControlCommand.SUSPEND)
+
+
+@dataclass
+class ExitSuspend(Message):
+    message_type = Message.MessageType.CONTROL
+
+    def __bytes__(self) -> bytes:
+        return self.header(Message.ControlCommand.EXIT_SUSPEND)
+
+
+@dataclass
+class VirtualCableUnplug(Message):
+    message_type = Message.MessageType.CONTROL
+
+    def __bytes__(self) -> bytes:
+        return self.header(Message.ControlCommand.VIRTUAL_CABLE_UNPLUG)
+
+
+@dataclass
+class SendData(Message):
+    data: bytes
+    message_type = Message.MessageType.DATA
+
+    def __bytes__(self) -> bytes:
+        return self.header(Message.ReportType.OUTPUT_REPORT) + self.data
+
+
+# -----------------------------------------------------------------------------
+class Host(EventEmitter):
+    l2cap_ctrl_channel: Optional[l2cap.ClassicChannel]
+    l2cap_intr_channel: Optional[l2cap.ClassicChannel]
+
+    def __init__(self, device: Device, connection: Connection) -> None:
+        super().__init__()
+        self.device = device
+        self.connection = connection
+
+        self.l2cap_ctrl_channel = None
+        self.l2cap_intr_channel = None
+
+        # Register ourselves with the L2CAP channel manager
+        device.register_l2cap_server(HID_CONTROL_PSM, self.on_connection)
+        device.register_l2cap_server(HID_INTERRUPT_PSM, self.on_connection)
+
+    async def connect_control_channel(self) -> None:
+        # Create a new L2CAP connection - control channel
+        try:
+            self.l2cap_ctrl_channel = await self.device.l2cap_channel_manager.connect(
+                self.connection, HID_CONTROL_PSM
+            )
+        except ProtocolError:
+            logging.exception(f'L2CAP connection failed.')
+            raise
+
+        assert self.l2cap_ctrl_channel is not None
+        # Become a sink for the L2CAP channel
+        self.l2cap_ctrl_channel.sink = self.on_ctrl_pdu
+
+    async def connect_interrupt_channel(self) -> None:
+        # Create a new L2CAP connection - interrupt channel
+        try:
+            self.l2cap_intr_channel = await self.device.l2cap_channel_manager.connect(
+                self.connection, HID_INTERRUPT_PSM
+            )
+        except ProtocolError:
+            logging.exception(f'L2CAP connection failed.')
+            raise
+
+        assert self.l2cap_intr_channel is not None
+        # Become a sink for the L2CAP channel
+        self.l2cap_intr_channel.sink = self.on_intr_pdu
+
+    async def disconnect_interrupt_channel(self) -> None:
+        if self.l2cap_intr_channel is None:
+            raise InvalidStateError('invalid state')
+        channel = self.l2cap_intr_channel
+        self.l2cap_intr_channel = None
+        await channel.disconnect()
+
+    async def disconnect_control_channel(self) -> None:
+        if self.l2cap_ctrl_channel is None:
+            raise InvalidStateError('invalid state')
+        channel = self.l2cap_ctrl_channel
+        self.l2cap_ctrl_channel = None
+        await channel.disconnect()
+
+    def on_connection(self, l2cap_channel: l2cap.ClassicChannel) -> None:
+        logger.debug(f'+++ New L2CAP connection: {l2cap_channel}')
+        l2cap_channel.on('open', lambda: self.on_l2cap_channel_open(l2cap_channel))
+
+    def on_l2cap_channel_open(self, l2cap_channel: l2cap.ClassicChannel) -> None:
+        if l2cap_channel.psm == HID_CONTROL_PSM:
+            self.l2cap_ctrl_channel = l2cap_channel
+            self.l2cap_ctrl_channel.sink = self.on_ctrl_pdu
+        else:
+            self.l2cap_intr_channel = l2cap_channel
+            self.l2cap_intr_channel.sink = self.on_intr_pdu
+        logger.debug(f'$$$ L2CAP channel open: {l2cap_channel}')
+
+    def on_ctrl_pdu(self, pdu: bytes) -> None:
+        logger.debug(f'<<< HID CONTROL PDU: {pdu.hex()}')
+        # Here we will receive all kinds of packets, parse and then call respective callbacks
+        message_type = pdu[0] >> 4
+        param = pdu[0] & 0x0F
+
+        if message_type == Message.MessageType.HANDSHAKE:
+            logger.debug(f'<<< HID HANDSHAKE: {Message.Handshake(param).name}')
+            self.emit('handshake', Message.Handshake(param))
+        elif message_type == Message.MessageType.DATA:
+            logger.debug('<<< HID CONTROL DATA')
+            self.emit('data', pdu)
+        elif message_type == Message.MessageType.CONTROL:
+            if param == Message.ControlCommand.SUSPEND:
+                logger.debug('<<< HID SUSPEND')
+                self.emit('suspend', pdu)
+            elif param == Message.ControlCommand.EXIT_SUSPEND:
+                logger.debug('<<< HID EXIT SUSPEND')
+                self.emit('exit_suspend', pdu)
+            elif param == Message.ControlCommand.VIRTUAL_CABLE_UNPLUG:
+                logger.debug('<<< HID VIRTUAL CABLE UNPLUG')
+                self.emit('virtual_cable_unplug')
+            else:
+                logger.debug('<<< HID CONTROL OPERATION UNSUPPORTED')
+        else:
+            logger.debug('<<< HID CONTROL DATA')
+            self.emit('data', pdu)
+
+    def on_intr_pdu(self, pdu: bytes) -> None:
+        logger.debug(f'<<< HID INTERRUPT PDU: {pdu.hex()}')
+        self.emit("data", pdu)
+
+    def get_report(self, report_type: int, report_id: int, buffer_size: int) -> None:
+        msg = GetReportMessage(
+            report_type=report_type, report_id=report_id, buffer_size=buffer_size
+        )
+        hid_message = bytes(msg)
+        logger.debug(f'>>> HID CONTROL GET REPORT, PDU: {hid_message.hex()}')
+        self.send_pdu_on_ctrl(hid_message)
+
+    def set_report(self, report_type: int, data: bytes):
+        msg = SetReportMessage(report_type=report_type, data=data)
+        hid_message = bytes(msg)
+        logger.debug(f'>>> HID CONTROL SET REPORT, PDU:{hid_message.hex()}')
+        self.send_pdu_on_ctrl(hid_message)
+
+    def get_protocol(self):
+        msg = GetProtocolMessage()
+        hid_message = bytes(msg)
+        logger.debug(f'>>> HID CONTROL GET PROTOCOL, PDU: {hid_message.hex()}')
+        self.send_pdu_on_ctrl(hid_message)
+
+    def set_protocol(self, protocol_mode: int):
+        msg = SetProtocolMessage(protocol_mode=protocol_mode)
+        hid_message = bytes(msg)
+        logger.debug(f'>>> HID CONTROL SET PROTOCOL, PDU: {hid_message.hex()}')
+        self.send_pdu_on_ctrl(hid_message)
+
+    def send_pdu_on_ctrl(self, msg: bytes) -> None:
+        self.l2cap_ctrl_channel.send_pdu(msg)  # type: ignore
+
+    def send_pdu_on_intr(self, msg: bytes) -> None:
+        self.l2cap_intr_channel.send_pdu(msg)  # type: ignore
+
+    def send_data(self, data):
+        msg = SendData(data)
+        hid_message = bytes(msg)
+        logger.debug(f'>>> HID INTERRUPT SEND DATA, PDU: {hid_message.hex()}')
+        self.send_pdu_on_intr(hid_message)
+
+    def suspend(self):
+        msg = Suspend()
+        hid_message = bytes(msg)
+        logger.debug(f'>>> HID CONTROL SUSPEND, PDU:{hid_message.hex()}')
+        self.send_pdu_on_ctrl(msg)
+
+    def exit_suspend(self):
+        msg = ExitSuspend()
+        hid_message = bytes(msg)
+        logger.debug(f'>>> HID CONTROL EXIT SUSPEND, PDU:{hid_message.hex()}')
+        self.send_pdu_on_ctrl(msg)
+
+    def virtual_cable_unplug(self):
+        msg = VirtualCableUnplug()
+        hid_message = bytes(msg)
+        logger.debug(f'>>> HID CONTROL VIRTUAL CABLE UNPLUG, PDU: {hid_message.hex()}')
+        self.send_pdu_on_ctrl(msg)
diff --git a/bumble/l2cap.py b/bumble/l2cap.py
index cccb172..7a2f0ed 100644
--- a/bumble/l2cap.py
+++ b/bumble/l2cap.py
@@ -17,6 +17,7 @@
 # -----------------------------------------------------------------------------
 from __future__ import annotations
 import asyncio
+import dataclasses
 import enum
 import logging
 import struct
@@ -38,6 +39,7 @@
     TYPE_CHECKING,
 )
 
+from .utils import deprecated
 from .colors import color
 from .core import BT_CENTRAL_ROLE, InvalidStateError, ProtocolError
 from .hci import (
@@ -167,6 +169,34 @@
 # pylint: disable=invalid-name
 
 
+@dataclasses.dataclass
+class ClassicChannelSpec:
+    psm: Optional[int] = None
+    mtu: int = L2CAP_MIN_BR_EDR_MTU
+
+
+@dataclasses.dataclass
+class LeCreditBasedChannelSpec:
+    psm: Optional[int] = None
+    mtu: int = L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MTU
+    mps: int = L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MPS
+    max_credits: int = L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_INITIAL_CREDITS
+
+    def __post_init__(self):
+        if (
+            self.max_credits < 1
+            or self.max_credits > L2CAP_LE_CREDIT_BASED_CONNECTION_MAX_CREDITS
+        ):
+            raise ValueError('max credits out of range')
+        if self.mtu < L2CAP_LE_CREDIT_BASED_CONNECTION_MIN_MTU:
+            raise ValueError('MTU too small')
+        if (
+            self.mps < L2CAP_LE_CREDIT_BASED_CONNECTION_MIN_MPS
+            or self.mps > L2CAP_LE_CREDIT_BASED_CONNECTION_MAX_MPS
+        ):
+            raise ValueError('MPS out of range')
+
+
 class L2CAP_PDU:
     '''
     See Bluetooth spec @ Vol 3, Part A - 3 DATA PACKET FORMAT
@@ -676,7 +706,7 @@
 
 
 # -----------------------------------------------------------------------------
-class Channel(EventEmitter):
+class ClassicChannel(EventEmitter):
     class State(enum.IntEnum):
         # States
         CLOSED = 0x00
@@ -990,7 +1020,7 @@
 
 
 # -----------------------------------------------------------------------------
-class LeConnectionOrientedChannel(EventEmitter):
+class LeCreditBasedChannel(EventEmitter):
     """
     LE Credit-based Connection Oriented Channel
     """
@@ -1004,11 +1034,13 @@
         CONNECTION_ERROR = 5
 
     out_queue: Deque[bytes]
-    connection_result: Optional[asyncio.Future[LeConnectionOrientedChannel]]
+    connection_result: Optional[asyncio.Future[LeCreditBasedChannel]]
     disconnection_result: Optional[asyncio.Future[None]]
+    in_sdu: Optional[bytes]
     out_sdu: Optional[bytes]
     state: State
     connection: Connection
+    sink: Optional[Callable[[bytes], Any]]
 
     def __init__(
         self,
@@ -1071,7 +1103,7 @@
     def send_control_frame(self, frame: L2CAP_Control_Frame) -> None:
         self.manager.send_control_frame(self.connection, L2CAP_LE_SIGNALING_CID, frame)
 
-    async def connect(self) -> LeConnectionOrientedChannel:
+    async def connect(self) -> LeCreditBasedChannel:
         # Check that we're in the right state
         if self.state != self.State.INIT:
             raise InvalidStateError('not in a connectable state')
@@ -1343,14 +1375,66 @@
 
 
 # -----------------------------------------------------------------------------
+class ClassicChannelServer(EventEmitter):
+    def __init__(
+        self,
+        manager: ChannelManager,
+        psm: int,
+        handler: Optional[Callable[[ClassicChannel], Any]],
+        mtu: int,
+    ) -> None:
+        super().__init__()
+        self.manager = manager
+        self.handler = handler
+        self.psm = psm
+        self.mtu = mtu
+
+    def on_connection(self, channel: ClassicChannel) -> None:
+        self.emit('connection', channel)
+        if self.handler:
+            self.handler(channel)
+
+    def close(self) -> None:
+        if self.psm in self.manager.servers:
+            del self.manager.servers[self.psm]
+
+
+# -----------------------------------------------------------------------------
+class LeCreditBasedChannelServer(EventEmitter):
+    def __init__(
+        self,
+        manager: ChannelManager,
+        psm: int,
+        handler: Optional[Callable[[LeCreditBasedChannel], Any]],
+        max_credits: int,
+        mtu: int,
+        mps: int,
+    ) -> None:
+        super().__init__()
+        self.manager = manager
+        self.handler = handler
+        self.psm = psm
+        self.max_credits = max_credits
+        self.mtu = mtu
+        self.mps = mps
+
+    def on_connection(self, channel: LeCreditBasedChannel) -> None:
+        self.emit('connection', channel)
+        if self.handler:
+            self.handler(channel)
+
+    def close(self) -> None:
+        if self.psm in self.manager.le_coc_servers:
+            del self.manager.le_coc_servers[self.psm]
+
+
+# -----------------------------------------------------------------------------
 class ChannelManager:
     identifiers: Dict[int, int]
-    channels: Dict[int, Dict[int, Union[Channel, LeConnectionOrientedChannel]]]
-    servers: Dict[int, Callable[[Channel], Any]]
-    le_coc_channels: Dict[int, Dict[int, LeConnectionOrientedChannel]]
-    le_coc_servers: Dict[
-        int, Tuple[Callable[[LeConnectionOrientedChannel], Any], int, int, int]
-    ]
+    channels: Dict[int, Dict[int, Union[ClassicChannel, LeCreditBasedChannel]]]
+    servers: Dict[int, ClassicChannelServer]
+    le_coc_channels: Dict[int, Dict[int, LeCreditBasedChannel]]
+    le_coc_servers: Dict[int, LeCreditBasedChannelServer]
     le_coc_requests: Dict[int, L2CAP_LE_Credit_Based_Connection_Request]
     fixed_channels: Dict[int, Optional[Callable[[int, bytes], Any]]]
     _host: Optional[Host]
@@ -1429,21 +1513,6 @@
 
         raise RuntimeError('no free CID')
 
-    @staticmethod
-    def check_le_coc_parameters(max_credits: int, mtu: int, mps: int) -> None:
-        if (
-            max_credits < 1
-            or max_credits > L2CAP_LE_CREDIT_BASED_CONNECTION_MAX_CREDITS
-        ):
-            raise ValueError('max credits out of range')
-        if mtu < L2CAP_LE_CREDIT_BASED_CONNECTION_MIN_MTU:
-            raise ValueError('MTU too small')
-        if (
-            mps < L2CAP_LE_CREDIT_BASED_CONNECTION_MIN_MPS
-            or mps > L2CAP_LE_CREDIT_BASED_CONNECTION_MAX_MPS
-        ):
-            raise ValueError('MPS out of range')
-
     def next_identifier(self, connection: Connection) -> int:
         identifier = (self.identifiers.setdefault(connection.handle, 0) + 1) % 256
         self.identifiers[connection.handle] = identifier
@@ -1458,8 +1527,22 @@
         if cid in self.fixed_channels:
             del self.fixed_channels[cid]
 
-    def register_server(self, psm: int, server: Callable[[Channel], Any]) -> int:
-        if psm == 0:
+    @deprecated("Please use create_classic_server")
+    def register_server(
+        self,
+        psm: int,
+        server: Callable[[ClassicChannel], Any],
+    ) -> int:
+        return self.create_classic_server(
+            handler=server, spec=ClassicChannelSpec(psm=psm)
+        ).psm
+
+    def create_classic_server(
+        self,
+        spec: ClassicChannelSpec,
+        handler: Optional[Callable[[ClassicChannel], Any]] = None,
+    ) -> ClassicChannelServer:
+        if not spec.psm:
             # Find a free PSM
             for candidate in range(
                 L2CAP_PSM_DYNAMIC_RANGE_START, L2CAP_PSM_DYNAMIC_RANGE_END + 1, 2
@@ -1468,62 +1551,75 @@
                     continue
                 if candidate in self.servers:
                     continue
-                psm = candidate
+                spec.psm = candidate
                 break
             else:
                 raise InvalidStateError('no free PSM')
         else:
             # Check that the PSM isn't already in use
-            if psm in self.servers:
+            if spec.psm in self.servers:
                 raise ValueError('PSM already in use')
 
             # Check that the PSM is valid
-            if psm % 2 == 0:
+            if spec.psm % 2 == 0:
                 raise ValueError('invalid PSM (not odd)')
-            check = psm >> 8
+            check = spec.psm >> 8
             while check:
                 if check % 2 != 0:
                     raise ValueError('invalid PSM')
                 check >>= 8
 
-        self.servers[psm] = server
+        self.servers[spec.psm] = ClassicChannelServer(self, spec.psm, handler, spec.mtu)
 
-        return psm
+        return self.servers[spec.psm]
 
+    @deprecated("Please use create_le_credit_based_server()")
     def register_le_coc_server(
         self,
         psm: int,
-        server: Callable[[LeConnectionOrientedChannel], Any],
-        max_credits: int = L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_INITIAL_CREDITS,
-        mtu: int = L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MTU,
-        mps: int = L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MPS,
+        server: Callable[[LeCreditBasedChannel], Any],
+        max_credits: int,
+        mtu: int,
+        mps: int,
     ) -> int:
-        self.check_le_coc_parameters(max_credits, mtu, mps)
+        return self.create_le_credit_based_server(
+            spec=LeCreditBasedChannelSpec(
+                psm=None if psm == 0 else psm, mtu=mtu, mps=mps, max_credits=max_credits
+            ),
+            handler=server,
+        ).psm
 
-        if psm == 0:
+    def create_le_credit_based_server(
+        self,
+        spec: LeCreditBasedChannelSpec,
+        handler: Optional[Callable[[LeCreditBasedChannel], Any]] = None,
+    ) -> LeCreditBasedChannelServer:
+        if not spec.psm:
             # Find a free PSM
             for candidate in range(
                 L2CAP_LE_PSM_DYNAMIC_RANGE_START, L2CAP_LE_PSM_DYNAMIC_RANGE_END + 1
             ):
                 if candidate in self.le_coc_servers:
                     continue
-                psm = candidate
+                spec.psm = candidate
                 break
             else:
                 raise InvalidStateError('no free PSM')
         else:
             # Check that the PSM isn't already in use
-            if psm in self.le_coc_servers:
+            if spec.psm in self.le_coc_servers:
                 raise ValueError('PSM already in use')
 
-        self.le_coc_servers[psm] = (
-            server,
-            max_credits or L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_INITIAL_CREDITS,
-            mtu or L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MTU,
-            mps or L2CAP_LE_CREDIT_BASED_CONNECTION_DEFAULT_MPS,
+        self.le_coc_servers[spec.psm] = LeCreditBasedChannelServer(
+            self,
+            spec.psm,
+            handler,
+            max_credits=spec.max_credits,
+            mtu=spec.mtu,
+            mps=spec.mps,
         )
 
-        return psm
+        return self.le_coc_servers[spec.psm]
 
     def on_disconnection(self, connection_handle: int, _reason: int) -> None:
         logger.debug(f'disconnection from {connection_handle}, cleaning up channels')
@@ -1650,13 +1746,13 @@
             logger.debug(
                 f'creating server channel with cid={source_cid} for psm {request.psm}'
             )
-            channel = Channel(
-                self, connection, cid, request.psm, source_cid, L2CAP_MIN_BR_EDR_MTU
+            channel = ClassicChannel(
+                self, connection, cid, request.psm, source_cid, server.mtu
             )
             connection_channels[source_cid] = channel
 
             # Notify
-            server(channel)
+            server.on_connection(channel)
             channel.on_connection_request(request)
         else:
             logger.warning(
@@ -1878,7 +1974,7 @@
         self, connection: Connection, cid: int, request
     ) -> None:
         if request.le_psm in self.le_coc_servers:
-            (server, max_credits, mtu, mps) = self.le_coc_servers[request.le_psm]
+            server = self.le_coc_servers[request.le_psm]
 
             # Check that the CID isn't already used
             le_connection_channels = self.le_coc_channels.setdefault(
@@ -1892,8 +1988,8 @@
                     L2CAP_LE_Credit_Based_Connection_Response(
                         identifier=request.identifier,
                         destination_cid=0,
-                        mtu=mtu,
-                        mps=mps,
+                        mtu=server.mtu,
+                        mps=server.mps,
                         initial_credits=0,
                         # pylint: disable=line-too-long
                         result=L2CAP_LE_Credit_Based_Connection_Response.CONNECTION_REFUSED_SOURCE_CID_ALREADY_ALLOCATED,
@@ -1911,8 +2007,8 @@
                     L2CAP_LE_Credit_Based_Connection_Response(
                         identifier=request.identifier,
                         destination_cid=0,
-                        mtu=mtu,
-                        mps=mps,
+                        mtu=server.mtu,
+                        mps=server.mps,
                         initial_credits=0,
                         # pylint: disable=line-too-long
                         result=L2CAP_LE_Credit_Based_Connection_Response.CONNECTION_REFUSED_NO_RESOURCES_AVAILABLE,
@@ -1925,18 +2021,18 @@
                 f'creating LE CoC server channel with cid={source_cid} for psm '
                 f'{request.le_psm}'
             )
-            channel = LeConnectionOrientedChannel(
+            channel = LeCreditBasedChannel(
                 self,
                 connection,
                 request.le_psm,
                 source_cid,
                 request.source_cid,
-                mtu,
-                mps,
+                server.mtu,
+                server.mps,
                 request.initial_credits,
                 request.mtu,
                 request.mps,
-                max_credits,
+                server.max_credits,
                 True,
             )
             connection_channels[source_cid] = channel
@@ -1949,16 +2045,16 @@
                 L2CAP_LE_Credit_Based_Connection_Response(
                     identifier=request.identifier,
                     destination_cid=source_cid,
-                    mtu=mtu,
-                    mps=mps,
-                    initial_credits=max_credits,
+                    mtu=server.mtu,
+                    mps=server.mps,
+                    initial_credits=server.max_credits,
                     # pylint: disable=line-too-long
                     result=L2CAP_LE_Credit_Based_Connection_Response.CONNECTION_SUCCESSFUL,
                 ),
             )
 
             # Notify
-            server(channel)
+            server.on_connection(channel)
         else:
             logger.info(
                 f'No LE server for connection 0x{connection.handle:04X} '
@@ -2013,37 +2109,51 @@
 
         channel.on_credits(credit.credits)
 
-    def on_channel_closed(self, channel: Channel) -> None:
+    def on_channel_closed(self, channel: ClassicChannel) -> None:
         connection_channels = self.channels.get(channel.connection.handle)
         if connection_channels:
             if channel.source_cid in connection_channels:
                 del connection_channels[channel.source_cid]
 
+    @deprecated("Please use create_le_credit_based_channel()")
     async def open_le_coc(
         self, connection: Connection, psm: int, max_credits: int, mtu: int, mps: int
-    ) -> LeConnectionOrientedChannel:
-        self.check_le_coc_parameters(max_credits, mtu, mps)
+    ) -> LeCreditBasedChannel:
+        return await self.create_le_credit_based_channel(
+            connection=connection,
+            spec=LeCreditBasedChannelSpec(
+                psm=psm, max_credits=max_credits, mtu=mtu, mps=mps
+            ),
+        )
 
+    async def create_le_credit_based_channel(
+        self,
+        connection: Connection,
+        spec: LeCreditBasedChannelSpec,
+    ) -> LeCreditBasedChannel:
         # Find a free CID for the new channel
         connection_channels = self.channels.setdefault(connection.handle, {})
         source_cid = self.find_free_le_cid(connection_channels)
         if source_cid is None:  # Should never happen!
             raise RuntimeError('all CIDs already in use')
 
+        if spec.psm is None:
+            raise ValueError('PSM cannot be None')
+
         # Create the channel
-        logger.debug(f'creating coc channel with cid={source_cid} for psm {psm}')
-        channel = LeConnectionOrientedChannel(
+        logger.debug(f'creating coc channel with cid={source_cid} for psm {spec.psm}')
+        channel = LeCreditBasedChannel(
             manager=self,
             connection=connection,
-            le_psm=psm,
+            le_psm=spec.psm,
             source_cid=source_cid,
             destination_cid=0,
-            mtu=mtu,
-            mps=mps,
+            mtu=spec.mtu,
+            mps=spec.mps,
             credits=0,
             peer_mtu=0,
             peer_mps=0,
-            peer_credits=max_credits,
+            peer_credits=spec.max_credits,
             connected=False,
         )
         connection_channels[source_cid] = channel
@@ -2062,7 +2172,15 @@
 
         return channel
 
-    async def connect(self, connection: Connection, psm: int) -> Channel:
+    @deprecated("Please use create_classic_channel()")
+    async def connect(self, connection: Connection, psm: int) -> ClassicChannel:
+        return await self.create_classic_channel(
+            connection=connection, spec=ClassicChannelSpec(psm=psm)
+        )
+
+    async def create_classic_channel(
+        self, connection: Connection, spec: ClassicChannelSpec
+    ) -> ClassicChannel:
         # NOTE: this implementation hard-codes BR/EDR
 
         # Find a free CID for a new channel
@@ -2071,10 +2189,20 @@
         if source_cid is None:  # Should never happen!
             raise RuntimeError('all CIDs already in use')
 
+        if spec.psm is None:
+            raise ValueError('PSM cannot be None')
+
         # Create the channel
-        logger.debug(f'creating client channel with cid={source_cid} for psm {psm}')
-        channel = Channel(
-            self, connection, L2CAP_SIGNALING_CID, psm, source_cid, L2CAP_MIN_BR_EDR_MTU
+        logger.debug(
+            f'creating client channel with cid={source_cid} for psm {spec.psm}'
+        )
+        channel = ClassicChannel(
+            self,
+            connection,
+            L2CAP_SIGNALING_CID,
+            spec.psm,
+            source_cid,
+            spec.mtu,
         )
         connection_channels[source_cid] = channel
 
@@ -2086,3 +2214,20 @@
             raise e
 
         return channel
+
+
+# -----------------------------------------------------------------------------
+# Deprecated Classes
+# -----------------------------------------------------------------------------
+
+
+class Channel(ClassicChannel):
+    @deprecated("Please use ClassicChannel")
+    def __init__(self, *args, **kwargs) -> None:
+        super().__init__(*args, **kwargs)
+
+
+class LeConnectionOrientedChannel(LeCreditBasedChannel):
+    @deprecated("Please use LeCreditBasedChannel")
+    def __init__(self, *args, **kwargs) -> None:
+        super().__init__(*args, **kwargs)
diff --git a/bumble/pandora/security.py b/bumble/pandora/security.py
index 0f31512..85365e6 100644
--- a/bumble/pandora/security.py
+++ b/bumble/pandora/security.py
@@ -450,21 +450,18 @@
             'security_request': pair,
         }
 
-        # register event handlers
-        for event, listener in listeners.items():
-            connection.on(event, listener)
+        with contextlib.closing(EventWatcher()) as watcher:
+            # register event handlers
+            for event, listener in listeners.items():
+                watcher.on(connection, event, listener)
 
-        # security level already reached
-        if self.reached_security_level(connection, level):
-            return WaitSecurityResponse(success=empty_pb2.Empty())
+            # security level already reached
+            if self.reached_security_level(connection, level):
+                return WaitSecurityResponse(success=empty_pb2.Empty())
 
-        self.log.debug('Wait for security...')
-        kwargs = {}
-        kwargs[await wait_for_security] = empty_pb2.Empty()
-
-        # remove event handlers
-        for event, listener in listeners.items():
-            connection.remove_listener(event, listener)  # type: ignore
+            self.log.debug('Wait for security...')
+            kwargs = {}
+            kwargs[await wait_for_security] = empty_pb2.Empty()
 
         # wait for `authenticate` to finish if any
         if authenticate_task is not None:
diff --git a/bumble/profiles/asha_service.py b/bumble/profiles/asha_service.py
index 6898397..412b28a 100644
--- a/bumble/profiles/asha_service.py
+++ b/bumble/profiles/asha_service.py
@@ -19,6 +19,8 @@
 import struct
 import logging
 from typing import List
+
+from bumble import l2cap
 from ..core import AdvertisingData
 from ..device import Device, Connection
 from ..gatt import (
@@ -149,7 +151,10 @@
             channel.sink = on_data
 
         # let the server find a free PSM
-        self.psm = self.device.register_l2cap_channel_server(self.psm, on_coc, 8)
+        self.psm = device.create_l2cap_server(
+            spec=l2cap.LeCreditBasedChannelSpec(psm=self.psm, max_credits=8),
+            handler=on_coc,
+        ).psm
         self.le_psm_out_characteristic = Characteristic(
             GATT_ASHA_LE_PSM_OUT_CHARACTERISTIC,
             Characteristic.Properties.READ,
diff --git a/bumble/rfcomm.py b/bumble/rfcomm.py
index 02c18fa..53e98e0 100644
--- a/bumble/rfcomm.py
+++ b/bumble/rfcomm.py
@@ -674,7 +674,7 @@
     acceptor: Optional[Callable[[int], bool]]
     dlcs: Dict[int, DLC]
 
-    def __init__(self, l2cap_channel: l2cap.Channel, role: Role) -> None:
+    def __init__(self, l2cap_channel: l2cap.ClassicChannel, role: Role) -> None:
         super().__init__()
         self.role = role
         self.l2cap_channel = l2cap_channel
@@ -887,7 +887,7 @@
 # -----------------------------------------------------------------------------
 class Client:
     multiplexer: Optional[Multiplexer]
-    l2cap_channel: Optional[l2cap.Channel]
+    l2cap_channel: Optional[l2cap.ClassicChannel]
 
     def __init__(self, device: Device, connection: Connection) -> None:
         self.device = device
@@ -898,8 +898,8 @@
     async def start(self) -> Multiplexer:
         # Create a new L2CAP connection
         try:
-            self.l2cap_channel = await self.device.l2cap_channel_manager.connect(
-                self.connection, RFCOMM_PSM
+            self.l2cap_channel = await self.connection.create_l2cap_channel(
+                spec=l2cap.ClassicChannelSpec(RFCOMM_PSM)
             )
         except ProtocolError as error:
             logger.warning(f'L2CAP connection failed: {error}')
@@ -936,7 +936,9 @@
         self.acceptors = {}
 
         # Register ourselves with the L2CAP channel manager
-        device.register_l2cap_server(RFCOMM_PSM, self.on_connection)
+        device.create_l2cap_server(
+            spec=l2cap.ClassicChannelSpec(psm=RFCOMM_PSM), handler=self.on_connection
+        )
 
     def listen(self, acceptor: Callable[[DLC], None], channel: int = 0) -> int:
         if channel:
@@ -960,11 +962,11 @@
         self.acceptors[channel] = acceptor
         return channel
 
-    def on_connection(self, l2cap_channel: l2cap.Channel) -> None:
+    def on_connection(self, l2cap_channel: l2cap.ClassicChannel) -> None:
         logger.debug(f'+++ new L2CAP connection: {l2cap_channel}')
         l2cap_channel.on('open', lambda: self.on_l2cap_channel_open(l2cap_channel))
 
-    def on_l2cap_channel_open(self, l2cap_channel: l2cap.Channel) -> None:
+    def on_l2cap_channel_open(self, l2cap_channel: l2cap.ClassicChannel) -> None:
         logger.debug(f'$$$ L2CAP channel open: {l2cap_channel}')
 
         # Create a new multiplexer for the channel
diff --git a/bumble/sdp.py b/bumble/sdp.py
index 6428187..bc8303c 100644
--- a/bumble/sdp.py
+++ b/bumble/sdp.py
@@ -167,7 +167,7 @@
         UUID: lambda x: DataElement(
             DataElement.UUID, core.UUID.from_bytes(bytes(reversed(x)))
         ),
-        TEXT_STRING: lambda x: DataElement(DataElement.TEXT_STRING, x.decode('utf8')),
+        TEXT_STRING: lambda x: DataElement(DataElement.TEXT_STRING, x),
         BOOLEAN: lambda x: DataElement(DataElement.BOOLEAN, x[0] == 1),
         SEQUENCE: lambda x: DataElement(
             DataElement.SEQUENCE, DataElement.list_from_bytes(x)
@@ -229,7 +229,7 @@
         return DataElement(DataElement.UUID, value)
 
     @staticmethod
-    def text_string(value: str) -> DataElement:
+    def text_string(value: bytes) -> DataElement:
         return DataElement(DataElement.TEXT_STRING, value)
 
     @staticmethod
@@ -376,7 +376,7 @@
                 raise ValueError('invalid value_size')
         elif self.type == DataElement.UUID:
             data = bytes(reversed(bytes(self.value)))
-        elif self.type in (DataElement.TEXT_STRING, DataElement.URL):
+        elif self.type == DataElement.URL:
             data = self.value.encode('utf8')
         elif self.type == DataElement.BOOLEAN:
             data = bytes([1 if self.value else 0])
@@ -758,7 +758,7 @@
 
 # -----------------------------------------------------------------------------
 class Client:
-    channel: Optional[l2cap.Channel]
+    channel: Optional[l2cap.ClassicChannel]
 
     def __init__(self, device: Device) -> None:
         self.device = device
@@ -766,8 +766,9 @@
         self.channel = None
 
     async def connect(self, connection: Connection) -> None:
-        result = await self.device.l2cap_channel_manager.connect(connection, SDP_PSM)
-        self.channel = result
+        self.channel = await connection.create_l2cap_channel(
+            spec=l2cap.ClassicChannelSpec(SDP_PSM)
+        )
 
     async def disconnect(self) -> None:
         if self.channel:
@@ -921,7 +922,7 @@
 # -----------------------------------------------------------------------------
 class Server:
     CONTINUATION_STATE = bytes([0x01, 0x43])
-    channel: Optional[l2cap.Channel]
+    channel: Optional[l2cap.ClassicChannel]
     Service = NewType('Service', List[ServiceAttribute])
     service_records: Dict[int, Service]
     current_response: Union[None, bytes, Tuple[int, List[int]]]
@@ -933,7 +934,9 @@
         self.current_response = None
 
     def register(self, l2cap_channel_manager: l2cap.ChannelManager) -> None:
-        l2cap_channel_manager.register_server(SDP_PSM, self.on_connection)
+        l2cap_channel_manager.create_classic_server(
+            spec=l2cap.ClassicChannelSpec(psm=SDP_PSM), handler=self.on_connection
+        )
 
     def send_response(self, response):
         logger.debug(f'{color(">>> Sending SDP Response", "blue")}: {response}')
diff --git a/bumble/utils.py b/bumble/utils.py
index dc03725..a562618 100644
--- a/bumble/utils.py
+++ b/bumble/utils.py
@@ -21,6 +21,7 @@
 import traceback
 import collections
 import sys
+import warnings
 from typing import (
     Awaitable,
     Set,
@@ -33,7 +34,7 @@
     Union,
     overload,
 )
-from functools import wraps
+from functools import wraps, partial
 from pyee import EventEmitter
 
 from .colors import color
@@ -410,3 +411,36 @@
                     self.resume_source()
 
             self.check_pump()
+
+
+async def async_call(function, *args, **kwargs):
+    """
+    Immediately calls the function with provided args and kwargs, wrapping it in an async function.
+    Rust's `pyo3_asyncio` library needs functions to be marked async to properly inject a running loop.
+
+    result = await async_call(some_function, ...)
+    """
+    return function(*args, **kwargs)
+
+
+def wrap_async(function):
+    """
+    Wraps the provided function in an async function.
+    """
+    return partial(async_call, function)
+
+
+def deprecated(msg: str):
+    """
+    Throw deprecation warning before execution
+    """
+
+    def wrapper(function):
+        @wraps(function)
+        def inner(*args, **kwargs):
+            warnings.warn(msg, DeprecationWarning)
+            return function(*args, **kwargs)
+
+        return inner
+
+    return wrapper
diff --git a/docs/mkdocs/mkdocs.yml b/docs/mkdocs/mkdocs.yml
index 82a6f41..67bdd7c 100644
--- a/docs/mkdocs/mkdocs.yml
+++ b/docs/mkdocs/mkdocs.yml
@@ -67,6 +67,9 @@
     - Zephyr: platforms/zephyr.md
   - Examples:
     - Overview: examples/index.md
+  - Extras:
+    - Overview: extras/index.md
+    - Android Remote HCI: extras/android_remote_hci.md
 
 copyright: Copyright 2021-2023 Google LLC
 
diff --git a/docs/mkdocs/src/extras/android_remote_hci.md b/docs/mkdocs/src/extras/android_remote_hci.md
new file mode 100644
index 0000000..4eab132
--- /dev/null
+++ b/docs/mkdocs/src/extras/android_remote_hci.md
@@ -0,0 +1,141 @@
+ANDROID REMOTE HCI APP
+======================
+
+This application allows using an android phone's built-in Bluetooth controller with 
+a Bumble host stack running outside the phone (typically a development laptop or desktop).
+The app runs an HCI proxy between a TCP socket on the "outside" and the Bluetooth HCI HAL
+on the "inside". (See [this page](https://source.android.com/docs/core/connect/bluetooth) for a high level 
+description of the Android Bluetooth HCI HAL).
+The HCI packets received on the TCP socket are forwarded to the phone's controller, and the 
+packets coming from the controller are forwarded to the TCP socket.
+
+
+Building
+--------
+
+You can build the app by running `./gradlew build` (use `gradlew.bat` on Windows) from the `RemoteHCI` top level directory.
+You can also build with Android Studio: open the `RemoteHCI` project. You can build and/or debug from there.
+
+If the build succeeds, you can find the app APKs (debug and release) at:
+
+  * [Release] ``app/build/outputs/apk/release/app-release-unsigned.apk``
+  * [Debug] ``app/build/outputs/apk/debug/app-debug.apk``
+
+
+Running
+-------
+
+### Preconditions
+When the proxy starts (tapping the "Start" button in the app's main activity), it will try to 
+bind to the Bluetooth HAL. This requires disabling SELinux temporarily, and being the only HAL client.
+
+#### Disabling SELinux
+Binding to the Bluetooth HCI HAL requires certain SELinux permissions that can't simply be changed
+on a device without rebuilding its system image. To bypass these restrictions, you will need
+to disable SELinux on your phone (please be aware that this is global, not just for the proxy app,
+so proceed with caution).
+In order to disable SELinux, you need to root the phone (it may be advisable to do this on a
+development phone).
+
+!!! tip "Disabling SELinux Temporarily"
+    Restart `adb` as root:
+    ```bash
+    $ adb root
+    ```
+
+    Then disable SELinux
+    ```bash
+    $ adb shell setenforce 0
+    ```
+
+    Once you're done using the proxy, you can restore SELinux, if you need to, with
+    ```bash
+    $ adb shell setenforce 1
+    ```
+
+    This state will also reset to the normal SELinux enforcement when you reboot.
+
+#### Stopping the bluetooth process
+Since the Bluetooth HAL service can only accept one client, and that in normal conditions 
+that client is the Android's bluetooth stack, it is required to first shut down the 
+Android bluetooth stack process.
+
+!!! tip "Checking if the Bluetooth process is running"
+    ```bash
+    $ adb shell "ps -A | grep com.google.android.bluetooth"
+    ```
+    If the process is running, you will get a line like:
+    ```
+    bluetooth 10759 876 17455796 136620 do_epoll_wait 0 S com.google.android.bluetooth
+    ```
+    If you don't, it means that the process is not running and you are clear to proceed.
+
+Simply turning Bluetooth off from the phone's settings does not ensure that the bluetooth process will exit.
+If the bluetooth process is still running after toggling Bluetooth off from the settings, you may try enabling
+Airplane Mode, then rebooting. The bluetooth process should, in theory, not restart after the reboot.
+
+!!! tip "Stopping the bluetooth process with adb"
+    ```bash
+    $ adb shell cmd bluetooth_manager disable
+    ```
+
+### Starting the app
+You can start the app from the Android launcher, from Android Studio, or with `adb`
+
+#### Launching from the launcher
+Just tap the app icon on the launcher, check the TCP port that is configured, and tap
+the "Start" button.
+
+#### Launching with `adb`
+Using the `am` command, you can start the activity, and pass it arguments so that you can
+automatically start the proxy, and/or set the port number.
+
+!!! tip "Launching from adb with auto-start"
+    ```bash
+    $ adb shell am start -n com.github.google.bumble.remotehci/.MainActivity --ez autostart true
+    ```
+
+!!! tip "Launching from adb with auto-start and a port"
+    In this example, we auto-start the proxy upon launch, with the port set to 9995
+    ```bash
+    $ adb shell am start -n com.github.google.bumble.remotehci/.MainActivity --ez autostart true --ei port 9995
+    ```
+
+#### Selecting a TCP port
+The RemoteHCI app's main activity has a "TCP Port" setting where you can change the port on
+which the proxy is accepting connections. If the default value isn't suitable, you can 
+change it there (you can also use the special value 0 to let the OS assign a port number for you).
+
+### Connecting to the proxy
+To connect the Bumble stack to the proxy, you need to be able to reach the phone's network 
+stack. This can be done over the phone's WiFi connection, or, alternatively, using an `adb`
+TCP forward (which should be faster than over WiFi).
+
+!!! tip "Forwarding TCP with `adb`"
+    To connect to the proxy via an `adb` TCP forward, use:
+    ```bash
+    $ adb forward tcp:<outside-port> tcp:<inside-port>
+    ```
+    Where ``<outside-port>`` is the port number for a listening socket on your laptop or 
+    desktop machine, and <inside-port> is the TCP port selected in the app's user interface.
+    Those two ports may be the same, of course.
+    For example, with the default TCP port 9993:
+    ```bash
+    $ adb forward tcp:9993 tcp:9993
+    ```
+
+Once you've ensured that you can reach the proxy's TCP port on the phone, either directly or
+via an `adb` forward, you can then use it as a Bumble transport, using the transport name: 
+``tcp-client:<host>:<port>`` syntax.
+
+!!! example "Connecting a Bumble client"
+    Connecting the `bumble-controller-info` app to the phone's controller.
+    Assuming you have set up an `adb` forward on port 9993:
+    ```bash
+    $ bumble-controller-info tcp-client:localhost:9993
+    ```
+
+    Or over WiFi with, in this example, the IP address of the phone being ```192.168.86.27```
+    ```bash
+    $ bumble-controller-info tcp-client:192.168.86.27:9993
+    ```
diff --git a/docs/mkdocs/src/extras/index.md b/docs/mkdocs/src/extras/index.md
new file mode 100644
index 0000000..ae906c1
--- /dev/null
+++ b/docs/mkdocs/src/extras/index.md
@@ -0,0 +1,11 @@
+EXTRAS
+======
+
+A collection of add-ons, apps and tools, to the Bumble project.
+
+Android Remote HCI
+------------------
+
+Allows using an Android phone's built-in Bluetooth controller with a Bumble
+stack running on a development machine.
+See [Android Remote HCI](android_remote_hci.md) for details.
\ No newline at end of file
diff --git a/docs/mkdocs/src/hardware/index.md b/docs/mkdocs/src/hardware/index.md
index 986532b..9eabab0 100644
--- a/docs/mkdocs/src/hardware/index.md
+++ b/docs/mkdocs/src/hardware/index.md
@@ -3,7 +3,7 @@
 
 The Bumble Host connects to a controller over an [HCI Transport](../transports/index.md).
 To use a hardware controller attached to the host on which the host application is running, the transport is typically either [HCI over UART](../transports/serial.md) or [HCI over USB](../transports/usb.md).
-On Linux, the [VHCI Transport](../transports/vhci.md) can be used to communicate with any controller hardware managed by the operating system. Alternatively, a remote controller (a phyiscal controller attached to a remote host) can be used by connecting one of the networked transports (such as the [TCP Client transport](../transports/tcp_client.md), the [TCP Server transport](../transports/tcp_server.md) or the [UDP Transport](../transports/udp.md)) to an [HCI Bridge](../apps_and_tools/hci_bridge) bridging the network transport to a physical controller on a remote host.
+On Linux, the [VHCI Transport](../transports/vhci.md) can be used to communicate with any controller hardware managed by the operating system. Alternatively, a remote controller (a phyiscal controller attached to a remote host) can be used by connecting one of the networked transports (such as the [TCP Client transport](../transports/tcp_client.md), the [TCP Server transport](../transports/tcp_server.md) or the [UDP Transport](../transports/udp.md)) to an [HCI Bridge](../apps_and_tools/hci_bridge.md) bridging the network transport to a physical controller on a remote host.
 
 In theory, any controller that is compliant with the HCI over UART or HCI over USB protocols can be used.
 
diff --git a/examples/hid_key_map.py b/examples/hid_key_map.py
new file mode 100644
index 0000000..5f61e7f
--- /dev/null
+++ b/examples/hid_key_map.py
@@ -0,0 +1,248 @@
+# shift map
+
+# letters
+shift_map = {
+    'a': 'A',
+    'b': 'B',
+    'c': 'C',
+    'd': 'D',
+    'e': 'E',
+    'f': 'F',
+    'g': 'G',
+    'h': 'H',
+    'i': 'I',
+    'j': 'J',
+    'k': 'K',
+    'l': 'L',
+    'm': 'M',
+    'n': 'N',
+    'o': 'O',
+    'p': 'P',
+    'q': 'Q',
+    'r': 'R',
+    's': 'S',
+    't': 'T',
+    'u': 'U',
+    'v': 'V',
+    'w': 'W',
+    'x': 'X',
+    'y': 'Y',
+    'z': 'Z',
+    # numbers
+    '1': '!',
+    '2': '@',
+    '3': '#',
+    '4': '$',
+    '5': '%',
+    '6': '^',
+    '7': '&',
+    '8': '*',
+    '9': '(',
+    '0': ')',
+    # symbols
+    '-': '_',
+    '=': '+',
+    '[': '{',
+    ']': '}',
+    '\\': '|',
+    ';': ':',
+    '\'': '"',
+    ',': '<',
+    '.': '>',
+    '/': '?',
+    '`': '~',
+}
+
+# hex map
+
+# modifier keys
+mod_keys = {
+    '00': '',
+    '01': 'left_ctrl',
+    '02': 'left_shift',
+    '04': 'left_alt',
+    '08': 'left_meta',
+    '10': 'right_ctrl',
+    '20': 'right_shift',
+    '40': 'right_alt',
+    '80': 'right_meta',
+}
+
+# base keys
+
+base_keys = {
+    # meta
+    '00': '',  # none
+    '01': 'error_ovf',
+    # letters
+    '04': 'a',
+    '05': 'b',
+    '06': 'c',
+    '07': 'd',
+    '08': 'e',
+    '09': 'f',
+    '0a': 'g',
+    '0b': 'h',
+    '0c': 'i',
+    '0d': 'j',
+    '0e': 'k',
+    '0f': 'l',
+    '10': 'm',
+    '11': 'n',
+    '12': 'o',
+    '13': 'p',
+    '14': 'q',
+    '15': 'r',
+    '16': 's',
+    '17': 't',
+    '18': 'u',
+    '19': 'v',
+    '1a': 'w',
+    '1b': 'x',
+    '1c': 'y',
+    '1d': 'z',
+    # numbers
+    '1e': '1',
+    '1f': '2',
+    '20': '3',
+    '21': '4',
+    '22': '5',
+    '23': '6',
+    '24': '7',
+    '25': '8',
+    '26': '9',
+    '27': '0',
+    # misc
+    '28': 'enter',  # enter \n
+    '29': 'esc',
+    '2a': 'backspace',
+    '2b': 'tab',
+    '2c': 'spacebar',  # space
+    '2d': '-',
+    '2e': '=',
+    '2f': '[',
+    '30': ']',
+    '31': '\\',
+    '32': '=',
+    '33': '_SEMICOLON',
+    '34': 'KEY_APOSTROPHE',
+    '35': 'KEY_GRAVE',
+    '36': 'KEY_COMMA',
+    '37': 'KEY_DOT',
+    '38': 'KEY_SLASH',
+    '39': 'KEY_CAPSLOCK',
+    '3a': 'KEY_F1',
+    '3b': 'KEY_F2',
+    '3c': 'KEY_F3',
+    '3d': 'KEY_F4',
+    '3e': 'KEY_F5',
+    '3f': 'KEY_F6',
+    '40': 'KEY_F7',
+    '41': 'KEY_F8',
+    '42': 'KEY_F9',
+    '43': 'KEY_F10',
+    '44': 'KEY_F11',
+    '45': 'KEY_F12',
+    '46': 'KEY_SYSRQ',
+    '47': 'KEY_SCROLLLOCK',
+    '48': 'KEY_PAUSE',
+    '49': 'KEY_INSERT',
+    '4a': 'KEY_HOME',
+    '4b': 'KEY_PAGEUP',
+    '4c': 'KEY_DELETE',
+    '4d': 'KEY_END',
+    '4e': 'KEY_PAGEDOWN',
+    '4f': 'KEY_RIGHT',
+    '50': 'KEY_LEFT',
+    '51': 'KEY_DOWN',
+    '52': 'KEY_UP',
+    '53': 'KEY_NUMLOCK',
+    '54': 'KEY_KPSLASH',
+    '55': 'KEY_KPASTERISK',
+    '56': 'KEY_KPMINUS',
+    '57': 'KEY_KPPLUS',
+    '58': 'KEY_KPENTER',
+    '59': 'KEY_KP1',
+    '5a': 'KEY_KP2',
+    '5b': 'KEY_KP3',
+    '5c': 'KEY_KP4',
+    '5d': 'KEY_KP5',
+    '5e': 'KEY_KP6',
+    '5f': 'KEY_KP7',
+    '60': 'KEY_KP8',
+    '61': 'KEY_KP9',
+    '62': 'KEY_KP0',
+    '63': 'KEY_KPDOT',
+    '64': 'KEY_102ND',
+    '65': 'KEY_COMPOSE',
+    '66': 'KEY_POWER',
+    '67': 'KEY_KPEQUAL',
+    '68': 'KEY_F13',
+    '69': 'KEY_F14',
+    '6a': 'KEY_F15',
+    '6b': 'KEY_F16',
+    '6c': 'KEY_F17',
+    '6d': 'KEY_F18',
+    '6e': 'KEY_F19',
+    '6f': 'KEY_F20',
+    '70': 'KEY_F21',
+    '71': 'KEY_F22',
+    '72': 'KEY_F23',
+    '73': 'KEY_F24',
+    '74': 'KEY_OPEN',
+    '75': 'KEY_HELP',
+    '76': 'KEY_PROPS',
+    '77': 'KEY_FRONT',
+    '78': 'KEY_STOP',
+    '79': 'KEY_AGAIN',
+    '7a': 'KEY_UNDO',
+    '7b': 'KEY_CUT',
+    '7c': 'KEY_COPY',
+    '7d': 'KEY_PASTE',
+    '7e': 'KEY_FIND',
+    '7f': 'KEY_MUTE',
+    '80': 'KEY_VOLUMEUP',
+    '81': 'KEY_VOLUMEDOWN',
+    '85': 'KEY_KPCOMMA',
+    '87': 'KEY_RO',
+    '88': 'KEY_KATAKANAHIRAGANA',
+    '89': 'KEY_YEN',
+    '8a': 'KEY_HENKAN',
+    '8b': 'KEY_MUHENKAN',
+    '8c': 'KEY_KPJPCOMMA',
+    '90': 'KEY_HANGEUL',
+    '91': 'KEY_HANJA',
+    '92': 'KEY_KATAKANA',
+    '93': 'KEY_HIRAGANA',
+    '94': 'KEY_ZENKAKUHANKAKU',
+    'b6': 'KEY_KPLEFTPAREN',
+    'b7': 'KEY_KPRIGHTPAREN',
+    'e0': 'KEY_LEFTCTRL',
+    'e1': 'KEY_LEFTSHIFT',
+    'e2': 'KEY_LEFTALT',
+    'e3': 'KEY_LEFTMETA',
+    'e4': 'KEY_RIGHTCTRL',
+    'e5': 'KEY_RIGHTSHIFT',
+    'e6': 'KEY_RIGHTALT',
+    'e7': 'KEY_RIGHTMETA',
+    'e8': 'KEY_MEDIA_PLAYPAUSE',
+    'e9': 'KEY_MEDIA_STOPCD',
+    'ea': 'KEY_MEDIA_PREVIOUSSONG',
+    'eb': 'KEY_MEDIA_NEXTSONG',
+    'ec': 'KEY_MEDIA_EJECTCD',
+    'ed': 'KEY_MEDIA_VOLUMEUP',
+    'ee': 'KEY_MEDIA_VOLUMEDOWN',
+    'ef': 'KEY_MEDIA_MUTE',
+    'f0': 'KEY_MEDIA_WWW',
+    'f1': 'KEY_MEDIA_BACK',
+    'f2': 'KEY_MEDIA_FORWARD',
+    'f3': 'KEY_MEDIA_STOP',
+    'f4': 'KEY_MEDIA_FIND',
+    'f5': 'KEY_MEDIA_SCROLLUP',
+    'f6': 'KEY_MEDIA_SCROLLDOWN',
+    'f7': 'KEY_MEDIA_EDIT',
+    'f8': 'KEY_MEDIA_SLEEP',
+    'f9': 'KEY_MEDIA_COFFEE',
+    'fa': 'KEY_MEDIA_REFRESH',
+    'fb': 'KEY_MEDIA_CALC',
+}
diff --git a/examples/hid_report_parser.py b/examples/hid_report_parser.py
new file mode 100644
index 0000000..61561b3
--- /dev/null
+++ b/examples/hid_report_parser.py
@@ -0,0 +1,159 @@
+from bumble.colors import color
+from hid_key_map import base_keys, mod_keys, shift_map
+
+
+# ------------------------------------------------------------------------------
+def get_key(modifier: str, key: str) -> str:
+    if modifier == '22':
+        modifier = '02'
+    if modifier in mod_keys:
+        modifier = mod_keys[modifier]
+    else:
+        return ''
+    if key in base_keys:
+        key = base_keys[key]
+    else:
+        return ''
+    if (modifier == 'left_shift' or modifier == 'right_shift') and key in shift_map:
+        key = shift_map[key]
+
+    return key
+
+
+class Keyboard:
+    def __init__(self):  # type: ignore
+        self.report = [
+            [  # Bit array for Modifier keys
+                0,  # Right GUI - (usually the Windows key)
+                0,  # Right ALT
+                0,  # Right Shift
+                0,  # Right Control
+                0,  # Left GUI - (usually the Windows key)
+                0,  # Left ALT
+                0,  # Left Shift
+                0,  # Left Control
+            ],
+            0x00,  # Vendor reserved
+            '',  # Rest is space for 6 keys
+            '',
+            '',
+            '',
+            '',
+            '',
+        ]
+
+    def decode_keyboard_report(self, input_report: bytes, report_length: int) -> None:
+        if report_length >= 8:
+            modifier = input_report[1]
+            self.report[0] = [int(x) for x in '{0:08b}'.format(modifier)]
+            self.report[0].reverse()  # type: ignore
+
+            modifier_key = str((modifier & 0x22).to_bytes(1, "big").hex())
+            keycodes = []
+            for k in range(3, report_length):
+                keycodes.append(str(input_report[k].to_bytes(1, "big").hex()))
+                self.report[k - 1] = get_key(modifier_key, keycodes[k - 3])
+        else:
+            print(color('Warning: Not able to parse report', 'yellow'))
+
+    def print_keyboard_report(self) -> None:
+        print(color('\tKeyboard Input Received', 'green', None, 'bold'))
+        print(color(f'Keys:', 'white', None, 'bold'))
+        for i in range(1, 7):
+            print(
+                color(f' Key{i}{" ":>8s}=  ', 'cyan', None, 'bold'), self.report[i + 1]
+            )
+        print(color(f'\nModifier Keys:', 'white', None, 'bold'))
+        print(
+            color(f'  Left Ctrl   : ', 'cyan'),
+            f'{self.report[0][0] == 1!s:<5}',  # type: ignore
+            color(f'  Left Shift  : ', 'cyan'),
+            f'{self.report[0][1] == 1!s:<5}',  # type: ignore
+            color(f'  Left ALT    : ', 'cyan'),
+            f'{self.report[0][2] == 1!s:<5}',  # type: ignore
+            color(f'  Left GUI    : ', 'cyan'),
+            f'{self.report[0][3] == 1!s:<5}\n',  # type: ignore
+            color(f' Right Ctrl  : ', 'cyan'),
+            f'{self.report[0][4] == 1!s:<5}',  # type: ignore
+            color(f'  Right Shift : ', 'cyan'),
+            f'{self.report[0][5] == 1!s:<5}',  # type: ignore
+            color(f'  Right ALT   : ', 'cyan'),
+            f'{self.report[0][6] == 1!s:<5}',  # type: ignore
+            color(f'  Right GUI   : ', 'cyan'),
+            f'{self.report[0][7] == 1!s:<5}',  # type: ignore
+        )
+
+
+# ------------------------------------------------------------------------------
+class Mouse:
+    def __init__(self):  # type: ignore
+        self.report = [
+            [  # Bit array for Buttons
+                0,  # Button 1 (primary/trigger
+                0,  # Button 2 (secondary)
+                0,  # Button 3 (tertiary)
+                0,  # Button 4
+                0,  # Button 5
+                0,  # unused padding bits
+                0,  # unused padding bits
+                0,  # unused padding bits
+            ],
+            0,  # X
+            0,  # Y
+            0,  # Wheel
+            0,  # AC Pan
+        ]
+
+    def decode_mouse_report(self, input_report: bytes, report_length: int) -> None:
+        self.report[0] = [int(x) for x in '{0:08b}'.format(input_report[1])]
+        self.report[0].reverse()  # type: ignore
+        self.report[1] = input_report[2]
+        self.report[2] = input_report[3]
+        if report_length in [5, 6]:
+            self.report[3] = input_report[4]
+            self.report[4] = input_report[5] if report_length == 6 else 0
+
+    def print_mouse_report(self) -> None:
+        print(color('\tMouse Input Received', 'green', None, 'bold'))
+        print(
+            color(f' Button 1 (primary/trigger) = ', 'cyan'),
+            self.report[0][0] == 1,  # type: ignore
+            color(f'\n Button 2 (secondary)       = ', 'cyan'),
+            self.report[0][1] == 1,  # type: ignore
+            color(f'\n Button 3 (tertiary)        = ', 'cyan'),
+            self.report[0][2] == 1,  # type: ignore
+            color(f'\n Button4                    = ', 'cyan'),
+            self.report[0][3] == 1,  # type: ignore
+            color(f'\n Button5                    = ', 'cyan'),
+            self.report[0][4] == 1,  # type: ignore
+            color(f'\n X (X-axis displacement)    = ', 'cyan'),
+            self.report[1],
+            color(f'\n Y (Y-axis displacement)    = ', 'cyan'),
+            self.report[2],
+            color(f'\n Wheel                      = ', 'cyan'),
+            self.report[3],
+            color(f'\n AC PAN                     = ', 'cyan'),
+            self.report[4],
+        )
+
+
+# ------------------------------------------------------------------------------
+class ReportParser:
+    @staticmethod
+    def parse_input_report(input_report: bytes) -> None:
+
+        report_id = input_report[0]  # pylint: disable=unsubscriptable-object
+        report_length = len(input_report)
+
+        # Keyboard input report (report id = 1)
+        if report_id == 1 and report_length >= 8:
+            keyboard = Keyboard()  # type: ignore
+            keyboard.decode_keyboard_report(input_report, report_length)
+            keyboard.print_keyboard_report()
+        # Mouse input report (report id = 2)
+        elif report_id == 2 and report_length in [4, 5, 6]:
+            mouse = Mouse()  # type: ignore
+            mouse.decode_mouse_report(input_report, report_length)
+            mouse.print_mouse_report()
+        else:
+            print(color(f'Warning: Parse Error Report ID {report_id}', 'yellow'))
diff --git a/examples/run_a2dp_sink.py b/examples/run_a2dp_sink.py
index b20f0d6..61bdce3 100644
--- a/examples/run_a2dp_sink.py
+++ b/examples/run_a2dp_sink.py
@@ -131,7 +131,7 @@
             await device.power_on()
 
             # Create a listener to wait for AVDTP connections
-            listener = Listener(Listener.create_registrar(device))
+            listener = Listener.for_device(device)
             listener.on('connection', on_avdtp_connection)
 
             if len(sys.argv) >= 5:
diff --git a/examples/run_a2dp_source.py b/examples/run_a2dp_source.py
index 2443518..69dc2d0 100644
--- a/examples/run_a2dp_source.py
+++ b/examples/run_a2dp_source.py
@@ -179,7 +179,7 @@
                 await stream_packets(read, protocol)
             else:
                 # Create a listener to wait for AVDTP connections
-                listener = Listener(Listener.create_registrar(device), version=(1, 2))
+                listener = Listener.for_device(device=device, version=(1, 2))
                 listener.on(
                     'connection', lambda protocol: on_avdtp_connection(read, protocol)
                 )
diff --git a/examples/run_asha_sink.py b/examples/run_asha_sink.py
index 3e4955d..2d6f0d5 100644
--- a/examples/run_asha_sink.py
+++ b/examples/run_asha_sink.py
@@ -21,6 +21,7 @@
 import os
 import logging
 
+from bumble import l2cap
 from bumble.core import AdvertisingData
 from bumble.device import Device
 from bumble.transport import open_transport_or_link
@@ -95,8 +96,10 @@
 
             channel.sink = on_data
 
-        psm = device.register_l2cap_channel_server(0, on_coc, 8)
-        print(f'### LE_PSM_OUT = {psm}')
+        server = device.create_l2cap_server(
+            spec=l2cap.LeCreditBasedChannelSpec(max_credits=8), handler=on_coc
+        )
+        print(f'### LE_PSM_OUT = {server.psm}')
 
         # Add the ASHA service to the GATT server
         read_only_properties_characteristic = Characteristic(
@@ -147,7 +150,7 @@
             ASHA_LE_PSM_OUT_CHARACTERISTIC,
             Characteristic.Properties.READ,
             Characteristic.READABLE,
-            struct.pack('<H', psm),
+            struct.pack('<H', server.psm),
         )
         device.add_service(
             Service(
diff --git a/examples/run_hid_host.py b/examples/run_hid_host.py
new file mode 100644
index 0000000..a174444
--- /dev/null
+++ b/examples/run_hid_host.py
@@ -0,0 +1,540 @@
+# Copyright 2021-2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# -----------------------------------------------------------------------------
+# Imports
+# -----------------------------------------------------------------------------
+import asyncio
+import sys
+import os
+import logging
+
+from bumble.colors import color
+
+import bumble.core
+from bumble.device import Device
+from bumble.transport import open_transport_or_link
+from bumble.core import (
+    BT_L2CAP_PROTOCOL_ID,
+    BT_HIDP_PROTOCOL_ID,
+    BT_HUMAN_INTERFACE_DEVICE_SERVICE,
+    BT_BR_EDR_TRANSPORT,
+)
+from bumble.hci import Address
+from bumble.hid import Host, Message
+from bumble.sdp import (
+    Client as SDP_Client,
+    DataElement,
+    ServiceAttribute,
+    SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID,
+    SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID,
+    SDP_BLUETOOTH_PROFILE_DESCRIPTOR_LIST_ATTRIBUTE_ID,
+    SDP_ALL_ATTRIBUTES_RANGE,
+    SDP_LANGUAGE_BASE_ATTRIBUTE_ID_LIST_ATTRIBUTE_ID,
+    SDP_ADDITIONAL_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID,
+    SDP_SERVICE_RECORD_HANDLE_ATTRIBUTE_ID,
+    SDP_BROWSE_GROUP_LIST_ATTRIBUTE_ID,
+)
+from hid_report_parser import ReportParser
+
+# -----------------------------------------------------------------------------
+# SDP attributes for Bluetooth HID devices
+SDP_HID_SERVICE_NAME_ATTRIBUTE_ID = 0x0100
+SDP_HID_SERVICE_DESCRIPTION_ATTRIBUTE_ID = 0x0101
+SDP_HID_PROVIDER_NAME_ATTRIBUTE_ID = 0x0102
+SDP_HID_DEVICE_RELEASE_NUMBER_ATTRIBUTE_ID = 0x0200  # [DEPRECATED]
+SDP_HID_PARSER_VERSION_ATTRIBUTE_ID = 0x0201
+SDP_HID_DEVICE_SUBCLASS_ATTRIBUTE_ID = 0x0202
+SDP_HID_COUNTRY_CODE_ATTRIBUTE_ID = 0x0203
+SDP_HID_VIRTUAL_CABLE_ATTRIBUTE_ID = 0x0204
+SDP_HID_RECONNECT_INITIATE_ATTRIBUTE_ID = 0x0205
+SDP_HID_DESCRIPTOR_LIST_ATTRIBUTE_ID = 0x0206
+SDP_HID_LANGID_BASE_LIST_ATTRIBUTE_ID = 0x0207
+SDP_HID_SDP_DISABLE_ATTRIBUTE_ID = 0x0208  # [DEPRECATED]
+SDP_HID_BATTERY_POWER_ATTRIBUTE_ID = 0x0209
+SDP_HID_REMOTE_WAKE_ATTRIBUTE_ID = 0x020A
+SDP_HID_PROFILE_VERSION_ATTRIBUTE_ID = 0x020B  # DEPRECATED]
+SDP_HID_SUPERVISION_TIMEOUT_ATTRIBUTE_ID = 0x020C
+SDP_HID_NORMALLY_CONNECTABLE_ATTRIBUTE_ID = 0x020D
+SDP_HID_BOOT_DEVICE_ATTRIBUTE_ID = 0x020E
+SDP_HID_SSR_HOST_MAX_LATENCY_ATTRIBUTE_ID = 0x020F
+SDP_HID_SSR_HOST_MIN_TIMEOUT_ATTRIBUTE_ID = 0x0210
+
+
+# -----------------------------------------------------------------------------
+
+
+async def get_hid_device_sdp_record(device, connection):
+
+    # Connect to the SDP Server
+    sdp_client = SDP_Client(device)
+    await sdp_client.connect(connection)
+    if sdp_client:
+        print(color('Connected to SDP Server', 'blue'))
+    else:
+        print(color('Failed to connect to SDP Server', 'red'))
+
+    # List BT HID Device service in the root browse group
+    service_record_handles = await sdp_client.search_services(
+        [BT_HUMAN_INTERFACE_DEVICE_SERVICE]
+    )
+
+    if len(service_record_handles) < 1:
+        await sdp_client.disconnect()
+        raise Exception(
+            color(f'BT HID Device service not found on peer device!!!!', 'red')
+        )
+
+    # For BT_HUMAN_INTERFACE_DEVICE_SERVICE service, get all its attributes
+    for service_record_handle in service_record_handles:
+        attributes = await sdp_client.get_attributes(
+            service_record_handle, [SDP_ALL_ATTRIBUTES_RANGE]
+        )
+        print(color(f'SERVICE {service_record_handle:04X} attributes:', 'yellow'))
+        print(color(f'SDP attributes for HID device', 'magenta'))
+        for attribute in attributes:
+            if attribute.id == SDP_SERVICE_RECORD_HANDLE_ATTRIBUTE_ID:
+                print(
+                    color('  Service Record Handle : ', 'cyan'),
+                    hex(attribute.value.value),
+                )
+
+            elif attribute.id == SDP_SERVICE_CLASS_ID_LIST_ATTRIBUTE_ID:
+                print(
+                    color('  Service Class : ', 'cyan'), attribute.value.value[0].value
+                )
+
+            elif attribute.id == SDP_BROWSE_GROUP_LIST_ATTRIBUTE_ID:
+                print(
+                    color('  SDP Browse Group List : ', 'cyan'),
+                    attribute.value.value[0].value,
+                )
+
+            elif attribute.id == SDP_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID:
+                print(
+                    color('  BT_L2CAP_PROTOCOL_ID : ', 'cyan'),
+                    attribute.value.value[0].value[0].value,
+                )
+                print(
+                    color('  PSM for Bluetooth HID Control channel : ', 'cyan'),
+                    hex(attribute.value.value[0].value[1].value),
+                )
+                print(
+                    color('  BT_HIDP_PROTOCOL_ID : ', 'cyan'),
+                    attribute.value.value[1].value[0].value,
+                )
+
+            elif attribute.id == SDP_LANGUAGE_BASE_ATTRIBUTE_ID_LIST_ATTRIBUTE_ID:
+                print(
+                    color('  Lanugage : ', 'cyan'), hex(attribute.value.value[0].value)
+                )
+                print(
+                    color('  Encoding : ', 'cyan'), hex(attribute.value.value[1].value)
+                )
+                print(
+                    color('  PrimaryLanguageBaseID : ', 'cyan'),
+                    hex(attribute.value.value[2].value),
+                )
+
+            elif attribute.id == SDP_BLUETOOTH_PROFILE_DESCRIPTOR_LIST_ATTRIBUTE_ID:
+                print(
+                    color('  BT_HUMAN_INTERFACE_DEVICE_SERVICE ', 'cyan'),
+                    attribute.value.value[0].value[0].value,
+                )
+                print(
+                    color('  HID Profileversion number : ', 'cyan'),
+                    hex(attribute.value.value[0].value[1].value),
+                )
+
+            elif attribute.id == SDP_ADDITIONAL_PROTOCOL_DESCRIPTOR_LIST_ATTRIBUTE_ID:
+                print(
+                    color('  BT_L2CAP_PROTOCOL_ID : ', 'cyan'),
+                    attribute.value.value[0].value[0].value[0].value,
+                )
+                print(
+                    color('  PSM for Bluetooth HID Interrupt channel : ', 'cyan'),
+                    hex(attribute.value.value[0].value[0].value[1].value),
+                )
+                print(
+                    color('  BT_HIDP_PROTOCOL_ID : ', 'cyan'),
+                    attribute.value.value[0].value[1].value[0].value,
+                )
+
+            elif attribute.id == SDP_HID_SERVICE_NAME_ATTRIBUTE_ID:
+                print(color('  Service Name: ', 'cyan'), attribute.value.value)
+
+            elif attribute.id == SDP_HID_SERVICE_DESCRIPTION_ATTRIBUTE_ID:
+                print(color('  Service Description: ', 'cyan'), attribute.value.value)
+
+            elif attribute.id == SDP_HID_PROVIDER_NAME_ATTRIBUTE_ID:
+                print(color('  Provider Name: ', 'cyan'), attribute.value.value)
+
+            elif attribute.id == SDP_HID_DEVICE_RELEASE_NUMBER_ATTRIBUTE_ID:
+                print(color('  Release Number: ', 'cyan'), hex(attribute.value.value))
+
+            elif attribute.id == SDP_HID_PARSER_VERSION_ATTRIBUTE_ID:
+                print(
+                    color('  HID Parser Version: ', 'cyan'), hex(attribute.value.value)
+                )
+
+            elif attribute.id == SDP_HID_DEVICE_SUBCLASS_ATTRIBUTE_ID:
+                print(
+                    color('  HIDDeviceSubclass: ', 'cyan'), hex(attribute.value.value)
+                )
+
+            elif attribute.id == SDP_HID_COUNTRY_CODE_ATTRIBUTE_ID:
+                print(color('  HIDCountryCode: ', 'cyan'), hex(attribute.value.value))
+
+            elif attribute.id == SDP_HID_VIRTUAL_CABLE_ATTRIBUTE_ID:
+                print(color('  HIDVirtualCable: ', 'cyan'), attribute.value.value)
+
+            elif attribute.id == SDP_HID_RECONNECT_INITIATE_ATTRIBUTE_ID:
+                print(color('  HIDReconnectInitiate: ', 'cyan'), attribute.value.value)
+
+            elif attribute.id == SDP_HID_DESCRIPTOR_LIST_ATTRIBUTE_ID:
+                print(
+                    color('  HID Report Descriptor type: ', 'cyan'),
+                    hex(attribute.value.value[0].value[0].value),
+                )
+                print(
+                    color('  HID Report DescriptorList: ', 'cyan'),
+                    attribute.value.value[0].value[1].value,
+                )
+
+            elif attribute.id == SDP_HID_LANGID_BASE_LIST_ATTRIBUTE_ID:
+                print(
+                    color('  HID LANGID Base Language: ', 'cyan'),
+                    hex(attribute.value.value[0].value[0].value),
+                )
+                print(
+                    color('  HID LANGID Base Bluetooth String Offset: ', 'cyan'),
+                    hex(attribute.value.value[0].value[1].value),
+                )
+
+            elif attribute.id == SDP_HID_BATTERY_POWER_ATTRIBUTE_ID:
+                print(color('  HIDBatteryPower: ', 'cyan'), attribute.value.value)
+
+            elif attribute.id == SDP_HID_REMOTE_WAKE_ATTRIBUTE_ID:
+                print(color('  HIDRemoteWake: ', 'cyan'), attribute.value.value)
+
+            elif attribute.id == SDP_HID_PROFILE_VERSION_ATTRIBUTE_ID:
+                print(
+                    color('  HIDProfileVersion : ', 'cyan'), hex(attribute.value.value)
+                )
+
+            elif attribute.id == SDP_HID_SUPERVISION_TIMEOUT_ATTRIBUTE_ID:
+                print(
+                    color('  HIDSupervisionTimeout: ', 'cyan'),
+                    hex(attribute.value.value),
+                )
+
+            elif attribute.id == SDP_HID_NORMALLY_CONNECTABLE_ATTRIBUTE_ID:
+                print(
+                    color('  HIDNormallyConnectable: ', 'cyan'), attribute.value.value
+                )
+
+            elif attribute.id == SDP_HID_BOOT_DEVICE_ATTRIBUTE_ID:
+                print(color('  HIDBootDevice: ', 'cyan'), attribute.value.value)
+
+            elif attribute.id == SDP_HID_SSR_HOST_MAX_LATENCY_ATTRIBUTE_ID:
+                print(
+                    color('  HIDSSRHostMaxLatency: ', 'cyan'),
+                    hex(attribute.value.value),
+                )
+
+            elif attribute.id == SDP_HID_SSR_HOST_MIN_TIMEOUT_ATTRIBUTE_ID:
+                print(
+                    color('  HIDSSRHostMinTimeout: ', 'cyan'),
+                    hex(attribute.value.value),
+                )
+
+            else:
+                print(
+                    color(
+                        f'  Warning: Attribute ID: {attribute.id} match not found.\n  Attribute Info: {attribute}',
+                        'yellow',
+                    )
+                )
+
+    await sdp_client.disconnect()
+
+
+# -----------------------------------------------------------------------------
+async def get_stream_reader(pipe) -> asyncio.StreamReader:
+    loop = asyncio.get_event_loop()
+    reader = asyncio.StreamReader(loop=loop)
+    protocol = asyncio.StreamReaderProtocol(reader)
+    await loop.connect_read_pipe(lambda: protocol, pipe)
+    return reader
+
+
+# -----------------------------------------------------------------------------
+async def main():
+    if len(sys.argv) < 4:
+        print(
+            'Usage: run_hid_host.py <device-config> <transport-spec> '
+            '<bluetooth-address> [test-mode]'
+        )
+
+        print('example: run_hid_host.py classic1.json usb:0 E1:CA:72:48:C4:E8/P')
+        return
+
+    def on_hid_data_cb(pdu):
+        report_type = pdu[0] & 0x0F
+        if len(pdu) == 1:
+            print(color(f'Warning: No report received', 'yellow'))
+            return
+        report_length = len(pdu[1:])
+        report_id = pdu[1]
+
+        if report_type != Message.ReportType.OTHER_REPORT:
+            print(
+                color(
+                    f' Report type = {report_type}, Report length = {report_length}, Report id = {report_id}',
+                    'blue',
+                    None,
+                    'bold',
+                )
+            )
+
+        if (report_length <= 1) or (report_id == 0):
+            return
+
+        if report_type == Message.ReportType.INPUT_REPORT:
+            ReportParser.parse_input_report(pdu[1:])  # type: ignore
+
+    async def handle_virtual_cable_unplug():
+        await hid_host.disconnect_interrupt_channel()
+        await hid_host.disconnect_control_channel()
+        await device.keystore.delete(target_address)  # type: ignore
+        await connection.disconnect()
+
+    def on_hid_virtual_cable_unplug_cb():
+        asyncio.create_task(handle_virtual_cable_unplug())
+
+    print('<<< connecting to HCI...')
+    async with await open_transport_or_link(sys.argv[2]) as (hci_source, hci_sink):
+        print('<<< CONNECTED')
+
+        # Create a device
+        device = Device.from_config_file_with_hci(sys.argv[1], hci_source, hci_sink)
+        device.classic_enabled = True
+        await device.power_on()
+
+        # Connect to a peer
+        target_address = sys.argv[3]
+        print(f'=== Connecting to {target_address}...')
+        connection = await device.connect(target_address, transport=BT_BR_EDR_TRANSPORT)
+        print(f'=== Connected to {connection.peer_address}!')
+
+        # Request authentication
+        print('*** Authenticating...')
+        await connection.authenticate()
+        print('*** Authenticated...')
+
+        # Enable encryption
+        print('*** Enabling encryption...')
+        await connection.encrypt()
+        print('*** Encryption on')
+
+        await get_hid_device_sdp_record(device, connection)
+
+        # Create HID host and start it
+        print('@@@ Starting HID Host...')
+        hid_host = Host(device, connection)
+
+        # Register for HID data call back
+        hid_host.on('data', on_hid_data_cb)
+
+        # Register for virtual cable unplug call back
+        hid_host.on('virtual_cable_unplug', on_hid_virtual_cable_unplug_cb)
+
+        async def menu():
+            reader = await get_stream_reader(sys.stdin)
+            while True:
+                print(
+                    "\n************************ HID Host Menu *****************************\n"
+                )
+                print(" 1. Connect Control Channel")
+                print(" 2. Connect Interrupt Channel")
+                print(" 3. Disconnect Control Channel")
+                print(" 4. Disconnect Interrupt Channel")
+                print(" 5. Get Report")
+                print(" 6. Set Report")
+                print(" 7. Set Protocol Mode")
+                print(" 8. Get Protocol Mode")
+                print(" 9. Send Report")
+                print("10. Suspend")
+                print("11. Exit Suspend")
+                print("12. Virtual Cable Unplug")
+                print("13. Disconnect device")
+                print("14. Delete Bonding")
+                print("15. Re-connect to device")
+                print("\nEnter your choice : \n")
+
+                choice = await reader.readline()
+                choice = choice.decode('utf-8').strip()
+
+                if choice == '1':
+                    await hid_host.connect_control_channel()
+
+                elif choice == '2':
+                    await hid_host.connect_interrupt_channel()
+
+                elif choice == '3':
+                    await hid_host.disconnect_control_channel()
+
+                elif choice == '4':
+                    await hid_host.disconnect_interrupt_channel()
+
+                elif choice == '5':
+                    print(" 1. Report ID 0x02")
+                    print(" 2. Report ID 0x03")
+                    print(" 3. Report ID 0x05")
+                    choice1 = await reader.readline()
+                    choice1 = choice1.decode('utf-8').strip()
+
+                    if choice1 == '1':
+                        hid_host.get_report(1, 2, 3)
+
+                    elif choice1 == '2':
+                        hid_host.get_report(2, 3, 2)
+
+                    elif choice1 == '3':
+                        hid_host.get_report(3, 5, 3)
+
+                    else:
+                        print('Incorrect option selected')
+
+                elif choice == '6':
+                    print(" 1. Report type 1 and Report id 0x01")
+                    print(" 2. Report type 2 and Report id 0x03")
+                    print(" 3. Report type 3 and Report id 0x05")
+                    choice1 = await reader.readline()
+                    choice1 = choice1.decode('utf-8').strip()
+
+                    if choice1 == '1':
+                        # data includes first octet as report id
+                        data = bytearray(
+                            [0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01]
+                        )
+                        hid_host.set_report(1, data)
+
+                    elif choice1 == '2':
+                        data = bytearray([0x03, 0x01, 0x01])
+                        hid_host.set_report(2, data)
+
+                    elif choice1 == '3':
+                        data = bytearray([0x05, 0x01, 0x01, 0x01])
+                        hid_host.set_report(3, data)
+
+                    else:
+                        print('Incorrect option selected')
+
+                elif choice == '7':
+                    print(" 0. Boot")
+                    print(" 1. Report")
+                    choice1 = await reader.readline()
+                    choice1 = choice1.decode('utf-8').strip()
+
+                    if choice1 == '0':
+                        hid_host.set_protocol(Message.ProtocolMode.BOOT_PROTOCOL)
+
+                    elif choice1 == '1':
+                        hid_host.set_protocol(Message.ProtocolMode.REPORT_PROTOCOL)
+
+                    else:
+                        print('Incorrect option selected')
+
+                elif choice == '8':
+                    hid_host.get_protocol()
+
+                elif choice == '9':
+                    print(" 1. Report ID 0x01")
+                    print(" 2. Report ID 0x03")
+                    choice1 = await reader.readline()
+                    choice1 = choice1.decode('utf-8').strip()
+
+                    if choice1 == '1':
+                        data = bytearray(
+                            [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+                        )
+                        hid_host.send_data(data)
+
+                    elif choice1 == '2':
+                        data = bytearray([0x03, 0x00, 0x0D, 0xFD, 0x00, 0x00])
+                        hid_host.send_data(data)
+
+                    else:
+                        print('Incorrect option selected')
+
+                elif choice == '10':
+                    hid_host.suspend()
+
+                elif choice == '11':
+                    hid_host.exit_suspend()
+
+                elif choice == '12':
+                    hid_host.virtual_cable_unplug()
+                    try:
+                        await device.keystore.delete(target_address)
+                    except KeyError:
+                        print('Device not found or Device already unpaired.')
+
+                elif choice == '13':
+                    peer_address = Address.from_string_for_transport(
+                        target_address, transport=BT_BR_EDR_TRANSPORT
+                    )
+                    connection = device.find_connection_by_bd_addr(
+                        peer_address, transport=BT_BR_EDR_TRANSPORT
+                    )
+                    if connection is not None:
+                        await connection.disconnect()
+                    else:
+                        print("Already disconnected from device")
+
+                elif choice == '14':
+                    try:
+                        await device.keystore.delete(target_address)
+                        print("Unpair successful")
+                    except KeyError:
+                        print('Device not found or Device already unpaired.')
+
+                elif choice == '15':
+                    connection = await device.connect(
+                        target_address, transport=BT_BR_EDR_TRANSPORT
+                    )
+                    await connection.authenticate()
+                    await connection.encrypt()
+
+                else:
+                    print("Invalid option selected.")
+
+        if (len(sys.argv) > 4) and (sys.argv[4] == 'test-mode'):
+            # Enabling menu for testing
+            await menu()
+        else:
+            # HID Connection
+            # Control channel
+            await hid_host.connect_control_channel()
+            # Interrupt Channel
+            await hid_host.connect_interrupt_channel()
+
+        await hci_source.wait_for_termination()
+
+
+# -----------------------------------------------------------------------------
+
+logging.basicConfig(level=os.environ.get('BUMBLE_LOGLEVEL', 'DEBUG').upper())
+asyncio.run(main())
diff --git a/extras/android/RemoteHCI/.gitignore b/extras/android/RemoteHCI/.gitignore
new file mode 100644
index 0000000..10cfdbf
--- /dev/null
+++ b/extras/android/RemoteHCI/.gitignore
@@ -0,0 +1,10 @@
+*.iml
+.gradle
+/local.properties
+/.idea
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/extras/android/RemoteHCI/app/.gitignore b/extras/android/RemoteHCI/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/extras/android/RemoteHCI/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/extras/android/RemoteHCI/app/build.gradle.kts b/extras/android/RemoteHCI/app/build.gradle.kts
new file mode 100644
index 0000000..2e2df38
--- /dev/null
+++ b/extras/android/RemoteHCI/app/build.gradle.kts
@@ -0,0 +1,73 @@
+@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
+plugins {
+    alias(libs.plugins.androidApplication)
+    alias(libs.plugins.kotlinAndroid)
+}
+
+android {
+    namespace = "com.github.google.bumble.remotehci"
+    compileSdk = 33
+
+    defaultConfig {
+        applicationId = "com.github.google.bumble.remotehci"
+        minSdk = 26
+        targetSdk = 33
+        versionCode = 1
+        versionName = "1.0"
+
+        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+        vectorDrawables {
+            useSupportLibrary = true
+        }
+    }
+
+    buildTypes {
+        release {
+            isMinifyEnabled = false
+            proguardFiles(
+                getDefaultProguardFile("proguard-android-optimize.txt"),
+                "proguard-rules.pro"
+            )
+        }
+    }
+    compileOptions {
+        sourceCompatibility = JavaVersion.VERSION_1_8
+        targetCompatibility = JavaVersion.VERSION_1_8
+    }
+    kotlinOptions {
+        jvmTarget = "1.8"
+    }
+    buildFeatures {
+        compose = true
+        aidl = false
+    }
+    composeOptions {
+        kotlinCompilerExtensionVersion = "1.4.3"
+    }
+    packaging {
+        resources {
+            excludes += "/META-INF/{AL2.0,LGPL2.1}"
+        }
+    }
+}
+
+dependencies {
+    implementation(kotlin("reflect"))
+    implementation(libs.core.ktx)
+    implementation(libs.lifecycle.runtime.ktx)
+    implementation(libs.activity.compose)
+    implementation(platform(libs.compose.bom))
+    implementation(libs.ui)
+    implementation(libs.ui.graphics)
+    implementation(libs.ui.tooling.preview)
+    implementation(libs.material3)
+    compileOnly(project(":lib"))
+    testImplementation(libs.junit)
+    androidTestImplementation(libs.androidx.test.ext.junit)
+    androidTestImplementation(libs.espresso.core)
+    androidTestImplementation(platform(libs.compose.bom))
+    androidTestImplementation(libs.ui.test.junit4)
+    debugImplementation(libs.ui.tooling)
+    debugImplementation(libs.ui.test.manifest)
+    //compileOnly(files("${project.rootDir.absolutePath}/sdk/framework.jar"))
+}
\ No newline at end of file
diff --git a/extras/android/RemoteHCI/app/proguard-rules.pro b/extras/android/RemoteHCI/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/extras/android/RemoteHCI/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/extras/android/RemoteHCI/app/src/main/AndroidManifest.xml b/extras/android/RemoteHCI/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..2cca4ad
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools">
+
+    <uses-permission android:name="android.permission.INTERNET" />
+
+    <application
+        android:allowBackup="true"
+        android:dataExtractionRules="@xml/data_extraction_rules"
+        android:fullBackupContent="@xml/backup_rules"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/Theme.RemoteHCI"
+        tools:targetApi="31">
+        <activity
+            android:name=".MainActivity"
+            android:exported="true"
+            android:label="@string/app_name"
+            android:theme="@style/Theme.RemoteHCI">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/extras/android/RemoteHCI/app/src/main/aidl/android/hardware/bluetooth/IBluetoothHci.aidl b/extras/android/RemoteHCI/app/src/main/aidl/android/hardware/bluetooth/IBluetoothHci.aidl
new file mode 100644
index 0000000..92feaa5
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/aidl/android/hardware/bluetooth/IBluetoothHci.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.bluetooth;
+@VintfStability
+interface IBluetoothHci {
+  void close();
+  void initialize(in android.hardware.bluetooth.IBluetoothHciCallbacks callback);
+  void sendAclData(in byte[] data);
+  void sendHciCommand(in byte[] command);
+  void sendIsoData(in byte[] data);
+  void sendScoData(in byte[] data);
+}
\ No newline at end of file
diff --git a/extras/android/RemoteHCI/app/src/main/aidl/android/hardware/bluetooth/IBluetoothHciCallbacks.aidl b/extras/android/RemoteHCI/app/src/main/aidl/android/hardware/bluetooth/IBluetoothHciCallbacks.aidl
new file mode 100644
index 0000000..f0d8c29
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/aidl/android/hardware/bluetooth/IBluetoothHciCallbacks.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.bluetooth;
+@VintfStability
+interface IBluetoothHciCallbacks {
+  void aclDataReceived(in byte[] data);
+  void hciEventReceived(in byte[] event);
+  void initializationComplete(in android.hardware.bluetooth.Status status);
+  void isoDataReceived(in byte[] data);
+  void scoDataReceived(in byte[] data);
+}
\ No newline at end of file
diff --git a/extras/android/RemoteHCI/app/src/main/aidl/android/hardware/bluetooth/Status.aidl b/extras/android/RemoteHCI/app/src/main/aidl/android/hardware/bluetooth/Status.aidl
new file mode 100644
index 0000000..f3ba792
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/aidl/android/hardware/bluetooth/Status.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.bluetooth;
+@Backing(type="int")
+@VintfStability
+enum Status {
+  SUCCESS = 0,
+  ALREADY_INITIALIZED = 1,
+  UNABLE_TO_OPEN_INTERFACE = 2,
+  HARDWARE_INITIALIZATION_ERROR = 3,
+  UNKNOWN = 4,
+}
\ No newline at end of file
diff --git a/extras/android/RemoteHCI/app/src/main/hidl/bluetooth/1.1/IBluetoothHci.hal b/extras/android/RemoteHCI/app/src/main/hidl/bluetooth/1.1/IBluetoothHci.hal
new file mode 100644
index 0000000..b7845d5
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/hidl/bluetooth/1.1/IBluetoothHci.hal
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.bluetooth@1.1;
+
+import @1.0::HciPacket;
+import @1.0::IBluetoothHci;
+import IBluetoothHciCallbacks;
+
+/**
+ * The Host Controller Interface (HCI) is the layer defined by the Bluetooth
+ * specification between the software that runs on the host and the Bluetooth
+ * controller chip. This boundary is the natural choice for a Hardware
+ * Abstraction Layer (HAL). Dealing only in HCI packets and events simplifies
+ * the stack and abstracts away power management, initialization, and other
+ * implementation-specific details related to the hardware.
+ */
+interface IBluetoothHci extends @1.0::IBluetoothHci {
+    /**
+     * Same as @1.0, but uses 1.1 Callbacks version
+     */
+    initialize_1_1(@1.1::IBluetoothHciCallbacks callback);
+
+    /**
+     * Send an ISO data packet (as specified in the Bluetooth Core
+     * Specification v5.2) to the Bluetooth controller.
+     * Packets must be processed in order.
+     * @param data HCI data packet to be sent
+     */
+    sendIsoData(HciPacket data);
+};
\ No newline at end of file
diff --git a/extras/android/RemoteHCI/app/src/main/hidl/bluetooth/1.1/IBluetoothHciCallbacks.hal b/extras/android/RemoteHCI/app/src/main/hidl/bluetooth/1.1/IBluetoothHciCallbacks.hal
new file mode 100644
index 0000000..b8d0b8a
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/hidl/bluetooth/1.1/IBluetoothHciCallbacks.hal
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.bluetooth@1.1;
+
+import @1.0::HciPacket;
+import @1.0::IBluetoothHciCallbacks;
+
+/**
+ * The interface from the Bluetooth Controller to the stack.
+ */
+interface IBluetoothHciCallbacks extends @1.0::IBluetoothHciCallbacks {
+    /**
+     * Send a ISO data packet form the controller to the host.
+     * @param data the ISO HCI packet to be passed to the host stack
+     */
+    isoDataReceived(HciPacket data);
+};
\ No newline at end of file
diff --git a/extras/android/RemoteHCI/app/src/main/ic_launcher-playstore.png b/extras/android/RemoteHCI/app/src/main/ic_launcher-playstore.png
new file mode 100644
index 0000000..b0a2f40
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/ic_launcher-playstore.png
Binary files differ
diff --git a/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/IBluetoothHci.java b/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/IBluetoothHci.java
new file mode 100644
index 0000000..c235137
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/IBluetoothHci.java
@@ -0,0 +1,259 @@
+/*
+ * This file is auto-generated.  DO NOT MODIFY.
+ */
+package android.hardware.bluetooth;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public interface IBluetoothHci extends android.os.IInterface
+{
+  /** Default implementation for IBluetoothHci. */
+  public static class Default implements android.hardware.bluetooth.IBluetoothHci
+  {
+    @Override public void close() throws android.os.RemoteException
+    {
+    }
+    @Override public void initialize(android.hardware.bluetooth.IBluetoothHciCallbacks callback) throws android.os.RemoteException
+    {
+    }
+    @Override public void sendAclData(byte[] data) throws android.os.RemoteException
+    {
+    }
+    @Override public void sendHciCommand(byte[] command) throws android.os.RemoteException
+    {
+    }
+    @Override public void sendIsoData(byte[] data) throws android.os.RemoteException
+    {
+    }
+    @Override public void sendScoData(byte[] data) throws android.os.RemoteException
+    {
+    }
+    @Override
+    public android.os.IBinder asBinder() {
+      return null;
+    }
+  }
+  /** Local-side IPC implementation stub class. */
+  public static abstract class Stub extends android.os.Binder implements android.hardware.bluetooth.IBluetoothHci
+  {
+    /** Construct the stub at attach it to the interface. */
+    public Stub()
+    {
+      //this.markVintfStability();
+      try {
+        Method method = this.getClass().getMethod("markVintfStability", (Class<?>[])null);
+        method.invoke(this);
+      } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
+        throw new RuntimeException(e);
+      }
+      this.attachInterface(this, DESCRIPTOR);
+    }
+    /**
+     * Cast an IBinder object into an android.hardware.bluetooth.IBluetoothHci interface,
+     * generating a proxy if needed.
+     */
+    public static android.hardware.bluetooth.IBluetoothHci asInterface(android.os.IBinder obj)
+    {
+      if ((obj==null)) {
+        return null;
+      }
+      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
+      if (((iin!=null)&&(iin instanceof android.hardware.bluetooth.IBluetoothHci))) {
+        return ((android.hardware.bluetooth.IBluetoothHci)iin);
+      }
+      return new android.hardware.bluetooth.IBluetoothHci.Stub.Proxy(obj);
+    }
+    @Override public android.os.IBinder asBinder()
+    {
+      return this;
+    }
+    @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
+    {
+      java.lang.String descriptor = DESCRIPTOR;
+      if (code >= android.os.IBinder.FIRST_CALL_TRANSACTION && code <= android.os.IBinder.LAST_CALL_TRANSACTION) {
+        data.enforceInterface(descriptor);
+      }
+      switch (code)
+      {
+        case INTERFACE_TRANSACTION:
+        {
+          reply.writeString(descriptor);
+          return true;
+        }
+      }
+      switch (code)
+      {
+        case TRANSACTION_close:
+        {
+          this.close();
+          reply.writeNoException();
+          break;
+        }
+        case TRANSACTION_initialize:
+        {
+          android.hardware.bluetooth.IBluetoothHciCallbacks _arg0;
+          _arg0 = android.hardware.bluetooth.IBluetoothHciCallbacks.Stub.asInterface(data.readStrongBinder());
+          this.initialize(_arg0);
+          reply.writeNoException();
+          break;
+        }
+        case TRANSACTION_sendAclData:
+        {
+          byte[] _arg0;
+          _arg0 = data.createByteArray();
+          this.sendAclData(_arg0);
+          reply.writeNoException();
+          break;
+        }
+        case TRANSACTION_sendHciCommand:
+        {
+          byte[] _arg0;
+          _arg0 = data.createByteArray();
+          this.sendHciCommand(_arg0);
+          reply.writeNoException();
+          break;
+        }
+        case TRANSACTION_sendIsoData:
+        {
+          byte[] _arg0;
+          _arg0 = data.createByteArray();
+          this.sendIsoData(_arg0);
+          reply.writeNoException();
+          break;
+        }
+        case TRANSACTION_sendScoData:
+        {
+          byte[] _arg0;
+          _arg0 = data.createByteArray();
+          this.sendScoData(_arg0);
+          reply.writeNoException();
+          break;
+        }
+        default:
+        {
+          return super.onTransact(code, data, reply, flags);
+        }
+      }
+      return true;
+    }
+    private static class Proxy implements android.hardware.bluetooth.IBluetoothHci
+    {
+      private android.os.IBinder mRemote;
+      Proxy(android.os.IBinder remote)
+      {
+        mRemote = remote;
+      }
+      @Override public android.os.IBinder asBinder()
+      {
+        return mRemote;
+      }
+      public java.lang.String getInterfaceDescriptor()
+      {
+        return DESCRIPTOR;
+      }
+      @Override public void close() throws android.os.RemoteException
+      {
+        android.os.Parcel _data = android.os.Parcel.obtain();
+        android.os.Parcel _reply = android.os.Parcel.obtain();
+        try {
+          _data.writeInterfaceToken(DESCRIPTOR);
+          boolean _status = mRemote.transact(Stub.TRANSACTION_close, _data, _reply, 0);
+          _reply.readException();
+        }
+        finally {
+          _reply.recycle();
+          _data.recycle();
+        }
+      }
+      @Override public void initialize(android.hardware.bluetooth.IBluetoothHciCallbacks callback) throws android.os.RemoteException
+      {
+        android.os.Parcel _data = android.os.Parcel.obtain();
+        android.os.Parcel _reply = android.os.Parcel.obtain();
+        try {
+          _data.writeInterfaceToken(DESCRIPTOR);
+          _data.writeStrongInterface(callback);
+          boolean _status = mRemote.transact(Stub.TRANSACTION_initialize, _data, _reply, 0);
+          _reply.readException();
+        }
+        finally {
+          _reply.recycle();
+          _data.recycle();
+        }
+      }
+      @Override public void sendAclData(byte[] data) throws android.os.RemoteException
+      {
+        android.os.Parcel _data = android.os.Parcel.obtain();
+        android.os.Parcel _reply = android.os.Parcel.obtain();
+        try {
+          _data.writeInterfaceToken(DESCRIPTOR);
+          _data.writeByteArray(data);
+          boolean _status = mRemote.transact(Stub.TRANSACTION_sendAclData, _data, _reply, 0);
+          _reply.readException();
+        }
+        finally {
+          _reply.recycle();
+          _data.recycle();
+        }
+      }
+      @Override public void sendHciCommand(byte[] command) throws android.os.RemoteException
+      {
+        android.os.Parcel _data = android.os.Parcel.obtain();
+        android.os.Parcel _reply = android.os.Parcel.obtain();
+        try {
+          _data.writeInterfaceToken(DESCRIPTOR);
+          _data.writeByteArray(command);
+          boolean _status = mRemote.transact(Stub.TRANSACTION_sendHciCommand, _data, _reply, 0);
+          _reply.readException();
+        }
+        finally {
+          _reply.recycle();
+          _data.recycle();
+        }
+      }
+      @Override public void sendIsoData(byte[] data) throws android.os.RemoteException
+      {
+        android.os.Parcel _data = android.os.Parcel.obtain();
+        android.os.Parcel _reply = android.os.Parcel.obtain();
+        try {
+          _data.writeInterfaceToken(DESCRIPTOR);
+          _data.writeByteArray(data);
+          boolean _status = mRemote.transact(Stub.TRANSACTION_sendIsoData, _data, _reply, 0);
+          _reply.readException();
+        }
+        finally {
+          _reply.recycle();
+          _data.recycle();
+        }
+      }
+      @Override public void sendScoData(byte[] data) throws android.os.RemoteException
+      {
+        android.os.Parcel _data = android.os.Parcel.obtain();
+        android.os.Parcel _reply = android.os.Parcel.obtain();
+        try {
+          _data.writeInterfaceToken(DESCRIPTOR);
+          _data.writeByteArray(data);
+          boolean _status = mRemote.transact(Stub.TRANSACTION_sendScoData, _data, _reply, 0);
+          _reply.readException();
+        }
+        finally {
+          _reply.recycle();
+          _data.recycle();
+        }
+      }
+    }
+    static final int TRANSACTION_close = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
+    static final int TRANSACTION_initialize = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
+    static final int TRANSACTION_sendAclData = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
+    static final int TRANSACTION_sendHciCommand = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);
+    static final int TRANSACTION_sendIsoData = (android.os.IBinder.FIRST_CALL_TRANSACTION + 4);
+    static final int TRANSACTION_sendScoData = (android.os.IBinder.FIRST_CALL_TRANSACTION + 5);
+  }
+  public static final java.lang.String DESCRIPTOR = "android$hardware$bluetooth$IBluetoothHci".replace('$', '.');
+  public void close() throws android.os.RemoteException;
+  public void initialize(android.hardware.bluetooth.IBluetoothHciCallbacks callback) throws android.os.RemoteException;
+  public void sendAclData(byte[] data) throws android.os.RemoteException;
+  public void sendHciCommand(byte[] command) throws android.os.RemoteException;
+  public void sendIsoData(byte[] data) throws android.os.RemoteException;
+  public void sendScoData(byte[] data) throws android.os.RemoteException;
+}
diff --git a/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/IBluetoothHciCallbacks.java b/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/IBluetoothHciCallbacks.java
new file mode 100644
index 0000000..61a70ad
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/IBluetoothHciCallbacks.java
@@ -0,0 +1,234 @@
+/*
+ * This file is auto-generated.  DO NOT MODIFY.
+ */
+package android.hardware.bluetooth;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public interface IBluetoothHciCallbacks extends android.os.IInterface
+{
+  /** Default implementation for IBluetoothHciCallbacks. */
+  public static class Default implements android.hardware.bluetooth.IBluetoothHciCallbacks
+  {
+    @Override public void aclDataReceived(byte[] data) throws android.os.RemoteException
+    {
+    }
+    @Override public void hciEventReceived(byte[] event) throws android.os.RemoteException
+    {
+    }
+    @Override public void initializationComplete(int status) throws android.os.RemoteException
+    {
+    }
+    @Override public void isoDataReceived(byte[] data) throws android.os.RemoteException
+    {
+    }
+    @Override public void scoDataReceived(byte[] data) throws android.os.RemoteException
+    {
+    }
+    @Override
+    public android.os.IBinder asBinder() {
+      return null;
+    }
+  }
+  /** Local-side IPC implementation stub class. */
+  public static abstract class Stub extends android.os.Binder implements android.hardware.bluetooth.IBluetoothHciCallbacks
+  {
+    /** Construct the stub at attach it to the interface. */
+    public Stub()
+    {
+      //this.markVintfStability();
+      try {
+        Method method = this.getClass().getMethod("markVintfStability", (Class<?>[])null);
+        method.invoke(this);
+      } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
+        throw new RuntimeException(e);
+      }
+      this.attachInterface(this, DESCRIPTOR);
+    }
+    /**
+     * Cast an IBinder object into an android.hardware.bluetooth.IBluetoothHciCallbacks interface,
+     * generating a proxy if needed.
+     */
+    public static android.hardware.bluetooth.IBluetoothHciCallbacks asInterface(android.os.IBinder obj)
+    {
+      if ((obj==null)) {
+        return null;
+      }
+      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
+      if (((iin!=null)&&(iin instanceof android.hardware.bluetooth.IBluetoothHciCallbacks))) {
+        return ((android.hardware.bluetooth.IBluetoothHciCallbacks)iin);
+      }
+      return new android.hardware.bluetooth.IBluetoothHciCallbacks.Stub.Proxy(obj);
+    }
+    @Override public android.os.IBinder asBinder()
+    {
+      return this;
+    }
+    @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
+    {
+      java.lang.String descriptor = DESCRIPTOR;
+      if (code >= android.os.IBinder.FIRST_CALL_TRANSACTION && code <= android.os.IBinder.LAST_CALL_TRANSACTION) {
+        data.enforceInterface(descriptor);
+      }
+      switch (code)
+      {
+        case INTERFACE_TRANSACTION:
+        {
+          reply.writeString(descriptor);
+          return true;
+        }
+      }
+      switch (code)
+      {
+        case TRANSACTION_aclDataReceived:
+        {
+          byte[] _arg0;
+          _arg0 = data.createByteArray();
+          this.aclDataReceived(_arg0);
+          reply.writeNoException();
+          break;
+        }
+        case TRANSACTION_hciEventReceived:
+        {
+          byte[] _arg0;
+          _arg0 = data.createByteArray();
+          this.hciEventReceived(_arg0);
+          reply.writeNoException();
+          break;
+        }
+        case TRANSACTION_initializationComplete:
+        {
+          int _arg0;
+          _arg0 = data.readInt();
+          this.initializationComplete(_arg0);
+          reply.writeNoException();
+          break;
+        }
+        case TRANSACTION_isoDataReceived:
+        {
+          byte[] _arg0;
+          _arg0 = data.createByteArray();
+          this.isoDataReceived(_arg0);
+          reply.writeNoException();
+          break;
+        }
+        case TRANSACTION_scoDataReceived:
+        {
+          byte[] _arg0;
+          _arg0 = data.createByteArray();
+          this.scoDataReceived(_arg0);
+          reply.writeNoException();
+          break;
+        }
+        default:
+        {
+          return super.onTransact(code, data, reply, flags);
+        }
+      }
+      return true;
+    }
+    private static class Proxy implements android.hardware.bluetooth.IBluetoothHciCallbacks
+    {
+      private android.os.IBinder mRemote;
+      Proxy(android.os.IBinder remote)
+      {
+        mRemote = remote;
+      }
+      @Override public android.os.IBinder asBinder()
+      {
+        return mRemote;
+      }
+      public java.lang.String getInterfaceDescriptor()
+      {
+        return DESCRIPTOR;
+      }
+      @Override public void aclDataReceived(byte[] data) throws android.os.RemoteException
+      {
+        android.os.Parcel _data = android.os.Parcel.obtain();
+        android.os.Parcel _reply = android.os.Parcel.obtain();
+        try {
+          _data.writeInterfaceToken(DESCRIPTOR);
+          _data.writeByteArray(data);
+          boolean _status = mRemote.transact(Stub.TRANSACTION_aclDataReceived, _data, _reply, 0);
+          _reply.readException();
+        }
+        finally {
+          _reply.recycle();
+          _data.recycle();
+        }
+      }
+      @Override public void hciEventReceived(byte[] event) throws android.os.RemoteException
+      {
+        android.os.Parcel _data = android.os.Parcel.obtain();
+        android.os.Parcel _reply = android.os.Parcel.obtain();
+        try {
+          _data.writeInterfaceToken(DESCRIPTOR);
+          _data.writeByteArray(event);
+          boolean _status = mRemote.transact(Stub.TRANSACTION_hciEventReceived, _data, _reply, 0);
+          _reply.readException();
+        }
+        finally {
+          _reply.recycle();
+          _data.recycle();
+        }
+      }
+      @Override public void initializationComplete(int status) throws android.os.RemoteException
+      {
+        android.os.Parcel _data = android.os.Parcel.obtain();
+        android.os.Parcel _reply = android.os.Parcel.obtain();
+        try {
+          _data.writeInterfaceToken(DESCRIPTOR);
+          _data.writeInt(status);
+          boolean _status = mRemote.transact(Stub.TRANSACTION_initializationComplete, _data, _reply, 0);
+          _reply.readException();
+        }
+        finally {
+          _reply.recycle();
+          _data.recycle();
+        }
+      }
+      @Override public void isoDataReceived(byte[] data) throws android.os.RemoteException
+      {
+        android.os.Parcel _data = android.os.Parcel.obtain();
+        android.os.Parcel _reply = android.os.Parcel.obtain();
+        try {
+          _data.writeInterfaceToken(DESCRIPTOR);
+          _data.writeByteArray(data);
+          boolean _status = mRemote.transact(Stub.TRANSACTION_isoDataReceived, _data, _reply, 0);
+          _reply.readException();
+        }
+        finally {
+          _reply.recycle();
+          _data.recycle();
+        }
+      }
+      @Override public void scoDataReceived(byte[] data) throws android.os.RemoteException
+      {
+        android.os.Parcel _data = android.os.Parcel.obtain();
+        android.os.Parcel _reply = android.os.Parcel.obtain();
+        try {
+          _data.writeInterfaceToken(DESCRIPTOR);
+          _data.writeByteArray(data);
+          boolean _status = mRemote.transact(Stub.TRANSACTION_scoDataReceived, _data, _reply, 0);
+          _reply.readException();
+        }
+        finally {
+          _reply.recycle();
+          _data.recycle();
+        }
+      }
+    }
+    static final int TRANSACTION_aclDataReceived = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
+    static final int TRANSACTION_hciEventReceived = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
+    static final int TRANSACTION_initializationComplete = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
+    static final int TRANSACTION_isoDataReceived = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);
+    static final int TRANSACTION_scoDataReceived = (android.os.IBinder.FIRST_CALL_TRANSACTION + 4);
+  }
+  public static final java.lang.String DESCRIPTOR = "android$hardware$bluetooth$IBluetoothHciCallbacks".replace('$', '.');
+  public void aclDataReceived(byte[] data) throws android.os.RemoteException;
+  public void hciEventReceived(byte[] event) throws android.os.RemoteException;
+  public void initializationComplete(int status) throws android.os.RemoteException;
+  public void isoDataReceived(byte[] data) throws android.os.RemoteException;
+  public void scoDataReceived(byte[] data) throws android.os.RemoteException;
+}
diff --git a/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/Status.java b/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/Status.java
new file mode 100644
index 0000000..710d6c9
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/Status.java
@@ -0,0 +1,11 @@
+/*
+ * This file is auto-generated.  DO NOT MODIFY.
+ */
+package android.hardware.bluetooth;
+public @interface Status {
+  public static final int SUCCESS = 0;
+  public static final int ALREADY_INITIALIZED = 1;
+  public static final int UNABLE_TO_OPEN_INTERFACE = 2;
+  public static final int HARDWARE_INITIALIZATION_ERROR = 3;
+  public static final int UNKNOWN = 4;
+}
diff --git a/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/V1_0/IBluetoothHci.java b/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/V1_0/IBluetoothHci.java
new file mode 100644
index 0000000..e7cd577
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/V1_0/IBluetoothHci.java
@@ -0,0 +1,816 @@
+package android.hardware.bluetooth.V1_0;
+
+import android.os.HidlSupport;
+import android.os.HwBinder;
+import android.os.IHwBinder;
+import android.os.HwBlob;
+import android.os.HwParcel;
+import android.os.IHwInterface;
+import android.os.NativeHandle;
+
+/**
+ * The Host Controller Interface (HCI) is the layer defined by the Bluetooth
+ * specification between the software that runs on the host and the Bluetooth
+ * controller chip. This boundary is the natural choice for a Hardware
+ * Abstraction Layer (HAL). Dealing only in HCI packets and events simplifies
+ * the stack and abstracts away power management, initialization, and other
+ * implementation-specific details related to the hardware.
+ */
+public interface IBluetoothHci extends android.internal.hidl.base.V1_0.IBase {
+    /**
+     * Fully-qualified interface name for this interface.
+     */
+    public static final String kInterfaceName = "android.hardware.bluetooth@1.0::IBluetoothHci";
+
+    /**
+     * Does a checked conversion from a binder to this class.
+     */
+    /* package private */ static IBluetoothHci asInterface(IHwBinder binder) {
+        if (binder == null) {
+            return null;
+        }
+
+        IHwInterface iface =
+                binder.queryLocalInterface(kInterfaceName);
+
+        if ((iface != null) && (iface instanceof IBluetoothHci)) {
+            return (IBluetoothHci)iface;
+        }
+
+        IBluetoothHci proxy = new IBluetoothHci.Proxy(binder);
+
+        try {
+            for (String descriptor : proxy.interfaceChain()) {
+                if (descriptor.equals(kInterfaceName)) {
+                    return proxy;
+                }
+            }
+        } catch (android.os.RemoteException e) {
+        }
+
+        return null;
+    }
+
+    /**
+     * Does a checked conversion from any interface to this class.
+     */
+    public static IBluetoothHci castFrom(IHwInterface iface) {
+        return (iface == null) ? null : IBluetoothHci.asInterface(iface.asBinder());
+    }
+
+    @Override
+    public IHwBinder asBinder();
+
+    /**
+     * This will invoke the equivalent of the C++ getService(std::string) if retry is
+     * true or tryGetService(std::string) if retry is false. If the service is
+     * available on the device and retry is true, this will wait for the service to
+     * start.
+     *
+     */
+    public static IBluetoothHci getService(String serviceName, boolean retry) throws android.os.RemoteException {
+        return IBluetoothHci.asInterface(HwBinder.getService("android.hardware.bluetooth@1.0::IBluetoothHci", serviceName, retry));
+    }
+
+    /**
+     * Calls getService("default",retry).
+     */
+    public static IBluetoothHci getService(boolean retry) throws android.os.RemoteException {
+        return getService("default", retry);
+    }
+
+    /**
+     * @deprecated this will not wait for the interface to come up if it hasn't yet
+     * started. See getService(String,boolean) instead.
+     */
+    @Deprecated
+    public static IBluetoothHci getService(String serviceName) throws android.os.RemoteException {
+        return IBluetoothHci.asInterface(HwBinder.getService("android.hardware.bluetooth@1.0::IBluetoothHci", serviceName));
+    }
+
+    /**
+     * @deprecated this will not wait for the interface to come up if it hasn't yet
+     * started. See getService(boolean) instead.
+     */
+    @Deprecated
+    public static IBluetoothHci getService() throws android.os.RemoteException {
+        return getService("default");
+    }
+
+    /**
+     * Initialize the underlying HCI interface.
+     *
+     * This method should be used to initialize any hardware interfaces
+     * required to communicate with the Bluetooth hardware in the
+     * device.
+     *
+     * The |oninitializationComplete| callback must be invoked in response
+     * to this function to indicate success before any other function
+     * (sendHciCommand, sendAclData, * sendScoData) is invoked on this
+     * interface.
+     *
+     * @param callback implements IBluetoothHciCallbacks which will
+     *    receive callbacks when incoming HCI packets are received
+     *    from the controller to be sent to the host.
+     */
+    void initialize(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks callback)
+        throws android.os.RemoteException;
+    /**
+     * Send an HCI command (as specified in the Bluetooth Specification
+     * V4.2, Vol 2, Part 5, Section 5.4.1) to the Bluetooth controller.
+     * Commands must be executed in order.
+     *
+     * @param command is the HCI command to be sent
+     */
+    void sendHciCommand(java.util.ArrayList<Byte> command)
+        throws android.os.RemoteException;
+    /**
+     * Send an HCI ACL data packet (as specified in the Bluetooth Specification
+     * V4.2, Vol 2, Part 5, Section 5.4.2) to the Bluetooth controller.
+     * Packets must be processed in order.
+     * @param data HCI data packet to be sent
+     */
+    void sendAclData(java.util.ArrayList<Byte> data)
+        throws android.os.RemoteException;
+    /**
+     * Send an SCO data packet (as specified in the Bluetooth Specification
+     * V4.2, Vol 2, Part 5, Section 5.4.3) to the Bluetooth controller.
+     * Packets must be processed in order.
+     * @param data HCI data packet to be sent
+     */
+    void sendScoData(java.util.ArrayList<Byte> data)
+        throws android.os.RemoteException;
+    /**
+     * Close the HCI interface
+     */
+    void close()
+        throws android.os.RemoteException;
+    /*
+     * Provides run-time type information for this object.
+     * For example, for the following interface definition:
+     *     package android.hardware.foo@1.0;
+     *     interface IParent {};
+     *     interface IChild extends IParent {};
+     * Calling interfaceChain on an IChild object must yield the following:
+     *     ["android.hardware.foo@1.0::IChild",
+     *      "android.hardware.foo@1.0::IParent"
+     *      "android.internal.hidl.base@1.0::IBase"]
+     *
+     * @return descriptors a vector of descriptors of the run-time type of the
+     *         object.
+     */
+    java.util.ArrayList<String> interfaceChain()
+        throws android.os.RemoteException;
+    /*
+     * Emit diagnostic information to the given file.
+     *
+     * Optionally overriden.
+     *
+     * @param fd      File descriptor to dump data to.
+     *                Must only be used for the duration of this call.
+     * @param options Arguments for debugging.
+     *                Must support empty for default debug information.
+     */
+    void debug(NativeHandle fd, java.util.ArrayList<String> options)
+        throws android.os.RemoteException;
+    /*
+     * Provides run-time type information for this object.
+     * For example, for the following interface definition:
+     *     package android.hardware.foo@1.0;
+     *     interface IParent {};
+     *     interface IChild extends IParent {};
+     * Calling interfaceDescriptor on an IChild object must yield
+     *     "android.hardware.foo@1.0::IChild"
+     *
+     * @return descriptor a descriptor of the run-time type of the
+     *         object (the first element of the vector returned by
+     *         interfaceChain())
+     */
+    String interfaceDescriptor()
+        throws android.os.RemoteException;
+    /*
+     * Returns hashes of the source HAL files that define the interfaces of the
+     * runtime type information on the object.
+     * For example, for the following interface definition:
+     *     package android.hardware.foo@1.0;
+     *     interface IParent {};
+     *     interface IChild extends IParent {};
+     * Calling interfaceChain on an IChild object must yield the following:
+     *     [(hash of IChild.hal),
+     *      (hash of IParent.hal)
+     *      (hash of IBase.hal)].
+     *
+     * SHA-256 is used as the hashing algorithm. Each hash has 32 bytes
+     * according to SHA-256 standard.
+     *
+     * @return hashchain a vector of SHA-1 digests
+     */
+    java.util.ArrayList<byte[/* 32 */]> getHashChain()
+        throws android.os.RemoteException;
+    /*
+     * This method trigger the interface to enable/disable instrumentation based
+     * on system property hal.instrumentation.enable.
+     */
+    void setHALInstrumentation()
+        throws android.os.RemoteException;
+    /*
+     * Registers a death recipient, to be called when the process hosting this
+     * interface dies.
+     *
+     * @param recipient a hidl_death_recipient callback object
+     * @param cookie a cookie that must be returned with the callback
+     * @return success whether the death recipient was registered successfully.
+     */
+    boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie)
+        throws android.os.RemoteException;
+    /*
+     * Provides way to determine if interface is running without requesting
+     * any functionality.
+     */
+    void ping()
+        throws android.os.RemoteException;
+    /*
+     * Get debug information on references on this interface.
+     * @return info debugging information. See comments of DebugInfo.
+     */
+    android.internal.hidl.base.V1_0.DebugInfo getDebugInfo()
+        throws android.os.RemoteException;
+    /*
+     * This method notifies the interface that one or more system properties
+     * have changed. The default implementation calls
+     * (C++)  report_sysprop_change() in libcutils or
+     * (Java) android.os.SystemProperties.reportSyspropChanged,
+     * which in turn calls a set of registered callbacks (eg to update trace
+     * tags).
+     */
+    void notifySyspropsChanged()
+        throws android.os.RemoteException;
+    /*
+     * Unregisters the registered death recipient. If this service was registered
+     * multiple times with the same exact death recipient, this unlinks the most
+     * recently registered one.
+     *
+     * @param recipient a previously registered hidl_death_recipient callback
+     * @return success whether the death recipient was unregistered successfully.
+     */
+    boolean unlinkToDeath(IHwBinder.DeathRecipient recipient)
+        throws android.os.RemoteException;
+
+    public static final class Proxy implements IBluetoothHci {
+        private IHwBinder mRemote;
+
+        public Proxy(IHwBinder remote) {
+            mRemote = java.util.Objects.requireNonNull(remote);
+        }
+
+        @Override
+        public IHwBinder asBinder() {
+            return mRemote;
+        }
+
+        @Override
+        public String toString() {
+            try {
+                return this.interfaceDescriptor() + "@Proxy";
+            } catch (android.os.RemoteException ex) {
+                /* ignored; handled below. */
+            }
+            return "[class or subclass of " + IBluetoothHci.kInterfaceName + "]@Proxy";
+        }
+
+        @Override
+        public final boolean equals(java.lang.Object other) {
+            return HidlSupport.interfacesEqual(this, other);
+        }
+
+        @Override
+        public final int hashCode() {
+            return this.asBinder().hashCode();
+        }
+
+        // Methods from ::android::hardware::bluetooth::V1_0::IBluetoothHci follow.
+        @Override
+        public void initialize(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks callback)
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
+            _hidl_request.writeStrongBinder(callback == null ? null : callback.asBinder());
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(1 /* initialize */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public void sendHciCommand(java.util.ArrayList<Byte> command)
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
+            _hidl_request.writeInt8Vector(command);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(2 /* sendHciCommand */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public void sendAclData(java.util.ArrayList<Byte> data)
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
+            _hidl_request.writeInt8Vector(data);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(3 /* sendAclData */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public void sendScoData(java.util.ArrayList<Byte> data)
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
+            _hidl_request.writeInt8Vector(data);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(4 /* sendScoData */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public void close()
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(5 /* close */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        // Methods from ::android::hidl::base::V1_0::IBase follow.
+        @Override
+        public java.util.ArrayList<String> interfaceChain()
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(256067662 /* interfaceChain */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+
+                java.util.ArrayList<String> _hidl_out_descriptors = _hidl_reply.readStringVector();
+                return _hidl_out_descriptors;
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public void debug(NativeHandle fd, java.util.ArrayList<String> options)
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+            _hidl_request.writeNativeHandle(fd);
+            _hidl_request.writeStringVector(options);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(256131655 /* debug */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public String interfaceDescriptor()
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(256136003 /* interfaceDescriptor */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+
+                String _hidl_out_descriptor = _hidl_reply.readString();
+                return _hidl_out_descriptor;
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public java.util.ArrayList<byte[/* 32 */]> getHashChain()
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(256398152 /* getHashChain */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+
+                java.util.ArrayList<byte[/* 32 */]> _hidl_out_hashchain =  new java.util.ArrayList<byte[/* 32 */]>();
+                {
+                    HwBlob _hidl_blob = _hidl_reply.readBuffer(16 /* size */);
+                    {
+                        int _hidl_vec_size = _hidl_blob.getInt32(0 /* offset */ + 8 /* offsetof(hidl_vec<T>, mSize) */);
+                        HwBlob childBlob = _hidl_reply.readEmbeddedBuffer(
+                                _hidl_vec_size * 32,_hidl_blob.handle(),
+                                0 /* offset */ + 0 /* offsetof(hidl_vec<T>, mBuffer) */,true /* nullable */);
+
+                        ((java.util.ArrayList<byte[/* 32 */]>) _hidl_out_hashchain).clear();
+                        for (int _hidl_index_0 = 0; _hidl_index_0 < _hidl_vec_size; ++_hidl_index_0) {
+                            byte[/* 32 */] _hidl_vec_element = new byte[32];
+                            {
+                                long _hidl_array_offset_1 = _hidl_index_0 * 32;
+                                childBlob.copyToInt8Array(_hidl_array_offset_1, (byte[/* 32 */]) _hidl_vec_element, 32 /* size */);
+                                _hidl_array_offset_1 += 32 * 1;
+                            }
+                            ((java.util.ArrayList<byte[/* 32 */]>) _hidl_out_hashchain).add(_hidl_vec_element);
+                        }
+                    }
+                }
+                return _hidl_out_hashchain;
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public void setHALInstrumentation()
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(256462420 /* setHALInstrumentation */, _hidl_request, _hidl_reply, 1 /* oneway */);
+                _hidl_request.releaseTemporaryStorage();
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie)
+                throws android.os.RemoteException {
+            return mRemote.linkToDeath(recipient, cookie);
+        }
+        @Override
+        public void ping()
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(256921159 /* ping */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public android.internal.hidl.base.V1_0.DebugInfo getDebugInfo()
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(257049926 /* getDebugInfo */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+
+                android.internal.hidl.base.V1_0.DebugInfo _hidl_out_info = new android.internal.hidl.base.V1_0.DebugInfo();
+                ((android.internal.hidl.base.V1_0.DebugInfo) _hidl_out_info).readFromParcel(_hidl_reply);
+                return _hidl_out_info;
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public void notifySyspropsChanged()
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(257120595 /* notifySyspropsChanged */, _hidl_request, _hidl_reply, 1 /* oneway */);
+                _hidl_request.releaseTemporaryStorage();
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public boolean unlinkToDeath(IHwBinder.DeathRecipient recipient)
+                throws android.os.RemoteException {
+            return mRemote.unlinkToDeath(recipient);
+        }
+    }
+
+    public static abstract class Stub extends HwBinder implements IBluetoothHci {
+        @Override
+        public IHwBinder asBinder() {
+            return this;
+        }
+
+        @Override
+        public final java.util.ArrayList<String> interfaceChain() {
+            return new java.util.ArrayList<String>(java.util.Arrays.asList(
+                    android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName,
+                    android.internal.hidl.base.V1_0.IBase.kInterfaceName));
+
+        }
+
+        @Override
+        public void debug(NativeHandle fd, java.util.ArrayList<String> options) {
+            return;
+
+        }
+
+        @Override
+        public final String interfaceDescriptor() {
+            return android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName;
+
+        }
+
+        @Override
+        public final java.util.ArrayList<byte[/* 32 */]> getHashChain() {
+            return new java.util.ArrayList<byte[/* 32 */]>(java.util.Arrays.asList(
+                    new byte[/* 32 */]{52,124,-25,70,-127,86,7,86,127,95,59,83,-28,-128,9,-104,-54,90,-71,53,81,65,-16,-120,15,-64,-49,12,31,-59,-61,85} /* 347ce746815607567f5f3b53e4800998ca5ab9355141f0880fc0cf0c1fc5c355 */,
+                    new byte[/* 32 */]{-20,127,-41,-98,-48,45,-6,-123,-68,73,-108,38,-83,-82,62,-66,35,-17,5,36,-13,-51,105,87,19,-109,36,-72,59,24,-54,76} /* ec7fd79ed02dfa85bc499426adae3ebe23ef0524f3cd6957139324b83b18ca4c */));
+
+        }
+
+        @Override
+        public final void setHALInstrumentation() {
+
+        }
+
+        @Override
+        public final boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie) {
+            return true;
+
+        }
+
+        @Override
+        public final void ping() {
+            return;
+
+        }
+
+        @Override
+        public final android.internal.hidl.base.V1_0.DebugInfo getDebugInfo() {
+            android.internal.hidl.base.V1_0.DebugInfo info = new android.internal.hidl.base.V1_0.DebugInfo();
+            info.pid = HidlSupport.getPidIfSharable();
+            info.ptr = 0;
+            info.arch = android.internal.hidl.base.V1_0.DebugInfo.Architecture.UNKNOWN;
+            return info;
+
+        }
+
+        @Override
+        public final void notifySyspropsChanged() {
+            HwBinder.enableInstrumentation();
+
+        }
+
+        @Override
+        public final boolean unlinkToDeath(IHwBinder.DeathRecipient recipient) {
+            return true;
+
+        }
+
+        @Override
+        public IHwInterface queryLocalInterface(String descriptor) {
+            if (kInterfaceName.equals(descriptor)) {
+                return this;
+            }
+            return null;
+        }
+
+        public void registerAsService(String serviceName) throws android.os.RemoteException {
+            registerService(serviceName);
+        }
+
+        @Override
+        public String toString() {
+            return this.interfaceDescriptor() + "@Stub";
+        }
+
+        //@Override
+        public void onTransact(int _hidl_code, HwParcel _hidl_request, final HwParcel _hidl_reply, int _hidl_flags)
+                throws android.os.RemoteException {
+            switch (_hidl_code) {
+                case 1 /* initialize */:
+                {
+                    _hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
+
+                    android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks callback = android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.asInterface(_hidl_request.readStrongBinder());
+                    initialize(callback);
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 2 /* sendHciCommand */:
+                {
+                    _hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
+
+                    java.util.ArrayList<Byte> command = _hidl_request.readInt8Vector();
+                    sendHciCommand(command);
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 3 /* sendAclData */:
+                {
+                    _hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
+
+                    java.util.ArrayList<Byte> data = _hidl_request.readInt8Vector();
+                    sendAclData(data);
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 4 /* sendScoData */:
+                {
+                    _hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
+
+                    java.util.ArrayList<Byte> data = _hidl_request.readInt8Vector();
+                    sendScoData(data);
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 5 /* close */:
+                {
+                    _hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
+
+                    close();
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 256067662 /* interfaceChain */:
+                {
+                    _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+                    java.util.ArrayList<String> _hidl_out_descriptors = interfaceChain();
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    _hidl_reply.writeStringVector(_hidl_out_descriptors);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 256131655 /* debug */:
+                {
+                    _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+                    NativeHandle fd = _hidl_request.readNativeHandle();
+                    java.util.ArrayList<String> options = _hidl_request.readStringVector();
+                    debug(fd, options);
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 256136003 /* interfaceDescriptor */:
+                {
+                    _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+                    String _hidl_out_descriptor = interfaceDescriptor();
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    _hidl_reply.writeString(_hidl_out_descriptor);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 256398152 /* getHashChain */:
+                {
+                    _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+                    java.util.ArrayList<byte[/* 32 */]> _hidl_out_hashchain = getHashChain();
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    {
+                        HwBlob _hidl_blob = new HwBlob(16 /* size */);
+                        {
+                            int _hidl_vec_size = _hidl_out_hashchain.size();
+                            _hidl_blob.putInt32(0 /* offset */ + 8 /* offsetof(hidl_vec<T>, mSize) */, _hidl_vec_size);
+                            _hidl_blob.putBool(0 /* offset */ + 12 /* offsetof(hidl_vec<T>, mOwnsBuffer) */, false);
+                            HwBlob childBlob = new HwBlob((int)(_hidl_vec_size * 32));
+                            for (int _hidl_index_0 = 0; _hidl_index_0 < _hidl_vec_size; ++_hidl_index_0) {
+                                {
+                                    long _hidl_array_offset_1 = _hidl_index_0 * 32;
+                                    byte[] _hidl_array_item_1 = (byte[/* 32 */]) _hidl_out_hashchain.get(_hidl_index_0);
+
+                                    if (_hidl_array_item_1 == null || _hidl_array_item_1.length != 32) {
+                                        throw new IllegalArgumentException("Array element is not of the expected length");
+                                    }
+
+                                    childBlob.putInt8Array(_hidl_array_offset_1, _hidl_array_item_1);
+                                    _hidl_array_offset_1 += 32 * 1;
+                                }
+                            }
+                            _hidl_blob.putBlob(0 /* offset */ + 0 /* offsetof(hidl_vec<T>, mBuffer) */, childBlob);
+                        }
+                        _hidl_reply.writeBuffer(_hidl_blob);
+                    }
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 256462420 /* setHALInstrumentation */:
+                {
+                    _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+                    setHALInstrumentation();
+                    break;
+                }
+
+                case 256660548 /* linkToDeath */:
+                {
+                break;
+                }
+
+                case 256921159 /* ping */:
+                {
+                    _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+                    ping();
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 257049926 /* getDebugInfo */:
+                {
+                    _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+                    android.internal.hidl.base.V1_0.DebugInfo _hidl_out_info = getDebugInfo();
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    ((android.internal.hidl.base.V1_0.DebugInfo) _hidl_out_info).writeToParcel(_hidl_reply);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 257120595 /* notifySyspropsChanged */:
+                {
+                    _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+                    notifySyspropsChanged();
+                    break;
+                }
+
+                case 257250372 /* unlinkToDeath */:
+                {
+                break;
+                }
+
+            }
+        }
+    }
+}
diff --git a/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/V1_0/IBluetoothHciCallbacks.java b/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/V1_0/IBluetoothHciCallbacks.java
new file mode 100644
index 0000000..694fb72
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/V1_0/IBluetoothHciCallbacks.java
@@ -0,0 +1,762 @@
+package android.hardware.bluetooth.V1_0;
+
+import android.os.HidlSupport;
+import android.os.HwBinder;
+import android.os.IHwBinder;
+import android.os.HwBlob;
+import android.os.HwParcel;
+import android.os.IHwInterface;
+import android.os.NativeHandle;
+
+/**
+ * The interface from the Bluetooth Controller to the stack.
+ */
+public interface IBluetoothHciCallbacks extends android.internal.hidl.base.V1_0.IBase {
+    /**
+     * Fully-qualified interface name for this interface.
+     */
+    public static final String kInterfaceName = "android.hardware.bluetooth@1.0::IBluetoothHciCallbacks";
+
+    /**
+     * Does a checked conversion from a binder to this class.
+     */
+    /* package private */ static IBluetoothHciCallbacks asInterface(IHwBinder binder) {
+        if (binder == null) {
+            return null;
+        }
+
+        IHwInterface iface =
+                binder.queryLocalInterface(kInterfaceName);
+
+        if ((iface != null) && (iface instanceof IBluetoothHciCallbacks)) {
+            return (IBluetoothHciCallbacks)iface;
+        }
+
+        IBluetoothHciCallbacks proxy = new IBluetoothHciCallbacks.Proxy(binder);
+
+        try {
+            for (String descriptor : proxy.interfaceChain()) {
+                if (descriptor.equals(kInterfaceName)) {
+                    return proxy;
+                }
+            }
+        } catch (android.os.RemoteException e) {
+        }
+
+        return null;
+    }
+
+    /**
+     * Does a checked conversion from any interface to this class.
+     */
+    public static IBluetoothHciCallbacks castFrom(IHwInterface iface) {
+        return (iface == null) ? null : IBluetoothHciCallbacks.asInterface(iface.asBinder());
+    }
+
+    @Override
+    public IHwBinder asBinder();
+
+    /**
+     * This will invoke the equivalent of the C++ getService(std::string) if retry is
+     * true or tryGetService(std::string) if retry is false. If the service is
+     * available on the device and retry is true, this will wait for the service to
+     * start.
+     *
+     */
+    public static IBluetoothHciCallbacks getService(String serviceName, boolean retry) throws android.os.RemoteException {
+        return IBluetoothHciCallbacks.asInterface(HwBinder.getService("android.hardware.bluetooth@1.0::IBluetoothHciCallbacks", serviceName, retry));
+    }
+
+    /**
+     * Calls getService("default",retry).
+     */
+    public static IBluetoothHciCallbacks getService(boolean retry) throws android.os.RemoteException {
+        return getService("default", retry);
+    }
+
+    /**
+     * @deprecated this will not wait for the interface to come up if it hasn't yet
+     * started. See getService(String,boolean) instead.
+     */
+    @Deprecated
+    public static IBluetoothHciCallbacks getService(String serviceName) throws android.os.RemoteException {
+        return IBluetoothHciCallbacks.asInterface(HwBinder.getService("android.hardware.bluetooth@1.0::IBluetoothHciCallbacks", serviceName));
+    }
+
+    /**
+     * @deprecated this will not wait for the interface to come up if it hasn't yet
+     * started. See getService(boolean) instead.
+     */
+    @Deprecated
+    public static IBluetoothHciCallbacks getService() throws android.os.RemoteException {
+        return getService("default");
+    }
+
+    /**
+     * Invoked when the Bluetooth controller initialization has been
+     * completed.
+     */
+    void initializationComplete(int status)
+        throws android.os.RemoteException;
+    /**
+     * This function is invoked when an HCI event is received from the
+     * Bluetooth controller to be forwarded to the Bluetooth stack.
+     * @param event is the HCI event to be sent to the Bluetooth stack.
+     */
+    void hciEventReceived(java.util.ArrayList<Byte> event)
+        throws android.os.RemoteException;
+    /**
+     * Send an ACL data packet form the controller to the host.
+     * @param data the ACL HCI packet to be passed to the host stack
+     */
+    void aclDataReceived(java.util.ArrayList<Byte> data)
+        throws android.os.RemoteException;
+    /**
+     * Send a SCO data packet form the controller to the host.
+     * @param data the SCO HCI packet to be passed to the host stack
+     */
+    void scoDataReceived(java.util.ArrayList<Byte> data)
+        throws android.os.RemoteException;
+    /*
+     * Provides run-time type information for this object.
+     * For example, for the following interface definition:
+     *     package android.hardware.foo@1.0;
+     *     interface IParent {};
+     *     interface IChild extends IParent {};
+     * Calling interfaceChain on an IChild object must yield the following:
+     *     ["android.hardware.foo@1.0::IChild",
+     *      "android.hardware.foo@1.0::IParent"
+     *      "android.internal.hidl.base@1.0::IBase"]
+     *
+     * @return descriptors a vector of descriptors of the run-time type of the
+     *         object.
+     */
+    java.util.ArrayList<String> interfaceChain()
+        throws android.os.RemoteException;
+    /*
+     * Emit diagnostic information to the given file.
+     *
+     * Optionally overriden.
+     *
+     * @param fd      File descriptor to dump data to.
+     *                Must only be used for the duration of this call.
+     * @param options Arguments for debugging.
+     *                Must support empty for default debug information.
+     */
+    void debug(NativeHandle fd, java.util.ArrayList<String> options)
+        throws android.os.RemoteException;
+    /*
+     * Provides run-time type information for this object.
+     * For example, for the following interface definition:
+     *     package android.hardware.foo@1.0;
+     *     interface IParent {};
+     *     interface IChild extends IParent {};
+     * Calling interfaceDescriptor on an IChild object must yield
+     *     "android.hardware.foo@1.0::IChild"
+     *
+     * @return descriptor a descriptor of the run-time type of the
+     *         object (the first element of the vector returned by
+     *         interfaceChain())
+     */
+    String interfaceDescriptor()
+        throws android.os.RemoteException;
+    /*
+     * Returns hashes of the source HAL files that define the interfaces of the
+     * runtime type information on the object.
+     * For example, for the following interface definition:
+     *     package android.hardware.foo@1.0;
+     *     interface IParent {};
+     *     interface IChild extends IParent {};
+     * Calling interfaceChain on an IChild object must yield the following:
+     *     [(hash of IChild.hal),
+     *      (hash of IParent.hal)
+     *      (hash of IBase.hal)].
+     *
+     * SHA-256 is used as the hashing algorithm. Each hash has 32 bytes
+     * according to SHA-256 standard.
+     *
+     * @return hashchain a vector of SHA-1 digests
+     */
+    java.util.ArrayList<byte[/* 32 */]> getHashChain()
+        throws android.os.RemoteException;
+    /*
+     * This method trigger the interface to enable/disable instrumentation based
+     * on system property hal.instrumentation.enable.
+     */
+    void setHALInstrumentation()
+        throws android.os.RemoteException;
+    /*
+     * Registers a death recipient, to be called when the process hosting this
+     * interface dies.
+     *
+     * @param recipient a hidl_death_recipient callback object
+     * @param cookie a cookie that must be returned with the callback
+     * @return success whether the death recipient was registered successfully.
+     */
+    boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie)
+        throws android.os.RemoteException;
+    /*
+     * Provides way to determine if interface is running without requesting
+     * any functionality.
+     */
+    void ping()
+        throws android.os.RemoteException;
+    /*
+     * Get debug information on references on this interface.
+     * @return info debugging information. See comments of DebugInfo.
+     */
+    android.internal.hidl.base.V1_0.DebugInfo getDebugInfo()
+        throws android.os.RemoteException;
+    /*
+     * This method notifies the interface that one or more system properties
+     * have changed. The default implementation calls
+     * (C++)  report_sysprop_change() in libcutils or
+     * (Java) android.os.SystemProperties.reportSyspropChanged,
+     * which in turn calls a set of registered callbacks (eg to update trace
+     * tags).
+     */
+    void notifySyspropsChanged()
+        throws android.os.RemoteException;
+    /*
+     * Unregisters the registered death recipient. If this service was registered
+     * multiple times with the same exact death recipient, this unlinks the most
+     * recently registered one.
+     *
+     * @param recipient a previously registered hidl_death_recipient callback
+     * @return success whether the death recipient was unregistered successfully.
+     */
+    boolean unlinkToDeath(IHwBinder.DeathRecipient recipient)
+        throws android.os.RemoteException;
+
+    public static final class Proxy implements IBluetoothHciCallbacks {
+        private IHwBinder mRemote;
+
+        public Proxy(IHwBinder remote) {
+            mRemote = java.util.Objects.requireNonNull(remote);
+        }
+
+        @Override
+        public IHwBinder asBinder() {
+            return mRemote;
+        }
+
+        @Override
+        public String toString() {
+            try {
+                return this.interfaceDescriptor() + "@Proxy";
+            } catch (android.os.RemoteException ex) {
+                /* ignored; handled below. */
+            }
+            return "[class or subclass of " + IBluetoothHciCallbacks.kInterfaceName + "]@Proxy";
+        }
+
+        @Override
+        public final boolean equals(java.lang.Object other) {
+            return HidlSupport.interfacesEqual(this, other);
+        }
+
+        @Override
+        public final int hashCode() {
+            return this.asBinder().hashCode();
+        }
+
+        // Methods from ::android::hardware::bluetooth::V1_0::IBluetoothHciCallbacks follow.
+        @Override
+        public void initializationComplete(int status)
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
+            _hidl_request.writeInt32(status);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(1 /* initializationComplete */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public void hciEventReceived(java.util.ArrayList<Byte> event)
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
+            _hidl_request.writeInt8Vector(event);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(2 /* hciEventReceived */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public void aclDataReceived(java.util.ArrayList<Byte> data)
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
+            _hidl_request.writeInt8Vector(data);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(3 /* aclDataReceived */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public void scoDataReceived(java.util.ArrayList<Byte> data)
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
+            _hidl_request.writeInt8Vector(data);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(4 /* scoDataReceived */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        // Methods from ::android::hidl::base::V1_0::IBase follow.
+        @Override
+        public java.util.ArrayList<String> interfaceChain()
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(256067662 /* interfaceChain */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+
+                java.util.ArrayList<String> _hidl_out_descriptors = _hidl_reply.readStringVector();
+                return _hidl_out_descriptors;
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public void debug(NativeHandle fd, java.util.ArrayList<String> options)
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+            _hidl_request.writeNativeHandle(fd);
+            _hidl_request.writeStringVector(options);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(256131655 /* debug */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public String interfaceDescriptor()
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(256136003 /* interfaceDescriptor */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+
+                String _hidl_out_descriptor = _hidl_reply.readString();
+                return _hidl_out_descriptor;
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public java.util.ArrayList<byte[/* 32 */]> getHashChain()
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(256398152 /* getHashChain */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+
+                java.util.ArrayList<byte[/* 32 */]> _hidl_out_hashchain =  new java.util.ArrayList<byte[/* 32 */]>();
+                {
+                    HwBlob _hidl_blob = _hidl_reply.readBuffer(16 /* size */);
+                    {
+                        int _hidl_vec_size = _hidl_blob.getInt32(0 /* offset */ + 8 /* offsetof(hidl_vec<T>, mSize) */);
+                        HwBlob childBlob = _hidl_reply.readEmbeddedBuffer(
+                                _hidl_vec_size * 32,_hidl_blob.handle(),
+                                0 /* offset */ + 0 /* offsetof(hidl_vec<T>, mBuffer) */,true /* nullable */);
+
+                        ((java.util.ArrayList<byte[/* 32 */]>) _hidl_out_hashchain).clear();
+                        for (int _hidl_index_0 = 0; _hidl_index_0 < _hidl_vec_size; ++_hidl_index_0) {
+                            byte[/* 32 */] _hidl_vec_element = new byte[32];
+                            {
+                                long _hidl_array_offset_1 = _hidl_index_0 * 32;
+                                childBlob.copyToInt8Array(_hidl_array_offset_1, (byte[/* 32 */]) _hidl_vec_element, 32 /* size */);
+                                _hidl_array_offset_1 += 32 * 1;
+                            }
+                            ((java.util.ArrayList<byte[/* 32 */]>) _hidl_out_hashchain).add(_hidl_vec_element);
+                        }
+                    }
+                }
+                return _hidl_out_hashchain;
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public void setHALInstrumentation()
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(256462420 /* setHALInstrumentation */, _hidl_request, _hidl_reply, 1 /* oneway */);
+                _hidl_request.releaseTemporaryStorage();
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie)
+                throws android.os.RemoteException {
+            return mRemote.linkToDeath(recipient, cookie);
+        }
+        @Override
+        public void ping()
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(256921159 /* ping */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public android.internal.hidl.base.V1_0.DebugInfo getDebugInfo()
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(257049926 /* getDebugInfo */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+
+                android.internal.hidl.base.V1_0.DebugInfo _hidl_out_info = new android.internal.hidl.base.V1_0.DebugInfo();
+                ((android.internal.hidl.base.V1_0.DebugInfo) _hidl_out_info).readFromParcel(_hidl_reply);
+                return _hidl_out_info;
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public void notifySyspropsChanged()
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(257120595 /* notifySyspropsChanged */, _hidl_request, _hidl_reply, 1 /* oneway */);
+                _hidl_request.releaseTemporaryStorage();
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public boolean unlinkToDeath(IHwBinder.DeathRecipient recipient)
+                throws android.os.RemoteException {
+            return mRemote.unlinkToDeath(recipient);
+        }
+    }
+
+    public static abstract class Stub extends HwBinder implements IBluetoothHciCallbacks {
+        @Override
+        public IHwBinder asBinder() {
+            return this;
+        }
+
+        @Override
+        public final java.util.ArrayList<String> interfaceChain() {
+            return new java.util.ArrayList<String>(java.util.Arrays.asList(
+                    android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName,
+                    android.internal.hidl.base.V1_0.IBase.kInterfaceName));
+
+        }
+
+        @Override
+        public void debug(NativeHandle fd, java.util.ArrayList<String> options) {
+            return;
+
+        }
+
+        @Override
+        public final String interfaceDescriptor() {
+            return android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName;
+
+        }
+
+        @Override
+        public final java.util.ArrayList<byte[/* 32 */]> getHashChain() {
+            return new java.util.ArrayList<byte[/* 32 */]>(java.util.Arrays.asList(
+                    new byte[/* 32 */]{-125,95,65,-66,34,-127,-65,-78,47,62,51,-58,-6,-121,11,-34,123,-62,30,55,-27,-49,-70,-7,-93,111,-1,23,6,50,-9,84} /* 835f41be2281bfb22f3e33c6fa870bde7bc21e37e5cfbaf9a36fff170632f754 */,
+                    new byte[/* 32 */]{-20,127,-41,-98,-48,45,-6,-123,-68,73,-108,38,-83,-82,62,-66,35,-17,5,36,-13,-51,105,87,19,-109,36,-72,59,24,-54,76} /* ec7fd79ed02dfa85bc499426adae3ebe23ef0524f3cd6957139324b83b18ca4c */));
+
+        }
+
+        @Override
+        public final void setHALInstrumentation() {
+
+        }
+
+        @Override
+        public final boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie) {
+            return true;
+
+        }
+
+        @Override
+        public final void ping() {
+            return;
+
+        }
+
+        @Override
+        public final android.internal.hidl.base.V1_0.DebugInfo getDebugInfo() {
+            android.internal.hidl.base.V1_0.DebugInfo info = new android.internal.hidl.base.V1_0.DebugInfo();
+            info.pid = HidlSupport.getPidIfSharable();
+            info.ptr = 0;
+            info.arch = android.internal.hidl.base.V1_0.DebugInfo.Architecture.UNKNOWN;
+            return info;
+
+        }
+
+        @Override
+        public final void notifySyspropsChanged() {
+            HwBinder.enableInstrumentation();
+
+        }
+
+        @Override
+        public final boolean unlinkToDeath(IHwBinder.DeathRecipient recipient) {
+            return true;
+
+        }
+
+        @Override
+        public IHwInterface queryLocalInterface(String descriptor) {
+            if (kInterfaceName.equals(descriptor)) {
+                return this;
+            }
+            return null;
+        }
+
+        public void registerAsService(String serviceName) throws android.os.RemoteException {
+            registerService(serviceName);
+        }
+
+        @Override
+        public String toString() {
+            return this.interfaceDescriptor() + "@Stub";
+        }
+
+        //@Override
+        public void onTransact(int _hidl_code, HwParcel _hidl_request, final HwParcel _hidl_reply, int _hidl_flags)
+                throws android.os.RemoteException {
+            switch (_hidl_code) {
+                case 1 /* initializationComplete */:
+                {
+                    _hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
+
+                    int status = _hidl_request.readInt32();
+                    initializationComplete(status);
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 2 /* hciEventReceived */:
+                {
+                    _hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
+
+                    java.util.ArrayList<Byte> event = _hidl_request.readInt8Vector();
+                    hciEventReceived(event);
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 3 /* aclDataReceived */:
+                {
+                    _hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
+
+                    java.util.ArrayList<Byte> data = _hidl_request.readInt8Vector();
+                    aclDataReceived(data);
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 4 /* scoDataReceived */:
+                {
+                    _hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
+
+                    java.util.ArrayList<Byte> data = _hidl_request.readInt8Vector();
+                    scoDataReceived(data);
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 256067662 /* interfaceChain */:
+                {
+                    _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+                    java.util.ArrayList<String> _hidl_out_descriptors = interfaceChain();
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    _hidl_reply.writeStringVector(_hidl_out_descriptors);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 256131655 /* debug */:
+                {
+                    _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+                    NativeHandle fd = _hidl_request.readNativeHandle();
+                    java.util.ArrayList<String> options = _hidl_request.readStringVector();
+                    debug(fd, options);
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 256136003 /* interfaceDescriptor */:
+                {
+                    _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+                    String _hidl_out_descriptor = interfaceDescriptor();
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    _hidl_reply.writeString(_hidl_out_descriptor);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 256398152 /* getHashChain */:
+                {
+                    _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+                    java.util.ArrayList<byte[/* 32 */]> _hidl_out_hashchain = getHashChain();
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    {
+                        HwBlob _hidl_blob = new HwBlob(16 /* size */);
+                        {
+                            int _hidl_vec_size = _hidl_out_hashchain.size();
+                            _hidl_blob.putInt32(0 /* offset */ + 8 /* offsetof(hidl_vec<T>, mSize) */, _hidl_vec_size);
+                            _hidl_blob.putBool(0 /* offset */ + 12 /* offsetof(hidl_vec<T>, mOwnsBuffer) */, false);
+                            HwBlob childBlob = new HwBlob((int)(_hidl_vec_size * 32));
+                            for (int _hidl_index_0 = 0; _hidl_index_0 < _hidl_vec_size; ++_hidl_index_0) {
+                                {
+                                    long _hidl_array_offset_1 = _hidl_index_0 * 32;
+                                    byte[] _hidl_array_item_1 = (byte[/* 32 */]) _hidl_out_hashchain.get(_hidl_index_0);
+
+                                    if (_hidl_array_item_1 == null || _hidl_array_item_1.length != 32) {
+                                        throw new IllegalArgumentException("Array element is not of the expected length");
+                                    }
+
+                                    childBlob.putInt8Array(_hidl_array_offset_1, _hidl_array_item_1);
+                                    _hidl_array_offset_1 += 32 * 1;
+                                }
+                            }
+                            _hidl_blob.putBlob(0 /* offset */ + 0 /* offsetof(hidl_vec<T>, mBuffer) */, childBlob);
+                        }
+                        _hidl_reply.writeBuffer(_hidl_blob);
+                    }
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 256462420 /* setHALInstrumentation */:
+                {
+                    _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+                    setHALInstrumentation();
+                    break;
+                }
+
+                case 256660548 /* linkToDeath */:
+                {
+                break;
+                }
+
+                case 256921159 /* ping */:
+                {
+                    _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+                    ping();
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 257049926 /* getDebugInfo */:
+                {
+                    _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+                    android.internal.hidl.base.V1_0.DebugInfo _hidl_out_info = getDebugInfo();
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    ((android.internal.hidl.base.V1_0.DebugInfo) _hidl_out_info).writeToParcel(_hidl_reply);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 257120595 /* notifySyspropsChanged */:
+                {
+                    _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+                    notifySyspropsChanged();
+                    break;
+                }
+
+                case 257250372 /* unlinkToDeath */:
+                {
+                break;
+                }
+
+            }
+        }
+    }
+}
diff --git a/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/V1_0/Status.java b/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/V1_0/Status.java
new file mode 100644
index 0000000..9235005
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/V1_0/Status.java
@@ -0,0 +1,48 @@
+package android.hardware.bluetooth.V1_0;
+
+
+public final class Status {
+    public static final int SUCCESS = 0;
+    public static final int TRANSPORT_ERROR = 1 /* ::android::hardware::bluetooth::V1_0::Status.SUCCESS implicitly + 1 */;
+    public static final int INITIALIZATION_ERROR = 2 /* ::android::hardware::bluetooth::V1_0::Status.TRANSPORT_ERROR implicitly + 1 */;
+    public static final int UNKNOWN = 3 /* ::android::hardware::bluetooth::V1_0::Status.INITIALIZATION_ERROR implicitly + 1 */;
+    public static final String toString(int o) {
+        if (o == SUCCESS) {
+            return "SUCCESS";
+        }
+        if (o == TRANSPORT_ERROR) {
+            return "TRANSPORT_ERROR";
+        }
+        if (o == INITIALIZATION_ERROR) {
+            return "INITIALIZATION_ERROR";
+        }
+        if (o == UNKNOWN) {
+            return "UNKNOWN";
+        }
+        return "0x" + Integer.toHexString(o);
+    }
+
+    public static final String dumpBitfield(int o) {
+        java.util.ArrayList<String> list = new java.util.ArrayList<>();
+        int flipped = 0;
+        list.add("SUCCESS"); // SUCCESS == 0
+        if ((o & TRANSPORT_ERROR) == TRANSPORT_ERROR) {
+            list.add("TRANSPORT_ERROR");
+            flipped |= TRANSPORT_ERROR;
+        }
+        if ((o & INITIALIZATION_ERROR) == INITIALIZATION_ERROR) {
+            list.add("INITIALIZATION_ERROR");
+            flipped |= INITIALIZATION_ERROR;
+        }
+        if ((o & UNKNOWN) == UNKNOWN) {
+            list.add("UNKNOWN");
+            flipped |= UNKNOWN;
+        }
+        if (o != flipped) {
+            list.add("0x" + Integer.toHexString(o & (~flipped)));
+        }
+        return String.join(" | ", list);
+    }
+
+};
+
diff --git a/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/V1_1/IBluetoothHci.java b/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/V1_1/IBluetoothHci.java
new file mode 100644
index 0000000..4439451
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/V1_1/IBluetoothHci.java
@@ -0,0 +1,840 @@
+package android.hardware.bluetooth.V1_1;
+
+import android.os.HidlSupport;
+import android.os.HwBinder;
+import android.os.IHwBinder;
+import android.os.HwBlob;
+import android.os.HwParcel;
+import android.os.IHwInterface;
+import android.os.NativeHandle;
+
+/**
+ * The Host Controller Interface (HCI) is the layer defined by the Bluetooth
+ * specification between the software that runs on the host and the Bluetooth
+ * controller chip. This boundary is the natural choice for a Hardware
+ * Abstraction Layer (HAL). Dealing only in HCI packets and events simplifies
+ * the stack and abstracts away power management, initialization, and other
+ * implementation-specific details related to the hardware.
+ */
+public interface IBluetoothHci extends android.hardware.bluetooth.V1_0.IBluetoothHci {
+    /**
+     * Fully-qualified interface name for this interface.
+     */
+    public static final String kInterfaceName = "android.hardware.bluetooth@1.1::IBluetoothHci";
+
+    /**
+     * Does a checked conversion from a binder to this class.
+     */
+    /* package private */ static IBluetoothHci asInterface(IHwBinder binder) {
+        if (binder == null) {
+            return null;
+        }
+
+        IHwInterface iface =
+                binder.queryLocalInterface(kInterfaceName);
+
+        if ((iface != null) && (iface instanceof IBluetoothHci)) {
+            return (IBluetoothHci)iface;
+        }
+
+        IBluetoothHci proxy = new IBluetoothHci.Proxy(binder);
+
+        try {
+            for (String descriptor : proxy.interfaceChain()) {
+                if (descriptor.equals(kInterfaceName)) {
+                    return proxy;
+                }
+            }
+        } catch (android.os.RemoteException e) {
+        }
+
+        return null;
+    }
+
+    /**
+     * Does a checked conversion from any interface to this class.
+     */
+    public static IBluetoothHci castFrom(IHwInterface iface) {
+        return (iface == null) ? null : IBluetoothHci.asInterface(iface.asBinder());
+    }
+
+    @Override
+    public IHwBinder asBinder();
+
+    /**
+     * This will invoke the equivalent of the C++ getService(std::string) if retry is
+     * true or tryGetService(std::string) if retry is false. If the service is
+     * available on the device and retry is true, this will wait for the service to
+     * start.
+     *
+     */
+    public static IBluetoothHci getService(String serviceName, boolean retry) throws android.os.RemoteException {
+        return IBluetoothHci.asInterface(HwBinder.getService("android.hardware.bluetooth@1.1::IBluetoothHci", serviceName, retry));
+    }
+
+    /**
+     * Calls getService("default",retry).
+     */
+    public static IBluetoothHci getService(boolean retry) throws android.os.RemoteException {
+        return getService("default", retry);
+    }
+
+    /**
+     * @deprecated this will not wait for the interface to come up if it hasn't yet
+     * started. See getService(String,boolean) instead.
+     */
+    @Deprecated
+    public static IBluetoothHci getService(String serviceName) throws android.os.RemoteException {
+        return IBluetoothHci.asInterface(HwBinder.getService("android.hardware.bluetooth@1.1::IBluetoothHci", serviceName));
+    }
+
+    /**
+     * @deprecated this will not wait for the interface to come up if it hasn't yet
+     * started. See getService(boolean) instead.
+     */
+    @Deprecated
+    public static IBluetoothHci getService() throws android.os.RemoteException {
+        return getService("default");
+    }
+
+    /**
+     * Same as @1.0, but uses 1.1 Callbacks version
+     */
+    void initialize_1_1(android.hardware.bluetooth.V1_1.IBluetoothHciCallbacks callback)
+        throws android.os.RemoteException;
+    /**
+     * Send an ISO data packet (as specified in the Bluetooth Core
+     * Specification v5.2) to the Bluetooth controller.
+     * Packets must be processed in order.
+     * @param data HCI data packet to be sent
+     */
+    void sendIsoData(java.util.ArrayList<Byte> data)
+        throws android.os.RemoteException;
+    /*
+     * Provides run-time type information for this object.
+     * For example, for the following interface definition:
+     *     package android.hardware.foo@1.0;
+     *     interface IParent {};
+     *     interface IChild extends IParent {};
+     * Calling interfaceChain on an IChild object must yield the following:
+     *     ["android.hardware.foo@1.0::IChild",
+     *      "android.hardware.foo@1.0::IParent"
+     *      "android.internal.hidl.base@1.0::IBase"]
+     *
+     * @return descriptors a vector of descriptors of the run-time type of the
+     *         object.
+     */
+    java.util.ArrayList<String> interfaceChain()
+        throws android.os.RemoteException;
+    /*
+     * Emit diagnostic information to the given file.
+     *
+     * Optionally overriden.
+     *
+     * @param fd      File descriptor to dump data to.
+     *                Must only be used for the duration of this call.
+     * @param options Arguments for debugging.
+     *                Must support empty for default debug information.
+     */
+    void debug(NativeHandle fd, java.util.ArrayList<String> options)
+        throws android.os.RemoteException;
+    /*
+     * Provides run-time type information for this object.
+     * For example, for the following interface definition:
+     *     package android.hardware.foo@1.0;
+     *     interface IParent {};
+     *     interface IChild extends IParent {};
+     * Calling interfaceDescriptor on an IChild object must yield
+     *     "android.hardware.foo@1.0::IChild"
+     *
+     * @return descriptor a descriptor of the run-time type of the
+     *         object (the first element of the vector returned by
+     *         interfaceChain())
+     */
+    String interfaceDescriptor()
+        throws android.os.RemoteException;
+    /*
+     * Returns hashes of the source HAL files that define the interfaces of the
+     * runtime type information on the object.
+     * For example, for the following interface definition:
+     *     package android.hardware.foo@1.0;
+     *     interface IParent {};
+     *     interface IChild extends IParent {};
+     * Calling interfaceChain on an IChild object must yield the following:
+     *     [(hash of IChild.hal),
+     *      (hash of IParent.hal)
+     *      (hash of IBase.hal)].
+     *
+     * SHA-256 is used as the hashing algorithm. Each hash has 32 bytes
+     * according to SHA-256 standard.
+     *
+     * @return hashchain a vector of SHA-1 digests
+     */
+    java.util.ArrayList<byte[/* 32 */]> getHashChain()
+        throws android.os.RemoteException;
+    /*
+     * This method trigger the interface to enable/disable instrumentation based
+     * on system property hal.instrumentation.enable.
+     */
+    void setHALInstrumentation()
+        throws android.os.RemoteException;
+    /*
+     * Registers a death recipient, to be called when the process hosting this
+     * interface dies.
+     *
+     * @param recipient a hidl_death_recipient callback object
+     * @param cookie a cookie that must be returned with the callback
+     * @return success whether the death recipient was registered successfully.
+     */
+    boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie)
+        throws android.os.RemoteException;
+    /*
+     * Provides way to determine if interface is running without requesting
+     * any functionality.
+     */
+    void ping()
+        throws android.os.RemoteException;
+    /*
+     * Get debug information on references on this interface.
+     * @return info debugging information. See comments of DebugInfo.
+     */
+    android.internal.hidl.base.V1_0.DebugInfo getDebugInfo()
+        throws android.os.RemoteException;
+    /*
+     * This method notifies the interface that one or more system properties
+     * have changed. The default implementation calls
+     * (C++)  report_sysprop_change() in libcutils or
+     * (Java) android.os.SystemProperties.reportSyspropChanged,
+     * which in turn calls a set of registered callbacks (eg to update trace
+     * tags).
+     */
+    void notifySyspropsChanged()
+        throws android.os.RemoteException;
+    /*
+     * Unregisters the registered death recipient. If this service was registered
+     * multiple times with the same exact death recipient, this unlinks the most
+     * recently registered one.
+     *
+     * @param recipient a previously registered hidl_death_recipient callback
+     * @return success whether the death recipient was unregistered successfully.
+     */
+    boolean unlinkToDeath(IHwBinder.DeathRecipient recipient)
+        throws android.os.RemoteException;
+
+    public static final class Proxy implements IBluetoothHci {
+        private IHwBinder mRemote;
+
+        public Proxy(IHwBinder remote) {
+            mRemote = java.util.Objects.requireNonNull(remote);
+        }
+
+        @Override
+        public IHwBinder asBinder() {
+            return mRemote;
+        }
+
+        @Override
+        public String toString() {
+            try {
+                return this.interfaceDescriptor() + "@Proxy";
+            } catch (android.os.RemoteException ex) {
+                /* ignored; handled below. */
+            }
+            return "[class or subclass of " + IBluetoothHci.kInterfaceName + "]@Proxy";
+        }
+
+        @Override
+        public final boolean equals(java.lang.Object other) {
+            return HidlSupport.interfacesEqual(this, other);
+        }
+
+        @Override
+        public final int hashCode() {
+            return this.asBinder().hashCode();
+        }
+
+        // Methods from ::android::hardware::bluetooth::V1_0::IBluetoothHci follow.
+        @Override
+        public void initialize(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks callback)
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
+            _hidl_request.writeStrongBinder(callback == null ? null : callback.asBinder());
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(1 /* initialize */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public void sendHciCommand(java.util.ArrayList<Byte> command)
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
+            _hidl_request.writeInt8Vector(command);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(2 /* sendHciCommand */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public void sendAclData(java.util.ArrayList<Byte> data)
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
+            _hidl_request.writeInt8Vector(data);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(3 /* sendAclData */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public void sendScoData(java.util.ArrayList<Byte> data)
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
+            _hidl_request.writeInt8Vector(data);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(4 /* sendScoData */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public void close()
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(5 /* close */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        // Methods from ::android::hardware::bluetooth::V1_1::IBluetoothHci follow.
+        @Override
+        public void initialize_1_1(android.hardware.bluetooth.V1_1.IBluetoothHciCallbacks callback)
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_1.IBluetoothHci.kInterfaceName);
+            _hidl_request.writeStrongBinder(callback == null ? null : callback.asBinder());
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(6 /* initialize_1_1 */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public void sendIsoData(java.util.ArrayList<Byte> data)
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_1.IBluetoothHci.kInterfaceName);
+            _hidl_request.writeInt8Vector(data);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(7 /* sendIsoData */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        // Methods from ::android::hidl::base::V1_0::IBase follow.
+        @Override
+        public java.util.ArrayList<String> interfaceChain()
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(256067662 /* interfaceChain */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+
+                java.util.ArrayList<String> _hidl_out_descriptors = _hidl_reply.readStringVector();
+                return _hidl_out_descriptors;
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public void debug(NativeHandle fd, java.util.ArrayList<String> options)
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+            _hidl_request.writeNativeHandle(fd);
+            _hidl_request.writeStringVector(options);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(256131655 /* debug */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public String interfaceDescriptor()
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(256136003 /* interfaceDescriptor */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+
+                String _hidl_out_descriptor = _hidl_reply.readString();
+                return _hidl_out_descriptor;
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public java.util.ArrayList<byte[/* 32 */]> getHashChain()
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(256398152 /* getHashChain */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+
+                java.util.ArrayList<byte[/* 32 */]> _hidl_out_hashchain =  new java.util.ArrayList<byte[/* 32 */]>();
+                {
+                    HwBlob _hidl_blob = _hidl_reply.readBuffer(16 /* size */);
+                    {
+                        int _hidl_vec_size = _hidl_blob.getInt32(0 /* offset */ + 8 /* offsetof(hidl_vec<T>, mSize) */);
+                        HwBlob childBlob = _hidl_reply.readEmbeddedBuffer(
+                                _hidl_vec_size * 32,_hidl_blob.handle(),
+                                0 /* offset */ + 0 /* offsetof(hidl_vec<T>, mBuffer) */,true /* nullable */);
+
+                        ((java.util.ArrayList<byte[/* 32 */]>) _hidl_out_hashchain).clear();
+                        for (int _hidl_index_0 = 0; _hidl_index_0 < _hidl_vec_size; ++_hidl_index_0) {
+                            byte[/* 32 */] _hidl_vec_element = new byte[32];
+                            {
+                                long _hidl_array_offset_1 = _hidl_index_0 * 32;
+                                childBlob.copyToInt8Array(_hidl_array_offset_1, (byte[/* 32 */]) _hidl_vec_element, 32 /* size */);
+                                _hidl_array_offset_1 += 32 * 1;
+                            }
+                            ((java.util.ArrayList<byte[/* 32 */]>) _hidl_out_hashchain).add(_hidl_vec_element);
+                        }
+                    }
+                }
+                return _hidl_out_hashchain;
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public void setHALInstrumentation()
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(256462420 /* setHALInstrumentation */, _hidl_request, _hidl_reply, 1 /* oneway */);
+                _hidl_request.releaseTemporaryStorage();
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie)
+                throws android.os.RemoteException {
+            return mRemote.linkToDeath(recipient, cookie);
+        }
+        @Override
+        public void ping()
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(256921159 /* ping */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public android.internal.hidl.base.V1_0.DebugInfo getDebugInfo()
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(257049926 /* getDebugInfo */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+
+                android.internal.hidl.base.V1_0.DebugInfo _hidl_out_info = new android.internal.hidl.base.V1_0.DebugInfo();
+                ((android.internal.hidl.base.V1_0.DebugInfo) _hidl_out_info).readFromParcel(_hidl_reply);
+                return _hidl_out_info;
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public void notifySyspropsChanged()
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(257120595 /* notifySyspropsChanged */, _hidl_request, _hidl_reply, 1 /* oneway */);
+                _hidl_request.releaseTemporaryStorage();
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public boolean unlinkToDeath(IHwBinder.DeathRecipient recipient)
+                throws android.os.RemoteException {
+            return mRemote.unlinkToDeath(recipient);
+        }
+    }
+
+    public static abstract class Stub extends HwBinder implements IBluetoothHci {
+        @Override
+        public IHwBinder asBinder() {
+            return this;
+        }
+
+        @Override
+        public final java.util.ArrayList<String> interfaceChain() {
+            return new java.util.ArrayList<String>(java.util.Arrays.asList(
+                    android.hardware.bluetooth.V1_1.IBluetoothHci.kInterfaceName,
+                    android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName,
+                    android.internal.hidl.base.V1_0.IBase.kInterfaceName));
+
+        }
+
+        @Override
+        public void debug(NativeHandle fd, java.util.ArrayList<String> options) {
+            return;
+
+        }
+
+        @Override
+        public final String interfaceDescriptor() {
+            return android.hardware.bluetooth.V1_1.IBluetoothHci.kInterfaceName;
+
+        }
+
+        @Override
+        public final java.util.ArrayList<byte[/* 32 */]> getHashChain() {
+            return new java.util.ArrayList<byte[/* 32 */]>(java.util.Arrays.asList(
+                    new byte[/* 32 */]{54,47,-47,-62,22,65,-62,34,79,59,-128,-61,13,-105,-105,-71,-120,-6,63,52,66,67,-43,49,-70,115,-59,83,119,-102,87,99} /* 362fd1c21641c2224f3b80c30d9797b988fa3f344243d531ba73c553779a5763 */,
+                    new byte[/* 32 */]{52,124,-25,70,-127,86,7,86,127,95,59,83,-28,-128,9,-104,-54,90,-71,53,81,65,-16,-120,15,-64,-49,12,31,-59,-61,85} /* 347ce746815607567f5f3b53e4800998ca5ab9355141f0880fc0cf0c1fc5c355 */,
+                    new byte[/* 32 */]{-20,127,-41,-98,-48,45,-6,-123,-68,73,-108,38,-83,-82,62,-66,35,-17,5,36,-13,-51,105,87,19,-109,36,-72,59,24,-54,76} /* ec7fd79ed02dfa85bc499426adae3ebe23ef0524f3cd6957139324b83b18ca4c */));
+
+        }
+
+        @Override
+        public final void setHALInstrumentation() {
+
+        }
+
+        @Override
+        public final boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie) {
+            return true;
+
+        }
+
+        @Override
+        public final void ping() {
+            return;
+
+        }
+
+        @Override
+        public final android.internal.hidl.base.V1_0.DebugInfo getDebugInfo() {
+            android.internal.hidl.base.V1_0.DebugInfo info = new android.internal.hidl.base.V1_0.DebugInfo();
+            info.pid = HidlSupport.getPidIfSharable();
+            info.ptr = 0;
+            info.arch = android.internal.hidl.base.V1_0.DebugInfo.Architecture.UNKNOWN;
+            return info;
+
+        }
+
+        @Override
+        public final void notifySyspropsChanged() {
+            HwBinder.enableInstrumentation();
+
+        }
+
+        @Override
+        public final boolean unlinkToDeath(IHwBinder.DeathRecipient recipient) {
+            return true;
+
+        }
+
+        @Override
+        public IHwInterface queryLocalInterface(String descriptor) {
+            if (kInterfaceName.equals(descriptor)) {
+                return this;
+            }
+            return null;
+        }
+
+        public void registerAsService(String serviceName) throws android.os.RemoteException {
+            registerService(serviceName);
+        }
+
+        @Override
+        public String toString() {
+            return this.interfaceDescriptor() + "@Stub";
+        }
+
+        //@Override
+        public void onTransact(int _hidl_code, HwParcel _hidl_request, final HwParcel _hidl_reply, int _hidl_flags)
+                throws android.os.RemoteException {
+            switch (_hidl_code) {
+                case 1 /* initialize */:
+                {
+                    _hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
+
+                    android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks callback = android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.asInterface(_hidl_request.readStrongBinder());
+                    initialize(callback);
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 2 /* sendHciCommand */:
+                {
+                    _hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
+
+                    java.util.ArrayList<Byte> command = _hidl_request.readInt8Vector();
+                    sendHciCommand(command);
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 3 /* sendAclData */:
+                {
+                    _hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
+
+                    java.util.ArrayList<Byte> data = _hidl_request.readInt8Vector();
+                    sendAclData(data);
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 4 /* sendScoData */:
+                {
+                    _hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
+
+                    java.util.ArrayList<Byte> data = _hidl_request.readInt8Vector();
+                    sendScoData(data);
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 5 /* close */:
+                {
+                    _hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName);
+
+                    close();
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 6 /* initialize_1_1 */:
+                {
+                    _hidl_request.enforceInterface(android.hardware.bluetooth.V1_1.IBluetoothHci.kInterfaceName);
+
+                    android.hardware.bluetooth.V1_1.IBluetoothHciCallbacks callback = android.hardware.bluetooth.V1_1.IBluetoothHciCallbacks.asInterface(_hidl_request.readStrongBinder());
+                    initialize_1_1(callback);
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 7 /* sendIsoData */:
+                {
+                    _hidl_request.enforceInterface(android.hardware.bluetooth.V1_1.IBluetoothHci.kInterfaceName);
+
+                    java.util.ArrayList<Byte> data = _hidl_request.readInt8Vector();
+                    sendIsoData(data);
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 256067662 /* interfaceChain */:
+                {
+                    _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+                    java.util.ArrayList<String> _hidl_out_descriptors = interfaceChain();
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    _hidl_reply.writeStringVector(_hidl_out_descriptors);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 256131655 /* debug */:
+                {
+                    _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+                    NativeHandle fd = _hidl_request.readNativeHandle();
+                    java.util.ArrayList<String> options = _hidl_request.readStringVector();
+                    debug(fd, options);
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 256136003 /* interfaceDescriptor */:
+                {
+                    _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+                    String _hidl_out_descriptor = interfaceDescriptor();
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    _hidl_reply.writeString(_hidl_out_descriptor);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 256398152 /* getHashChain */:
+                {
+                    _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+                    java.util.ArrayList<byte[/* 32 */]> _hidl_out_hashchain = getHashChain();
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    {
+                        HwBlob _hidl_blob = new HwBlob(16 /* size */);
+                        {
+                            int _hidl_vec_size = _hidl_out_hashchain.size();
+                            _hidl_blob.putInt32(0 /* offset */ + 8 /* offsetof(hidl_vec<T>, mSize) */, _hidl_vec_size);
+                            _hidl_blob.putBool(0 /* offset */ + 12 /* offsetof(hidl_vec<T>, mOwnsBuffer) */, false);
+                            HwBlob childBlob = new HwBlob((int)(_hidl_vec_size * 32));
+                            for (int _hidl_index_0 = 0; _hidl_index_0 < _hidl_vec_size; ++_hidl_index_0) {
+                                {
+                                    long _hidl_array_offset_1 = _hidl_index_0 * 32;
+                                    byte[] _hidl_array_item_1 = (byte[/* 32 */]) _hidl_out_hashchain.get(_hidl_index_0);
+
+                                    if (_hidl_array_item_1 == null || _hidl_array_item_1.length != 32) {
+                                        throw new IllegalArgumentException("Array element is not of the expected length");
+                                    }
+
+                                    childBlob.putInt8Array(_hidl_array_offset_1, _hidl_array_item_1);
+                                    _hidl_array_offset_1 += 32 * 1;
+                                }
+                            }
+                            _hidl_blob.putBlob(0 /* offset */ + 0 /* offsetof(hidl_vec<T>, mBuffer) */, childBlob);
+                        }
+                        _hidl_reply.writeBuffer(_hidl_blob);
+                    }
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 256462420 /* setHALInstrumentation */:
+                {
+                    _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+                    setHALInstrumentation();
+                    break;
+                }
+
+                case 256660548 /* linkToDeath */:
+                {
+                break;
+                }
+
+                case 256921159 /* ping */:
+                {
+                    _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+                    ping();
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 257049926 /* getDebugInfo */:
+                {
+                    _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+                    android.internal.hidl.base.V1_0.DebugInfo _hidl_out_info = getDebugInfo();
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    ((android.internal.hidl.base.V1_0.DebugInfo) _hidl_out_info).writeToParcel(_hidl_reply);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 257120595 /* notifySyspropsChanged */:
+                {
+                    _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+                    notifySyspropsChanged();
+                    break;
+                }
+
+                case 257250372 /* unlinkToDeath */:
+                {
+                break;
+                }
+
+            }
+        }
+    }
+}
diff --git a/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/V1_1/IBluetoothHciCallbacks.java b/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/V1_1/IBluetoothHciCallbacks.java
new file mode 100644
index 0000000..905e3a0
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/V1_1/IBluetoothHciCallbacks.java
@@ -0,0 +1,774 @@
+package android.hardware.bluetooth.V1_1;
+
+import android.os.HidlSupport;
+import android.os.HwBinder;
+import android.os.IHwBinder;
+import android.os.HwBlob;
+import android.os.HwParcel;
+import android.os.IHwInterface;
+import android.os.NativeHandle;
+
+/**
+ * The interface from the Bluetooth Controller to the stack.
+ */
+public interface IBluetoothHciCallbacks extends android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks {
+    /**
+     * Fully-qualified interface name for this interface.
+     */
+    public static final String kInterfaceName = "android.hardware.bluetooth@1.1::IBluetoothHciCallbacks";
+
+    /**
+     * Does a checked conversion from a binder to this class.
+     */
+    /* package private */ static IBluetoothHciCallbacks asInterface(IHwBinder binder) {
+        if (binder == null) {
+            return null;
+        }
+
+        IHwInterface iface =
+                binder.queryLocalInterface(kInterfaceName);
+
+        if ((iface != null) && (iface instanceof IBluetoothHciCallbacks)) {
+            return (IBluetoothHciCallbacks)iface;
+        }
+
+        IBluetoothHciCallbacks proxy = new IBluetoothHciCallbacks.Proxy(binder);
+
+        try {
+            for (String descriptor : proxy.interfaceChain()) {
+                if (descriptor.equals(kInterfaceName)) {
+                    return proxy;
+                }
+            }
+        } catch (android.os.RemoteException e) {
+        }
+
+        return null;
+    }
+
+    /**
+     * Does a checked conversion from any interface to this class.
+     */
+    public static IBluetoothHciCallbacks castFrom(IHwInterface iface) {
+        return (iface == null) ? null : IBluetoothHciCallbacks.asInterface(iface.asBinder());
+    }
+
+    @Override
+    public IHwBinder asBinder();
+
+    /**
+     * This will invoke the equivalent of the C++ getService(std::string) if retry is
+     * true or tryGetService(std::string) if retry is false. If the service is
+     * available on the device and retry is true, this will wait for the service to
+     * start.
+     *
+     */
+    public static IBluetoothHciCallbacks getService(String serviceName, boolean retry) throws android.os.RemoteException {
+        return IBluetoothHciCallbacks.asInterface(HwBinder.getService("android.hardware.bluetooth@1.1::IBluetoothHciCallbacks", serviceName, retry));
+    }
+
+    /**
+     * Calls getService("default",retry).
+     */
+    public static IBluetoothHciCallbacks getService(boolean retry) throws android.os.RemoteException {
+        return getService("default", retry);
+    }
+
+    /**
+     * @deprecated this will not wait for the interface to come up if it hasn't yet
+     * started. See getService(String,boolean) instead.
+     */
+    @Deprecated
+    public static IBluetoothHciCallbacks getService(String serviceName) throws android.os.RemoteException {
+        return IBluetoothHciCallbacks.asInterface(HwBinder.getService("android.hardware.bluetooth@1.1::IBluetoothHciCallbacks", serviceName));
+    }
+
+    /**
+     * @deprecated this will not wait for the interface to come up if it hasn't yet
+     * started. See getService(boolean) instead.
+     */
+    @Deprecated
+    public static IBluetoothHciCallbacks getService() throws android.os.RemoteException {
+        return getService("default");
+    }
+
+    /**
+     * Send a ISO data packet form the controller to the host.
+     * @param data the ISO HCI packet to be passed to the host stack
+     */
+    void isoDataReceived(java.util.ArrayList<Byte> data)
+        throws android.os.RemoteException;
+    /*
+     * Provides run-time type information for this object.
+     * For example, for the following interface definition:
+     *     package android.hardware.foo@1.0;
+     *     interface IParent {};
+     *     interface IChild extends IParent {};
+     * Calling interfaceChain on an IChild object must yield the following:
+     *     ["android.hardware.foo@1.0::IChild",
+     *      "android.hardware.foo@1.0::IParent"
+     *      "android.internal.hidl.base@1.0::IBase"]
+     *
+     * @return descriptors a vector of descriptors of the run-time type of the
+     *         object.
+     */
+    java.util.ArrayList<String> interfaceChain()
+        throws android.os.RemoteException;
+    /*
+     * Emit diagnostic information to the given file.
+     *
+     * Optionally overriden.
+     *
+     * @param fd      File descriptor to dump data to.
+     *                Must only be used for the duration of this call.
+     * @param options Arguments for debugging.
+     *                Must support empty for default debug information.
+     */
+    void debug(NativeHandle fd, java.util.ArrayList<String> options)
+        throws android.os.RemoteException;
+    /*
+     * Provides run-time type information for this object.
+     * For example, for the following interface definition:
+     *     package android.hardware.foo@1.0;
+     *     interface IParent {};
+     *     interface IChild extends IParent {};
+     * Calling interfaceDescriptor on an IChild object must yield
+     *     "android.hardware.foo@1.0::IChild"
+     *
+     * @return descriptor a descriptor of the run-time type of the
+     *         object (the first element of the vector returned by
+     *         interfaceChain())
+     */
+    String interfaceDescriptor()
+        throws android.os.RemoteException;
+    /*
+     * Returns hashes of the source HAL files that define the interfaces of the
+     * runtime type information on the object.
+     * For example, for the following interface definition:
+     *     package android.hardware.foo@1.0;
+     *     interface IParent {};
+     *     interface IChild extends IParent {};
+     * Calling interfaceChain on an IChild object must yield the following:
+     *     [(hash of IChild.hal),
+     *      (hash of IParent.hal)
+     *      (hash of IBase.hal)].
+     *
+     * SHA-256 is used as the hashing algorithm. Each hash has 32 bytes
+     * according to SHA-256 standard.
+     *
+     * @return hashchain a vector of SHA-1 digests
+     */
+    java.util.ArrayList<byte[/* 32 */]> getHashChain()
+        throws android.os.RemoteException;
+    /*
+     * This method trigger the interface to enable/disable instrumentation based
+     * on system property hal.instrumentation.enable.
+     */
+    void setHALInstrumentation()
+        throws android.os.RemoteException;
+    /*
+     * Registers a death recipient, to be called when the process hosting this
+     * interface dies.
+     *
+     * @param recipient a hidl_death_recipient callback object
+     * @param cookie a cookie that must be returned with the callback
+     * @return success whether the death recipient was registered successfully.
+     */
+    boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie)
+        throws android.os.RemoteException;
+    /*
+     * Provides way to determine if interface is running without requesting
+     * any functionality.
+     */
+    void ping()
+        throws android.os.RemoteException;
+    /*
+     * Get debug information on references on this interface.
+     * @return info debugging information. See comments of DebugInfo.
+     */
+    android.internal.hidl.base.V1_0.DebugInfo getDebugInfo()
+        throws android.os.RemoteException;
+    /*
+     * This method notifies the interface that one or more system properties
+     * have changed. The default implementation calls
+     * (C++)  report_sysprop_change() in libcutils or
+     * (Java) android.os.SystemProperties.reportSyspropChanged,
+     * which in turn calls a set of registered callbacks (eg to update trace
+     * tags).
+     */
+    void notifySyspropsChanged()
+        throws android.os.RemoteException;
+    /*
+     * Unregisters the registered death recipient. If this service was registered
+     * multiple times with the same exact death recipient, this unlinks the most
+     * recently registered one.
+     *
+     * @param recipient a previously registered hidl_death_recipient callback
+     * @return success whether the death recipient was unregistered successfully.
+     */
+    boolean unlinkToDeath(IHwBinder.DeathRecipient recipient)
+        throws android.os.RemoteException;
+
+    public static final class Proxy implements IBluetoothHciCallbacks {
+        private IHwBinder mRemote;
+
+        public Proxy(IHwBinder remote) {
+            mRemote = java.util.Objects.requireNonNull(remote);
+        }
+
+        @Override
+        public IHwBinder asBinder() {
+            return mRemote;
+        }
+
+        @Override
+        public String toString() {
+            try {
+                return this.interfaceDescriptor() + "@Proxy";
+            } catch (android.os.RemoteException ex) {
+                /* ignored; handled below. */
+            }
+            return "[class or subclass of " + IBluetoothHciCallbacks.kInterfaceName + "]@Proxy";
+        }
+
+        @Override
+        public final boolean equals(java.lang.Object other) {
+            return HidlSupport.interfacesEqual(this, other);
+        }
+
+        @Override
+        public final int hashCode() {
+            return this.asBinder().hashCode();
+        }
+
+        // Methods from ::android::hardware::bluetooth::V1_0::IBluetoothHciCallbacks follow.
+        @Override
+        public void initializationComplete(int status)
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
+            _hidl_request.writeInt32(status);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(1 /* initializationComplete */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public void hciEventReceived(java.util.ArrayList<Byte> event)
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
+            _hidl_request.writeInt8Vector(event);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(2 /* hciEventReceived */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public void aclDataReceived(java.util.ArrayList<Byte> data)
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
+            _hidl_request.writeInt8Vector(data);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(3 /* aclDataReceived */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public void scoDataReceived(java.util.ArrayList<Byte> data)
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
+            _hidl_request.writeInt8Vector(data);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(4 /* scoDataReceived */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        // Methods from ::android::hardware::bluetooth::V1_1::IBluetoothHciCallbacks follow.
+        @Override
+        public void isoDataReceived(java.util.ArrayList<Byte> data)
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_1.IBluetoothHciCallbacks.kInterfaceName);
+            _hidl_request.writeInt8Vector(data);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(5 /* isoDataReceived */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        // Methods from ::android::hidl::base::V1_0::IBase follow.
+        @Override
+        public java.util.ArrayList<String> interfaceChain()
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(256067662 /* interfaceChain */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+
+                java.util.ArrayList<String> _hidl_out_descriptors = _hidl_reply.readStringVector();
+                return _hidl_out_descriptors;
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public void debug(NativeHandle fd, java.util.ArrayList<String> options)
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+            _hidl_request.writeNativeHandle(fd);
+            _hidl_request.writeStringVector(options);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(256131655 /* debug */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public String interfaceDescriptor()
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(256136003 /* interfaceDescriptor */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+
+                String _hidl_out_descriptor = _hidl_reply.readString();
+                return _hidl_out_descriptor;
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public java.util.ArrayList<byte[/* 32 */]> getHashChain()
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(256398152 /* getHashChain */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+
+                java.util.ArrayList<byte[/* 32 */]> _hidl_out_hashchain =  new java.util.ArrayList<byte[/* 32 */]>();
+                {
+                    HwBlob _hidl_blob = _hidl_reply.readBuffer(16 /* size */);
+                    {
+                        int _hidl_vec_size = _hidl_blob.getInt32(0 /* offset */ + 8 /* offsetof(hidl_vec<T>, mSize) */);
+                        HwBlob childBlob = _hidl_reply.readEmbeddedBuffer(
+                                _hidl_vec_size * 32,_hidl_blob.handle(),
+                                0 /* offset */ + 0 /* offsetof(hidl_vec<T>, mBuffer) */,true /* nullable */);
+
+                        ((java.util.ArrayList<byte[/* 32 */]>) _hidl_out_hashchain).clear();
+                        for (int _hidl_index_0 = 0; _hidl_index_0 < _hidl_vec_size; ++_hidl_index_0) {
+                            byte[/* 32 */] _hidl_vec_element = new byte[32];
+                            {
+                                long _hidl_array_offset_1 = _hidl_index_0 * 32;
+                                childBlob.copyToInt8Array(_hidl_array_offset_1, (byte[/* 32 */]) _hidl_vec_element, 32 /* size */);
+                                _hidl_array_offset_1 += 32 * 1;
+                            }
+                            ((java.util.ArrayList<byte[/* 32 */]>) _hidl_out_hashchain).add(_hidl_vec_element);
+                        }
+                    }
+                }
+                return _hidl_out_hashchain;
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public void setHALInstrumentation()
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(256462420 /* setHALInstrumentation */, _hidl_request, _hidl_reply, 1 /* oneway */);
+                _hidl_request.releaseTemporaryStorage();
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie)
+                throws android.os.RemoteException {
+            return mRemote.linkToDeath(recipient, cookie);
+        }
+        @Override
+        public void ping()
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(256921159 /* ping */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public android.internal.hidl.base.V1_0.DebugInfo getDebugInfo()
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(257049926 /* getDebugInfo */, _hidl_request, _hidl_reply, 0 /* flags */);
+                _hidl_reply.verifySuccess();
+                _hidl_request.releaseTemporaryStorage();
+
+                android.internal.hidl.base.V1_0.DebugInfo _hidl_out_info = new android.internal.hidl.base.V1_0.DebugInfo();
+                ((android.internal.hidl.base.V1_0.DebugInfo) _hidl_out_info).readFromParcel(_hidl_reply);
+                return _hidl_out_info;
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public void notifySyspropsChanged()
+                throws android.os.RemoteException {
+            HwParcel _hidl_request = new HwParcel();
+            _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+            HwParcel _hidl_reply = new HwParcel();
+            try {
+                mRemote.transact(257120595 /* notifySyspropsChanged */, _hidl_request, _hidl_reply, 1 /* oneway */);
+                _hidl_request.releaseTemporaryStorage();
+            } finally {
+                _hidl_reply.release();
+            }
+        }
+
+        @Override
+        public boolean unlinkToDeath(IHwBinder.DeathRecipient recipient)
+                throws android.os.RemoteException {
+            return mRemote.unlinkToDeath(recipient);
+        }
+    }
+
+    public static abstract class Stub extends HwBinder implements IBluetoothHciCallbacks {
+        @Override
+        public IHwBinder asBinder() {
+            return this;
+        }
+
+        @Override
+        public final java.util.ArrayList<String> interfaceChain() {
+            return new java.util.ArrayList<String>(java.util.Arrays.asList(
+                    android.hardware.bluetooth.V1_1.IBluetoothHciCallbacks.kInterfaceName,
+                    android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName,
+                    android.internal.hidl.base.V1_0.IBase.kInterfaceName));
+
+        }
+
+        @Override
+        public void debug(NativeHandle fd, java.util.ArrayList<String> options) {
+            return;
+
+        }
+
+        @Override
+        public final String interfaceDescriptor() {
+            return android.hardware.bluetooth.V1_1.IBluetoothHciCallbacks.kInterfaceName;
+
+        }
+
+        @Override
+        public final java.util.ArrayList<byte[/* 32 */]> getHashChain() {
+            return new java.util.ArrayList<byte[/* 32 */]>(java.util.Arrays.asList(
+                    new byte[/* 32 */]{64,-85,44,104,102,-63,-115,50,-70,-10,-28,-98,48,83,-108,-98,121,96,31,86,-106,58,121,30,-109,-26,-117,-98,-31,-113,113,-115} /* 40ab2c6866c18d32baf6e49e3053949e79601f56963a791e93e68b9ee18f718d */,
+                    new byte[/* 32 */]{-125,95,65,-66,34,-127,-65,-78,47,62,51,-58,-6,-121,11,-34,123,-62,30,55,-27,-49,-70,-7,-93,111,-1,23,6,50,-9,84} /* 835f41be2281bfb22f3e33c6fa870bde7bc21e37e5cfbaf9a36fff170632f754 */,
+                    new byte[/* 32 */]{-20,127,-41,-98,-48,45,-6,-123,-68,73,-108,38,-83,-82,62,-66,35,-17,5,36,-13,-51,105,87,19,-109,36,-72,59,24,-54,76} /* ec7fd79ed02dfa85bc499426adae3ebe23ef0524f3cd6957139324b83b18ca4c */));
+
+        }
+
+        @Override
+        public final void setHALInstrumentation() {
+
+        }
+
+        @Override
+        public final boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie) {
+            return true;
+
+        }
+
+        @Override
+        public final void ping() {
+            return;
+
+        }
+
+        @Override
+        public final android.internal.hidl.base.V1_0.DebugInfo getDebugInfo() {
+            android.internal.hidl.base.V1_0.DebugInfo info = new android.internal.hidl.base.V1_0.DebugInfo();
+            info.pid = HidlSupport.getPidIfSharable();
+            info.ptr = 0;
+            info.arch = android.internal.hidl.base.V1_0.DebugInfo.Architecture.UNKNOWN;
+            return info;
+
+        }
+
+        @Override
+        public final void notifySyspropsChanged() {
+            HwBinder.enableInstrumentation();
+
+        }
+
+        @Override
+        public final boolean unlinkToDeath(IHwBinder.DeathRecipient recipient) {
+            return true;
+
+        }
+
+        @Override
+        public IHwInterface queryLocalInterface(String descriptor) {
+            if (kInterfaceName.equals(descriptor)) {
+                return this;
+            }
+            return null;
+        }
+
+        public void registerAsService(String serviceName) throws android.os.RemoteException {
+            registerService(serviceName);
+        }
+
+        @Override
+        public String toString() {
+            return this.interfaceDescriptor() + "@Stub";
+        }
+
+        //@Override
+        public void onTransact(int _hidl_code, HwParcel _hidl_request, final HwParcel _hidl_reply, int _hidl_flags)
+                throws android.os.RemoteException {
+            switch (_hidl_code) {
+                case 1 /* initializationComplete */:
+                {
+                    _hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
+
+                    int status = _hidl_request.readInt32();
+                    initializationComplete(status);
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 2 /* hciEventReceived */:
+                {
+                    _hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
+
+                    java.util.ArrayList<Byte> event = _hidl_request.readInt8Vector();
+                    hciEventReceived(event);
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 3 /* aclDataReceived */:
+                {
+                    _hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
+
+                    java.util.ArrayList<Byte> data = _hidl_request.readInt8Vector();
+                    aclDataReceived(data);
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 4 /* scoDataReceived */:
+                {
+                    _hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName);
+
+                    java.util.ArrayList<Byte> data = _hidl_request.readInt8Vector();
+                    scoDataReceived(data);
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 5 /* isoDataReceived */:
+                {
+                    _hidl_request.enforceInterface(android.hardware.bluetooth.V1_1.IBluetoothHciCallbacks.kInterfaceName);
+
+                    java.util.ArrayList<Byte> data = _hidl_request.readInt8Vector();
+                    isoDataReceived(data);
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 256067662 /* interfaceChain */:
+                {
+                    _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+                    java.util.ArrayList<String> _hidl_out_descriptors = interfaceChain();
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    _hidl_reply.writeStringVector(_hidl_out_descriptors);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 256131655 /* debug */:
+                {
+                    _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+                    NativeHandle fd = _hidl_request.readNativeHandle();
+                    java.util.ArrayList<String> options = _hidl_request.readStringVector();
+                    debug(fd, options);
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 256136003 /* interfaceDescriptor */:
+                {
+                    _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+                    String _hidl_out_descriptor = interfaceDescriptor();
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    _hidl_reply.writeString(_hidl_out_descriptor);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 256398152 /* getHashChain */:
+                {
+                    _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+                    java.util.ArrayList<byte[/* 32 */]> _hidl_out_hashchain = getHashChain();
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    {
+                        HwBlob _hidl_blob = new HwBlob(16 /* size */);
+                        {
+                            int _hidl_vec_size = _hidl_out_hashchain.size();
+                            _hidl_blob.putInt32(0 /* offset */ + 8 /* offsetof(hidl_vec<T>, mSize) */, _hidl_vec_size);
+                            _hidl_blob.putBool(0 /* offset */ + 12 /* offsetof(hidl_vec<T>, mOwnsBuffer) */, false);
+                            HwBlob childBlob = new HwBlob((int)(_hidl_vec_size * 32));
+                            for (int _hidl_index_0 = 0; _hidl_index_0 < _hidl_vec_size; ++_hidl_index_0) {
+                                {
+                                    long _hidl_array_offset_1 = _hidl_index_0 * 32;
+                                    byte[] _hidl_array_item_1 = (byte[/* 32 */]) _hidl_out_hashchain.get(_hidl_index_0);
+
+                                    if (_hidl_array_item_1 == null || _hidl_array_item_1.length != 32) {
+                                        throw new IllegalArgumentException("Array element is not of the expected length");
+                                    }
+
+                                    childBlob.putInt8Array(_hidl_array_offset_1, _hidl_array_item_1);
+                                    _hidl_array_offset_1 += 32 * 1;
+                                }
+                            }
+                            _hidl_blob.putBlob(0 /* offset */ + 0 /* offsetof(hidl_vec<T>, mBuffer) */, childBlob);
+                        }
+                        _hidl_reply.writeBuffer(_hidl_blob);
+                    }
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 256462420 /* setHALInstrumentation */:
+                {
+                    _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+                    setHALInstrumentation();
+                    break;
+                }
+
+                case 256660548 /* linkToDeath */:
+                {
+                break;
+                }
+
+                case 256921159 /* ping */:
+                {
+                    _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+                    ping();
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 257049926 /* getDebugInfo */:
+                {
+                    _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+                    android.internal.hidl.base.V1_0.DebugInfo _hidl_out_info = getDebugInfo();
+                    _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS);
+                    ((android.internal.hidl.base.V1_0.DebugInfo) _hidl_out_info).writeToParcel(_hidl_reply);
+                    _hidl_reply.send();
+                    break;
+                }
+
+                case 257120595 /* notifySyspropsChanged */:
+                {
+                    _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName);
+
+                    notifySyspropsChanged();
+                    break;
+                }
+
+                case 257250372 /* unlinkToDeath */:
+                {
+                break;
+                }
+
+            }
+        }
+    }
+}
diff --git a/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciHal.java b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciHal.java
new file mode 100644
index 0000000..fd81921
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciHal.java
@@ -0,0 +1,260 @@
+package com.github.google.bumble.remotehci;
+
+import android.hardware.bluetooth.V1_0.Status;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.NoSuchElementException;
+
+public interface HciHal {
+    public enum Status {
+        SUCCESS("SUCCESS"),
+        ALREADY_INITIALIZED("ALREADY_INITIALIZED"),
+        UNABLE_TO_OPEN_INTERFACE("UNABLE_TO_OPEN_INTERFACE"),
+        INITIALIZATION_ERROR("INITIALIZATION_ERROR"),
+        TRANSPORT_ERROR("TRANSPORT_ERROR"),
+        UNKNOWN("UNKNOWN");
+
+        public final String label;
+
+        private Status(String label) {
+            this.label = label;
+        }
+    }
+    static final String TAG = "HciHal";
+    public static HciHal create(HciHalCallback hciCallbacks) {
+        // First try HIDL
+        HciHal hciHal = HciHidlHal.create(hciCallbacks);
+        if (hciHal != null) {
+            Log.d(TAG, "Found HIDL HAL");
+            return hciHal;
+        }
+
+        // Then try AIDL
+        hciHal = HciAidlHal.create(hciCallbacks);
+        if (hciHal != null) {
+            Log.d(TAG, "Found AIDL HAL");
+            return hciHal;
+        }
+
+        Log.d(TAG, "No HAL found");
+        return null;
+    }
+
+    public Status initialize() throws RemoteException, InterruptedException;
+    public void sendPacket(HciPacket.Type type, byte[] packet);
+}
+
+class HciHidlHal extends android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.Stub implements HciHal {
+    private static final String TAG = "HciHidlHal";
+    private final android.hardware.bluetooth.V1_0.IBluetoothHci mHciService;
+    private final HciHalCallback mHciCallbacks;
+    private int mInitializationStatus = -1;
+
+
+    public static HciHidlHal create(HciHalCallback hciCallbacks) {
+        // Get the HAL service.
+        android.hardware.bluetooth.V1_0.IBluetoothHci hciService;
+        try {
+            hciService = android.hardware.bluetooth.V1_0.IBluetoothHci.getService(true);
+        } catch (NoSuchElementException e) {
+            Log.d(TAG, "HIDL HAL V1.0 not found");
+            return null;
+        } catch (android.os.RemoteException e) {
+            Log.w(TAG, "Exception from getService: " + e);
+            return null;
+        }
+        Log.d(TAG, "Found HIDL HAL V1.0");
+        return new HciHidlHal(hciService, hciCallbacks);
+    }
+
+    private HciHidlHal(android.hardware.bluetooth.V1_0.IBluetoothHci hciService, HciHalCallback hciCallbacks) {
+        mHciService = hciService;
+        mHciCallbacks = hciCallbacks;
+    }
+
+    public Status initialize() throws RemoteException, InterruptedException {
+        // Trigger the initialization.
+        mHciService.initialize(this);
+
+        // Wait for the initialization to complete.
+        Log.d(TAG, "Waiting for initialization status...");
+        synchronized (this) {
+            while (mInitializationStatus == -1) {
+                wait();
+            }
+        }
+
+        // Map the status code.
+        switch (mInitializationStatus) {
+            case android.hardware.bluetooth.V1_0.Status.SUCCESS:
+                return Status.SUCCESS;
+
+            case android.hardware.bluetooth.V1_0.Status.TRANSPORT_ERROR:
+                return Status.TRANSPORT_ERROR;
+
+            case android.hardware.bluetooth.V1_0.Status.INITIALIZATION_ERROR:
+                return Status.INITIALIZATION_ERROR;
+
+            default:
+                return Status.UNKNOWN;
+        }
+    }
+
+    @Override
+    public void sendPacket(HciPacket.Type type, byte[] packet) {
+        ArrayList<Byte> data = HciPacket.byteArrayToList(packet);
+
+        try {
+            switch (type) {
+                case COMMAND:
+                    mHciService.sendHciCommand(data);
+                    break;
+
+                case ACL_DATA:
+                    mHciService.sendAclData(data);
+                    break;
+
+                case SCO_DATA:
+                    mHciService.sendScoData(data);
+                    break;
+            }
+        } catch (RemoteException error) {
+            Log.w(TAG, "failed to forward packet: " + error);
+        }
+    }
+
+    @Override
+    public synchronized void initializationComplete(int status) throws RemoteException {
+        mInitializationStatus = status;
+        notifyAll();
+    }
+
+    @Override
+    public void hciEventReceived(ArrayList<Byte> event) throws RemoteException {
+        byte[] packet = HciPacket.listToByteArray(event);
+        mHciCallbacks.onPacket(HciPacket.Type.EVENT, packet);
+    }
+
+    @Override
+    public void aclDataReceived(ArrayList<Byte> data) throws RemoteException {
+        byte[] packet = HciPacket.listToByteArray(data);
+        mHciCallbacks.onPacket(HciPacket.Type.ACL_DATA, packet);
+    }
+
+    @Override
+    public void scoDataReceived(ArrayList<Byte> data) throws RemoteException {
+        byte[] packet = HciPacket.listToByteArray(data);
+        mHciCallbacks.onPacket(HciPacket.Type.SCO_DATA, packet);
+    }
+}
+
+class HciAidlHal extends android.hardware.bluetooth.IBluetoothHciCallbacks.Stub implements HciHal {
+    private static final String TAG = "HciAidlHal";
+    private final android.hardware.bluetooth.IBluetoothHci mHciService;
+    private final HciHalCallback mHciCallbacks;
+    private int mInitializationStatus = android.hardware.bluetooth.Status.SUCCESS;
+
+    public static HciAidlHal create(HciHalCallback hciCallbacks) {
+        IBinder binder = ServiceManager.getService("android.hardware.bluetooth.IBluetoothHci/default");
+        if (binder == null) {
+            Log.d(TAG, "AIDL HAL not found");
+            return null;
+        }
+        android.hardware.bluetooth.IBluetoothHci hciService = android.hardware.bluetooth.IBluetoothHci.Stub.asInterface(binder);
+        return new HciAidlHal(hciService, hciCallbacks);
+    }
+
+    private HciAidlHal(android.hardware.bluetooth.IBluetoothHci hciService, HciHalCallback hciCallbacks) {
+        super();
+        mHciService = hciService;
+        mHciCallbacks = hciCallbacks;
+    }
+
+    public Status initialize() throws RemoteException, InterruptedException {
+        // Trigger the initialization.
+        mHciService.initialize(this);
+
+        // Wait for the initialization to complete.
+        Log.d(TAG, "Waiting for initialization status...");
+        synchronized (this) {
+            while (mInitializationStatus == -1) {
+                wait();
+            }
+        }
+
+        // Map the status code.
+        switch (mInitializationStatus) {
+            case android.hardware.bluetooth.Status.SUCCESS:
+                return Status.SUCCESS;
+
+            case android.hardware.bluetooth.Status.ALREADY_INITIALIZED:
+                return Status.ALREADY_INITIALIZED;
+
+            case android.hardware.bluetooth.Status.UNABLE_TO_OPEN_INTERFACE:
+                return Status.UNABLE_TO_OPEN_INTERFACE;
+
+            case android.hardware.bluetooth.Status.HARDWARE_INITIALIZATION_ERROR:
+                return Status.INITIALIZATION_ERROR;
+
+            default:
+                return Status.UNKNOWN;
+        }
+    }
+
+    // HciHal methods.
+    @Override
+    public void sendPacket(HciPacket.Type type, byte[] packet) {
+        try {
+            switch (type) {
+                case COMMAND:
+                    mHciService.sendHciCommand(packet);
+                    break;
+
+                case ACL_DATA:
+                    mHciService.sendAclData(packet);
+                    break;
+
+                case SCO_DATA:
+                    mHciService.sendScoData(packet);
+                    break;
+
+                case ISO_DATA:
+                    mHciService.sendIsoData(packet);
+                    break;
+            }
+        } catch (RemoteException error) {
+            Log.w(TAG, "failed to forward packet: " + error);
+        }
+    }
+
+    // IBluetoothHciCallbacks methods.
+    @Override
+    public synchronized void initializationComplete(int status) throws RemoteException {
+        mInitializationStatus = status;
+        notifyAll();
+    }
+
+    @Override
+    public void hciEventReceived(byte[] event) throws RemoteException {
+        mHciCallbacks.onPacket(HciPacket.Type.EVENT, event);
+    }
+
+    @Override
+    public void aclDataReceived(byte[] data) throws RemoteException {
+        mHciCallbacks.onPacket(HciPacket.Type.ACL_DATA, data);
+    }
+
+    @Override
+    public void scoDataReceived(byte[] data) throws RemoteException {
+        mHciCallbacks.onPacket(HciPacket.Type.SCO_DATA, data);
+    }
+
+    @Override
+    public void isoDataReceived(byte[] data) throws RemoteException {
+        mHciCallbacks.onPacket(HciPacket.Type.ISO_DATA, data);
+    }
+}
\ No newline at end of file
diff --git a/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciHalCallback.java b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciHalCallback.java
new file mode 100644
index 0000000..5878fc1
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciHalCallback.java
@@ -0,0 +1,5 @@
+package com.github.google.bumble.remotehci;
+
+public interface HciHalCallback {
+    public void onPacket(HciPacket.Type type, byte[] packet);
+}
diff --git a/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciPacket.java b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciPacket.java
new file mode 100644
index 0000000..2f3162b
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciPacket.java
@@ -0,0 +1,69 @@
+package com.github.google.bumble.remotehci;
+
+import java.util.ArrayList;
+
+public class HciPacket {
+    public enum Type {
+        COMMAND((byte) 1),
+        ACL_DATA((byte) 2),
+        SCO_DATA((byte) 3),
+        EVENT((byte) 4),
+        ISO_DATA((byte)5);
+
+        final byte value;
+        final int lengthSize;
+        final int lengthOffset;
+
+        Type(byte value) throws IllegalArgumentException {
+            switch (value) {
+                case 1:
+                case 3:
+                    lengthSize = 1;
+                    lengthOffset = 2;
+                    break;
+
+                case 2:
+                case 5:
+                    lengthSize = 2;
+                    lengthOffset = 2;
+                    break;
+
+                case 4:
+                    lengthSize = 1;
+                    lengthOffset = 1;
+                    break;
+
+                default:
+                    throw new IllegalArgumentException();
+
+            }
+            this.value = value;
+        }
+
+        static Type fromValue(byte value) {
+            for (Type type : values()) {
+                if (type.value == value) {
+                    return type;
+                }
+            }
+            return null;
+        }
+    }
+
+    public static ArrayList<Byte> byteArrayToList(byte[] byteArray) {
+        ArrayList<Byte> list = new ArrayList<>();
+        list.ensureCapacity(byteArray.length);
+        for (byte x : byteArray) {
+            list.add(x);
+        }
+        return list;
+    }
+
+    public static byte[] listToByteArray(ArrayList<Byte> byteList) {
+        byte[] byteArray = new byte[byteList.size()];
+        for (int i = 0; i < byteList.size(); i++) {
+            byteArray[i] = byteList.get(i);
+        }
+        return byteArray;
+    }
+}
diff --git a/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciParser.java b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciParser.java
new file mode 100644
index 0000000..8647468
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciParser.java
@@ -0,0 +1,83 @@
+package com.github.google.bumble.remotehci;
+
+import static java.lang.Integer.min;
+
+import java.io.ByteArrayOutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+
+class HciParser {
+    Sink sink;
+    State state;
+    int bytesNeeded;
+    ByteArrayOutputStream packet = new ByteArrayOutputStream();
+    HciPacket.Type packetType;
+
+    HciParser(Sink sink) {
+        this.sink = sink;
+        reset();
+    }
+
+    void feedData(byte[] data, int dataSize) {
+        int dataOffset = 0;
+        int dataLeft = dataSize;
+
+        while (dataLeft > 0 && bytesNeeded > 0) {
+            int consumed = min(dataLeft, bytesNeeded);
+            if (state != State.NEED_TYPE) {
+                packet.write(data, dataOffset, consumed);
+            }
+            bytesNeeded -= consumed;
+
+            if (bytesNeeded == 0) {
+                if (state == State.NEED_TYPE) {
+                    packetType = HciPacket.Type.fromValue(data[dataOffset]);
+                    if (packetType == null) {
+                        throw new InvalidFormatException();
+                    }
+                    bytesNeeded = packetType.lengthOffset + packetType.lengthSize;
+                    state = State.NEED_LENGTH;
+                } else if (state == State.NEED_LENGTH) {
+                    ByteBuffer lengthBuffer =
+                            ByteBuffer.wrap(packet.toByteArray())
+                                    .order(ByteOrder.LITTLE_ENDIAN);
+                    bytesNeeded = packetType.lengthSize == 1 ?
+                            lengthBuffer.get(packetType.lengthOffset) & 0xFF :
+                            lengthBuffer.getShort(packetType.lengthOffset) & 0xFFFF;
+                    state = State.NEED_BODY;
+                }
+
+                // Emit a packet if one is complete.
+                if (state == State.NEED_BODY && bytesNeeded == 0) {
+                    if (sink != null) {
+                        sink.onPacket(packetType, packet.toByteArray());
+                    }
+
+                    reset();
+                }
+            }
+
+            dataOffset += consumed;
+            dataLeft -= consumed;
+        }
+    }
+
+    void reset() {
+        state = State.NEED_TYPE;
+        bytesNeeded = 1;
+        packet.reset();
+        packetType = null;
+    }
+
+    enum State {
+        NEED_TYPE, NEED_LENGTH, NEED_BODY
+    }
+
+    interface Sink {
+        void onPacket(HciPacket.Type type, byte[] packet);
+    }
+
+    static class InvalidFormatException extends RuntimeException {
+    }
+}
\ No newline at end of file
diff --git a/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciProxy.java b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciProxy.java
new file mode 100644
index 0000000..1949a63
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciProxy.java
@@ -0,0 +1,143 @@
+package com.github.google.bumble.remotehci;
+
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.IOException;
+
+public class HciProxy {
+    private static final String TAG = "HciProxy";
+    private final HciServer mServer;
+    private final Listener mListener;
+    private int mCommandPacketsReceived;
+    private int mAclPacketsReceived;
+    private int mScoPacketsReceived;
+    private int mEventPacketsSent;
+    private int mAclPacketsSent;
+    private int mScoPacketsSent;
+
+    HciProxy(int port, Listener listener) throws HalException {
+        this.mListener = listener;
+
+        // Instantiate a HAL to communicate with the hardware.
+        HciHal hciHal = HciHal.create(new HciHalCallback() {
+            @Override
+            public void onPacket(HciPacket.Type type, byte[] packet) {
+                mServer.sendPacket(type, packet);
+
+                switch (type) {
+                    case EVENT:
+                        mEventPacketsSent += 1;
+                        break;
+
+                    case ACL_DATA:
+                        mAclPacketsSent += 1;
+                        break;
+
+                    case SCO_DATA:
+                        mScoPacketsSent += 1;
+                        break;
+                }
+                updateHciPacketCount();
+            }
+        });
+        if (hciHal == null) {
+            String message = "Could not instantiate a HAL instance";
+            Log.w(TAG, message);
+            throw new HalException(message);
+        }
+
+        // Initialize the HAL.
+        HciHal.Status status = null;
+        try {
+            status = hciHal.initialize();
+        } catch (RemoteException | InterruptedException e) {
+            throw new HalException("Exception while initializing");
+        }
+        if (status != HciHal.Status.SUCCESS) {
+            String message = "HAL initialization failed: " + status.label;
+            Log.w(TAG, message);
+            throw new HalException(message);
+        }
+
+        // Create a server to accept clients.
+        mServer = new HciServer(port, new HciServer.Listener() {
+            @Override
+            public void onHostConnectionState(boolean connected) {
+                mListener.onHostConnectionState(connected);
+                if (connected) {
+                    mCommandPacketsReceived = 0;
+                    mAclPacketsReceived = 0;
+                    mScoPacketsReceived = 0;
+                    mEventPacketsSent = 0;
+                    mAclPacketsSent = 0;
+                    mScoPacketsSent = 0;
+                    updateHciPacketCount();
+                }
+            }
+
+            @Override
+            public void onMessage(String message) {
+                listener.onMessage(message);
+            }
+
+            @Override
+            public void onPacket(HciPacket.Type type, byte[] packet) {
+                Log.d(TAG, String.format("onPacket: type=%s, size=%d", type, packet.length));
+                hciHal.sendPacket(type, packet);
+
+                switch (type) {
+                    case COMMAND:
+                        mCommandPacketsReceived += 1;
+                        break;
+
+                    case ACL_DATA:
+                        mAclPacketsReceived += 1;
+                        break;
+
+                    case SCO_DATA:
+                        mScoPacketsReceived += 1;
+                        break;
+                }
+                updateHciPacketCount();
+            }
+        });
+    }
+
+    public void run() throws IOException {
+        mServer.run();
+    }
+
+    private void updateHciPacketCount() {
+        mListener.onHciPacketCountChange(
+                mCommandPacketsReceived,
+                mAclPacketsReceived,
+                mScoPacketsReceived,
+                mEventPacketsSent,
+                mAclPacketsSent,
+                mScoPacketsSent
+        );
+    }
+
+    public interface Listener {
+        void onHostConnectionState(boolean connected);
+
+        void onHciPacketCountChange(
+                int commandPacketsReceived,
+                int aclPacketsReceived,
+                int scoPacketsReceived,
+                int eventPacketsSent,
+                int aclPacketsSent,
+                int scoPacketsSent
+        );
+
+        void onMessage(String message);
+    }
+
+    public static class HalException extends RuntimeException {
+        public final String message;
+        public HalException(String message) {
+            this.message = message;
+        }
+    }
+}
diff --git a/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciServer.java b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciServer.java
new file mode 100644
index 0000000..a78a86a
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciServer.java
@@ -0,0 +1,92 @@
+package com.github.google.bumble.remotehci;
+
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+
+public class HciServer {
+    private static final String TAG = "HciServer";
+    private static final int BUFFER_SIZE = 1024;
+    private final int mPort;
+    private final Listener mListener;
+    private OutputStream mOutputStream;
+
+    public interface Listener extends HciParser.Sink {
+        void onHostConnectionState(boolean connected);
+        void onMessage(String message);
+    }
+
+    HciServer(int port, Listener listener) {
+        this.mPort = port;
+        this.mListener = listener;
+    }
+
+    public void run() throws IOException {
+        for (;;) {
+            try {
+                loop();
+            } catch (IOException error) {
+                mListener.onMessage("Cannot listen on port " + mPort);
+                return;
+            }
+        }
+    }
+
+    private void loop() throws IOException {
+        mListener.onHostConnectionState(false);
+        try (ServerSocket serverSocket = new ServerSocket(mPort)) {
+            mListener.onMessage("Waiting for connection on port " + serverSocket.getLocalPort());
+            try (Socket clientSocket = serverSocket.accept()) {
+                mListener.onHostConnectionState(true);
+                mListener.onMessage("Connected");
+                HciParser parser = new HciParser(mListener);
+                InputStream inputStream = clientSocket.getInputStream();
+                synchronized (this) {
+                    mOutputStream = clientSocket.getOutputStream();
+                }
+                byte[] buffer = new byte[BUFFER_SIZE];
+
+                try {
+                    for (; ; ) {
+                        int bytesRead = inputStream.read(buffer);
+                        if (bytesRead < 0) {
+                            Log.d(TAG, "end of stream");
+                            break;
+                        }
+                        parser.feedData(buffer, bytesRead);
+                    }
+                } catch (IOException error) {
+                    Log.d(TAG, "exception in read loop: " + error);
+                }
+            }
+        } finally {
+            synchronized (this) {
+                mOutputStream = null;
+            }
+        }
+    }
+
+    public void sendPacket(HciPacket.Type type, byte[] packet) {
+        // Create a combined data buffer so we can write it out in a single call.
+        byte[] data = new byte[packet.length + 1];
+        data[0] = type.value;
+        System.arraycopy(packet, 0, data, 1, packet.length);
+
+        synchronized (this) {
+            if (mOutputStream != null) {
+                try {
+                    mOutputStream.write(data);
+                } catch (IOException error) {
+                    Log.w(TAG, "failed to write packet: " + error);
+                }
+            } else {
+                Log.d(TAG, "no client, dropping packet");
+            }
+        }
+    }
+}
diff --git a/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/MainActivity.kt b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/MainActivity.kt
new file mode 100644
index 0000000..3a2630a
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/MainActivity.kt
@@ -0,0 +1,229 @@
+package com.github.google.bumble.remotehci
+
+import android.content.Context
+import android.content.SharedPreferences
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.text.KeyboardActions
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.material3.Button
+import androidx.compose.material3.Divider
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextField
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalSoftwareKeyboardController
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.lifecycle.ViewModel
+import com.github.google.bumble.remotehci.HciProxy.HalException
+import com.github.google.bumble.remotehci.ui.theme.RemoteHCITheme
+import java.io.IOException
+import java.util.logging.Logger
+import kotlin.concurrent.thread
+
+const val DEFAULT_TCP_PORT = 9993
+const val TCP_PORT_PREF_KEY = "tcp_port"
+
+class AppViewModel : ViewModel(), HciProxy.Listener {
+    private var preferences: SharedPreferences? = null
+    var tcpPort by mutableStateOf(DEFAULT_TCP_PORT)
+    var canStart by mutableStateOf(true)
+    var message by mutableStateOf("")
+    var hostConnected by mutableStateOf(false)
+    var hciCommandPacketsReceived by mutableStateOf(0)
+    var hciAclPacketsReceived by mutableStateOf(0)
+    var hciScoPacketsReceived by mutableStateOf(0)
+    var hciEventPacketsSent by mutableStateOf(0)
+    var hciAclPacketsSent by mutableStateOf(0)
+    var hciScoPacketsSent by mutableStateOf(0)
+
+    fun loadPreferences(preferences: SharedPreferences) {
+        this.preferences = preferences
+        val savedTcpPortString = preferences.getString(TCP_PORT_PREF_KEY, null)
+        if (savedTcpPortString != null) {
+            val savedTcpPortInt = savedTcpPortString.toIntOrNull()
+            if (savedTcpPortInt != null) {
+                tcpPort = savedTcpPortInt
+            }
+        }
+    }
+
+    fun updateTcpPort(tcpPort: Int) {
+        this.tcpPort = tcpPort
+
+        // Save the port to the preferences
+        with (preferences!!.edit()) {
+            putString(TCP_PORT_PREF_KEY, tcpPort.toString())
+            apply()
+        }
+    }
+
+    override fun onHostConnectionState(connected: Boolean) {
+        hostConnected = connected
+    }
+
+    override fun onHciPacketCountChange(
+        commandPacketsReceived: Int,
+        aclPacketsReceived: Int,
+        scoPacketsReceived: Int,
+        eventPacketsSent: Int,
+        aclPacketsSent: Int,
+        scoPacketsSent: Int
+    ) {
+        hciCommandPacketsReceived = commandPacketsReceived
+        hciAclPacketsReceived = aclPacketsReceived
+        hciScoPacketsReceived = scoPacketsReceived
+        hciEventPacketsSent = eventPacketsSent
+        hciAclPacketsSent = aclPacketsSent
+        hciScoPacketsSent = scoPacketsSent
+
+    }
+
+    override fun onMessage(message: String) {
+        this.message = message
+    }
+}
+
+class MainActivity : ComponentActivity() {
+    private val log = Logger.getLogger(MainActivity::class.java.name)
+    private val appViewModel = AppViewModel()
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        appViewModel.loadPreferences(getPreferences(Context.MODE_PRIVATE))
+
+        val tcpPort = intent.getIntExtra("port", -1)
+        if (tcpPort >= 0) {
+            appViewModel.tcpPort = tcpPport
+        }
+
+        setContent {
+            MainView(appViewModel, ::startProxy)
+        }
+
+        if (intent.getBooleanExtra("autostart", false)) {
+            startProxy()
+        }
+    }
+
+    private fun startProxy() {
+        // Run the proxy in a thread.
+        appViewModel.message = ""
+        thread {
+            log.info("HCI Proxy thread starting")
+            appViewModel.canStart = false
+            try {
+                val hciProxy = HciProxy(appViewModel.tcpPort, appViewModel)
+                hciProxy.run()
+            } catch (error: IOException) {
+                log.warning("Exception while running HCI Server: $error")
+            } catch (error: HalException) {
+                log.warning("HAL exception: ${error.message}")
+                appViewModel.message = "Cannot bind to HAL (${error.message}). You may need to use the command 'setenforce 0' in a root adb shell."
+            }
+            log.info("HCI Proxy thread ended")
+            appViewModel.canStart = true
+        }
+    }
+}
+
+@Composable
+fun ActionButton(text: String, onClick: () -> Unit, enabled: Boolean) {
+    Button(onClick = onClick, enabled = enabled) {
+        Text(text = text)
+    }
+}
+
+@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class)
+@Composable
+fun MainView(appViewModel: AppViewModel, startProxy: () -> Unit) {
+    RemoteHCITheme {
+        // A surface container using the 'background' color from the theme
+        Surface(
+            modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background
+        ) {
+            Column(modifier = Modifier.padding(horizontal = 16.dp)) {
+                Text(
+                    text = "Bumble Remote HCI",
+                    fontSize = 24.sp,
+                    fontWeight = FontWeight.Bold,
+                    textAlign = TextAlign.Center
+                )
+                Divider()
+                Text(
+                    text = appViewModel.message
+                )
+                Divider()
+                val keyboardController = LocalSoftwareKeyboardController.current
+                TextField(
+                    label = {
+                        Text(text = "TCP Port")
+                    },
+                    value = appViewModel.tcpPort.toString(),
+                    modifier = Modifier.fillMaxWidth(),
+                    keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done),
+                    onValueChange = {
+                        if (it.isNotEmpty()) {
+                            val tcpPort = it.toIntOrNull()
+                            if (tcpPort != null) {
+                                appViewModel.updateTcpPort(tcpPort)
+                            }
+                        }
+                    },
+                    keyboardActions = KeyboardActions(
+                        onDone = {keyboardController?.hide()}
+                    )
+                )
+                Divider()
+                val connectState = if (appViewModel.hostConnected) "CONNECTED" else "DISCONNECTED"
+                Text(
+                    text = "HOST: $connectState",
+                    modifier = Modifier.background(color = if (appViewModel.hostConnected) Color.Green else Color.Red),
+                    color = Color.Black
+                )
+                Divider()
+                Text(
+                    text = "Command Packets Received: ${appViewModel.hciCommandPacketsReceived}"
+                )
+                Text(
+                    text = "ACL Packets Received: ${appViewModel.hciAclPacketsReceived}"
+                )
+                Text(
+                    text = "SCO Packets Received: ${appViewModel.hciScoPacketsReceived}"
+                )
+                Text(
+                    text = "Event Packets Sent: ${appViewModel.hciEventPacketsSent}"
+                )
+                Text(
+                    text = "ACL Packets Sent: ${appViewModel.hciAclPacketsSent}"
+                )
+                Text(
+                    text = "SCO Packets Sent: ${appViewModel.hciScoPacketsSent}"
+                )
+                Divider()
+                ActionButton(
+                    text = "Start", onClick = startProxy, enabled = appViewModel.canStart
+                )
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/ui/theme/Color.kt b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/ui/theme/Color.kt
new file mode 100644
index 0000000..420958d
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/ui/theme/Color.kt
@@ -0,0 +1,11 @@
+package com.github.google.bumble.remotehci.ui.theme
+
+import androidx.compose.ui.graphics.Color
+
+val Purple80 = Color(0xFFD0BCFF)
+val PurpleGrey80 = Color(0xFFCCC2DC)
+val Pink80 = Color(0xFFEFB8C8)
+
+val Purple40 = Color(0xFF6650a4)
+val PurpleGrey40 = Color(0xFF625b71)
+val Pink40 = Color(0xFF7D5260)
\ No newline at end of file
diff --git a/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/ui/theme/Theme.kt b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/ui/theme/Theme.kt
new file mode 100644
index 0000000..8d707c9
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/ui/theme/Theme.kt
@@ -0,0 +1,70 @@
+package com.github.google.bumble.remotehci.ui.theme
+
+import android.app.Activity
+import android.os.Build
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.darkColorScheme
+import androidx.compose.material3.dynamicDarkColorScheme
+import androidx.compose.material3.dynamicLightColorScheme
+import androidx.compose.material3.lightColorScheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalView
+import androidx.core.view.WindowCompat
+
+private val DarkColorScheme = darkColorScheme(
+    primary = Purple80,
+    secondary = PurpleGrey80,
+    tertiary = Pink80
+)
+
+private val LightColorScheme = lightColorScheme(
+    primary = Purple40,
+    secondary = PurpleGrey40,
+    tertiary = Pink40
+
+    /* Other default colors to override
+    background = Color(0xFFFFFBFE),
+    surface = Color(0xFFFFFBFE),
+    onPrimary = Color.White,
+    onSecondary = Color.White,
+    onTertiary = Color.White,
+    onBackground = Color(0xFF1C1B1F),
+    onSurface = Color(0xFF1C1B1F),
+    */
+)
+
+@Composable
+fun RemoteHCITheme(
+    darkTheme: Boolean = isSystemInDarkTheme(),
+    // Dynamic color is available on Android 12+
+    dynamicColor: Boolean = true,
+    content: @Composable () -> Unit
+) {
+    val colorScheme = when {
+        dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
+            val context = LocalContext.current
+            if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
+        }
+
+        darkTheme -> DarkColorScheme
+        else -> LightColorScheme
+    }
+    val view = LocalView.current
+    if (!view.isInEditMode) {
+        SideEffect {
+            val window = (view.context as Activity).window
+            window.statusBarColor = colorScheme.primary.toArgb()
+            WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
+        }
+    }
+
+    MaterialTheme(
+        colorScheme = colorScheme,
+        typography = Typography,
+        content = content
+    )
+}
\ No newline at end of file
diff --git a/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/ui/theme/Type.kt b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/ui/theme/Type.kt
new file mode 100644
index 0000000..5017af2
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/ui/theme/Type.kt
@@ -0,0 +1,34 @@
+package com.github.google.bumble.remotehci.ui.theme
+
+import androidx.compose.material3.Typography
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.sp
+
+// Set of Material typography styles to start with
+val Typography = Typography(
+    bodyLarge = TextStyle(
+        fontFamily = FontFamily.Default,
+        fontWeight = FontWeight.Normal,
+        fontSize = 16.sp,
+        lineHeight = 24.sp,
+        letterSpacing = 0.5.sp
+    )
+    /* Other default text styles to override
+    titleLarge = TextStyle(
+        fontFamily = FontFamily.Default,
+        fontWeight = FontWeight.Normal,
+        fontSize = 22.sp,
+        lineHeight = 28.sp,
+        letterSpacing = 0.sp
+    ),
+    labelSmall = TextStyle(
+        fontFamily = FontFamily.Default,
+        fontWeight = FontWeight.Medium,
+        fontSize = 11.sp,
+        lineHeight = 16.sp,
+        letterSpacing = 0.5.sp
+    )
+    */
+)
\ No newline at end of file
diff --git a/extras/android/RemoteHCI/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/extras/android/RemoteHCI/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..036d09b
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@color/ic_launcher_background"/>
+    <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
+</adaptive-icon>
\ No newline at end of file
diff --git a/extras/android/RemoteHCI/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/extras/android/RemoteHCI/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..036d09b
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@color/ic_launcher_background"/>
+    <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
+</adaptive-icon>
\ No newline at end of file
diff --git a/extras/android/RemoteHCI/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/extras/android/RemoteHCI/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..f01f61e
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/res/mipmap-hdpi/ic_launcher.webp
Binary files differ
diff --git a/extras/android/RemoteHCI/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/extras/android/RemoteHCI/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..bb2143e
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
Binary files differ
diff --git a/extras/android/RemoteHCI/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/extras/android/RemoteHCI/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..1d9c6ae
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Binary files differ
diff --git a/extras/android/RemoteHCI/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/extras/android/RemoteHCI/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..2a80e30
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/res/mipmap-mdpi/ic_launcher.webp
Binary files differ
diff --git a/extras/android/RemoteHCI/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/extras/android/RemoteHCI/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..9764352
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
Binary files differ
diff --git a/extras/android/RemoteHCI/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/extras/android/RemoteHCI/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..45f205b
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Binary files differ
diff --git a/extras/android/RemoteHCI/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/extras/android/RemoteHCI/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..c607bc0
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
Binary files differ
diff --git a/extras/android/RemoteHCI/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/extras/android/RemoteHCI/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..bb2d0f8
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp
Binary files differ
diff --git a/extras/android/RemoteHCI/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/extras/android/RemoteHCI/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..54c5d2f
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/extras/android/RemoteHCI/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/extras/android/RemoteHCI/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..9afe565
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Binary files differ
diff --git a/extras/android/RemoteHCI/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp b/extras/android/RemoteHCI/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..a5f9571
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp
Binary files differ
diff --git a/extras/android/RemoteHCI/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/extras/android/RemoteHCI/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..142c967
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/extras/android/RemoteHCI/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/extras/android/RemoteHCI/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..c9792ad
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Binary files differ
diff --git a/extras/android/RemoteHCI/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/extras/android/RemoteHCI/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
new file mode 100644
index 0000000..434da3f
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
Binary files differ
diff --git a/extras/android/RemoteHCI/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/extras/android/RemoteHCI/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b48797d
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/extras/android/RemoteHCI/app/src/main/res/values/colors.xml b/extras/android/RemoteHCI/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..f8c6127
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/res/values/colors.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="purple_200">#FFBB86FC</color>
+    <color name="purple_500">#FF6200EE</color>
+    <color name="purple_700">#FF3700B3</color>
+    <color name="teal_200">#FF03DAC5</color>
+    <color name="teal_700">#FF018786</color>
+    <color name="black">#FF000000</color>
+    <color name="white">#FFFFFFFF</color>
+</resources>
\ No newline at end of file
diff --git a/extras/android/RemoteHCI/app/src/main/res/values/ic_launcher_background.xml b/extras/android/RemoteHCI/app/src/main/res/values/ic_launcher_background.xml
new file mode 100644
index 0000000..c5d5899
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/res/values/ic_launcher_background.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="ic_launcher_background">#FFFFFF</color>
+</resources>
\ No newline at end of file
diff --git a/extras/android/RemoteHCI/app/src/main/res/values/strings.xml b/extras/android/RemoteHCI/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..af77941
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">Remote HCI</string>
+</resources>
\ No newline at end of file
diff --git a/extras/android/RemoteHCI/app/src/main/res/values/themes.xml b/extras/android/RemoteHCI/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..cd813bf
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/res/values/themes.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+    <style name="Theme.RemoteHCI" parent="android:Theme.Material.Light.NoActionBar" />
+</resources>
\ No newline at end of file
diff --git a/extras/android/RemoteHCI/app/src/main/res/xml/backup_rules.xml b/extras/android/RemoteHCI/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 0000000..fa0f996
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+   Sample backup rules file; uncomment and customize as necessary.
+   See https://developer.android.com/guide/topics/data/autobackup
+   for details.
+   Note: This file is ignored for devices older that API 31
+   See https://developer.android.com/about/versions/12/backup-restore
+-->
+<full-backup-content>
+    <!--
+   <include domain="sharedpref" path="."/>
+   <exclude domain="sharedpref" path="device.xml"/>
+-->
+</full-backup-content>
\ No newline at end of file
diff --git a/extras/android/RemoteHCI/app/src/main/res/xml/data_extraction_rules.xml b/extras/android/RemoteHCI/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..9ee9997
--- /dev/null
+++ b/extras/android/RemoteHCI/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+   Sample data extraction rules file; uncomment and customize as necessary.
+   See https://developer.android.com/about/versions/12/backup-restore#xml-changes
+   for details.
+-->
+<data-extraction-rules>
+    <cloud-backup>
+        <!-- TODO: Use <include> and <exclude> to control what is backed up.
+        <include .../>
+        <exclude .../>
+        -->
+    </cloud-backup>
+    <!--
+    <device-transfer>
+        <include .../>
+        <exclude .../>
+    </device-transfer>
+    -->
+</data-extraction-rules>
\ No newline at end of file
diff --git a/extras/android/RemoteHCI/build.gradle.kts b/extras/android/RemoteHCI/build.gradle.kts
new file mode 100644
index 0000000..3a03bf6
--- /dev/null
+++ b/extras/android/RemoteHCI/build.gradle.kts
@@ -0,0 +1,8 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
+plugins {
+    alias(libs.plugins.androidApplication) apply false
+    alias(libs.plugins.kotlinAndroid) apply false
+    alias(libs.plugins.androidLibrary) apply false
+}
+true // Needed to make the Suppress annotation work for the plugins block
diff --git a/extras/android/RemoteHCI/gradle.properties b/extras/android/RemoteHCI/gradle.properties
new file mode 100644
index 0000000..2cbd6d1
--- /dev/null
+++ b/extras/android/RemoteHCI/gradle.properties
@@ -0,0 +1,23 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Kotlin code style for this project: "official" or "obsolete":
+kotlin.code.style=official
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true
diff --git a/extras/android/RemoteHCI/gradle/libs.versions.toml b/extras/android/RemoteHCI/gradle/libs.versions.toml
new file mode 100644
index 0000000..cc1b0f5
--- /dev/null
+++ b/extras/android/RemoteHCI/gradle/libs.versions.toml
@@ -0,0 +1,36 @@
+[versions]
+agp = "8.3.0-alpha05"
+kotlin = "1.8.10"
+core-ktx = "1.9.0"
+junit = "4.13.2"
+androidx-test-ext-junit = "1.1.5"
+espresso-core = "3.5.1"
+lifecycle-runtime-ktx = "2.6.1"
+activity-compose = "1.7.2"
+compose-bom = "2023.03.00"
+appcompat = "1.6.1"
+material = "1.9.0"
+
+[libraries]
+core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" }
+junit = { group = "junit", name = "junit", version.ref = "junit" }
+androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-ext-junit" }
+espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso-core" }
+lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle-runtime-ktx" }
+activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activity-compose" }
+compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
+ui = { group = "androidx.compose.ui", name = "ui" }
+ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
+ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
+ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
+ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
+ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
+material3 = { group = "androidx.compose.material3", name = "material3" }
+appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
+material = { group = "com.google.android.material", name = "material", version.ref = "material" }
+
+[plugins]
+androidApplication = { id = "com.android.application", version.ref = "agp" }
+kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
+androidLibrary = { id = "com.android.library", version.ref = "agp" }
+
diff --git a/extras/android/RemoteHCI/gradle/wrapper/gradle-wrapper.jar b/extras/android/RemoteHCI/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..e708b1c
--- /dev/null
+++ b/extras/android/RemoteHCI/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/extras/android/RemoteHCI/gradle/wrapper/gradle-wrapper.properties b/extras/android/RemoteHCI/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..d58714b
--- /dev/null
+++ b/extras/android/RemoteHCI/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Sun Aug 06 12:53:26 PDT 2023
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-rc-2-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/extras/android/RemoteHCI/gradlew b/extras/android/RemoteHCI/gradlew
new file mode 100755
index 0000000..4f906e0
--- /dev/null
+++ b/extras/android/RemoteHCI/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+    echo "$*"
+}
+
+die () {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+  NONSTOP* )
+    nonstop=true
+    ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=`expr $i + 1`
+    done
+    case $i in
+        0) set -- ;;
+        1) set -- "$args0" ;;
+        2) set -- "$args0" "$args1" ;;
+        3) set -- "$args0" "$args1" "$args2" ;;
+        4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Escape application args
+save () {
+    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+    echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/extras/android/RemoteHCI/gradlew.bat b/extras/android/RemoteHCI/gradlew.bat
new file mode 100644
index 0000000..ac1b06f
--- /dev/null
+++ b/extras/android/RemoteHCI/gradlew.bat
@@ -0,0 +1,89 @@
+@rem

+@rem Copyright 2015 the original author or authors.

+@rem

+@rem Licensed under the Apache License, Version 2.0 (the "License");

+@rem you may not use this file except in compliance with the License.

+@rem You may obtain a copy of the License at

+@rem

+@rem      https://www.apache.org/licenses/LICENSE-2.0

+@rem

+@rem Unless required by applicable law or agreed to in writing, software

+@rem distributed under the License is distributed on an "AS IS" BASIS,

+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+@rem See the License for the specific language governing permissions and

+@rem limitations under the License.

+@rem

+

+@if "%DEBUG%" == "" @echo off

+@rem ##########################################################################

+@rem

+@rem  Gradle startup script for Windows

+@rem

+@rem ##########################################################################

+

+@rem Set local scope for the variables with windows NT shell

+if "%OS%"=="Windows_NT" setlocal

+

+set DIRNAME=%~dp0

+if "%DIRNAME%" == "" set DIRNAME=.

+set APP_BASE_NAME=%~n0

+set APP_HOME=%DIRNAME%

+

+@rem Resolve any "." and ".." in APP_HOME to make it shorter.

+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi

+

+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"

+

+@rem Find java.exe

+if defined JAVA_HOME goto findJavaFromJavaHome

+

+set JAVA_EXE=java.exe

+%JAVA_EXE% -version >NUL 2>&1

+if "%ERRORLEVEL%" == "0" goto execute

+

+echo.

+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:findJavaFromJavaHome

+set JAVA_HOME=%JAVA_HOME:"=%

+set JAVA_EXE=%JAVA_HOME%/bin/java.exe

+

+if exist "%JAVA_EXE%" goto execute

+

+echo.

+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%

+echo.

+echo Please set the JAVA_HOME variable in your environment to match the

+echo location of your Java installation.

+

+goto fail

+

+:execute

+@rem Setup the command line

+

+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

+

+

+@rem Execute Gradle

+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*

+

+:end

+@rem End local scope for the variables with windows NT shell

+if "%ERRORLEVEL%"=="0" goto mainEnd

+

+:fail

+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of

+rem the _cmd.exe /c_ return code!

+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1

+exit /b 1

+

+:mainEnd

+if "%OS%"=="Windows_NT" endlocal

+

+:omega

diff --git a/extras/android/RemoteHCI/lib/.gitignore b/extras/android/RemoteHCI/lib/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/extras/android/RemoteHCI/lib/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/extras/android/RemoteHCI/lib/build.gradle.kts b/extras/android/RemoteHCI/lib/build.gradle.kts
new file mode 100644
index 0000000..70ab0d4
--- /dev/null
+++ b/extras/android/RemoteHCI/lib/build.gradle.kts
@@ -0,0 +1,39 @@
+@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
+plugins {
+    alias(libs.plugins.androidLibrary)
+}
+
+android {
+    namespace = "com.github.google.bumble"
+    compileSdk = 34
+
+    defaultConfig {
+        minSdk = 26
+
+        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+        consumerProguardFiles("consumer-rules.pro")
+    }
+
+    buildTypes {
+        release {
+            isMinifyEnabled = false
+            proguardFiles(
+                getDefaultProguardFile("proguard-android-optimize.txt"),
+                "proguard-rules.pro"
+            )
+        }
+    }
+    compileOptions {
+        sourceCompatibility = JavaVersion.VERSION_1_8
+        targetCompatibility = JavaVersion.VERSION_1_8
+    }
+}
+
+dependencies {
+
+    implementation(libs.appcompat)
+    implementation(libs.material)
+    testImplementation(libs.junit)
+    androidTestImplementation(libs.androidx.test.ext.junit)
+    androidTestImplementation(libs.espresso.core)
+}
\ No newline at end of file
diff --git a/extras/android/RemoteHCI/lib/consumer-rules.pro b/extras/android/RemoteHCI/lib/consumer-rules.pro
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/extras/android/RemoteHCI/lib/consumer-rules.pro
diff --git a/extras/android/RemoteHCI/lib/proguard-rules.pro b/extras/android/RemoteHCI/lib/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/extras/android/RemoteHCI/lib/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/extras/android/RemoteHCI/lib/src/main/AndroidManifest.xml b/extras/android/RemoteHCI/lib/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a5918e6
--- /dev/null
+++ b/extras/android/RemoteHCI/lib/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+</manifest>
\ No newline at end of file
diff --git a/extras/android/RemoteHCI/lib/src/main/java/android/internal/hidl/base/V1_0/DebugInfo.java b/extras/android/RemoteHCI/lib/src/main/java/android/internal/hidl/base/V1_0/DebugInfo.java
new file mode 100644
index 0000000..cceffde
--- /dev/null
+++ b/extras/android/RemoteHCI/lib/src/main/java/android/internal/hidl/base/V1_0/DebugInfo.java
@@ -0,0 +1,17 @@
+package android.internal.hidl.base.V1_0;
+
+import android.os.HwParcel;
+
+public class DebugInfo {
+    public static final class Architecture {
+        public static final int UNKNOWN = 0;
+    }
+
+    public int pid = 0;
+    public long ptr = 0L;
+    public int arch = 0;
+    public final void readFromParcel(HwParcel parcel) {
+    }
+    public final void writeToParcel(HwParcel parcel) {
+    }
+}
\ No newline at end of file
diff --git a/extras/android/RemoteHCI/lib/src/main/java/android/internal/hidl/base/V1_0/IBase.java b/extras/android/RemoteHCI/lib/src/main/java/android/internal/hidl/base/V1_0/IBase.java
new file mode 100644
index 0000000..eb03a26
--- /dev/null
+++ b/extras/android/RemoteHCI/lib/src/main/java/android/internal/hidl/base/V1_0/IBase.java
@@ -0,0 +1,20 @@
+package android.internal.hidl.base.V1_0;
+
+import android.os.IHwBinder;
+import android.os.IHwInterface;
+
+public interface IBase extends IHwInterface {
+    public static final String kInterfaceName = "android.hidl.base@1.0::IBase";
+
+    public static abstract class Stub extends android.os.HwBinder implements IBase {
+        public void onTransact(int _hidl_code, android.os.HwParcel _hidl_request, final android.os.HwParcel _hidl_reply, int _hidl_flags)
+                throws android.os.RemoteException {}
+    }
+
+    public static final class Proxy implements IBase {
+        @Override
+        public IHwBinder asBinder() {
+            return null;
+        }
+    }
+}
diff --git a/extras/android/RemoteHCI/lib/src/main/java/android/os/HidlSupport.java b/extras/android/RemoteHCI/lib/src/main/java/android/os/HidlSupport.java
new file mode 100644
index 0000000..c14e70f
--- /dev/null
+++ b/extras/android/RemoteHCI/lib/src/main/java/android/os/HidlSupport.java
@@ -0,0 +1,8 @@
+package android.os;
+
+public class HidlSupport {
+    public static boolean interfacesEqual(IHwInterface lft, Object rgt) {
+        return false; // STUB
+    }
+    public static native int getPidIfSharable();
+}
diff --git a/extras/android/RemoteHCI/lib/src/main/java/android/os/HwBinder.java b/extras/android/RemoteHCI/lib/src/main/java/android/os/HwBinder.java
new file mode 100644
index 0000000..101a02d
--- /dev/null
+++ b/extras/android/RemoteHCI/lib/src/main/java/android/os/HwBinder.java
@@ -0,0 +1,40 @@
+package android.os;
+
+public class HwBinder implements IHwBinder {
+    public native final void registerService(String serviceName);
+    public static final IHwBinder getService(
+            String iface,
+            String serviceName) {
+        return null; //STUB
+    }
+
+    public static final IHwBinder getService(
+            String iface,
+            String serviceName,
+            boolean retry) {
+        return null; // STUB
+    }
+
+    public static void enableInstrumentation() {
+
+    }
+    @Override
+    public IHwInterface queryLocalInterface(String descriptor) {
+        return null; // STUB
+    }
+
+    @Override
+    public void transact(int code, HwParcel request, HwParcel reply, int flags) {
+
+    }
+
+    @Override
+    public boolean linkToDeath(DeathRecipient recipient, long cookie) {
+        return false;
+    }
+
+    @Override
+    public boolean unlinkToDeath(DeathRecipient recipient) {
+        return false;
+    }
+}
diff --git a/extras/android/RemoteHCI/lib/src/main/java/android/os/HwBlob.java b/extras/android/RemoteHCI/lib/src/main/java/android/os/HwBlob.java
new file mode 100644
index 0000000..5639fb9
--- /dev/null
+++ b/extras/android/RemoteHCI/lib/src/main/java/android/os/HwBlob.java
@@ -0,0 +1,13 @@
+package android.os;
+
+public class HwBlob {
+    public HwBlob(int size) {}
+    public native final long handle();
+
+    public native final int getInt32(long offset);
+    public native final void putInt32(long offset, int x);
+    public native final void putBool(long offset, boolean x);
+    public native final void putInt8Array(long offset, byte[] x);
+    public native final void putBlob(long offset, HwBlob blob);
+    public native final void copyToInt8Array(long offset, byte[] array, int size);
+}
diff --git a/extras/android/RemoteHCI/lib/src/main/java/android/os/HwParcel.java b/extras/android/RemoteHCI/lib/src/main/java/android/os/HwParcel.java
new file mode 100644
index 0000000..eac0e69
--- /dev/null
+++ b/extras/android/RemoteHCI/lib/src/main/java/android/os/HwParcel.java
@@ -0,0 +1,107 @@
+package android.os;
+
+import java.util.ArrayList;
+
+public class HwParcel {
+    public static final int STATUS_SUCCESS = 0;
+    public native final void writeInterfaceToken(String interfaceName);
+    public native final void writeBool(boolean val);
+    public native final void writeInt8(byte val);
+    public native final void writeInt16(short val);
+    public native final void writeInt32(int val);
+    public native final void writeInt64(long val);
+    public native final void writeFloat(float val);
+    public native final void writeDouble(double val);
+    public native final void writeString(String val);
+    public native final void writeNativeHandle(NativeHandle val);
+    private native final void writeBoolVector(boolean[] val);
+    private native final void writeInt8Vector(byte[] val);
+    private native final void writeInt16Vector(short[] val);
+    private native final void writeInt32Vector(int[] val);
+    private native final void writeInt64Vector(long[] val);
+    private native final void writeFloatVector(float[] val);
+    private native final void writeDoubleVector(double[] val);
+    private native final void writeStringVector(String[] val);
+    private native final void writeNativeHandleVector(NativeHandle[] val);
+    public final void writeBoolVector(ArrayList<Boolean> val) {
+    }
+    public final void writeInt8Vector(ArrayList<Byte> val) {
+    }
+    public final void writeInt16Vector(ArrayList<Short> val) {
+    }
+    public final void writeInt32Vector(ArrayList<Integer> val) {
+    }
+    public final void writeInt64Vector(ArrayList<Long> val) {
+    }
+    public final void writeFloatVector(ArrayList<Float> val) {
+    }
+    public final void writeDoubleVector(ArrayList<Double> val) {
+    }
+    public final void writeStringVector(ArrayList<String> val) {
+    }
+    public final void writeNativeHandleVector(ArrayList<NativeHandle> val) {
+    }
+    public native final void writeStrongBinder(IHwBinder binder);
+    //public native final void writeHidlMemory(HidlMemory memory);
+    public native final void enforceInterface(String interfaceName);
+    public native final boolean readBool();
+    public native final byte readInt8();
+    public native final short readInt16();
+    public native final int readInt32();
+    public native final long readInt64();
+    public native final float readFloat();
+    public native final double readDouble();
+    public native final String readString();
+    public native final NativeHandle readNativeHandle();
+    public native final NativeHandle readEmbeddedNativeHandle(
+            long parentHandle, long offset);
+    private native final boolean[] readBoolVectorAsArray();
+    private native final byte[] readInt8VectorAsArray();
+    private native final short[] readInt16VectorAsArray();
+    private native final int[] readInt32VectorAsArray();
+    private native final long[] readInt64VectorAsArray();
+    private native final float[] readFloatVectorAsArray();
+    private native final double[] readDoubleVectorAsArray();
+    private native final String[] readStringVectorAsArray();
+    private native final NativeHandle[] readNativeHandleAsArray();
+    public final ArrayList<Boolean> readBoolVector() {
+        return null;
+    }
+    public final ArrayList<Byte> readInt8Vector() {
+        return null;
+    }
+    public final ArrayList<Short> readInt16Vector() {
+        return null;
+    }
+    public final ArrayList<Integer> readInt32Vector() {
+        return null;
+    }
+    public final ArrayList<Long> readInt64Vector() {
+        return null;
+    }
+    public final ArrayList<Float> readFloatVector() {
+        return null;
+    }
+    public final ArrayList<Double> readDoubleVector() {
+        return null;
+    }
+    public final ArrayList<String> readStringVector() {
+        return null;
+    }
+    public final ArrayList<NativeHandle> readNativeHandleVector() {
+        return null;
+    }
+    public native final IHwBinder readStrongBinder();
+//    public native final HidlMemory readHidlMemory();
+//    public native final
+//    HidlMemory readEmbeddedHidlMemory(long fieldHandle, long parentHandle, long offset);
+    public native final HwBlob readBuffer(long expectedSize);
+    public native final HwBlob readEmbeddedBuffer(
+            long expectedSize, long parentHandle, long offset,
+            boolean nullable);
+    public native final void writeBuffer(HwBlob blob);
+    public native final void writeStatus(int status);
+    public native final void verifySuccess();
+    public native final void releaseTemporaryStorage();
+    public native final void release();
+    public native final void send();}
diff --git a/extras/android/RemoteHCI/lib/src/main/java/android/os/IHwBinder.java b/extras/android/RemoteHCI/lib/src/main/java/android/os/IHwBinder.java
new file mode 100644
index 0000000..46e9515
--- /dev/null
+++ b/extras/android/RemoteHCI/lib/src/main/java/android/os/IHwBinder.java
@@ -0,0 +1,11 @@
+package android.os;
+
+public interface IHwBinder {
+    public interface DeathRecipient {
+        public void serviceDied(long cookie);
+    }
+    public IHwInterface queryLocalInterface(String descriptor);
+    public void transact(int code, HwParcel request, HwParcel reply, int flags);
+    public boolean linkToDeath(DeathRecipient recipient, long cookie);
+    public boolean unlinkToDeath(DeathRecipient recipient);
+}
diff --git a/extras/android/RemoteHCI/lib/src/main/java/android/os/IHwInterface.java b/extras/android/RemoteHCI/lib/src/main/java/android/os/IHwInterface.java
new file mode 100644
index 0000000..9a4de57
--- /dev/null
+++ b/extras/android/RemoteHCI/lib/src/main/java/android/os/IHwInterface.java
@@ -0,0 +1,5 @@
+package android.os;
+
+public interface IHwInterface {
+    public IHwBinder asBinder();
+}
diff --git a/extras/android/RemoteHCI/lib/src/main/java/android/os/NativeHandle.java b/extras/android/RemoteHCI/lib/src/main/java/android/os/NativeHandle.java
new file mode 100644
index 0000000..b6ec8c7
--- /dev/null
+++ b/extras/android/RemoteHCI/lib/src/main/java/android/os/NativeHandle.java
@@ -0,0 +1,4 @@
+package android.os;
+
+public class NativeHandle {
+}
diff --git a/extras/android/RemoteHCI/lib/src/main/java/android/os/ServiceManager.java b/extras/android/RemoteHCI/lib/src/main/java/android/os/ServiceManager.java
new file mode 100644
index 0000000..5db2047
--- /dev/null
+++ b/extras/android/RemoteHCI/lib/src/main/java/android/os/ServiceManager.java
@@ -0,0 +1,7 @@
+package android.os;
+
+public final class ServiceManager {
+    public static IBinder getService(String name) {
+        return null; // Stub
+    }
+}
\ No newline at end of file
diff --git a/extras/android/RemoteHCI/scripts/generate_java_from_aidl.sh b/extras/android/RemoteHCI/scripts/generate_java_from_aidl.sh
new file mode 100644
index 0000000..1f39821
--- /dev/null
+++ b/extras/android/RemoteHCI/scripts/generate_java_from_aidl.sh
@@ -0,0 +1,32 @@
+# Run this script to generate the .java files from the .aidl files
+# then replace `this.markVintfStability()` with:
+#   try {
+#     Method method = this.getClass().getMethod("markVintfStability", null);
+#     method.invoke(this);
+#   } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
+#     throw new RuntimeException(e);
+#   }
+
+AIDL=$ANDROID_SDK_ROOT/build-tools/34.0.0/aidl
+
+$AIDL \
+-oapp/src/main/java \
+-Iapp/src/main/aidl \
+--stability=vintf \
+--structured \
+app/src/main/aidl/android/hardware/bluetooth/IBluetoothHci.aidl
+
+$AIDL \
+-oapp/src/main/java \
+-Iapp/src/main/aidl \
+--stability=vintf \
+--structured \
+app/src/main/aidl/android/hardware/bluetooth/IBluetoothHciCallbacks.aidl
+
+$AIDL \
+-oapp/src/main/java \
+-Iapp/src/main/aidl \
+--stability=vintf \
+--structured \
+app/src/main/aidl/android/hardware/bluetooth/Status.aidl
+
diff --git a/extras/android/RemoteHCI/settings.gradle.kts b/extras/android/RemoteHCI/settings.gradle.kts
new file mode 100644
index 0000000..0edc3ec
--- /dev/null
+++ b/extras/android/RemoteHCI/settings.gradle.kts
@@ -0,0 +1,18 @@
+pluginManagement {
+    repositories {
+        google()
+        mavenCentral()
+        gradlePluginPortal()
+    }
+}
+dependencyResolutionManagement {
+    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+    repositories {
+        google()
+        mavenCentral()
+    }
+}
+
+rootProject.name = "RemoteHCI"
+include(":app")
+include(":lib")
diff --git a/rust/Cargo.lock b/rust/Cargo.lock
index c2d0cd3..c443561 100644
--- a/rust/Cargo.lock
+++ b/rust/Cargo.lock
@@ -81,6 +81,37 @@
 checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
 
 [[package]]
+name = "argh"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7af5ba06967ff7214ce4c7419c7d185be7ecd6cc4965a8f6e1d8ce0398aad219"
+dependencies = [
+ "argh_derive",
+ "argh_shared",
+]
+
+[[package]]
+name = "argh_derive"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56df0aeedf6b7a2fc67d06db35b09684c3e8da0c95f8f27685cb17e08413d87a"
+dependencies = [
+ "argh_shared",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.29",
+]
+
+[[package]]
+name = "argh_shared"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5693f39141bda5760ecc4111ab08da40565d1771038c4a0250f03457ec707531"
+dependencies = [
+ "serde",
+]
+
+[[package]]
 name = "atty"
 version = "0.2.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -131,6 +162,15 @@
 checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
 
 [[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
 name = "bstr"
 version = "1.6.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -145,6 +185,7 @@
 version = "0.1.0"
 dependencies = [
  "anyhow",
+ "bytes",
  "clap 4.4.1",
  "directories",
  "env_logger",
@@ -158,6 +199,8 @@
  "nix",
  "nom",
  "owo-colors",
+ "pdl-derive",
+ "pdl-runtime",
  "pyo3",
  "pyo3-asyncio",
  "rand",
@@ -178,9 +221,9 @@
 
 [[package]]
 name = "bytes"
-version = "1.4.0"
+version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
+checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
 
 [[package]]
 name = "cc"
@@ -263,6 +306,16 @@
 checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961"
 
 [[package]]
+name = "codespan-reporting"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
+dependencies = [
+ "termcolor",
+ "unicode-width",
+]
+
+[[package]]
 name = "colorchoice"
 version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -285,6 +338,15 @@
 checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
 
 [[package]]
+name = "cpufeatures"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1"
+dependencies = [
+ "libc",
+]
+
+[[package]]
 name = "crossbeam"
 version = "0.8.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -352,6 +414,26 @@
 ]
 
 [[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+]
+
+[[package]]
 name = "directories"
 version = "5.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -560,6 +642,16 @@
 ]
 
 [[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
 name = "getrandom"
 version = "0.2.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1065,12 +1157,100 @@
 ]
 
 [[package]]
+name = "pdl-compiler"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee66995739fb9ddd9155767990a54aadd226ee32408a94f99f94883ff445ceba"
+dependencies = [
+ "argh",
+ "codespan-reporting",
+ "heck",
+ "pest",
+ "pest_derive",
+ "prettyplease",
+ "proc-macro2",
+ "quote",
+ "serde",
+ "serde_json",
+ "syn 2.0.29",
+]
+
+[[package]]
+name = "pdl-derive"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "113e4a1215c407466b36d2c2f6a6318819d6b22ccdd3acb7bb35e27a68806034"
+dependencies = [
+ "codespan-reporting",
+ "pdl-compiler",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.29",
+ "termcolor",
+]
+
+[[package]]
+name = "pdl-runtime"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d36c2f9799613babe78eb5cd9a353d527daaba6c3d1f39a1175657a35790732"
+dependencies = [
+ "bytes",
+ "thiserror",
+]
+
+[[package]]
 name = "percent-encoding"
 version = "2.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
 
 [[package]]
+name = "pest"
+version = "2.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c022f1e7b65d6a24c0dbbd5fb344c66881bc01f3e5ae74a1c8100f2f985d98a4"
+dependencies = [
+ "memchr",
+ "thiserror",
+ "ucd-trie",
+]
+
+[[package]]
+name = "pest_derive"
+version = "2.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35513f630d46400a977c4cb58f78e1bfbe01434316e60c37d27b9ad6139c66d8"
+dependencies = [
+ "pest",
+ "pest_generator",
+]
+
+[[package]]
+name = "pest_generator"
+version = "2.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc9fc1b9e7057baba189b5c626e2d6f40681ae5b6eb064dc7c7834101ec8123a"
+dependencies = [
+ "pest",
+ "pest_meta",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.29",
+]
+
+[[package]]
+name = "pest_meta"
+version = "2.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1df74e9e7ec4053ceb980e7c0c8bd3594e977fde1af91daba9c928e8e8c6708d"
+dependencies = [
+ "once_cell",
+ "pest",
+ "sha2",
+]
+
+[[package]]
 name = "pin-project-lite"
 version = "0.2.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1095,6 +1275,16 @@
 checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
 
 [[package]]
+name = "prettyplease"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c64d9ba0963cdcea2e1b2230fbae2bab30eb25a174be395c41e764bfb65dd62"
+dependencies = [
+ "proc-macro2",
+ "syn 2.0.29",
+]
+
+[[package]]
 name = "proc-macro2"
 version = "1.0.66"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1466,6 +1656,17 @@
 ]
 
 [[package]]
+name = "sha2"
+version = "0.10.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
 name = "signal-hook-registry"
 version = "1.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1712,6 +1913,18 @@
 checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
 
 [[package]]
+name = "typenum"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
+
+[[package]]
+name = "ucd-trie"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9"
+
+[[package]]
 name = "unicode-bidi"
 version = "0.3.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1739,6 +1952,12 @@
 checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
 
 [[package]]
+name = "unicode-width"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
+
+[[package]]
 name = "unindent"
 version = "0.1.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1768,6 +1987,12 @@
 checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
 
 [[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
 name = "walkdir"
 version = "2.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
index a553afd..35a0f4c 100644
--- a/rust/Cargo.toml
+++ b/rust/Cargo.toml
@@ -23,6 +23,9 @@
 itertools = "0.11.0"
 lazy_static = "1.4.0"
 thiserror = "1.0.41"
+bytes = "1.5.0"
+pdl-derive = "0.2.0"
+pdl-runtime = "0.2.0"
 
 # Dev tools
 file-header = { version = "0.1.2", optional = true }
diff --git a/rust/examples/scanner.rs b/rust/examples/scanner.rs
index 1b68ea5..3c328ed 100644
--- a/rust/examples/scanner.rs
+++ b/rust/examples/scanner.rs
@@ -20,7 +20,8 @@
 use bumble::{
     adv::CommonDataType,
     wrapper::{
-        core::AdvertisementDataUnit, device::Device, hci::AddressType, transport::Transport,
+        core::AdvertisementDataUnit, device::Device, hci::packets::AddressType,
+        transport::Transport,
     },
 };
 use clap::Parser as _;
@@ -69,9 +70,7 @@
             let mut seen_adv_cache = seen_adv_clone.lock().unwrap();
             let expiry_duration = time::Duration::from_secs(cli.dedup_expiry_secs);
 
-            let advs_from_addr = seen_adv_cache
-                .entry(addr_bytes)
-                .or_insert_with(collections::HashMap::new);
+            let advs_from_addr = seen_adv_cache.entry(addr_bytes).or_default();
             // we expect cache hits to be the norm, so we do a separate lookup to avoid cloning
             // on every lookup with entry()
             let show = if let Some(prev) = advs_from_addr.get_mut(&data_units) {
@@ -102,7 +101,9 @@
         };
 
         let (type_style, qualifier) = match adv.address()?.address_type()? {
-            AddressType::PublicIdentity | AddressType::PublicDevice => (Style::new().cyan(), ""),
+            AddressType::PublicIdentityAddress | AddressType::PublicDeviceAddress => {
+                (Style::new().cyan(), "")
+            }
             _ => {
                 if addr.is_static()? {
                     (Style::new().green(), "(static)")
diff --git a/rust/pytests/wrapper.rs b/rust/pytests/wrapper.rs
index 8f69dd7..9fd65e7 100644
--- a/rust/pytests/wrapper.rs
+++ b/rust/pytests/wrapper.rs
@@ -12,9 +12,26 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use bumble::wrapper::{drivers::rtk::DriverInfo, transport::Transport};
+use bumble::wrapper::{
+    controller::Controller,
+    device::Device,
+    drivers::rtk::DriverInfo,
+    hci::{
+        packets::{
+            AddressType, ErrorCode, ReadLocalVersionInformationBuilder,
+            ReadLocalVersionInformationComplete,
+        },
+        Address, Error,
+    },
+    host::Host,
+    link::Link,
+    transport::Transport,
+};
 use nix::sys::stat::Mode;
-use pyo3::PyResult;
+use pyo3::{
+    exceptions::PyException,
+    {PyErr, PyResult},
+};
 
 #[pyo3_asyncio::tokio::test]
 async fn fifo_transport_can_open() -> PyResult<()> {
@@ -35,3 +52,26 @@
     assert_eq!(12, DriverInfo::all_drivers()?.len());
     Ok(())
 }
+
+#[pyo3_asyncio::tokio::test]
+async fn hci_command_wrapper_has_correct_methods() -> PyResult<()> {
+    let address = Address::new("F0:F1:F2:F3:F4:F5", &AddressType::RandomDeviceAddress)?;
+    let link = Link::new_local_link()?;
+    let controller = Controller::new("C1", None, None, Some(link), Some(address.clone())).await?;
+    let host = Host::new(controller.clone().into(), controller.into()).await?;
+    let device = Device::new(None, Some(address), None, Some(host), None)?;
+
+    device.power_on().await?;
+
+    // Send some simple command. A successful response means [HciCommandWrapper] has the minimum
+    // required interface for the Python code to think its an [HCI_Command] object.
+    let command = ReadLocalVersionInformationBuilder {};
+    let event: ReadLocalVersionInformationComplete = device
+        .send_command(&command.into(), true)
+        .await?
+        .try_into()
+        .map_err(|e: Error| PyErr::new::<PyException, _>(e.to_string()))?;
+
+    assert_eq!(ErrorCode::Success, event.get_status());
+    Ok(())
+}
diff --git a/rust/src/cli/firmware/rtk.rs b/rust/src/cli/firmware/rtk.rs
index f5524a4..1028564 100644
--- a/rust/src/cli/firmware/rtk.rs
+++ b/rust/src/cli/firmware/rtk.rs
@@ -179,7 +179,7 @@
 pub(crate) async fn info(transport: &str, force: bool) -> PyResult<()> {
     let transport = Transport::open(transport).await?;
 
-    let mut host = Host::new(transport.source()?, transport.sink()?)?;
+    let mut host = Host::new(transport.source()?, transport.sink()?).await?;
     host.reset(DriverFactory::None).await?;
 
     if !force && !Driver::check(&host).await? {
@@ -203,7 +203,7 @@
 pub(crate) async fn load(transport: &str, force: bool) -> PyResult<()> {
     let transport = Transport::open(transport).await?;
 
-    let mut host = Host::new(transport.source()?, transport.sink()?)?;
+    let mut host = Host::new(transport.source()?, transport.sink()?).await?;
     host.reset(DriverFactory::None).await?;
 
     match Driver::for_host(&host, force).await? {
@@ -219,7 +219,7 @@
 pub(crate) async fn drop(transport: &str) -> PyResult<()> {
     let transport = Transport::open(transport).await?;
 
-    let mut host = Host::new(transport.source()?, transport.sink()?)?;
+    let mut host = Host::new(transport.source()?, transport.sink()?).await?;
     host.reset(DriverFactory::None).await?;
 
     Driver::drop_firmware(&mut host).await?;
diff --git a/rust/src/cli/l2cap/client_bridge.rs b/rust/src/cli/l2cap/client_bridge.rs
index 37606fc..31bc021 100644
--- a/rust/src/cli/l2cap/client_bridge.rs
+++ b/rust/src/cli/l2cap/client_bridge.rs
@@ -21,8 +21,7 @@
 /// TCP client to connect.
 /// When the L2CAP CoC channel is closed, the TCP connection is closed as well.
 use crate::cli::l2cap::{
-    proxy_l2cap_rx_to_tcp_tx, proxy_tcp_rx_to_l2cap_tx, run_future_with_current_task_locals,
-    BridgeData,
+    inject_py_event_loop, proxy_l2cap_rx_to_tcp_tx, proxy_tcp_rx_to_l2cap_tx, BridgeData,
 };
 use bumble::wrapper::{
     device::{Connection, Device},
@@ -85,11 +84,12 @@
     let mtu = args.mtu;
     let mps = args.mps;
     let ble_connection = Arc::new(Mutex::new(ble_connection));
-    // Ensure Python event loop is available to l2cap `disconnect`
-    let _ = run_future_with_current_task_locals(async move {
+    // spawn thread to handle incoming tcp connections
+    tokio::spawn(inject_py_event_loop(async move {
         while let Ok((tcp_stream, addr)) = listener.accept().await {
             let ble_connection = ble_connection.clone();
-            let _ = run_future_with_current_task_locals(proxy_data_between_tcp_and_l2cap(
+            // spawn thread to handle this specific tcp connection
+            if let Ok(future) = inject_py_event_loop(proxy_data_between_tcp_and_l2cap(
                 ble_connection,
                 tcp_stream,
                 addr,
@@ -97,10 +97,11 @@
                 max_credits,
                 mtu,
                 mps,
-            ));
+            )) {
+                tokio::spawn(future);
+            }
         }
-        Ok(())
-    });
+    })?);
     Ok(())
 }
 
diff --git a/rust/src/cli/l2cap/mod.rs b/rust/src/cli/l2cap/mod.rs
index 31097ed..3f86b24 100644
--- a/rust/src/cli/l2cap/mod.rs
+++ b/rust/src/cli/l2cap/mod.rs
@@ -18,7 +18,7 @@
 use anyhow::anyhow;
 use bumble::wrapper::{device::Device, l2cap::LeConnectionOrientedChannel, transport::Transport};
 use owo_colors::{colors::css::Orange, OwoColorize};
-use pyo3::{PyObject, PyResult, Python};
+use pyo3::{PyResult, Python};
 use std::{future::Future, path::PathBuf, sync::Arc};
 use tokio::{
     io::{AsyncReadExt, AsyncWriteExt},
@@ -170,21 +170,12 @@
     }
 }
 
-/// Copies the current thread's TaskLocals into a Python "awaitable" and encapsulates it in a Rust
-/// future, running it as a Python Task.
-/// `TaskLocals` stores the current event loop, and allows the user to copy the current Python
-/// context if necessary. In this case, the python event loop is used when calling `disconnect` on
-/// an l2cap connection, or else the call will fail.
-pub fn run_future_with_current_task_locals<F>(
-    fut: F,
-) -> PyResult<impl Future<Output = PyResult<PyObject>> + Send>
+/// Copies the current thread's Python even loop (contained in `TaskLocals`) into the given future.
+/// Useful when sending work to another thread that calls Python code which calls `get_running_loop()`.
+pub fn inject_py_event_loop<F, R>(fut: F) -> PyResult<impl Future<Output = R>>
 where
-    F: Future<Output = PyResult<()>> + Send + 'static,
+    F: Future<Output = R> + Send + 'static,
 {
-    Python::with_gil(|py| {
-        let locals = pyo3_asyncio::tokio::get_current_locals(py)?;
-        let future = pyo3_asyncio::tokio::scope(locals.clone(), fut);
-        pyo3_asyncio::tokio::future_into_py_with_locals(py, locals, future)
-            .and_then(pyo3_asyncio::tokio::into_future)
-    })
+    let locals = Python::with_gil(pyo3_asyncio::tokio::get_current_locals)?;
+    Ok(pyo3_asyncio::tokio::scope(locals, fut))
 }
diff --git a/rust/src/cli/l2cap/server_bridge.rs b/rust/src/cli/l2cap/server_bridge.rs
index 3a32db9..3f8041a 100644
--- a/rust/src/cli/l2cap/server_bridge.rs
+++ b/rust/src/cli/l2cap/server_bridge.rs
@@ -19,10 +19,7 @@
 /// When the L2CAP CoC channel is closed, the bridge disconnects the TCP socket
 /// and waits for a new L2CAP CoC channel to be connected.
 /// When the TCP connection is closed by the TCP server, the L2CAP connection is closed as well.
-use crate::cli::l2cap::{
-    proxy_l2cap_rx_to_tcp_tx, proxy_tcp_rx_to_l2cap_tx, run_future_with_current_task_locals,
-    BridgeData,
-};
+use crate::cli::l2cap::{proxy_l2cap_rx_to_tcp_tx, proxy_tcp_rx_to_l2cap_tx, BridgeData};
 use bumble::wrapper::{device::Device, hci::HciConstant, l2cap::LeConnectionOrientedChannel};
 use futures::executor::block_on;
 use owo_colors::OwoColorize;
@@ -49,19 +46,19 @@
     let port = args.tcp_port;
     device.register_l2cap_channel_server(
         args.psm,
-        move |_py, l2cap_channel| {
+        move |py, l2cap_channel| {
             let channel_info = l2cap_channel
                 .debug_string()
                 .unwrap_or_else(|e| format!("failed to get l2cap channel info ({e})"));
             println!("{} {channel_info}", "*** L2CAP channel:".cyan());
 
             let host = host.clone();
-            // Ensure Python event loop is available to l2cap `disconnect`
-            let _ = run_future_with_current_task_locals(proxy_data_between_l2cap_and_tcp(
-                l2cap_channel,
-                host,
-                port,
-            ));
+            // Handles setting up a tokio runtime that runs this future to completion while also
+            // containing the necessary context vars.
+            pyo3_asyncio::tokio::future_into_py(
+                py,
+                proxy_data_between_l2cap_and_tcp(l2cap_channel, host, port),
+            )?;
             Ok(())
         },
         args.max_credits,
diff --git a/rust/src/cli/usb/mod.rs b/rust/src/cli/usb/mod.rs
index 7adbd75..5cab29a 100644
--- a/rust/src/cli/usb/mod.rs
+++ b/rust/src/cli/usb/mod.rs
@@ -143,10 +143,7 @@
         );
         if let Some(s) = serial {
             println!("{:26}{}", "  Serial:".green(), s);
-            device_serials_by_id
-                .entry(device_id)
-                .or_insert(HashSet::new())
-                .insert(s);
+            device_serials_by_id.entry(device_id).or_default().insert(s);
         }
         if let Some(m) = mfg {
             println!("{:26}{}", "  Manufacturer:".green(), m);
@@ -314,7 +311,7 @@
             self.protocol,
             self.protocol_name()
                 .map(|s| format!(" [{}]", s))
-                .unwrap_or_else(String::new)
+                .unwrap_or_default()
         )
     }
 }
diff --git a/rust/src/internal/hci/mod.rs b/rust/src/internal/hci/mod.rs
new file mode 100644
index 0000000..232c49f
--- /dev/null
+++ b/rust/src/internal/hci/mod.rs
@@ -0,0 +1,161 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+pub use pdl_runtime::{Error, Packet};
+
+use crate::internal::hci::packets::{Acl, Command, Event, Sco};
+use pdl_derive::pdl;
+
+#[allow(missing_docs, warnings, clippy::all)]
+#[pdl("src/internal/hci/packets.pdl")]
+pub mod packets {}
+#[cfg(test)]
+mod tests;
+
+/// HCI Packet type, prepended to the packet.
+/// Rootcanal's PDL declaration excludes this from ser/deser and instead is implemented in code.
+/// To maintain the ability to easily use future versions of their packet PDL, packet type is
+/// implemented here.
+#[derive(Debug, PartialEq)]
+pub(crate) enum PacketType {
+    Command = 0x01,
+    Acl = 0x02,
+    Sco = 0x03,
+    Event = 0x04,
+}
+
+impl TryFrom<u8> for PacketType {
+    type Error = PacketTypeParseError;
+
+    fn try_from(value: u8) -> Result<Self, Self::Error> {
+        match value {
+            0x01 => Ok(PacketType::Command),
+            0x02 => Ok(PacketType::Acl),
+            0x03 => Ok(PacketType::Sco),
+            0x04 => Ok(PacketType::Event),
+            _ => Err(PacketTypeParseError::InvalidPacketType { value }),
+        }
+    }
+}
+
+impl From<PacketType> for u8 {
+    fn from(packet_type: PacketType) -> Self {
+        match packet_type {
+            PacketType::Command => 0x01,
+            PacketType::Acl => 0x02,
+            PacketType::Sco => 0x03,
+            PacketType::Event => 0x04,
+        }
+    }
+}
+
+/// Allows for smoother interoperability between a [Packet] and a bytes representation of it that
+/// includes its type as a header
+pub(crate) trait WithPacketType<T: Packet> {
+    /// Converts the [Packet] into bytes, prefixed with its type
+    fn to_vec_with_packet_type(self) -> Vec<u8>;
+
+    /// Parses a [Packet] out of bytes that are prefixed with the packet's type
+    fn parse_with_packet_type(bytes: &[u8]) -> Result<T, PacketTypeParseError>;
+}
+
+/// Errors that may arise when parsing a packet that is prefixed with its type
+#[derive(Debug, PartialEq, thiserror::Error)]
+pub(crate) enum PacketTypeParseError {
+    #[error("The slice being parsed was empty")]
+    EmptySlice,
+    #[error("Packet type ({value:#X}) is invalid")]
+    InvalidPacketType { value: u8 },
+    #[error("Expected packet type: {expected:?}, but got: {actual:?}")]
+    PacketTypeMismatch {
+        expected: PacketType,
+        actual: PacketType,
+    },
+    #[error("Failed to parse packet after header: {error}")]
+    PacketParse { error: Error },
+}
+
+impl From<Error> for PacketTypeParseError {
+    fn from(error: Error) -> Self {
+        Self::PacketParse { error }
+    }
+}
+
+impl WithPacketType<Self> for Command {
+    fn to_vec_with_packet_type(self) -> Vec<u8> {
+        prepend_packet_type(PacketType::Command, self.to_vec())
+    }
+
+    fn parse_with_packet_type(bytes: &[u8]) -> Result<Self, PacketTypeParseError> {
+        parse_with_expected_packet_type(Command::parse, PacketType::Command, bytes)
+    }
+}
+
+impl WithPacketType<Self> for Acl {
+    fn to_vec_with_packet_type(self) -> Vec<u8> {
+        prepend_packet_type(PacketType::Acl, self.to_vec())
+    }
+
+    fn parse_with_packet_type(bytes: &[u8]) -> Result<Self, PacketTypeParseError> {
+        parse_with_expected_packet_type(Acl::parse, PacketType::Acl, bytes)
+    }
+}
+
+impl WithPacketType<Self> for Sco {
+    fn to_vec_with_packet_type(self) -> Vec<u8> {
+        prepend_packet_type(PacketType::Sco, self.to_vec())
+    }
+
+    fn parse_with_packet_type(bytes: &[u8]) -> Result<Self, PacketTypeParseError> {
+        parse_with_expected_packet_type(Sco::parse, PacketType::Sco, bytes)
+    }
+}
+
+impl WithPacketType<Self> for Event {
+    fn to_vec_with_packet_type(self) -> Vec<u8> {
+        prepend_packet_type(PacketType::Event, self.to_vec())
+    }
+
+    fn parse_with_packet_type(bytes: &[u8]) -> Result<Self, PacketTypeParseError> {
+        parse_with_expected_packet_type(Event::parse, PacketType::Event, bytes)
+    }
+}
+
+fn prepend_packet_type(packet_type: PacketType, mut packet_bytes: Vec<u8>) -> Vec<u8> {
+    packet_bytes.insert(0, packet_type.into());
+    packet_bytes
+}
+
+fn parse_with_expected_packet_type<T: Packet, F, E>(
+    parser: F,
+    expected_packet_type: PacketType,
+    bytes: &[u8],
+) -> Result<T, PacketTypeParseError>
+where
+    F: Fn(&[u8]) -> Result<T, E>,
+    PacketTypeParseError: From<E>,
+{
+    let (first_byte, packet_bytes) = bytes
+        .split_first()
+        .ok_or(PacketTypeParseError::EmptySlice)?;
+    let actual_packet_type = PacketType::try_from(*first_byte)?;
+    if actual_packet_type == expected_packet_type {
+        Ok(parser(packet_bytes)?)
+    } else {
+        Err(PacketTypeParseError::PacketTypeMismatch {
+            expected: expected_packet_type,
+            actual: actual_packet_type,
+        })
+    }
+}
diff --git a/rust/src/internal/hci/packets.pdl b/rust/src/internal/hci/packets.pdl
new file mode 100644
index 0000000..694d37c
--- /dev/null
+++ b/rust/src/internal/hci/packets.pdl
@@ -0,0 +1,6253 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// Copied from:
+// https://github.com/google/rootcanal/blob/2b57c6bb3598babd99b07126e093fb6811637a47/packets/hci_packets.pdl
+little_endian_packets
+
+custom_field Address : 48 "hci/"
+
+enum Enable : 8 {
+  DISABLED = 0x00,
+  ENABLED = 0x01,
+}
+
+// HCI ACL Packets
+
+enum PacketBoundaryFlag : 2 {
+  FIRST_NON_AUTOMATICALLY_FLUSHABLE = 0,
+  CONTINUING_FRAGMENT = 1,
+  FIRST_AUTOMATICALLY_FLUSHABLE = 2,
+}
+
+enum BroadcastFlag : 2 {
+  POINT_TO_POINT = 0,
+  ACTIVE_PERIPHERAL_BROADCAST = 1,
+}
+
+packet Acl {
+  handle : 12,
+  packet_boundary_flag : PacketBoundaryFlag,
+  broadcast_flag : BroadcastFlag,
+  _size_(_payload_) : 16,
+  _payload_,
+}
+
+// HCI SCO Packets
+
+enum PacketStatusFlag : 2 {
+  CORRECTLY_RECEIVED = 0,
+  POSSIBLY_INCOMPLETE = 1,
+  NO_DATA_RECEIVED = 2,
+  PARTIALLY_LOST = 3,
+}
+
+packet Sco {
+  handle : 12,
+  packet_status_flag : PacketStatusFlag,
+  _reserved_ : 2, // BroadcastFlag
+  _size_(data) : 8,
+  data : 8[],
+}
+
+// HCI Command Packets
+
+enum OpCode : 16 {
+  NONE = 0x0000,
+
+  // LINK_CONTROL
+  INQUIRY = 0x0401,
+  INQUIRY_CANCEL = 0x0402,
+  PERIODIC_INQUIRY_MODE = 0x0403,
+  EXIT_PERIODIC_INQUIRY_MODE = 0x0404,
+  CREATE_CONNECTION = 0x0405,
+  DISCONNECT = 0x0406,
+  ADD_SCO_CONNECTION = 0x0407,
+  CREATE_CONNECTION_CANCEL = 0x0408,
+  ACCEPT_CONNECTION_REQUEST = 0x0409,
+  REJECT_CONNECTION_REQUEST = 0x040A,
+  LINK_KEY_REQUEST_REPLY = 0x040B,
+  LINK_KEY_REQUEST_NEGATIVE_REPLY = 0x040C,
+  PIN_CODE_REQUEST_REPLY = 0x040D,
+  PIN_CODE_REQUEST_NEGATIVE_REPLY = 0x040E,
+  CHANGE_CONNECTION_PACKET_TYPE = 0x040F,
+  AUTHENTICATION_REQUESTED = 0x0411,
+  SET_CONNECTION_ENCRYPTION = 0x0413,
+  CHANGE_CONNECTION_LINK_KEY = 0x0415,
+  CENTRAL_LINK_KEY = 0x0417,
+  REMOTE_NAME_REQUEST = 0x0419,
+  REMOTE_NAME_REQUEST_CANCEL = 0x041A,
+  READ_REMOTE_SUPPORTED_FEATURES = 0x041B,
+  READ_REMOTE_EXTENDED_FEATURES = 0x041C,
+  READ_REMOTE_VERSION_INFORMATION = 0x041D,
+  READ_CLOCK_OFFSET = 0x041F,
+  READ_LMP_HANDLE = 0x0420,
+  SETUP_SYNCHRONOUS_CONNECTION = 0x0428,
+  ACCEPT_SYNCHRONOUS_CONNECTION = 0x0429,
+  REJECT_SYNCHRONOUS_CONNECTION = 0x042A,
+  IO_CAPABILITY_REQUEST_REPLY = 0x042B,
+  USER_CONFIRMATION_REQUEST_REPLY = 0x042C,
+  USER_CONFIRMATION_REQUEST_NEGATIVE_REPLY = 0x042D,
+  USER_PASSKEY_REQUEST_REPLY = 0x042E,
+  USER_PASSKEY_REQUEST_NEGATIVE_REPLY = 0x042F,
+  REMOTE_OOB_DATA_REQUEST_REPLY = 0x0430,
+  REMOTE_OOB_DATA_REQUEST_NEGATIVE_REPLY = 0x0433,
+  IO_CAPABILITY_REQUEST_NEGATIVE_REPLY = 0x0434,
+  ENHANCED_SETUP_SYNCHRONOUS_CONNECTION = 0x043D,
+  ENHANCED_ACCEPT_SYNCHRONOUS_CONNECTION = 0x043E,
+  TRUNCATED_PAGE = 0x043F,
+  TRUNCATED_PAGE_CANCEL = 0x0440,
+  SET_CONNECTIONLESS_PERIPHERAL_BROADCAST = 0x0441,
+  SET_CONNECTIONLESS_PERIPHERAL_BROADCAST_RECEIVE = 0x0442,
+  START_SYNCHRONIZATION_TRAIN = 0x0443,
+  RECEIVE_SYNCHRONIZATION_TRAIN = 0x0444,
+  REMOTE_OOB_EXTENDED_DATA_REQUEST_REPLY = 0x0445,
+
+  // LINK_POLICY
+  HOLD_MODE = 0x0801,
+  SNIFF_MODE = 0x0803,
+  EXIT_SNIFF_MODE = 0x0804,
+  QOS_SETUP = 0x0807,
+  ROLE_DISCOVERY = 0x0809,
+  SWITCH_ROLE = 0x080B,
+  READ_LINK_POLICY_SETTINGS = 0x080C,
+  WRITE_LINK_POLICY_SETTINGS = 0x080D,
+  READ_DEFAULT_LINK_POLICY_SETTINGS = 0x080E,
+  WRITE_DEFAULT_LINK_POLICY_SETTINGS = 0x080F,
+  FLOW_SPECIFICATION = 0x0810,
+  SNIFF_SUBRATING = 0x0811,
+
+  // CONTROLLER_AND_BASEBAND
+  SET_EVENT_MASK = 0x0C01,
+  RESET = 0x0C03,
+  SET_EVENT_FILTER = 0x0C05,
+  FLUSH = 0x0C08,
+  READ_PIN_TYPE = 0x0C09,
+  WRITE_PIN_TYPE = 0x0C0A,
+  READ_STORED_LINK_KEY = 0x0C0D,
+  WRITE_STORED_LINK_KEY = 0x0C11,
+  DELETE_STORED_LINK_KEY = 0x0C12,
+  WRITE_LOCAL_NAME = 0x0C13,
+  READ_LOCAL_NAME = 0x0C14,
+  READ_CONNECTION_ACCEPT_TIMEOUT = 0x0C15,
+  WRITE_CONNECTION_ACCEPT_TIMEOUT = 0x0C16,
+  READ_PAGE_TIMEOUT = 0x0C17,
+  WRITE_PAGE_TIMEOUT = 0x0C18,
+  READ_SCAN_ENABLE = 0x0C19,
+  WRITE_SCAN_ENABLE = 0x0C1A,
+  READ_PAGE_SCAN_ACTIVITY = 0x0C1B,
+  WRITE_PAGE_SCAN_ACTIVITY = 0x0C1C,
+  READ_INQUIRY_SCAN_ACTIVITY = 0x0C1D,
+  WRITE_INQUIRY_SCAN_ACTIVITY = 0x0C1E,
+  READ_AUTHENTICATION_ENABLE = 0x0C1F,
+  WRITE_AUTHENTICATION_ENABLE = 0x0C20,
+  READ_CLASS_OF_DEVICE = 0x0C23,
+  WRITE_CLASS_OF_DEVICE = 0x0C24,
+  READ_VOICE_SETTING = 0x0C25,
+  WRITE_VOICE_SETTING = 0x0C26,
+  READ_AUTOMATIC_FLUSH_TIMEOUT = 0x0C27,
+  WRITE_AUTOMATIC_FLUSH_TIMEOUT = 0x0C28,
+  READ_NUM_BROADCAST_RETRANSMITS = 0x0C29,
+  WRITE_NUM_BROADCAST_RETRANSMITS = 0x0C2A,
+  READ_HOLD_MODE_ACTIVITY = 0x0C2B,
+  WRITE_HOLD_MODE_ACTIVITY = 0x0C2C,
+  READ_TRANSMIT_POWER_LEVEL = 0x0C2D,
+  READ_SYNCHRONOUS_FLOW_CONTROL_ENABLE = 0x0C2E,
+  WRITE_SYNCHRONOUS_FLOW_CONTROL_ENABLE = 0x0C2F,
+  SET_CONTROLLER_TO_HOST_FLOW_CONTROL = 0x0C31,
+  HOST_BUFFER_SIZE = 0x0C33,
+  HOST_NUMBER_OF_COMPLETED_PACKETS = 0x0C35,
+  READ_LINK_SUPERVISION_TIMEOUT = 0x0C36,
+  WRITE_LINK_SUPERVISION_TIMEOUT = 0x0C37,
+  READ_NUMBER_OF_SUPPORTED_IAC = 0x0C38,
+  READ_CURRENT_IAC_LAP = 0x0C39,
+  WRITE_CURRENT_IAC_LAP = 0x0C3A,
+  SET_AFH_HOST_CHANNEL_CLASSIFICATION = 0x0C3F,
+  READ_INQUIRY_SCAN_TYPE = 0x0C42,
+  WRITE_INQUIRY_SCAN_TYPE = 0x0C43,
+  READ_INQUIRY_MODE = 0x0C44,
+  WRITE_INQUIRY_MODE = 0x0C45,
+  READ_PAGE_SCAN_TYPE = 0x0C46,
+  WRITE_PAGE_SCAN_TYPE = 0x0C47,
+  READ_AFH_CHANNEL_ASSESSMENT_MODE = 0x0C48,
+  WRITE_AFH_CHANNEL_ASSESSMENT_MODE = 0x0C49,
+  READ_EXTENDED_INQUIRY_RESPONSE = 0x0C51,
+  WRITE_EXTENDED_INQUIRY_RESPONSE = 0x0C52,
+  REFRESH_ENCRYPTION_KEY = 0x0C53,
+  READ_SIMPLE_PAIRING_MODE = 0x0C55,
+  WRITE_SIMPLE_PAIRING_MODE = 0x0C56,
+  READ_LOCAL_OOB_DATA = 0x0C57,
+  READ_INQUIRY_RESPONSE_TRANSMIT_POWER_LEVEL = 0x0C58,
+  WRITE_INQUIRY_TRANSMIT_POWER_LEVEL = 0x0C59,
+  READ_DEFAULT_ERRONEOUS_DATA_REPORTING = 0x0C5A,
+  WRITE_DEFAULT_ERRONEOUS_DATA_REPORTING = 0x0C5B,
+  ENHANCED_FLUSH = 0x0C5F,
+  SEND_KEYPRESS_NOTIFICATION = 0x0C60,
+  SET_EVENT_MASK_PAGE_2 = 0x0C63,
+  READ_FLOW_CONTROL_MODE = 0x0C66,
+  WRITE_FLOW_CONTROL_MODE = 0x0C67,
+  READ_ENHANCED_TRANSMIT_POWER_LEVEL = 0x0C68,
+  READ_LE_HOST_SUPPORT = 0x0C6C,
+  WRITE_LE_HOST_SUPPORT = 0x0C6D,
+  SET_MWS_CHANNEL_PARAMETERS = 0x0C6E,
+  SET_EXTERNAL_FRAME_CONFIGURATION = 0x0C6F,
+  SET_MWS_SIGNALING = 0x0C70,
+  SET_MWS_TRANSPORT_LAYER = 0x0C71,
+  SET_MWS_SCAN_FREQUENCY_TABLE = 0x0C72,
+  SET_MWS_PATTERN_CONFIGURATION = 0x0C73,
+  SET_RESERVED_LT_ADDR = 0x0C74,
+  DELETE_RESERVED_LT_ADDR = 0x0C75,
+  SET_CONNECTIONLESS_PERIPHERAL_BROADCAST_DATA = 0x0C76,
+  READ_SYNCHRONIZATION_TRAIN_PARAMETERS = 0x0C77,
+  WRITE_SYNCHRONIZATION_TRAIN_PARAMETERS = 0x0C78,
+  READ_SECURE_CONNECTIONS_HOST_SUPPORT = 0x0C79,
+  WRITE_SECURE_CONNECTIONS_HOST_SUPPORT = 0x0C7A,
+  READ_AUTHENTICATED_PAYLOAD_TIMEOUT = 0x0C7B,
+  WRITE_AUTHENTICATED_PAYLOAD_TIMEOUT = 0x0C7C,
+  READ_LOCAL_OOB_EXTENDED_DATA = 0x0C7D,
+  READ_EXTENDED_PAGE_TIMEOUT = 0x0C7E,
+  WRITE_EXTENDED_PAGE_TIMEOUT = 0x0C7F,
+  READ_EXTENDED_INQUIRY_LENGTH = 0x0C80,
+  WRITE_EXTENDED_INQUIRY_LENGTH = 0x0C81,
+  SET_ECOSYSTEM_BASE_INTERVAL = 0x0C82,
+  CONFIGURE_DATA_PATH = 0x0C83,
+  SET_MIN_ENCRYPTION_KEY_SIZE = 0x0C84,
+
+  // INFORMATIONAL_PARAMETERS
+  READ_LOCAL_VERSION_INFORMATION = 0x1001,
+  READ_LOCAL_SUPPORTED_COMMANDS = 0x1002,
+  READ_LOCAL_SUPPORTED_FEATURES = 0x1003,
+  READ_LOCAL_EXTENDED_FEATURES = 0x1004,
+  READ_BUFFER_SIZE = 0x1005,
+  READ_BD_ADDR = 0x1009,
+  READ_DATA_BLOCK_SIZE = 0x100A,
+  READ_LOCAL_SUPPORTED_CODECS_V1 = 0x100B,
+  READ_LOCAL_SIMPLE_PAIRING_OPTIONS = 0x100C,
+  READ_LOCAL_SUPPORTED_CODECS_V2 = 0x100D,
+  READ_LOCAL_SUPPORTED_CODEC_CAPABILITIES = 0x100E,
+  READ_LOCAL_SUPPORTED_CONTROLLER_DELAY = 0x100F,
+
+  // STATUS_PARAMETERS
+  READ_FAILED_CONTACT_COUNTER = 0x1401,
+  RESET_FAILED_CONTACT_COUNTER = 0x1402,
+  READ_LINK_QUALITY = 0x1403,
+  READ_RSSI = 0x1405,
+  READ_AFH_CHANNEL_MAP = 0x1406,
+  READ_CLOCK = 0x1407,
+  READ_ENCRYPTION_KEY_SIZE = 0x1408,
+  GET_MWS_TRANSPORT_LAYER_CONFIGURATION = 0x140C,
+  SET_TRIGGERED_CLOCK_CAPTURE = 0x140D,
+
+  // TESTING
+  READ_LOOPBACK_MODE = 0x1801,
+  WRITE_LOOPBACK_MODE = 0x1802,
+  ENABLE_DEVICE_UNDER_TEST_MODE = 0x1803,
+  WRITE_SIMPLE_PAIRING_DEBUG_MODE = 0x1804,
+  WRITE_SECURE_CONNECTIONS_TEST_MODE = 0x180A,
+
+  // LE_CONTROLLER
+  LE_SET_EVENT_MASK = 0x2001,
+  LE_READ_BUFFER_SIZE_V1 = 0x2002,
+  LE_READ_LOCAL_SUPPORTED_FEATURES = 0x2003,
+  LE_SET_RANDOM_ADDRESS = 0x2005,
+  LE_SET_ADVERTISING_PARAMETERS = 0x2006,
+  LE_READ_ADVERTISING_PHYSICAL_CHANNEL_TX_POWER = 0x2007,
+  LE_SET_ADVERTISING_DATA = 0x2008,
+  LE_SET_SCAN_RESPONSE_DATA = 0x2009,
+  LE_SET_ADVERTISING_ENABLE = 0x200A,
+  LE_SET_SCAN_PARAMETERS = 0x200B,
+  LE_SET_SCAN_ENABLE = 0x200C,
+  LE_CREATE_CONNECTION = 0x200D,
+  LE_CREATE_CONNECTION_CANCEL = 0x200E,
+  LE_READ_FILTER_ACCEPT_LIST_SIZE = 0x200F,
+  LE_CLEAR_FILTER_ACCEPT_LIST = 0x2010,
+  LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST = 0x2011,
+  LE_REMOVE_DEVICE_FROM_FILTER_ACCEPT_LIST = 0x2012,
+  LE_CONNECTION_UPDATE = 0x2013,
+  LE_SET_HOST_CHANNEL_CLASSIFICATION = 0x2014,
+  LE_READ_CHANNEL_MAP = 0x2015,
+  LE_READ_REMOTE_FEATURES = 0x2016,
+  LE_ENCRYPT = 0x2017,
+  LE_RAND = 0x2018,
+  LE_START_ENCRYPTION = 0x2019,
+  LE_LONG_TERM_KEY_REQUEST_REPLY = 0x201A,
+  LE_LONG_TERM_KEY_REQUEST_NEGATIVE_REPLY = 0x201B,
+  LE_READ_SUPPORTED_STATES = 0x201C,
+  LE_RECEIVER_TEST_V1 = 0x201D,
+  LE_TRANSMITTER_TEST_V1 = 0x201E,
+  LE_TEST_END = 0x201F,
+  LE_REMOTE_CONNECTION_PARAMETER_REQUEST_REPLY = 0x2020,
+  LE_REMOTE_CONNECTION_PARAMETER_REQUEST_NEGATIVE_REPLY = 0x2021,
+  LE_SET_DATA_LENGTH = 0x2022,
+  LE_READ_SUGGESTED_DEFAULT_DATA_LENGTH = 0x2023,
+  LE_WRITE_SUGGESTED_DEFAULT_DATA_LENGTH = 0x2024,
+  LE_READ_LOCAL_P_256_PUBLIC_KEY = 0x2025,
+  LE_GENERATE_DHKEY_V1 = 0x2026,
+  LE_ADD_DEVICE_TO_RESOLVING_LIST = 0x2027,
+  LE_REMOVE_DEVICE_FROM_RESOLVING_LIST = 0x2028,
+  LE_CLEAR_RESOLVING_LIST = 0x2029,
+  LE_READ_RESOLVING_LIST_SIZE = 0x202A,
+  LE_READ_PEER_RESOLVABLE_ADDRESS = 0x202B,
+  LE_READ_LOCAL_RESOLVABLE_ADDRESS = 0x202C,
+  LE_SET_ADDRESS_RESOLUTION_ENABLE = 0x202D,
+  LE_SET_RESOLVABLE_PRIVATE_ADDRESS_TIMEOUT = 0x202E,
+  LE_READ_MAXIMUM_DATA_LENGTH = 0x202F,
+  LE_READ_PHY = 0x2030,
+  LE_SET_DEFAULT_PHY = 0x2031,
+  LE_SET_PHY = 0x2032,
+  LE_RECEIVER_TEST_V2 = 0x2033,
+  LE_TRANSMITTER_TEST_V2 = 0x2034,
+  LE_SET_ADVERTISING_SET_RANDOM_ADDRESS = 0x2035,
+  LE_SET_EXTENDED_ADVERTISING_PARAMETERS = 0x2036,
+  LE_SET_EXTENDED_ADVERTISING_DATA = 0x2037,
+  LE_SET_EXTENDED_SCAN_RESPONSE_DATA = 0x2038,
+  LE_SET_EXTENDED_ADVERTISING_ENABLE = 0x2039,
+  LE_READ_MAXIMUM_ADVERTISING_DATA_LENGTH = 0x203A,
+  LE_READ_NUMBER_OF_SUPPORTED_ADVERTISING_SETS = 0x203B,
+  LE_REMOVE_ADVERTISING_SET = 0x203C,
+  LE_CLEAR_ADVERTISING_SETS = 0x203D,
+  LE_SET_PERIODIC_ADVERTISING_PARAMETERS = 0x203E,
+  LE_SET_PERIODIC_ADVERTISING_DATA = 0x203F,
+  LE_SET_PERIODIC_ADVERTISING_ENABLE = 0x2040,
+  LE_SET_EXTENDED_SCAN_PARAMETERS = 0x2041,
+  LE_SET_EXTENDED_SCAN_ENABLE = 0x2042,
+  LE_EXTENDED_CREATE_CONNECTION = 0x2043,
+  LE_PERIODIC_ADVERTISING_CREATE_SYNC = 0x2044,
+  LE_PERIODIC_ADVERTISING_CREATE_SYNC_CANCEL = 0x2045,
+  LE_PERIODIC_ADVERTISING_TERMINATE_SYNC = 0x2046,
+  LE_ADD_DEVICE_TO_PERIODIC_ADVERTISER_LIST = 0x2047,
+  LE_REMOVE_DEVICE_FROM_PERIODIC_ADVERTISER_LIST = 0x2048,
+  LE_CLEAR_PERIODIC_ADVERTISER_LIST = 0x2049,
+  LE_READ_PERIODIC_ADVERTISER_LIST_SIZE = 0x204A,
+  LE_READ_TRANSMIT_POWER = 0x204B,
+  LE_READ_RF_PATH_COMPENSATION_POWER = 0x204C,
+  LE_WRITE_RF_PATH_COMPENSATION_POWER = 0x204D,
+  LE_SET_PRIVACY_MODE = 0x204E,
+  LE_RECEIVER_TEST_V3 = 0x204F,
+  LE_TRANSMITTER_TEST_V3 = 0x2050,
+  LE_SET_CONNECTIONLESS_CTE_TRANSMIT_PARAMETERS = 0x2051,
+  LE_SET_CONNECTIONLESS_CTE_TRANSMIT_ENABLE = 0x2052,
+  LE_SET_CONNECTIONLESS_IQ_SAMPLING_ENABLE = 0x2053,
+  LE_SET_CONNECTION_CTE_RECEIVE_PARAMETERS = 0x2054,
+  LE_SET_CONNECTION_CTE_TRANSMIT_PARAMETERS = 0x2055,
+  LE_CONNECTION_CTE_REQUEST_ENABLE = 0x2056,
+  LE_CONNECTION_CTE_RESPONSE_ENABLE = 0x2057,
+  LE_READ_ANTENNA_INFORMATION = 0x2058,
+  LE_SET_PERIODIC_ADVERTISING_RECEIVE_ENABLE = 0x2059,
+  LE_PERIODIC_ADVERTISING_SYNC_TRANSFER = 0x205A,
+  LE_PERIODIC_ADVERTISING_SET_INFO_TRANSFER = 0x205B,
+  LE_SET_PERIODIC_ADVERTISING_SYNC_TRANSFER_PARAMETERS = 0x205C,
+  LE_SET_DEFAULT_PERIODIC_ADVERTISING_SYNC_TRANSFER_PARAMETERS = 0x205D,
+  LE_GENERATE_DHKEY_V2 = 0x205E,
+  LE_MODIFY_SLEEP_CLOCK_ACCURACY = 0x205F,
+  LE_READ_BUFFER_SIZE_V2 = 0x2060,
+  LE_READ_ISO_TX_SYNC = 0x2061,
+  LE_SET_CIG_PARAMETERS = 0x2062,
+  LE_SET_CIG_PARAMETERS_TEST = 0x2063,
+  LE_CREATE_CIS = 0x2064,
+  LE_REMOVE_CIG = 0x2065,
+  LE_ACCEPT_CIS_REQUEST = 0x2066,
+  LE_REJECT_CIS_REQUEST = 0x2067,
+  LE_CREATE_BIG = 0x2068,
+  LE_CREATE_BIG_TEST = 0x2069,
+  LE_TERMINATE_BIG = 0x206A,
+  LE_BIG_CREATE_SYNC = 0x206B,
+  LE_BIG_TERMINATE_SYNC = 0x206C,
+  LE_REQUEST_PEER_SCA = 0x206D,
+  LE_SETUP_ISO_DATA_PATH = 0x206E,
+  LE_REMOVE_ISO_DATA_PATH = 0x206F,
+  LE_ISO_TRANSMIT_TEST = 0x2070,
+  LE_ISO_RECEIVE_TEST = 0x2071,
+  LE_ISO_READ_TEST_COUNTERS = 0x2072,
+  LE_ISO_TEST_END = 0x2073,
+  LE_SET_HOST_FEATURE = 0x2074,
+  LE_READ_ISO_LINK_QUALITY = 0x2075,
+  LE_ENHANCED_READ_TRANSMIT_POWER_LEVEL = 0x2076,
+  LE_READ_REMOTE_TRANSMIT_POWER_LEVEL = 0x2077,
+  LE_SET_PATH_LOSS_REPORTING_PARAMETERS = 0x2078,
+  LE_SET_PATH_LOSS_REPORTING_ENABLE = 0x2079,
+  LE_SET_TRANSMIT_POWER_REPORTING_ENABLE = 0x207A,
+  LE_TRANSMITTER_TEST_V4 = 0x207B,
+  LE_SET_DATA_RELATED_ADDRESS_CHANGES = 0x207C,
+  LE_SET_DEFAULT_SUBRATE = 0x207D,
+  LE_SUBRATE_REQUEST = 0x207E,
+
+  // VENDOR_SPECIFIC
+  // MSFT_OPCODE_xxxx below is needed for the tests.
+  MSFT_OPCODE_INTEL = 0xFC1E,
+  LE_GET_VENDOR_CAPABILITIES = 0xFD53,
+  LE_BATCH_SCAN = 0xFD56,
+  LE_APCF = 0xFD57,
+  LE_GET_CONTROLLER_ACTIVITY_ENERGY_INFO = 0xFD59,
+  LE_EX_SET_SCAN_PARAMETERS = 0xFD5A,
+  GET_CONTROLLER_DEBUG_INFO = 0xFD5B,
+  // MSFT_OPCODE_xxxx below are needed for the tests.
+  MSFT_OPCODE_MEDIATEK = 0xFD30,
+  MSFT_OPCODE_QUALCOMM = 0xFD70,
+}
+
+// For mapping Local Supported Commands command
+// Value = Octet * 10 + bit
+enum OpCodeIndex : 16 {
+  INQUIRY = 0,
+  INQUIRY_CANCEL = 1,
+  PERIODIC_INQUIRY_MODE = 2,
+  EXIT_PERIODIC_INQUIRY_MODE = 3,
+  CREATE_CONNECTION = 4,
+  DISCONNECT = 5,
+  ADD_SCO_CONNECTION = 6,
+  CREATE_CONNECTION_CANCEL = 7,
+  ACCEPT_CONNECTION_REQUEST = 10,
+  REJECT_CONNECTION_REQUEST = 11,
+  LINK_KEY_REQUEST_REPLY = 12,
+  LINK_KEY_REQUEST_NEGATIVE_REPLY = 13,
+  PIN_CODE_REQUEST_REPLY = 14,
+  PIN_CODE_REQUEST_NEGATIVE_REPLY = 15,
+  CHANGE_CONNECTION_PACKET_TYPE = 16,
+  AUTHENTICATION_REQUESTED = 17,
+  SET_CONNECTION_ENCRYPTION = 20,
+  CHANGE_CONNECTION_LINK_KEY = 21,
+  CENTRAL_LINK_KEY = 22,
+  REMOTE_NAME_REQUEST = 23,
+  REMOTE_NAME_REQUEST_CANCEL = 24,
+  READ_REMOTE_SUPPORTED_FEATURES = 25,
+  READ_REMOTE_EXTENDED_FEATURES = 26,
+  READ_REMOTE_VERSION_INFORMATION = 27,
+  READ_CLOCK_OFFSET = 30,
+  READ_LMP_HANDLE = 31,
+  HOLD_MODE = 41,
+  SNIFF_MODE = 42,
+  EXIT_SNIFF_MODE = 43,
+  QOS_SETUP = 46,
+  ROLE_DISCOVERY = 47,
+  SWITCH_ROLE = 50,
+  READ_LINK_POLICY_SETTINGS = 51,
+  WRITE_LINK_POLICY_SETTINGS = 52,
+  READ_DEFAULT_LINK_POLICY_SETTINGS = 53,
+  WRITE_DEFAULT_LINK_POLICY_SETTINGS = 54,
+  FLOW_SPECIFICATION = 55,
+  SET_EVENT_MASK = 56,
+  RESET = 57,
+  SET_EVENT_FILTER = 60,
+  FLUSH = 61,
+  READ_PIN_TYPE = 62,
+  WRITE_PIN_TYPE = 63,
+  READ_STORED_LINK_KEY = 65,
+  WRITE_STORED_LINK_KEY = 66,
+  DELETE_STORED_LINK_KEY = 67,
+  WRITE_LOCAL_NAME = 70,
+  READ_LOCAL_NAME = 71,
+  READ_CONNECTION_ACCEPT_TIMEOUT = 72,
+  WRITE_CONNECTION_ACCEPT_TIMEOUT = 73,
+  READ_PAGE_TIMEOUT = 74,
+  WRITE_PAGE_TIMEOUT = 75,
+  READ_SCAN_ENABLE = 76,
+  WRITE_SCAN_ENABLE = 77,
+  READ_PAGE_SCAN_ACTIVITY = 80,
+  WRITE_PAGE_SCAN_ACTIVITY = 81,
+  READ_INQUIRY_SCAN_ACTIVITY = 82,
+  WRITE_INQUIRY_SCAN_ACTIVITY = 83,
+  READ_AUTHENTICATION_ENABLE = 84,
+  WRITE_AUTHENTICATION_ENABLE = 85,
+  READ_CLASS_OF_DEVICE = 90,
+  WRITE_CLASS_OF_DEVICE = 91,
+  READ_VOICE_SETTING = 92,
+  WRITE_VOICE_SETTING = 93,
+  READ_AUTOMATIC_FLUSH_TIMEOUT = 94,
+  WRITE_AUTOMATIC_FLUSH_TIMEOUT = 95,
+  READ_NUM_BROADCAST_RETRANSMITS = 96,
+  WRITE_NUM_BROADCAST_RETRANSMITS = 97,
+  READ_HOLD_MODE_ACTIVITY = 100,
+  WRITE_HOLD_MODE_ACTIVITY = 101,
+  READ_TRANSMIT_POWER_LEVEL = 102,
+  READ_SYNCHRONOUS_FLOW_CONTROL_ENABLE = 103,
+  WRITE_SYNCHRONOUS_FLOW_CONTROL_ENABLE = 104,
+  SET_CONTROLLER_TO_HOST_FLOW_CONTROL = 105,
+  HOST_BUFFER_SIZE = 106,
+  HOST_NUMBER_OF_COMPLETED_PACKETS = 107,
+  READ_LINK_SUPERVISION_TIMEOUT = 110,
+  WRITE_LINK_SUPERVISION_TIMEOUT = 111,
+  READ_NUMBER_OF_SUPPORTED_IAC = 112,
+  READ_CURRENT_IAC_LAP = 113,
+  WRITE_CURRENT_IAC_LAP = 114,
+  SET_AFH_HOST_CHANNEL_CLASSIFICATION = 121,
+  READ_INQUIRY_SCAN_TYPE = 124,
+  WRITE_INQUIRY_SCAN_TYPE = 125,
+  READ_INQUIRY_MODE = 126,
+  WRITE_INQUIRY_MODE = 127,
+  READ_PAGE_SCAN_TYPE = 130,
+  WRITE_PAGE_SCAN_TYPE = 131,
+  READ_AFH_CHANNEL_ASSESSMENT_MODE = 132,
+  WRITE_AFH_CHANNEL_ASSESSMENT_MODE = 133,
+  READ_LOCAL_VERSION_INFORMATION = 143,
+  READ_LOCAL_SUPPORTED_FEATURES = 145,
+  READ_LOCAL_EXTENDED_FEATURES = 146,
+  READ_BUFFER_SIZE = 147,
+  READ_BD_ADDR = 151,
+  READ_FAILED_CONTACT_COUNTER = 152,
+  RESET_FAILED_CONTACT_COUNTER = 153,
+  READ_LINK_QUALITY = 154,
+  READ_RSSI = 155,
+  READ_AFH_CHANNEL_MAP = 156,
+  READ_CLOCK = 157,
+  READ_LOOPBACK_MODE = 160,
+  WRITE_LOOPBACK_MODE = 161,
+  ENABLE_DEVICE_UNDER_TEST_MODE = 162,
+  SETUP_SYNCHRONOUS_CONNECTION = 163,
+  ACCEPT_SYNCHRONOUS_CONNECTION = 164,
+  REJECT_SYNCHRONOUS_CONNECTION = 165,
+  READ_EXTENDED_INQUIRY_RESPONSE = 170,
+  WRITE_EXTENDED_INQUIRY_RESPONSE = 171,
+  REFRESH_ENCRYPTION_KEY = 172,
+  SNIFF_SUBRATING = 174,
+  READ_SIMPLE_PAIRING_MODE = 175,
+  WRITE_SIMPLE_PAIRING_MODE = 176,
+  READ_LOCAL_OOB_DATA = 177,
+  READ_INQUIRY_RESPONSE_TRANSMIT_POWER_LEVEL = 180,
+  WRITE_INQUIRY_TRANSMIT_POWER_LEVEL = 181,
+  READ_DEFAULT_ERRONEOUS_DATA_REPORTING = 182,
+  WRITE_DEFAULT_ERRONEOUS_DATA_REPORTING = 183,
+  IO_CAPABILITY_REQUEST_REPLY = 187,
+  USER_CONFIRMATION_REQUEST_REPLY = 190,
+  USER_CONFIRMATION_REQUEST_NEGATIVE_REPLY = 191,
+  USER_PASSKEY_REQUEST_REPLY = 192,
+  USER_PASSKEY_REQUEST_NEGATIVE_REPLY = 193,
+  REMOTE_OOB_DATA_REQUEST_REPLY = 194,
+  WRITE_SIMPLE_PAIRING_DEBUG_MODE = 195,
+  ENHANCED_FLUSH = 196,
+  REMOTE_OOB_DATA_REQUEST_NEGATIVE_REPLY = 197,
+  SEND_KEYPRESS_NOTIFICATION = 202,
+  IO_CAPABILITY_REQUEST_NEGATIVE_REPLY = 203,
+  READ_ENCRYPTION_KEY_SIZE = 204,
+  SET_EVENT_MASK_PAGE_2 = 222,
+  READ_FLOW_CONTROL_MODE = 230,
+  WRITE_FLOW_CONTROL_MODE = 231,
+  READ_DATA_BLOCK_SIZE = 232,
+  READ_ENHANCED_TRANSMIT_POWER_LEVEL = 240,
+  READ_LE_HOST_SUPPORT = 245,
+  WRITE_LE_HOST_SUPPORT = 246,
+  LE_SET_EVENT_MASK = 250,
+  LE_READ_BUFFER_SIZE_V1 = 251,
+  LE_READ_LOCAL_SUPPORTED_FEATURES = 252,
+  LE_SET_RANDOM_ADDRESS = 254,
+  LE_SET_ADVERTISING_PARAMETERS = 255,
+  LE_READ_ADVERTISING_PHYSICAL_CHANNEL_TX_POWER = 256,
+  LE_SET_ADVERTISING_DATA = 257,
+  LE_SET_SCAN_RESPONSE_DATA = 260,
+  LE_SET_ADVERTISING_ENABLE = 261,
+  LE_SET_SCAN_PARAMETERS = 262,
+  LE_SET_SCAN_ENABLE = 263,
+  LE_CREATE_CONNECTION = 264,
+  LE_CREATE_CONNECTION_CANCEL = 265,
+  LE_READ_FILTER_ACCEPT_LIST_SIZE = 266,
+  LE_CLEAR_FILTER_ACCEPT_LIST = 267,
+  LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST = 270,
+  LE_REMOVE_DEVICE_FROM_FILTER_ACCEPT_LIST = 271,
+  LE_CONNECTION_UPDATE = 272,
+  LE_SET_HOST_CHANNEL_CLASSIFICATION = 273,
+  LE_READ_CHANNEL_MAP = 274,
+  LE_READ_REMOTE_FEATURES = 275,
+  LE_ENCRYPT = 276,
+  LE_RAND = 277,
+  LE_START_ENCRYPTION = 280,
+  LE_LONG_TERM_KEY_REQUEST_REPLY = 281,
+  LE_LONG_TERM_KEY_REQUEST_NEGATIVE_REPLY = 282,
+  LE_READ_SUPPORTED_STATES = 283,
+  LE_RECEIVER_TEST_V1 = 284,
+  LE_TRANSMITTER_TEST_V1 = 285,
+  LE_TEST_END = 286,
+  ENHANCED_SETUP_SYNCHRONOUS_CONNECTION = 293,
+  ENHANCED_ACCEPT_SYNCHRONOUS_CONNECTION = 294,
+  READ_LOCAL_SUPPORTED_CODECS_V1 = 295,
+  SET_MWS_CHANNEL_PARAMETERS = 296,
+  SET_EXTERNAL_FRAME_CONFIGURATION = 297,
+  SET_MWS_SIGNALING = 300,
+  SET_MWS_TRANSPORT_LAYER = 301,
+  SET_MWS_SCAN_FREQUENCY_TABLE = 302,
+  GET_MWS_TRANSPORT_LAYER_CONFIGURATION = 303,
+  SET_MWS_PATTERN_CONFIGURATION = 304,
+  SET_TRIGGERED_CLOCK_CAPTURE = 305,
+  TRUNCATED_PAGE = 306,
+  TRUNCATED_PAGE_CANCEL = 307,
+  SET_CONNECTIONLESS_PERIPHERAL_BROADCAST = 310,
+  SET_CONNECTIONLESS_PERIPHERAL_BROADCAST_RECEIVE = 311,
+  START_SYNCHRONIZATION_TRAIN = 312,
+  RECEIVE_SYNCHRONIZATION_TRAIN = 313,
+  SET_RESERVED_LT_ADDR = 314,
+  DELETE_RESERVED_LT_ADDR = 315,
+  SET_CONNECTIONLESS_PERIPHERAL_BROADCAST_DATA = 316,
+  READ_SYNCHRONIZATION_TRAIN_PARAMETERS = 317,
+  WRITE_SYNCHRONIZATION_TRAIN_PARAMETERS = 320,
+  REMOTE_OOB_EXTENDED_DATA_REQUEST_REPLY = 321,
+  READ_SECURE_CONNECTIONS_HOST_SUPPORT = 322,
+  WRITE_SECURE_CONNECTIONS_HOST_SUPPORT = 323,
+  READ_AUTHENTICATED_PAYLOAD_TIMEOUT = 324,
+  WRITE_AUTHENTICATED_PAYLOAD_TIMEOUT = 325,
+  READ_LOCAL_OOB_EXTENDED_DATA = 326,
+  WRITE_SECURE_CONNECTIONS_TEST_MODE = 327,
+  READ_EXTENDED_PAGE_TIMEOUT = 330,
+  WRITE_EXTENDED_PAGE_TIMEOUT = 331,
+  READ_EXTENDED_INQUIRY_LENGTH = 332,
+  WRITE_EXTENDED_INQUIRY_LENGTH = 333,
+  LE_REMOTE_CONNECTION_PARAMETER_REQUEST_REPLY = 334,
+  LE_REMOTE_CONNECTION_PARAMETER_REQUEST_NEGATIVE_REPLY = 335,
+  LE_SET_DATA_LENGTH = 336,
+  LE_READ_SUGGESTED_DEFAULT_DATA_LENGTH = 337,
+  LE_WRITE_SUGGESTED_DEFAULT_DATA_LENGTH = 340,
+  LE_READ_LOCAL_P_256_PUBLIC_KEY = 341,
+  LE_GENERATE_DHKEY_V1 = 342,
+  LE_ADD_DEVICE_TO_RESOLVING_LIST = 343,
+  LE_REMOVE_DEVICE_FROM_RESOLVING_LIST = 344,
+  LE_CLEAR_RESOLVING_LIST = 345,
+  LE_READ_RESOLVING_LIST_SIZE = 346,
+  LE_READ_PEER_RESOLVABLE_ADDRESS = 347,
+  LE_READ_LOCAL_RESOLVABLE_ADDRESS = 350,
+  LE_SET_ADDRESS_RESOLUTION_ENABLE = 351,
+  LE_SET_RESOLVABLE_PRIVATE_ADDRESS_TIMEOUT = 352,
+  LE_READ_MAXIMUM_DATA_LENGTH = 353,
+  LE_READ_PHY = 354,
+  LE_SET_DEFAULT_PHY = 355,
+  LE_SET_PHY = 356,
+  LE_RECEIVER_TEST_V2 = 357,
+  LE_TRANSMITTER_TEST_V2 = 360,
+  LE_SET_ADVERTISING_SET_RANDOM_ADDRESS = 361,
+  LE_SET_EXTENDED_ADVERTISING_PARAMETERS = 362,
+  LE_SET_EXTENDED_ADVERTISING_DATA = 363,
+  LE_SET_EXTENDED_SCAN_RESPONSE_DATA = 364,
+  LE_SET_EXTENDED_ADVERTISING_ENABLE = 365,
+  LE_READ_MAXIMUM_ADVERTISING_DATA_LENGTH = 366,
+  LE_READ_NUMBER_OF_SUPPORTED_ADVERTISING_SETS = 367,
+  LE_REMOVE_ADVERTISING_SET = 370,
+  LE_CLEAR_ADVERTISING_SETS = 371,
+  LE_SET_PERIODIC_ADVERTISING_PARAMETERS = 372,
+  LE_SET_PERIODIC_ADVERTISING_DATA = 373,
+  LE_SET_PERIODIC_ADVERTISING_ENABLE = 374,
+  LE_SET_EXTENDED_SCAN_PARAMETERS = 375,
+  LE_SET_EXTENDED_SCAN_ENABLE = 376,
+  LE_EXTENDED_CREATE_CONNECTION = 377,
+  LE_PERIODIC_ADVERTISING_CREATE_SYNC = 380,
+  LE_PERIODIC_ADVERTISING_CREATE_SYNC_CANCEL = 381,
+  LE_PERIODIC_ADVERTISING_TERMINATE_SYNC = 382,
+  LE_ADD_DEVICE_TO_PERIODIC_ADVERTISER_LIST = 383,
+  LE_REMOVE_DEVICE_FROM_PERIODIC_ADVERTISER_LIST = 384,
+  LE_CLEAR_PERIODIC_ADVERTISER_LIST = 385,
+  LE_READ_PERIODIC_ADVERTISER_LIST_SIZE = 386,
+  LE_READ_TRANSMIT_POWER = 387,
+  LE_READ_RF_PATH_COMPENSATION_POWER = 390,
+  LE_WRITE_RF_PATH_COMPENSATION_POWER = 391,
+  LE_SET_PRIVACY_MODE = 392,
+  LE_RECEIVER_TEST_V3 = 393,
+  LE_TRANSMITTER_TEST_V3 = 394,
+  LE_SET_CONNECTIONLESS_CTE_TRANSMIT_PARAMETERS = 395,
+  LE_SET_CONNECTIONLESS_CTE_TRANSMIT_ENABLE = 396,
+  LE_SET_CONNECTIONLESS_IQ_SAMPLING_ENABLE = 397,
+  LE_SET_CONNECTION_CTE_RECEIVE_PARAMETERS = 400,
+  LE_SET_CONNECTION_CTE_TRANSMIT_PARAMETERS = 401,
+  LE_CONNECTION_CTE_REQUEST_ENABLE = 402,
+  LE_CONNECTION_CTE_RESPONSE_ENABLE = 403,
+  LE_READ_ANTENNA_INFORMATION = 404,
+  LE_SET_PERIODIC_ADVERTISING_RECEIVE_ENABLE = 405,
+  LE_PERIODIC_ADVERTISING_SYNC_TRANSFER = 406,
+  LE_PERIODIC_ADVERTISING_SET_INFO_TRANSFER = 407,
+  LE_SET_PERIODIC_ADVERTISING_SYNC_TRANSFER_PARAMETERS = 410,
+  LE_SET_DEFAULT_PERIODIC_ADVERTISING_SYNC_TRANSFER_PARAMETERS = 411,
+  LE_GENERATE_DHKEY_V2 = 412,
+  READ_LOCAL_SIMPLE_PAIRING_OPTIONS = 413,
+  LE_MODIFY_SLEEP_CLOCK_ACCURACY = 414,
+  LE_READ_BUFFER_SIZE_V2 = 415,
+  LE_READ_ISO_TX_SYNC = 416,
+  LE_SET_CIG_PARAMETERS = 417,
+  LE_SET_CIG_PARAMETERS_TEST = 420,
+  LE_CREATE_CIS = 421,
+  LE_REMOVE_CIG = 422,
+  LE_ACCEPT_CIS_REQUEST = 423,
+  LE_REJECT_CIS_REQUEST = 424,
+  LE_CREATE_BIG = 425,
+  LE_CREATE_BIG_TEST = 426,
+  LE_TERMINATE_BIG = 427,
+  LE_BIG_CREATE_SYNC = 430,
+  LE_BIG_TERMINATE_SYNC = 431,
+  LE_REQUEST_PEER_SCA = 432,
+  LE_SETUP_ISO_DATA_PATH = 433,
+  LE_REMOVE_ISO_DATA_PATH = 434,
+  LE_ISO_TRANSMIT_TEST = 435,
+  LE_ISO_RECEIVE_TEST = 436,
+  LE_ISO_READ_TEST_COUNTERS = 437,
+  LE_ISO_TEST_END = 440,
+  LE_SET_HOST_FEATURE = 441,
+  LE_READ_ISO_LINK_QUALITY = 442,
+  LE_ENHANCED_READ_TRANSMIT_POWER_LEVEL = 443,
+  LE_READ_REMOTE_TRANSMIT_POWER_LEVEL = 444,
+  LE_SET_PATH_LOSS_REPORTING_PARAMETERS = 445,
+  LE_SET_PATH_LOSS_REPORTING_ENABLE = 446,
+  LE_SET_TRANSMIT_POWER_REPORTING_ENABLE = 447,
+  LE_TRANSMITTER_TEST_V4 = 450,
+  SET_ECOSYSTEM_BASE_INTERVAL = 451,
+  READ_LOCAL_SUPPORTED_CODECS_V2 = 452,
+  READ_LOCAL_SUPPORTED_CODEC_CAPABILITIES = 453,
+  READ_LOCAL_SUPPORTED_CONTROLLER_DELAY = 454,
+  CONFIGURE_DATA_PATH = 455,
+  LE_SET_DATA_RELATED_ADDRESS_CHANGES = 456,
+  SET_MIN_ENCRYPTION_KEY_SIZE = 457,
+  LE_SET_DEFAULT_SUBRATE = 460,
+  LE_SUBRATE_REQUEST = 461,
+}
+
+packet Command {
+  op_code : OpCode,
+  _size_(_payload_) : 8,
+  _payload_,
+}
+
+// HCI Event Packets
+
+enum EventCode : 8 {
+  INQUIRY_COMPLETE = 0x01,
+  INQUIRY_RESULT = 0x02,
+  CONNECTION_COMPLETE = 0x03,
+  CONNECTION_REQUEST = 0x04,
+  DISCONNECTION_COMPLETE = 0x05,
+  AUTHENTICATION_COMPLETE = 0x06,
+  REMOTE_NAME_REQUEST_COMPLETE = 0x07,
+  ENCRYPTION_CHANGE = 0x08,
+  CHANGE_CONNECTION_LINK_KEY_COMPLETE = 0x09,
+  CENTRAL_LINK_KEY_COMPLETE = 0x0A,
+  READ_REMOTE_SUPPORTED_FEATURES_COMPLETE = 0x0B,
+  READ_REMOTE_VERSION_INFORMATION_COMPLETE = 0x0C,
+  QOS_SETUP_COMPLETE = 0x0D,
+  COMMAND_COMPLETE = 0x0E,
+  COMMAND_STATUS = 0x0F,
+  HARDWARE_ERROR = 0x10,
+  FLUSH_OCCURRED = 0x11,
+  ROLE_CHANGE = 0x12,
+  NUMBER_OF_COMPLETED_PACKETS = 0x13,
+  MODE_CHANGE = 0x14,
+  RETURN_LINK_KEYS = 0x15,
+  PIN_CODE_REQUEST = 0x16,
+  LINK_KEY_REQUEST = 0x17,
+  LINK_KEY_NOTIFICATION = 0x18,
+  LOOPBACK_COMMAND = 0x19,
+  DATA_BUFFER_OVERFLOW = 0x1A,
+  MAX_SLOTS_CHANGE = 0x1B,
+  READ_CLOCK_OFFSET_COMPLETE = 0x1C,
+  CONNECTION_PACKET_TYPE_CHANGED = 0x1D,
+  QOS_VIOLATION = 0x1E,
+  PAGE_SCAN_REPETITION_MODE_CHANGE = 0x20,
+  FLOW_SPECIFICATION_COMPLETE = 0x21,
+  INQUIRY_RESULT_WITH_RSSI = 0x22,
+  READ_REMOTE_EXTENDED_FEATURES_COMPLETE = 0x23,
+  SYNCHRONOUS_CONNECTION_COMPLETE = 0x2C,
+  SYNCHRONOUS_CONNECTION_CHANGED = 0x2D,
+  SNIFF_SUBRATING = 0x2E,
+  EXTENDED_INQUIRY_RESULT = 0x2F,
+  ENCRYPTION_KEY_REFRESH_COMPLETE = 0x30,
+  IO_CAPABILITY_REQUEST = 0x31,
+  IO_CAPABILITY_RESPONSE = 0x32,
+  USER_CONFIRMATION_REQUEST = 0x33,
+  USER_PASSKEY_REQUEST = 0x34,
+  REMOTE_OOB_DATA_REQUEST = 0x35,
+  SIMPLE_PAIRING_COMPLETE = 0x36,
+  LINK_SUPERVISION_TIMEOUT_CHANGED = 0x38,
+  ENHANCED_FLUSH_COMPLETE = 0x39,
+  USER_PASSKEY_NOTIFICATION = 0x3B,
+  KEYPRESS_NOTIFICATION = 0x3C,
+  REMOTE_HOST_SUPPORTED_FEATURES_NOTIFICATION = 0x3D,
+  LE_META_EVENT = 0x3e,
+  NUMBER_OF_COMPLETED_DATA_BLOCKS = 0x48,
+  VENDOR_SPECIFIC = 0xFF,
+}
+
+// LE events
+enum SubeventCode : 8 {
+  CONNECTION_COMPLETE = 0x01,
+  ADVERTISING_REPORT = 0x02,
+  CONNECTION_UPDATE_COMPLETE = 0x03,
+  READ_REMOTE_FEATURES_COMPLETE = 0x04,
+  LONG_TERM_KEY_REQUEST = 0x05,
+  REMOTE_CONNECTION_PARAMETER_REQUEST = 0x06,
+  DATA_LENGTH_CHANGE = 0x07,
+  READ_LOCAL_P256_PUBLIC_KEY_COMPLETE = 0x08,
+  GENERATE_DHKEY_COMPLETE = 0x09,
+  ENHANCED_CONNECTION_COMPLETE = 0x0a,
+  DIRECTED_ADVERTISING_REPORT = 0x0b,
+  PHY_UPDATE_COMPLETE = 0x0c,
+  EXTENDED_ADVERTISING_REPORT = 0x0D,
+  PERIODIC_ADVERTISING_SYNC_ESTABLISHED = 0x0E,
+  PERIODIC_ADVERTISING_REPORT = 0x0F,
+  PERIODIC_ADVERTISING_SYNC_LOST = 0x10,
+  SCAN_TIMEOUT = 0x11,
+  ADVERTISING_SET_TERMINATED = 0x12,
+  SCAN_REQUEST_RECEIVED = 0x13,
+  CHANNEL_SELECTION_ALGORITHM = 0x14,
+  CONNECTIONLESS_IQ_REPORT = 0x15,
+  CONNECTION_IQ_REPORT = 0x16,
+  CTE_REQUEST_FAILED = 0x17,
+  PERIODIC_ADVERTISING_SYNC_TRANSFER_RECEIVED = 0x18,
+  CIS_ESTABLISHED = 0x19,
+  CIS_REQUEST = 0x1A,
+  CREATE_BIG_COMPLETE = 0x1B,
+  TERMINATE_BIG_COMPLETE = 0x1C,
+  BIG_SYNC_ESTABLISHED = 0x1D,
+  BIG_SYNC_LOST = 0x1E,
+  REQUEST_PEER_SCA_COMPLETE = 0x1F,
+  PATH_LOSS_THRESHOLD = 0x20,
+  TRANSMIT_POWER_REPORTING = 0x21,
+  BIG_INFO_ADVERTISING_REPORT = 0x22,
+  LE_SUBRATE_CHANGE = 0x23,
+}
+
+// Vendor specific events
+enum VseSubeventCode : 8 {
+  STORAGE_THRESHOLD_BREACH = 0x54,
+  LE_MULTI_ADVERTISING_STATE_CHANGE = 0x55,
+  LE_ADVERTISEMENT_TRACKING = 0x56,
+  CONTROLLER_DEBUG_INFO = 0x57,
+  BLUETOOTH_QUALITY_REPORT = 0x58,
+}
+
+packet Event {
+  event_code : EventCode,
+  _size_(_payload_) : 8,
+  _payload_,
+}
+
+// Common definitions for commands and events
+
+enum FeatureFlag : 1 {
+  UNSUPPORTED = 0,
+  SUPPORTED = 1,
+}
+
+enum ErrorCode: 8 {
+  STATUS_UNKNOWN = 0xFF,
+  SUCCESS = 0x00,
+  UNKNOWN_HCI_COMMAND = 0x01,
+  UNKNOWN_CONNECTION = 0x02,
+  HARDWARE_FAILURE = 0x03,
+  PAGE_TIMEOUT = 0x04,
+  AUTHENTICATION_FAILURE = 0x05,
+  PIN_OR_KEY_MISSING = 0x06,
+  MEMORY_CAPACITY_EXCEEDED = 0x07,
+  CONNECTION_TIMEOUT = 0x08,
+  CONNECTION_LIMIT_EXCEEDED = 0x09,
+  SYNCHRONOUS_CONNECTION_LIMIT_EXCEEDED = 0x0A,
+  CONNECTION_ALREADY_EXISTS = 0x0B,
+  COMMAND_DISALLOWED = 0x0C,
+  CONNECTION_REJECTED_LIMITED_RESOURCES = 0x0D,
+  CONNECTION_REJECTED_SECURITY_REASONS = 0x0E,
+  CONNECTION_REJECTED_UNACCEPTABLE_BD_ADDR = 0x0F,
+  CONNECTION_ACCEPT_TIMEOUT = 0x10,
+  UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE = 0x11,
+  INVALID_HCI_COMMAND_PARAMETERS = 0x12,
+  REMOTE_USER_TERMINATED_CONNECTION = 0x13,
+  REMOTE_DEVICE_TERMINATED_CONNECTION_LOW_RESOURCES = 0x14,
+  REMOTE_DEVICE_TERMINATED_CONNECTION_POWER_OFF = 0x15,
+  CONNECTION_TERMINATED_BY_LOCAL_HOST = 0x16,
+  REPEATED_ATTEMPTS = 0x17,
+  PAIRING_NOT_ALLOWED = 0x18,
+  UNKNOWN_LMP_PDU = 0x19,
+  UNSUPPORTED_REMOTE_OR_LMP_FEATURE = 0x1A,
+  SCO_OFFSET_REJECTED = 0x1B,
+  SCO_INTERVAL_REJECTED = 0x1C,
+  SCO_AIR_MODE_REJECTED = 0x1D,
+  INVALID_LMP_OR_LL_PARAMETERS = 0x1E,
+  UNSPECIFIED_ERROR = 0x1F,
+  UNSUPPORTED_LMP_OR_LL_PARAMETER = 0x20,
+  ROLE_CHANGE_NOT_ALLOWED = 0x21,
+  TRANSACTION_RESPONSE_TIMEOUT = 0x22,
+  LINK_LAYER_COLLISION = 0x23,
+  ENCRYPTION_MODE_NOT_ACCEPTABLE = 0x25,
+  ROLE_SWITCH_FAILED = 0x35,
+  HOST_BUSY = 0x38,
+  CONTROLLER_BUSY = 0x3A,
+  ADVERTISING_TIMEOUT = 0x3C,
+  CONNECTION_FAILED_ESTABLISHMENT = 0x3E,
+  UNKNOWN_ADVERTISING_IDENTIFIER = 0x42,
+  LIMIT_REACHED = 0x43,
+  OPERATION_CANCELLED_BY_HOST = 0x44,
+  PACKET_TOO_LONG = 0x45,
+}
+
+// Events that are defined with their respective commands
+
+packet CommandComplete : Event (event_code = COMMAND_COMPLETE) {
+  num_hci_command_packets : 8,
+  command_op_code : OpCode,
+  _payload_,
+}
+
+packet CommandStatus : Event (event_code = COMMAND_STATUS) {
+  status : ErrorCode, // SUCCESS means PENDING
+  num_hci_command_packets : 8,
+  command_op_code : OpCode,
+  _payload_,
+}
+
+packet VendorSpecificEvent : Event (event_code = VENDOR_SPECIFIC) {
+  subevent_code : VseSubeventCode,
+  _payload_,
+}
+
+  // Credits
+packet NoCommandComplete : CommandComplete (command_op_code = NONE) {
+}
+
+struct Lap { // Lower Address Part
+  lap : 6,
+  _reserved_ : 2,
+  _fixed_ = 0x9e8b : 16,
+}
+
+  // LINK_CONTROL
+packet Inquiry : Command (op_code = INQUIRY) {
+  lap : Lap,
+  inquiry_length : 8, // 0x1 - 0x30 (times 1.28s)
+  num_responses : 8, // 0x00 unlimited
+}
+
+test Inquiry {
+  "\x01\x04\x05\x33\x8b\x9e\xaa\xbb",
+}
+
+packet InquiryStatus : CommandStatus (command_op_code = INQUIRY) {
+}
+
+test InquiryStatus {
+  "\x0f\x04\x00\x01\x01\x04",
+}
+
+packet InquiryCancel : Command (op_code = INQUIRY_CANCEL) {
+}
+
+test InquiryCancel {
+  "\x02\x04\x00",
+}
+
+packet InquiryCancelComplete : CommandComplete (command_op_code = INQUIRY_CANCEL) {
+  status : ErrorCode,
+}
+
+test InquiryCancelComplete {
+  "\x0e\x04\x01\x02\x04\x00",
+}
+
+packet PeriodicInquiryMode : Command (op_code = PERIODIC_INQUIRY_MODE) {
+  max_period_length : 16, // Range 0x0003 to 0xffff (times 1.28s)
+  min_period_length : 16, // Range 0x0002 to 0xfffe (times 1.28s)
+  lap : Lap,
+  inquiry_length : 8, // 0x1 - 0x30 (times 1.28s)
+  num_responses : 8, // 0x00 unlimited
+}
+
+test PeriodicInquiryMode {
+  "\x03\x04\x09\x12\x34\x56\x78\x11\x8b\x9e\x9a\xbc",
+}
+
+packet PeriodicInquiryModeComplete : CommandComplete (command_op_code = PERIODIC_INQUIRY_MODE) {
+  status : ErrorCode,
+}
+
+test PeriodicInquiryModeComplete {
+  "\x0e\x04\x01\x03\x04\x00",
+}
+
+packet ExitPeriodicInquiryMode : Command (op_code = EXIT_PERIODIC_INQUIRY_MODE) {
+}
+
+test ExitPeriodicInquiryMode {
+  "\x04\x04\x00",
+}
+
+packet ExitPeriodicInquiryModeComplete : CommandComplete (command_op_code = EXIT_PERIODIC_INQUIRY_MODE) {
+  status : ErrorCode,
+}
+
+test ExitPeriodicInquiryModeComplete {
+  "\x0e\x04\x01\x04\x04\x00",
+}
+
+enum PageScanRepetitionMode : 8 {
+  R0 = 0x00,
+  R1 = 0x01,
+  R2 = 0x02,
+}
+
+enum ClockOffsetValid : 1 {
+  INVALID = 0,
+  VALID = 1,
+}
+
+enum CreateConnectionRoleSwitch : 8 {
+  REMAIN_CENTRAL = 0x00,
+  ALLOW_ROLE_SWITCH = 0x01,
+}
+
+packet CreateConnection : Command (op_code = CREATE_CONNECTION) {
+  bd_addr : Address,
+  packet_type : 16,
+  page_scan_repetition_mode : PageScanRepetitionMode,
+  _reserved_ : 8,
+  clock_offset : 15,
+  clock_offset_valid : ClockOffsetValid,
+  allow_role_switch : CreateConnectionRoleSwitch,
+}
+
+packet CreateConnectionStatus : CommandStatus (command_op_code = CREATE_CONNECTION) {
+}
+
+enum DisconnectReason : 8 {
+  AUTHENTICATION_FAILURE = 0x05,
+  REMOTE_USER_TERMINATED_CONNECTION = 0x13,
+  REMOTE_DEVICE_TERMINATED_CONNECTION_LOW_RESOURCES = 0x14,
+  REMOTE_DEVICE_TERMINATED_CONNECTION_POWER_OFF = 0x15,
+  UNSUPPORTED_REMOTE_FEATURE = 0x1A,
+  PAIRING_WITH_UNIT_KEY_NOT_SUPPORTED = 0x29,
+  UNACCEPTABLE_CONNECTION_PARAMETERS = 0x3B,
+}
+
+packet Disconnect : Command (op_code = DISCONNECT) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  reason : DisconnectReason,
+}
+
+packet DisconnectStatus : CommandStatus (command_op_code = DISCONNECT) {
+}
+
+packet AddScoConnection : Command (op_code = ADD_SCO_CONNECTION) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  packet_type : 16,
+}
+
+packet AddScoConnectionStatus : CommandStatus (command_op_code = ADD_SCO_CONNECTION) {
+}
+
+packet CreateConnectionCancel : Command (op_code = CREATE_CONNECTION_CANCEL) {
+  bd_addr : Address,
+}
+
+packet CreateConnectionCancelComplete : CommandComplete (command_op_code = CREATE_CONNECTION_CANCEL) {
+  status : ErrorCode,
+  bd_addr : Address,
+}
+
+enum AcceptConnectionRequestRole : 8 {
+  BECOME_CENTRAL = 0x00,
+  REMAIN_PERIPHERAL = 0x01,
+}
+
+packet AcceptConnectionRequest : Command (op_code = ACCEPT_CONNECTION_REQUEST) {
+  bd_addr : Address,
+  role : AcceptConnectionRequestRole,
+}
+
+packet AcceptConnectionRequestStatus : CommandStatus (command_op_code = ACCEPT_CONNECTION_REQUEST) {
+}
+
+enum RejectConnectionReason : 8 {
+  LIMITED_RESOURCES = 0x0D,
+  SECURITY_REASONS = 0x0E,
+  UNACCEPTABLE_BD_ADDR = 0x0F,
+}
+
+packet RejectConnectionRequest : Command (op_code = REJECT_CONNECTION_REQUEST) {
+  bd_addr : Address,
+  reason : RejectConnectionReason,
+}
+
+packet RejectConnectionRequestStatus : CommandStatus (command_op_code = REJECT_CONNECTION_REQUEST) {
+}
+
+packet LinkKeyRequestReply : Command (op_code = LINK_KEY_REQUEST_REPLY) {
+  bd_addr : Address,
+  link_key : 8[16],
+}
+
+packet LinkKeyRequestReplyComplete : CommandComplete (command_op_code = LINK_KEY_REQUEST_REPLY) {
+  status : ErrorCode,
+  bd_addr : Address,
+}
+
+packet LinkKeyRequestNegativeReply : Command (op_code = LINK_KEY_REQUEST_NEGATIVE_REPLY) {
+  bd_addr : Address,
+}
+
+packet LinkKeyRequestNegativeReplyComplete : CommandComplete (command_op_code = LINK_KEY_REQUEST_NEGATIVE_REPLY) {
+  status : ErrorCode,
+  bd_addr : Address,
+}
+
+packet PinCodeRequestReply : Command (op_code = PIN_CODE_REQUEST_REPLY) {
+  bd_addr : Address,
+  pin_code_length : 5, // 0x01 - 0x10
+  _reserved_ : 3,
+  pin_code : 8[16], // string parameter, first octet first
+}
+
+packet PinCodeRequestReplyComplete : CommandComplete (command_op_code = PIN_CODE_REQUEST_REPLY) {
+  status : ErrorCode,
+  bd_addr : Address,
+}
+
+packet PinCodeRequestNegativeReply : Command (op_code = PIN_CODE_REQUEST_NEGATIVE_REPLY) {
+  bd_addr : Address,
+}
+
+packet PinCodeRequestNegativeReplyComplete : CommandComplete (command_op_code = PIN_CODE_REQUEST_NEGATIVE_REPLY) {
+  status : ErrorCode,
+  bd_addr : Address,
+}
+
+packet ChangeConnectionPacketType : Command (op_code = CHANGE_CONNECTION_PACKET_TYPE) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  packet_type : 16,
+}
+
+packet ChangeConnectionPacketTypeStatus : CommandStatus (command_op_code = CHANGE_CONNECTION_PACKET_TYPE) {
+}
+
+packet AuthenticationRequested : Command (op_code = AUTHENTICATION_REQUESTED) {
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet AuthenticationRequestedStatus : CommandStatus (command_op_code = AUTHENTICATION_REQUESTED) {
+}
+
+packet SetConnectionEncryption : Command (op_code = SET_CONNECTION_ENCRYPTION) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  encryption_enable : Enable,
+}
+
+packet SetConnectionEncryptionStatus : CommandStatus (command_op_code = SET_CONNECTION_ENCRYPTION) {
+}
+
+packet ChangeConnectionLinkKey : Command (op_code = CHANGE_CONNECTION_LINK_KEY) {
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet ChangeConnectionLinkKeyStatus : CommandStatus (command_op_code = CHANGE_CONNECTION_LINK_KEY) {
+}
+
+enum KeyFlag : 8 {
+  SEMI_PERMANENT = 0x00,
+  TEMPORARY = 0x01,
+}
+
+packet CentralLinkKey : Command (op_code = CENTRAL_LINK_KEY) {
+  key_flag : KeyFlag,
+}
+
+packet CentralLinkKeyStatus : CommandStatus (command_op_code = CENTRAL_LINK_KEY) {
+}
+
+packet RemoteNameRequest : Command (op_code = REMOTE_NAME_REQUEST) {
+  bd_addr : Address,
+  page_scan_repetition_mode : PageScanRepetitionMode,
+  _reserved_ : 8,
+  clock_offset : 15,
+  clock_offset_valid : ClockOffsetValid,
+}
+
+packet RemoteNameRequestStatus : CommandStatus (command_op_code = REMOTE_NAME_REQUEST) {
+}
+
+packet RemoteNameRequestCancel : Command (op_code = REMOTE_NAME_REQUEST_CANCEL) {
+  bd_addr : Address,
+}
+
+packet RemoteNameRequestCancelComplete : CommandComplete (command_op_code = REMOTE_NAME_REQUEST_CANCEL) {
+  status : ErrorCode,
+  bd_addr : Address,
+}
+
+packet ReadRemoteSupportedFeatures : Command (op_code = READ_REMOTE_SUPPORTED_FEATURES) {
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet ReadRemoteSupportedFeaturesStatus : CommandStatus (command_op_code = READ_REMOTE_SUPPORTED_FEATURES) {
+}
+
+packet ReadRemoteExtendedFeatures : Command (op_code = READ_REMOTE_EXTENDED_FEATURES) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  page_number : 8,
+}
+
+packet ReadRemoteExtendedFeaturesStatus : CommandStatus (command_op_code = READ_REMOTE_EXTENDED_FEATURES) {
+}
+
+packet ReadRemoteVersionInformation : Command (op_code = READ_REMOTE_VERSION_INFORMATION) {
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet ReadRemoteVersionInformationStatus : CommandStatus (command_op_code = READ_REMOTE_VERSION_INFORMATION) {
+}
+
+packet ReadClockOffset : Command (op_code = READ_CLOCK_OFFSET) {
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet ReadClockOffsetStatus : CommandStatus (command_op_code = READ_CLOCK_OFFSET) {
+}
+
+packet ReadLmpHandle : Command (op_code = READ_LMP_HANDLE) {
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet ReadLmpHandleComplete : CommandComplete (command_op_code = READ_LMP_HANDLE) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+  lmp_handle : 8,
+  _reserved_ : 32,
+}
+
+enum SynchronousPacketTypeBits : 16 {
+  HV1_ALLOWED = 0x0001,
+  HV2_ALLOWED = 0x0002,
+  HV3_ALLOWED = 0x0004,
+  EV3_ALLOWED = 0x0008,
+  EV4_ALLOWED = 0x0010,
+  EV5_ALLOWED = 0x0020,
+  NO_2_EV3_ALLOWED = 0x0040,
+  NO_3_EV3_ALLOWED = 0x0080,
+  NO_2_EV5_ALLOWED = 0x0100,
+  NO_3_EV5_ALLOWED = 0x0200,
+}
+
+enum RetransmissionEffort : 8 {
+  NO_RETRANSMISSION = 0x00,
+  OPTIMIZED_FOR_POWER = 0x01,
+  OPTIMIZED_FOR_LINK_QUALITY = 0x02,
+  DO_NOT_CARE = 0xFF,
+}
+
+packet SetupSynchronousConnection : Command (op_code = SETUP_SYNCHRONOUS_CONNECTION) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  transmit_bandwidth : 32,
+  receive_bandwidth : 32,
+  max_latency : 16, // 0-3 reserved, 0xFFFF = don't care
+  voice_setting : 10,
+  _reserved_ : 6,
+  retransmission_effort : RetransmissionEffort,
+  packet_type : 16, // See SynchronousPacketTypeBits
+}
+
+packet SetupSynchronousConnectionStatus : CommandStatus (command_op_code = SETUP_SYNCHRONOUS_CONNECTION) {
+}
+
+packet AcceptSynchronousConnection : Command (op_code = ACCEPT_SYNCHRONOUS_CONNECTION) {
+  bd_addr : Address,
+  transmit_bandwidth : 32,
+  receive_bandwidth : 32,
+  max_latency : 16, // 0-3 reserved, 0xFFFF = don't care
+  voice_setting : 10,
+  _reserved_ : 6,
+  retransmission_effort : RetransmissionEffort,
+  packet_type : 16, // See SynchronousPacketTypeBits
+}
+
+packet AcceptSynchronousConnectionStatus : CommandStatus (command_op_code = ACCEPT_SYNCHRONOUS_CONNECTION) {
+}
+
+packet RejectSynchronousConnection : Command (op_code = REJECT_SYNCHRONOUS_CONNECTION) {
+  bd_addr : Address,
+  reason : RejectConnectionReason,
+}
+
+packet RejectSynchronousConnectionStatus : CommandStatus (command_op_code = REJECT_SYNCHRONOUS_CONNECTION) {
+}
+
+enum IoCapability : 8 {
+  DISPLAY_ONLY = 0x00,
+  DISPLAY_YES_NO = 0x01,
+  KEYBOARD_ONLY = 0x02,
+  NO_INPUT_NO_OUTPUT = 0x03,
+}
+
+enum OobDataPresent : 8 {
+  NOT_PRESENT = 0x00,
+  P_192_PRESENT = 0x01,
+  P_256_PRESENT = 0x02,
+  P_192_AND_256_PRESENT = 0x03,
+}
+
+enum AuthenticationRequirements : 8 {
+  NO_BONDING = 0x00,
+  NO_BONDING_MITM_PROTECTION = 0x01,
+  DEDICATED_BONDING = 0x02,
+  DEDICATED_BONDING_MITM_PROTECTION = 0x03,
+  GENERAL_BONDING = 0x04,
+  GENERAL_BONDING_MITM_PROTECTION = 0x05,
+}
+
+packet IoCapabilityRequestReply : Command (op_code = IO_CAPABILITY_REQUEST_REPLY) {
+  bd_addr : Address,
+  io_capability : IoCapability,
+  oob_present : OobDataPresent,
+  authentication_requirements : AuthenticationRequirements,
+}
+
+packet IoCapabilityRequestReplyComplete : CommandComplete (command_op_code = IO_CAPABILITY_REQUEST_REPLY) {
+  status : ErrorCode,
+  bd_addr : Address,
+}
+
+packet UserConfirmationRequestReply : Command (op_code = USER_CONFIRMATION_REQUEST_REPLY) {
+  bd_addr : Address,
+}
+
+packet UserConfirmationRequestReplyComplete : CommandComplete (command_op_code = USER_CONFIRMATION_REQUEST_REPLY) {
+  status : ErrorCode,
+  bd_addr : Address,
+}
+
+packet UserConfirmationRequestNegativeReply : Command (op_code = USER_CONFIRMATION_REQUEST_NEGATIVE_REPLY) {
+  bd_addr : Address,
+}
+
+packet UserConfirmationRequestNegativeReplyComplete : CommandComplete (command_op_code = USER_CONFIRMATION_REQUEST_NEGATIVE_REPLY) {
+  status : ErrorCode,
+  bd_addr : Address,
+}
+
+packet UserPasskeyRequestReply : Command (op_code = USER_PASSKEY_REQUEST_REPLY) {
+  bd_addr : Address,
+  numeric_value : 32, // 000000-999999 decimal or 0x0-0xF423F
+}
+
+packet UserPasskeyRequestReplyComplete : CommandComplete (command_op_code = USER_PASSKEY_REQUEST_REPLY) {
+  status : ErrorCode,
+  bd_addr : Address,
+}
+
+packet UserPasskeyRequestNegativeReply : Command (op_code = USER_PASSKEY_REQUEST_NEGATIVE_REPLY) {
+  bd_addr : Address,
+}
+
+packet UserPasskeyRequestNegativeReplyComplete : CommandComplete (command_op_code = USER_PASSKEY_REQUEST_NEGATIVE_REPLY) {
+  status : ErrorCode,
+  bd_addr : Address,
+}
+
+packet RemoteOobDataRequestReply : Command (op_code = REMOTE_OOB_DATA_REQUEST_REPLY) {
+  bd_addr : Address,
+  c : 8[16],
+  r : 8[16],
+}
+
+packet RemoteOobDataRequestReplyComplete : CommandComplete (command_op_code = REMOTE_OOB_DATA_REQUEST_REPLY) {
+  status : ErrorCode,
+  bd_addr : Address,
+}
+
+packet RemoteOobDataRequestNegativeReply : Command (op_code = REMOTE_OOB_DATA_REQUEST_NEGATIVE_REPLY) {
+  bd_addr : Address,
+}
+
+packet RemoteOobDataRequestNegativeReplyComplete : CommandComplete (command_op_code = REMOTE_OOB_DATA_REQUEST_NEGATIVE_REPLY) {
+  status : ErrorCode,
+  bd_addr : Address,
+}
+
+packet IoCapabilityRequestNegativeReply : Command (op_code = IO_CAPABILITY_REQUEST_NEGATIVE_REPLY) {
+  bd_addr : Address,
+  reason : ErrorCode,
+}
+
+packet IoCapabilityRequestNegativeReplyComplete : CommandComplete (command_op_code = IO_CAPABILITY_REQUEST_NEGATIVE_REPLY) {
+  status : ErrorCode,
+  bd_addr : Address,
+}
+
+enum ScoCodingFormatValues : 8 {
+  ULAW_LONG = 0x00,
+  ALAW_LONG = 0x01,
+  CVSD = 0x02,
+  TRANSPARENT = 0x03,
+  LINEAR_PCM = 0x04,
+  MSBC = 0x05,
+  LC3 = 0x06,
+  VENDOR_SPECIFIC = 0xFF,
+}
+
+struct ScoCodingFormat {
+  coding_format : ScoCodingFormatValues,
+  company_id : 16,
+  vendor_specific_codec_id : 16,
+}
+
+enum ScoPcmDataFormat : 8 {
+  NOT_USED = 0x00,
+  ONES_COMPLEMENT = 0x01,
+  TWOS_COMPLEMENT = 0x02,
+  SIGN_MAGNITUDE = 0x03,
+  UNSIGNED = 0x04,
+}
+
+enum ScoDataPath : 8 {
+  HCI = 0x00,
+  // 0x01 to 0xFE are Logical_Channel_Number.
+  // The meaning of the logical channels will be vendor specific.
+  // In GD and legacy Android Bluetooth stack, we use channel 0x01 for hardware
+  // offloaded SCO encoding
+  GD_PCM = 0x01,
+  AUDIO_TEST_MODE = 0xFF,
+}
+
+packet EnhancedSetupSynchronousConnection : Command (op_code = ENHANCED_SETUP_SYNCHRONOUS_CONNECTION) {
+  connection_handle: 12,
+  _reserved_ : 4,
+  // Next two items
+  // [0x00000000, 0xFFFFFFFE] Bandwidth in octets per second.
+  // [0xFFFFFFFF]: Don't care
+  transmit_bandwidth : 32,
+  receive_bandwidth : 32,
+  transmit_coding_format : ScoCodingFormat,
+  receive_coding_format : ScoCodingFormat,
+  // Next two items
+  // [0x0001, 0xFFFF]: the actual size of the over-the-air encoded frame in
+  //                   octets.
+  transmit_codec_frame_size : 16,
+  receive_codec_frame_size : 16,
+  // Next two items
+  // Host to Controller nominal data rate in octets per second.
+  input_bandwidth : 32,
+  output_bandwidth : 32,
+  input_coding_format : ScoCodingFormat,
+  output_coding_format : ScoCodingFormat,
+  // Next two items
+  // Size, in bits, of the sample or framed data
+  input_coded_data_bits : 16,
+  output_coded_data_bits : 16,
+  input_pcm_data_format : ScoPcmDataFormat,
+  output_pcm_data_format : ScoPcmDataFormat,
+  // Next two items
+  // The number of bit positions within an audio sample that the MSB of the
+  // sample is away from starting at the MSB of the data.
+  input_pcm_sample_payload_msb_position : 8,
+  output_pcm_sample_payload_msb_position : 8,
+  input_data_path : ScoDataPath,
+  output_data_path : ScoDataPath,
+  // Next two items
+  // [1, 255] The number of bits in each unit of data received from the Host
+  //          over the audio data transport.
+  // [0] Not applicable (implied by the choice of audio data transport)
+  input_transport_unit_bits : 8,
+  output_transport_unit_bits : 8,
+  // [0x0004, 0xFFFE]: in milliseconds
+  //     Upper limit represent the sum of the synchronous interval and the size
+  //     of the eSCO window, where the eSCO window is reserved slots plus the
+  //     retransmission window
+  // [0xFFFF]: don't care
+  max_latency: 16,
+  packet_type : 16, // See SynchronousPacketTypeBits
+  retransmission_effort : RetransmissionEffort,
+}
+
+test EnhancedSetupSynchronousConnection {
+  "\x3d\x04\x3b\x02\x00\x40\x1f\x00\x00\x40\x1f\x00\x00\x05\x00\x00\x00\x00\x05\x00\x00\x00\x00\x3c\x00\x3c\x00\x00\x7d\x00\x00\x00\x7d\x00\x00\x04\x00\x00\x00\x00\x04\x00\x00\x00\x00\x10\x00\x10\x00\x02\x02\x00\x00\x01\x01\x00\x00\x0d\x00\x88\x03\x02",
+}
+
+packet EnhancedSetupSynchronousConnectionStatus : CommandStatus (command_op_code = ENHANCED_SETUP_SYNCHRONOUS_CONNECTION) {
+}
+
+packet EnhancedAcceptSynchronousConnection : Command (op_code = ENHANCED_ACCEPT_SYNCHRONOUS_CONNECTION) {
+  bd_addr : Address,
+  // Next two items
+  // [0x00000000, 0xFFFFFFFE] Bandwidth in octets per second.
+  // [0xFFFFFFFF]: Don't care
+  transmit_bandwidth : 32,
+  receive_bandwidth : 32,
+  transmit_coding_format : ScoCodingFormat,
+  receive_coding_format : ScoCodingFormat,
+  // Next two items
+  // [0x0001, 0xFFFF]: the actual size of the over-the-air encoded frame in
+  //                   octets.
+  transmit_codec_frame_size : 16,
+  receive_codec_frame_size : 16,
+  // Next two items
+  // Host to Controller nominal data rate in octets per second.
+  input_bandwidth : 32,
+  output_bandwidth : 32,
+  input_coding_format : ScoCodingFormat,
+  output_coding_format : ScoCodingFormat,
+  // Next two items
+  // Size, in bits, of the sample or framed data
+  input_coded_data_bits : 16,
+  output_coded_data_bits : 16,
+  input_pcm_data_format : ScoPcmDataFormat,
+  output_pcm_data_format : ScoPcmDataFormat,
+  // Next two items
+  // The number of bit positions within an audio sample that the MSB of the
+  // sample is away from starting at the MSB of the data.
+  input_pcm_sample_payload_msb_position : 8,
+  output_pcm_sample_payload_msb_position : 8,
+  input_data_path : ScoDataPath,
+  output_data_path : ScoDataPath,
+  // Next two items
+  // [1, 255] The number of bits in each unit of data received from the Host
+  //          over the audio data transport.
+  // [0] Not applicable (implied by the choice of audio data transport)
+  input_transport_unit_bits : 8,
+  output_transport_unit_bits : 8,
+  // [0x0004, 0xFFFE]: in milliseconds
+  //     Upper limit represent the sum of the synchronous interval and the size
+  //     of the eSCO window, where the eSCO window is reserved slots plus the
+  //     retransmission window
+  // [0xFFFF]: don't care
+  max_latency : 16,
+  packet_type : 16, // See SynchronousPacketTypeBits
+  retransmission_effort : RetransmissionEffort,
+}
+
+packet EnhancedAcceptSynchronousConnectionStatus : CommandStatus (command_op_code = ENHANCED_ACCEPT_SYNCHRONOUS_CONNECTION) {
+}
+
+packet RemoteOobExtendedDataRequestReply : Command (op_code = REMOTE_OOB_EXTENDED_DATA_REQUEST_REPLY) {
+  bd_addr : Address,
+  c_192 : 8[16],
+  r_192 : 8[16],
+  c_256 : 8[16],
+  r_256 : 8[16],
+}
+
+packet RemoteOobExtendedDataRequestReplyComplete : CommandComplete (command_op_code = REMOTE_OOB_EXTENDED_DATA_REQUEST_REPLY) {
+  status : ErrorCode,
+  bd_addr : Address,
+}
+
+
+  // LINK_POLICY
+packet HoldMode : Command (op_code = HOLD_MODE) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  hold_mode_max_interval: 16, // 0x0002-0xFFFE (1.25ms-40.9s)
+  hold_mode_min_interval: 16, // 0x0002-0xFFFE (1.25ms-40.9s)
+}
+
+packet HoldModeStatus : CommandStatus (command_op_code = HOLD_MODE) {
+}
+
+
+packet SniffMode : Command (op_code = SNIFF_MODE) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  sniff_max_interval: 16, // 0x0002-0xFFFE (1.25ms-40.9s)
+  sniff_min_interval: 16, // 0x0002-0xFFFE (1.25ms-40.9s)
+  sniff_attempt: 16, // 0x0001-0x7FFF (1.25ms-40.9s)
+  sniff_timeout: 16, // 0x0000-0x7FFF (0ms-40.9s)
+}
+
+packet SniffModeStatus : CommandStatus (command_op_code = SNIFF_MODE) {
+}
+
+
+packet ExitSniffMode : Command (op_code = EXIT_SNIFF_MODE) {
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet ExitSniffModeStatus : CommandStatus (command_op_code = EXIT_SNIFF_MODE) {
+}
+
+enum ServiceType : 8 {
+  NO_TRAFFIC = 0x00,
+  BEST_EFFORT = 0x01,
+  GUARANTEED = 0x02,
+}
+
+packet QosSetup : Command (op_code = QOS_SETUP) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  _reserved_ : 8,
+  service_type : ServiceType,
+  token_rate : 32, // Octets/s
+  peak_bandwidth : 32, // Octets/s
+  latency : 32, // Octets/s
+  delay_variation : 32, // microseconds
+}
+
+packet QosSetupStatus : CommandStatus (command_op_code = QOS_SETUP) {
+}
+
+packet RoleDiscovery : Command (op_code = ROLE_DISCOVERY) {
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+enum Role : 8 {
+  CENTRAL = 0x00,
+  PERIPHERAL = 0x01,
+}
+
+packet RoleDiscoveryComplete : CommandComplete (command_op_code = ROLE_DISCOVERY) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+  current_role : Role,
+}
+
+packet SwitchRole : Command (op_code = SWITCH_ROLE) {
+  bd_addr : Address,
+  role : Role,
+}
+
+packet SwitchRoleStatus : CommandStatus (command_op_code = SWITCH_ROLE) {
+}
+
+
+packet ReadLinkPolicySettings : Command (op_code = READ_LINK_POLICY_SETTINGS) {
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+enum LinkPolicy : 16 {
+  ENABLE_ROLE_SWITCH = 0x01,
+  ENABLE_HOLD_MODE = 0x02,
+  ENABLE_SNIFF_MODE = 0x04,
+  ENABLE_PARK_MODE = 0x08, // deprecated after 5.0
+}
+
+packet ReadLinkPolicySettingsComplete : CommandComplete (command_op_code = READ_LINK_POLICY_SETTINGS) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+  link_policy_settings : 16,
+}
+
+packet WriteLinkPolicySettings : Command (op_code = WRITE_LINK_POLICY_SETTINGS) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  link_policy_settings : 16,
+}
+
+packet WriteLinkPolicySettingsComplete : CommandComplete (command_op_code = WRITE_LINK_POLICY_SETTINGS) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet ReadDefaultLinkPolicySettings : Command (op_code = READ_DEFAULT_LINK_POLICY_SETTINGS) {
+}
+
+packet ReadDefaultLinkPolicySettingsComplete : CommandComplete (command_op_code = READ_DEFAULT_LINK_POLICY_SETTINGS) {
+  status : ErrorCode,
+  default_link_policy_settings : 16,
+}
+
+packet WriteDefaultLinkPolicySettings : Command (op_code = WRITE_DEFAULT_LINK_POLICY_SETTINGS) {
+  default_link_policy_settings : 16,
+}
+
+packet WriteDefaultLinkPolicySettingsComplete : CommandComplete (command_op_code = WRITE_DEFAULT_LINK_POLICY_SETTINGS) {
+  status : ErrorCode,
+}
+
+enum FlowDirection : 8 {
+  OUTGOING_FLOW = 0x00,
+  INCOMING_FLOW = 0x01,
+}
+
+packet FlowSpecification : Command (op_code = FLOW_SPECIFICATION) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  _reserved_ : 8,
+  flow_direction : FlowDirection,
+  service_type : ServiceType,
+  token_rate : 32, // Octets/s
+  token_bucket_size : 32,
+  peak_bandwidth : 32, // Octets/s
+  access_latency : 32, // Octets/s
+}
+
+packet FlowSpecificationStatus : CommandStatus (command_op_code = FLOW_SPECIFICATION) {
+}
+
+packet SniffSubrating : Command (op_code = SNIFF_SUBRATING) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  maximum_latency : 16,  // 0x0002-0xFFFE (1.25ms-40.9s)
+  minimum_remote_timeout : 16, // 0x0000-0xFFFE (0-40.9s)
+  minimum_local_timeout: 16, // 0x0000-0xFFFE (0-40.9s)
+}
+
+packet SniffSubratingComplete : CommandComplete (command_op_code = SNIFF_SUBRATING) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+  // CONTROLLER_AND_BASEBAND
+packet SetEventMask : Command (op_code = SET_EVENT_MASK) {
+  event_mask : 64,
+}
+
+packet SetEventMaskComplete : CommandComplete (command_op_code = SET_EVENT_MASK) {
+  status : ErrorCode,
+}
+
+packet Reset : Command (op_code = RESET) {
+}
+
+test Reset {
+  "\x03\x0c\x00",
+}
+
+packet ResetComplete : CommandComplete (command_op_code = RESET) {
+  status : ErrorCode,
+}
+
+test ResetComplete {
+  "\x0e\x04\x01\x03\x0c\x00",
+  "\x0e\x04\x01\x03\x0c\x01", // unknown command
+}
+
+enum FilterType : 8 {
+  CLEAR_ALL_FILTERS = 0x00,
+  INQUIRY_RESULT = 0x01,
+  CONNECTION_SETUP = 0x02,
+}
+
+packet SetEventFilter : Command (op_code = SET_EVENT_FILTER) {
+  filter_type : FilterType,
+  _body_,
+}
+
+packet SetEventFilterComplete : CommandComplete (command_op_code = SET_EVENT_FILTER) {
+  status : ErrorCode,
+}
+
+packet SetEventFilterClearAll : SetEventFilter (filter_type = CLEAR_ALL_FILTERS) {
+}
+
+enum FilterConditionType : 8 {
+  ALL_DEVICES = 0x00,
+  CLASS_OF_DEVICE = 0x01,
+  ADDRESS = 0x02,
+}
+
+packet SetEventFilterInquiryResult : SetEventFilter (filter_type = INQUIRY_RESULT) {
+  filter_condition_type : FilterConditionType,
+  _body_,
+}
+
+packet SetEventFilterInquiryResultAllDevices : SetEventFilterInquiryResult (filter_condition_type = ALL_DEVICES) {
+}
+
+packet SetEventFilterInquiryResultClassOfDevice : SetEventFilterInquiryResult (filter_condition_type = CLASS_OF_DEVICE) {
+  class_of_device : 24,
+  class_of_device_mask : 24,
+}
+
+packet SetEventFilterInquiryResultAddress : SetEventFilterInquiryResult (filter_condition_type = ADDRESS) {
+  address : Address,
+}
+
+packet SetEventFilterConnectionSetup : SetEventFilter (filter_type = CONNECTION_SETUP) {
+  filter_condition_type : FilterConditionType,
+  _body_,
+}
+
+enum AutoAcceptFlag : 8 {
+  AUTO_ACCEPT_OFF = 0x01,
+  AUTO_ACCEPT_ON_ROLE_SWITCH_DISABLED = 0x02,
+  AUTO_ACCEPT_ON_ROLE_SWITCH_ENABLED = 0x03,
+}
+
+packet SetEventFilterConnectionSetupAllDevices : SetEventFilterConnectionSetup (filter_condition_type = ALL_DEVICES) {
+  auto_accept_flag : AutoAcceptFlag,
+}
+
+packet SetEventFilterConnectionSetupClassOfDevice : SetEventFilterConnectionSetup (filter_condition_type = CLASS_OF_DEVICE) {
+  class_of_device : 24,
+  class_of_device_mask : 24,
+  auto_accept_flag : AutoAcceptFlag,
+}
+
+packet SetEventFilterConnectionSetupAddress : SetEventFilterConnectionSetup (filter_condition_type = ADDRESS) {
+  address : Address,
+  auto_accept_flag : AutoAcceptFlag,
+}
+
+packet Flush : Command (op_code = FLUSH) {
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet FlushComplete : CommandComplete (command_op_code = FLUSH) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+enum PinType : 8 {
+  VARIABLE = 0,
+  FIXED = 1,
+}
+
+packet ReadPinType : Command (op_code = READ_PIN_TYPE) {
+}
+
+packet ReadPinTypeComplete : CommandComplete (command_op_code = READ_PIN_TYPE) {
+  status : ErrorCode,
+  pin_type : PinType,
+}
+
+packet WritePinType : Command (op_code = WRITE_PIN_TYPE) {
+  pin_type : PinType,
+}
+
+packet WritePinTypeComplete : CommandComplete (command_op_code = WRITE_PIN_TYPE) {
+  status : ErrorCode,
+}
+
+enum ReadStoredLinkKeyReadAllFlag : 8 {
+  SPECIFIED_BD_ADDR = 0x00,
+  ALL = 0x01,
+}
+
+packet ReadStoredLinkKey : Command (op_code = READ_STORED_LINK_KEY) {
+  bd_addr : Address,
+  read_all_flag : ReadStoredLinkKeyReadAllFlag,
+}
+
+packet ReadStoredLinkKeyComplete : CommandComplete (command_op_code = READ_STORED_LINK_KEY) {
+  status : ErrorCode,
+  max_num_keys : 16,
+  num_keys_read : 16,
+}
+
+struct KeyAndAddress {
+  address : Address,
+  link_key : 8[16],
+}
+
+packet WriteStoredLinkKey : Command (op_code = WRITE_STORED_LINK_KEY) {
+  _count_(keys_to_write) : 8, // 0x01-0x0B
+  keys_to_write : KeyAndAddress[],
+}
+
+packet WriteStoredLinkKeyComplete : CommandComplete (command_op_code = WRITE_STORED_LINK_KEY) {
+  status : ErrorCode,
+  num_keys_written : 8,
+}
+
+enum DeleteStoredLinkKeyDeleteAllFlag : 8 {
+  SPECIFIED_BD_ADDR = 0x00,
+  ALL = 0x01,
+}
+
+packet DeleteStoredLinkKey : Command (op_code = DELETE_STORED_LINK_KEY) {
+  bd_addr : Address,
+  delete_all_flag : DeleteStoredLinkKeyDeleteAllFlag,
+}
+
+packet DeleteStoredLinkKeyComplete : CommandComplete (command_op_code = DELETE_STORED_LINK_KEY) {
+  status : ErrorCode,
+  num_keys_deleted : 16,
+}
+
+packet WriteLocalName : Command (op_code = WRITE_LOCAL_NAME) {
+  local_name : 8[248], // Null-terminated UTF-8 encoded name
+}
+
+packet WriteLocalNameComplete : CommandComplete (command_op_code = WRITE_LOCAL_NAME) {
+  status : ErrorCode,
+}
+
+packet ReadLocalName : Command (op_code = READ_LOCAL_NAME) {
+}
+
+packet ReadLocalNameComplete : CommandComplete (command_op_code = READ_LOCAL_NAME) {
+  status : ErrorCode,
+  local_name : 8[248], // Null-terminated UTF-8 encoded name
+}
+
+packet ReadConnectionAcceptTimeout : Command (op_code = READ_CONNECTION_ACCEPT_TIMEOUT) {
+}
+
+packet ReadConnectionAcceptTimeoutComplete : CommandComplete (command_op_code = READ_CONNECTION_ACCEPT_TIMEOUT) {
+  status : ErrorCode,
+  conn_accept_timeout : 16, // 0x0001 to 0xB540 (N * 0.625 ms) 0.625 ms to 29 s
+}
+
+packet WriteConnectionAcceptTimeout : Command (op_code = WRITE_CONNECTION_ACCEPT_TIMEOUT) {
+  conn_accept_timeout : 16, // 0x0001 to 0xB540 (N * 0.625 ms) 0.625 ms to 29 s, Default 0x1FA0, 5.06s
+}
+
+packet WriteConnectionAcceptTimeoutComplete : CommandComplete (command_op_code = WRITE_CONNECTION_ACCEPT_TIMEOUT) {
+  status : ErrorCode,
+}
+
+packet ReadPageTimeout : Command (op_code = READ_PAGE_TIMEOUT) {
+}
+
+test ReadPageTimeout {
+  "\x17\x0c\x00",
+}
+
+packet ReadPageTimeoutComplete : CommandComplete (command_op_code = READ_PAGE_TIMEOUT) {
+  status : ErrorCode,
+  page_timeout : 16,
+}
+
+test ReadPageTimeoutComplete {
+  "\x0e\x06\x01\x17\x0c\x00\x11\x22",
+}
+
+packet WritePageTimeout : Command (op_code = WRITE_PAGE_TIMEOUT) {
+  page_timeout : 16,
+}
+
+test WritePageTimeout {
+  "\x18\x0c\x02\x00\x20",
+}
+
+packet WritePageTimeoutComplete : CommandComplete (command_op_code = WRITE_PAGE_TIMEOUT) {
+  status : ErrorCode,
+}
+
+test WritePageTimeoutComplete {
+  "\x0e\x04\x01\x18\x0c\x00",
+}
+
+enum ScanEnable : 8 {
+  NO_SCANS = 0x00,
+  INQUIRY_SCAN_ONLY = 0x01,
+  PAGE_SCAN_ONLY = 0x02,
+  INQUIRY_AND_PAGE_SCAN = 0x03,
+}
+
+packet ReadScanEnable : Command (op_code = READ_SCAN_ENABLE) {
+}
+
+packet ReadScanEnableComplete : CommandComplete (command_op_code = READ_SCAN_ENABLE) {
+  status : ErrorCode,
+  scan_enable : ScanEnable,
+}
+
+packet WriteScanEnable : Command (op_code = WRITE_SCAN_ENABLE) {
+  scan_enable : ScanEnable,
+}
+
+packet WriteScanEnableComplete : CommandComplete (command_op_code = WRITE_SCAN_ENABLE) {
+  status : ErrorCode,
+}
+
+packet ReadPageScanActivity : Command (op_code = READ_PAGE_SCAN_ACTIVITY) {
+}
+
+packet ReadPageScanActivityComplete : CommandComplete (command_op_code = READ_PAGE_SCAN_ACTIVITY) {
+  status : ErrorCode,
+  page_scan_interval : 16, // Range: 0x0012 to 0x1000; only even values are valid * 0x625 ms
+  page_scan_window : 16, // 0x0011 to PageScanInterval
+}
+
+packet WritePageScanActivity : Command (op_code = WRITE_PAGE_SCAN_ACTIVITY) {
+  page_scan_interval : 16, // Range: 0x0012 to 0x1000; only even values are valid * 0x625 ms
+  page_scan_window : 16, // 0x0011 to PageScanInterval
+}
+
+packet WritePageScanActivityComplete : CommandComplete (command_op_code = WRITE_PAGE_SCAN_ACTIVITY) {
+  status : ErrorCode,
+}
+
+packet ReadInquiryScanActivity : Command (op_code = READ_INQUIRY_SCAN_ACTIVITY) {
+}
+
+test ReadInquiryScanActivity {
+  "\x1d\x0c\x00",
+}
+
+packet ReadInquiryScanActivityComplete : CommandComplete (command_op_code = READ_INQUIRY_SCAN_ACTIVITY) {
+  status : ErrorCode,
+  inquiry_scan_interval : 16, // Range: 0x0012 to 0x1000; only even values are valid * 0x625 ms
+  inquiry_scan_window : 16, // Range: 0x0011 to 0x1000
+}
+
+test ReadInquiryScanActivityComplete {
+  "\x0e\x08\x01\x1d\x0c\x00\xaa\xbb\xcc\xdd",
+}
+
+packet WriteInquiryScanActivity : Command (op_code = WRITE_INQUIRY_SCAN_ACTIVITY) {
+  inquiry_scan_interval : 16, // Range: 0x0012 to 0x1000; only even values are valid * 0x625 ms
+  inquiry_scan_window : 16, // Range: 0x0011 to 0x1000
+}
+
+test WriteInquiryScanActivity {
+  "\x1e\x0c\x04\x00\x08\x12\x00",
+}
+
+packet WriteInquiryScanActivityComplete : CommandComplete (command_op_code = WRITE_INQUIRY_SCAN_ACTIVITY) {
+  status : ErrorCode,
+}
+
+test WriteInquiryScanActivityComplete {
+  "\x0e\x04\x01\x1e\x0c\x00",
+}
+
+enum AuthenticationEnable : 8 {
+  NOT_REQUIRED = 0x00,
+  REQUIRED = 0x01,
+}
+
+packet ReadAuthenticationEnable : Command (op_code = READ_AUTHENTICATION_ENABLE) {
+}
+
+packet ReadAuthenticationEnableComplete : CommandComplete (command_op_code = READ_AUTHENTICATION_ENABLE) {
+  status : ErrorCode,
+  authentication_enable : AuthenticationEnable,
+}
+
+packet WriteAuthenticationEnable : Command (op_code = WRITE_AUTHENTICATION_ENABLE) {
+  authentication_enable : AuthenticationEnable,
+}
+
+packet WriteAuthenticationEnableComplete : CommandComplete (command_op_code = WRITE_AUTHENTICATION_ENABLE) {
+  status : ErrorCode,
+}
+
+packet ReadClassOfDevice : Command (op_code = READ_CLASS_OF_DEVICE) {
+}
+
+packet ReadClassOfDeviceComplete : CommandComplete (command_op_code = READ_CLASS_OF_DEVICE) {
+  status : ErrorCode,
+  class_of_device : 24,
+}
+
+packet WriteClassOfDevice : Command (op_code = WRITE_CLASS_OF_DEVICE) {
+  class_of_device : 24,
+}
+
+packet WriteClassOfDeviceComplete : CommandComplete (command_op_code = WRITE_CLASS_OF_DEVICE) {
+  status : ErrorCode,
+}
+
+packet ReadVoiceSetting : Command (op_code = READ_VOICE_SETTING) {
+}
+
+packet ReadVoiceSettingComplete : CommandComplete (command_op_code = READ_VOICE_SETTING) {
+  status : ErrorCode,
+  voice_setting : 10,
+  _reserved_ : 6,
+}
+
+packet WriteVoiceSetting : Command (op_code = WRITE_VOICE_SETTING) {
+  voice_setting : 10,
+  _reserved_ : 6,
+}
+
+packet WriteVoiceSettingComplete : CommandComplete (command_op_code = WRITE_VOICE_SETTING) {
+  status : ErrorCode,
+}
+
+packet ReadAutomaticFlushTimeout : Command (op_code = READ_AUTOMATIC_FLUSH_TIMEOUT) {
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet ReadAutomaticFlushTimeoutComplete : CommandComplete (command_op_code = READ_AUTOMATIC_FLUSH_TIMEOUT) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+  flush_timeout : 16,
+}
+
+packet WriteAutomaticFlushTimeout : Command (op_code = WRITE_AUTOMATIC_FLUSH_TIMEOUT) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  flush_timeout : 16, // 0x0000-0x07FF Default 0x0000 (No Automatic Flush)
+}
+
+packet WriteAutomaticFlushTimeoutComplete : CommandComplete (command_op_code = WRITE_AUTOMATIC_FLUSH_TIMEOUT) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet ReadNumBroadcastRetransmits : Command (op_code = READ_NUM_BROADCAST_RETRANSMITS) {
+}
+
+packet ReadNumBroadcastRetransmitsComplete : CommandComplete (command_op_code = READ_NUM_BROADCAST_RETRANSMITS) {
+  status : ErrorCode,
+  num_broadcast_retransmissions : 8,
+}
+
+packet WriteNumBroadcastRetransmits : Command (op_code = WRITE_NUM_BROADCAST_RETRANSMITS) {
+  num_broadcast_retransmissions : 8,
+}
+
+packet WriteNumBroadcastRetransmitsComplete : CommandComplete (command_op_code = WRITE_NUM_BROADCAST_RETRANSMITS) {
+  status : ErrorCode,
+}
+
+enum HoldModeActivity : 8 {
+  MAINTAIN_CURRENT_POWER_STATE = 0x00,
+  SUSPEND_PAGE_SCAN = 0x01,
+  SUSPEND_INQUIRY_SCAN = 0x02,
+  SUSPEND_PERIODIC_INQUIRY = 0x03,
+}
+
+packet ReadHoldModeActivity : Command (op_code = READ_HOLD_MODE_ACTIVITY) {
+}
+
+packet ReadHoldModeActivityComplete : CommandComplete (command_op_code = READ_HOLD_MODE_ACTIVITY) {
+  status : ErrorCode,
+  hold_mode_activity : HoldModeActivity,
+}
+
+packet WriteHoldModeActivity : Command (op_code = WRITE_HOLD_MODE_ACTIVITY) {
+  hold_mode_activity : HoldModeActivity,
+}
+
+packet WriteHoldModeActivityComplete : CommandComplete (command_op_code = WRITE_HOLD_MODE_ACTIVITY) {
+  status : ErrorCode,
+}
+
+enum TransmitPowerLevelType : 8 {
+  CURRENT = 0x00,
+  MAXIMUM = 0x01,
+}
+
+packet ReadTransmitPowerLevel : Command (op_code = READ_TRANSMIT_POWER_LEVEL) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  transmit_power_level_type : TransmitPowerLevelType,
+}
+
+packet ReadTransmitPowerLevelComplete : CommandComplete (command_op_code = READ_TRANSMIT_POWER_LEVEL) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+  transmit_power_level : 8,
+}
+
+packet ReadSynchronousFlowControlEnable : Command (op_code = READ_SYNCHRONOUS_FLOW_CONTROL_ENABLE) {
+}
+
+packet ReadSynchronousFlowControlEnableComplete : CommandComplete (command_op_code = READ_SYNCHRONOUS_FLOW_CONTROL_ENABLE) {
+  status : ErrorCode,
+  enable : Enable,
+}
+
+packet WriteSynchronousFlowControlEnable : Command (op_code = WRITE_SYNCHRONOUS_FLOW_CONTROL_ENABLE) {
+  enable : Enable,
+}
+
+packet WriteSynchronousFlowControlEnableComplete : CommandComplete (command_op_code = WRITE_SYNCHRONOUS_FLOW_CONTROL_ENABLE) {
+  status : ErrorCode,
+}
+
+packet SetControllerToHostFlowControl : Command (op_code = SET_CONTROLLER_TO_HOST_FLOW_CONTROL) {
+  acl : 1,
+  synchronous : 1,
+  _reserved_ : 6,
+}
+
+packet SetControllerToHostFlowControlComplete : CommandComplete (command_op_code = SET_CONTROLLER_TO_HOST_FLOW_CONTROL) {
+  status : ErrorCode,
+}
+
+packet HostBufferSize : Command (op_code = HOST_BUFFER_SIZE) {
+  host_acl_data_packet_length : 16,
+  host_synchronous_data_packet_length : 8,
+  host_total_num_acl_data_packets : 16,
+  host_total_num_synchronous_data_packets : 16,
+}
+
+test HostBufferSize {
+  "\x33\x0c\x07\x9b\x06\xff\x14\x00\x0a\x00",
+}
+
+packet HostBufferSizeComplete : CommandComplete (command_op_code = HOST_BUFFER_SIZE) {
+  status : ErrorCode,
+}
+
+test HostBufferSizeComplete {
+  "\x0e\x04\x01\x33\x0c\x00",
+}
+
+struct CompletedPackets {
+  connection_handle : 12,
+  _reserved_ : 4,
+  host_num_of_completed_packets : 16,
+}
+
+packet HostNumCompletedPackets : Command (op_code = HOST_NUMBER_OF_COMPLETED_PACKETS) {
+  _count_(completed_packets) : 8,
+  completed_packets : CompletedPackets[],
+}
+
+packet HostNumCompletedPacketsError : CommandComplete (command_op_code = HOST_NUMBER_OF_COMPLETED_PACKETS) {
+  error_code : ErrorCode,
+}
+
+packet ReadLinkSupervisionTimeout : Command (op_code = READ_LINK_SUPERVISION_TIMEOUT) {
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet ReadLinkSupervisionTimeoutComplete : CommandComplete (command_op_code = READ_LINK_SUPERVISION_TIMEOUT) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+  link_supervision_timeout : 16, // 0x001-0xFFFF (0.625ms-40.9s)
+}
+
+packet WriteLinkSupervisionTimeout : Command (op_code = WRITE_LINK_SUPERVISION_TIMEOUT) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  link_supervision_timeout : 16, // 0x001-0xFFFF (0.625ms-40.9s)
+}
+
+packet WriteLinkSupervisionTimeoutComplete : CommandComplete (command_op_code = WRITE_LINK_SUPERVISION_TIMEOUT) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet ReadNumberOfSupportedIac : Command (op_code = READ_NUMBER_OF_SUPPORTED_IAC) {
+}
+
+test ReadNumberOfSupportedIac {
+  "\x38\x0c\x00",
+}
+
+packet ReadNumberOfSupportedIacComplete : CommandComplete (command_op_code = READ_NUMBER_OF_SUPPORTED_IAC) {
+  status : ErrorCode,
+  num_support_iac : 8,
+}
+
+test ReadNumberOfSupportedIacComplete {
+  "\x0e\x05\x01\x38\x0c\x00\x99",
+}
+
+packet ReadCurrentIacLap : Command (op_code = READ_CURRENT_IAC_LAP) {
+}
+
+test ReadCurrentIacLap {
+  "\x39\x0c\x00",
+}
+
+packet ReadCurrentIacLapComplete : CommandComplete (command_op_code = READ_CURRENT_IAC_LAP) {
+  status : ErrorCode,
+  _count_(laps_to_read) : 8,
+  laps_to_read : Lap[],
+}
+
+test ReadCurrentIacLapComplete {
+  "\x0e\x0b\x01\x39\x0c\x00\x02\x11\x8b\x9e\x22\x8b\x9e",
+}
+
+packet WriteCurrentIacLap : Command (op_code = WRITE_CURRENT_IAC_LAP) {
+  _count_(laps_to_write) : 8,
+  laps_to_write : Lap[],
+}
+
+test WriteCurrentIacLap {
+  "\x3a\x0c\x07\x02\x11\x8b\x9e\x22\x8b\x9e",
+}
+
+packet WriteCurrentIacLapComplete : CommandComplete (command_op_code = WRITE_CURRENT_IAC_LAP) {
+  status : ErrorCode,
+}
+
+test WriteCurrentIacLapComplete {
+  "\x0e\x04\x01\x3a\x0c\x00",
+}
+
+packet SetAfhHostChannelClassification : Command (op_code = SET_AFH_HOST_CHANNEL_CLASSIFICATION) {
+  afh_host_channel_classification : 8[10],
+}
+
+packet SetAfhHostChannelClassificationComplete : CommandComplete (command_op_code = SET_AFH_HOST_CHANNEL_CLASSIFICATION) {
+  status : ErrorCode,
+}
+
+enum InquiryScanType : 8 {
+  STANDARD = 0x00,
+  INTERLACED = 0x01,
+}
+
+packet ReadInquiryScanType : Command (op_code = READ_INQUIRY_SCAN_TYPE) {
+}
+
+packet ReadInquiryScanTypeComplete : CommandComplete (command_op_code = READ_INQUIRY_SCAN_TYPE) {
+  status : ErrorCode,
+  inquiry_scan_type : InquiryScanType,
+}
+
+packet WriteInquiryScanType : Command (op_code = WRITE_INQUIRY_SCAN_TYPE) {
+  inquiry_scan_type : InquiryScanType,
+}
+
+packet WriteInquiryScanTypeComplete : CommandComplete (command_op_code = WRITE_INQUIRY_SCAN_TYPE) {
+  status : ErrorCode,
+}
+
+enum InquiryMode : 8 {
+  STANDARD = 0x00,
+  RSSI = 0x01,
+  RSSI_OR_EXTENDED = 0x02,
+}
+
+packet ReadInquiryMode : Command (op_code = READ_INQUIRY_MODE) {
+}
+
+packet ReadInquiryModeComplete : CommandComplete (command_op_code = READ_INQUIRY_MODE) {
+  status : ErrorCode,
+  inquiry_mode : InquiryMode,
+}
+
+packet WriteInquiryMode : Command (op_code = WRITE_INQUIRY_MODE) {
+  inquiry_mode : InquiryMode,
+}
+
+packet WriteInquiryModeComplete : CommandComplete (command_op_code = WRITE_INQUIRY_MODE) {
+  status : ErrorCode,
+}
+
+enum PageScanType : 8 {
+  STANDARD = 0x00,
+  INTERLACED = 0x01,
+}
+
+packet ReadPageScanType : Command (op_code = READ_PAGE_SCAN_TYPE) {
+}
+
+packet ReadPageScanTypeComplete : CommandComplete (command_op_code = READ_PAGE_SCAN_TYPE) {
+  status : ErrorCode,
+  page_scan_type : PageScanType,
+}
+
+packet WritePageScanType : Command (op_code = WRITE_PAGE_SCAN_TYPE) {
+  page_scan_type : PageScanType,
+}
+
+packet WritePageScanTypeComplete : CommandComplete (command_op_code = WRITE_PAGE_SCAN_TYPE) {
+  status : ErrorCode,
+}
+
+packet ReadAfhChannelAssessmentMode : Command (op_code = READ_AFH_CHANNEL_ASSESSMENT_MODE) {
+}
+
+packet ReadAfhChannelAssessmentModeComplete : CommandComplete (command_op_code = READ_AFH_CHANNEL_ASSESSMENT_MODE) {
+  status : ErrorCode,
+  controller_channel_assessment : Enable,
+}
+
+packet WriteAfhChannelAssessmentMode : Command (op_code = WRITE_AFH_CHANNEL_ASSESSMENT_MODE) {
+  controller_channel_assessment : Enable,
+}
+
+packet WriteAfhChannelAssessmentModeComplete : CommandComplete (command_op_code = WRITE_AFH_CHANNEL_ASSESSMENT_MODE) {
+  status : ErrorCode,
+}
+
+enum FecRequired : 8 {
+  NOT_REQUIRED = 0x00,
+  REQUIRED = 0x01,
+}
+
+packet ReadExtendedInquiryResponse : Command (op_code = READ_EXTENDED_INQUIRY_RESPONSE) {
+}
+
+packet ReadExtendedInquiryResponseComplete : CommandComplete (command_op_code = READ_EXTENDED_INQUIRY_RESPONSE) {
+  status : ErrorCode,
+  fec_required : FecRequired,
+  extended_inquiry_response : 8[240],
+}
+
+packet WriteExtendedInquiryResponse : Command (op_code = WRITE_EXTENDED_INQUIRY_RESPONSE) {
+  fec_required : FecRequired,
+  extended_inquiry_response : 8[240],
+}
+
+packet WriteExtendedInquiryResponseComplete : CommandComplete (command_op_code = WRITE_EXTENDED_INQUIRY_RESPONSE) {
+  status : ErrorCode,
+}
+
+packet RefreshEncryptionKey : Command (op_code = REFRESH_ENCRYPTION_KEY) {
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet RefreshEncryptionKeyStatus : CommandStatus (command_op_code = REFRESH_ENCRYPTION_KEY) {
+}
+
+packet ReadSimplePairingMode : Command (op_code = READ_SIMPLE_PAIRING_MODE) {
+}
+
+packet ReadSimplePairingModeComplete : CommandComplete (command_op_code = READ_SIMPLE_PAIRING_MODE) {
+  status : ErrorCode,
+  simple_pairing_mode : Enable,
+}
+
+packet WriteSimplePairingMode : Command (op_code = WRITE_SIMPLE_PAIRING_MODE) {
+  simple_pairing_mode : Enable,
+}
+
+test WriteSimplePairingMode {
+  "\x56\x0c\x01\x01",
+}
+
+packet WriteSimplePairingModeComplete : CommandComplete (command_op_code = WRITE_SIMPLE_PAIRING_MODE) {
+  status : ErrorCode,
+}
+
+test WriteSimplePairingModeComplete {
+  "\x0e\x04\x01\x56\x0c\x00",
+}
+
+packet ReadLocalOobData : Command (op_code = READ_LOCAL_OOB_DATA) {
+}
+
+packet ReadLocalOobDataComplete : CommandComplete (command_op_code = READ_LOCAL_OOB_DATA) {
+  status : ErrorCode,
+  c : 8[16],
+  r : 8[16],
+}
+
+packet ReadInquiryResponseTransmitPowerLevel : Command (op_code = READ_INQUIRY_RESPONSE_TRANSMIT_POWER_LEVEL) {
+}
+
+packet ReadInquiryResponseTransmitPowerLevelComplete : CommandComplete (command_op_code = READ_INQUIRY_RESPONSE_TRANSMIT_POWER_LEVEL) {
+  status : ErrorCode,
+  tx_power : 8, // (-70dBm to 20dBm)
+}
+
+packet WriteInquiryTransmitPowerLevel : Command (op_code = WRITE_INQUIRY_TRANSMIT_POWER_LEVEL) {
+  tx_power : 8,
+}
+
+packet WriteInquiryResponseTransmitPowerLevelComplete : CommandComplete (command_op_code = WRITE_INQUIRY_TRANSMIT_POWER_LEVEL) {
+  status : ErrorCode,
+}
+
+enum KeypressNotificationType : 8 {
+  ENTRY_STARTED = 0,
+  DIGIT_ENTERED = 1,
+  DIGIT_ERASED = 2,
+  CLEARED = 3,
+  ENTRY_COMPLETED = 4,
+}
+
+packet SendKeypressNotification : Command (op_code = SEND_KEYPRESS_NOTIFICATION) {
+  bd_addr : Address,
+  notification_type : KeypressNotificationType,
+}
+
+packet SendKeypressNotificationComplete : CommandComplete (command_op_code = SEND_KEYPRESS_NOTIFICATION) {
+  status : ErrorCode,
+  bd_addr : Address,
+}
+
+packet SetEventMaskPage2 : Command (op_code = SET_EVENT_MASK_PAGE_2) {
+  event_mask_page_2: 64,
+}
+
+packet SetEventMaskPage2Complete : CommandComplete (command_op_code = SET_EVENT_MASK_PAGE_2) {
+  status: ErrorCode,
+}
+
+packet ReadEnhancedTransmitPowerLevel : Command (op_code = READ_ENHANCED_TRANSMIT_POWER_LEVEL) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  transmit_power_level_type : TransmitPowerLevelType,
+}
+
+packet ReadEnhancedTransmitPowerLevelComplete : CommandComplete (command_op_code = READ_ENHANCED_TRANSMIT_POWER_LEVEL) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+  tx_power_level_gfsk : 8,
+  tx_power_level_dqpsk : 8,
+  tx_power_level_8dpsk : 8,
+}
+
+packet ReadLeHostSupport : Command (op_code = READ_LE_HOST_SUPPORT) {
+}
+
+packet ReadLeHostSupportComplete : CommandComplete (command_op_code = READ_LE_HOST_SUPPORT) {
+  status : ErrorCode,
+  le_supported_host : Enable,
+  _reserved_ : 8, // simultaneous_le_host reserved since 4.1
+}
+
+packet WriteLeHostSupport : Command (op_code = WRITE_LE_HOST_SUPPORT) {
+  le_supported_host : Enable,
+  simultaneous_le_host : Enable,  // According to the spec, this should be 0x00 since 4.1
+}
+
+test WriteLeHostSupport {
+  "\x6d\x0c\x02\x01\x01",
+}
+
+packet WriteLeHostSupportComplete : CommandComplete (command_op_code = WRITE_LE_HOST_SUPPORT) {
+  status : ErrorCode,
+}
+
+test WriteLeHostSupportComplete {
+  "\x0e\x04\x01\x6d\x0c\x00",
+}
+
+packet ReadSecureConnectionsHostSupport : Command (op_code = READ_SECURE_CONNECTIONS_HOST_SUPPORT) {
+}
+
+packet ReadSecureConnectionsHostSupportComplete : CommandComplete (command_op_code = READ_SECURE_CONNECTIONS_HOST_SUPPORT) {
+  status : ErrorCode,
+  secure_connections_host_support : Enable,
+}
+
+packet WriteSecureConnectionsHostSupport : Command (op_code = WRITE_SECURE_CONNECTIONS_HOST_SUPPORT) {
+  secure_connections_host_support : Enable,
+}
+
+test WriteSecureConnectionsHostSupport {
+  "\x7a\x0c\x01\x01",
+}
+
+packet WriteSecureConnectionsHostSupportComplete : CommandComplete (command_op_code = WRITE_SECURE_CONNECTIONS_HOST_SUPPORT) {
+  status : ErrorCode,
+}
+
+test WriteSecureConnectionsHostSupportComplete {
+  "\x0e\x04\x01\x7a\x0c\x00",
+}
+
+packet ReadLocalOobExtendedData : Command (op_code = READ_LOCAL_OOB_EXTENDED_DATA) {
+}
+
+packet ReadLocalOobExtendedDataComplete : CommandComplete (command_op_code = READ_LOCAL_OOB_EXTENDED_DATA) {
+  status : ErrorCode,
+  c_192 : 8[16],
+  r_192 : 8[16],
+  c_256 : 8[16],
+  r_256 : 8[16],
+}
+
+packet SetEcosystemBaseInterval : Command (op_code = SET_ECOSYSTEM_BASE_INTERVAL) {
+  interval : 16,
+}
+
+packet SetEcosystemBaseIntervalComplete : CommandComplete (command_op_code = SET_ECOSYSTEM_BASE_INTERVAL) {
+  status : ErrorCode,
+}
+
+enum DataPathDirection : 8 {
+  INPUT = 0,
+  OUTPUT = 1,
+}
+
+packet ConfigureDataPath : Command (op_code = CONFIGURE_DATA_PATH) {
+  data_path_direction : DataPathDirection,
+  data_path_id : 8,
+  _size_(vendor_specific_config) : 8,
+  vendor_specific_config : 8[],
+}
+
+packet ConfigureDataPathComplete : CommandComplete (command_op_code = CONFIGURE_DATA_PATH) {
+  status : ErrorCode,
+}
+
+packet SetMinEncryptionKeySize : Command (op_code = SET_MIN_ENCRYPTION_KEY_SIZE) {
+  min_encryption_key_size : 8,
+}
+
+packet SetMinEncryptionKeySizeComplete : CommandComplete (command_op_code = SET_MIN_ENCRYPTION_KEY_SIZE) {
+  status : ErrorCode,
+}
+
+
+  // INFORMATIONAL_PARAMETERS
+packet ReadLocalVersionInformation : Command (op_code = READ_LOCAL_VERSION_INFORMATION) {
+}
+
+test ReadLocalVersionInformation {
+  "\x01\x10\x00",
+}
+
+enum HciVersion : 8 {
+  V_1_0B = 0x00,
+  V_1_1 = 0x01,
+  V_1_2 = 0x02,
+  V_2_0 = 0x03, //  + EDR
+  V_2_1 = 0x04, //  + EDR
+  V_3_0 = 0x05, //  + HS
+  V_4_0 = 0x06,
+  V_4_1 = 0x07,
+  V_4_2 = 0x08,
+  V_5_0 = 0x09,
+  V_5_1 = 0x0a,
+  V_5_2 = 0x0b,
+  V_5_3 = 0x0c,
+  V_5_4 = 0x0d,
+}
+
+enum LmpVersion : 8 {
+  V_1_0B = 0x00, // withdrawn
+  V_1_1 = 0x01, // withdrawn
+  V_1_2 = 0x02, // withdrawn
+  V_2_0 = 0x03, //  + EDR
+  V_2_1 = 0x04, //  + EDR
+  V_3_0 = 0x05, //  + HS
+  V_4_0 = 0x06,
+  V_4_1 = 0x07,
+  V_4_2 = 0x08,
+  V_5_0 = 0x09,
+  V_5_1 = 0x0a,
+  V_5_2 = 0x0b,
+  V_5_3 = 0x0c,
+  V_5_4 = 0x0d,
+}
+
+struct LocalVersionInformation {
+  hci_version : HciVersion,
+  hci_revision : 16,
+  lmp_version : LmpVersion,
+  manufacturer_name : 16,
+  lmp_subversion : 16,
+}
+
+packet ReadLocalVersionInformationComplete : CommandComplete (command_op_code = READ_LOCAL_VERSION_INFORMATION) {
+  status : ErrorCode,
+  local_version_information : LocalVersionInformation,
+}
+
+test ReadLocalVersionInformationComplete {
+  "\x0e\x0c\x01\x01\x10\x00\x09\x00\x00\x09\x1d\x00\xbe\x02",
+}
+
+packet ReadLocalSupportedCommands : Command (op_code = READ_LOCAL_SUPPORTED_COMMANDS) {
+}
+
+test ReadLocalSupportedCommands {
+  "\x02\x10\x00",
+}
+
+packet ReadLocalSupportedCommandsComplete : CommandComplete (command_op_code = READ_LOCAL_SUPPORTED_COMMANDS) {
+  status : ErrorCode,
+  supported_commands : 8[64],
+}
+
+test ReadLocalSupportedCommandsComplete {
+  "\x0e\x44\x01\x02\x10\x00\xff\xff\xff\x03\xce\xff\xef\xff\xff\xff\xff\x7f\xf2\x0f\xe8\xfe\x3f\xf7\x83\xff\x1c\x00\x00\x00\x61\xff\xff\xff\x7f\xbe\x20\xf5\xff\xf0\xff\xff\xff\xff\xff\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
+}
+
+packet ReadLocalSupportedFeatures : Command (op_code = READ_LOCAL_SUPPORTED_FEATURES) {
+}
+
+packet ReadLocalSupportedFeaturesComplete : CommandComplete (command_op_code = READ_LOCAL_SUPPORTED_FEATURES) {
+  status : ErrorCode,
+  lmp_features : 64,
+}
+
+packet ReadLocalExtendedFeatures : Command (op_code = READ_LOCAL_EXTENDED_FEATURES) {
+  page_number : 8,
+}
+
+test ReadLocalExtendedFeatures {
+  "\x04\x10\x01\x00",
+  "\x04\x10\x01\x01",
+  "\x04\x10\x01\x02",
+}
+
+enum LMPFeaturesPage0Bits: 64 {
+  // Byte 0
+  LMP_3_SLOT_PACKETS = 0x01,
+  LMP_5_SLOT_PACKETS = 0x02,
+  ENCRYPTION         = 0x04,
+  SLOT_OFFSET        = 0x08,
+  TIMING_ACCURACY    = 0x10,
+  ROLE_SWITCH        = 0x20,
+  HOLD_MODE          = 0x40,
+  SNIFF_MODE         = 0x80,
+
+  // Byte 1
+  POWER_CONTROL_REQUESTS           = 0x0200,
+  CHANNEL_QUALITY_DRIVEN_DATA_RATE = 0x0400,
+  SCO_LINK                         = 0x0800,
+  HV2_PACKETS                      = 0x1000,
+  HV3_PACKETS                      = 0x2000,
+  M_LAW_LOG_SYNCHRONOUS_DATA       = 0x4000,
+  A_LAW_LOG_SYNCHRONOUS_DATA       = 0x8000,
+
+  // Byte 2
+  CVSD_SYNCHRONOUS_DATA                  = 0x010000,
+  PAGING_PARAMETER_NEGOTIATION           = 0x020000,
+  POWER_CONTROL                          = 0x040000,
+  TRANSPARENT_SYNCHRONOUS_DATA           = 0x080000,
+  FLOW_CONTROL_LAG_LEAST_SIGNIFICANT_BIT = 0x100000,
+  FLOW_CONTROL_LAG_MIDDLE_BIT            = 0x200000,
+  FLOW_CONTROL_LAG_MOST_SIGNIFICANT_BIT  = 0x400000,
+  BROADCAST_ENCRYPTION                   = 0x800000,
+
+  // Byte 3
+  ENHANCED_DATA_RATE_ACL_2_MB_S_MODE = 0x02000000,
+  ENHANCED_DATA_RATE_ACL_3_MB_S_MODE = 0x04000000,
+  ENHANCED_INQUIRY_SCAN              = 0x08000000,
+  INTERLACED_INQUIRY_SCAN            = 0x10000000,
+  INTERLACED_PAGE_SCAN               = 0x20000000,
+  RSSI_WITH_INQUIRY_RESULTS          = 0x40000000,
+  EXTENDED_SCO_LINK                  = 0x80000000,
+
+  // Byte 4
+  EV4_PACKETS                               = 0x0100000000,
+  EV5_PACKETS                               = 0x0200000000,
+  AFH_CAPABLE_PERIPHERAL                    = 0x0800000000,
+  AFH_CLASSIFICATION_PERIPHERAL             = 0x1000000000,
+  BR_EDR_NOT_SUPPORTED                      = 0x2000000000,
+  LE_SUPPORTED_CONTROLLER                   = 0x4000000000,
+  LMP_3_SLOT_ENHANCED_DATA_RATE_ACL_PACKETS = 0x8000000000,
+
+  // Byte 5
+  LMP_5_SLOT_ENHANCED_DATA_RATE_ACL_PACKETS  = 0x010000000000,
+  SNIFF_SUBRATING                            = 0x020000000000,
+  PAUSE_ENCRYPTION                           = 0x040000000000,
+  AFH_CAPABLE_CENTRAL                        = 0x080000000000,
+  AFH_CLASSIFICATION_CENTRAL                 = 0x100000000000,
+  ENHANCED_DATA_RATE_ESCO_2_MB_S_MODE        = 0x200000000000,
+  ENHANCED_DATA_RATE_ESCO_3_MB_S_MODE        = 0x400000000000,
+  LMP_3_SLOT_ENHANCED_DATA_RATE_ESCO_PACKETS = 0x800000000000,
+
+  // Byte 6
+  EXTENDED_INQUIRY_RESPONSE          = 0x01000000000000,
+  SIMULTANEOUS_LE_AND_BR_CONTROLLER  = 0x02000000000000,
+  SECURE_SIMPLE_PAIRING_CONTROLLER   = 0x08000000000000,
+  ENCAPSULATED_PDU                   = 0x10000000000000,
+  ERRONEOUS_DATA_REPORTING           = 0x20000000000000,
+  NON_FLUSHABLE_PACKET_BOUNDARY_FLAG = 0x40000000000000,
+
+  // Byte 7
+  HCI_LINK_SUPERVISION_TIMEOUT_CHANGED_EVENT = 0x0100000000000000,
+  VARIABLE_INQUIRY_TX_POWER_LEVEL            = 0x0200000000000000,
+  ENHANCED_POWER_CONTROL                     = 0x0400000000000000,
+  EXTENDED_FEATURES                          = 0x8000000000000000,
+}
+
+enum LMPFeaturesPage1Bits: 64 {
+  // Byte 0
+  SECURE_SIMPLE_PAIRING_HOST_SUPPORT = 0x01,
+  LE_SUPPORTED_HOST                  = 0x02,
+  SIMULTANEOUS_LE_AND_BR_HOST        = 0x04,
+  SECURE_CONNECTIONS_HOST_SUPPORT    = 0x08,
+}
+
+enum LMPFeaturesPage2Bits: 64 {
+  // Byte 0
+  CONNECTIONLESS_PERIPHERAL_BROADCAST_TRANSMITTER_OPERATION = 0x01,
+  CONNECTIONLESS_PERIPHERAL_BROADCAST_RECEIVER_OPERATION    = 0x02,
+  SYNCHRONIZATION_TRAIN                                     = 0x04,
+  SYNCHRONIZATION_SCAN                                      = 0x08,
+  HCI_INQUIRY_RESPONSE_NOTIFICATION_EVENT                   = 0x10,
+  GENERALIZED_INTERLACED_SCAN                               = 0x20,
+  COARSE_CLOCK_ADJUSTMENT                                   = 0x40,
+
+  // Byte 1
+  SECURE_CONNECTIONS_CONTROLLER_SUPPORT = 0x0100,
+  PING                                  = 0x0200,
+  SLOT_AVAILABILITY_MASK                = 0x0400,
+  TRAIN_NUDGING                         = 0x0800,
+}
+
+packet ReadLocalExtendedFeaturesComplete : CommandComplete (command_op_code = READ_LOCAL_EXTENDED_FEATURES) {
+  status : ErrorCode,
+  page_number : 8,
+  maximum_page_number : 8,
+  extended_lmp_features : 64,
+}
+
+test ReadLocalExtendedFeaturesComplete {
+  "\x0e\x0e\x01\x04\x10\x00\x00\x02\xff\xfe\x8f\xfe\xd8\x3f\x5b\x87",
+  "\x0e\x0e\x01\x04\x10\x00\x01\x02\x07\x00\x00\x00\x00\x00\x00\x00",
+  "\x0e\x0e\x01\x04\x10\x00\x02\x02\x45\x03\x00\x00\x00\x00\x00\x00",
+}
+
+packet ReadBufferSize : Command (op_code = READ_BUFFER_SIZE) {
+}
+
+test ReadBufferSize {
+  "\x05\x10\x00",
+}
+
+packet ReadBufferSizeComplete : CommandComplete (command_op_code = READ_BUFFER_SIZE) {
+  status : ErrorCode,
+  acl_data_packet_length : 16,
+  synchronous_data_packet_length : 8,
+  total_num_acl_data_packets : 16,
+  total_num_synchronous_data_packets : 16,
+}
+
+test ReadBufferSizeComplete {
+  "\x0e\x0b\x01\x05\x10\x00\x00\x04\x3c\x07\x00\x08\x00",
+}
+
+packet ReadBdAddr : Command (op_code = READ_BD_ADDR) {
+}
+
+test ReadBdAddr {
+  "\x09\x10\x00",
+}
+
+packet ReadBdAddrComplete : CommandComplete (command_op_code = READ_BD_ADDR) {
+  status : ErrorCode,
+  bd_addr : Address,
+}
+
+test ReadBdAddrComplete {
+  "\x0e\x0a\x01\x09\x10\x00\x14\x8e\x61\x5f\x36\x88",
+}
+
+packet ReadDataBlockSize : Command (op_code = READ_DATA_BLOCK_SIZE) {
+}
+
+packet ReadDataBlockSizeComplete : CommandComplete (command_op_code = READ_DATA_BLOCK_SIZE) {
+  status : ErrorCode,
+  max_acl_data_packet_length : 16,
+  data_block_length : 16,
+  total_num_data_blocks : 16,
+}
+
+packet ReadLocalSupportedCodecsV1 : Command (op_code = READ_LOCAL_SUPPORTED_CODECS_V1) {
+}
+
+packet ReadLocalSupportedCodecsV1Complete : CommandComplete (command_op_code = READ_LOCAL_SUPPORTED_CODECS_V1) {
+  status : ErrorCode,
+  _count_(supported_codecs) : 8,
+  supported_codecs : 8[],
+  _count_(vendor_specific_codecs) : 8,
+  vendor_specific_codecs : 32[],
+}
+
+packet ReadLocalSupportedCodecsV2 : Command (op_code = READ_LOCAL_SUPPORTED_CODECS_V2) {
+}
+
+group CodecTransport {
+  br_edr : 1,
+  br_edr_sco_and_esco : 1,
+  le_cis : 1,
+  le_bis : 1,
+  _reserved_ : 4,
+}
+
+struct CodecConfiguration {
+  codec_id : 8,
+  CodecTransport,
+}
+
+struct VendorCodecConfiguration {
+  company_id : 16,
+  codec_vendor_id : 16,
+  CodecTransport,
+}
+
+packet ReadLocalSupportedCodecsV2Complete : CommandComplete (command_op_code = READ_LOCAL_SUPPORTED_CODECS_V2) {
+  status : ErrorCode,
+  _count_(supported_codecs) : 8,
+  supported_codecs : CodecConfiguration[],
+  _count_(vendor_specific_codecs) : 8,
+  vendor_specific_codecs : VendorCodecConfiguration[],
+}
+
+packet ReadLocalSupportedCodecCapabilities : Command (op_code = READ_LOCAL_SUPPORTED_CODEC_CAPABILITIES) {
+  codec_id : 8,
+  company_id : 16,
+  codec_vendor_id : 16,
+  CodecTransport,
+  direction : DataPathDirection,
+}
+
+struct CodecCapability {
+  _size_(capability) : 8,
+  capability : 8[],
+}
+
+packet ReadLocalSupportedCodecCapabilitiesComplete : CommandComplete (command_op_code = READ_LOCAL_SUPPORTED_CODEC_CAPABILITIES) {
+  status : ErrorCode,
+  _count_(codec_capabilities) : 8,
+  codec_capabilities : CodecCapability[],
+}
+
+packet ReadLocalSupportedControllerDelay : Command (op_code = READ_LOCAL_SUPPORTED_CONTROLLER_DELAY) {
+  codec_id : 8,
+  company_id : 16,
+  codec_vendor_id : 16,
+  CodecTransport,
+  direction : DataPathDirection,
+  _size_(codec_configuration) : 8,
+  codec_configuration : 8[],
+}
+
+packet ReadLocalSupportedControllerDelayComplete : CommandComplete (command_op_code = READ_LOCAL_SUPPORTED_CONTROLLER_DELAY) {
+  status : ErrorCode,
+  min_controller_delay : 24,
+  max_controller_delay : 24,
+}
+
+
+  // STATUS_PARAMETERS
+packet ReadFailedContactCounter : Command (op_code = READ_FAILED_CONTACT_COUNTER) {
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet ReadFailedContactCounterComplete : CommandComplete (command_op_code = READ_FAILED_CONTACT_COUNTER) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+  failed_contact_counter : 16,
+}
+
+packet ResetFailedContactCounter : Command (op_code = RESET_FAILED_CONTACT_COUNTER) {
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet ResetFailedContactCounterComplete : CommandComplete (command_op_code = RESET_FAILED_CONTACT_COUNTER) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet ReadLinkQuality : Command (op_code = READ_LINK_QUALITY) {
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet ReadLinkQualityComplete : CommandComplete (command_op_code = READ_LINK_QUALITY) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+  link_quality : 8,
+}
+
+packet ReadRssi : Command (op_code = READ_RSSI) {
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet ReadRssiComplete : CommandComplete (command_op_code = READ_RSSI) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+  rssi : 8,
+}
+
+packet ReadAfhChannelMap : Command (op_code = READ_AFH_CHANNEL_MAP) {
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+enum AfhMode : 8 {
+  AFH_DISABLED = 0x00,
+  AFH_ENABLED = 0x01,
+}
+
+packet ReadAfhChannelMapComplete : CommandComplete (command_op_code = READ_AFH_CHANNEL_MAP) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+  afh_mode : AfhMode,
+  afh_channel_map : 8[10],
+}
+
+
+enum WhichClock : 8 {
+  LOCAL = 0x00,
+  PICONET = 0x01,
+}
+
+packet ReadClock : Command (op_code = READ_CLOCK) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  which_clock : WhichClock,
+}
+
+packet ReadClockComplete : CommandComplete (command_op_code = READ_CLOCK) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+  clock : 28,
+  _reserved_ : 4,
+  accuracy : 16,
+}
+
+packet ReadEncryptionKeySize : Command (op_code = READ_ENCRYPTION_KEY_SIZE) {
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet ReadEncryptionKeySizeComplete : CommandComplete (command_op_code = READ_ENCRYPTION_KEY_SIZE) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+  key_size : 8,
+}
+
+  // TESTING
+enum LoopbackMode : 8 {
+  NO_LOOPBACK = 0x00,
+  ENABLE_LOCAL = 0x01,
+  ENABLE_REMOTE = 0x02,
+}
+
+packet ReadLoopbackMode : Command (op_code = READ_LOOPBACK_MODE) {
+}
+
+packet ReadLoopbackModeComplete : CommandComplete (command_op_code = READ_LOOPBACK_MODE) {
+  status : ErrorCode,
+  loopback_mode : LoopbackMode,
+}
+
+packet WriteLoopbackMode : Command (op_code = WRITE_LOOPBACK_MODE) {
+  loopback_mode : LoopbackMode,
+}
+
+packet WriteLoopbackModeComplete : CommandComplete (command_op_code = WRITE_LOOPBACK_MODE) {
+  status : ErrorCode,
+}
+
+packet EnableDeviceUnderTestMode : Command (op_code = ENABLE_DEVICE_UNDER_TEST_MODE) {
+}
+
+packet EnableDeviceUnderTestModeComplete : CommandComplete (command_op_code = ENABLE_DEVICE_UNDER_TEST_MODE) {
+  status : ErrorCode,
+}
+
+packet WriteSimplePairingDebugMode : Command (op_code = WRITE_SIMPLE_PAIRING_DEBUG_MODE) {
+  simple_pairing_debug_mode : Enable,
+}
+
+packet WriteSimplePairingDebugModeComplete : CommandComplete (command_op_code = WRITE_SIMPLE_PAIRING_DEBUG_MODE) {
+  status : ErrorCode,
+}
+
+packet WriteSecureConnectionsTestMode : Command (op_code = WRITE_SECURE_CONNECTIONS_TEST_MODE) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  dm1_aclu_mode : Enable,
+  esco_loopback_mode : Enable,
+}
+
+packet WriteSecureConnectionsTestModeComplete : CommandComplete (command_op_code = WRITE_SECURE_CONNECTIONS_TEST_MODE) {
+  status : ErrorCode,
+}
+
+  // LE_CONTROLLER
+packet LeSetEventMask : Command (op_code = LE_SET_EVENT_MASK) {
+  le_event_mask : 64,
+}
+
+packet LeSetEventMaskComplete : CommandComplete (command_op_code = LE_SET_EVENT_MASK) {
+  status : ErrorCode,
+}
+
+packet LeReadBufferSizeV1 : Command (op_code = LE_READ_BUFFER_SIZE_V1) {
+}
+
+struct LeBufferSize {
+  le_data_packet_length : 16,
+  total_num_le_packets : 8,
+}
+
+test LeReadBufferSizeV1 {
+  "\x02\x20\x00",
+}
+
+packet LeReadBufferSizeV1Complete : CommandComplete (command_op_code = LE_READ_BUFFER_SIZE_V1) {
+  status : ErrorCode,
+  le_buffer_size : LeBufferSize,
+}
+
+test LeReadBufferSizeV1Complete {
+  "\x0e\x07\x01\x02\x20\x00\xfb\x00\x10",
+}
+
+enum LLFeaturesBits : 64 {
+  // Byte 0
+  LE_ENCRYPTION                                 = 0x0000000000000001,
+  CONNECTION_PARAMETERS_REQUEST_PROCEDURE       = 0x0000000000000002,
+  EXTENDED_REJECT_INDICATION                    = 0x0000000000000004,
+  PERIPHERAL_INITIATED_FEATURES_EXCHANGE        = 0x0000000000000008,
+  LE_PING                                       = 0x0000000000000010,
+  LE_DATA_PACKET_LENGTH_EXTENSION               = 0x0000000000000020,
+  LL_PRIVACY                                    = 0x0000000000000040,
+  EXTENDED_SCANNER_FILTER_POLICIES              = 0x0000000000000080,
+
+  // Byte 1
+  LE_2M_PHY                                     = 0x0000000000000100,
+  STABLE_MODULATION_INDEX_TRANSMITTER           = 0x0000000000000200,
+  STABLE_MODULATION_INDEX_RECEIVER              = 0x0000000000000400,
+  LE_CODED_PHY                                  = 0x0000000000000800,
+  LE_EXTENDED_ADVERTISING                       = 0x0000000000001000,
+  LE_PERIODIC_ADVERTISING                       = 0x0000000000002000,
+  CHANNEL_SELECTION_ALGORITHM_2                 = 0x0000000000004000,
+  LE_POWER_CLASS_1                              = 0x0000000000008000,
+
+  // Byte 2
+  MINIMUM_NUMBER_OF_USED_CHANNELS_PROCEDURE     = 0x0000000000010000,
+  CONNECTION_CTE_REQUEST                        = 0x0000000000020000,
+  CONNECTION_CTE_RESPONSE                       = 0x0000000000040000,
+  CONNECTIONLESS_CTE_TRANSMITTER                = 0x0000000000080000,
+  CONNECTIONLESS_CTE_RECEIVER                   = 0x0000000000100000,
+  ANTENNA_SWITCHING_DURING_CTE_TRANSMISSION     = 0x0000000000200000,
+  ANTENNA_SWITCHING_DURING_CTE_RECEPTION        = 0x0000000000400000,
+  RECEIVING_CONSTANT_TONE_EXTENSIONS            = 0x0000000000800000,
+
+  // Byte 3
+  PERIODIC_ADVERTISING_SYNC_TRANSFER_SENDER     = 0x0000000001000000,
+  PERIODIC_ADVERTISING_SYNC_TRANSFER_RECIPIENT  = 0x0000000002000000,
+  SLEEP_CLOCK_ACCURACY_UPDATES                  = 0x0000000004000000,
+  REMOTE_PUBLIC_KEY_VALIDATION                  = 0x0000000008000000,
+  CONNECTED_ISOCHRONOUS_STREAM_CENTRAL          = 0x0000000010000000,
+  CONNECTED_ISOCHRONOUS_STREAM_PERIPHERAL       = 0x0000000020000000,
+  ISOCHRONOUS_BROADCASTER                       = 0x0000000040000000,
+  SYNCHRONIZED_RECEIVER                         = 0x0000000080000000,
+
+  // Byte 4
+  CONNECTED_ISOCHRONOUS_STREAM_HOST_SUPPORT     = 0x0000000100000000,
+  LE_POWER_CONTROL_REQUEST                      = 0x0000000200000000,
+  LE_POWER_CONTROL_REQUEST_BIS                  = 0x0000000400000000,
+  LE_PATH_LOSS_MONITORING                       = 0x0000000800000000,
+  PERIODIC_ADVERTISING_ADI_SUPPORT              = 0x0000001000000000,
+  CONNECTION_SUBRATING                          = 0x0000002000000000,
+  CONNECTION_SUBRATING_HOST_SUPPORT             = 0x0000004000000000,
+  CHANNEL_CLASSIFICATION                        = 0x0000008000000000,
+}
+
+packet LeReadLocalSupportedFeatures : Command (op_code = LE_READ_LOCAL_SUPPORTED_FEATURES) {
+}
+
+packet LeReadLocalSupportedFeaturesComplete : CommandComplete (command_op_code = LE_READ_LOCAL_SUPPORTED_FEATURES) {
+  status : ErrorCode,
+  le_features : 64,
+}
+
+packet LeSetRandomAddress : Command (op_code = LE_SET_RANDOM_ADDRESS) {
+  random_address : Address,
+}
+
+packet LeSetRandomAddressComplete : CommandComplete (command_op_code = LE_SET_RANDOM_ADDRESS) {
+  status : ErrorCode,
+}
+
+enum AdvertisingFilterPolicy : 2 {
+  ALL_DEVICES = 0, // Default
+  LISTED_SCAN = 1,
+  LISTED_CONNECT = 2,
+  LISTED_SCAN_AND_CONNECT = 3,
+}
+
+enum PeerAddressType : 8 {
+  PUBLIC_DEVICE_OR_IDENTITY_ADDRESS = 0x00,
+  RANDOM_DEVICE_OR_IDENTITY_ADDRESS = 0x01,
+}
+
+enum AdvertisingType : 8 {
+  ADV_IND = 0x00,
+  ADV_DIRECT_IND_HIGH = 0x01,
+  ADV_SCAN_IND = 0x02,
+  ADV_NONCONN_IND = 0x03,
+  ADV_DIRECT_IND_LOW = 0x04,
+}
+
+enum AddressType : 8 {
+  PUBLIC_DEVICE_ADDRESS = 0x00,
+  RANDOM_DEVICE_ADDRESS = 0x01,
+  PUBLIC_IDENTITY_ADDRESS = 0x02,
+  RANDOM_IDENTITY_ADDRESS = 0x03,
+}
+
+enum OwnAddressType : 8 {
+  PUBLIC_DEVICE_ADDRESS = 0x00,
+  RANDOM_DEVICE_ADDRESS = 0x01,
+  RESOLVABLE_OR_PUBLIC_ADDRESS = 0x02,
+  RESOLVABLE_OR_RANDOM_ADDRESS = 0x03,
+}
+
+packet LeSetAdvertisingParameters : Command (op_code = LE_SET_ADVERTISING_PARAMETERS) {
+  advertising_interval_min : 16,
+  advertising_interval_max : 16,
+  advertising_type : AdvertisingType,
+  own_address_type : OwnAddressType,
+  peer_address_type : PeerAddressType,
+  peer_address : Address,
+  advertising_channel_map : 8,
+  advertising_filter_policy : AdvertisingFilterPolicy,
+  _reserved_ : 6,
+}
+
+packet LeSetAdvertisingParametersComplete : CommandComplete (command_op_code = LE_SET_ADVERTISING_PARAMETERS) {
+  status : ErrorCode,
+}
+
+packet LeReadAdvertisingPhysicalChannelTxPower : Command (op_code = LE_READ_ADVERTISING_PHYSICAL_CHANNEL_TX_POWER) {
+}
+
+packet LeReadAdvertisingPhysicalChannelTxPowerComplete : CommandComplete (command_op_code = LE_READ_ADVERTISING_PHYSICAL_CHANNEL_TX_POWER) {
+  status : ErrorCode,
+  transmit_power_level : 8, // (-127dBm to 20dBm) Accuracy: +/-4dB
+}
+
+packet LeSetAdvertisingData : Command (op_code = LE_SET_ADVERTISING_DATA) {
+  _size_(advertising_data) : 8,
+  advertising_data : 8[],
+  _padding_[31], // Zero padding to 31 bytes of advertising_data
+}
+
+packet LeSetAdvertisingDataComplete : CommandComplete (command_op_code = LE_SET_ADVERTISING_DATA) {
+  status : ErrorCode,
+}
+
+packet LeSetScanResponseData : Command (op_code = LE_SET_SCAN_RESPONSE_DATA) {
+  _size_(advertising_data) : 8,
+  advertising_data : 8[],
+  _padding_[31], // Zero padding to 31 bytes of advertising_data
+}
+
+packet LeSetScanResponseDataComplete : CommandComplete (command_op_code = LE_SET_SCAN_RESPONSE_DATA) {
+  status : ErrorCode,
+}
+
+packet LeSetAdvertisingEnable : Command (op_code = LE_SET_ADVERTISING_ENABLE) {
+  advertising_enable : Enable, // Default DISABLED
+}
+
+packet LeSetAdvertisingEnableComplete : CommandComplete (command_op_code = LE_SET_ADVERTISING_ENABLE) {
+  status : ErrorCode,
+}
+
+enum LeScanType : 8 {
+  PASSIVE = 0x00, // Default
+  ACTIVE = 0x01,
+}
+
+enum LeScanningFilterPolicy : 8 {
+  ACCEPT_ALL = 0x00, // Default
+  FILTER_ACCEPT_LIST_ONLY = 0x01,
+  CHECK_INITIATORS_IDENTITY = 0x02,
+  FILTER_ACCEPT_LIST_AND_INITIATORS_IDENTITY = 0x03,
+}
+
+packet LeSetScanParameters : Command (op_code = LE_SET_SCAN_PARAMETERS) {
+  le_scan_type : LeScanType,
+  le_scan_interval : 16, // 0x0004-0x4000 Default 0x10 (10ms)
+  le_scan_window : 16, // Default 0x10 (10ms)
+  own_address_type : OwnAddressType,
+  scanning_filter_policy : LeScanningFilterPolicy,
+}
+
+test LeSetScanParameters {
+  "\x0b\x20\x07\x01\x12\x00\x12\x00\x01\x00",
+}
+
+packet LeSetScanParametersComplete : CommandComplete (command_op_code = LE_SET_SCAN_PARAMETERS) {
+  status : ErrorCode,
+}
+
+packet LeSetScanEnable : Command (op_code = LE_SET_SCAN_ENABLE) {
+  le_scan_enable : Enable,
+  filter_duplicates : Enable,
+}
+
+test LeSetScanEnable {
+  "\x0c\x20\x02\x01\x00",
+}
+
+packet LeSetScanEnableComplete : CommandComplete (command_op_code = LE_SET_SCAN_ENABLE) {
+  status : ErrorCode,
+}
+
+enum InitiatorFilterPolicy : 8 {
+  USE_PEER_ADDRESS = 0x00,
+  USE_FILTER_ACCEPT_LIST = 0x01,
+}
+
+packet LeCreateConnection : Command (op_code = LE_CREATE_CONNECTION) {
+  le_scan_interval : 16, // 0x0004-0x4000
+  le_scan_window : 16, // < = LeScanInterval
+  initiator_filter_policy : InitiatorFilterPolicy,
+  peer_address_type : AddressType,
+  peer_address : Address,
+  own_address_type : OwnAddressType,
+  connection_interval_min : 16, // 0x0006-0x0C80 (7.5ms to 4s)
+  connection_interval_max : 16, // 0x0006-0x0C80 (7.5ms to 4s)
+  max_latency : 16, // 0x0006-0x01F3
+  supervision_timeout : 16, // 0x00A to 0x0C80 (100ms to 32s)
+  min_ce_length : 16, // 0.625ms
+  max_ce_length : 16, // 0.625ms
+}
+
+packet LeCreateConnectionStatus : CommandStatus (command_op_code = LE_CREATE_CONNECTION) {
+}
+
+packet LeCreateConnectionCancel : Command (op_code = LE_CREATE_CONNECTION_CANCEL) {
+}
+
+packet LeCreateConnectionCancelComplete : CommandComplete (command_op_code = LE_CREATE_CONNECTION_CANCEL) {
+  status : ErrorCode,
+}
+
+packet LeReadFilterAcceptListSize : Command (op_code = LE_READ_FILTER_ACCEPT_LIST_SIZE) {
+}
+
+test LeReadFilterAcceptListSize {
+  "\x0f\x20\x00",
+}
+
+packet LeReadFilterAcceptListSizeComplete : CommandComplete (command_op_code = LE_READ_FILTER_ACCEPT_LIST_SIZE) {
+  status : ErrorCode,
+  filter_accept_list_size : 8,
+}
+
+test LeReadFilterAcceptListSizeComplete {
+  "\x0e\x05\x01\x0f\x20\x00\x80",
+}
+
+packet LeClearFilterAcceptList : Command (op_code = LE_CLEAR_FILTER_ACCEPT_LIST) {
+}
+
+packet LeClearFilterAcceptListComplete : CommandComplete (command_op_code = LE_CLEAR_FILTER_ACCEPT_LIST) {
+  status : ErrorCode,
+}
+
+enum FilterAcceptListAddressType : 8 {
+  PUBLIC = 0x00,
+  RANDOM = 0x01,
+  ANONYMOUS_ADVERTISERS = 0xFF,
+}
+
+packet LeAddDeviceToFilterAcceptList : Command (op_code = LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST) {
+  address_type : FilterAcceptListAddressType,
+  address : Address,
+}
+
+packet LeAddDeviceToFilterAcceptListComplete : CommandComplete (command_op_code = LE_ADD_DEVICE_TO_FILTER_ACCEPT_LIST) {
+  status : ErrorCode,
+}
+
+packet LeRemoveDeviceFromFilterAcceptList : Command (op_code = LE_REMOVE_DEVICE_FROM_FILTER_ACCEPT_LIST) {
+  address_type : FilterAcceptListAddressType,
+  address : Address,
+}
+
+packet LeRemoveDeviceFromFilterAcceptListComplete : CommandComplete (command_op_code = LE_REMOVE_DEVICE_FROM_FILTER_ACCEPT_LIST) {
+  status : ErrorCode,
+}
+
+packet LeConnectionUpdate : Command (op_code = LE_CONNECTION_UPDATE) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  connection_interval_min : 16, // 0x0006-0x0C80 (7.5ms to 4s)
+  connection_interval_max : 16, // 0x0006-0x0C80 (7.5ms to 4s)
+  max_latency : 16, // 0x0006-0x01F3
+  supervision_timeout : 16, // 0x00A to 0x0C80 (100ms to 32s)
+  min_ce_length : 16, // 0.625ms
+  max_ce_length : 16, // 0.625ms
+}
+
+packet LeConnectionUpdateStatus : CommandStatus (command_op_code = LE_CONNECTION_UPDATE) {
+}
+
+packet LeSetHostChannelClassification : Command (op_code = LE_SET_HOST_CHANNEL_CLASSIFICATION) {
+  channel_map : 8[5],
+}
+
+packet LeSetHostChannelClassificationComplete : CommandComplete (command_op_code = LE_SET_HOST_CHANNEL_CLASSIFICATION) {
+  status : ErrorCode,
+}
+
+packet LeReadChannelMap : Command (op_code = LE_READ_CHANNEL_MAP) {
+}
+
+packet LeReadChannelMapComplete : CommandComplete (command_op_code = LE_READ_CHANNEL_MAP) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+  channel_map : 8[5],
+}
+
+packet LeReadRemoteFeatures : Command (op_code = LE_READ_REMOTE_FEATURES) {
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet LeReadRemoteFeaturesStatus : CommandStatus (command_op_code = LE_READ_REMOTE_FEATURES) {
+}
+
+packet LeEncrypt : Command (op_code = LE_ENCRYPT) {
+  key : 8[16],
+  plaintext_data : 8[16],
+}
+
+packet LeEncryptComplete : CommandComplete (command_op_code = LE_ENCRYPT) {
+  status : ErrorCode,
+  encrypted_data : 8[16],
+}
+
+packet LeRand : Command (op_code = LE_RAND) {
+}
+
+packet LeRandComplete : CommandComplete (command_op_code = LE_RAND) {
+  status : ErrorCode,
+  random_number : 64,
+}
+
+packet LeStartEncryption : Command (op_code = LE_START_ENCRYPTION) {
+  connection_handle: 16,
+  rand: 8[8],
+  ediv: 16,
+  ltk: 8[16],
+}
+
+packet LeStartEncryptionStatus : CommandStatus (command_op_code = LE_START_ENCRYPTION) {
+}
+
+packet LeLongTermKeyRequestReply : Command (op_code = LE_LONG_TERM_KEY_REQUEST_REPLY) {
+  connection_handle: 16,
+  long_term_key: 8[16],
+}
+
+packet LeLongTermKeyRequestReplyComplete : CommandComplete (command_op_code = LE_LONG_TERM_KEY_REQUEST_REPLY) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet LeLongTermKeyRequestNegativeReply : Command (op_code = LE_LONG_TERM_KEY_REQUEST_NEGATIVE_REPLY) {
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet LeLongTermKeyRequestNegativeReplyComplete : CommandComplete (command_op_code = LE_LONG_TERM_KEY_REQUEST_NEGATIVE_REPLY) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet LeReadSupportedStates : Command (op_code = LE_READ_SUPPORTED_STATES) {
+}
+
+packet LeReadSupportedStatesComplete : CommandComplete (command_op_code = LE_READ_SUPPORTED_STATES) {
+  status : ErrorCode,
+  le_states : 64,
+}
+
+packet LeReceiverTest : Command (op_code = LE_RECEIVER_TEST_V1) {
+  rx_channel : 8,
+}
+
+packet LeReceiverTestComplete : CommandComplete (command_op_code = LE_RECEIVER_TEST_V1) {
+  status : ErrorCode,
+}
+
+enum LeTestPayload : 8 {
+  PRBS9 = 0x00,
+  REPEATED_F0 = 0x01,
+  REPEATED_AA = 0x02,
+  PRBS15 = 0x03,
+  REPEATED_FF = 0x04,
+  REPEATED_00 = 0x05,
+  REPEATED_0F = 0x06,
+  REPEATED_55 = 0x07,
+}
+
+packet LeTransmitterTest : Command (op_code = LE_TRANSMITTER_TEST_V1) {
+  tx_channel : 8,
+  test_data_length : 8,
+  packet_payload : LeTestPayload,
+}
+
+packet LeTransmitterTestComplete : CommandComplete (command_op_code = LE_TRANSMITTER_TEST_V1) {
+  status : ErrorCode,
+}
+
+packet LeTestEnd : Command (op_code = LE_TEST_END) {
+}
+
+packet LeTestEndComplete : CommandComplete (command_op_code = LE_TEST_END) {
+  status : ErrorCode,
+}
+
+packet LeRemoteConnectionParameterRequestReply : Command (op_code = LE_REMOTE_CONNECTION_PARAMETER_REQUEST_REPLY) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  interval_min : 16, // 0x0006-0x0C80 (7.5ms to 4s)
+  interval_max : 16, // 0x0006-0x0C80 (7.5ms to 4s)
+  latency : 16, // 0x0006-0x01F3
+  timeout : 16, // 0x00A to 0x0C80 (100ms to 32s)
+  minimum_ce_length : 16, // 0.625ms
+  maximum_ce_length : 16, // 0.625ms
+}
+
+packet LeRemoteConnectionParameterRequestReplyComplete : CommandComplete (command_op_code = LE_REMOTE_CONNECTION_PARAMETER_REQUEST_REPLY) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet LeRemoteConnectionParameterRequestNegativeReply : Command (op_code = LE_REMOTE_CONNECTION_PARAMETER_REQUEST_NEGATIVE_REPLY) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  reason : ErrorCode,
+}
+
+packet LeRemoteConnectionParameterRequestNegativeReplyComplete : CommandComplete (command_op_code = LE_REMOTE_CONNECTION_PARAMETER_REQUEST_NEGATIVE_REPLY) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet LeSetDataLength : Command (op_code = LE_SET_DATA_LENGTH) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  tx_octets : 16, // payload octets per single PDU 0x1B to 0x00FB
+  tx_time : 16, // microseconds used to transmit a single PDU 0x0148 to 0x4290
+}
+
+packet LeSetDataLengthComplete : CommandComplete (command_op_code = LE_SET_DATA_LENGTH) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet LeReadSuggestedDefaultDataLength : Command (op_code = LE_READ_SUGGESTED_DEFAULT_DATA_LENGTH) {
+}
+
+packet LeReadSuggestedDefaultDataLengthComplete : CommandComplete (command_op_code = LE_READ_SUGGESTED_DEFAULT_DATA_LENGTH) {
+  status : ErrorCode,
+  tx_octets : 16, // payload octets per single PDU 0x1B to 0x00FB
+  tx_time : 16, // microseconds used to transmit a single PDU 0x0148 to 0x4290
+}
+
+packet LeWriteSuggestedDefaultDataLength : Command (op_code = LE_WRITE_SUGGESTED_DEFAULT_DATA_LENGTH) {
+  tx_octets : 16, // payload octets per single PDU 0x1B to 0x00FB
+  tx_time : 16, // microseconds used to transmit a single PDU 0x0148 to 0x4290
+}
+
+packet LeWriteSuggestedDefaultDataLengthComplete : CommandComplete (command_op_code = LE_WRITE_SUGGESTED_DEFAULT_DATA_LENGTH) {
+  status : ErrorCode,
+}
+
+packet LeReadLocalP256PublicKeyCommand : Command (op_code = LE_READ_LOCAL_P_256_PUBLIC_KEY) {
+}
+
+packet LeReadLocalP256PublicKeyCommandStatus : CommandStatus (command_op_code = LE_READ_LOCAL_P_256_PUBLIC_KEY) {
+}
+
+packet LeGenerateDhkeyV1Command : Command (op_code = LE_GENERATE_DHKEY_V1) {
+  remote_p_256_public_key : 8[64],
+}
+
+packet LeGenerateDhkeyV1CommandStatus : CommandStatus (command_op_code = LE_GENERATE_DHKEY_V1) {
+}
+
+packet LeAddDeviceToResolvingList : Command (op_code = LE_ADD_DEVICE_TO_RESOLVING_LIST) {
+  peer_identity_address_type : PeerAddressType,
+  peer_identity_address : Address,
+  peer_irk : 8[16],
+  local_irk : 8[16],
+}
+
+packet LeAddDeviceToResolvingListComplete : CommandComplete (command_op_code = LE_ADD_DEVICE_TO_RESOLVING_LIST) {
+  status : ErrorCode,
+}
+
+packet LeRemoveDeviceFromResolvingList : Command (op_code = LE_REMOVE_DEVICE_FROM_RESOLVING_LIST) {
+  peer_identity_address_type : PeerAddressType,
+  peer_identity_address : Address,
+}
+
+packet LeRemoveDeviceFromResolvingListComplete : CommandComplete (command_op_code = LE_REMOVE_DEVICE_FROM_RESOLVING_LIST) {
+  status : ErrorCode,
+}
+
+packet LeClearResolvingList : Command (op_code = LE_CLEAR_RESOLVING_LIST) {
+}
+
+packet LeClearResolvingListComplete : CommandComplete (command_op_code = LE_CLEAR_RESOLVING_LIST) {
+  status : ErrorCode,
+}
+
+packet LeReadResolvingListSize : Command (op_code = LE_READ_RESOLVING_LIST_SIZE) {
+}
+
+packet LeReadResolvingListSizeComplete : CommandComplete (command_op_code = LE_READ_RESOLVING_LIST_SIZE) {
+  status : ErrorCode,
+  resolving_list_size : 8,
+}
+
+packet LeReadPeerResolvableAddress : Command (op_code = LE_READ_PEER_RESOLVABLE_ADDRESS) {
+  peer_identity_address_type : PeerAddressType,
+  peer_identity_address : Address,
+}
+
+packet LeReadPeerResolvableAddressComplete : CommandComplete (command_op_code = LE_READ_PEER_RESOLVABLE_ADDRESS) {
+  status : ErrorCode,
+  peer_resolvable_address : Address,
+}
+
+packet LeReadLocalResolvableAddress : Command (op_code = LE_READ_LOCAL_RESOLVABLE_ADDRESS) {
+  peer_identity_address_type : PeerAddressType,
+  peer_identity_address : Address,
+}
+
+packet LeReadLocalResolvableAddressComplete : CommandComplete (command_op_code = LE_READ_LOCAL_RESOLVABLE_ADDRESS) {
+  status : ErrorCode,
+  local_resolvable_address : Address,
+}
+
+packet LeSetAddressResolutionEnable : Command (op_code = LE_SET_ADDRESS_RESOLUTION_ENABLE) {
+  address_resolution_enable : Enable,
+}
+
+packet LeSetAddressResolutionEnableComplete : CommandComplete (command_op_code = LE_SET_ADDRESS_RESOLUTION_ENABLE) {
+  status : ErrorCode,
+}
+
+packet LeSetResolvablePrivateAddressTimeout : Command (op_code = LE_SET_RESOLVABLE_PRIVATE_ADDRESS_TIMEOUT) {
+  rpa_timeout : 16, // RPA_Timeout measured in seconds 0x0001 to 0xA1B8 1s to 11.5 hours
+}
+
+packet LeSetResolvablePrivateAddressTimeoutComplete : CommandComplete (command_op_code = LE_SET_RESOLVABLE_PRIVATE_ADDRESS_TIMEOUT) {
+  status : ErrorCode,
+}
+
+packet LeReadMaximumDataLength : Command (op_code = LE_READ_MAXIMUM_DATA_LENGTH) {
+}
+
+struct LeMaximumDataLength {
+  supported_max_tx_octets : 16,
+  supported_max_tx_time: 16,
+  supported_max_rx_octets : 16,
+  supported_max_rx_time: 16,
+}
+
+packet LeReadMaximumDataLengthComplete : CommandComplete (command_op_code = LE_READ_MAXIMUM_DATA_LENGTH) {
+  status : ErrorCode,
+  le_maximum_data_length : LeMaximumDataLength,
+}
+
+packet LeReadPhy : Command (op_code = LE_READ_PHY) {
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+enum PhyType : 8 {
+  LE_1M = 0x01,
+  LE_2M = 0x02,
+  LE_CODED = 0x03,
+}
+
+packet LeReadPhyComplete : CommandComplete (command_op_code = LE_READ_PHY) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+  tx_phy : PhyType,
+  rx_phy : PhyType,
+}
+
+packet LeSetDefaultPhy : Command (op_code = LE_SET_DEFAULT_PHY) {
+  all_phys_no_transmit_preference : 1,
+  all_phys_no_receive_preference : 1,
+  _reserved_ : 6,
+  tx_phys : 3,
+  _reserved_ : 5,
+  rx_phys : 3,
+  _reserved_ : 5,
+}
+
+packet LeSetDefaultPhyComplete : CommandComplete (command_op_code = LE_SET_DEFAULT_PHY) {
+  status : ErrorCode,
+}
+
+enum PhyOptions : 8 {
+  NO_PREFERENCE = 0x00,
+  S_2 = 0x01,
+  S_8 = 0x02,
+}
+
+packet LeSetPhy : Command (op_code = LE_SET_PHY) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  all_phys_no_transmit_preference : 1,
+  all_phys_no_receive_preference : 1,
+  _reserved_ : 6,
+  tx_phys : 3,
+  _reserved_ : 5,
+  rx_phys : 3,
+  _reserved_ : 5,
+  phy_options : PhyOptions,
+}
+
+packet LeSetPhyStatus : CommandStatus (command_op_code = LE_SET_PHY) {
+}
+
+enum ModulationIndex : 8 {
+  STANDARD = 0x00,
+  STABLE = 0x01,
+}
+
+packet LeEnhancedReceiverTest : Command (op_code = LE_RECEIVER_TEST_V2) {
+  rx_channel : 8,
+  phy : PhyType,
+  modulation_index : ModulationIndex,
+}
+
+packet LeEnhancedReceiverTestComplete : CommandComplete (command_op_code = LE_RECEIVER_TEST_V2) {
+  status : ErrorCode,
+}
+
+packet LeEnhancedTransmitterTest : Command (op_code = LE_TRANSMITTER_TEST_V2) {
+  tx_channel : 8,
+  test_data_length : 8,
+  packet_payload : LeTestPayload,
+  phy : PhyType,
+}
+
+packet LeEnhancedTransmitterTestComplete : CommandComplete (command_op_code = LE_TRANSMITTER_TEST_V2) {
+  status : ErrorCode,
+}
+
+packet LeSetAdvertisingSetRandomAddress : Command (op_code = LE_SET_ADVERTISING_SET_RANDOM_ADDRESS) {
+  advertising_handle : 8,
+  random_address : Address,
+}
+
+test LeSetAdvertisingSetRandomAddress {
+  "\x35\x20\x07\x00\x77\x58\xeb\xd3\x1c\x6e",
+}
+
+packet LeSetAdvertisingSetRandomAddressComplete : CommandComplete (command_op_code = LE_SET_ADVERTISING_SET_RANDOM_ADDRESS) {
+  status : ErrorCode,
+}
+
+test LeSetAdvertisingSetRandomAddressComplete {
+  "\x0e\x04\x01\x35\x20\x00",
+}
+
+// The lower 4 bits of the advertising event properties
+enum LegacyAdvertisingEventProperties : 4 {
+  ADV_IND = 0x3,
+  ADV_DIRECT_IND_LOW = 0x5,
+  ADV_DIRECT_IND_HIGH = 0xD,
+  ADV_SCAN_IND = 0x2,
+  ADV_NONCONN_IND = 0,
+}
+
+enum PrimaryPhyType : 8 {
+  LE_1M = 0x01,
+  LE_CODED = 0x03,
+}
+
+enum SecondaryPhyType : 8 {
+  NO_PACKETS = 0x00,
+  LE_1M = 0x01,
+  LE_2M = 0x02,
+  LE_CODED = 0x03,
+}
+
+packet LeSetExtendedAdvertisingParametersLegacy : Command (op_code = LE_SET_EXTENDED_ADVERTISING_PARAMETERS) {
+  advertising_handle : 8,
+  legacy_advertising_event_properties : LegacyAdvertisingEventProperties,
+  _fixed_ = 0x1 : 1, // legacy bit set
+  _reserved_ : 11, // advertising_event_properties reserved bits
+  primary_advertising_interval_min : 24, // 0x20 - 0xFFFFFF N * 0.625 ms
+  primary_advertising_interval_max : 24, // 0x20 - 0xFFFFFF N * 0.625 ms
+  primary_advertising_channel_map : 3,  // bit 0 - Channel 37, bit 1 - 38, bit 2 - 39
+  _reserved_ : 5,
+  own_address_type : OwnAddressType,
+  peer_address_type : PeerAddressType,
+  peer_address : Address,
+  advertising_filter_policy : AdvertisingFilterPolicy,
+  _reserved_ : 6,
+  advertising_tx_power : 8, // -127 to +20, 0x7F - no preference
+  _fixed_ = 0x1 : 8, // PrimaryPhyType LE_1M
+  _reserved_ : 8, // secondary_advertising_max_skip
+  _fixed_ = 0x1 : 8, // secondary_advertising_phy LE_1M
+  advertising_sid : 8, // SID subfield from the ADI field of the PDU
+  scan_request_notification_enable : Enable,
+}
+
+test LeSetExtendedAdvertisingParametersLegacy {
+  "\x36\x20\x19\x00\x13\x00\x90\x01\x00\xc2\x01\x00\x07\x01\x00\x00\x00\x00\x00\x00\x00\x00\xf9\x01\x00\x01\x01\x00",
+  "\x36\x20\x19\x01\x13\x00\x90\x01\x00\xc2\x01\x00\x07\x01\x00\x00\x00\x00\x00\x00\x00\x00\xf9\x01\x00\x01\x01\x00",
+}
+
+struct AdvertisingEventProperties {
+  connectable : 1,
+  scannable : 1,
+  directed : 1,
+  high_duty_cycle : 1,
+  legacy : 1,
+  anonymous : 1,
+  tx_power : 1,
+  _reserved_ : 9,
+}
+
+packet LeSetExtendedAdvertisingParameters : Command (op_code = LE_SET_EXTENDED_ADVERTISING_PARAMETERS) {
+  advertising_handle : 8,
+  advertising_event_properties : AdvertisingEventProperties,
+  primary_advertising_interval_min : 24, // 0x20 - 0xFFFFFF N * 0.625 ms
+  primary_advertising_interval_max : 24, // 0x20 - 0xFFFFFF N * 0.625 ms
+  primary_advertising_channel_map : 3,  // bit 0 - Channel 37, bit 1 - 38, bit 2 - 39
+  _reserved_ : 5,
+  own_address_type : OwnAddressType,
+  peer_address_type : PeerAddressType,
+  peer_address : Address,
+  advertising_filter_policy : AdvertisingFilterPolicy,
+  _reserved_ : 6,
+  advertising_tx_power : 8, // -127 to +20, 0x7F - no preference
+  primary_advertising_phy : PrimaryPhyType,
+  secondary_advertising_max_skip : 8, // 1 to 255, 0x00 - AUX_ADV_IND sent before next advertising event
+  secondary_advertising_phy : SecondaryPhyType,
+  advertising_sid : 8, // SID subfield from the ADI field of the PDU
+  scan_request_notification_enable : Enable,
+}
+
+packet LeSetExtendedAdvertisingParametersComplete : CommandComplete (command_op_code = LE_SET_EXTENDED_ADVERTISING_PARAMETERS) {
+  status : ErrorCode,
+  selected_tx_power : 8, // -127 to +20
+}
+
+enum Operation : 3 {
+  INTERMEDIATE_FRAGMENT = 0,
+  FIRST_FRAGMENT = 1,
+  LAST_FRAGMENT = 2,
+  COMPLETE_ADVERTISEMENT = 3,
+  UNCHANGED_DATA = 4,
+}
+
+enum FragmentPreference : 1 {
+  CONTROLLER_MAY_FRAGMENT = 0,
+  CONTROLLER_SHOULD_NOT = 1,
+}
+
+packet LeSetExtendedAdvertisingData : Command (op_code = LE_SET_EXTENDED_ADVERTISING_DATA) {
+  advertising_handle : 8,
+  operation : Operation,
+  _reserved_ : 5,
+  fragment_preference : FragmentPreference,
+  _reserved_ : 7,
+  _size_(advertising_data) : 8,
+  advertising_data : 8[],
+}
+
+test LeSetExtendedAdvertisingData {
+  "\x37\x20\x12\x00\x03\x01\x0e\x02\x01\x02\x0a\x09\x50\x69\x78\x65\x6c\x20\x33\x20\x58",
+}
+
+packet LeSetExtendedAdvertisingDataComplete : CommandComplete (command_op_code = LE_SET_EXTENDED_ADVERTISING_DATA) {
+  status : ErrorCode,
+}
+
+test LeSetExtendedAdvertisingDataComplete {
+  "\x0e\x04\x01\x37\x20\x00",
+}
+
+packet LeSetExtendedScanResponseData : Command (op_code = LE_SET_EXTENDED_SCAN_RESPONSE_DATA) {
+  advertising_handle : 8,
+  operation : Operation,
+  _reserved_ : 5,
+  fragment_preference : FragmentPreference,
+  _reserved_ : 7,
+  _size_(scan_response_data) : 8,
+  scan_response_data : 8[],
+}
+
+packet LeSetExtendedScanResponseDataComplete : CommandComplete (command_op_code = LE_SET_EXTENDED_SCAN_RESPONSE_DATA) {
+  status : ErrorCode,
+}
+
+packet LeSetExtendedAdvertisingEnableDisableAll : Command (op_code = LE_SET_EXTENDED_ADVERTISING_ENABLE) {
+  _fixed_ = 0x00 : 8, // Enable::DISABLED
+  _fixed_ = 0x00 : 8, // Disable all sets
+}
+
+struct EnabledSet {
+  advertising_handle : 8,
+  duration : 16,
+  max_extended_advertising_events : 8,
+}
+
+struct DisabledSet {
+  advertising_handle : 8,
+  _fixed_ = 0x00 : 16, // duration
+  _fixed_ = 0x00 : 8, // max_extended_advertising_events
+}
+
+packet LeSetExtendedAdvertisingDisable : Command (op_code = LE_SET_EXTENDED_ADVERTISING_ENABLE) {
+  _fixed_ = 0x00 : 8, // Enable::DISABLED
+  _count_(disabled_sets) : 8,
+  disabled_sets : DisabledSet[],
+}
+
+packet LeSetExtendedAdvertisingEnable : Command (op_code = LE_SET_EXTENDED_ADVERTISING_ENABLE) {
+  enable : Enable,
+  _count_(enabled_sets) : 8,
+  enabled_sets : EnabledSet[],
+}
+
+test LeSetExtendedAdvertisingEnable {
+  "\x39\x20\x06\x01\x01\x01\x00\x00\x00",
+}
+
+test LeSetExtendedAdvertisingDisable {
+  "\x39\x20\x06\x00\x01\x01\x00\x00\x00",
+}
+
+packet LeSetExtendedAdvertisingEnableComplete : CommandComplete (command_op_code = LE_SET_EXTENDED_ADVERTISING_ENABLE) {
+  status : ErrorCode,
+}
+
+test LeSetExtendedAdvertisingEnableComplete {
+  "\x0e\x04\x01\x39\x20\x00",
+}
+
+packet LeReadMaximumAdvertisingDataLength : Command (op_code = LE_READ_MAXIMUM_ADVERTISING_DATA_LENGTH) {
+}
+
+packet LeReadMaximumAdvertisingDataLengthComplete : CommandComplete (command_op_code = LE_READ_MAXIMUM_ADVERTISING_DATA_LENGTH) {
+  status : ErrorCode,
+  maximum_advertising_data_length : 16,
+}
+
+packet LeReadNumberOfSupportedAdvertisingSets : Command (op_code = LE_READ_NUMBER_OF_SUPPORTED_ADVERTISING_SETS) {
+}
+
+packet LeReadNumberOfSupportedAdvertisingSetsComplete : CommandComplete (command_op_code = LE_READ_NUMBER_OF_SUPPORTED_ADVERTISING_SETS) {
+  status : ErrorCode,
+  number_supported_advertising_sets : 8,
+}
+
+packet LeRemoveAdvertisingSet : Command (op_code = LE_REMOVE_ADVERTISING_SET) {
+  advertising_handle : 8,
+}
+
+test LeRemoveAdvertisingSet {
+  "\x3c\x20\x01\x01",
+}
+
+packet LeRemoveAdvertisingSetComplete : CommandComplete (command_op_code = LE_REMOVE_ADVERTISING_SET) {
+  status : ErrorCode,
+}
+
+test LeRemoveAdvertisingSetComplete {
+  "\x0e\x04\x01\x3c\x20\x00",
+}
+
+packet LeClearAdvertisingSets : Command (op_code = LE_CLEAR_ADVERTISING_SETS) {
+}
+
+packet LeClearAdvertisingSetsComplete : CommandComplete (command_op_code = LE_CLEAR_ADVERTISING_SETS) {
+  status : ErrorCode,
+}
+
+packet LeSetPeriodicAdvertisingParameters : Command (op_code = LE_SET_PERIODIC_ADVERTISING_PARAMETERS) {
+  advertising_handle : 8,
+  periodic_advertising_interval_min : 16, // 0x006 to 0xFFFF (7.5 ms to 82s)
+  periodic_advertising_interval_max : 16, // 0x006 to 0xFFFF (7.5 ms to 82s)
+  _reserved_ : 6,
+  include_tx_power : 1,
+  _reserved_ : 9,
+}
+
+packet LeSetPeriodicAdvertisingParametersComplete : CommandComplete (command_op_code = LE_SET_PERIODIC_ADVERTISING_PARAMETERS) {
+  status : ErrorCode,
+}
+
+packet LeSetPeriodicAdvertisingData : Command (op_code = LE_SET_PERIODIC_ADVERTISING_DATA) {
+  advertising_handle : 8,
+  operation : Operation,
+  _reserved_ : 5,
+  _size_(advertising_data) : 8,
+  advertising_data : 8[],
+}
+
+packet LeSetPeriodicAdvertisingDataComplete : CommandComplete (command_op_code = LE_SET_PERIODIC_ADVERTISING_DATA) {
+  status : ErrorCode,
+}
+
+packet LeSetPeriodicAdvertisingEnable : Command (op_code = LE_SET_PERIODIC_ADVERTISING_ENABLE) {
+  enable : 1,
+  include_adi: 1,
+  _reserved_: 6,
+  advertising_handle : 8,
+}
+
+packet LeSetPeriodicAdvertisingEnableComplete : CommandComplete (command_op_code = LE_SET_PERIODIC_ADVERTISING_ENABLE) {
+  status : ErrorCode,
+}
+
+struct ScanningPhyParameters {
+  le_scan_type : LeScanType,
+  le_scan_interval : 16, // 0x0004-0xFFFF Default 0x10 (10ms)
+  le_scan_window : 16, // 0x004-0xFFFF Default 0x10 (10ms)
+}
+
+packet LeSetExtendedScanParameters : Command (op_code = LE_SET_EXTENDED_SCAN_PARAMETERS) {
+  own_address_type : OwnAddressType,
+  scanning_filter_policy : LeScanningFilterPolicy,
+  scanning_phys : 8,
+  scanning_phy_parameters : ScanningPhyParameters[],
+}
+
+test LeSetExtendedScanParameters {
+  "\x41\x20\x08\x01\x00\x01\x01\x12\x00\x12\x00",
+  "\x41\x20\x08\x01\x00\x01\x01\x99\x19\x99\x19",
+}
+
+packet LeSetExtendedScanParametersComplete : CommandComplete (command_op_code = LE_SET_EXTENDED_SCAN_PARAMETERS) {
+  status : ErrorCode,
+}
+
+test LeSetExtendedScanParametersComplete {
+  "\x0e\x04\x01\x41\x20\x00",
+}
+
+enum FilterDuplicates : 8 {
+  DISABLED = 0,
+  ENABLED = 1,
+  RESET_EACH_PERIOD = 2,
+}
+
+packet LeSetExtendedScanEnable : Command (op_code = LE_SET_EXTENDED_SCAN_ENABLE) {
+  enable : Enable,
+  filter_duplicates : FilterDuplicates,
+  duration : 16, // 0 - Scan continuously,  N * 10 ms
+  period : 16, // 0 - Scan continuously,  N * 1.28 sec
+}
+
+test LeSetExtendedScanEnable {
+  "\x42\x20\x06\x01\x00\x00\x00\x00\x00",
+  "\x42\x20\x06\x00\x01\x00\x00\x00\x00",
+}
+
+packet LeSetExtendedScanEnableComplete : CommandComplete (command_op_code = LE_SET_EXTENDED_SCAN_ENABLE) {
+  status : ErrorCode,
+}
+
+test LeSetExtendedScanEnableComplete {
+  "\x0e\x04\x01\x42\x20\x00",
+}
+
+struct InitiatingPhyParameters {
+  scan_interval : 16, // 0x0004-0xFFFF
+  scan_window : 16, // < = LeScanInterval
+  connection_interval_min : 16, // 0x0006-0x0C80 (7.5ms to 4s)
+  connection_interval_max : 16, // 0x0006-0x0C80 (7.5ms to 4s)
+  max_latency : 16, // 0x0006-0x01F3
+  supervision_timeout : 16, // 0x00A to 0x0C80 (100ms to 32s)
+  min_ce_length : 16, // 0.625ms
+  max_ce_length : 16, // 0.625ms
+}
+
+packet LeExtendedCreateConnection : Command (op_code = LE_EXTENDED_CREATE_CONNECTION) {
+  initiator_filter_policy : InitiatorFilterPolicy,
+  own_address_type : OwnAddressType,
+  peer_address_type : PeerAddressType,
+  peer_address : Address,
+  initiating_phys : 8,
+  initiating_phy_parameters : InitiatingPhyParameters[],
+}
+
+test LeExtendedCreateConnection {
+  "\x43\x20\x2a\x01\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x08\x30\x00\x18\x00\x28\x00\x00\x00\xf4\x01\x00\x00\x00\x00\x00\x08\x30\x00\x18\x00\x28\x00\x00\x00\xf4\x01\x00\x00\x00\x00",
+}
+
+packet LeExtendedCreateConnectionStatus : CommandStatus (command_op_code = LE_EXTENDED_CREATE_CONNECTION) {
+}
+
+enum PeriodicSyncCteType : 8 {
+  AVOID_AOA_CONSTANT_TONE_EXTENSION = 0x01,
+  AVOID_AOD_CONSTANT_TONE_EXTENSION_WITH_ONE_US_SLOTS = 0x02,
+  AVOID_AOD_CONSTANT_TONE_EXTENSION_WITH_TWO_US_SLOTS = 0x04,
+  AVOID_TYPE_THREE_CONSTANT_TONE_EXTENSION = 0x08,
+  AVOID_NO_CONSTANT_TONE_EXTENSION = 0x10,
+}
+
+struct PeriodicAdvertisingOptions {
+  use_periodic_advertiser_list: 1,
+  disable_reporting: 1,
+  enable_duplicate_filtering: 1,
+  _reserved_: 5,
+}
+
+enum AdvertiserAddressType : 8 {
+  PUBLIC_DEVICE_OR_IDENTITY_ADDRESS = 0x00,
+  RANDOM_DEVICE_OR_IDENTITY_ADDRESS = 0x01,
+}
+
+packet LePeriodicAdvertisingCreateSync : Command (op_code = LE_PERIODIC_ADVERTISING_CREATE_SYNC) {
+  options : PeriodicAdvertisingOptions,
+  advertising_sid : 8,
+  advertiser_address_type : AdvertiserAddressType,
+  advertiser_address : Address,
+  skip : 16,
+  sync_timeout : 16,
+  sync_cte_type : 8,
+}
+
+packet LePeriodicAdvertisingCreateSyncStatus : CommandStatus (command_op_code = LE_PERIODIC_ADVERTISING_CREATE_SYNC) {
+}
+
+packet LePeriodicAdvertisingCreateSyncCancel : Command (op_code = LE_PERIODIC_ADVERTISING_CREATE_SYNC_CANCEL) {
+}
+
+packet LePeriodicAdvertisingCreateSyncCancelComplete : CommandComplete (command_op_code = LE_PERIODIC_ADVERTISING_CREATE_SYNC_CANCEL) {
+  status : ErrorCode,
+}
+
+packet LePeriodicAdvertisingTerminateSync : Command (op_code = LE_PERIODIC_ADVERTISING_TERMINATE_SYNC) {
+  sync_handle : 12,
+  _reserved_ : 4,
+}
+
+packet LePeriodicAdvertisingTerminateSyncComplete : CommandComplete (command_op_code = LE_PERIODIC_ADVERTISING_TERMINATE_SYNC) {
+  status : ErrorCode,
+}
+
+packet LeAddDeviceToPeriodicAdvertiserList : Command (op_code = LE_ADD_DEVICE_TO_PERIODIC_ADVERTISER_LIST) {
+  advertiser_address_type : AdvertiserAddressType,
+  advertiser_address : Address,
+  advertising_sid : 8,
+}
+
+packet LeAddDeviceToPeriodicAdvertiserListComplete : CommandComplete (command_op_code = LE_ADD_DEVICE_TO_PERIODIC_ADVERTISER_LIST) {
+  status : ErrorCode,
+}
+
+packet LeRemoveDeviceFromPeriodicAdvertiserList : Command (op_code = LE_REMOVE_DEVICE_FROM_PERIODIC_ADVERTISER_LIST) {
+  advertiser_address_type : AdvertiserAddressType,
+  advertiser_address : Address,
+  advertising_sid : 8,
+}
+
+packet LeRemoveDeviceFromPeriodicAdvertiserListComplete : CommandComplete (command_op_code = LE_REMOVE_DEVICE_FROM_PERIODIC_ADVERTISER_LIST) {
+  status : ErrorCode,
+}
+
+packet LeClearPeriodicAdvertiserList : Command (op_code = LE_CLEAR_PERIODIC_ADVERTISER_LIST) {
+}
+
+packet LeClearPeriodicAdvertiserListComplete : CommandComplete (command_op_code = LE_CLEAR_PERIODIC_ADVERTISER_LIST) {
+  status : ErrorCode,
+}
+
+packet LeReadPeriodicAdvertiserListSize : Command (op_code = LE_READ_PERIODIC_ADVERTISER_LIST_SIZE) {
+}
+
+packet LeReadPeriodicAdvertiserListSizeComplete : CommandComplete (command_op_code = LE_READ_PERIODIC_ADVERTISER_LIST_SIZE) {
+  status : ErrorCode,
+  periodic_advertiser_list_size : 8,
+}
+
+packet LeReadTransmitPower : Command (op_code = LE_READ_TRANSMIT_POWER) {
+}
+
+packet LeReadTransmitPowerComplete : CommandComplete (command_op_code = LE_READ_TRANSMIT_POWER) {
+  status : ErrorCode,
+  min_tx_power_dbm : 8,
+  max_tx_power_dbm : 8,
+}
+
+packet LeReadRfPathCompensationPower : Command (op_code = LE_READ_RF_PATH_COMPENSATION_POWER) {
+}
+
+packet LeReadRfPathCompensationPowerComplete : CommandComplete (command_op_code = LE_READ_RF_PATH_COMPENSATION_POWER) {
+  status : ErrorCode,
+  rf_tx_path_compensation_tenths_db : 16,
+  rf_rx_path_compensation_tenths_db : 16,
+}
+
+packet LeWriteRfPathCompensationPower : Command (op_code = LE_WRITE_RF_PATH_COMPENSATION_POWER) {
+  rf_tx_path_compensation_tenths_db : 16,
+  rf_rx_path_compensation_tenths_db : 16,
+}
+
+packet LeWriteRfPathCompensationPowerComplete : CommandComplete (command_op_code = LE_WRITE_RF_PATH_COMPENSATION_POWER) {
+  status : ErrorCode,
+}
+
+enum PrivacyMode : 8 {
+  NETWORK = 0,
+  DEVICE = 1,
+}
+
+packet LeSetPrivacyMode : Command (op_code = LE_SET_PRIVACY_MODE) {
+  peer_identity_address_type : PeerAddressType,
+  peer_identity_address : Address,
+  privacy_mode : PrivacyMode,
+}
+
+packet LeSetPrivacyModeComplete : CommandComplete (command_op_code = LE_SET_PRIVACY_MODE) {
+  status : ErrorCode,
+}
+
+packet LeSetPeriodicAdvertisingReceiveEnable : Command (op_code = LE_SET_PERIODIC_ADVERTISING_RECEIVE_ENABLE) {
+  sync_handle : 12,
+  _reserved_ : 4,
+  enable : 8,
+}
+
+packet LeSetPeriodicAdvertisingReceiveEnableComplete : CommandComplete (command_op_code = LE_SET_PERIODIC_ADVERTISING_RECEIVE_ENABLE) {
+  status : ErrorCode,
+}
+
+packet LePeriodicAdvertisingSyncTransfer : Command (op_code = LE_PERIODIC_ADVERTISING_SYNC_TRANSFER) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  service_data : 16,
+  sync_handle: 12,
+  _reserved_ : 4,
+}
+
+packet LePeriodicAdvertisingSyncTransferComplete : CommandComplete (command_op_code = LE_PERIODIC_ADVERTISING_SYNC_TRANSFER) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet LePeriodicAdvertisingSetInfoTransfer : Command (op_code = LE_PERIODIC_ADVERTISING_SET_INFO_TRANSFER) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  service_data : 16,
+  advertising_handle: 8,
+}
+
+packet LePeriodicAdvertisingSetInfoTransferComplete : CommandComplete (command_op_code = LE_PERIODIC_ADVERTISING_SET_INFO_TRANSFER) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+enum SyncTransferMode : 8 {
+  NO_SYNC = 0,
+  SEND_SYNC_RECEIVED_DISABLE_REPORTS = 1,
+  SEND_SYNC_RECEIVED_SEND_REPORTS = 2,
+}
+
+enum CteType : 8 {
+  AOA_CONSTANT_TONE_EXTENSION = 0x00,
+  AOD_CONSTANT_TONE_EXTENSION_ONE_US_SLOTS = 0x01,
+  AOD_CONSTANT_TONE_EXTENSION_TWO_US_SLOTS = 0x02,
+  NO_CONSTANT_TONE_EXTENSION = 0xFF,
+}
+
+packet LeSetPeriodicAdvertisingSyncTransferParameters : Command (op_code = LE_SET_PERIODIC_ADVERTISING_SYNC_TRANSFER_PARAMETERS) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  mode : SyncTransferMode,
+  skip: 16,
+  sync_timeout : 16,
+  cte_type : CteType,
+}
+
+packet LeSetPeriodicAdvertisingSyncTransferParametersComplete : CommandComplete (command_op_code = LE_SET_PERIODIC_ADVERTISING_SYNC_TRANSFER_PARAMETERS) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet LeSetDefaultPeriodicAdvertisingSyncTransferParameters : Command (op_code = LE_SET_DEFAULT_PERIODIC_ADVERTISING_SYNC_TRANSFER_PARAMETERS) {
+  mode : SyncTransferMode,
+  skip: 16,
+  sync_timeout : 16,
+  cte_type : CteType,
+}
+
+packet LeSetDefaultPeriodicAdvertisingSyncTransferParametersComplete : CommandComplete (command_op_code = LE_SET_DEFAULT_PERIODIC_ADVERTISING_SYNC_TRANSFER_PARAMETERS) {
+  status : ErrorCode,
+}
+
+enum UseDebugKey : 8 {
+  USE_GENERATED_KEY = 0,
+  USE_DEBUG_KEY = 1,
+}
+
+packet LeGenerateDhkeyCommand : Command (op_code = LE_GENERATE_DHKEY_V2) {
+  remote_p_256_public_key : 8[64],
+  key_type : UseDebugKey,
+}
+
+packet LeGenerateDhkeyCommandStatus : CommandStatus (command_op_code = LE_GENERATE_DHKEY_V2) {
+}
+
+enum ScaAction : 8 {
+  MORE_ACCURATE_CLOCK = 0,
+  LESS_ACCURATE_CLOCK = 1,
+}
+
+packet LeModifySleepClockAccuracy : Command (op_code = LE_MODIFY_SLEEP_CLOCK_ACCURACY) {
+  action : ScaAction,
+}
+
+packet LeModifySleepClockAccuracyComplete : CommandComplete (command_op_code = LE_MODIFY_SLEEP_CLOCK_ACCURACY) {
+  status : ErrorCode,
+}
+
+packet LeReadBufferSizeV2 : Command (op_code = LE_READ_BUFFER_SIZE_V2) {
+}
+
+packet LeReadBufferSizeV2Complete : CommandComplete (command_op_code = LE_READ_BUFFER_SIZE_V2) {
+  status : ErrorCode,
+  le_buffer_size : LeBufferSize,
+  iso_buffer_size : LeBufferSize,
+}
+
+packet LeReadIsoTxSync : Command (op_code = LE_READ_ISO_TX_SYNC) {
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet LeReadIsoTxSyncComplete : CommandComplete (command_op_code = LE_READ_ISO_TX_SYNC) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  packet_sequence_number : 16,
+  timestamp : 32,
+  time_offset : 24,
+}
+
+struct CisParametersConfig {
+  cis_id : 8,
+  max_sdu_c_to_p : 12,
+  _reserved_ : 4,
+  max_sdu_p_to_c : 12,
+  _reserved_ : 4,
+  phy_c_to_p : 3,
+  _reserved_ : 5,
+  phy_p_to_c : 3,
+  _reserved_ : 5,
+  rtn_c_to_p : 4,
+  _reserved_ : 4,
+  rtn_p_to_c : 4,
+  _reserved_ : 4,
+}
+
+enum Packing : 8 {
+  SEQUENTIAL = 0,
+  INTERLEAVED = 1,
+}
+
+enum ClockAccuracy : 8 {
+  PPM_500 = 0x00,
+  PPM_250 = 0x01,
+  PPM_150 = 0x02,
+  PPM_100 = 0x03,
+  PPM_75 = 0x04,
+  PPM_50 = 0x05,
+  PPM_30 = 0x06,
+  PPM_20 = 0x07,
+}
+
+packet LeSetCigParameters : Command (op_code = LE_SET_CIG_PARAMETERS) {
+  cig_id : 8,
+  sdu_interval_c_to_p : 24,
+  sdu_interval_p_to_c : 24,
+  worst_case_sca: ClockAccuracy,
+  packing : Packing,
+  framing : Enable,
+  max_transport_latency_c_to_p : 16,
+  max_transport_latency_p_to_c : 16,
+  _count_(cis_config) : 8,
+  cis_config : CisParametersConfig[],
+}
+
+packet LeSetCigParametersComplete : CommandComplete (command_op_code = LE_SET_CIG_PARAMETERS) {
+  status : ErrorCode,
+  cig_id : 8,
+  _count_(connection_handle) : 8,
+  connection_handle : 16[],
+}
+
+struct LeCisParametersTestConfig {
+  cis_id : 8,
+  nse : 8,
+  max_sdu_c_to_p : 16,
+  max_sdu_p_to_c : 16,
+  max_pdu_c_to_p : 16,
+  max_pdu_p_to_c : 16,
+  phy_c_to_p : 8,
+  phy_p_to_c : 8,
+  bn_c_to_p : 8,
+  bn_p_to_c : 8,
+}
+
+packet LeSetCigParametersTest : Command (op_code = LE_SET_CIG_PARAMETERS_TEST) {
+  cig_id : 8,
+  sdu_interval_c_to_p : 24,
+  sdu_interval_p_to_c : 24,
+  ft_c_to_p : 8,
+  ft_p_to_c : 8,
+  iso_interval : 16,
+  worst_case_sca: ClockAccuracy,
+  packing : Packing,
+  framing : Enable,
+  _count_(cis_config) : 8,
+  cis_config : LeCisParametersTestConfig[],
+}
+
+packet LeSetCigParametersTestComplete : CommandComplete (command_op_code = LE_SET_CIG_PARAMETERS_TEST) {
+  status : ErrorCode,
+  cig_id : 8,
+  _count_(connection_handle) : 8,
+  connection_handle : 16[],
+}
+
+struct LeCreateCisConfig {
+  cis_connection_handle : 12,
+  _reserved_ : 4,
+  acl_connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet LeCreateCis : Command (op_code = LE_CREATE_CIS) {
+  _count_(cis_config) : 8,
+  cis_config : LeCreateCisConfig[],
+}
+
+packet LeCreateCisStatus : CommandStatus (command_op_code = LE_CREATE_CIS) {
+}
+
+packet LeRemoveCig : Command (op_code = LE_REMOVE_CIG) {
+  cig_id : 8,
+}
+
+packet LeRemoveCigComplete : CommandComplete (command_op_code = LE_REMOVE_CIG) {
+  status : ErrorCode,
+  cig_id : 8,
+}
+
+packet LeAcceptCisRequest : Command (op_code = LE_ACCEPT_CIS_REQUEST) {
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet LeAcceptCisRequestStatus : CommandStatus (command_op_code = LE_ACCEPT_CIS_REQUEST) {
+}
+
+packet LeRejectCisRequest : Command (op_code = LE_REJECT_CIS_REQUEST) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  reason : ErrorCode,
+}
+
+packet LeRejectCisRequestComplete : CommandComplete (command_op_code = LE_REJECT_CIS_REQUEST) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet LeCreateBig : Command (op_code = LE_CREATE_BIG) {
+  big_handle : 8,
+  advertising_handle : 8,
+  num_bis : 8,
+  sdu_interval : 24,
+  max_sdu : 16,
+  max_transport_latency : 16,
+  rtn : 4,
+  _reserved_ : 4,
+  phy : SecondaryPhyType,
+  packing : Packing,
+  framing : Enable,
+  encryption : Enable,
+  broadcast_code: 8[16],
+}
+
+packet LeCreateBigStatus : CommandStatus (command_op_code = LE_CREATE_BIG) {
+}
+
+packet LeTerminateBig : Command (op_code = LE_TERMINATE_BIG) {
+  big_handle : 8,
+  reason : ErrorCode,
+}
+
+packet LeTerminateBigStatus : CommandStatus (command_op_code = LE_TERMINATE_BIG) {
+}
+
+packet LeBigCreateSync : Command (op_code = LE_BIG_CREATE_SYNC) {
+  big_handle : 8,
+  sync_handle : 12,
+  _reserved_ : 4,
+  encryption : Enable,
+  broadcast_code : 8[16],
+  mse : 5,
+  _reserved_ : 3,
+  big_sync_timeout : 16,
+  _count_(bis) : 8,
+  bis : 8[],
+}
+
+packet LeBigCreateSyncStatus : CommandStatus (command_op_code = LE_BIG_CREATE_SYNC) {
+}
+
+packet LeBigTerminateSync : Command (op_code = LE_BIG_TERMINATE_SYNC) {
+  big_handle : 8,
+}
+
+packet LeBigTerminateSyncComplete : CommandComplete (command_op_code = LE_BIG_TERMINATE_SYNC) {
+  status : ErrorCode,
+  big_handle : 8,
+}
+
+packet LeRequestPeerSca : Command (op_code = LE_REQUEST_PEER_SCA) {
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet LeRequestPeerScaStatus : CommandStatus (command_op_code = LE_REQUEST_PEER_SCA) {
+}
+
+packet LeSetupIsoDataPath : Command (op_code = LE_SETUP_ISO_DATA_PATH) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  data_path_direction : DataPathDirection,
+  data_path_id : 8,
+  codec_id : 40,
+  controller_delay : 24,
+  _count_(codec_configuration) : 8,
+  codec_configuration : 8[],
+}
+
+packet LeSetupIsoDataPathComplete : CommandComplete (command_op_code = LE_SETUP_ISO_DATA_PATH) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+enum RemoveDataPathDirection : 8 {
+  INPUT = 1,
+  OUTPUT = 2,
+  INPUT_AND_OUTPUT = 3,
+}
+
+packet LeRemoveIsoDataPath : Command (op_code = LE_REMOVE_ISO_DATA_PATH) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  remove_data_path_direction : RemoveDataPathDirection,
+}
+
+packet LeRemoveIsoDataPathComplete : CommandComplete (command_op_code = LE_REMOVE_ISO_DATA_PATH) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+enum LeHostFeatureBits : 8 {
+  CONNECTED_ISO_STREAM_HOST_SUPPORT = 32,
+  CONNECTION_SUBRATING_HOST_SUPPORT = 38,
+}
+
+packet LeSetHostFeature : Command (op_code = LE_SET_HOST_FEATURE) {
+  bit_number : LeHostFeatureBits,
+  bit_value:  Enable,
+}
+
+packet LeSetHostFeatureComplete : CommandComplete (command_op_code = LE_SET_HOST_FEATURE) {
+  status : ErrorCode,
+}
+
+packet LeReadIsoLinkQuality : Command (op_code = LE_READ_ISO_LINK_QUALITY) {
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet LeReadIsoLinkQualityComplete : CommandComplete (command_op_code = LE_READ_ISO_LINK_QUALITY) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+  tx_unacked_packets : 32,
+  tx_flushed_packets : 32,
+  tx_last_subevent_packets : 32,
+  retransmitted_packets : 32,
+  crc_error_packets : 32,
+  rx_unreceived_packets : 32,
+  duplicate_packets : 32,
+}
+
+packet LeEnhancedReadTransmitPowerLevel : Command (op_code = LE_ENHANCED_READ_TRANSMIT_POWER_LEVEL) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  phy : 8,
+}
+
+enum PhyWithCodedSpecified : 8 {
+  LE_1M = 1,
+  LE_2M = 2,
+  LE_CODED_S_8 = 3,
+  LE_CODED_S_2 = 4,
+}
+
+packet LeEnhancedReadTransmitPowerLevelComplete : CommandComplete (command_op_code = LE_ENHANCED_READ_TRANSMIT_POWER_LEVEL) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+  phy : PhyWithCodedSpecified,
+  current_transmit_power_level : 8,
+  max_transmit_power_level : 8,
+}
+
+packet LeReadRemoteTransmitPowerLevel : Command (op_code = LE_READ_REMOTE_TRANSMIT_POWER_LEVEL) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  phy : 8,
+}
+
+packet LeReadRemoteTransmitPowerLevelStatus : CommandStatus (command_op_code = LE_READ_REMOTE_TRANSMIT_POWER_LEVEL) {
+}
+
+packet LeSetPathLossReportingParameters : Command (op_code = LE_SET_PATH_LOSS_REPORTING_PARAMETERS) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  high_threshold : 8,
+  high_hysteresis : 8,
+  low_threshold : 8,
+  low_hysteresis : 8,
+  min_time_spent : 16,
+}
+
+packet LeSetPathLossReportingParametersComplete : CommandComplete (command_op_code = LE_SET_PATH_LOSS_REPORTING_PARAMETERS) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet LeSetPathLossReportingEnable : Command (op_code = LE_SET_PATH_LOSS_REPORTING_ENABLE) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  enable : 8,
+}
+
+packet LeSetPathLossReportingEnableComplete : CommandComplete (command_op_code = LE_SET_PATH_LOSS_REPORTING_ENABLE) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet LeSetTransmitPowerReportingEnable : Command (op_code = LE_SET_TRANSMIT_POWER_REPORTING_ENABLE) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  local_enable : 8,
+  remote_enable : 8,
+}
+
+packet LeSetTransmitPowerReportingEnableComplete : CommandComplete (command_op_code = LE_SET_TRANSMIT_POWER_REPORTING_ENABLE) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet LeSetDataRelatedAddressChanges : Command (op_code = LE_SET_DATA_RELATED_ADDRESS_CHANGES) {
+  advertising_handle : 8,
+  change_reasons : 8,
+}
+
+packet LeSetDataRelatedAddressChangesComplete : CommandComplete (command_op_code = LE_SET_DATA_RELATED_ADDRESS_CHANGES) {
+  status : ErrorCode,
+}
+
+packet LeSetDefaultSubrate : Command (op_code = LE_SET_DEFAULT_SUBRATE) {
+  subrate_min : 9,
+  _reserved_ : 7,
+  subrate_max : 9,
+  _reserved_ : 7,
+  max_latency : 9,
+  _reserved_ : 7,
+  continuation_number : 9,
+  _reserved_ : 7,
+  supervision_timeout: 12,
+  _reserved_ : 4,
+}
+
+packet LeSetDefaultSubrateComplete : CommandComplete (command_op_code = LE_SET_DEFAULT_SUBRATE) {
+  status : ErrorCode,
+}
+
+packet LeSubrateRequest : Command (op_code = LE_SUBRATE_REQUEST) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  subrate_min : 9,
+  _reserved_ : 7,
+  subrate_max : 9,
+  _reserved_ : 7,
+  max_latency : 9,
+  _reserved_ : 7,
+  continuation_number : 9,
+  _reserved_ : 7,
+  supervision_timeout: 12,
+  _reserved_ : 4,
+}
+
+packet LeSubrateRequestStatus : CommandStatus (command_op_code = LE_SUBRATE_REQUEST) {
+}
+
+// HCI Event Packets
+
+packet InquiryComplete : Event (event_code = INQUIRY_COMPLETE) {
+  status : ErrorCode,
+}
+
+struct InquiryResponse {
+  bd_addr : Address,
+  page_scan_repetition_mode : PageScanRepetitionMode,
+  _reserved_ : 8,
+  _reserved_ : 8,
+  class_of_device : 24,
+  clock_offset : 15,
+  _reserved_ : 1,
+}
+
+packet InquiryResult : Event (event_code = INQUIRY_RESULT) {
+  _count_(responses) : 8,
+  responses : InquiryResponse[],
+}
+
+enum LinkType : 8 {
+  SCO = 0x00,
+  ACL = 0x01,
+}
+
+packet ConnectionComplete : Event (event_code = CONNECTION_COMPLETE) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+  bd_addr : Address,
+  link_type : LinkType,
+  encryption_enabled : Enable,
+}
+
+enum ConnectionRequestLinkType : 8 {
+  UNKNOWN = 0xFF,
+  SCO = 0x00,
+  ACL = 0x01,
+  ESCO = 0x02,
+}
+
+packet ConnectionRequest : Event (event_code = CONNECTION_REQUEST) {
+  bd_addr : Address,
+  class_of_device : 24,
+  link_type : ConnectionRequestLinkType,
+}
+
+packet DisconnectionComplete : Event (event_code = DISCONNECTION_COMPLETE) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+  reason : ErrorCode,
+}
+
+packet AuthenticationComplete : Event (event_code = AUTHENTICATION_COMPLETE) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet RemoteNameRequestComplete : Event (event_code = REMOTE_NAME_REQUEST_COMPLETE) {
+  status : ErrorCode,
+  bd_addr : Address,
+  remote_name : 8[248], // UTF-8 encoded user-friendly descriptive name
+}
+
+enum EncryptionEnabled : 8 {
+  OFF = 0x00,
+  ON = 0x01, // E0 for BR/EDR and AES-CCM for LE
+  BR_EDR_AES_CCM = 0x02,
+}
+
+packet EncryptionChange : Event (event_code = ENCRYPTION_CHANGE) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+  encryption_enabled : EncryptionEnabled,
+}
+
+packet ChangeConnectionLinkKeyComplete : Event (event_code = CHANGE_CONNECTION_LINK_KEY_COMPLETE) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet CentralLinkKeyComplete : Event (event_code = CENTRAL_LINK_KEY_COMPLETE) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+  key_flag : KeyFlag,
+}
+
+packet ReadRemoteSupportedFeaturesComplete : Event (event_code = READ_REMOTE_SUPPORTED_FEATURES_COMPLETE) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+  lmp_features : 64,
+}
+
+packet ReadRemoteVersionInformationComplete : Event (event_code = READ_REMOTE_VERSION_INFORMATION_COMPLETE) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+  version : 8,
+  manufacturer_name : 16,
+  sub_version : 16,
+}
+
+packet QosSetupComplete : Event (event_code = QOS_SETUP_COMPLETE) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+  _reserved_ : 8,
+  service_type : ServiceType,
+  token_rate : 32, // Octets/s
+  peak_bandwidth : 32, // Octets/s
+  latency : 32, // Octets/s
+  delay_variation : 32, // microseconds
+}
+
+// Command Complete and Command Status Events are implemented above Commands.
+
+packet HardwareError : Event (event_code = HARDWARE_ERROR) {
+  hardware_code : 8,
+}
+
+packet FlushOccurred : Event (event_code = FLUSH_OCCURRED) {
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet RoleChange : Event (event_code = ROLE_CHANGE) {
+  status : ErrorCode,
+  bd_addr : Address,
+  new_role : Role,
+}
+
+packet NumberOfCompletedPackets : Event (event_code = NUMBER_OF_COMPLETED_PACKETS) {
+  _count_(completed_packets) : 8,
+  completed_packets : CompletedPackets[],
+}
+
+enum Mode : 8 {
+  ACTIVE = 0x00,
+  HOLD = 0x01,
+  SNIFF = 0x02,
+}
+
+packet ModeChange : Event (event_code = MODE_CHANGE) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+  current_mode : Mode,
+  interval : 16, // 0x002 - 0xFFFE (1.25ms - 40.9s)
+}
+
+struct ZeroKeyAndAddress {
+  address : Address,
+  _fixed_ = 0 : 64,
+  _fixed_ = 0 : 64,
+}
+
+packet ReturnLinkKeys : Event (event_code = RETURN_LINK_KEYS) {
+  _count_(keys) : 8,
+  keys : ZeroKeyAndAddress[],
+}
+
+packet PinCodeRequest : Event (event_code = PIN_CODE_REQUEST) {
+  bd_addr : Address,
+}
+
+packet LinkKeyRequest : Event (event_code = LINK_KEY_REQUEST) {
+  bd_addr : Address,
+}
+
+enum KeyType : 8 {
+  COMBINATION = 0x00,
+  DEBUG_COMBINATION = 0x03,
+  UNAUTHENTICATED_P192 = 0x04,
+  AUTHENTICATED_P192 = 0x05,
+  CHANGED = 0x06,
+  UNAUTHENTICATED_P256 = 0x07,
+  AUTHENTICATED_P256 = 0x08,
+}
+
+packet LinkKeyNotification : Event (event_code = LINK_KEY_NOTIFICATION) {
+  bd_addr : Address,
+  link_key : 8[16],
+  key_type : KeyType,
+}
+
+packet LoopbackCommand : Event (event_code = LOOPBACK_COMMAND) {
+  _payload_, // Command packet, truncated if it was longer than 252 bytes
+}
+
+packet DataBufferOverflow : Event (event_code = DATA_BUFFER_OVERFLOW) {
+  link_type : LinkType,
+}
+
+packet MaxSlotsChange : Event (event_code = MAX_SLOTS_CHANGE) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  lmp_max_slots : 8,
+}
+
+packet ReadClockOffsetComplete : Event (event_code = READ_CLOCK_OFFSET_COMPLETE) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+  clock_offset : 15,
+  _reserved_ : 1,
+}
+
+packet ConnectionPacketTypeChanged : Event (event_code = CONNECTION_PACKET_TYPE_CHANGED) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+  packet_type : 16,
+}
+
+packet QosViolation : Event (event_code = QOS_VIOLATION) {
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet PageScanRepetitionModeChange : Event (event_code = PAGE_SCAN_REPETITION_MODE_CHANGE) {
+  bd_addr : Address,
+  page_scan_repetition_mode : PageScanRepetitionMode,
+}
+
+packet FlowSpecificationComplete : Event (event_code = FLOW_SPECIFICATION_COMPLETE) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+  _reserved_ : 8,
+  flow_direction : FlowDirection,
+  service_type : ServiceType,
+  token_rate : 32, // Octets/s
+  token_bucket_size : 32,
+  peak_bandwidth : 32, // Octets/s
+  access_latency : 32, // Octets/s
+}
+
+struct InquiryResponseWithRssi {
+  address : Address,
+  page_scan_repetition_mode : PageScanRepetitionMode,
+  _reserved_ : 8,
+  class_of_device : 24,
+  clock_offset : 15,
+  _reserved_ : 1,
+  rssi : 8,
+}
+
+packet InquiryResultWithRssi : Event (event_code = INQUIRY_RESULT_WITH_RSSI) {
+  _count_(responses) : 8,
+  responses : InquiryResponseWithRssi[],
+}
+
+packet ReadRemoteExtendedFeaturesComplete : Event (event_code = READ_REMOTE_EXTENDED_FEATURES_COMPLETE) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+  page_number : 8,
+  maximum_page_number : 8,
+  extended_lmp_features : 64,
+}
+
+enum ScoLinkType : 8 {
+  SCO = 0x00,
+  ESCO = 0x02,
+}
+
+enum ScoAirMode : 8 {
+  ULAW_LOG = 0x00,
+  ALAW_LOG = 0x01,
+  CVSD = 0x02,
+  TRANSPARENT = 0x03,
+}
+
+packet SynchronousConnectionComplete : Event (event_code = SYNCHRONOUS_CONNECTION_COMPLETE) {
+  status : ErrorCode,
+  connection_handle : 12,
+   _reserved_ : 4,
+  bd_addr : Address,
+  link_type : ScoLinkType,
+  // Time between two consecutive eSCO instants measured in slots.
+  // eSCO only, Shall be zero for SCO links.
+  transmission_interval_slots : 8,
+  // The size of the retransmission window measured in slots.
+  // eSCO only. Shall be zero for SCO links.
+  retransmission_window_slots : 8,
+  // Length in bytes of the eSCO payload in the receive direction.
+  // eSCO only. Shall be zero for SCO links.
+  rx_packet_length : 16,
+  // Length in bytes of the eSCO payload in the transmit direction.
+  // eSCO only. Shall be zero for SCO links.
+  tx_packet_length : 16,
+  air_mode : ScoAirMode,
+}
+
+test SynchronousConnectionComplete {
+  "\x2c\x11\x00\x03\x00\x1d\xdf\xed\x2b\x1a\xf8\x02\x0c\x04\x3c\x00\x3c\x00\x03",
+}
+
+packet SynchronousConnectionChanged : Event (event_code = SYNCHRONOUS_CONNECTION_CHANGED) {
+  status : ErrorCode,
+  connection_handle : 12,
+   _reserved_ : 4,
+  // Time between two consecutive eSCO instants measured in slots.
+  // eSCO only, Shall be zero for SCO links.
+  transmission_interval_slots : 8,
+  // Time between two consecutive SCO/eSCO instants measured in slots.
+  retransmission_window_slots : 8,
+  // Length in bytes of the SCO/eSCO payload in the receive direction.
+  rx_packet_length : 16,
+  // Length in bytes of the SCO/eSCO payload in the transmit direction.
+  tx_packet_length : 16,
+}
+
+packet SniffSubratingEvent : Event (event_code = SNIFF_SUBRATING) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+  maximum_transmit_latency : 16, // 0x000 - 0xFFFE (0s - 40.9s)
+  maximum_receive_latency : 16, // 0x000 - 0xFFFE (0s - 40.9s)
+  minimum_remote_timeout : 16, // 0x000 - 0xFFFE (0s - 40.9s)
+  minimum_local_timeout : 16, // 0x000 - 0xFFFE (0s - 40.9s)
+}
+
+packet ExtendedInquiryResult : Event (event_code = EXTENDED_INQUIRY_RESULT) {
+  _fixed_ = 0x01 : 8,
+  address : Address,
+  page_scan_repetition_mode : PageScanRepetitionMode,
+  _reserved_ : 8,
+  class_of_device : 24,
+  clock_offset : 15,
+  _reserved_ : 1,
+  rssi : 8,
+  // Extended inquiry Result is always 255 bytes long;
+  // the gap data is padded with zeros as necessary.
+  // Refer to BLUETOOTH CORE SPECIFICATION Version 5.2 | Vol 3, Part C Section 8 on page 1340
+  extended_inquiry_response : 8[240],
+}
+
+packet EncryptionKeyRefreshComplete : Event (event_code = ENCRYPTION_KEY_REFRESH_COMPLETE) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+packet IoCapabilityRequest : Event (event_code = IO_CAPABILITY_REQUEST) {
+  bd_addr : Address,
+}
+
+packet IoCapabilityResponse : Event (event_code = IO_CAPABILITY_RESPONSE) {
+  bd_addr : Address,
+  io_capability : IoCapability,
+  oob_data_present : OobDataPresent,
+  authentication_requirements : AuthenticationRequirements,
+}
+
+packet UserConfirmationRequest : Event (event_code = USER_CONFIRMATION_REQUEST) {
+  bd_addr : Address,
+  numeric_value : 20, // 0x00000-0xF423F (000000 - 999999)
+  _reserved_ : 12,
+}
+
+packet UserPasskeyRequest : Event (event_code = USER_PASSKEY_REQUEST) {
+  bd_addr : Address,
+}
+
+packet RemoteOobDataRequest : Event (event_code = REMOTE_OOB_DATA_REQUEST) {
+  bd_addr : Address,
+}
+
+packet SimplePairingComplete : Event (event_code = SIMPLE_PAIRING_COMPLETE) {
+  status : ErrorCode,
+  bd_addr : Address,
+}
+
+packet LinkSupervisionTimeoutChanged : Event (event_code = LINK_SUPERVISION_TIMEOUT_CHANGED) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  link_supervision_timeout : 16, // 0x001-0xFFFF (0.625ms-40.9s)
+}
+
+enum FlushablePacketType : 8 {
+  AUTOMATICALLY_FLUSHABLE_ONLY = 0,
+}
+
+packet EnhancedFlush : Command (op_code = ENHANCED_FLUSH) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  packet_type : FlushablePacketType,
+}
+
+test EnhancedFlush {
+  "\x5f\x0c\x03\x02\x00\x00",
+}
+
+packet EnhancedFlushStatus : CommandStatus (command_op_code = ENHANCED_FLUSH) {
+}
+
+packet EnhancedFlushComplete : Event (event_code = ENHANCED_FLUSH_COMPLETE) {
+  connection_handle : 12,
+  _reserved_ : 4,
+}
+
+test EnhancedFlushComplete {
+  "\x39\x02\x02\x00",
+}
+
+packet UserPasskeyNotification : Event (event_code = USER_PASSKEY_NOTIFICATION) {
+  bd_addr : Address,
+  passkey : 20, // 0x00000-0xF423F (000000 - 999999)
+  _reserved_ : 12,
+}
+
+packet KeypressNotification : Event (event_code = KEYPRESS_NOTIFICATION) {
+  bd_addr : Address,
+  notification_type : KeypressNotificationType,
+}
+
+packet RemoteHostSupportedFeaturesNotification : Event (event_code = REMOTE_HOST_SUPPORTED_FEATURES_NOTIFICATION) {
+  bd_addr : Address,
+  host_supported_features : 64,
+}
+
+packet LeMetaEvent : Event (event_code = LE_META_EVENT) {
+  subevent_code : SubeventCode,
+  _body_,
+}
+
+packet NumberOfCompletedDataBlocks : Event (event_code = NUMBER_OF_COMPLETED_DATA_BLOCKS) {
+  total_num_data_blocks : 16,
+  _payload_, // placeholder (unimplemented)
+}
+
+// LE Events
+packet LeConnectionComplete : LeMetaEvent (subevent_code = CONNECTION_COMPLETE) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+  role : Role,
+  peer_address_type : AddressType,
+  peer_address : Address,
+  connection_interval : 16, // 0x006 - 0x0C80 (7.5ms - 4000ms)
+  peripheral_latency : 16,
+  supervision_timeout : 16,  // 0x000A to 0x0C80 (100ms to 32s)
+  central_clock_accuracy : ClockAccuracy,
+}
+
+enum AdvertisingEventType : 8 {
+  ADV_IND = 0x00,
+  ADV_DIRECT_IND = 0x01,
+  ADV_SCAN_IND = 0x02,
+  ADV_NONCONN_IND = 0x03,
+  SCAN_RESPONSE = 0x04,
+}
+
+struct LeAdvertisingResponse {
+  event_type : AdvertisingEventType,
+  address_type : AddressType,
+  address : Address,
+  _size_(advertising_data) : 8,
+  advertising_data : 8[],
+  rssi : 8,
+}
+
+packet LeAdvertisingReport : LeMetaEvent (subevent_code = ADVERTISING_REPORT) {
+  _count_(responses) : 8,
+  responses : LeAdvertisingResponse[],
+}
+
+packet LeConnectionUpdateComplete : LeMetaEvent (subevent_code = CONNECTION_UPDATE_COMPLETE) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+  connection_interval : 16, // 0x006 - 0x0C80 (7.5ms - 4000ms)
+  peripheral_latency : 16,  // Number of connection events
+  supervision_timeout : 16,  // 0x000A to 0x0C80 (100ms to 32s)
+}
+
+packet LeReadRemoteFeaturesComplete : LeMetaEvent (subevent_code = READ_REMOTE_FEATURES_COMPLETE) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+  le_features : 64,
+}
+
+packet LeLongTermKeyRequest : LeMetaEvent (subevent_code = LONG_TERM_KEY_REQUEST) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  random_number : 8[8],
+  encrypted_diversifier : 16,
+}
+
+packet LeRemoteConnectionParameterRequest : LeMetaEvent (subevent_code = REMOTE_CONNECTION_PARAMETER_REQUEST) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  interval_min : 16, // 0x006 - 0x0C80 (7.5ms - 4s)
+  interval_max : 16, // 0x006 - 0x0C80 (7.5ms - 4s)
+  latency : 16,  // Number of connection events (0x0000 to 0x01f3 (499)
+  timeout : 16,  // 0x000A to 0x0C80 (100ms to 32s)
+}
+
+packet LeDataLengthChange : LeMetaEvent (subevent_code = DATA_LENGTH_CHANGE) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  max_tx_octets : 16, // 0x001B - 0x00FB
+  max_tx_time : 16, // 0x0148 - 0x4290
+  max_rx_octets : 16, // 0x001B - 0x00FB
+  max_rx_time : 16, // 0x0148 - 0x4290
+}
+
+packet ReadLocalP256PublicKeyComplete : LeMetaEvent (subevent_code = READ_LOCAL_P256_PUBLIC_KEY_COMPLETE) {
+  status : ErrorCode,
+  local_p_256_public_key : 8[64],
+}
+
+packet GenerateDhKeyComplete : LeMetaEvent (subevent_code = GENERATE_DHKEY_COMPLETE) {
+  status : ErrorCode,
+  dh_key : 8[32],
+}
+
+packet LeEnhancedConnectionComplete : LeMetaEvent (subevent_code = ENHANCED_CONNECTION_COMPLETE) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+  role : Role,
+  peer_address_type : AddressType,
+  peer_address : Address,
+  local_resolvable_private_address : Address,
+  peer_resolvable_private_address : Address,
+  connection_interval : 16, // 0x006 - 0x0C80 (7.5ms - 4000ms)
+  peripheral_latency : 16,
+  supervision_timeout : 16,  // 0x000A to 0x0C80 (100ms to 32s)
+  central_clock_accuracy : ClockAccuracy,
+}
+
+enum DirectAdvertisingAddressType : 8 {
+  PUBLIC_DEVICE_ADDRESS = 0x00,
+  RANDOM_DEVICE_ADDRESS = 0x01,
+  PUBLIC_IDENTITY_ADDRESS = 0x02,
+  RANDOM_IDENTITY_ADDRESS = 0x03,
+  CONTROLLER_UNABLE_TO_RESOLVE = 0xFE,
+  NO_ADDRESS_PROVIDED = 0xFF,
+}
+
+enum DirectAdvertisingEventType : 8 {
+  ADV_DIRECT_IND = 0x01,
+}
+
+enum DirectAddressType : 8 {
+  RANDOM_DEVICE_ADDRESS = 0x01,
+}
+
+struct LeDirectedAdvertisingResponse {
+  event_type : DirectAdvertisingEventType,
+  address_type : DirectAdvertisingAddressType,
+  address : Address,
+  direct_address_type : DirectAddressType,
+  direct_address : Address,
+  rssi : 8,
+}
+
+packet LeDirectedAdvertisingReport : LeMetaEvent (subevent_code = DIRECTED_ADVERTISING_REPORT) {
+  _count_(responses) : 8,
+  responses : LeDirectedAdvertisingResponse[],
+}
+
+packet LePhyUpdateComplete : LeMetaEvent (subevent_code = PHY_UPDATE_COMPLETE) {
+    status : ErrorCode,
+    connection_handle : 12,
+    _reserved_ : 4,
+    tx_phy : PhyType,
+    rx_phy : PhyType,
+}
+
+enum DataStatus : 2 {
+  COMPLETE = 0x0,
+  CONTINUING = 0x1,
+  TRUNCATED = 0x2,
+  RESERVED = 0x3,
+}
+
+struct LeExtendedAdvertisingResponse {
+  connectable : 1,
+  scannable : 1,
+  directed : 1,
+  scan_response : 1,
+  legacy : 1,
+  data_status : DataStatus,
+  _reserved_ : 9,
+  address_type : DirectAdvertisingAddressType,
+  address : Address,
+  primary_phy : PrimaryPhyType,
+  secondary_phy : SecondaryPhyType,
+  advertising_sid : 8, // SID subfield in the ADI field
+  tx_power : 8,
+  rssi : 8, // -127 to +20 (0x7F means not available)
+  periodic_advertising_interval : 16, // 0x006 to 0xFFFF (7.5 ms to 82s)
+  direct_address_type : DirectAdvertisingAddressType,
+  direct_address : Address,
+  _size_(advertising_data) : 8,
+  advertising_data: 8[],
+}
+
+packet LeExtendedAdvertisingReport : LeMetaEvent (subevent_code = EXTENDED_ADVERTISING_REPORT) {
+  _count_(responses) : 8,
+  responses : LeExtendedAdvertisingResponse[],
+}
+
+packet LePeriodicAdvertisingSyncEstablished : LeMetaEvent (subevent_code = PERIODIC_ADVERTISING_SYNC_ESTABLISHED) {
+  status : ErrorCode,
+  sync_handle : 12,
+  _reserved_ : 4,
+  advertising_sid : 8,
+  advertiser_address_type : AddressType,
+  advertiser_address : Address,
+  advertiser_phy : SecondaryPhyType,
+  periodic_advertising_interval : 16,
+  advertiser_clock_accuracy : ClockAccuracy,
+}
+
+packet LePeriodicAdvertisingReport : LeMetaEvent (subevent_code = PERIODIC_ADVERTISING_REPORT) {
+  sync_handle : 12,
+  _reserved_ : 4,
+  tx_power : 8,
+  rssi : 8,
+  cte_type : CteType,
+  data_status : DataStatus,
+  _reserved_: 6,
+  _size_(data) : 8,
+  data : 8[],
+}
+
+packet LePeriodicAdvertisingSyncLost : LeMetaEvent (subevent_code = PERIODIC_ADVERTISING_SYNC_LOST) {
+  sync_handle : 12,
+  _reserved_ : 4,
+}
+
+packet LeScanTimeout : LeMetaEvent (subevent_code = SCAN_TIMEOUT) {
+}
+
+packet LeAdvertisingSetTerminated : LeMetaEvent (subevent_code = ADVERTISING_SET_TERMINATED) {
+  status : ErrorCode,
+  advertising_handle : 8,
+  connection_handle : 12,
+  _reserved_ : 4,
+  num_completed_extended_advertising_events : 8,
+}
+
+packet LeScanRequestReceived : LeMetaEvent (subevent_code = SCAN_REQUEST_RECEIVED) {
+  advertising_handle : 8,
+  scanner_address_type : AddressType,
+  scanner_address : Address,
+}
+
+enum ChannelSelectionAlgorithm : 8 {
+  ALGORITHM_1 = 0,
+  ALGORITHM_2 = 1,
+}
+
+packet LeChannelSelectionAlgorithm : LeMetaEvent (subevent_code = CHANNEL_SELECTION_ALGORITHM) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  channel_selection_algorithm : ChannelSelectionAlgorithm,
+}
+
+packet LeConnectionlessIqReport : LeMetaEvent (subevent_code = CONNECTIONLESS_IQ_REPORT) {
+  _payload_, // placeholder (unimplemented)
+}
+
+packet LeConnectionIqReport : LeMetaEvent (subevent_code = CONNECTION_IQ_REPORT) {
+  _payload_, // placeholder (unimplemented)
+}
+
+packet LeCteRequestFailed : LeMetaEvent (subevent_code = CTE_REQUEST_FAILED) {
+  _payload_, // placeholder (unimplemented)
+}
+
+packet LePeriodicAdvertisingSyncTransferReceived : LeMetaEvent (subevent_code = PERIODIC_ADVERTISING_SYNC_TRANSFER_RECEIVED) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+  service_data : 16,
+  sync_handle : 12,
+  _reserved_ : 4,
+  advertising_sid : 4,
+  _reserved_ : 4,
+  advertiser_address_type : AddressType,
+  advertiser_address : Address,
+  advertiser_phy : SecondaryPhyType,
+  periodic_advertising_interval : 16,
+  advertiser_clock_accuracy : ClockAccuracy,
+}
+
+packet LeCisEstablished : LeMetaEvent (subevent_code = CIS_ESTABLISHED) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+  cig_sync_delay : 24,
+  cis_sync_delay : 24,
+  transport_latency_c_to_p : 24,
+  transport_latency_p_to_c : 24,
+  phy_c_to_p : SecondaryPhyType,
+  phy_p_to_c : SecondaryPhyType,
+  nse : 8,
+  bn_c_to_p : 4,
+  _reserved_ : 4,
+  bn_p_to_c : 4,
+  _reserved_ : 4,
+  ft_c_to_p : 8,
+  ft_p_to_c : 8,
+  max_pdu_c_to_p : 8,
+  _reserved_ : 8,
+  max_pdu_p_to_c : 8,
+  _reserved_ : 8,
+  iso_interval : 16,
+}
+
+packet LeCisRequest : LeMetaEvent (subevent_code = CIS_REQUEST) {
+  acl_connection_handle : 12,
+  _reserved_ : 4,
+  cis_connection_handle : 12,
+  _reserved_ : 4,
+  cig_id : 8,
+  cis_id : 8,
+}
+
+packet LeCreateBigComplete : LeMetaEvent (subevent_code = CREATE_BIG_COMPLETE) {
+  status : ErrorCode,
+  big_handle : 8,
+  big_sync_delay : 24,
+  transport_latency_big: 24,
+  phy : SecondaryPhyType,
+  nse : 8,
+  bn : 8,
+  pto : 8,
+  irc : 8,
+  max_pdu : 16,
+  iso_interval : 16,
+  _size_(connection_handle) : 8,
+  connection_handle : 16[],
+}
+
+packet LeTerminateBigComplete : LeMetaEvent (subevent_code = TERMINATE_BIG_COMPLETE) {
+  big_handle : 8,
+  reason : ErrorCode,
+}
+
+packet LeBigSyncEstablished : LeMetaEvent (subevent_code = BIG_SYNC_ESTABLISHED) {
+  status : ErrorCode,
+  big_handle : 8,
+  transport_latency_big : 24,
+  nse : 8,
+  bn : 8,
+  pto : 8,
+  irc : 8,
+  max_pdu : 16,
+  iso_interval : 16,
+  _size_(connection_handle) : 8,
+  connection_handle : 16[],
+}
+
+packet LeBigSyncLost : LeMetaEvent (subevent_code = BIG_SYNC_LOST) {
+  big_handle : 8,
+  reason : ErrorCode,
+}
+
+packet LeRequestPeerScaComplete : LeMetaEvent (subevent_code = REQUEST_PEER_SCA_COMPLETE) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+  peer_clock_accuracy : ClockAccuracy,
+}
+
+enum PathLossZone : 8 {
+  LOW = 0,
+  MID = 1,
+  HIGH = 2,
+}
+
+packet LePathLossThreshold : LeMetaEvent (subevent_code = PATH_LOSS_THRESHOLD) {
+  connection_handle : 12,
+  _reserved_ : 4,
+  current_path_loss : 8,
+  zone_entered : PathLossZone,
+}
+
+enum ReportingReason : 8 {
+  LOCAL_TRANSMIT_POWER_CHANGED = 0x00,
+  REMOTE_TRANSMIT_POWER_CHANGED = 0x01,
+  READ_COMMAND_COMPLETE = 0x02,
+}
+
+packet LeTransmitPowerReporting : LeMetaEvent (subevent_code = TRANSMIT_POWER_REPORTING) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+  reason : ReportingReason,
+  phy : 8,
+  transmit_power_level : 8,
+  transmit_power_level_flag : 8,
+  delta : 8,
+}
+
+packet LeBigInfoAdvertisingReport : LeMetaEvent (subevent_code = BIG_INFO_ADVERTISING_REPORT) {
+  sync_handle : 12,
+  _reserved_ : 4,
+  num_bis : 8,
+  nse : 8,
+  iso_interval : 16,
+  bn : 8,
+  pto : 8,
+  irc : 8,
+  max_pdu : 16,
+  sdu_interval : 24,
+  max_sdu : 16,
+  phy : SecondaryPhyType,
+  framing : Enable,
+  encryption : Enable,
+}
+
+packet LeSubrateChange : LeMetaEvent (subevent_code = LE_SUBRATE_CHANGE) {
+  status : ErrorCode,
+  connection_handle : 12,
+  _reserved_ : 4,
+  subrate_factor : 9,
+  _reserved_ : 7,
+  peripheral_latency : 9,
+  _reserved_ : 7,
+  continuation_number : 9,
+  _reserved_ : 7,
+  supervision_timeout: 12,
+  _reserved_ : 4,
+}
+
+enum IsoPacketBoundaryFlag : 2 {
+  FIRST_FRAGMENT = 0,
+  CONTINUATION_FRAGMENT = 1,
+  COMPLETE_SDU = 2,
+  LAST_FRAGMENT = 3,
+}
+
+enum TimeStampFlag : 1 {
+  NOT_PRESENT = 0,
+  PRESENT = 1,
+}
+
+packet Iso {
+  connection_handle : 12,
+  pb_flag : IsoPacketBoundaryFlag,
+  ts_flag : TimeStampFlag,
+  _reserved_ : 1,
+  _size_(_payload_) : 14,
+  _reserved_ : 2,
+  _payload_,
+}
+
+enum IsoPacketStatusFlag : 2 {
+  VALID = 0,
+  POSSIBLY_INVALID = 1,
+  LOST_DATA = 2,
+}
+
+packet IsoWithTimestamp : Iso (ts_flag = PRESENT) {
+  time_stamp : 32,
+  packet_sequence_number : 16,
+  iso_sdu_length : 12,
+  _reserved_ : 2,
+  packet_status_flag : IsoPacketStatusFlag,
+  _payload_,
+}
+
+packet IsoWithoutTimestamp : Iso (ts_flag = NOT_PRESENT) {
+  packet_sequence_number : 16,
+  iso_sdu_length : 12,
+  _reserved_ : 2,
+  packet_status_flag : IsoPacketStatusFlag,
+  _payload_,
+}
+
+// https://www.bluetooth.com/specifications/assigned-numbers/generic-access-profile
+enum GapDataType : 8 {
+  INVALID = 0x00,
+  FLAGS = 0x01,
+  INCOMPLETE_LIST_16_BIT_UUIDS = 0x02,
+  COMPLETE_LIST_16_BIT_UUIDS = 0x03,
+  INCOMPLETE_LIST_32_BIT_UUIDS = 0x04,
+  COMPLETE_LIST_32_BIT_UUIDS = 0x05,
+  INCOMPLETE_LIST_128_BIT_UUIDS = 0x06,
+  COMPLETE_LIST_128_BIT_UUIDS = 0x07,
+  SHORTENED_LOCAL_NAME = 0x08,
+  COMPLETE_LOCAL_NAME = 0x09,
+  TX_POWER_LEVEL = 0x0A,
+  CLASS_OF_DEVICE = 0x0D,
+  SIMPLE_PAIRING_HASH_C = 0x0E,
+  SIMPLE_PAIRING_RANDOMIZER_R = 0x0F,
+  DEVICE_ID = 0x10,
+  SECURITY_MANAGER_OOB_FLAGS = 0x11,
+  SLAVE_CONNECTION_INTERVAL_RANGE = 0x12,
+  LIST_16BIT_SERVICE_SOLICITATION_UUIDS = 0x14,
+  LIST_128BIT_SERVICE_SOLICITATION_UUIDS = 0x15,
+  SERVICE_DATA_16_BIT_UUIDS = 0x16,
+  PUBLIC_TARGET_ADDRESS = 0x17,
+  RANDOM_TARGET_ADDRESS = 0x18,
+  APPEARANCE = 0x19,
+  ADVERTISING_INTERVAL = 0x1A,
+  LE_BLUETOOTH_DEVICE_ADDRESS = 0x1B,
+  LE_ROLE = 0x1C,
+  SIMPLE_PAIRING_HASH_C_256 = 0x1D,
+  SIMPLE_PAIRING_RANDOMIZER_R_256 = 0x1E,
+  LIST_32BIT_SERVICE_SOLICITATION_UUIDS = 0x1F,
+  SERVICE_DATA_32_BIT_UUIDS = 0x20,
+  SERVICE_DATA_128_BIT_UUIDS = 0x21,
+  LE_SECURE_CONNECTIONS_CONFIRMATION_VALUE = 0x22,
+  LE_SECURE_CONNECTIONS_RANDOM_VALUE = 0x23,
+  URI = 0x24,
+  INDOOR_POSITIONING = 0x25,
+  TRANSPORT_DISCOVERY_DATA = 0x26,
+  LE_SUPPORTED_FEATURES = 0x27,
+  CHANNEL_MAP_UPDATE_INDICATION = 0x28,
+  MESH_PB_ADV = 0x29,
+  MESH_MESSAGE = 0x2A,
+  MESH_BEACON = 0x2B,
+  BIG_INFO = 0x2C,
+  BROADCAST_CODE = 0x2D,
+  THREE_D_INFORMATION_DATA = 0x3D,
+  MANUFACTURER_SPECIFIC_DATA = 0xFF,
+}
+
+// -----------------------------------------------------------------------------
+//  LE Get Vendor Capabilities Command
+//  https://source.android.com/docs/core/connect/bluetooth/hci_requirements#vendor-specific-capabilities
+// -----------------------------------------------------------------------------
+
+packet LeGetVendorCapabilities : Command (op_code = LE_GET_VENDOR_CAPABILITIES) {
+}
+
+test LeGetVendorCapabilities {
+  "\x53\xfd\x00",
+}
+
+struct BaseVendorCapabilities {
+  max_advt_instances: 8,
+  offloaded_resolution_of_private_address : 8,
+  total_scan_results_storage: 16,
+  max_irk_list_sz: 8,
+  filtering_support: 8,
+  max_filter: 8,
+  activity_energy_info_support: 8,
+}
+
+packet LeGetVendorCapabilitiesComplete : CommandComplete (command_op_code = LE_GET_VENDOR_CAPABILITIES) {
+  status : ErrorCode,
+  base_vendor_capabilities : BaseVendorCapabilities,
+  _payload_,
+}
+
+packet LeGetVendorCapabilitiesComplete095 : LeGetVendorCapabilitiesComplete {
+  version_supported: 16,
+  total_num_of_advt_tracked: 16,
+  extended_scan_support: 8,
+  debug_logging_supported: 8,
+  _payload_,
+}
+
+packet LeGetVendorCapabilitiesComplete096 : LeGetVendorCapabilitiesComplete095 {
+  le_address_generation_offloading_support: 8,
+  _payload_,
+}
+
+packet LeGetVendorCapabilitiesComplete098 : LeGetVendorCapabilitiesComplete096 {
+  a2dp_source_offload_capability_mask: 32,
+  bluetooth_quality_report_support: 8
+}
+
+// -----------------------------------------------------------------------------
+//  LE Batch Scan Command
+//  https://source.android.com/docs/core/connect/bluetooth/hci_requirements#le_batch_scan_command
+//  https://source.android.com/docs/core/connect/bluetooth/hci_requirements#storage-threshold-breach-subevent
+// -----------------------------------------------------------------------------
+
+enum BatchScanOpcode : 8 {
+  ENABLE = 0x01,
+  SET_STORAGE_PARAMETERS = 0x02,
+  SET_SCAN_PARAMETERS = 0x03,
+  READ_RESULT_PARAMETERS = 0x04,
+}
+
+packet LeBatchScan : Command (op_code = LE_BATCH_SCAN) {
+  batch_scan_opcode : BatchScanOpcode,
+  _body_,
+}
+
+packet LeBatchScanComplete : CommandComplete (command_op_code = LE_BATCH_SCAN) {
+  status : ErrorCode,
+  batch_scan_opcode : BatchScanOpcode,
+  _body_,
+}
+
+packet LeBatchScanEnable : LeBatchScan (batch_scan_opcode = ENABLE) {
+  enable : Enable,
+}
+
+packet LeBatchScanEnableComplete : LeBatchScanComplete (batch_scan_opcode = ENABLE) {
+}
+
+packet LeBatchScanSetStorageParameters : LeBatchScan (batch_scan_opcode = SET_STORAGE_PARAMETERS) {
+  batch_scan_full_max_percentage : 8,
+  batch_scan_truncated_max_percentage : 8,
+  batch_scan_notify_threshold_percentage : 8,
+}
+
+packet LeBatchScanSetStorageParametersComplete : LeBatchScanComplete (batch_scan_opcode = SET_STORAGE_PARAMETERS) {
+}
+
+enum BatchScanDiscardRule : 8 {
+  OLDEST = 0x00,
+  WEAKEST_RSSI = 0x01,
+}
+
+packet LeBatchScanSetScanParameters : LeBatchScan (batch_scan_opcode = SET_SCAN_PARAMETERS) {
+  truncated_mode_enabled : 1,
+  full_mode_enabled : 1,
+  _reserved_ : 6,
+  duty_cycle_scan_window_slots : 32,
+  duty_cycle_scan_interval_slots : 32,
+  own_address_type : PeerAddressType,
+  batch_scan_discard_rule : BatchScanDiscardRule,
+}
+
+packet LeBatchScanSetScanParametersComplete : LeBatchScanComplete (batch_scan_opcode = SET_SCAN_PARAMETERS) {
+}
+
+enum BatchScanDataRead : 8 {
+  TRUNCATED_MODE_DATA = 0x01,
+  FULL_MODE_DATA = 0x02,
+}
+
+packet LeBatchScanReadResultParameters : LeBatchScan (batch_scan_opcode = READ_RESULT_PARAMETERS) {
+  batch_scan_data_read : BatchScanDataRead,
+}
+
+packet LeBatchScanReadResultParametersCompleteRaw : LeBatchScanComplete (batch_scan_opcode = READ_RESULT_PARAMETERS) {
+  batch_scan_data_read : BatchScanDataRead,
+  num_of_records : 8,
+  raw_data : 8[],
+}
+
+packet LeBatchScanReadResultParametersComplete : LeBatchScanComplete (batch_scan_opcode = READ_RESULT_PARAMETERS) {
+  batch_scan_data_read : BatchScanDataRead,
+  _body_,
+}
+
+struct TruncatedResult {
+  bd_addr : Address,
+  address_type : AddressType,
+  tx_power : 8,
+  rssi : 8,
+  timestamp : 16,
+}
+
+packet LeBatchScanReadTruncatedResultParametersComplete : LeBatchScanReadResultParametersComplete (batch_scan_data_read = TRUNCATED_MODE_DATA) {
+  _count_(results) : 8,
+  results : TruncatedResult[],
+}
+
+struct FullResult {
+  bd_addr : Address,
+  address_type : AddressType,
+  tx_power : 8,
+  rssi : 8,
+  timestamp : 16,
+  _size_(adv_packet) : 8,
+  adv_packet : 8[],
+  _size_(scan_response) : 8,
+  scan_response : 8[],
+}
+
+packet LeBatchScanReadFullResultParametersComplete : LeBatchScanReadResultParametersComplete (batch_scan_data_read = FULL_MODE_DATA) {
+  _count_(results) : 8,
+  results : FullResult[],
+}
+
+packet StorageThresholdBreachEvent : VendorSpecificEvent (subevent_code = STORAGE_THRESHOLD_BREACH) {
+}
+
+// -----------------------------------------------------------------------------
+//  Advertising Packet Content Filter (APCF) Command.
+//  https://source.android.com/docs/core/connect/bluetooth/hci_requirements#advertising-packet-content-filter
+//  https://source.android.com/docs/core/connect/bluetooth/hci_requirements#le-advertisement-tracking-sub-event
+// -----------------------------------------------------------------------------
+
+enum ApcfOpcode : 8 {
+  ENABLE = 0x00,
+  SET_FILTERING_PARAMETERS = 0x01,
+  BROADCASTER_ADDRESS = 0x02,
+  SERVICE_UUID = 0x03,
+  SERVICE_SOLICITATION_UUID = 0x04,
+  LOCAL_NAME = 0x05,
+  MANUFACTURER_DATA = 0x06,
+  SERVICE_DATA = 0x07,
+  TRANSPORT_DISCOVERY_SERVICE = 0x08,
+  AD_TYPE_FILTER = 0x09,
+  READ_EXTENDED_FEATURES = 0xFF,
+}
+
+packet LeApcf : Command (op_code = LE_APCF) {
+  apcf_opcode : ApcfOpcode,
+  _payload_,
+}
+
+packet LeApcfComplete : CommandComplete (command_op_code = LE_APCF) {
+  status : ErrorCode,
+  apcf_opcode : ApcfOpcode,
+  _payload_
+}
+
+packet LeApcfEnable : LeApcf (apcf_opcode = ENABLE) {
+  apcf_enable : Enable,
+}
+
+packet LeApcfEnableComplete : LeApcfComplete (apcf_opcode = ENABLE) {
+  apcf_enable : Enable,
+}
+
+enum ApcfAction : 8 {
+  ADD = 0x00,
+  DELETE = 0x01,
+  CLEAR = 0x02,
+}
+
+enum DeliveryMode : 8 {
+  IMMEDIATE = 0x00,
+  ONFOUND = 0x01,
+  BATCHED = 0x02,
+}
+
+// Bit mask for the APCF Feature Selection field.
+enum ApcfFeatureSelection : 8 {
+  BROADCASTER_ADDRESS = 0x00,
+  SERVICE_DATA_CHANGE = 0x01,
+  SERVICE_UUID = 0x02,
+  SERVICE_SOLICITATION_UUID = 0x03,
+  LOCAL_NAME = 0x04,
+  MANUFACTURER_DATA = 0x05,
+  SERVICE_DATA = 0x06,
+  TRANSPORT_DISCOVERY_DATA = 0x07,
+  AD_TYPE = 0x08,
+}
+
+packet LeApcfSetFilteringParameters : LeApcf (apcf_opcode = SET_FILTERING_PARAMETERS) {
+  apcf_action : ApcfAction,
+  apcf_filter_index : 8,
+  apcf_feature_selection : 16,
+  apcf_list_logic_type : 16,
+  apcf_filter_logic_type : 8,
+  rssi_high_thresh : 8,
+  delivery_mode : DeliveryMode,
+  onfound_timeout : 16,
+  onfound_timeout_cnt : 8,
+  rssi_low_thresh : 8,
+  onlost_timeout : 16,
+  num_of_tracking_entries : 16,
+}
+
+packet LeApcfSetFilteringParametersComplete : LeApcfComplete (apcf_opcode = SET_FILTERING_PARAMETERS) {
+  apcf_action : ApcfAction,
+  apcf_available_spaces : 8,
+}
+
+enum ApcfApplicationAddressType : 8 {
+  PUBLIC = 0x00,
+  RANDOM = 0x01,
+  NOT_APPLICABLE = 0x02,
+}
+
+packet LeApcfBroadcasterAddress : LeApcf (apcf_opcode = BROADCASTER_ADDRESS) {
+  apcf_action : ApcfAction,
+  apcf_filter_index : 8,
+  apcf_broadcaster_address : Address,
+  apcf_application_address_type : ApcfApplicationAddressType,
+}
+
+packet LeApcfBroadcasterAddressComplete : LeApcfComplete (apcf_opcode = BROADCASTER_ADDRESS) {
+  apcf_action : ApcfAction,
+  apcf_available_spaces : 8,
+}
+
+packet LeApcfServiceUuid : LeApcf (apcf_opcode = SERVICE_UUID) {
+  apcf_action : ApcfAction,
+  apcf_filter_index : 8,
+  acpf_uuid_data : 8[],
+}
+
+packet LeApcfServiceUuidComplete : LeApcfComplete (apcf_opcode = SERVICE_UUID) {
+  apcf_action : ApcfAction,
+  apcf_available_spaces : 8,
+}
+
+packet LeApcfSolicitationUuid : LeApcf (apcf_opcode = SERVICE_SOLICITATION_UUID) {
+  apcf_action : ApcfAction,
+  apcf_filter_index : 8,
+  acpf_uuid_data : 8[],
+}
+
+packet LeApcfSolicitationUuidComplete : LeApcfComplete (apcf_opcode = SERVICE_SOLICITATION_UUID) {
+  apcf_action : ApcfAction,
+  apcf_available_spaces : 8,
+}
+
+packet LeApcfLocalName : LeApcf (apcf_opcode = LOCAL_NAME) {
+  apcf_action : ApcfAction,
+  apcf_filter_index : 8,
+  apcf_local_name : 8[],
+}
+
+packet LeApcfLocalNameComplete : LeApcfComplete (apcf_opcode = LOCAL_NAME) {
+  apcf_action : ApcfAction,
+  apcf_available_spaces : 8,
+}
+
+packet LeApcfManufacturerData : LeApcf (apcf_opcode = MANUFACTURER_DATA) {
+  apcf_action : ApcfAction,
+  apcf_filter_index : 8,
+  apcf_manufacturer_data : 8[],
+}
+
+packet LeApcfManufacturerDataComplete : LeApcfComplete (apcf_opcode = MANUFACTURER_DATA) {
+  apcf_action : ApcfAction,
+  apcf_available_spaces : 8,
+}
+
+packet LeApcfServiceData : LeApcf (apcf_opcode = SERVICE_DATA) {
+  apcf_action : ApcfAction,
+  apcf_filter_index : 8,
+  apcf_service_data : 8[],
+}
+
+packet LeApcfServiceDataComplete : LeApcfComplete (apcf_opcode = SERVICE_DATA) {
+  apcf_action : ApcfAction,
+  apcf_available_spaces : 8,
+}
+
+packet LeApcfADType : LeApcf (apcf_opcode = AD_TYPE_FILTER) {
+  apcf_action : ApcfAction,
+  apcf_filter_index : 8,
+  apcf_ad_type_data : 8[],
+}
+
+packet LeApcfADTypeComplete : LeApcfComplete (apcf_opcode = AD_TYPE_FILTER) {
+  apcf_action : ApcfAction,
+  apcf_available_spaces : 8,
+}
+
+packet LeApcfReadExtendedFeatures : LeApcf (apcf_opcode = READ_EXTENDED_FEATURES) {
+}
+
+test LeApcfReadExtendedFeatures {
+  "\x57\xfd\x01\xff",
+}
+
+packet LeApcfReadExtendedFeaturesComplete : LeApcfComplete (apcf_opcode = READ_EXTENDED_FEATURES) {
+  transport_discovery_data_filter : 1,
+  ad_type_filter : 1,
+  _reserved_ : 14,
+}
+
+test LeApcfReadExtendedFeaturesComplete {
+  "\x0e\x07\x01\x57\xfd\x00\xff\x03\x00",
+}
+
+enum AdvertiserState : 8 {
+  ADVERTISER_FOUND = 0x0,
+  ADVERTISER_LOST = 0x1,
+}
+
+enum AdvtInfoPresent : 8 {
+  ADVT_INFO_PRESENT = 0x0,
+  ADVT_INFO_NOT_PRESENT = 0x1,
+}
+
+struct AdvtInfo {
+  tx_power : 8,
+  rssi : 8,
+  timestamp : 16,
+  _size_(adv_packet) : 8,
+  adv_packet : 8[],
+  _size_(scan_data_resp) : 8,
+  scan_data_resp : 8[],
+}
+
+packet LeAdvertisementTrackingEvent : VendorSpecificEvent (subevent_code = LE_ADVERTISEMENT_TRACKING) {
+  apcf_filter_index : 8,
+  advertiser_state : AdvertiserState,
+  advt_info_present : AdvtInfoPresent,
+  advertiser_address : Address,
+  advertiser_address_type : PeerAddressType,
+  advt_info : AdvtInfo[],
+}
+
+// -----------------------------------------------------------------------------
+//  LE Get Controller Activity Energy Info Command
+//  https://source.android.com/docs/core/connect/bluetooth/hci_requirements#le_get_controller_activity_energy_info
+// -----------------------------------------------------------------------------
+
+packet LeGetControllerActivityEnergyInfo : Command (op_code = LE_GET_CONTROLLER_ACTIVITY_ENERGY_INFO) {
+}
+
+packet LeGetControllerActivityEnergyInfoComplete : CommandComplete (command_op_code = LE_GET_CONTROLLER_ACTIVITY_ENERGY_INFO) {
+  status : ErrorCode,
+  total_tx_time_ms : 32,
+  total_rx_time_ms : 32,
+  total_idle_time_ms : 32,
+  total_energy_used : 32,
+}
+
+// -----------------------------------------------------------------------------
+//  LE Extended Set Scan Parameters Command
+//  https://source.android.com/docs/core/connect/bluetooth/hci_requirements#le-extended-set-scan-parameters-command
+// -----------------------------------------------------------------------------
+
+enum LeExScanType : 8 {
+  PASSIVE = 0x0,
+  ACTIVE = 0x1,
+}
+
+enum LeExScanFilterPolicy : 8 {
+  ACCEPT_ALL = 0x0,
+  FILTER_ACCEPT_LIST_ONLY = 0x01,
+}
+
+packet LeExSetScanParameters : Command (op_code = LE_EX_SET_SCAN_PARAMETERS) {
+  le_ex_scan_type : LeExScanType,
+  le_ex_scan_interval : 32,
+  le_ex_scan_window : 32,
+  own_address_type : OwnAddressType,
+  le_ex_scan_filter_policy : LeExScanFilterPolicy,
+}
+
+packet LeExSetScanParametersComplete : CommandComplete (command_op_code = LE_EX_SET_SCAN_PARAMETERS) {
+  status : ErrorCode,
+}
+
+// -----------------------------------------------------------------------------
+//  Get Controller Debug Info Command
+//  https://source.android.com/docs/core/connect/bluetooth/hci_requirements#get-controller-debug-info-command
+// -----------------------------------------------------------------------------
+
+packet GetControllerDebugInfo : Command (op_code = GET_CONTROLLER_DEBUG_INFO) {
+}
+
+packet GetControllerDebugInfoComplete : CommandComplete (command_op_code = GET_CONTROLLER_DEBUG_INFO) {
+  status : ErrorCode,
+}
+
+packet ControllerDebugInfoEvent : VendorSpecificEvent (subevent_code = CONTROLLER_DEBUG_INFO) {
+  debug_block_byte_offset_start : 16,
+  last_block : 8,
+  _size_(debug_data) : 16,
+  debug_data : 8[],
+}
+
+// -----------------------------------------------------------------------------
+//  Microsoft Commands
+//  https://learn.microsoft.com/en-us/windows-hardware/drivers/bluetooth/microsoft-defined-bluetooth-hci-commands-and-events
+// -----------------------------------------------------------------------------
+
+enum MsftSubcommandOpcode : 8 {
+  MSFT_READ_SUPPORTED_FEATURES = 0x00,
+  MSFT_MONITOR_RSSI = 0x01,
+  MSFT_CANCEL_MONITOR_RSSI = 0x02,
+  MSFT_LE_MONITOR_ADV = 0x03,
+  MSFT_LE_CANCEL_MONITOR_ADV = 0x04,
+  MSFT_LE_SET_ADV_FILTER_ENABLE = 0x05,
+  MSFT_READ_ABSOLUTE_RSSI = 0x06,
+}
+
+// MSFT Commands do not have a constant opcode, so leave `op_code` undefined.
+packet MsftCommand : Command {
+  subcommand_opcode: MsftSubcommandOpcode,
+  _payload_,
+}
+
+packet MsftReadSupportedFeatures : MsftCommand (subcommand_opcode = MSFT_READ_SUPPORTED_FEATURES) {}
+
+enum MsftLeMonitorAdvConditionType : 8 {
+  MSFT_CONDITION_TYPE_PATTERNS = 0x01,
+  MSFT_CONDITION_TYPE_UUID = 0x02,
+  MSFT_CONDITION_TYPE_IRK_RESOLUTION = 0x03,
+  MSFT_CONDITION_TYPE_ADDRESS = 0x04,
+}
+
+enum MsftLeMonitorAdvConditionUuidType : 8 {
+  MSFT_CONDITION_UUID_TYPE_16_BIT = 0x01,
+  MSFT_CONDITION_UUID_TYPE_32_BIT = 0x02,
+  MSFT_CONDITION_UUID_TYPE_128_BIT = 0x03,
+}
+
+packet MsftLeMonitorAdv : MsftCommand (subcommand_opcode = MSFT_LE_MONITOR_ADV) {
+  rssi_threshold_high : 8,
+  rssi_threshold_low : 8,
+  rssi_threshold_low_time_interval : 8,
+  rssi_sampling_period : 8,
+  condition_type: MsftLeMonitorAdvConditionType,
+  _payload_,
+}
+
+struct MsftLeMonitorAdvConditionPattern {
+  _size_(pattern) : 8, // including one byte for ad_type and one byte for start_of_pattern
+  ad_type: 8,
+  start_of_pattern: 8,
+  pattern: 8[+2],
+}
+
+packet MsftLeMonitorAdvConditionPatterns : MsftLeMonitorAdv (condition_type = MSFT_CONDITION_TYPE_PATTERNS) {
+  _count_(patterns): 8,
+  patterns: MsftLeMonitorAdvConditionPattern[],
+}
+
+test MsftLeMonitorAdvConditionPatterns {
+  "\x1e\xfc\x0e\x03\x10\x05\x04\xaa\x01\x01\x06\x03\x00\x80\x81\x82\x83", // 1 pattern
+  "\x70\xfd\x13\x03\x15\x04\x02\xbb\x01\x02\x04\x03\x00\x80\x81\x06\x0f\x00\x90\x91\x92\x93", // 2 patterns
+}
+
+packet MsftLeMonitorAdvConditionUuid : MsftLeMonitorAdv (condition_type = MSFT_CONDITION_TYPE_UUID) {
+  uuid_type: MsftLeMonitorAdvConditionUuidType,
+  _payload_,
+}
+
+packet MsftLeMonitorAdvConditionUuid2 : MsftLeMonitorAdvConditionUuid (uuid_type = MSFT_CONDITION_UUID_TYPE_16_BIT) {
+  uuid2: 8[2],
+}
+
+test MsftLeMonitorAdvConditionUuid2 {
+  "\x1e\xfc\x09\x03\x10\x11\x12\x13\x02\x01\x70\x71", // opcode = fc1e for Intel
+  "\x70\xfd\x09\x03\x10\x11\x12\x13\x02\x01\x70\x71", // opcode = fd70 for Qualcomm
+}
+
+packet MsftLeMonitorAdvConditionUuid4 : MsftLeMonitorAdvConditionUuid (uuid_type = MSFT_CONDITION_UUID_TYPE_32_BIT) {
+  uuid4: 8[4],
+}
+
+test MsftLeMonitorAdvConditionUuid4 {
+  "\x1e\xfc\x0b\x03\x10\x11\x12\x13\x02\x02\x70\x71\x72\x73",
+  "\x70\xfd\x0b\x03\x10\x11\x12\x13\x02\x02\x70\x71\x72\x73",
+}
+
+packet MsftLeMonitorAdvConditionUuid16 : MsftLeMonitorAdvConditionUuid (uuid_type = MSFT_CONDITION_UUID_TYPE_128_BIT) {
+  uuid16: 8[16],
+}
+
+test MsftLeMonitorAdvConditionUuid16 {
+  "\x1e\xfc\x17\x03\x10\x11\x12\x13\x02\x03\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f",
+  "\x70\xfd\x17\x03\x10\x11\x12\x13\x02\x03\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f",
+}
+
+packet MsftLeCancelMonitorAdv: MsftCommand (subcommand_opcode = MSFT_LE_CANCEL_MONITOR_ADV) {
+  monitor_handle: 8,
+}
+
+test MsftLeCancelMonitorAdv {
+  "\x1e\xfc\x02\x04\x01", // cancel handle 0x01
+}
+
+packet MsftLeSetAdvFilterEnable : MsftCommand (subcommand_opcode = MSFT_LE_SET_ADV_FILTER_ENABLE) {
+  enable: 8,
+}
+
+test MsftLeSetAdvFilterEnable {
+  "\x1e\xfc\x02\x05\x01", // disable
+  "\x70\xfd\x02\x05\x01", // enable
+}
+
+packet MsftCommandComplete : CommandComplete {
+  status: ErrorCode,
+  subcommand_opcode: MsftSubcommandOpcode,
+  _payload_,
+}
+
+packet MsftReadSupportedFeaturesCommandComplete : MsftCommandComplete (subcommand_opcode = MSFT_READ_SUPPORTED_FEATURES) {
+  supported_features: 64,
+  _size_(prefix) : 8,
+  prefix: 8[],
+}
+
+test MsftReadSupportedFeaturesCommandComplete {
+  "\x0e\x10\x01\x1e\xfc\x00\x00\x7f\x00\x00\x00\x00\x00\x00\x00\x02\x87\x80", // Msft opcode by Intel
+  "\x0e\x12\x01\x70\xfd\x00\x00\x1f\x00\x00\x00\x00\x00\x00\x00\x04\x4d\x53\x46\x54", // Msft opcode by Qualcomm
+}
+
+packet MsftLeMonitorAdvCommandComplete : MsftCommandComplete (subcommand_opcode = MSFT_LE_MONITOR_ADV) {
+  monitor_handle: 8,
+}
+
+test MsftLeMonitorAdvCommandComplete {
+  "\x0e\x06\x01\x1e\xfc\x00\x03\x05", // succeeded
+  "\x0e\x06\x01\x70\xfd\x01\x03\x06", // failed
+}
+
+packet MsftLeCancelMonitorAdvCommandComplete : MsftCommandComplete (subcommand_opcode = MSFT_LE_CANCEL_MONITOR_ADV) {}
+
+packet MsftLeSetAdvFilterEnableCommandComplete : MsftCommandComplete (subcommand_opcode = MSFT_LE_SET_ADV_FILTER_ENABLE) {}
+
+enum MsftEventCode : 8 {
+  MSFT_RSSI_EVENT = 0x01,
+  MSFT_LE_MONITOR_DEVICE_EVENT = 0x02,
+}
+
+enum MsftEventStatus : 8 {
+  MSFT_EVENT_STATUS_SUCCESS = 0x00,
+  MSFT_EVENT_STATUS_FAILURE = 0x01,
+}
+
+// It is not possible to define MSFT Event packet by deriving `Event` packet
+// because it starts with variable-length event prefix which can only be determined
+// at run-time (after receiving return of MSFT Read Supported Features).
+// Therefore we only define the payload which is located after the event prefix.
+packet MsftEventPayload {
+  msft_event_code : MsftEventCode,
+  _payload_,
+}
+
+packet MsftRssiEventPayload : MsftEventPayload (msft_event_code = MSFT_RSSI_EVENT) {
+  status: MsftEventStatus,
+  connection_handle: 16,
+  rssi: 8,
+}
+
+test MsftRssiEventPayload {
+  "\x01\x00\x01\x10\xf0", // MSFT_RSSI_EVENT succeeded
+  "\x01\x01\x02\x02\x08", // MSFT_RSSI_EVENT failed
+}
+
+packet MsftLeMonitorDeviceEventPayload : MsftEventPayload (msft_event_code = MSFT_LE_MONITOR_DEVICE_EVENT) {
+  address_type: 8,
+  bd_addr: Address,
+  monitor_handle: 8,
+  monitor_state: 8,
+}
+
+test MsftLeMonitorDeviceEventPayload {
+  "\x02\x01\x00\x01\x02\x03\x04\x05\x10\x00",
+  "\x02\x02\xf0\xf1\xf2\xf3\xf4\xf5\xaa\x02",
+}
\ No newline at end of file
diff --git a/rust/src/internal/hci/tests.rs b/rust/src/internal/hci/tests.rs
new file mode 100644
index 0000000..7962c88
--- /dev/null
+++ b/rust/src/internal/hci/tests.rs
@@ -0,0 +1,94 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use crate::internal::hci::{
+    packets::{Event, EventBuilder, EventCode, Sco},
+    parse_with_expected_packet_type, prepend_packet_type, Error, Packet, PacketType,
+    PacketTypeParseError, WithPacketType,
+};
+use bytes::Bytes;
+
+#[test]
+fn prepends_packet_type() {
+    let packet_type = PacketType::Event;
+    let packet_bytes = vec![0x00, 0x00, 0x00, 0x00];
+    let actual = prepend_packet_type(packet_type, packet_bytes);
+    assert_eq!(vec![0x04, 0x00, 0x00, 0x00, 0x00], actual);
+}
+
+#[test]
+fn parse_empty_slice_should_error() {
+    let actual = parse_with_expected_packet_type(FakePacket::parse, PacketType::Event, &[]);
+    assert_eq!(Err(PacketTypeParseError::EmptySlice), actual);
+}
+
+#[test]
+fn parse_invalid_packet_type_should_error() {
+    let actual = parse_with_expected_packet_type(FakePacket::parse, PacketType::Event, &[0xFF]);
+    assert_eq!(
+        Err(PacketTypeParseError::InvalidPacketType { value: 0xFF }),
+        actual
+    );
+}
+
+#[test]
+fn parse_mismatched_packet_type_should_error() {
+    let actual = parse_with_expected_packet_type(FakePacket::parse, PacketType::Acl, &[0x01]);
+    assert_eq!(
+        Err(PacketTypeParseError::PacketTypeMismatch {
+            expected: PacketType::Acl,
+            actual: PacketType::Command
+        }),
+        actual
+    );
+}
+
+#[test]
+fn parse_invalid_packet_should_error() {
+    let actual = parse_with_expected_packet_type(Sco::parse, PacketType::Sco, &[0x03]);
+    assert!(actual.is_err());
+}
+
+#[test]
+fn test_packet_roundtrip_with_type() {
+    let event_packet = EventBuilder {
+        event_code: EventCode::InquiryComplete,
+        payload: None,
+    }
+    .build();
+    let event_packet_bytes = event_packet.clone().to_vec_with_packet_type();
+    let actual =
+        parse_with_expected_packet_type(Event::parse, PacketType::Event, &event_packet_bytes)
+            .unwrap();
+    assert_eq!(event_packet, actual);
+}
+
+#[derive(Debug, PartialEq)]
+struct FakePacket;
+
+impl FakePacket {
+    fn parse(_bytes: &[u8]) -> Result<Self, Error> {
+        Ok(Self)
+    }
+}
+
+impl Packet for FakePacket {
+    fn to_bytes(self) -> Bytes {
+        Bytes::new()
+    }
+
+    fn to_vec(self) -> Vec<u8> {
+        Vec::new()
+    }
+}
diff --git a/rust/src/internal/mod.rs b/rust/src/internal/mod.rs
index f474c2d..9514ed6 100644
--- a/rust/src/internal/mod.rs
+++ b/rust/src/internal/mod.rs
@@ -18,3 +18,4 @@
 //! to discover.
 
 pub(crate) mod drivers;
+pub(crate) mod hci;
diff --git a/rust/src/wrapper/assigned_numbers/company_ids.rs b/rust/src/wrapper/assigned_numbers/company_ids.rs
index 2eebcd5..1c558b1 100644
--- a/rust/src/wrapper/assigned_numbers/company_ids.rs
+++ b/rust/src/wrapper/assigned_numbers/company_ids.rs
@@ -21,7 +21,7 @@
 lazy_static! {
     /// Assigned company IDs
     pub static ref COMPANY_IDS: collections::HashMap<Uuid16, &'static str> = [
-        (0_u16, r#"Ericsson Technology Licensing"#),
+        (0_u16, r#"Ericsson AB"#),
         (1_u16, r#"Nokia Mobile Phones"#),
         (2_u16, r#"Intel Corp."#),
         (3_u16, r#"IBM Corp."#),
@@ -42,7 +42,7 @@
         (18_u16, r#"Zeevo, Inc."#),
         (19_u16, r#"Atmel Corporation"#),
         (20_u16, r#"Mitsubishi Electric Corporation"#),
-        (21_u16, r#"RTX Telecom A/S"#),
+        (21_u16, r#"RTX A/S"#),
         (22_u16, r#"KC Technology Inc."#),
         (23_u16, r#"Newlogic"#),
         (24_u16, r#"Transilica, Inc."#),
@@ -58,7 +58,7 @@
         (34_u16, r#"NEC Corporation"#),
         (35_u16, r#"WavePlus Technology Co., Ltd."#),
         (36_u16, r#"Alcatel"#),
-        (37_u16, r#"NXP Semiconductors (formerly Philips Semiconductors)"#),
+        (37_u16, r#"NXP B.V."#),
         (38_u16, r#"C Technologies"#),
         (39_u16, r#"Open Interface"#),
         (40_u16, r#"R F Micro Devices"#),
@@ -79,9 +79,9 @@
         (55_u16, r#"Mobilian Corporation"#),
         (56_u16, r#"Syntronix Corporation"#),
         (57_u16, r#"Integrated System Solution Corp."#),
-        (58_u16, r#"Panasonic Corporation (formerly Matsushita Electric Industrial Co., Ltd.)"#),
+        (58_u16, r#"Panasonic Holdings Corporation"#),
         (59_u16, r#"Gennum Corporation"#),
-        (60_u16, r#"BlackBerry Limited  (formerly Research In Motion)"#),
+        (60_u16, r#"BlackBerry Limited"#),
         (61_u16, r#"IPextreme, Inc."#),
         (62_u16, r#"Systems and Chips, Inc"#),
         (63_u16, r#"Bluetooth SIG, Inc"#),
@@ -122,9 +122,9 @@
         (98_u16, r#"Gibson Guitars"#),
         (99_u16, r#"MiCommand Inc."#),
         (100_u16, r#"Band XI International, LLC"#),
-        (101_u16, r#"Hewlett-Packard Company"#),
+        (101_u16, r#"HP, Inc."#),
         (102_u16, r#"9Solutions Oy"#),
-        (103_u16, r#"GN Netcom A/S"#),
+        (103_u16, r#"GN Audio A/S"#),
         (104_u16, r#"General Motors"#),
         (105_u16, r#"A&D Engineering, Inc."#),
         (106_u16, r#"MindTree Ltd."#),
@@ -140,7 +140,7 @@
         (116_u16, r#"Zomm, LLC"#),
         (117_u16, r#"Samsung Electronics Co. Ltd."#),
         (118_u16, r#"Creative Technology Ltd."#),
-        (119_u16, r#"Laird Technologies"#),
+        (119_u16, r#"Laird Connectivity LLC"#),
         (120_u16, r#"Nike, Inc."#),
         (121_u16, r#"lesswire AG"#),
         (122_u16, r#"MStar Semiconductor, Inc."#),
@@ -151,20 +151,20 @@
         (127_u16, r#"Autonet Mobile"#),
         (128_u16, r#"DeLorme Publishing Company, Inc."#),
         (129_u16, r#"WuXi Vimicro"#),
-        (130_u16, r#"Sennheiser Communications A/S"#),
+        (130_u16, r#"DSEA A/S"#),
         (131_u16, r#"TimeKeeping Systems, Inc."#),
         (132_u16, r#"Ludus Helsinki Ltd."#),
         (133_u16, r#"BlueRadios, Inc."#),
         (134_u16, r#"Equinux AG"#),
         (135_u16, r#"Garmin International, Inc."#),
         (136_u16, r#"Ecotest"#),
-        (137_u16, r#"GN ReSound A/S"#),
+        (137_u16, r#"GN Hearing A/S"#),
         (138_u16, r#"Jawbone"#),
         (139_u16, r#"Topcon Positioning Systems, LLC"#),
-        (140_u16, r#"Gimbal Inc. (formerly Qualcomm Labs, Inc. and Qualcomm Retail Solutions, Inc.)"#),
+        (140_u16, r#"Gimbal Inc."#),
         (141_u16, r#"Zscan Software"#),
         (142_u16, r#"Quintic Corp"#),
-        (143_u16, r#"Telit Wireless Solutions GmbH (formerly Stollmann E+V GmbH)"#),
+        (143_u16, r#"Telit Wireless Solutions GmbH"#),
         (144_u16, r#"Funai Electric Co., Ltd."#),
         (145_u16, r#"Advanced PANMOBIL systems GmbH & Co. KG"#),
         (146_u16, r#"ThinkOptics, Inc."#),
@@ -190,7 +190,7 @@
         (166_u16, r#"Panda Ocean Inc."#),
         (167_u16, r#"Visteon Corporation"#),
         (168_u16, r#"ARP Devices Limited"#),
-        (169_u16, r#"MARELLI EUROPE S.P.A. (formerly Magneti Marelli S.p.A.)"#),
+        (169_u16, r#"MARELLI EUROPE S.P.A."#),
         (170_u16, r#"CAEN RFID srl"#),
         (171_u16, r#"Ingenieur-Systemgruppe Zahn GmbH"#),
         (172_u16, r#"Green Throttle Games"#),
@@ -207,7 +207,7 @@
         (183_u16, r#"TreLab Ltd"#),
         (184_u16, r#"Qualcomm Innovation Center, Inc. (QuIC)"#),
         (185_u16, r#"Johnson Controls, Inc."#),
-        (186_u16, r#"Starkey Laboratories Inc."#),
+        (186_u16, r#"Starkey Hearing Technologies"#),
         (187_u16, r#"S-Power Electronics Limited"#),
         (188_u16, r#"Ace Sensor Inc"#),
         (189_u16, r#"Aplix Corporation"#),
@@ -227,7 +227,7 @@
         (203_u16, r#"Binauric SE"#),
         (204_u16, r#"Beats Electronics"#),
         (205_u16, r#"Microchip Technology Inc."#),
-        (206_u16, r#"Elgato Systems GmbH"#),
+        (206_u16, r#"Eve Systems GmbH"#),
         (207_u16, r#"ARCHOS SA"#),
         (208_u16, r#"Dexcom, Inc."#),
         (209_u16, r#"Polar Electro Europe B.V."#),
@@ -240,7 +240,7 @@
         (216_u16, r#"Qualcomm Connected Experiences, Inc."#),
         (217_u16, r#"Voyetra Turtle Beach"#),
         (218_u16, r#"txtr GmbH"#),
-        (219_u16, r#"Biosentronics"#),
+        (219_u16, r#"Snuza (Pty) Ltd"#),
         (220_u16, r#"Procter & Gamble"#),
         (221_u16, r#"Hosiden Corporation"#),
         (222_u16, r#"Muzik LLC"#),
@@ -249,13 +249,13 @@
         (225_u16, r#"Danlers Ltd"#),
         (226_u16, r#"Semilink Inc"#),
         (227_u16, r#"inMusic Brands, Inc"#),
-        (228_u16, r#"Laird Connectivity, Inc. formerly L.S. Research Inc."#),
+        (228_u16, r#"L.S. Research, Inc."#),
         (229_u16, r#"Eden Software Consultants Ltd."#),
         (230_u16, r#"Freshtemp"#),
         (231_u16, r#"KS Technologies"#),
         (232_u16, r#"ACTS Technologies"#),
         (233_u16, r#"Vtrack Systems"#),
-        (234_u16, r#"Nielsen-Kellerman Company"#),
+        (234_u16, r#"www.vtracksystems.com"#),
         (235_u16, r#"Server Technology Inc."#),
         (236_u16, r#"BioResearch Associates"#),
         (237_u16, r#"Jolly Logic, LLC"#),
@@ -271,7 +271,7 @@
         (247_u16, r#"VSN Technologies, Inc."#),
         (248_u16, r#"AceUni Corp., Ltd."#),
         (249_u16, r#"StickNFind"#),
-        (250_u16, r#"Crystal Code AB"#),
+        (250_u16, r#"Crystal Alarm AB"#),
         (251_u16, r#"KOUKAAM a.s."#),
         (252_u16, r#"Delphi Corporation"#),
         (253_u16, r#"ValenceTech Limited"#),
@@ -284,13 +284,13 @@
         (260_u16, r#"PLUS Location Systems Pty Ltd"#),
         (261_u16, r#"Ubiquitous Computing Technology Corporation"#),
         (262_u16, r#"Innovative Yachtter Solutions"#),
-        (263_u16, r#"William Demant Holding A/S"#),
+        (263_u16, r#"Demant A/S"#),
         (264_u16, r#"Chicony Electronics Co., Ltd."#),
         (265_u16, r#"Atus BV"#),
         (266_u16, r#"Codegate Ltd"#),
         (267_u16, r#"ERi, Inc"#),
         (268_u16, r#"Transducers Direct, LLC"#),
-        (269_u16, r#"DENSO TEN LIMITED (formerly Fujitsu Ten LImited)"#),
+        (269_u16, r#"DENSO TEN Limited"#),
         (270_u16, r#"Audi AG"#),
         (271_u16, r#"HiSilicon Technologies CO., LIMITED"#),
         (272_u16, r#"Nippon Seiki Co., Ltd."#),
@@ -373,24 +373,24 @@
         (349_u16, r#"Estimote, Inc."#),
         (350_u16, r#"Unikey Technologies, Inc."#),
         (351_u16, r#"Timer Cap Co."#),
-        (352_u16, r#"Awox formerly AwoX"#),
+        (352_u16, r#"AwoX"#),
         (353_u16, r#"yikes"#),
         (354_u16, r#"MADSGlobalNZ Ltd."#),
         (355_u16, r#"PCH International"#),
         (356_u16, r#"Qingdao Yeelink Information Technology Co., Ltd."#),
-        (357_u16, r#"Milwaukee Tool (Formally Milwaukee Electric Tools)"#),
+        (357_u16, r#"Milwaukee Electric Tools"#),
         (358_u16, r#"MISHIK Pte Ltd"#),
         (359_u16, r#"Ascensia Diabetes Care US Inc."#),
         (360_u16, r#"Spicebox LLC"#),
         (361_u16, r#"emberlight"#),
-        (362_u16, r#"Cooper-Atkins Corporation"#),
+        (362_u16, r#"Emerson Digital Cold Chain, Inc."#),
         (363_u16, r#"Qblinks"#),
         (364_u16, r#"MYSPHERA"#),
         (365_u16, r#"LifeScan Inc"#),
         (366_u16, r#"Volantic AB"#),
         (367_u16, r#"Podo Labs, Inc"#),
         (368_u16, r#"Roche Diabetes Care AG"#),
-        (369_u16, r#"Amazon.com Services, LLC (formerly Amazon Fulfillment Service)"#),
+        (369_u16, r#"Amazon.com Services LLC"#),
         (370_u16, r#"Connovate Technology Private Limited"#),
         (371_u16, r#"Kocomojo, LLC"#),
         (372_u16, r#"Everykey Inc."#),
@@ -398,10 +398,10 @@
         (374_u16, r#"SentriLock"#),
         (375_u16, r#"I-SYST inc."#),
         (376_u16, r#"CASIO COMPUTER CO., LTD."#),
-        (377_u16, r#"LAPIS Technology Co., Ltd. formerly LAPIS Semiconductor Co., Ltd."#),
+        (377_u16, r#"LAPIS Semiconductor Co.,Ltd"#),
         (378_u16, r#"Telemonitor, Inc."#),
         (379_u16, r#"taskit GmbH"#),
-        (380_u16, r#"Daimler AG"#),
+        (380_u16, r#"Mercedes-Benz Group AG"#),
         (381_u16, r#"BatAndCat"#),
         (382_u16, r#"BluDotz Ltd"#),
         (383_u16, r#"XTel Wireless ApS"#),
@@ -431,7 +431,7 @@
         (407_u16, r#"WiSilica Inc."#),
         (408_u16, r#"VENGIT Korlatolt Felelossegu Tarsasag"#),
         (409_u16, r#"SALTO SYSTEMS S.L."#),
-        (410_u16, r#"TRON Forum (formerly T-Engine Forum)"#),
+        (410_u16, r#"TRON Forum"#),
         (411_u16, r#"CUBETECH s.r.o."#),
         (412_u16, r#"Cokiya Incorporated"#),
         (413_u16, r#"CVS Health"#),
@@ -441,14 +441,14 @@
         (417_u16, r#"FIAMM"#),
         (418_u16, r#"GIGALANE.CO.,LTD"#),
         (419_u16, r#"EROAD"#),
-        (420_u16, r#"Mine Safety Appliances"#),
+        (420_u16, r#"MSA Innovation, LLC"#),
         (421_u16, r#"Icon Health and Fitness"#),
-        (422_u16, r#"Wille Engineering (formely as Asandoo GmbH)"#),
+        (422_u16, r#"Wille Engineering"#),
         (423_u16, r#"ENERGOUS CORPORATION"#),
         (424_u16, r#"Taobao"#),
         (425_u16, r#"Canon Inc."#),
         (426_u16, r#"Geophysical Technology Inc."#),
-        (427_u16, r#"Facebook, Inc."#),
+        (427_u16, r#"Meta Platforms, Inc."#),
         (428_u16, r#"Trividia Health, Inc."#),
         (429_u16, r#"FlightSafety International"#),
         (430_u16, r#"Earlens Corporation"#),
@@ -468,7 +468,7 @@
         (444_u16, r#"SenionLab AB"#),
         (445_u16, r#"Syszone Co., Ltd"#),
         (446_u16, r#"Pulsate Mobile Ltd."#),
-        (447_u16, r#"Hong Kong HunterSun Electronic Limited"#),
+        (447_u16, r#"Hongkong OnMicro Electronics Limited"#),
         (448_u16, r#"pironex GmbH"#),
         (449_u16, r#"BRADATECH Corp."#),
         (450_u16, r#"Transenergooil AG"#),
@@ -498,7 +498,7 @@
         (474_u16, r#"Logitech International SA"#),
         (475_u16, r#"Innblue Consulting"#),
         (476_u16, r#"iParking Ltd."#),
-        (477_u16, r#"Koninklijke Philips Electronics N.V."#),
+        (477_u16, r#"Koninklijke Philips N.V."#),
         (478_u16, r#"Minelab Electronics Pty Limited"#),
         (479_u16, r#"Bison Group Ltd."#),
         (480_u16, r#"Widex A/S"#),
@@ -576,7 +576,7 @@
         (552_u16, r#"Twocanoes Labs, LLC"#),
         (553_u16, r#"Muoverti Limited"#),
         (554_u16, r#"Stamer Musikanlagen GMBH"#),
-        (555_u16, r#"Tesla Motors"#),
+        (555_u16, r#"Tesla, Inc."#),
         (556_u16, r#"Pharynks Corporation"#),
         (557_u16, r#"Lupine"#),
         (558_u16, r#"Siemens AG"#),
@@ -602,7 +602,7 @@
         (578_u16, r#"16Lab Inc"#),
         (579_u16, r#"Masimo Corp"#),
         (580_u16, r#"Iotera Inc"#),
-        (581_u16, r#"Endress+Hauser "#),
+        (581_u16, r#"Endress+Hauser"#),
         (582_u16, r#"ACKme Networks, Inc."#),
         (583_u16, r#"FiftyThree Inc."#),
         (584_u16, r#"Parker Hannifin Corp"#),
@@ -666,7 +666,7 @@
         (642_u16, r#"Sonova AG"#),
         (643_u16, r#"Maven Machines, Inc."#),
         (644_u16, r#"Synapse Electronics"#),
-        (645_u16, r#"Standard Innovation Inc."#),
+        (645_u16, r#"WOWTech Canada Ltd."#),
         (646_u16, r#"RF Code, Inc."#),
         (647_u16, r#"Wally Ventures S.L."#),
         (648_u16, r#"Willowbank Electronics Ltd"#),
@@ -695,10 +695,10 @@
         (671_u16, r#"Areus Engineering GmbH"#),
         (672_u16, r#"Impossible Camera GmbH"#),
         (673_u16, r#"InventureTrack Systems"#),
-        (674_u16, r#"LockedUp"#),
+        (674_u16, r#"Sera4 Ltd."#),
         (675_u16, r#"Itude"#),
         (676_u16, r#"Pacific Lock Company"#),
-        (677_u16, r#"Tendyron Corporation ( 天地融科技股份有限公司 )"#),
+        (677_u16, r#"Tendyron Corporation"#),
         (678_u16, r#"Robert Bosch GmbH"#),
         (679_u16, r#"Illuxtron international B.V."#),
         (680_u16, r#"miSport Ltd."#),
@@ -711,7 +711,7 @@
         (687_u16, r#"Technicolor USA Inc."#),
         (688_u16, r#"Bestechnic(Shanghai),Ltd"#),
         (689_u16, r#"Raden Inc"#),
-        (690_u16, r#"JouZen Oy"#),
+        (690_u16, r#"Oura Health Oy"#),
         (691_u16, r#"CLABER S.P.A."#),
         (692_u16, r#"Hyginex, Inc."#),
         (693_u16, r#"HANSHIN ELECTRIC RAILWAY CO.,LTD."#),
@@ -730,10 +730,10 @@
         (706_u16, r#"Guillemot Corporation"#),
         (707_u16, r#"Techtronic Power Tools Technology Limited"#),
         (708_u16, r#"Wilson Sporting Goods"#),
-        (709_u16, r#"Lenovo (Singapore) Pte Ltd. ( 联想(新加坡) )"#),
+        (709_u16, r#"Lenovo (Singapore) Pte Ltd."#),
         (710_u16, r#"Ayatan Sensors"#),
         (711_u16, r#"Electronics Tomorrow Limited"#),
-        (712_u16, r#"VASCO Data Security International, Inc."#),
+        (712_u16, r#"OneSpan"#),
         (713_u16, r#"PayRange Inc."#),
         (714_u16, r#"ABOV Semiconductor"#),
         (715_u16, r#"AINA-Wireless Inc."#),
@@ -762,7 +762,7 @@
         (738_u16, r#"NTT docomo"#),
         (739_u16, r#"Carmanah Technologies Corp."#),
         (740_u16, r#"Bytestorm Ltd."#),
-        (741_u16, r#"Espressif Incorporated ( 乐鑫信息科技(上海)有限公司 )"#),
+        (741_u16, r#"Espressif Systems (Shanghai) Co., Ltd."#),
         (742_u16, r#"Unwire"#),
         (743_u16, r#"Connected Yard, Inc."#),
         (744_u16, r#"American Music Environments"#),
@@ -770,10 +770,10 @@
         (746_u16, r#"Fujitsu Limited"#),
         (747_u16, r#"Ardic Technology"#),
         (748_u16, r#"Delta Systems, Inc"#),
-        (749_u16, r#"HTC Corporation "#),
-        (750_u16, r#"Citizen Holdings Co., Ltd. "#),
+        (749_u16, r#"HTC Corporation"#),
+        (750_u16, r#"Citizen Holdings Co., Ltd."#),
         (751_u16, r#"SMART-INNOVATION.inc"#),
-        (752_u16, r#"Blackrat Software "#),
+        (752_u16, r#"Blackrat Software"#),
         (753_u16, r#"The Idea Cave, LLC"#),
         (754_u16, r#"GoPro, Inc."#),
         (755_u16, r#"AuthAir, Inc"#),
@@ -782,7 +782,7 @@
         (758_u16, r#"Intemo Technologies"#),
         (759_u16, r#"DreamVisions co., Ltd."#),
         (760_u16, r#"Runteq Oy Ltd"#),
-        (761_u16, r#"IMAGINATION TECHNOLOGIES LTD "#),
+        (761_u16, r#"IMAGINATION TECHNOLOGIES LTD"#),
         (762_u16, r#"CoSTAR TEchnologies"#),
         (763_u16, r#"Clarius Mobile Health Corp."#),
         (764_u16, r#"Shanghai Frequen Microelectronics Co., Ltd."#),
@@ -795,7 +795,7 @@
         (771_u16, r#"IACA electronique"#),
         (772_u16, r#"Proxy Technologies, Inc."#),
         (773_u16, r#"Swipp ApS"#),
-        (774_u16, r#"Life Laboratory Inc. "#),
+        (774_u16, r#"Life Laboratory Inc."#),
         (775_u16, r#"FUJI INDUSTRIAL CO.,LTD."#),
         (776_u16, r#"Surefire, LLC"#),
         (777_u16, r#"Dolby Labs"#),
@@ -807,101 +807,101 @@
         (783_u16, r#"Shortcut Labs"#),
         (784_u16, r#"SGL Italia S.r.l."#),
         (785_u16, r#"PEEQ DATA"#),
-        (786_u16, r#"Ducere Technologies Pvt Ltd "#),
-        (787_u16, r#"DiveNav, Inc. "#),
+        (786_u16, r#"Ducere Technologies Pvt Ltd"#),
+        (787_u16, r#"DiveNav, Inc."#),
         (788_u16, r#"RIIG AI Sp. z o.o."#),
-        (789_u16, r#"Thermo Fisher Scientific "#),
-        (790_u16, r#"AG Measurematics Pvt. Ltd. "#),
-        (791_u16, r#"CHUO Electronics CO., LTD. "#),
-        (792_u16, r#"Aspenta International "#),
-        (793_u16, r#"Eugster Frismag AG "#),
-        (794_u16, r#"Amber wireless GmbH "#),
-        (795_u16, r#"HQ Inc "#),
-        (796_u16, r#"Lab Sensor Solutions "#),
-        (797_u16, r#"Enterlab ApS "#),
+        (789_u16, r#"Thermo Fisher Scientific"#),
+        (790_u16, r#"AG Measurematics Pvt. Ltd."#),
+        (791_u16, r#"CHUO Electronics CO., LTD."#),
+        (792_u16, r#"Aspenta International"#),
+        (793_u16, r#"Eugster Frismag AG"#),
+        (794_u16, r#"Wurth Elektronik eiSos GmbH & Co. KG"#),
+        (795_u16, r#"HQ Inc"#),
+        (796_u16, r#"Lab Sensor Solutions"#),
+        (797_u16, r#"Enterlab ApS"#),
         (798_u16, r#"Eyefi, Inc."#),
-        (799_u16, r#"MetaSystem S.p.A. "#),
-        (800_u16, r#"SONO ELECTRONICS. CO., LTD "#),
-        (801_u16, r#"Jewelbots "#),
-        (802_u16, r#"Compumedics Limited "#),
-        (803_u16, r#"Rotor Bike Components "#),
-        (804_u16, r#"Astro, Inc. "#),
-        (805_u16, r#"Amotus Solutions "#),
-        (806_u16, r#"Healthwear Technologies (Changzhou)Ltd "#),
-        (807_u16, r#"Essex Electronics "#),
+        (799_u16, r#"MetaSystem S.p.A."#),
+        (800_u16, r#"SONO ELECTRONICS. CO., LTD"#),
+        (801_u16, r#"Jewelbots"#),
+        (802_u16, r#"Compumedics Limited"#),
+        (803_u16, r#"Rotor Bike Components"#),
+        (804_u16, r#"Astro, Inc."#),
+        (805_u16, r#"Amotus Solutions"#),
+        (806_u16, r#"Healthwear Technologies (Changzhou)Ltd"#),
+        (807_u16, r#"Essex Electronics"#),
         (808_u16, r#"Grundfos A/S"#),
-        (809_u16, r#"Eargo, Inc. "#),
-        (810_u16, r#"Electronic Design Lab "#),
-        (811_u16, r#"ESYLUX "#),
+        (809_u16, r#"Eargo, Inc."#),
+        (810_u16, r#"Electronic Design Lab"#),
+        (811_u16, r#"ESYLUX"#),
         (812_u16, r#"NIPPON SMT.CO.,Ltd"#),
-        (813_u16, r#"BM innovations GmbH "#),
+        (813_u16, r#"BM innovations GmbH"#),
         (814_u16, r#"indoormap"#),
-        (815_u16, r#"OttoQ Inc "#),
-        (816_u16, r#"North Pole Engineering "#),
+        (815_u16, r#"OttoQ Inc"#),
+        (816_u16, r#"North Pole Engineering"#),
         (817_u16, r#"3flares Technologies Inc."#),
-        (818_u16, r#"Electrocompaniet A.S. "#),
+        (818_u16, r#"Electrocompaniet A.S."#),
         (819_u16, r#"Mul-T-Lock"#),
-        (820_u16, r#"Corentium AS "#),
+        (820_u16, r#"Airthings ASA"#),
         (821_u16, r#"Enlighted Inc"#),
         (822_u16, r#"GISTIC"#),
         (823_u16, r#"AJP2 Holdings, LLC"#),
-        (824_u16, r#"COBI GmbH "#),
-        (825_u16, r#"Blue Sky Scientific, LLC "#),
+        (824_u16, r#"COBI GmbH"#),
+        (825_u16, r#"Blue Sky Scientific, LLC"#),
         (826_u16, r#"Appception, Inc."#),
-        (827_u16, r#"Courtney Thorne Limited "#),
+        (827_u16, r#"Courtney Thorne Limited"#),
         (828_u16, r#"Virtuosys"#),
-        (829_u16, r#"TPV Technology Limited "#),
+        (829_u16, r#"TPV Technology Limited"#),
         (830_u16, r#"Monitra SA"#),
-        (831_u16, r#"Automation Components, Inc. "#),
-        (832_u16, r#"Letsense s.r.l. "#),
-        (833_u16, r#"Etesian Technologies LLC "#),
-        (834_u16, r#"GERTEC BRASIL LTDA. "#),
+        (831_u16, r#"Automation Components, Inc."#),
+        (832_u16, r#"Letsense s.r.l."#),
+        (833_u16, r#"Etesian Technologies LLC"#),
+        (834_u16, r#"GERTEC BRASIL LTDA."#),
         (835_u16, r#"Drekker Development Pty. Ltd."#),
-        (836_u16, r#"Whirl Inc "#),
-        (837_u16, r#"Locus Positioning "#),
-        (838_u16, r#"Acuity Brands Lighting, Inc "#),
-        (839_u16, r#"Prevent Biometrics "#),
+        (836_u16, r#"Whirl Inc"#),
+        (837_u16, r#"Locus Positioning"#),
+        (838_u16, r#"Acuity Brands Lighting, Inc"#),
+        (839_u16, r#"Prevent Biometrics"#),
         (840_u16, r#"Arioneo"#),
-        (841_u16, r#"VersaMe "#),
-        (842_u16, r#"Vaddio "#),
-        (843_u16, r#"Libratone A/S "#),
-        (844_u16, r#"HM Electronics, Inc. "#),
+        (841_u16, r#"VersaMe"#),
+        (842_u16, r#"Vaddio"#),
+        (843_u16, r#"Libratone A/S"#),
+        (844_u16, r#"HM Electronics, Inc."#),
         (845_u16, r#"TASER International, Inc."#),
-        (846_u16, r#"SafeTrust Inc. "#),
-        (847_u16, r#"Heartland Payment Systems "#),
-        (848_u16, r#"Bitstrata Systems Inc. "#),
-        (849_u16, r#"Pieps GmbH "#),
+        (846_u16, r#"SafeTrust Inc."#),
+        (847_u16, r#"Heartland Payment Systems"#),
+        (848_u16, r#"Bitstrata Systems Inc."#),
+        (849_u16, r#"Pieps GmbH"#),
         (850_u16, r#"iRiding(Xiamen)Technology Co.,Ltd."#),
-        (851_u16, r#"Alpha Audiotronics, Inc. "#),
-        (852_u16, r#"TOPPAN FORMS CO.,LTD. "#),
-        (853_u16, r#"Sigma Designs, Inc. "#),
-        (854_u16, r#"Spectrum Brands, Inc. "#),
-        (855_u16, r#"Polymap Wireless "#),
+        (851_u16, r#"Alpha Audiotronics, Inc."#),
+        (852_u16, r#"TOPPAN FORMS CO.,LTD."#),
+        (853_u16, r#"Sigma Designs, Inc."#),
+        (854_u16, r#"Spectrum Brands, Inc."#),
+        (855_u16, r#"Polymap Wireless"#),
         (856_u16, r#"MagniWare Ltd."#),
-        (857_u16, r#"Novotec Medical GmbH "#),
-        (858_u16, r#"Medicom Innovation Partner a/s "#),
-        (859_u16, r#"Matrix Inc. "#),
-        (860_u16, r#"Eaton Corporation "#),
+        (857_u16, r#"Novotec Medical GmbH"#),
+        (858_u16, r#"Phillips-Medisize A/S"#),
+        (859_u16, r#"Matrix Inc."#),
+        (860_u16, r#"Eaton Corporation"#),
         (861_u16, r#"KYS"#),
-        (862_u16, r#"Naya Health, Inc. "#),
-        (863_u16, r#"Acromag "#),
-        (864_u16, r#"Insulet Corporation "#),
-        (865_u16, r#"Wellinks Inc. "#),
+        (862_u16, r#"Naya Health, Inc."#),
+        (863_u16, r#"Acromag"#),
+        (864_u16, r#"Insulet Corporation"#),
+        (865_u16, r#"Wellinks Inc."#),
         (866_u16, r#"ON Semiconductor"#),
-        (867_u16, r#"FREELAP SA "#),
-        (868_u16, r#"Favero Electronics Srl "#),
-        (869_u16, r#"BioMech Sensor LLC "#),
+        (867_u16, r#"FREELAP SA"#),
+        (868_u16, r#"Favero Electronics Srl"#),
+        (869_u16, r#"BioMech Sensor LLC"#),
         (870_u16, r#"BOLTT Sports technologies Private limited"#),
-        (871_u16, r#"Saphe International "#),
-        (872_u16, r#"Metormote AB "#),
-        (873_u16, r#"littleBits "#),
-        (874_u16, r#"SetPoint Medical "#),
-        (875_u16, r#"BRControls Products BV "#),
-        (876_u16, r#"Zipcar "#),
-        (877_u16, r#"AirBolt Pty Ltd "#),
-        (878_u16, r#"KeepTruckin Inc "#),
-        (879_u16, r#"Motiv, Inc. "#),
-        (880_u16, r#"Wazombi Labs OÜ "#),
+        (871_u16, r#"Saphe International"#),
+        (872_u16, r#"Metormote AB"#),
+        (873_u16, r#"littleBits"#),
+        (874_u16, r#"SetPoint Medical"#),
+        (875_u16, r#"BRControls Products BV"#),
+        (876_u16, r#"Zipcar"#),
+        (877_u16, r#"AirBolt Pty Ltd"#),
+        (878_u16, r#"MOTIVE TECHNOLOGIES, INC."#),
+        (879_u16, r#"Motiv, Inc."#),
+        (880_u16, r#"Wazombi Labs OÜ"#),
         (881_u16, r#"ORBCOMM"#),
         (882_u16, r#"Nixie Labs, Inc."#),
         (883_u16, r#"AppNearMe Ltd"#),
@@ -916,7 +916,7 @@
         (892_u16, r#"Cronologics Corporation"#),
         (893_u16, r#"MICRODIA Ltd."#),
         (894_u16, r#"lulabytes S.L."#),
-        (895_u16, r#"Société des Produits Nestlé S.A. (formerly Nestec S.A.)"#),
+        (895_u16, r#"Société des Produits Nestlé S.A."#),
         (896_u16, r#"LLC "MEGA-F service""#),
         (897_u16, r#"Sharp Corporation"#),
         (898_u16, r#"Precision Outcomes Ltd"#),
@@ -1024,7 +1024,7 @@
         (1000_u16, r#"Reiner Kartengeraete GmbH & Co. KG."#),
         (1001_u16, r#"SHENZHEN LEMONJOY TECHNOLOGY CO., LTD."#),
         (1002_u16, r#"Hello Inc."#),
-        (1003_u16, r#"Evollve Inc."#),
+        (1003_u16, r#"Ozo Edu, Inc."#),
         (1004_u16, r#"Jigowatts Inc."#),
         (1005_u16, r#"BASIC MICRO.COM,INC."#),
         (1006_u16, r#"CUBE TECHNOLOGIES"#),
@@ -1042,7 +1042,7 @@
         (1018_u16, r#"Vyassoft Technologies Inc"#),
         (1019_u16, r#"Nox Medical"#),
         (1020_u16, r#"Kimberly-Clark"#),
-        (1021_u16, r#"Trimble Navigation Ltd."#),
+        (1021_u16, r#"Trimble Inc."#),
         (1022_u16, r#"Littelfuse"#),
         (1023_u16, r#"Withings"#),
         (1024_u16, r#"i-developer IT Beratung UG"#),
@@ -1054,8 +1054,8 @@
         (1030_u16, r#"Airtago"#),
         (1031_u16, r#"Swiss Audio SA"#),
         (1032_u16, r#"ToGetHome Inc."#),
-        (1033_u16, r#"AXIS"#),
-        (1034_u16, r#"Openmatics"#),
+        (1033_u16, r#"RYSE INC."#),
+        (1034_u16, r#"ZF OPENMATICS s.r.o."#),
         (1035_u16, r#"Jana Care Inc."#),
         (1036_u16, r#"Senix Corporation"#),
         (1037_u16, r#"NorthStar Battery Company, LLC"#),
@@ -1094,7 +1094,7 @@
         (1070_u16, r#"MemCachier Inc."#),
         (1071_u16, r#"Danfoss A/S"#),
         (1072_u16, r#"SnapStyk Inc."#),
-        (1073_u16, r#"Amway Corporation"#),
+        (1073_u16, r#"Alticor Inc."#),
         (1074_u16, r#"Silk Labs, Inc."#),
         (1075_u16, r#"Pillsy Inc."#),
         (1076_u16, r#"Hatch Baby, Inc."#),
@@ -1128,7 +1128,7 @@
         (1104_u16, r#"Teenage Engineering AB"#),
         (1105_u16, r#"Tunstall Nordic AB"#),
         (1106_u16, r#"Svep Design Center AB"#),
-        (1107_u16, r#"Qorvo Utrecht B.V. formerly GreenPeak Technologies BV"#),
+        (1107_u16, r#"Qorvo Utrecht B.V."#),
         (1108_u16, r#"Sphinx Electronics GmbH & Co KG"#),
         (1109_u16, r#"Atomation"#),
         (1110_u16, r#"Nemik Consulting Inc"#),
@@ -1204,7 +1204,7 @@
         (1180_u16, r#"DyOcean"#),
         (1181_u16, r#"Uhlmann & Zacher GmbH"#),
         (1182_u16, r#"AND!XOR LLC"#),
-        (1183_u16, r#"tictote AB"#),
+        (1183_u16, r#"Popper Pay AB"#),
         (1184_u16, r#"Vypin, LLC"#),
         (1185_u16, r#"PNI Sensor Corporation"#),
         (1186_u16, r#"ovrEngineered, LLC"#),
@@ -1262,13 +1262,13 @@
         (1238_u16, r#"LUGLOC LLC"#),
         (1239_u16, r#"Blincam, Inc."#),
         (1240_u16, r#"FUJIFILM Corporation"#),
-        (1241_u16, r#"RandMcNally"#),
+        (1241_u16, r#"RM Acquisition LLC"#),
         (1242_u16, r#"Franceschi Marina snc"#),
         (1243_u16, r#"Engineered Audio, LLC."#),
         (1244_u16, r#"IOTTIVE (OPC) PRIVATE LIMITED"#),
         (1245_u16, r#"4MOD Technology"#),
         (1246_u16, r#"Lutron Electronics Co., Inc."#),
-        (1247_u16, r#"Emerson"#),
+        (1247_u16, r#"Emerson Electric Co."#),
         (1248_u16, r#"Guardtec, Inc."#),
         (1249_u16, r#"REACTEC LIMITED"#),
         (1250_u16, r#"EllieGrid"#),
@@ -1292,7 +1292,7 @@
         (1268_u16, r#"ZanCompute Inc."#),
         (1269_u16, r#"Pirelli Tyre S.P.A."#),
         (1270_u16, r#"McLear Limited"#),
-        (1271_u16, r#"Shenzhen Huiding Technology Co.,Ltd."#),
+        (1271_u16, r#"Shenzhen Goodix Technology Co., Ltd"#),
         (1272_u16, r#"Convergence Systems Limited"#),
         (1273_u16, r#"Interactio"#),
         (1274_u16, r#"Androtec GmbH"#),
@@ -1306,11 +1306,11 @@
         (1282_u16, r#"Specifi-Kali LLC"#),
         (1283_u16, r#"Locoroll, Inc"#),
         (1284_u16, r#"PHYPLUS Inc"#),
-        (1285_u16, r#"Inplay Technologies LLC"#),
+        (1285_u16, r#"InPlay, Inc."#),
         (1286_u16, r#"Hager"#),
         (1287_u16, r#"Yellowcog"#),
         (1288_u16, r#"Axes System sp. z o. o."#),
-        (1289_u16, r#"myLIFTER Inc."#),
+        (1289_u16, r#"Garage Smart, Inc."#),
         (1290_u16, r#"Shake-on B.V."#),
         (1291_u16, r#"Vibrissa Inc."#),
         (1292_u16, r#"OSRAM GmbH"#),
@@ -1324,7 +1324,7 @@
         (1300_u16, r#"FIBRO GmbH"#),
         (1301_u16, r#"RB Controls Co., Ltd."#),
         (1302_u16, r#"Footmarks"#),
-        (1303_u16, r#"Amtronic Sverige AB (formerly Amcore AB)"#),
+        (1303_u16, r#"Amtronic Sverige AB"#),
         (1304_u16, r#"MAMORIO.inc"#),
         (1305_u16, r#"Tyto Life LLC"#),
         (1306_u16, r#"Leica Camera AG"#),
@@ -1372,7 +1372,7 @@
         (1348_u16, r#"OrthoSensor, Inc."#),
         (1349_u16, r#"Candy Hoover Group s.r.l"#),
         (1350_u16, r#"Apexar Technologies S.A."#),
-        (1351_u16, r#"LOGICDATA d.o.o."#),
+        (1351_u16, r#"LOGICDATA Electronic & Software Entwicklungs GmbH"#),
         (1352_u16, r#"Knick Elektronische Messgeraete GmbH & Co. KG"#),
         (1353_u16, r#"Smart Technologies and Investment Limited"#),
         (1354_u16, r#"Linough Inc."#),
@@ -1397,7 +1397,7 @@
         (1373_u16, r#"Valve Corporation"#),
         (1374_u16, r#"Hekatron Vertriebs GmbH"#),
         (1375_u16, r#"PROTECH S.A.S. DI GIRARDI ANDREA & C."#),
-        (1376_u16, r#"Sarita CareTech APS (formerly Sarita CareTech IVS)"#),
+        (1376_u16, r#"Sarita CareTech APS"#),
         (1377_u16, r#"Finder S.p.A."#),
         (1378_u16, r#"Thalmic Labs Inc."#),
         (1379_u16, r#"Steinel Vertrieb GmbH"#),
@@ -1441,9 +1441,9 @@
         (1417_u16, r#"Star Technologies"#),
         (1418_u16, r#"START TODAY CO.,LTD."#),
         (1419_u16, r#"Maxim Integrated Products"#),
-        (1420_u16, r#"MERCK Kommanditgesellschaft auf Aktien"#),
+        (1420_u16, r#"Fracarro Radioindustrie SRL"#),
         (1421_u16, r#"Jungheinrich Aktiengesellschaft"#),
-        (1422_u16, r#"Oculus VR, LLC"#),
+        (1422_u16, r#"Meta Platforms Technologies, LLC"#),
         (1423_u16, r#"HENDON SEMICONDUCTORS PTY LTD"#),
         (1424_u16, r#"Pur3 Ltd"#),
         (1425_u16, r#"Viasat Group S.p.A."#),
@@ -1476,7 +1476,7 @@
         (1452_u16, r#"GoerTek Dynaudio Co., Ltd."#),
         (1453_u16, r#"INIA"#),
         (1454_u16, r#"CARMATE MFG.CO.,LTD"#),
-        (1455_u16, r#"OV LOOP, INC. (formerly ONvocal)"#),
+        (1455_u16, r#"OV LOOP, INC."#),
         (1456_u16, r#"NewTec GmbH"#),
         (1457_u16, r#"Medallion Instrumentation Systems"#),
         (1458_u16, r#"CAREL INDUSTRIES S.P.A."#),
@@ -1539,7 +1539,7 @@
         (1515_u16, r#"Bayerische Motoren Werke AG"#),
         (1516_u16, r#"Gycom Svenska AB"#),
         (1517_u16, r#"Fuji Xerox Co., Ltd"#),
-        (1518_u16, r#"Glide Inc."#),
+        (1518_u16, r#"Wristcam Inc."#),
         (1519_u16, r#"SIKOM AS"#),
         (1520_u16, r#"beken"#),
         (1521_u16, r#"The Linux Foundation"#),
@@ -1572,7 +1572,7 @@
         (1548_u16, r#"Vuzix Corporation"#),
         (1549_u16, r#"TDK Corporation"#),
         (1550_u16, r#"Blueair AB"#),
-        (1551_u16, r#"Signify Netherlands"#),
+        (1551_u16, r#"Signify Netherlands B.V."#),
         (1552_u16, r#"ADH GUARDIAN USA LLC"#),
         (1553_u16, r#"Beurer GmbH"#),
         (1554_u16, r#"Playfinity AS"#),
@@ -1621,7 +1621,7 @@
         (1597_u16, r#"Xradio Technology Co.,Ltd."#),
         (1598_u16, r#"The Indoor Lab, LLC"#),
         (1599_u16, r#"LDL TECHNOLOGY"#),
-        (1600_u16, r#"Parkifi"#),
+        (1600_u16, r#"Dish Network LLC"#),
         (1601_u16, r#"Revenue Collection Systems FRANCE SAS"#),
         (1602_u16, r#"Bluetrum Technology Co.,Ltd"#),
         (1603_u16, r#"makita corporation"#),
@@ -1711,7 +1711,7 @@
         (1687_u16, r#"Stemco Products Inc"#),
         (1688_u16, r#"Wood IT Security, LLC"#),
         (1689_u16, r#"RandomLab SAS"#),
-        (1690_u16, r#"Adero, Inc. (formerly as TrackR, Inc.)"#),
+        (1690_u16, r#"Adero, Inc."#),
         (1691_u16, r#"Dragonchip Limited"#),
         (1692_u16, r#"Noomi AB"#),
         (1693_u16, r#"Vakaros LLC"#),
@@ -1721,7 +1721,7 @@
         (1697_u16, r#"Cardo Systems, Ltd"#),
         (1698_u16, r#"Globalworx GmbH"#),
         (1699_u16, r#"Nymbus, LLC"#),
-        (1700_u16, r#"Sanyo Techno Solutions Tottori Co., Ltd."#),
+        (1700_u16, r#"LIMNO Co. Ltd."#),
         (1701_u16, r#"TEKZITEL PTY LTD"#),
         (1702_u16, r#"Roambee Corporation"#),
         (1703_u16, r#"Chipsea Technologies (ShenZhen) Corp."#),
@@ -1780,8 +1780,8 @@
         (1756_u16, r#"Industrial Network Controls, LLC"#),
         (1757_u16, r#"Intellithings Ltd."#),
         (1758_u16, r#"Navcast, Inc."#),
-        (1759_u16, r#"Hubbell Lighting, Inc."#),
-        (1760_u16, r#"Avaya "#),
+        (1759_u16, r#"HLI Solutions Inc."#),
+        (1760_u16, r#"Avaya Inc."#),
         (1761_u16, r#"Milestone AV Technologies LLC"#),
         (1762_u16, r#"Alango Technologies Ltd"#),
         (1763_u16, r#"Spinlock Ltd"#),
@@ -1801,7 +1801,7 @@
         (1777_u16, r#"Shibutani Co., Ltd."#),
         (1778_u16, r#"Trapper Data AB"#),
         (1779_u16, r#"Alfred International Inc."#),
-        (1780_u16, r#"Near Field Solutions Ltd"#),
+        (1780_u16, r#"Touché Technology Ltd"#),
         (1781_u16, r#"Vigil Technologies Inc."#),
         (1782_u16, r#"Vitulo Plus BV"#),
         (1783_u16, r#"WILKA Schliesstechnik GmbH"#),
@@ -1834,11 +1834,11 @@
         (1810_u16, r#"Bull Group Company Limited"#),
         (1811_u16, r#"Respiri Limited"#),
         (1812_u16, r#"MindPeace Safety LLC"#),
-        (1813_u16, r#"Vgyan Solutions"#),
+        (1813_u16, r#"MBARC LABS Inc"#),
         (1814_u16, r#"Altonics"#),
         (1815_u16, r#"iQsquare BV"#),
         (1816_u16, r#"IDIBAIX enginneering"#),
-        (1817_u16, r#"ECSG"#),
+        (1817_u16, r#"COREIOT PTY LTD"#),
         (1818_u16, r#"REVSMART WEARABLE HK CO LTD"#),
         (1819_u16, r#"Precor"#),
         (1820_u16, r#"F5 Sports, Inc"#),
@@ -1867,7 +1867,7 @@
         (1843_u16, r#"Iguanavation, Inc."#),
         (1844_u16, r#"DiUS Computing Pty Ltd"#),
         (1845_u16, r#"UpRight Technologies LTD"#),
-        (1846_u16, r#"FrancisFund, LLC"#),
+        (1846_u16, r#"Luna XIO, Inc."#),
         (1847_u16, r#"LLC Navitek"#),
         (1848_u16, r#"Glass Security Pte Ltd"#),
         (1849_u16, r#"Jiangsu Qinheng Co., Ltd."#),
@@ -1950,6 +1950,7 @@
         (1926_u16, r#"HLP Controls Pty Limited"#),
         (1927_u16, r#"Pangaea Solution"#),
         (1928_u16, r#"BubblyNet, LLC"#),
+        (1929_u16, r#"PCB Piezotronics, Inc."#),
         (1930_u16, r#"The Wildflower Foundation"#),
         (1931_u16, r#"Optikam Tech Inc."#),
         (1932_u16, r#"MINIBREW HOLDING B.V"#),
@@ -1958,7 +1959,7 @@
         (1935_u16, r#"Hanna Instruments, Inc."#),
         (1936_u16, r#"KOMPAN A/S"#),
         (1937_u16, r#"Scosche Industries, Inc."#),
-        (1938_u16, r#"Provo Craft"#),
+        (1938_u16, r#"Cricut, Inc."#),
         (1939_u16, r#"AEV spol. s r.o."#),
         (1940_u16, r#"The Coca-Cola Company"#),
         (1941_u16, r#"GASTEC CORPORATION"#),
@@ -2032,17 +2033,17 @@
         (2009_u16, r#"Micro-Design, Inc."#),
         (2010_u16, r#"STARLITE Co., Ltd."#),
         (2011_u16, r#"Remedee Labs"#),
-        (2012_u16, r#"ThingOS GmbH"#),
+        (2012_u16, r#"ThingOS GmbH & Co KG"#),
         (2013_u16, r#"Linear Circuits"#),
         (2014_u16, r#"Unlimited Engineering SL"#),
         (2015_u16, r#"Snap-on Incorporated"#),
         (2016_u16, r#"Edifier International Limited"#),
         (2017_u16, r#"Lucie Labs"#),
         (2018_u16, r#"Alfred Kaercher SE & Co. KG"#),
-        (2019_u16, r#"Audiowise Technology Inc."#),
+        (2019_u16, r#"Airoha Technology Corp."#),
         (2020_u16, r#"Geeksme S.L."#),
         (2021_u16, r#"Minut, Inc."#),
-        (2022_u16, r#"Autogrow Systems Limited"#),
+        (2022_u16, r#"Waybeyond Limited"#),
         (2023_u16, r#"Komfort IQ, Inc."#),
         (2024_u16, r#"Packetcraft, Inc."#),
         (2025_u16, r#"Häfele GmbH & Co KG"#),
@@ -2053,7 +2054,7 @@
         (2030_u16, r#"KidzTek LLC"#),
         (2031_u16, r#"Aktiebolaget Sandvik Coromant"#),
         (2032_u16, r#"e-moola.com Pty Ltd"#),
-        (2033_u16, r#"GSM Innovations Pty Ltd"#),
+        (2033_u16, r#"Zimi Innovations Pty Ltd"#),
         (2034_u16, r#"SERENE GROUP, INC"#),
         (2035_u16, r#"DIGISINE ENERGYTECH CO. LTD."#),
         (2036_u16, r#"MEDIRLAB Orvosbiologiai Fejleszto Korlatolt Felelossegu Tarsasag"#),
@@ -2109,7 +2110,7 @@
         (2086_u16, r#"Hyundai Motor Company"#),
         (2087_u16, r#"Kickmaker"#),
         (2088_u16, r#"Shanghai Suisheng Information Technology Co., Ltd."#),
-        (2089_u16, r#"HEXAGON"#),
+        (2089_u16, r#"HEXAGON METROLOGY DIVISION ROMER"#),
         (2090_u16, r#"Mitutoyo Corporation"#),
         (2091_u16, r#"shenzhen fitcare electronics Co.,Ltd"#),
         (2092_u16, r#"INGICS TECHNOLOGY CO., LTD."#),
@@ -2153,20 +2154,20 @@
         (2130_u16, r#"Cognosos, Inc."#),
         (2131_u16, r#"Pektron Group Limited"#),
         (2132_u16, r#"Tap Sound System"#),
-        (2133_u16, r#"Helios Hockey, Inc."#),
+        (2133_u16, r#"Helios Sports, Inc."#),
         (2134_u16, r#"Canopy Growth Corporation"#),
         (2135_u16, r#"Parsyl Inc"#),
         (2136_u16, r#"SOUNDBOKS"#),
         (2137_u16, r#"BlueUp"#),
         (2138_u16, r#"DAKATECH"#),
-        (2139_u16, r#"RICOH ELECTRONIC DEVICES CO., LTD."#),
+        (2139_u16, r#"Nisshinbo Micro Devices Inc."#),
         (2140_u16, r#"ACOS CO.,LTD."#),
         (2141_u16, r#"Guilin Zhishen Information Technology Co.,Ltd."#),
         (2142_u16, r#"Krog Systems LLC"#),
         (2143_u16, r#"COMPEGPS TEAM,SOCIEDAD LIMITADA"#),
         (2144_u16, r#"Alflex Products B.V."#),
         (2145_u16, r#"SmartSensor Labs Ltd"#),
-        (2146_u16, r#"SmartDrive Inc."#),
+        (2146_u16, r#"SmartDrive"#),
         (2147_u16, r#"Yo-tronics Technology Co., Ltd."#),
         (2148_u16, r#"Rafaelmicro"#),
         (2149_u16, r#"Emergency Lighting Products Limited"#),
@@ -2181,7 +2182,7 @@
         (2158_u16, r#"Vorwerk Elektrowerke GmbH & Co. KG"#),
         (2159_u16, r#"Trackunit A/S"#),
         (2160_u16, r#"Wyze Labs, Inc"#),
-        (2161_u16, r#"Dension Elektronikai Kft. (formerly: Dension Audio Systems Ltd.)"#),
+        (2161_u16, r#"Dension Elektronikai Kft."#),
         (2162_u16, r#"11 Health & Technologies Limited"#),
         (2163_u16, r#"Innophase Incorporated"#),
         (2164_u16, r#"Treegreen Limited"#),
@@ -2201,8 +2202,8 @@
         (2178_u16, r#"PSYONIC, Inc."#),
         (2179_u16, r#"Wintersteiger AG"#),
         (2180_u16, r#"Controlid Industria, Comercio de Hardware e Servicos de Tecnologia Ltda"#),
-        (2181_u16, r#"LEVOLOR, INC."#),
-        (2182_u16, r#"Xsens Technologies B.V."#),
+        (2181_u16, r#"LEVOLOR INC"#),
+        (2182_u16, r#"Movella Technologies B.V."#),
         (2183_u16, r#"Hydro-Gear Limited Partnership"#),
         (2184_u16, r#"EnPointe Fencing Pty Ltd"#),
         (2185_u16, r#"XANTHIO"#),
@@ -2290,7 +2291,7 @@
         (2267_u16, r#"Tertium Technology"#),
         (2268_u16, r#"SHENZHEN AUKEY E BUSINESS CO., LTD"#),
         (2269_u16, r#"code-Q"#),
-        (2270_u16, r#"Tyco Electronics Corporation a TE Connectivity Ltd Company"#),
+        (2270_u16, r#"TE Connectivity Corporation"#),
         (2271_u16, r#"IRIS OHYAMA CO.,LTD."#),
         (2272_u16, r#"Philia Technology"#),
         (2273_u16, r#"KOZO KEIKAKU ENGINEERING Inc."#),
@@ -2299,14 +2300,14 @@
         (2276_u16, r#"Rashidov ltd"#),
         (2277_u16, r#"Crowd Connected Ltd"#),
         (2278_u16, r#"Eneso Tecnologia de Adaptacion S.L."#),
-        (2279_u16, r#"Barrot Technology Limited"#),
+        (2279_u16, r#"Barrot Technology Co.,Ltd."#),
         (2280_u16, r#"Naonext"#),
         (2281_u16, r#"Taiwan Intelligent Home Corp."#),
         (2282_u16, r#"COWBELL ENGINEERING CO.,LTD."#),
         (2283_u16, r#"Beijing Big Moment Technology Co., Ltd."#),
         (2284_u16, r#"Denso Corporation"#),
         (2285_u16, r#"IMI Hydronic Engineering International SA"#),
-        (2286_u16, r#"ASKEY"#),
+        (2286_u16, r#"Askey Computer Corp."#),
         (2287_u16, r#"Cumulus Digital Systems, Inc"#),
         (2288_u16, r#"Joovv, Inc."#),
         (2289_u16, r#"The L.S. Starrett Company"#),
@@ -2391,7 +2392,7 @@
         (2368_u16, r#"Hero Workout GmbH"#),
         (2369_u16, r#"Rivian Automotive, LLC"#),
         (2370_u16, r#"TRANSSION HOLDINGS LIMITED"#),
-        (2371_u16, r#"Inovonics Corp."#),
+        (2371_u16, r#"Reserved"#),
         (2372_u16, r#"Agitron d.o.o."#),
         (2373_u16, r#"Globe (Jiangsu) Co., Ltd"#),
         (2374_u16, r#"AMC International Alfa Metalcraft Corporation AG"#),
@@ -2417,7 +2418,7 @@
         (2394_u16, r#"Selekt Bilgisayar, lletisim Urunleri lnsaat Sanayi ve Ticaret Limited Sirketi"#),
         (2395_u16, r#"Lismore Instruments Limited"#),
         (2396_u16, r#"LogiLube, LLC"#),
-        (2397_u16, r#"ETC"#),
+        (2397_u16, r#"Electronic Theatre Controls"#),
         (2398_u16, r#"BioEchoNet inc."#),
         (2399_u16, r#"NUANCE HEARING LTD"#),
         (2400_u16, r#"Sena Technologies Inc."#),
@@ -2492,7 +2493,7 @@
         (2469_u16, r#"Security Enhancement Systems, LLC"#),
         (2470_u16, r#"BEIJING ELECTRIC VEHICLE CO.,LTD"#),
         (2471_u16, r#"Paybuddy ApS"#),
-        (2472_u16, r#"KHN Solutions Inc"#),
+        (2472_u16, r#"KHN Solutions LLC"#),
         (2473_u16, r#"Nippon Ceramic Co.,Ltd."#),
         (2474_u16, r#"PHOTODYNAMIC INCORPORATED"#),
         (2475_u16, r#"DashLogic, Inc."#),
@@ -2620,7 +2621,7 @@
         (2597_u16, r#"Eran Financial Services LLC"#),
         (2598_u16, r#"Louis Vuitton"#),
         (2599_u16, r#"AYU DEVICES PRIVATE LIMITED"#),
-        (2600_u16, r#"NanoFlex"#),
+        (2600_u16, r#"NanoFlex Power Corporation"#),
         (2601_u16, r#"Worthcloud Technology Co.,Ltd"#),
         (2602_u16, r#"Yamaha Corporation"#),
         (2603_u16, r#"PaceBait IVS"#),
@@ -2699,15 +2700,654 @@
         (2676_u16, r#"MICROSON S.A."#),
         (2677_u16, r#"Delta Cycle Corporation"#),
         (2678_u16, r#"Synaptics Incorporated"#),
-        (2679_u16, r#"JMD PACIFIC PTE. LTD."#),
+        (2679_u16, r#"AXTRO PTE. LTD."#),
         (2680_u16, r#"Shenzhen Sunricher Technology Limited"#),
         (2681_u16, r#"Webasto SE"#),
         (2682_u16, r#"Emlid Limited"#),
         (2683_u16, r#"UniqAir Oy"#),
         (2684_u16, r#"WAFERLOCK"#),
         (2685_u16, r#"Freedman Electronics Pty Ltd"#),
-        (2686_u16, r#"Keba AG"#),
+        (2686_u16, r#"KEBA Handover Automation GmbH"#),
         (2687_u16, r#"Intuity Medical"#),
+        (2688_u16, r#"Cleer Limited"#),
+        (2689_u16, r#"Universal Biosensors Pty Ltd"#),
+        (2690_u16, r#"Corsair"#),
+        (2691_u16, r#"Rivata, Inc."#),
+        (2692_u16, r#"Greennote Inc,"#),
+        (2693_u16, r#"Snowball Technology Co., Ltd."#),
+        (2694_u16, r#"ALIZENT International"#),
+        (2695_u16, r#"Shanghai Smart System Technology Co., Ltd"#),
+        (2696_u16, r#"PSA Peugeot Citroen"#),
+        (2697_u16, r#"SES-Imagotag"#),
+        (2698_u16, r#"HAINBUCH GMBH SPANNENDE TECHNIK"#),
+        (2699_u16, r#"SANlight GmbH"#),
+        (2700_u16, r#"DelpSys, s.r.o."#),
+        (2701_u16, r#"JCM TECHNOLOGIES S.A."#),
+        (2702_u16, r#"Perfect Company"#),
+        (2703_u16, r#"TOTO LTD."#),
+        (2704_u16, r#"Shenzhen Grandsun Electronic Co.,Ltd."#),
+        (2705_u16, r#"Monarch International Inc."#),
+        (2706_u16, r#"Carestream Dental LLC"#),
+        (2707_u16, r#"GiPStech S.r.l."#),
+        (2708_u16, r#"OOBIK Inc."#),
+        (2709_u16, r#"Pamex Inc."#),
+        (2710_u16, r#"Lightricity Ltd"#),
+        (2711_u16, r#"SensTek"#),
+        (2712_u16, r#"Foil, Inc."#),
+        (2713_u16, r#"Shanghai high-flying electronics technology Co.,Ltd"#),
+        (2714_u16, r#"TEMKIN ASSOCIATES, LLC"#),
+        (2715_u16, r#"Eello LLC"#),
+        (2716_u16, r#"Xi'an Fengyu Information Technology Co., Ltd."#),
+        (2717_u16, r#"Canon Finetech Nisca Inc."#),
+        (2718_u16, r#"LifePlus, Inc."#),
+        (2719_u16, r#"ista International GmbH"#),
+        (2720_u16, r#"Loy Tec electronics GmbH"#),
+        (2721_u16, r#"LINCOGN TECHNOLOGY CO. LIMITED"#),
+        (2722_u16, r#"Care Bloom, LLC"#),
+        (2723_u16, r#"DIC Corporation"#),
+        (2724_u16, r#"FAZEPRO LLC"#),
+        (2725_u16, r#"Shenzhen Uascent Technology Co., Ltd"#),
+        (2726_u16, r#"Realityworks, inc."#),
+        (2727_u16, r#"Urbanista AB"#),
+        (2728_u16, r#"Zencontrol Pty Ltd"#),
+        (2729_u16, r#"Spintly, Inc."#),
+        (2730_u16, r#"Computime International Ltd"#),
+        (2731_u16, r#"Anhui Listenai Co"#),
+        (2732_u16, r#"OSM HK Limited"#),
+        (2733_u16, r#"Adevo Consulting AB"#),
+        (2734_u16, r#"PS Engineering, Inc."#),
+        (2735_u16, r#"AIAIAI ApS"#),
+        (2736_u16, r#"Visiontronic s.r.o."#),
+        (2737_u16, r#"InVue Security Products Inc"#),
+        (2738_u16, r#"TouchTronics, Inc."#),
+        (2739_u16, r#"INNER RANGE PTY. LTD."#),
+        (2740_u16, r#"Ellenby Technologies, Inc."#),
+        (2741_u16, r#"Elstat Electronics Ltd."#),
+        (2742_u16, r#"Xenter, Inc."#),
+        (2743_u16, r#"LogTag North America Inc."#),
+        (2744_u16, r#"Sens.ai Incorporated"#),
+        (2745_u16, r#"STL"#),
+        (2746_u16, r#"Open Bionics Ltd."#),
+        (2747_u16, r#"R-DAS, s.r.o."#),
+        (2748_u16, r#"KCCS Mobile Engineering Co., Ltd."#),
+        (2749_u16, r#"Inventas AS"#),
+        (2750_u16, r#"Robkoo Information & Technologies Co., Ltd."#),
+        (2751_u16, r#"PAUL HARTMANN AG"#),
+        (2752_u16, r#"Omni-ID USA, INC."#),
+        (2753_u16, r#"Shenzhen Jingxun Technology Co., Ltd."#),
+        (2754_u16, r#"RealMega Microelectronics technology (Shanghai) Co. Ltd."#),
+        (2755_u16, r#"Kenzen, Inc."#),
+        (2756_u16, r#"CODIUM"#),
+        (2757_u16, r#"Flexoptix GmbH"#),
+        (2758_u16, r#"Barnes Group Inc."#),
+        (2759_u16, r#"Chengdu Aich Technology Co.,Ltd"#),
+        (2760_u16, r#"Keepin Co., Ltd."#),
+        (2761_u16, r#"Swedlock AB"#),
+        (2762_u16, r#"Shenzhen CoolKit Technology Co., Ltd"#),
+        (2763_u16, r#"ise Individuelle Software und Elektronik GmbH"#),
+        (2764_u16, r#"Nuvoton"#),
+        (2765_u16, r#"Visuallex Sport International Limited"#),
+        (2766_u16, r#"KOBATA GAUGE MFG. CO., LTD."#),
+        (2767_u16, r#"CACI Technologies"#),
+        (2768_u16, r#"Nordic Strong ApS"#),
+        (2769_u16, r#"EAGLE KINGDOM TECHNOLOGIES LIMITED"#),
+        (2770_u16, r#"Lautsprecher Teufel GmbH"#),
+        (2771_u16, r#"SSV Software Systems GmbH"#),
+        (2772_u16, r#"Zhuhai Pantum Electronisc Co., Ltd"#),
+        (2773_u16, r#"Streamit B.V."#),
+        (2774_u16, r#"nymea GmbH"#),
+        (2775_u16, r#"AL-KO Geraete GmbH"#),
+        (2776_u16, r#"Franz Kaldewei GmbH&Co KG"#),
+        (2777_u16, r#"Shenzhen Aimore. Co.,Ltd"#),
+        (2778_u16, r#"Codefabrik GmbH"#),
+        (2779_u16, r#"Reelables, Inc."#),
+        (2780_u16, r#"Duravit AG"#),
+        (2781_u16, r#"Boss Audio"#),
+        (2782_u16, r#"Vocera Communications, Inc."#),
+        (2783_u16, r#"Douglas Dynamics L.L.C."#),
+        (2784_u16, r#"Viceroy Devices Corporation"#),
+        (2785_u16, r#"ChengDu ForThink Technology Co., Ltd."#),
+        (2786_u16, r#"IMATRIX SYSTEMS, INC."#),
+        (2787_u16, r#"GlobalMed"#),
+        (2788_u16, r#"DALI Alliance"#),
+        (2789_u16, r#"unu GmbH"#),
+        (2790_u16, r#"Hexology"#),
+        (2791_u16, r#"Sunplus Technology Co., Ltd."#),
+        (2792_u16, r#"LEVEL, s.r.o."#),
+        (2793_u16, r#"FLIR Systems AB"#),
+        (2794_u16, r#"Borda Technology"#),
+        (2795_u16, r#"Square, Inc."#),
+        (2796_u16, r#"FUTEK ADVANCED SENSOR TECHNOLOGY, INC"#),
+        (2797_u16, r#"Saxonar GmbH"#),
+        (2798_u16, r#"Velentium, LLC"#),
+        (2799_u16, r#"GLP German Light Products GmbH"#),
+        (2800_u16, r#"Leupold & Stevens, Inc."#),
+        (2801_u16, r#"CRADERS,CO.,LTD"#),
+        (2802_u16, r#"Shanghai All Link Microelectronics Co.,Ltd"#),
+        (2803_u16, r#"701x Inc."#),
+        (2804_u16, r#"Radioworks Microelectronics PTY LTD"#),
+        (2805_u16, r#"Unitech Electronic Inc."#),
+        (2806_u16, r#"AMETEK, Inc."#),
+        (2807_u16, r#"Irdeto"#),
+        (2808_u16, r#"First Design System Inc."#),
+        (2809_u16, r#"Unisto AG"#),
+        (2810_u16, r#"Chengdu Ambit Technology Co., Ltd."#),
+        (2811_u16, r#"SMT ELEKTRONIK GmbH"#),
+        (2812_u16, r#"Cerebrum Sensor Technologies Inc."#),
+        (2813_u16, r#"Weber Sensors, LLC"#),
+        (2814_u16, r#"Earda Technologies Co.,Ltd"#),
+        (2815_u16, r#"FUSEAWARE LIMITED"#),
+        (2816_u16, r#"Flaircomm Microelectronics Inc."#),
+        (2817_u16, r#"RESIDEO TECHNOLOGIES, INC."#),
+        (2818_u16, r#"IORA Technology Development Ltd. Sti."#),
+        (2819_u16, r#"Precision Triathlon Systems Limited"#),
+        (2820_u16, r#"I-PERCUT"#),
+        (2821_u16, r#"Marquardt GmbH"#),
+        (2822_u16, r#"FAZUA GmbH"#),
+        (2823_u16, r#"Workaround Gmbh"#),
+        (2824_u16, r#"Shenzhen Qianfenyi Intelligent Technology Co., LTD"#),
+        (2825_u16, r#"soonisys"#),
+        (2826_u16, r#"Belun Technology Company Limited"#),
+        (2827_u16, r#"Sanistaal A/S"#),
+        (2828_u16, r#"BluPeak"#),
+        (2829_u16, r#"SANYO DENKO Co.,Ltd."#),
+        (2830_u16, r#"Honda Lock Mfg. Co.,Ltd."#),
+        (2831_u16, r#"B.E.A. S.A."#),
+        (2832_u16, r#"Alfa Laval Corporate AB"#),
+        (2833_u16, r#"ThermoWorks, Inc."#),
+        (2834_u16, r#"ToughBuilt Industries LLC"#),
+        (2835_u16, r#"IOTOOLS"#),
+        (2836_u16, r#"Olumee"#),
+        (2837_u16, r#"NAOS JAPAN K.K."#),
+        (2838_u16, r#"Guard RFID Solutions Inc."#),
+        (2839_u16, r#"SIG SAUER, INC."#),
+        (2840_u16, r#"DECATHLON SE"#),
+        (2841_u16, r#"WBS PROJECT H PTY LTD"#),
+        (2842_u16, r#"Roca Sanitario, S.A."#),
+        (2843_u16, r#"Enerpac Tool Group Corp."#),
+        (2844_u16, r#"Nanoleq AG"#),
+        (2845_u16, r#"Accelerated Systems"#),
+        (2846_u16, r#"PB INC."#),
+        (2847_u16, r#"Beijing ESWIN Computing Technology Co., Ltd."#),
+        (2848_u16, r#"TKH Security B.V."#),
+        (2849_u16, r#"ams AG"#),
+        (2850_u16, r#"Hygiene IQ, LLC."#),
+        (2851_u16, r#"iRhythm Technologies, Inc."#),
+        (2852_u16, r#"BeiJing ZiJie TiaoDong KeJi Co.,Ltd."#),
+        (2853_u16, r#"NIBROTECH LTD"#),
+        (2854_u16, r#"Baracoda Daily Healthtech."#),
+        (2855_u16, r#"Lumi United Technology Co., Ltd"#),
+        (2856_u16, r#"CHACON"#),
+        (2857_u16, r#"Tech-Venom Entertainment Private Limited"#),
+        (2858_u16, r#"ACL Airshop B.V."#),
+        (2859_u16, r#"MAINBOT"#),
+        (2860_u16, r#"ILLUMAGEAR, Inc."#),
+        (2861_u16, r#"REDARC ELECTRONICS PTY LTD"#),
+        (2862_u16, r#"MOCA System Inc."#),
+        (2863_u16, r#"Duke Manufacturing Co"#),
+        (2864_u16, r#"ART SPA"#),
+        (2865_u16, r#"Silver Wolf Vehicles Inc."#),
+        (2866_u16, r#"Hala Systems, Inc."#),
+        (2867_u16, r#"ARMATURA LLC"#),
+        (2868_u16, r#"CONZUMEX INDUSTRIES PRIVATE LIMITED"#),
+        (2869_u16, r#"BH SENS"#),
+        (2870_u16, r#"SINTEF"#),
+        (2871_u16, r#"Omnivoltaic Energy Solutions Limited Company"#),
+        (2872_u16, r#"WISYCOM S.R.L."#),
+        (2873_u16, r#"Red 100 Lighting Co., ltd."#),
+        (2874_u16, r#"Impact Biosystems, Inc."#),
+        (2875_u16, r#"AIC semiconductor (Shanghai) Co., Ltd."#),
+        (2876_u16, r#"Dodge Industrial, Inc."#),
+        (2877_u16, r#"REALTIMEID AS"#),
+        (2878_u16, r#"ISEO Serrature S.p.a."#),
+        (2879_u16, r#"MindRhythm, Inc."#),
+        (2880_u16, r#"Havells India Limited"#),
+        (2881_u16, r#"Sentrax GmbH"#),
+        (2882_u16, r#"TSI"#),
+        (2883_u16, r#"INCITAT ENVIRONNEMENT"#),
+        (2884_u16, r#"nFore Technology Co., Ltd."#),
+        (2885_u16, r#"Electronic Sensors, Inc."#),
+        (2886_u16, r#"Bird Rides, Inc."#),
+        (2887_u16, r#"Gentex Corporation"#),
+        (2888_u16, r#"NIO USA, Inc."#),
+        (2889_u16, r#"SkyHawke Technologies"#),
+        (2890_u16, r#"Nomono AS"#),
+        (2891_u16, r#"EMS Integrators, LLC"#),
+        (2892_u16, r#"BiosBob.Biz"#),
+        (2893_u16, r#"Adam Hall GmbH"#),
+        (2894_u16, r#"ICP Systems B.V."#),
+        (2895_u16, r#"Breezi.io, Inc."#),
+        (2896_u16, r#"Mesh Systems LLC"#),
+        (2897_u16, r#"FUN FACTORY GmbH"#),
+        (2898_u16, r#"ZIIP Inc"#),
+        (2899_u16, r#"SHENZHEN KAADAS INTELLIGENT TECHNOLOGY CO.,Ltd"#),
+        (2900_u16, r#"Emotion Fitness GmbH & Co. KG"#),
+        (2901_u16, r#"H G M Automotive Electronics, Inc."#),
+        (2902_u16, r#"BORA - Vertriebs GmbH & Co KG"#),
+        (2903_u16, r#"CONVERTRONIX TECHNOLOGIES AND SERVICES LLP"#),
+        (2904_u16, r#"TOKAI-DENSHI INC"#),
+        (2905_u16, r#"Versa Group B.V."#),
+        (2906_u16, r#"H.P. Shelby Manufacturing, LLC."#),
+        (2907_u16, r#"Shenzhen ImagineVision Technology Limited"#),
+        (2908_u16, r#"Exponential Power, Inc."#),
+        (2909_u16, r#"Fujian Newland Auto-ID Tech. Co., Ltd."#),
+        (2910_u16, r#"CELLCONTROL, INC."#),
+        (2911_u16, r#"Rivieh, Inc."#),
+        (2912_u16, r#"RATOC Systems, Inc."#),
+        (2913_u16, r#"Sentek Pty Ltd"#),
+        (2914_u16, r#"NOVEA ENERGIES"#),
+        (2915_u16, r#"Innolux Corporation"#),
+        (2916_u16, r#"NingBo klite Electric Manufacture Co.,LTD"#),
+        (2917_u16, r#"The Apache Software Foundation"#),
+        (2918_u16, r#"MITSUBISHI ELECTRIC AUTOMATION (THAILAND) COMPANY LIMITED"#),
+        (2919_u16, r#"CleanSpace Technology Pty Ltd"#),
+        (2920_u16, r#"Quha oy"#),
+        (2921_u16, r#"Addaday"#),
+        (2922_u16, r#"Dymo"#),
+        (2923_u16, r#"Samsara Networks, Inc"#),
+        (2924_u16, r#"Sensitech, Inc."#),
+        (2925_u16, r#"SOLUM CO., LTD"#),
+        (2926_u16, r#"React Mobile"#),
+        (2927_u16, r#"Shenzhen Malide Technology Co.,Ltd"#),
+        (2928_u16, r#"JDRF Electromag Engineering Inc"#),
+        (2929_u16, r#"lilbit ODM AS"#),
+        (2930_u16, r#"Geeknet, Inc."#),
+        (2931_u16, r#"HARADA INDUSTRY CO., LTD."#),
+        (2932_u16, r#"BQN"#),
+        (2933_u16, r#"Triple W Japan Inc."#),
+        (2934_u16, r#"MAX-co., ltd"#),
+        (2935_u16, r#"Aixlink(Chengdu) Co., Ltd."#),
+        (2936_u16, r#"FIELD DESIGN INC."#),
+        (2937_u16, r#"Sankyo Air Tech Co.,Ltd."#),
+        (2938_u16, r#"Shenzhen KTC Technology Co.,Ltd."#),
+        (2939_u16, r#"Hardcoder Oy"#),
+        (2940_u16, r#"Scangrip A/S"#),
+        (2941_u16, r#"FoundersLane GmbH"#),
+        (2942_u16, r#"Offcode Oy"#),
+        (2943_u16, r#"ICU tech GmbH"#),
+        (2944_u16, r#"AXELIFE"#),
+        (2945_u16, r#"SCM Group"#),
+        (2946_u16, r#"Mammut Sports Group AG"#),
+        (2947_u16, r#"Taiga Motors Inc."#),
+        (2948_u16, r#"Presidio Medical, Inc."#),
+        (2949_u16, r#"VIMANA TECH PTY LTD"#),
+        (2950_u16, r#"Trek Bicycle"#),
+        (2951_u16, r#"Ampetronic Ltd"#),
+        (2952_u16, r#"Muguang (Guangdong) Intelligent Lighting Technology Co., Ltd"#),
+        (2953_u16, r#"Rotronic AG"#),
+        (2954_u16, r#"Seiko Instruments Inc."#),
+        (2955_u16, r#"American Technology Components, Incorporated"#),
+        (2956_u16, r#"MOTREX"#),
+        (2957_u16, r#"Pertech Industries Inc"#),
+        (2958_u16, r#"Gentle Energy Corp."#),
+        (2959_u16, r#"Senscomm Semiconductor Co., Ltd."#),
+        (2960_u16, r#"Ineos Automotive Limited"#),
+        (2961_u16, r#"Alfen ICU B.V."#),
+        (2962_u16, r#"Citisend Solutions, SL"#),
+        (2963_u16, r#"Hangzhou BroadLink Technology Co., Ltd."#),
+        (2964_u16, r#"Dreem SAS"#),
+        (2965_u16, r#"Netwake GmbH"#),
+        (2966_u16, r#"Telecom Design"#),
+        (2967_u16, r#"SILVER TREE LABS, INC."#),
+        (2968_u16, r#"Gymstory B.V."#),
+        (2969_u16, r#"The Goodyear Tire & Rubber Company"#),
+        (2970_u16, r#"Beijing Wisepool Infinite Intelligence Technology Co.,Ltd"#),
+        (2971_u16, r#"GISMAN"#),
+        (2972_u16, r#"Komatsu Ltd."#),
+        (2973_u16, r#"Sensoria Holdings LTD"#),
+        (2974_u16, r#"Audio Partnership Plc"#),
+        (2975_u16, r#"Group Lotus Limited"#),
+        (2976_u16, r#"Data Sciences International"#),
+        (2977_u16, r#"Bunn-O-Matic Corporation"#),
+        (2978_u16, r#"TireCheck GmbH"#),
+        (2979_u16, r#"Sonova Consumer Hearing GmbH"#),
+        (2980_u16, r#"Vervent Audio Group"#),
+        (2981_u16, r#"SONICOS ENTERPRISES, LLC"#),
+        (2982_u16, r#"Nissan Motor Co., Ltd."#),
+        (2983_u16, r#"hearX Group (Pty) Ltd"#),
+        (2984_u16, r#"GLOWFORGE INC."#),
+        (2985_u16, r#"Allterco Robotics ltd"#),
+        (2986_u16, r#"Infinitegra, Inc."#),
+        (2987_u16, r#"Grandex International Corporation"#),
+        (2988_u16, r#"Machfu Inc."#),
+        (2989_u16, r#"Roambotics, Inc."#),
+        (2990_u16, r#"Soma Labs LLC"#),
+        (2991_u16, r#"NITTO KOGYO CORPORATION"#),
+        (2992_u16, r#"Ecolab Inc."#),
+        (2993_u16, r#"Beijing ranxin intelligence technology Co.,LTD"#),
+        (2994_u16, r#"Fjorden Electra AS"#),
+        (2995_u16, r#"Flender GmbH"#),
+        (2996_u16, r#"New Cosmos USA, Inc."#),
+        (2997_u16, r#"Xirgo Technologies, LLC"#),
+        (2998_u16, r#"Build With Robots Inc."#),
+        (2999_u16, r#"IONA Tech LLC"#),
+        (3000_u16, r#"INNOVAG PTY. LTD."#),
+        (3001_u16, r#"SaluStim Group Oy"#),
+        (3002_u16, r#"Huso, INC"#),
+        (3003_u16, r#"SWISSINNO SOLUTIONS AG"#),
+        (3004_u16, r#"T2REALITY SOLUTIONS PRIVATE LIMITED"#),
+        (3005_u16, r#"ETHEORY PTY LTD"#),
+        (3006_u16, r#"SAAB Aktiebolag"#),
+        (3007_u16, r#"HIMSA II K/S"#),
+        (3008_u16, r#"READY FOR SKY LLP"#),
+        (3009_u16, r#"Miele & Cie. KG"#),
+        (3010_u16, r#"EntWick Co."#),
+        (3011_u16, r#"MCOT INC."#),
+        (3012_u16, r#"TECHTICS ENGINEERING B.V."#),
+        (3013_u16, r#"Aperia Technologies, Inc."#),
+        (3014_u16, r#"TCL COMMUNICATION EQUIPMENT CO.,LTD."#),
+        (3015_u16, r#"Signtle Inc."#),
+        (3016_u16, r#"OTF Distribution, LLC"#),
+        (3017_u16, r#"Neuvatek Inc."#),
+        (3018_u16, r#"Perimeter Technologies, Inc."#),
+        (3019_u16, r#"Divesoft s.r.o."#),
+        (3020_u16, r#"Sylvac sa"#),
+        (3021_u16, r#"Amiko srl"#),
+        (3022_u16, r#"Neurosity, Inc."#),
+        (3023_u16, r#"LL Tec Group LLC"#),
+        (3024_u16, r#"Durag GmbH"#),
+        (3025_u16, r#"Hubei Yuan Times Technology Co., Ltd."#),
+        (3026_u16, r#"IDEC"#),
+        (3027_u16, r#"Procon Analytics, LLC"#),
+        (3028_u16, r#"ndd Medizintechnik AG"#),
+        (3029_u16, r#"Super B Lithium Power B.V."#),
+        (3030_u16, r#"Shenzhen Injoinic Technology Co., Ltd."#),
+        (3031_u16, r#"VINFAST TRADING AND PRODUCTION JOINT STOCK COMPANY"#),
+        (3032_u16, r#"PURA SCENTS, INC."#),
+        (3033_u16, r#"Elics Basis Ltd."#),
+        (3034_u16, r#"Aardex Ltd."#),
+        (3035_u16, r#"CHAR-BROIL, LLC"#),
+        (3036_u16, r#"Ledworks S.r.l."#),
+        (3037_u16, r#"Coroflo Limited"#),
+        (3038_u16, r#"Yale"#),
+        (3039_u16, r#"WINKEY ENTERPRISE (HONG KONG) LIMITED"#),
+        (3040_u16, r#"Koizumi Lighting Technology corp."#),
+        (3041_u16, r#"Back40 Precision"#),
+        (3042_u16, r#"OTC engineering"#),
+        (3043_u16, r#"Comtel Systems Ltd."#),
+        (3044_u16, r#"Deepfield Connect GmbH"#),
+        (3045_u16, r#"ZWILLING J.A. Henckels Aktiengesellschaft"#),
+        (3046_u16, r#"Puratap Pty Ltd"#),
+        (3047_u16, r#"Fresnel Technologies, Inc."#),
+        (3048_u16, r#"Sensormate AG"#),
+        (3049_u16, r#"Shindengen Electric Manufacturing Co., Ltd."#),
+        (3050_u16, r#"Twenty Five Seven, prodaja in storitve, d.o.o."#),
+        (3051_u16, r#"Luna Health, Inc."#),
+        (3052_u16, r#"Miracle-Ear, Inc."#),
+        (3053_u16, r#"CORAL-TAIYI Co. Ltd."#),
+        (3054_u16, r#"LINKSYS USA, INC."#),
+        (3055_u16, r#"Safetytest GmbH"#),
+        (3056_u16, r#"KIDO SPORTS CO., LTD."#),
+        (3057_u16, r#"Site IQ LLC"#),
+        (3058_u16, r#"Angel Medical Systems, Inc."#),
+        (3059_u16, r#"PONE BIOMETRICS AS"#),
+        (3060_u16, r#"ER Lab LLC"#),
+        (3061_u16, r#"T5 tek, Inc."#),
+        (3062_u16, r#"greenTEG AG"#),
+        (3063_u16, r#"Wacker Neuson SE"#),
+        (3064_u16, r#"Innovacionnye Resheniya"#),
+        (3065_u16, r#"Alio, Inc"#),
+        (3066_u16, r#"CleanBands Systems Ltd."#),
+        (3067_u16, r#"Dodam Enersys Co., Ltd"#),
+        (3068_u16, r#"T+A elektroakustik GmbH & Co.KG"#),
+        (3069_u16, r#"Esmé Solutions"#),
+        (3070_u16, r#"Media-Cartec GmbH"#),
+        (3071_u16, r#"Ratio Electric BV"#),
+        (3072_u16, r#"MQA Limited"#),
+        (3073_u16, r#"NEOWRK SISTEMAS INTELIGENTES S.A."#),
+        (3074_u16, r#"Loomanet, Inc."#),
+        (3075_u16, r#"Puff Corp"#),
+        (3076_u16, r#"Happy Health, Inc."#),
+        (3077_u16, r#"Montage Connect, Inc."#),
+        (3078_u16, r#"LED Smart Inc."#),
+        (3079_u16, r#"CONSTRUKTS, INC."#),
+        (3080_u16, r#"limited liability company "Red""#),
+        (3081_u16, r#"Senic Inc."#),
+        (3082_u16, r#"Automated Pet Care Products, LLC"#),
+        (3083_u16, r#"aconno GmbH"#),
+        (3084_u16, r#"Mendeltron, Inc."#),
+        (3085_u16, r#"Mereltron bv"#),
+        (3086_u16, r#"ALEX DENKO CO.,LTD."#),
+        (3087_u16, r#"AETERLINK"#),
+        (3088_u16, r#"Cosmed s.r.l."#),
+        (3089_u16, r#"Gordon Murray Design Limited"#),
+        (3090_u16, r#"IoSA"#),
+        (3091_u16, r#"Scandinavian Health Limited"#),
+        (3092_u16, r#"Fasetto, Inc."#),
+        (3093_u16, r#"Geva Sol B.V."#),
+        (3094_u16, r#"TYKEE PTY. LTD."#),
+        (3095_u16, r#"SomnoMed Limited"#),
+        (3096_u16, r#"CORROHM"#),
+        (3097_u16, r#"Arlo Technologies, Inc."#),
+        (3098_u16, r#"Catapult Group International Ltd"#),
+        (3099_u16, r#"Rockchip Electronics Co., Ltd."#),
+        (3100_u16, r#"GEMU"#),
+        (3101_u16, r#"OFF Line Japan Co., Ltd."#),
+        (3102_u16, r#"EC sense co., Ltd"#),
+        (3103_u16, r#"LVI Co."#),
+        (3104_u16, r#"COMELIT GROUP S.P.A."#),
+        (3105_u16, r#"Foshan Viomi Electrical Technology Co., Ltd"#),
+        (3106_u16, r#"Glamo Inc."#),
+        (3107_u16, r#"KEYTEC,Inc."#),
+        (3108_u16, r#"SMARTD TECHNOLOGIES INC."#),
+        (3109_u16, r#"JURA Elektroapparate AG"#),
+        (3110_u16, r#"Performance Electronics, Ltd."#),
+        (3111_u16, r#"Pal Electronics"#),
+        (3112_u16, r#"Embecta Corp."#),
+        (3113_u16, r#"DENSO AIRCOOL CORPORATION"#),
+        (3114_u16, r#"Caresix Inc."#),
+        (3115_u16, r#"GigaDevice Semiconductor Inc."#),
+        (3116_u16, r#"Zeku Technology (Shanghai) Corp., Ltd."#),
+        (3117_u16, r#"OTF Product Sourcing, LLC"#),
+        (3118_u16, r#"Easee AS"#),
+        (3119_u16, r#"BEEHERO, INC."#),
+        (3120_u16, r#"McIntosh Group Inc"#),
+        (3121_u16, r#"KINDOO LLP"#),
+        (3122_u16, r#"Xian Yisuobao Electronic Technology Co., Ltd."#),
+        (3123_u16, r#"Exeger Operations AB"#),
+        (3124_u16, r#"BYD Company Limited"#),
+        (3125_u16, r#"Thermokon-Sensortechnik GmbH"#),
+        (3126_u16, r#"Cosmicnode BV"#),
+        (3127_u16, r#"SignalQuest, LLC"#),
+        (3128_u16, r#"Noritz Corporation."#),
+        (3129_u16, r#"TIGER CORPORATION"#),
+        (3130_u16, r#"Equinosis, LLC"#),
+        (3131_u16, r#"ORB Innovations Ltd"#),
+        (3132_u16, r#"Classified Cycling"#),
+        (3133_u16, r#"Wrmth Corp."#),
+        (3134_u16, r#"BELLDESIGN Inc."#),
+        (3135_u16, r#"Stinger Equipment, Inc."#),
+        (3136_u16, r#"HORIBA, Ltd."#),
+        (3137_u16, r#"Control Solutions LLC"#),
+        (3138_u16, r#"Heath Consultants Inc."#),
+        (3139_u16, r#"Berlinger & Co. AG"#),
+        (3140_u16, r#"ONCELABS LLC"#),
+        (3141_u16, r#"Brose Verwaltung SE, Bamberg"#),
+        (3142_u16, r#"Granwin IoT Technology (Guangzhou) Co.,Ltd"#),
+        (3143_u16, r#"Epsilon Electronics,lnc"#),
+        (3144_u16, r#"VALEO MANAGEMENT SERVICES"#),
+        (3145_u16, r#"twopounds gmbh"#),
+        (3146_u16, r#"atSpiro ApS"#),
+        (3147_u16, r#"ADTRAN, Inc."#),
+        (3148_u16, r#"Orpyx Medical Technologies Inc."#),
+        (3149_u16, r#"Seekwave Technology Co.,ltd."#),
+        (3150_u16, r#"Tactile Engineering, Inc."#),
+        (3151_u16, r#"SharkNinja Operating LLC"#),
+        (3152_u16, r#"Imostar Technologies Inc."#),
+        (3153_u16, r#"INNOVA S.R.L."#),
+        (3154_u16, r#"ESCEA LIMITED"#),
+        (3155_u16, r#"Taco, Inc."#),
+        (3156_u16, r#"HiViz Lighting, Inc."#),
+        (3157_u16, r#"Zintouch B.V."#),
+        (3158_u16, r#"Rheem Sales Company, Inc."#),
+        (3159_u16, r#"UNEEG medical A/S"#),
+        (3160_u16, r#"Hykso Inc."#),
+        (3161_u16, r#"CYBERDYNE Inc."#),
+        (3162_u16, r#"Lockswitch Sdn Bhd"#),
+        (3163_u16, r#"Alban Giacomo S.P.A."#),
+        (3164_u16, r#"MGM WIRELESSS HOLDINGS PTY LTD"#),
+        (3165_u16, r#"StepUp Solutions ApS"#),
+        (3166_u16, r#"BlueID GmbH"#),
+        (3167_u16, r#"Nanjing Linkpower Microelectronics Co.,Ltd"#),
+        (3168_u16, r#"KEBA Energy Automation GmbH"#),
+        (3169_u16, r#"NNOXX, Inc"#),
+        (3170_u16, r#"Phiaton Corporation"#),
+        (3171_u16, r#"phg Peter Hengstler GmbH + Co. KG"#),
+        (3172_u16, r#"dormakaba Holding AG"#),
+        (3173_u16, r#"WAKO CO,.LTD"#),
+        (3174_u16, r#"DEN Smart Home B.V."#),
+        (3175_u16, r#"TRACKTING S.R.L."#),
+        (3176_u16, r#"Emerja Corporation"#),
+        (3177_u16, r#"BLITZ electric motors. LTD"#),
+        (3178_u16, r#"CONSORCIO TRUST CONTROL - NETTEL"#),
+        (3179_u16, r#"GILSON SAS"#),
+        (3180_u16, r#"SNIFF LOGIC LTD"#),
+        (3181_u16, r#"Fidure Corp."#),
+        (3182_u16, r#"Sensa LLC"#),
+        (3183_u16, r#"Parakey AB"#),
+        (3184_u16, r#"SCARAB SOLUTIONS LTD"#),
+        (3185_u16, r#"BitGreen Technolabz (OPC) Private Limited"#),
+        (3186_u16, r#"StreetCar ORV, LLC"#),
+        (3187_u16, r#"Truma Gerätetechnik GmbH & Co. KG"#),
+        (3188_u16, r#"yupiteru"#),
+        (3189_u16, r#"Embedded Engineering Solutions LLC"#),
+        (3190_u16, r#"Shenzhen Gwell Times Technology Co. , Ltd"#),
+        (3191_u16, r#"TEAC Corporation"#),
+        (3192_u16, r#"CHARGTRON IOT PRIVATE LIMITED"#),
+        (3193_u16, r#"Zhuhai Smartlink Technology Co., Ltd"#),
+        (3194_u16, r#"Triductor Technology (Suzhou), Inc."#),
+        (3195_u16, r#"PT SADAMAYA GRAHA TEKNOLOGI"#),
+        (3196_u16, r#"Mopeka Products LLC"#),
+        (3197_u16, r#"3ALogics, Inc."#),
+        (3198_u16, r#"BOOMING OF THINGS"#),
+        (3199_u16, r#"Rochester Sensors, LLC"#),
+        (3200_u16, r#"CARDIOID - TECHNOLOGIES, LDA"#),
+        (3201_u16, r#"Carrier Corporation"#),
+        (3202_u16, r#"NACON"#),
+        (3203_u16, r#"Watchdog Systems LLC"#),
+        (3204_u16, r#"MAXON INDUSTRIES, INC."#),
+        (3205_u16, r#"Amlogic, Inc."#),
+        (3206_u16, r#"Qingdao Eastsoft Communication Technology Co.,Ltd"#),
+        (3207_u16, r#"Weltek Technologies Company Limited"#),
+        (3208_u16, r#"Nextivity Inc."#),
+        (3209_u16, r#"AGZZX OPTOELECTRONICS TECHNOLOGY CO., LTD"#),
+        (3210_u16, r#"ARTISTIC&CO.GLOBAL Ltd."#),
+        (3211_u16, r#"Heavys Inc"#),
+        (3212_u16, r#"T-Mobile USA"#),
+        (3213_u16, r#"tonies GmbH"#),
+        (3214_u16, r#"Technocon Engineering Ltd."#),
+        (3215_u16, r#"Radar Automobile Sales(Shandong)Co.,Ltd."#),
+        (3216_u16, r#"WESCO AG"#),
+        (3217_u16, r#"Yashu Systems"#),
+        (3218_u16, r#"Kesseböhmer Ergonomietechnik GmbH"#),
+        (3219_u16, r#"Movesense Oy"#),
+        (3220_u16, r#"Baxter Healthcare Corporation"#),
+        (3221_u16, r#"Gemstone Lights Canada Ltd."#),
+        (3222_u16, r#"H+B Hightech GmbH"#),
+        (3223_u16, r#"Deako"#),
+        (3224_u16, r#"MiX Telematics International (PTY) LTD"#),
+        (3225_u16, r#"Vire Health Oy"#),
+        (3226_u16, r#"ALF Inc."#),
+        (3227_u16, r#"NTT sonority, Inc."#),
+        (3228_u16, r#"Sunstone-RTLS Ipari Szolgaltato Korlatolt Felelossegu Tarsasag"#),
+        (3229_u16, r#"Ribbiot, INC."#),
+        (3230_u16, r#"ECCEL CORPORATION SAS"#),
+        (3231_u16, r#"Dragonfly Energy Corp."#),
+        (3232_u16, r#"BIGBEN"#),
+        (3233_u16, r#"YAMAHA MOTOR CO.,LTD."#),
+        (3234_u16, r#"XSENSE LTD"#),
+        (3235_u16, r#"MAQUET GmbH"#),
+        (3236_u16, r#"MITSUBISHI ELECTRIC LIGHTING CO, LTD"#),
+        (3237_u16, r#"Princess Cruise Lines, Ltd."#),
+        (3238_u16, r#"Megger Ltd"#),
+        (3239_u16, r#"Verve InfoTec Pty Ltd"#),
+        (3240_u16, r#"Sonas, Inc."#),
+        (3241_u16, r#"Mievo Technologies Private Limited"#),
+        (3242_u16, r#"Shenzhen Poseidon Network Technology Co., Ltd"#),
+        (3243_u16, r#"HERUTU ELECTRONICS CORPORATION"#),
+        (3244_u16, r#"Shenzhen Shokz Co.,Ltd."#),
+        (3245_u16, r#"Shenzhen Openhearing Tech CO., LTD ."#),
+        (3246_u16, r#"Evident Corporation"#),
+        (3247_u16, r#"NEURINNOV"#),
+        (3248_u16, r#"SwipeSense, Inc."#),
+        (3249_u16, r#"RF Creations"#),
+        (3250_u16, r#"SHINKAWA Sensor Technology, Inc."#),
+        (3251_u16, r#"janova GmbH"#),
+        (3252_u16, r#"Eberspaecher Climate Control Systems GmbH"#),
+        (3253_u16, r#"Racketry, d. o. o."#),
+        (3254_u16, r#"THE EELECTRIC MACARON LLC"#),
+        (3255_u16, r#"Cucumber Lighting Controls Limited"#),
+        (3256_u16, r#"Shanghai Proxy Network Technology Co., Ltd."#),
+        (3257_u16, r#"seca GmbH & Co. KG"#),
+        (3258_u16, r#"Ameso Tech (OPC) Private Limited"#),
+        (3259_u16, r#"Emlid Tech Kft."#),
+        (3260_u16, r#"TROX GmbH"#),
+        (3261_u16, r#"Pricer AB"#),
+        (3263_u16, r#"Forward Thinking Systems LLC."#),
+        (3264_u16, r#"Garnet Instruments Ltd."#),
+        (3265_u16, r#"CLEIO Inc."#),
+        (3266_u16, r#"Anker Innovations Limited"#),
+        (3267_u16, r#"HMD Global Oy"#),
+        (3268_u16, r#"ABUS August Bremicker Soehne Kommanditgesellschaft"#),
+        (3269_u16, r#"Open Road Solutions, Inc."#),
+        (3270_u16, r#"Serial Technology Corporation"#),
+        (3271_u16, r#"SB C&S Corp."#),
+        (3272_u16, r#"TrikThom"#),
+        (3273_u16, r#"Innocent Technology Co., Ltd."#),
+        (3274_u16, r#"Cyclops Marine Ltd"#),
+        (3275_u16, r#"NOTHING TECHNOLOGY LIMITED"#),
+        (3276_u16, r#"Kord Defence Pty Ltd"#),
+        (3277_u16, r#"YanFeng Visteon(Chongqing) Automotive Electronic Co.,Ltd"#),
+        (3278_u16, r#"SENOSPACE LLC"#),
+        (3279_u16, r#"Shenzhen CESI Information Technology Co., Ltd."#),
+        (3280_u16, r#"MooreSilicon Semiconductor Technology (Shanghai) Co., LTD."#),
+        (3281_u16, r#"Imagine Marketing Limited"#),
+        (3282_u16, r#"EQOM SSC B.V."#),
+        (3283_u16, r#"TechSwipe"#),
+        (3284_u16, r#"Shenzhen Zhiduotun IoT Technology Co., Ltd"#),
+        (3285_u16, r#"Numa Products, LLC"#),
+        (3286_u16, r#"HHO (Hangzhou) Digital Technology Co., Ltd."#),
+        (3287_u16, r#"Maztech Industries, LLC"#),
+        (3288_u16, r#"SIA Mesh Group"#),
+        (3289_u16, r#"Minami acoustics Limited"#),
+        (3290_u16, r#"Wolf Steel ltd"#),
+        (3291_u16, r#"Circus World Displays Limited"#),
+        (3292_u16, r#"Ypsomed AG"#),
+        (3293_u16, r#"Alif Semiconductor, Inc."#),
+        (3294_u16, r#"RESPONSE TECHNOLOGIES, LTD."#),
+        (3295_u16, r#"SHENZHEN CHENYUN ELECTRONICS  CO., LTD"#),
+        (3296_u16, r#"VODALOGIC PTY LTD"#),
+        (3297_u16, r#"Regal Beloit America, Inc."#),
+        (3298_u16, r#"CORVENT MEDICAL, INC."#),
+        (3299_u16, r#"Taiwan Fuhsing"#),
+        (3300_u16, r#"Off-Highway Powertrain Services Germany GmbH"#),
+        (3301_u16, r#"Amina Distribution AS"#),
+        (3302_u16, r#"McWong International, Inc."#),
+        (3303_u16, r#"TAG HEUER SA"#),
+        (3304_u16, r#"Dongguan Yougo Electronics Co.,Ltd."#),
+        (3305_u16, r#"PEAG, LLC dba JLab Audio"#),
+        (3306_u16, r#"HAYWARD INDUSTRIES, INC."#),
+        (3307_u16, r#"Shenzhen Tingting Technology Co. LTD"#),
+        (3308_u16, r#"Pacific Coast Fishery Services (2003) Inc."#),
+        (3309_u16, r#"CV. NURI TEKNIK"#),
+        (3310_u16, r#"MadgeTech, Inc"#),
+        (3311_u16, r#"POGS B.V."#),
+        (3312_u16, r#"THOTAKA TEKHNOLOGIES INDIA PRIVATE LIMITED"#),
+        (3313_u16, r#"Midmark"#),
+        (3314_u16, r#"BestSens AG"#),
+        (3315_u16, r#"Radio Sound"#),
+        (3316_u16, r#"SOLUX PTY LTD"#),
+        (3317_u16, r#"BOS Balance of Storage Systems AG"#),
+        (3318_u16, r#"OJ Electronics A/S"#),
+        (3319_u16, r#"TVS Motor Company Ltd."#),
+        (3320_u16, r#"core sensing GmbH"#),
+        (3321_u16, r#"Tamblue Oy"#),
+        (3322_u16, r#"Protect Animals With Satellites LLC"#),
+        (3323_u16, r#"Tyromotion GmbH"#),
+        (3324_u16, r#"ElectronX design"#),
+        (3325_u16, r#"Wuhan Woncan Construction Technologies Co., Ltd."#),
+        (3326_u16, r#"Thule Group AB"#),
+        (3327_u16, r#"Ergodriven Inc"#),
     ]
     .into_iter()
     .map(|(id, name)| (Uuid16::from_be_bytes(id.to_be_bytes()), name))
diff --git a/rust/src/wrapper/common.rs b/rust/src/wrapper/common.rs
new file mode 100644
index 0000000..4947560
--- /dev/null
+++ b/rust/src/wrapper/common.rs
@@ -0,0 +1,34 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Shared resources found under bumble's common.py
+use pyo3::{PyObject, Python, ToPyObject};
+
+/// Represents the sink for some transport mechanism
+pub struct TransportSink(pub(crate) PyObject);
+
+impl ToPyObject for TransportSink {
+    fn to_object(&self, _py: Python<'_>) -> PyObject {
+        self.0.clone()
+    }
+}
+
+/// Represents the source for some transport mechanism
+pub struct TransportSource(pub(crate) PyObject);
+
+impl ToPyObject for TransportSource {
+    fn to_object(&self, _py: Python<'_>) -> PyObject {
+        self.0.clone()
+    }
+}
diff --git a/rust/src/wrapper/controller.rs b/rust/src/wrapper/controller.rs
new file mode 100644
index 0000000..4f19dd6
--- /dev/null
+++ b/rust/src/wrapper/controller.rs
@@ -0,0 +1,66 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Controller components
+use crate::wrapper::{
+    common::{TransportSink, TransportSource},
+    hci::Address,
+    link::Link,
+    wrap_python_async, PyDictExt,
+};
+use pyo3::{
+    intern,
+    types::{PyDict, PyModule},
+    PyObject, PyResult, Python,
+};
+use pyo3_asyncio::tokio::into_future;
+
+/// A controller that can send and receive HCI frames via some link
+#[derive(Clone)]
+pub struct Controller(pub(crate) PyObject);
+
+impl Controller {
+    /// Creates a new [Controller] object. When optional arguments are not specified, the Python
+    /// module specifies the defaults. Must be called from a thread with a Python event loop, which
+    /// should be true on `tokio::main` and `async_std::main`.
+    ///
+    /// For more info, see https://awestlake87.github.io/pyo3-asyncio/master/doc/pyo3_asyncio/#event-loop-references-and-contextvars.
+    pub async fn new(
+        name: &str,
+        host_source: Option<TransportSource>,
+        host_sink: Option<TransportSink>,
+        link: Option<Link>,
+        public_address: Option<Address>,
+    ) -> PyResult<Self> {
+        Python::with_gil(|py| {
+            let controller_ctr = PyModule::import(py, intern!(py, "bumble.controller"))?
+                .getattr(intern!(py, "Controller"))?;
+
+            let kwargs = PyDict::new(py);
+            kwargs.set_item("name", name)?;
+            kwargs.set_opt_item("host_source", host_source)?;
+            kwargs.set_opt_item("host_sink", host_sink)?;
+            kwargs.set_opt_item("link", link)?;
+            kwargs.set_opt_item("public_address", public_address)?;
+
+            // Controller constructor (`__init__`) is not (and can't be) marked async, but calls
+            // `get_running_loop`, and thus needs wrapped in an async function.
+            wrap_python_async(py, controller_ctr)?
+                .call((), Some(kwargs))
+                .and_then(into_future)
+        })?
+        .await
+        .map(Self)
+    }
+}
diff --git a/rust/src/wrapper/device.rs b/rust/src/wrapper/device.rs
index be5e4fa..6bf958a 100644
--- a/rust/src/wrapper/device.rs
+++ b/rust/src/wrapper/device.rs
@@ -14,12 +14,16 @@
 
 //! Devices and connections to them
 
+use crate::internal::hci::WithPacketType;
 use crate::{
     adv::AdvertisementDataBuilder,
     wrapper::{
         core::AdvertisingData,
         gatt_client::{ProfileServiceProxy, ServiceProxy},
-        hci::{Address, HciErrorCode},
+        hci::{
+            packets::{Command, ErrorCode, Event},
+            Address, HciCommandWrapper,
+        },
         host::Host,
         l2cap::LeConnectionOrientedChannel,
         transport::{Sink, Source},
@@ -27,18 +31,73 @@
     },
 };
 use pyo3::{
+    exceptions::PyException,
     intern,
     types::{PyDict, PyModule},
-    IntoPy, PyObject, PyResult, Python, ToPyObject,
+    IntoPy, PyErr, PyObject, PyResult, Python, ToPyObject,
 };
 use pyo3_asyncio::tokio::into_future;
 use std::path;
 
+/// Represents the various properties of some device
+pub struct DeviceConfiguration(PyObject);
+
+impl DeviceConfiguration {
+    /// Creates a new configuration, letting the internal Python object set all the defaults
+    pub fn new() -> PyResult<DeviceConfiguration> {
+        Python::with_gil(|py| {
+            PyModule::import(py, intern!(py, "bumble.device"))?
+                .getattr(intern!(py, "DeviceConfiguration"))?
+                .call0()
+                .map(|any| Self(any.into()))
+        })
+    }
+
+    /// Creates a new configuration from the specified file
+    pub fn load_from_file(&mut self, device_config: &path::Path) -> PyResult<()> {
+        Python::with_gil(|py| {
+            self.0
+                .call_method1(py, intern!(py, "load_from_file"), (device_config,))
+        })
+        .map(|_| ())
+    }
+}
+
+impl ToPyObject for DeviceConfiguration {
+    fn to_object(&self, _py: Python<'_>) -> PyObject {
+        self.0.clone()
+    }
+}
+
 /// A device that can send/receive HCI frames.
 #[derive(Clone)]
 pub struct Device(PyObject);
 
 impl Device {
+    /// Creates a Device. When optional arguments are not specified, the Python object specifies the
+    /// defaults.
+    pub fn new(
+        name: Option<&str>,
+        address: Option<Address>,
+        config: Option<DeviceConfiguration>,
+        host: Option<Host>,
+        generic_access_service: Option<bool>,
+    ) -> PyResult<Self> {
+        Python::with_gil(|py| {
+            let kwargs = PyDict::new(py);
+            kwargs.set_opt_item("name", name)?;
+            kwargs.set_opt_item("address", address)?;
+            kwargs.set_opt_item("config", config)?;
+            kwargs.set_opt_item("host", host)?;
+            kwargs.set_opt_item("generic_access_service", generic_access_service)?;
+
+            PyModule::import(py, intern!(py, "bumble.device"))?
+                .getattr(intern!(py, "Device"))?
+                .call((), Some(kwargs))
+                .map(|any| Self(any.into()))
+        })
+    }
+
     /// Create a Device per the provided file configured to communicate with a controller through an HCI source/sink
     pub fn from_config_file_with_hci(
         device_config: &path::Path,
@@ -66,6 +125,29 @@
         })
     }
 
+    /// Sends an HCI command on this Device, returning the command's event result.
+    pub async fn send_command(&self, command: &Command, check_result: bool) -> PyResult<Event> {
+        Python::with_gil(|py| {
+            self.0
+                .call_method1(
+                    py,
+                    intern!(py, "send_command"),
+                    (HciCommandWrapper(command.clone()), check_result),
+                )
+                .and_then(|coroutine| into_future(coroutine.as_ref(py)))
+        })?
+        .await
+        .and_then(|event| {
+            Python::with_gil(|py| {
+                let py_bytes = event.call_method0(py, intern!(py, "__bytes__"))?;
+                let bytes: &[u8] = py_bytes.extract(py)?;
+                let event = Event::parse_with_packet_type(bytes)
+                    .map_err(|e| PyErr::new::<PyException, _>(e.to_string()))?;
+                Ok(event)
+            })
+        })
+    }
+
     /// Turn the device on
     pub async fn power_on(&self) -> PyResult<()> {
         Python::with_gil(|py| {
@@ -236,7 +318,7 @@
             kwargs.set_opt_item("mps", mps)?;
             self.0
                 .call_method(py, intern!(py, "open_l2cap_channel"), (), Some(kwargs))
-                .and_then(|coroutine| pyo3_asyncio::tokio::into_future(coroutine.as_ref(py)))
+                .and_then(|coroutine| into_future(coroutine.as_ref(py)))
         })?
         .await
         .map(LeConnectionOrientedChannel::from)
@@ -244,13 +326,13 @@
 
     /// Disconnect from device with provided reason. When optional arguments are not specified, the
     /// Python module specifies the defaults.
-    pub async fn disconnect(&mut self, reason: Option<HciErrorCode>) -> PyResult<()> {
+    pub async fn disconnect(&mut self, reason: Option<ErrorCode>) -> PyResult<()> {
         Python::with_gil(|py| {
             let kwargs = PyDict::new(py);
             kwargs.set_opt_item("reason", reason)?;
             self.0
                 .call_method(py, intern!(py, "disconnect"), (), Some(kwargs))
-                .and_then(|coroutine| pyo3_asyncio::tokio::into_future(coroutine.as_ref(py)))
+                .and_then(|coroutine| into_future(coroutine.as_ref(py)))
         })?
         .await
         .map(|_| ())
@@ -259,7 +341,7 @@
     /// Register a callback to be called on disconnection.
     pub fn on_disconnection(
         &mut self,
-        callback: impl Fn(Python, HciErrorCode) -> PyResult<()> + Send + 'static,
+        callback: impl Fn(Python, ErrorCode) -> PyResult<()> + Send + 'static,
     ) -> PyResult<()> {
         let boxed = ClosureCallback::new(move |py, args, _kwargs| {
             callback(py, args.get_item(0)?.extract()?)
diff --git a/rust/src/wrapper/hci.rs b/rust/src/wrapper/hci.rs
index 41dcbf3..b029a65 100644
--- a/rust/src/wrapper/hci.rs
+++ b/rust/src/wrapper/hci.rs
@@ -14,84 +14,62 @@
 
 //! HCI
 
+pub use crate::internal::hci::{packets, Error, Packet};
+
+use crate::{
+    internal::hci::WithPacketType,
+    wrapper::hci::packets::{AddressType, Command, ErrorCode},
+};
 use itertools::Itertools as _;
 use pyo3::{
-    exceptions::PyException, intern, types::PyModule, FromPyObject, PyAny, PyErr, PyObject,
-    PyResult, Python, ToPyObject,
+    exceptions::PyException,
+    intern, pyclass, pymethods,
+    types::{PyBytes, PyModule},
+    FromPyObject, IntoPy, PyAny, PyErr, PyObject, PyResult, Python, ToPyObject,
 };
 
-/// HCI error code.
-pub struct HciErrorCode(u8);
-
-impl<'source> FromPyObject<'source> for HciErrorCode {
-    fn extract(ob: &'source PyAny) -> PyResult<Self> {
-        Ok(HciErrorCode(ob.extract()?))
-    }
-}
-
-impl ToPyObject for HciErrorCode {
-    fn to_object(&self, py: Python<'_>) -> PyObject {
-        self.0.to_object(py)
-    }
-}
-
 /// Provides helpers for interacting with HCI
 pub struct HciConstant;
 
 impl HciConstant {
     /// Human-readable error name
-    pub fn error_name(status: HciErrorCode) -> PyResult<String> {
+    pub fn error_name(status: ErrorCode) -> PyResult<String> {
         Python::with_gil(|py| {
             PyModule::import(py, intern!(py, "bumble.hci"))?
                 .getattr(intern!(py, "HCI_Constant"))?
-                .call_method1(intern!(py, "error_name"), (status.0,))?
+                .call_method1(intern!(py, "error_name"), (status.to_object(py),))?
                 .extract()
         })
     }
 }
 
 /// A Bluetooth address
+#[derive(Clone)]
 pub struct Address(pub(crate) PyObject);
 
 impl Address {
+    /// Creates a new [Address] object
+    pub fn new(address: &str, address_type: &AddressType) -> PyResult<Self> {
+        Python::with_gil(|py| {
+            PyModule::import(py, intern!(py, "bumble.device"))?
+                .getattr(intern!(py, "Address"))?
+                .call1((address, address_type.to_object(py)))
+                .map(|any| Self(any.into()))
+        })
+    }
+
     /// The type of address
     pub fn address_type(&self) -> PyResult<AddressType> {
         Python::with_gil(|py| {
-            let addr_type = self
-                .0
+            self.0
                 .getattr(py, intern!(py, "address_type"))?
-                .extract::<u32>(py)?;
-
-            let module = PyModule::import(py, intern!(py, "bumble.hci"))?;
-            let klass = module.getattr(intern!(py, "Address"))?;
-
-            if addr_type
-                == klass
-                    .getattr(intern!(py, "PUBLIC_DEVICE_ADDRESS"))?
-                    .extract::<u32>()?
-            {
-                Ok(AddressType::PublicDevice)
-            } else if addr_type
-                == klass
-                    .getattr(intern!(py, "RANDOM_DEVICE_ADDRESS"))?
-                    .extract::<u32>()?
-            {
-                Ok(AddressType::RandomDevice)
-            } else if addr_type
-                == klass
-                    .getattr(intern!(py, "PUBLIC_IDENTITY_ADDRESS"))?
-                    .extract::<u32>()?
-            {
-                Ok(AddressType::PublicIdentity)
-            } else if addr_type
-                == klass
-                    .getattr(intern!(py, "RANDOM_IDENTITY_ADDRESS"))?
-                    .extract::<u32>()?
-            {
-                Ok(AddressType::RandomIdentity)
-            } else {
-                Err(PyErr::new::<PyException, _>("Invalid address type"))
-            }
+                .extract::<u8>(py)?
+                .try_into()
+                .map_err(|addr_type| {
+                    PyErr::new::<PyException, _>(format!(
+                        "Failed to convert {addr_type} to AddressType"
+                    ))
+                })
         })
     }
 
@@ -134,12 +112,45 @@
     }
 }
 
-/// BT address types
-#[allow(missing_docs)]
-#[derive(PartialEq, Eq, Debug)]
-pub enum AddressType {
-    PublicDevice,
-    RandomDevice,
-    PublicIdentity,
-    RandomIdentity,
+impl ToPyObject for Address {
+    fn to_object(&self, _py: Python<'_>) -> PyObject {
+        self.0.clone()
+    }
+}
+
+/// Implements minimum necessary interface to be treated as bumble's [HCI_Command].
+/// While pyo3's macros do not support generics, this could probably be refactored to allow multiple
+/// implementations of the HCI_Command methods in the future, if needed.
+#[pyclass]
+pub(crate) struct HciCommandWrapper(pub(crate) Command);
+
+#[pymethods]
+impl HciCommandWrapper {
+    fn __bytes__(&self, py: Python) -> PyResult<PyObject> {
+        let bytes = PyBytes::new(py, &self.0.clone().to_vec_with_packet_type());
+        Ok(bytes.into_py(py))
+    }
+
+    #[getter]
+    fn op_code(&self) -> u16 {
+        self.0.get_op_code().into()
+    }
+}
+
+impl ToPyObject for AddressType {
+    fn to_object(&self, py: Python<'_>) -> PyObject {
+        u8::from(self).to_object(py)
+    }
+}
+
+impl<'source> FromPyObject<'source> for ErrorCode {
+    fn extract(ob: &'source PyAny) -> PyResult<Self> {
+        ob.extract()
+    }
+}
+
+impl ToPyObject for ErrorCode {
+    fn to_object(&self, py: Python<'_>) -> PyObject {
+        u8::from(self).to_object(py)
+    }
 }
diff --git a/rust/src/wrapper/host.rs b/rust/src/wrapper/host.rs
index ab81450..8295664 100644
--- a/rust/src/wrapper/host.rs
+++ b/rust/src/wrapper/host.rs
@@ -14,8 +14,12 @@
 
 //! Host-side types
 
-use crate::wrapper::transport::{Sink, Source};
-use pyo3::{intern, prelude::PyModule, types::PyDict, PyObject, PyResult, Python};
+use crate::wrapper::{
+    transport::{Sink, Source},
+    wrap_python_async,
+};
+use pyo3::{intern, prelude::PyModule, types::PyDict, PyObject, PyResult, Python, ToPyObject};
+use pyo3_asyncio::tokio::into_future;
 
 /// Host HCI commands
 pub struct Host {
@@ -29,13 +33,23 @@
     }
 
     /// Create a new Host
-    pub fn new(source: Source, sink: Sink) -> PyResult<Self> {
+    pub async fn new(source: Source, sink: Sink) -> PyResult<Self> {
         Python::with_gil(|py| {
-            PyModule::import(py, intern!(py, "bumble.host"))?
-                .getattr(intern!(py, "Host"))?
-                .call((source.0, sink.0), None)
-                .map(|any| Self { obj: any.into() })
-        })
+            let host_ctr =
+                PyModule::import(py, intern!(py, "bumble.host"))?.getattr(intern!(py, "Host"))?;
+
+            let kwargs = PyDict::new(py);
+            kwargs.set_item("controller_source", source.0)?;
+            kwargs.set_item("controller_sink", sink.0)?;
+
+            // Needed for Python 3.8-3.9, in which the Semaphore object, when constructed, calls
+            // `get_event_loop`.
+            wrap_python_async(py, host_ctr)?
+                .call((), Some(kwargs))
+                .and_then(into_future)
+        })?
+        .await
+        .map(|any| Self { obj: any })
     }
 
     /// Send a reset command and perform other reset tasks.
@@ -61,6 +75,12 @@
     }
 }
 
+impl ToPyObject for Host {
+    fn to_object(&self, _py: Python<'_>) -> PyObject {
+        self.obj.clone()
+    }
+}
+
 /// Driver factory to use when initializing a host
 #[derive(Debug, Clone)]
 pub enum DriverFactory {
diff --git a/rust/src/wrapper/link.rs b/rust/src/wrapper/link.rs
new file mode 100644
index 0000000..7169ef5
--- /dev/null
+++ b/rust/src/wrapper/link.rs
@@ -0,0 +1,38 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Link components
+use pyo3::{intern, types::PyModule, PyObject, PyResult, Python, ToPyObject};
+
+/// Link bus for controllers to communicate with each other
+#[derive(Clone)]
+pub struct Link(pub(crate) PyObject);
+
+impl Link {
+    /// Creates a [Link] object that transports messages locally
+    pub fn new_local_link() -> PyResult<Self> {
+        Python::with_gil(|py| {
+            PyModule::import(py, intern!(py, "bumble.link"))?
+                .getattr(intern!(py, "LocalLink"))?
+                .call0()
+                .map(|any| Self(any.into()))
+        })
+    }
+}
+
+impl ToPyObject for Link {
+    fn to_object(&self, _py: Python<'_>) -> PyObject {
+        self.0.clone()
+    }
+}
diff --git a/rust/src/wrapper/mod.rs b/rust/src/wrapper/mod.rs
index 94ac15a..27b86d9 100644
--- a/rust/src/wrapper/mod.rs
+++ b/rust/src/wrapper/mod.rs
@@ -22,13 +22,17 @@
 
 // Re-exported to make it easy for users to depend on the same `PyObject`, etc
 pub use pyo3;
+pub use pyo3_asyncio;
+
 use pyo3::{
+    intern,
     prelude::*,
     types::{PyDict, PyTuple},
 };
-pub use pyo3_asyncio;
 
 pub mod assigned_numbers;
+pub mod common;
+pub mod controller;
 pub mod core;
 pub mod device;
 pub mod drivers;
@@ -36,6 +40,7 @@
 pub mod hci;
 pub mod host;
 pub mod l2cap;
+pub mod link;
 pub mod logging;
 pub mod profile;
 pub mod transport;
@@ -119,3 +124,11 @@
         (self.callback)(py, args, kwargs).map(|_| py.None())
     }
 }
+
+/// Wraps the Python function in a Python async function. `pyo3_asyncio` needs functions to be
+/// marked async to properly inject a running loop.
+pub(crate) fn wrap_python_async<'a>(py: Python<'a>, function: &'a PyAny) -> PyResult<&'a PyAny> {
+    PyModule::import(py, intern!(py, "bumble.utils"))?
+        .getattr(intern!(py, "wrap_async"))?
+        .call1((function,))
+}
diff --git a/rust/src/wrapper/transport.rs b/rust/src/wrapper/transport.rs
index 6c9468d..a7ec9e9 100644
--- a/rust/src/wrapper/transport.rs
+++ b/rust/src/wrapper/transport.rs
@@ -14,6 +14,7 @@
 
 //! HCI packet transport
 
+use crate::wrapper::controller::Controller;
 use pyo3::{intern, types::PyModule, PyObject, PyResult, Python};
 
 /// A source/sink pair for HCI packet I/O.
@@ -67,6 +68,18 @@
 #[derive(Clone)]
 pub struct Source(pub(crate) PyObject);
 
+impl From<Controller> for Source {
+    fn from(value: Controller) -> Self {
+        Self(value.0)
+    }
+}
+
 /// The sink side of a [Transport].
 #[derive(Clone)]
 pub struct Sink(pub(crate) PyObject);
+
+impl From<Controller> for Sink {
+    fn from(value: Controller) -> Self {
+        Self(value.0)
+    }
+}
diff --git a/setup.cfg b/setup.cfg
index 1ca73c7..5cdf35a 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -91,9 +91,13 @@
     mypy == 1.5.0
     nox >= 2022
     pylint == 2.15.8
+    pyyaml >= 6.0
     types-appdirs >= 1.4.3
     types-invoke >= 1.7.3
     types-protobuf >= 4.21.0
+avatar =
+    pandora-avatar == 0.0.5
+    rootcanal == 1.3.0 ; python_version>='3.10'
 documentation =
     mkdocs >= 1.4.0
     mkdocs-material >= 8.5.6
diff --git a/tests/a2dp_test.py b/tests/a2dp_test.py
index 92f7915..ca59890 100644
--- a/tests/a2dp_test.py
+++ b/tests/a2dp_test.py
@@ -185,7 +185,7 @@
         sink.on('rtp_packet', on_rtp_packet)
 
     # Create a listener to wait for AVDTP connections
-    listener = Listener(Listener.create_registrar(two_devices.devices[1]))
+    listener = Listener.for_device(two_devices.devices[1])
     listener.on('connection', on_avdtp_connection)
 
     async def make_connection():
diff --git a/tests/avdtp_test.py b/tests/avdtp_test.py
index 1ca5254..666a84c 100644
--- a/tests/avdtp_test.py
+++ b/tests/avdtp_test.py
@@ -45,12 +45,14 @@
     ]
     message = Get_Capabilities_Response(capabilities)
     parsed = Message.create(
-        AVDTP_GET_CAPABILITIES, Message.RESPONSE_ACCEPT, message.payload
+        AVDTP_GET_CAPABILITIES, Message.MessageType.RESPONSE_ACCEPT, message.payload
     )
     assert message.payload == parsed.payload
 
     message = Set_Configuration_Command(3, 4, capabilities)
-    parsed = Message.create(AVDTP_SET_CONFIGURATION, Message.COMMAND, message.payload)
+    parsed = Message.create(
+        AVDTP_SET_CONFIGURATION, Message.MessageType.COMMAND, message.payload
+    )
     assert message.payload == parsed.payload
 
 
diff --git a/tests/keystore_test.py b/tests/keystore_test.py
index 2a3d48d..34a2cb2 100644
--- a/tests/keystore_test.py
+++ b/tests/keystore_test.py
@@ -94,6 +94,7 @@
 
 
 # -----------------------------------------------------------------------------
+@pytest.mark.asyncio
 async def test_basic(temporary_file):
     with open(temporary_file, mode='w', encoding='utf-8') as file:
         file.write("{}")
@@ -125,6 +126,7 @@
 
 
 # -----------------------------------------------------------------------------
+@pytest.mark.asyncio
 async def test_parsing(temporary_file):
     with open(temporary_file, mode='w', encoding='utf-8') as file:
         file.write(JSON1)
@@ -137,6 +139,7 @@
 
 
 # -----------------------------------------------------------------------------
+@pytest.mark.asyncio
 async def test_default_namespace(temporary_file):
     with open(temporary_file, mode='w', encoding='utf-8') as file:
         file.write(JSON1)
diff --git a/tests/l2cap_test.py b/tests/l2cap_test.py
index c6b2340..5cb285c 100644
--- a/tests/l2cap_test.py
+++ b/tests/l2cap_test.py
@@ -22,7 +22,11 @@
 import pytest
 
 from bumble.core import ProtocolError
-from bumble.l2cap import L2CAP_Connection_Request
+from bumble.l2cap import (
+    L2CAP_Connection_Request,
+    ClassicChannelSpec,
+    LeCreditBasedChannelSpec,
+)
 from .test_utils import TwoDevices
 
 
@@ -80,7 +84,9 @@
 
     # Check that if there's no one listening, we can't connect
     with pytest.raises(ProtocolError):
-        l2cap_channel = await devices.connections[0].open_l2cap_channel(psm)
+        l2cap_channel = await devices.connections[0].create_l2cap_channel(
+            spec=LeCreditBasedChannelSpec(psm)
+        )
 
     # Now add a listener
     incoming_channel = None
@@ -95,8 +101,12 @@
 
         channel.sink = on_data
 
-    devices.devices[1].register_l2cap_channel_server(psm, on_coc)
-    l2cap_channel = await devices.connections[0].open_l2cap_channel(psm)
+    devices.devices[1].create_l2cap_server(
+        spec=LeCreditBasedChannelSpec(psm=1234), handler=on_coc
+    )
+    l2cap_channel = await devices.connections[0].create_l2cap_channel(
+        spec=LeCreditBasedChannelSpec(psm)
+    )
 
     messages = (bytes([1, 2, 3]), bytes([4, 5, 6]), bytes(10000))
     for message in messages:
@@ -138,10 +148,13 @@
 
         channel.sink = on_data
 
-    psm = devices.devices[1].register_l2cap_channel_server(
-        psm=0, server=on_coc, max_credits=max_credits, mtu=mtu, mps=mps
+    server = devices.devices[1].create_l2cap_server(
+        spec=LeCreditBasedChannelSpec(max_credits=max_credits, mtu=mtu, mps=mps),
+        handler=on_coc,
     )
-    l2cap_channel = await devices.connections[0].open_l2cap_channel(psm)
+    l2cap_channel = await devices.connections[0].create_l2cap_channel(
+        spec=LeCreditBasedChannelSpec(server.psm)
+    )
 
     messages = [bytes([1, 2, 3, 4, 5, 6, 7]) * x for x in (3, 10, 100, 789)]
     for message in messages:
@@ -189,8 +202,12 @@
     def on_client_data(data):
         client_received.append(data)
 
-    psm = devices.devices[1].register_l2cap_channel_server(psm=0, server=on_server_coc)
-    client_channel = await devices.connections[0].open_l2cap_channel(psm)
+    server = devices.devices[1].create_l2cap_server(
+        spec=LeCreditBasedChannelSpec(), handler=on_server_coc
+    )
+    client_channel = await devices.connections[0].create_l2cap_channel(
+        spec=LeCreditBasedChannelSpec(server.psm)
+    )
     client_channel.sink = on_client_data
 
     messages = [bytes([1, 2, 3, 4, 5, 6, 7]) * x for x in (3, 10, 100)]
diff --git a/tests/sdp_test.py b/tests/sdp_test.py
index 090e7b2..29db875 100644
--- a/tests/sdp_test.py
+++ b/tests/sdp_test.py
@@ -18,6 +18,7 @@
 import asyncio
 import logging
 import os
+import pytest
 
 from bumble.core import UUID, BT_L2CAP_PROTOCOL_ID, BT_RFCOMM_PROTOCOL_ID
 from bumble.sdp import (
@@ -99,13 +100,13 @@
     e = DataElement(DataElement.UUID, UUID('61A3512C-09BE-4DDC-A6A6-0B03667AAFC6'))
     basic_check(e)
 
-    e = DataElement(DataElement.TEXT_STRING, 'hello')
+    e = DataElement(DataElement.TEXT_STRING, b'hello')
     basic_check(e)
 
-    e = DataElement(DataElement.TEXT_STRING, 'hello' * 60)
+    e = DataElement(DataElement.TEXT_STRING, b'hello' * 60)
     basic_check(e)
 
-    e = DataElement(DataElement.TEXT_STRING, 'hello' * 20000)
+    e = DataElement(DataElement.TEXT_STRING, b'hello' * 20000)
     basic_check(e)
 
     e = DataElement(DataElement.BOOLEAN, True)
@@ -121,7 +122,7 @@
         DataElement.SEQUENCE,
         [
             DataElement(DataElement.BOOLEAN, True),
-            DataElement(DataElement.TEXT_STRING, 'hello'),
+            DataElement(DataElement.TEXT_STRING, b'hello'),
         ],
     )
     basic_check(e)
@@ -133,7 +134,7 @@
         DataElement.ALTERNATIVE,
         [
             DataElement(DataElement.BOOLEAN, True),
-            DataElement(DataElement.TEXT_STRING, 'hello'),
+            DataElement(DataElement.TEXT_STRING, b'hello'),
         ],
     )
     basic_check(e)
@@ -151,19 +152,19 @@
     e = DataElement.uuid(UUID.from_16_bits(1234))
     basic_check(e)
 
-    e = DataElement.text_string('hello')
+    e = DataElement.text_string(b'hello')
     basic_check(e)
 
     e = DataElement.boolean(True)
     basic_check(e)
 
     e = DataElement.sequence(
-        [DataElement.signed_integer(0, 1), DataElement.text_string('hello')]
+        [DataElement.signed_integer(0, 1), DataElement.text_string(b'hello')]
     )
     basic_check(e)
 
     e = DataElement.alternative(
-        [DataElement.signed_integer(0, 1), DataElement.text_string('hello')]
+        [DataElement.signed_integer(0, 1), DataElement.text_string(b'hello')]
     )
     basic_check(e)
 
@@ -202,6 +203,7 @@
 
 
 # -----------------------------------------------------------------------------
+@pytest.mark.asyncio
 async def test_service_search():
     # Setup connections
     devices = TwoDevices()
@@ -224,6 +226,7 @@
 
 
 # -----------------------------------------------------------------------------
+@pytest.mark.asyncio
 async def test_service_attribute():
     # Setup connections
     devices = TwoDevices()
@@ -244,6 +247,7 @@
 
 
 # -----------------------------------------------------------------------------
+@pytest.mark.asyncio
 async def test_service_search_attribute():
     # Setup connections
     devices = TwoDevices()
diff --git a/tools/generate_company_id_list.py b/tools/generate_company_id_list.py
index bba42b8..d79a6d8 100644
--- a/tools/generate_company_id_list.py
+++ b/tools/generate_company_id_list.py
@@ -14,25 +14,25 @@
 
 # -----------------------------------------------------------------------------
 # This script generates a python-syntax list of dictionary entries for the
-# company IDs listed at: https://www.bluetooth.com/specifications/assigned-numbers/company-identifiers/
-# The input to this script is the CSV file that can be obtained at that URL
+# company IDs listed at:
+# https://bitbucket.org/bluetooth-SIG/public/src/main/assigned_numbers/company_identifiers/company_identifiers.yaml
+# The input to this script is the YAML file that can be obtained at that URL
 # -----------------------------------------------------------------------------
 
 # -----------------------------------------------------------------------------
 # Imports
 # -----------------------------------------------------------------------------
 import sys
-import csv
+import yaml
 
 # -----------------------------------------------------------------------------
-with open(sys.argv[1], newline='') as csvfile:
-    reader = csv.reader(csvfile, delimiter=',', quotechar='"')
-    lines = []
-    for row in reader:
-        if len(row) == 3 and row[1].startswith('0x'):
-            company_id = row[1]
-            company_name = row[2]
-            escaped_company_name = company_name.replace('"', '\\"')
-            lines.append(f'    {company_id}: "{escaped_company_name}"')
+with open(sys.argv[1], "r") as yaml_file:
+    root = yaml.safe_load(yaml_file)
+    companies = {}
+    for company in root["company_identifiers"]:
+        companies[company["value"]] = company["name"]
 
-    print(',\n'.join(reversed(lines)))
+    for company_id in sorted(companies.keys()):
+        company_name = companies[company_id]
+        escaped_company_name = company_name.replace('"', '\\"')
+        print(f'    0x{company_id:04X}: "{escaped_company_name}",')
diff --git a/web/speaker/speaker.py b/web/speaker/speaker.py
index d9293a4..d9488ce 100644
--- a/web/speaker/speaker.py
+++ b/web/speaker/speaker.py
@@ -296,7 +296,7 @@
         self.device.on('key_store_update', self.on_key_store_update)
 
         # Create a listener to wait for AVDTP connections
-        self.listener = Listener(Listener.create_registrar(self.device))
+        self.listener = Listener.for_device(self.device)
         self.listener.on('connection', self.on_avdtp_connection)
 
         print(f'Speaker ready to play, codec={self.codec}')