Merge remote-tracking branch 'aosp/upstream-main' am: 5a897454ff am: cc61586906

Original change: https://android-review.googlesource.com/c/platform/external/python/bumble/+/3270792

Change-Id: Id7e0f8743b71640ac5006f9dbcf736d5414f994a
Signed-off-by: Automerger Merge Worker <[email protected]>
diff --git a/METADATA b/METADATA
index 2f4e87b..e4b9b79 100644
--- a/METADATA
+++ b/METADATA
@@ -11,7 +11,7 @@
     type: GIT
     value: "https://github.com/google/bumble"
   }
-  version: "cd9feeb4551f478691e33450219adf6d58c4871f"
-  last_upgrade_date { year: 2024 month: 9 day: 12 }
+  version: "737abdc481b226b16d85174d9ae0ebd9346b0fb4"
+  last_upgrade_date { year: 2024 month: 9 day: 17 }
   license_type: NOTICE
 }
diff --git a/apps/pair.py b/apps/pair.py
index c1ea332..67eec90 100644
--- a/apps/pair.py
+++ b/apps/pair.py
@@ -46,6 +46,12 @@
     ATT_INSUFFICIENT_AUTHENTICATION_ERROR,
     ATT_INSUFFICIENT_ENCRYPTION_ERROR,
 )
+from bumble.utils import AsyncRunner
+
+# -----------------------------------------------------------------------------
+# Constants
+# -----------------------------------------------------------------------------
+POST_PAIRING_DELAY = 1
 
 
 # -----------------------------------------------------------------------------
@@ -235,8 +241,10 @@
 
     # Listen for pairing events
     connection.on('pairing_start', on_pairing_start)
-    connection.on('pairing', lambda keys: on_pairing(connection.peer_address, keys))
-    connection.on('pairing_failure', on_pairing_failure)
+    connection.on('pairing', lambda keys: on_pairing(connection, keys))
+    connection.on(
+        'pairing_failure', lambda reason: on_pairing_failure(connection, reason)
+    )
 
     # Listen for encryption changes
     connection.on(
@@ -270,19 +278,24 @@
 
 
 # -----------------------------------------------------------------------------
-def on_pairing(address, keys):
[email protected]_in_task()
+async def on_pairing(connection, keys):
     print(color('***-----------------------------------', 'cyan'))
-    print(color(f'*** Paired! (peer identity={address})', 'cyan'))
+    print(color(f'*** Paired! (peer identity={connection.peer_address})', 'cyan'))
     keys.print(prefix=color('*** ', 'cyan'))
     print(color('***-----------------------------------', 'cyan'))
+    await asyncio.sleep(POST_PAIRING_DELAY)
+    await connection.disconnect()
     Waiter.instance.terminate()
 
 
 # -----------------------------------------------------------------------------
-def on_pairing_failure(reason):
[email protected]_in_task()
+async def on_pairing_failure(connection, reason):
     print(color('***-----------------------------------', 'red'))
     print(color(f'*** Pairing failed: {smp_error_name(reason)}', 'red'))
     print(color('***-----------------------------------', 'red'))
+    await connection.disconnect()
     Waiter.instance.terminate()
 
 
@@ -293,6 +306,7 @@
     mitm,
     bond,
     ctkd,
+    identity_address,
     linger,
     io,
     oob,
@@ -382,11 +396,18 @@
             oob_contexts = None
 
         # Set up a pairing config factory
+        if identity_address == 'public':
+            identity_address_type = PairingConfig.AddressType.PUBLIC
+        elif identity_address == 'random':
+            identity_address_type = PairingConfig.AddressType.RANDOM
+        else:
+            identity_address_type = None
         device.pairing_config_factory = lambda connection: PairingConfig(
             sc=sc,
             mitm=mitm,
             bonding=bond,
             oob=oob_contexts,
+            identity_address_type=identity_address_type,
             delegate=Delegate(mode, connection, io, prompt),
         )
 
@@ -457,6 +478,10 @@
     help='Enable CTKD',
     show_default=True,
 )
[email protected](
+    '--identity-address',
+    type=click.Choice(['random', 'public']),
+)
 @click.option('--linger', default=False, is_flag=True, help='Linger after pairing')
 @click.option(
     '--io',
@@ -493,6 +518,7 @@
     mitm,
     bond,
     ctkd,
+    identity_address,
     linger,
     io,
     oob,
@@ -518,6 +544,7 @@
             mitm,
             bond,
             ctkd,
+            identity_address,
             linger,
             io,
             oob,
diff --git a/bumble/att.py b/bumble/att.py
index 86d7fc6..6eed040 100644
--- a/bumble/att.py
+++ b/bumble/att.py
@@ -23,7 +23,6 @@
 # Imports
 # -----------------------------------------------------------------------------
 from __future__ import annotations
-from bumble.utils import OpenIntEnum
 
 import enum
 import functools
@@ -213,15 +212,6 @@
 # pylint: disable=invalid-name
 
 
-class CommonErrorCode(OpenIntEnum):
-    '''See Supplement to the Bluetooth Code Specification 1.2 List of Error Codes.'''
-
-    WRITE_REQUEST_REJECTED = 0xFC
-    CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR_IMPROPERLY_CONFIGURED = 0xFD
-    PROCEDURE_ALREADY_IN_PROGRESS = 0xFE
-    OUT_OF_RANGE = 0xFF
-
-
 # -----------------------------------------------------------------------------
 # Exceptions
 # -----------------------------------------------------------------------------
diff --git a/bumble/hci.py b/bumble/hci.py
index af39976..1d0cd8e 100644
--- a/bumble/hci.py
+++ b/bumble/hci.py
@@ -267,6 +267,19 @@
 HCI_LE_PERIODIC_ADVERTISING_SUBEVENT_DATA_REQUEST_EVENT     = 0X27
 HCI_LE_PERIODIC_ADVERTISING_RESPONSE_REPORT_EVENT           = 0X28
 HCI_LE_ENHANCED_CONNECTION_COMPLETE_V2_EVENT                = 0X29
+HCI_LE_READ_ALL_REMOTE_FEATURES_COMPLETE_EVENT              = 0x2A
+HCI_LE_CIS_ESTABLISHED_V2_EVENT                             = 0x2B
+HCI_LE_CS_READ_REMOTE_SUPPORTED_CAPABILITIES_COMPLETE_EVENT = 0x2C
+HCI_LE_CS_READ_REMOTE_FAE_TABLE_COMPLETE_EVENT              = 0x2D
+HCI_LE_CS_SECURITY_ENABLE_COMPLETE_EVENT                    = 0x2E
+HCI_LE_CS_CONFIG_COMPLETE_EVENT                             = 0x2F
+HCI_LE_CS_PROCEDURE_ENABLE_EVENT                            = 0x30
+HCI_LE_CS_SUBEVENT_RESULT_EVENT                             = 0x31
+HCI_LE_CS_SUBEVENT_RESULT_CONTINUE_EVENT                    = 0x32
+HCI_LE_CS_TEST_END_COMPLETE_EVENT                           = 0x33
+HCI_LE_MONITORED_ADVERTISERS_REPORT_EVENT                   = 0x34
+HCI_LE_FRAME_SPACE_UPDATE_EVENT                             = 0x35
+
 
 
 # HCI Command
@@ -573,11 +586,36 @@
 HCI_LE_SET_DEFAULT_SUBRATE_COMMAND                                       = hci_command_op_code(0x08, 0x007D)
 HCI_LE_SUBRATE_REQUEST_COMMAND                                           = hci_command_op_code(0x08, 0x007E)
 HCI_LE_SET_EXTENDED_ADVERTISING_PARAMETERS_V2_COMMAND                    = hci_command_op_code(0x08, 0x007F)
+HCI_LE_SET_DECISION_DATA_COMMAND                                         = hci_command_op_code(0x08, 0x0080)
+HCI_LE_SET_DECISION_INSTRUCTIONS_COMMAND                                 = hci_command_op_code(0x08, 0x0081)
 HCI_LE_SET_PERIODIC_ADVERTISING_SUBEVENT_DATA_COMMAND                    = hci_command_op_code(0x08, 0x0082)
 HCI_LE_SET_PERIODIC_ADVERTISING_RESPONSE_DATA_COMMAND                    = hci_command_op_code(0x08, 0x0083)
 HCI_LE_SET_PERIODIC_SYNC_SUBEVENT_COMMAND                                = hci_command_op_code(0x08, 0x0084)
 HCI_LE_EXTENDED_CREATE_CONNECTION_V2_COMMAND                             = hci_command_op_code(0x08, 0x0085)
 HCI_LE_SET_PERIODIC_ADVERTISING_PARAMETERS_V2_COMMAND                    = hci_command_op_code(0x08, 0x0086)
+HCI_LE_READ_ALL_LOCAL_SUPPORTED_FEATURES_COMMAND                         = hci_command_op_code(0x08, 0x0087)
+HCI_LE_READ_ALL_REMOTE_FEATURES_COMMAND                                  = hci_command_op_code(0x08, 0x0088)
+HCI_LE_CS_READ_LOCAL_SUPPORTED_CAPABILITIES_COMMAND                      = hci_command_op_code(0x08, 0x0089)
+HCI_LE_CS_READ_REMOTE_SUPPORTED_CAPABILITIES_COMMAND                     = hci_command_op_code(0x08, 0x008A)
+HCI_LE_CS_WRITE_CACHED_REMOTE_SUPPORTED_CAPABILITIES                     = hci_command_op_code(0x08, 0x008B)
+HCI_LE_CS_SECURITY_ENABLE_COMMAND                                        = hci_command_op_code(0x08, 0x008C)
+HCI_LE_CS_SET_DEFAULT_SETTINGS_COMMAND                                   = hci_command_op_code(0x08, 0x008D)
+HCI_LE_CS_READ_REMOTE_FAE_TABLE_COMMAND                                  = hci_command_op_code(0x08, 0x008E)
+HCI_LE_CS_WRITE_CACHED_REMOTE_FAE_TABLE_COMMAND                          = hci_command_op_code(0x08, 0x008F)
+HCI_LE_CS_CREATE_CONFIG_COMMAND                                          = hci_command_op_code(0x08, 0x0090)
+HCI_LE_CS_REMOVE_CONFIG_COMMAND                                          = hci_command_op_code(0x08, 0x0091)
+HCI_LE_CS_SET_CHANNEL_CLASSIFICATION_COMMAND                             = hci_command_op_code(0x08, 0x0092)
+HCI_LE_CS_SET_PROCEDURE_PARAMETERS_COMMAND                               = hci_command_op_code(0x08, 0x0093)
+HCI_LE_CS_PROCEDURE_ENABLE_COMMAND                                       = hci_command_op_code(0x08, 0x0094)
+HCI_LE_CS_TEST_COMMAND                                                   = hci_command_op_code(0x08, 0x0095)
+HCI_LE_CS_TEST_END_COMMAND                                               = hci_command_op_code(0x08, 0x0096)
+HCI_LE_SET_HOST_FEATURE_V2_COMMAND                                       = hci_command_op_code(0x08, 0x0097)
+HCI_LE_ADD_DEVICE_TO_MONITORED_ADVERTISERS_LIST_COMMAND                  = hci_command_op_code(0x08, 0x0098)
+HCI_LE_REMOVE_DEVICE_FROM_MONITORED_ADVERTISERS_LIST_COMMAND             = hci_command_op_code(0x08, 0x0099)
+HCI_LE_CLEAR_MONITORED_ADVERTISERS_LIST_COMMAND                          = hci_command_op_code(0x08, 0x009A)
+HCI_LE_READ_MONITORED_ADVERTISERS_LIST_SIZE_COMMAND                      = hci_command_op_code(0x08, 0x009B)
+HCI_LE_ENABLE_MONITORING_ADVERTISERS_COMMAND                             = hci_command_op_code(0x08, 0x009C)
+HCI_LE_FRAME_SPACE_UPDATE_COMMAND                                        = hci_command_op_code(0x08, 0x009D)
 
 
 # HCI Error Codes
@@ -1150,8 +1188,16 @@
     CHANNEL_CLASSIFICATION                         = 39
     ADVERTISING_CODING_SELECTION                   = 40
     ADVERTISING_CODING_SELECTION_HOST_SUPPORT      = 41
+    DECISION_BASED_ADVERTISING_FILTERING           = 42
     PERIODIC_ADVERTISING_WITH_RESPONSES_ADVERTISER = 43
     PERIODIC_ADVERTISING_WITH_RESPONSES_SCANNER    = 44
+    UNSEGMENTED_FRAMED_MODE                        = 45
+    CHANNEL_SOUNDING                               = 46
+    CHANNEL_SOUNDING_HOST_SUPPORT                  = 47
+    CHANNEL_SOUNDING_TONE_QUALITY_INDICATION       = 48
+    LL_EXTENDED_FEATURE_SET                        = 63
+    MONITORING_ADVERTISERS                         = 64
+    FRAME_SPACE_UPDATE                             = 65
 
 class LeFeatureMask(enum.IntFlag):
     LE_ENCRYPTION                                  = 1 << LeFeature.LE_ENCRYPTION
@@ -1196,8 +1242,16 @@
     CHANNEL_CLASSIFICATION                         = 1 << LeFeature.CHANNEL_CLASSIFICATION
     ADVERTISING_CODING_SELECTION                   = 1 << LeFeature.ADVERTISING_CODING_SELECTION
     ADVERTISING_CODING_SELECTION_HOST_SUPPORT      = 1 << LeFeature.ADVERTISING_CODING_SELECTION_HOST_SUPPORT
+    DECISION_BASED_ADVERTISING_FILTERING           = 1 << LeFeature.DECISION_BASED_ADVERTISING_FILTERING
     PERIODIC_ADVERTISING_WITH_RESPONSES_ADVERTISER = 1 << LeFeature.PERIODIC_ADVERTISING_WITH_RESPONSES_ADVERTISER
     PERIODIC_ADVERTISING_WITH_RESPONSES_SCANNER    = 1 << LeFeature.PERIODIC_ADVERTISING_WITH_RESPONSES_SCANNER
+    UNSEGMENTED_FRAMED_MODE                        = 1 << LeFeature.UNSEGMENTED_FRAMED_MODE
+    CHANNEL_SOUNDING                               = 1 << LeFeature.CHANNEL_SOUNDING
+    CHANNEL_SOUNDING_HOST_SUPPORT                  = 1 << LeFeature.CHANNEL_SOUNDING_HOST_SUPPORT
+    CHANNEL_SOUNDING_TONE_QUALITY_INDICATION       = 1 << LeFeature.CHANNEL_SOUNDING_TONE_QUALITY_INDICATION
+    LL_EXTENDED_FEATURE_SET                        = 1 << LeFeature.LL_EXTENDED_FEATURE_SET
+    MONITORING_ADVERTISERS                         = 1 << LeFeature.MONITORING_ADVERTISERS
+    FRAME_SPACE_UPDATE                             = 1 << LeFeature.FRAME_SPACE_UPDATE
 
 class LmpFeature(enum.IntEnum):
     # Page 0 (Legacy LMP features)
@@ -1565,12 +1619,16 @@
                 # This is an array field, starting with a 1-byte item count.
                 item_count = data[offset]
                 offset += 1
+                # Set fields first, because item_count might be 0.
+                for sub_field_name, _ in field:
+                    result[sub_field_name] = []
+
                 for _ in range(item_count):
                     for sub_field_name, sub_field_type in field:
                         value, size = HCI_Object.parse_field(
                             data, offset, sub_field_type
                         )
-                        result.setdefault(sub_field_name, []).append(value)
+                        result[sub_field_name].append(value)
                         offset += size
                 continue
 
@@ -2986,6 +3044,27 @@
 @HCI_Command.command(
     return_parameters_fields=[
         ('status', STATUS_SPEC),
+        ('authentication_enable', 1),
+    ]
+)
+class HCI_Read_Authentication_Enable_Command(HCI_Command):
+    '''
+    See Bluetooth spec @ 7.3.23 Read Authentication Enable Command
+    '''
+
+
+# -----------------------------------------------------------------------------
+@HCI_Command.command([('authentication_enable', 1)])
+class HCI_Write_Authentication_Enable_Command(HCI_Command):
+    '''
+    See Bluetooth spec @ 7.3.24 Write Authentication Enable Command
+    '''
+
+
+# -----------------------------------------------------------------------------
+@HCI_Command.command(
+    return_parameters_fields=[
+        ('status', STATUS_SPEC),
         ('class_of_device', {'size': 3, 'mapper': map_class_of_device}),
     ]
 )
@@ -3022,7 +3101,12 @@
 
 
 # -----------------------------------------------------------------------------
-@HCI_Command.command()
+@HCI_Command.command(
+    return_parameters_fields=[
+        ('status', STATUS_SPEC),
+        ('synchronous_flow_control_enable', 1),
+    ]
+)
 class HCI_Read_Synchronous_Flow_Control_Enable_Command(HCI_Command):
     '''
     See Bluetooth spec @ 7.3.36 Read Synchronous Flow Control Enable Command
@@ -3191,7 +3275,13 @@
 
 
 # -----------------------------------------------------------------------------
-@HCI_Command.command()
+@HCI_Command.command(
+    return_parameters_fields=[
+        ('status', STATUS_SPEC),
+        ('le_supported_host', 1),
+        ('unused', 1),
+    ]
+)
 class HCI_Read_LE_Host_Support_Command(HCI_Command):
     '''
     See Bluetooth spec @ 7.3.78 Read LE Host Support Command
@@ -3324,7 +3414,13 @@
 
 
 # -----------------------------------------------------------------------------
-@HCI_Command.command()
+@HCI_Command.command(
+    return_parameters_fields=[
+        ("status", STATUS_SPEC),
+        [("standard_codec_ids", 1)],
+        [("vendor_specific_codec_ids", 4)],
+    ]
+)
 class HCI_Read_Local_Supported_Codecs_Command(HCI_Command):
     '''
     See Bluetooth spec @ 7.4.8 Read Local Supported Codecs Command
@@ -3333,6 +3429,26 @@
 
 # -----------------------------------------------------------------------------
 @HCI_Command.command(
+    return_parameters_fields=[
+        ("status", STATUS_SPEC),
+        [("standard_codec_ids", 1), ("standard_codec_transports", 1)],
+        [("vendor_specific_codec_ids", 4), ("vendor_specific_codec_transports", 1)],
+    ]
+)
+class HCI_Read_Local_Supported_Codecs_V2_Command(HCI_Command):
+    '''
+    See Bluetooth spec @ 7.4.8 Read Local Supported Codecs Command
+    '''
+
+    class Transport(OpenIntEnum):
+        BR_EDR_ACL = 0x00
+        BR_EDR_SCO = 0x01
+        LE_CIS = 0x02
+        LE_BIS = 0x03
+
+
+# -----------------------------------------------------------------------------
+@HCI_Command.command(
     fields=[('handle', 2)],
     return_parameters_fields=[('status', STATUS_SPEC), ('handle', 2), ('rssi', -1)],
 )
@@ -3488,7 +3604,12 @@
 
 
 # -----------------------------------------------------------------------------
-@HCI_Command.command()
+@HCI_Command.command(
+    return_parameters_fields=[
+        ('status', STATUS_SPEC),
+        ('tx_power_level', 1),
+    ]
+)
 class HCI_LE_Read_Advertising_Physical_Channel_Tx_Power_Command(HCI_Command):
     '''
     See Bluetooth spec @ 7.8.6 LE Read Advertising Physical Channel Tx Power Command
@@ -3612,7 +3733,12 @@
 
 
 # -----------------------------------------------------------------------------
-@HCI_Command.command()
+@HCI_Command.command(
+    return_parameters_fields=[
+        ('status', STATUS_SPEC),
+        ('filter_accept_list_size', 1),
+    ]
+)
 class HCI_LE_Read_Filter_Accept_List_Size_Command(HCI_Command):
     '''
     See Bluetooth spec @ 7.8.14 LE Read Filter Accept List Size Command
@@ -3723,7 +3849,12 @@
 
 
 # -----------------------------------------------------------------------------
-@HCI_Command.command()
+@HCI_Command.command(
+    return_parameters_fields=[
+        ('status', STATUS_SPEC),
+        ('le_states', 8),
+    ]
+)
 class HCI_LE_Read_Supported_States_Command(HCI_Command):
     '''
     See Bluetooth spec @ 7.8.27 LE Read Supported States Command
@@ -4701,6 +4832,102 @@
 # -----------------------------------------------------------------------------
 @HCI_Command.command(
     fields=[
+        ('big_handle', 1),
+        ('advertising_handle', 1),
+        ('num_bis', 1),
+        ('sdu_interval', 3),
+        ('max_sdu', 2),
+        ('max_transport_latency', 2),
+        ('rtn', 1),
+        ('phy', 1),
+        ('packing', 1),
+        ('framing', 1),
+        ('encryption', 1),
+        ('broadcast_code', 16),
+    ],
+)
+class HCI_LE_Create_BIG_Command(HCI_Command):
+    '''
+    See Bluetooth spec @ 7.8.103 LE Create BIG command
+    '''
+
+    big_handle: int
+    advertising_handle: int
+    num_bis: int
+    sdu_interval: int
+    max_sdu: int
+    max_transport_latency: int
+    rtn: int
+    phy: int
+    packing: int
+    framing: int
+    encryption: int
+    broadcast_code: int
+
+
+# -----------------------------------------------------------------------------
+@HCI_Command.command(
+    fields=[
+        ('big_handle', 1),
+        ('reason', {'size': 1, 'mapper': HCI_Constant.error_name}),
+    ],
+)
+class HCI_LE_Terminate_BIG_Command(HCI_Command):
+    '''
+    See Bluetooth spec @ 7.8.105 LE Terminate BIG command
+    '''
+
+    big_handle: int
+    reason: int
+
+
+# -----------------------------------------------------------------------------
+@HCI_Command.command(
+    fields=[
+        ('big_handle', 1),
+        ('sync_handle', 2),
+        ('encryption', 1),
+        ('broadcast_code', 16),
+        ('mse', 1),
+        ('big_sync_timeout', 2),
+        [('bis', 1)],
+    ],
+)
+class HCI_LE_BIG_Create_Sync_Command(HCI_Command):
+    '''
+    See Bluetooth spec @ 7.8.106 LE BIG Create Sync command
+    '''
+
+    big_handle: int
+    sync_handle: int
+    encryption: int
+    broadcast_code: int
+    mse: int
+    big_sync_timeout: int
+    bis: List[int]
+
+
+# -----------------------------------------------------------------------------
+@HCI_Command.command(
+    fields=[
+        ('big_handle', 1),
+    ],
+    return_parameters_fields=[
+        ('status', STATUS_SPEC),
+        ('big_handle', 2),
+    ],
+)
+class HCI_LE_BIG_Terminate_Sync_Command(HCI_Command):
+    '''
+    See Bluetooth spec @ 7.8.107. LE BIG Terminate Sync command
+    '''
+
+    big_handle: int
+
+
+# -----------------------------------------------------------------------------
+@HCI_Command.command(
+    fields=[
         ('connection_handle', 2),
         ('data_path_direction', 1),
         ('data_path_id', 1),
@@ -5538,6 +5765,27 @@
     [
         ('status', STATUS_SPEC),
         ('connection_handle', 2),
+        ('service_data', 2),
+        ('sync_handle', 2),
+        ('advertising_sid', 1),
+        ('advertiser_address_type', Address.ADDRESS_TYPE_SPEC),
+        ('advertiser_address', Address.parse_address_preceded_by_type),
+        ('advertiser_phy', 1),
+        ('periodic_advertising_interval', 2),
+        ('advertiser_clock_accuracy', 1),
+    ]
+)
+class HCI_LE_Periodic_Advertising_Sync_Transfer_Received_Event(HCI_LE_Meta_Event):
+    '''
+    See Bluetooth spec @ 7.7.65.24 LE Periodic Advertising Sync Transfer Received Event
+    '''
+
+
+# -----------------------------------------------------------------------------
+@HCI_LE_Meta_Event.event(
+    [
+        ('status', STATUS_SPEC),
+        ('connection_handle', 2),
         ('cig_sync_delay', 3),
         ('cis_sync_delay', 3),
         ('transport_latency_c_to_p', 3),
@@ -6228,6 +6476,23 @@
 # -----------------------------------------------------------------------------
 @HCI_Event.event(
     [
+        ('status', STATUS_SPEC),
+        ('connection_handle', 2),
+        ('max_tx_latency', 2),
+        ('max_rx_latency', 2),
+        ('min_remote_timeout', 2),
+        ('min_local_timeout', 2),
+    ]
+)
+class HCI_Sniff_Subrating_Event(HCI_Event):
+    '''
+    See Bluetooth spec @ 7.7.37 Sniff Subrating Event
+    '''
+
+
+# -----------------------------------------------------------------------------
+@HCI_Event.event(
+    [
         ('num_responses', 1),
         ('bd_addr', Address.parse_address),
         ('page_scan_repetition_mode', 1),
diff --git a/bumble/profiles/aics.py b/bumble/profiles/aics.py
index 8b7468f..3a69627 100644
--- a/bumble/profiles/aics.py
+++ b/bumble/profiles/aics.py
@@ -442,14 +442,15 @@
         )
 
         super().__init__(
-            [
+            characteristics=[
                 self.audio_input_state_characteristic,  # type: ignore
                 self.gain_settings_properties_characteristic,  # type: ignore
                 self.audio_input_type_characteristic,  # type: ignore
                 self.audio_input_status_characteristic,  # type: ignore
                 self.audio_input_control_point_characteristic,  # type: ignore
                 self.audio_input_description_characteristic,  # type: ignore
-            ]
+            ],
+            primary=False,
         )
 
 
diff --git a/bumble/profiles/hap.py b/bumble/profiles/hap.py
index e61ac4f..1ef055c 100644
--- a/bumble/profiles/hap.py
+++ b/bumble/profiles/hap.py
@@ -19,7 +19,6 @@
 import asyncio
 import functools
 from bumble import att, gatt, gatt_client
-from bumble.att import CommonErrorCode
 from bumble.core import InvalidArgumentError, InvalidStateError
 from bumble.device import Device, Connection
 from bumble.utils import AsyncRunner, OpenIntEnum
@@ -352,16 +351,16 @@
             logging.warning(f'HAS require MTU >= 49: {connection}')
 
         if self.read_presets_request_in_progress:
-            raise att.ATT_Error(CommonErrorCode.PROCEDURE_ALREADY_IN_PROGRESS)
+            raise att.ATT_Error(att.ErrorCode.PROCEDURE_ALREADY_IN_PROGRESS)
         self.read_presets_request_in_progress = True
 
         start_index = value[1]
         if start_index == 0x00:
-            raise att.ATT_Error(CommonErrorCode.OUT_OF_RANGE)
+            raise att.ATT_Error(att.ErrorCode.OUT_OF_RANGE)
 
         num_presets = value[2]
         if num_presets == 0x00:
-            raise att.ATT_Error(CommonErrorCode.OUT_OF_RANGE)
+            raise att.ATT_Error(att.ErrorCode.OUT_OF_RANGE)
 
         # Sending `num_presets` presets ordered by increasing index field, starting from start_index
         presets = [
@@ -371,7 +370,7 @@
         ]
         del presets[num_presets:]
         if len(presets) == 0:
-            raise att.ATT_Error(CommonErrorCode.OUT_OF_RANGE)
+            raise att.ATT_Error(att.ErrorCode.OUT_OF_RANGE)
 
         AsyncRunner.spawn(self._read_preset_response(connection, presets))
 
@@ -468,7 +467,7 @@
         assert connection
 
         if self.read_presets_request_in_progress:
-            raise att.ATT_Error(CommonErrorCode.PROCEDURE_ALREADY_IN_PROGRESS)
+            raise att.ATT_Error(att.ErrorCode.PROCEDURE_ALREADY_IN_PROGRESS)
 
         index = value[1]
         preset = self.preset_records.get(index, None)
diff --git a/bumble/profiles/vcp.py b/bumble/profiles/vcp.py
index 0788219..57452d9 100644
--- a/bumble/profiles/vcp.py
+++ b/bumble/profiles/vcp.py
@@ -24,7 +24,7 @@
 from bumble import gatt
 from bumble import gatt_client
 
-from typing import Optional
+from typing import Optional, Sequence
 
 # -----------------------------------------------------------------------------
 # Constants
@@ -88,6 +88,7 @@
         muted: int = 0,
         change_counter: int = 0,
         volume_flags: int = 0,
+        included_services: Sequence[gatt.Service] = (),
     ) -> None:
         self.step_size = step_size
         self.volume_setting = volume_setting
@@ -117,11 +118,12 @@
         )
 
         super().__init__(
-            [
+            characteristics=[
                 self.volume_state,
                 self.volume_control_point,
                 self.volume_flags,
-            ]
+            ],
+            included_services=list(included_services),
         )
 
     @property
diff --git a/tests/aics_test.py b/tests/aics_test.py
index 8b47298..9526558 100644
--- a/tests/aics_test.py
+++ b/tests/aics_test.py
@@ -30,10 +30,9 @@
     GainMode,
     AudioInputStatus,
     AudioInputControlPointOpCode,
-    GAIN_SETTINGS_MAX_VALUE,
-    GAIN_SETTINGS_MIN_VALUE,
     ErrorCode,
 )
+from bumble.profiles.vcp import VolumeControlService, VolumeControlServiceProxy
 
 from .test_utils import TwoDevices
 
@@ -42,23 +41,34 @@
 # Tests
 # -----------------------------------------------------------------------------
 aics_service = AICSService()
+vcp_service = VolumeControlService(
+    volume_setting=32, muted=1, volume_flags=1, included_services=[aics_service]
+)
 
 
 @pytest_asyncio.fixture
 async def aics_client():
     devices = TwoDevices()
-    devices[0].add_service(aics_service)
+    devices[0].add_service(vcp_service)
 
     await devices.setup_connection()
 
-    assert devices.connections[0] is not None
-    assert devices.connections[1] is not None
+    assert devices.connections[0]
+    assert devices.connections[1]
 
     devices.connections[0].encryption = 1
     devices.connections[1].encryption = 1
 
     peer = device.Peer(devices.connections[1])
-    aics_client = await peer.discover_service_and_create_proxy(AICSServiceProxy)
+
+    vcp_client = await peer.discover_service_and_create_proxy(VolumeControlServiceProxy)
+
+    assert vcp_client
+    included_services = await peer.discover_included_services(vcp_client.service_proxy)
+    assert included_services
+    aics_service_discovered = included_services[0]
+    await peer.discover_characteristics(service=aics_service_discovered)
+    aics_client = AICSServiceProxy(aics_service_discovered)
 
     yield aics_client
 
diff --git a/tests/hci_test.py b/tests/hci_test.py
index 72f4022..1b69cda 100644
--- a/tests/hci_test.py
+++ b/tests/hci_test.py
@@ -60,6 +60,8 @@
     HCI_Number_Of_Completed_Packets_Event,
     HCI_Packet,
     HCI_PIN_Code_Request_Reply_Command,
+    HCI_Read_Local_Supported_Codecs_Command,
+    HCI_Read_Local_Supported_Codecs_V2_Command,
     HCI_Read_Local_Supported_Commands_Command,
     HCI_Read_Local_Supported_Features_Command,
     HCI_Read_Local_Version_Information_Command,
@@ -477,6 +479,51 @@
 
 
 # -----------------------------------------------------------------------------
+def test_HCI_Read_Local_Supported_Codecs_Command_Complete():
+    returned_parameters = (
+        HCI_Read_Local_Supported_Codecs_Command.parse_return_parameters(
+            bytes([HCI_SUCCESS, 3, CodecID.A_LOG, CodecID.CVSD, CodecID.LINEAR_PCM, 0])
+        )
+    )
+    assert returned_parameters.standard_codec_ids == [
+        CodecID.A_LOG,
+        CodecID.CVSD,
+        CodecID.LINEAR_PCM,
+    ]
+
+
+# -----------------------------------------------------------------------------
+def test_HCI_Read_Local_Supported_Codecs_V2_Command_Complete():
+    returned_parameters = (
+        HCI_Read_Local_Supported_Codecs_V2_Command.parse_return_parameters(
+            bytes(
+                [
+                    HCI_SUCCESS,
+                    3,
+                    CodecID.A_LOG,
+                    HCI_Read_Local_Supported_Codecs_V2_Command.Transport.BR_EDR_ACL,
+                    CodecID.CVSD,
+                    HCI_Read_Local_Supported_Codecs_V2_Command.Transport.BR_EDR_SCO,
+                    CodecID.LINEAR_PCM,
+                    HCI_Read_Local_Supported_Codecs_V2_Command.Transport.LE_CIS,
+                    0,
+                ]
+            )
+        )
+    )
+    assert returned_parameters.standard_codec_ids == [
+        CodecID.A_LOG,
+        CodecID.CVSD,
+        CodecID.LINEAR_PCM,
+    ]
+    assert returned_parameters.standard_codec_transports == [
+        HCI_Read_Local_Supported_Codecs_V2_Command.Transport.BR_EDR_ACL,
+        HCI_Read_Local_Supported_Codecs_V2_Command.Transport.BR_EDR_SCO,
+        HCI_Read_Local_Supported_Codecs_V2_Command.Transport.LE_CIS,
+    ]
+
+
+# -----------------------------------------------------------------------------
 def test_address():
     a = Address('C4:F2:17:1A:1D:BB')
     assert not a.is_public
diff --git a/tests/vcp_test.py b/tests/vcp_test.py
index d45a5f5..5853ed9 100644
--- a/tests/vcp_test.py
+++ b/tests/vcp_test.py
@@ -39,6 +39,9 @@
 
     await devices.setup_connection()
 
+    assert devices.connections[0]
+    assert devices.connections[1]
+
     # Mock encryption.
     devices.connections[0].encryption = 1
     devices.connections[1].encryption = 1