summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Henri Chataing <henrichataing@google.com> 2023-06-07 18:07:49 +0000
committer Henri Chataing <henrichataing@google.com> 2023-06-14 17:55:44 +0000
commita9635a2a9158b272631c3216fee13abca32d1bdc (patch)
treec8e79be2305989985c92c9dc9e4589dc837b3792
parent1f3c9b0e0bc560d88a48508885f922a5fb3608ba (diff)
RootCanal: Implement all Role Change configurations
Reland original change Idf251f4e8234c015fc43863cd3d7f7928a510c2d. Bug: 274248798 Test: atest --host rootcanal_ll_test Change-Id: I6b3ab4ff9f6636a8fe4ddd9b0e9c71832ebe7783
-rw-r--r--tools/rootcanal/Android.bp1
-rw-r--r--tools/rootcanal/config.proto5
-rw-r--r--tools/rootcanal/model/controller/acl_connection.cc4
-rw-r--r--tools/rootcanal/model/controller/acl_connection.h16
-rw-r--r--tools/rootcanal/model/controller/controller_properties.cc1
-rw-r--r--tools/rootcanal/model/controller/link_layer_controller.cc215
-rw-r--r--tools/rootcanal/packets/link_layer_packets.pdl2
-rw-r--r--tools/rootcanal/py/bluetooth.py11
-rw-r--r--tools/rootcanal/test/LMP/LIH/BV_01_C.py63
-rw-r--r--tools/rootcanal/test/LMP/LIH/BV_02_C.py66
-rw-r--r--tools/rootcanal/test/LMP/LIH/BV_142_C.py53
-rw-r--r--tools/rootcanal/test/LMP/LIH/BV_143_C.py72
-rw-r--r--tools/rootcanal/test/LMP/LIH/BV_144_C.py41
-rw-r--r--tools/rootcanal/test/LMP/LIH/BV_149_C.py68
-rw-r--r--tools/rootcanal/test/LMP/LIH/BV_78_C.py41
-rw-r--r--tools/rootcanal/test/LMP/LIH/BV_79_C.py43
-rw-r--r--tools/rootcanal/test/main.py8
17 files changed, 623 insertions, 87 deletions
diff --git a/tools/rootcanal/Android.bp b/tools/rootcanal/Android.bp
index c84cc456f1..d50bccdfe7 100644
--- a/tools/rootcanal/Android.bp
+++ b/tools/rootcanal/Android.bp
@@ -310,6 +310,7 @@ python_test_host {
"test/LL/CON_/PER/*.py",
"test/LL/DDI/ADV/*.py",
"test/LL/DDI/SCN/*.py",
+ "test/LMP/LIH/*.py",
"test/main.py",
],
data: [
diff --git a/tools/rootcanal/config.proto b/tools/rootcanal/config.proto
index 317ba2e4e3..32b452ddc2 100644
--- a/tools/rootcanal/config.proto
+++ b/tools/rootcanal/config.proto
@@ -29,11 +29,8 @@ message ControllerQuirks {
optional bool send_acl_data_before_connection_complete = 1;
// Configure a default value for the LE random address.
optional bool has_default_random_address = 2;
- // Send the Role Change event before the Connection Complete event
- // in the case where a role switch is initiated at connection establishment.
- optional bool send_role_change_before_connection_complete = 3;
// Send an Hardware Error event if any command is called before HCI Reset.
- optional bool hardware_error_before_reset = 4;
+ optional bool hardware_error_before_reset = 3;
}
message Controller {
diff --git a/tools/rootcanal/model/controller/acl_connection.cc b/tools/rootcanal/model/controller/acl_connection.cc
index 2c47ad191d..926e373aa1 100644
--- a/tools/rootcanal/model/controller/acl_connection.cc
+++ b/tools/rootcanal/model/controller/acl_connection.cc
@@ -33,10 +33,6 @@ void AclConnection::Encrypt() { encrypted_ = true; };
bool AclConnection::IsEncrypted() const { return encrypted_; };
-uint16_t AclConnection::GetLinkPolicySettings() const {
- return link_policy_settings_;
-};
-
void AclConnection::SetLinkPolicySettings(uint16_t settings) {
link_policy_settings_ = settings;
}
diff --git a/tools/rootcanal/model/controller/acl_connection.h b/tools/rootcanal/model/controller/acl_connection.h
index 74bc9a700a..7945ef0ece 100644
--- a/tools/rootcanal/model/controller/acl_connection.h
+++ b/tools/rootcanal/model/controller/acl_connection.h
@@ -26,6 +26,12 @@ namespace rootcanal {
using ::bluetooth::hci::AddressWithType;
+enum AclConnectionState {
+ kActiveMode,
+ kHoldMode,
+ kSniffMode,
+};
+
// Model the connection of a device to the controller.
class AclConnection {
public:
@@ -44,8 +50,15 @@ class AclConnection {
void Encrypt();
bool IsEncrypted() const;
- uint16_t GetLinkPolicySettings() const;
void SetLinkPolicySettings(uint16_t settings);
+ uint16_t GetLinkPolicySettings() const { return link_policy_settings_; }
+ bool IsRoleSwitchEnabled() const {
+ return (link_policy_settings_ & 0x1) != 0;
+ }
+ bool IsHoldModeEnabled() const { return (link_policy_settings_ & 0x2) != 0; }
+ bool IsSniffModeEnabled() const { return (link_policy_settings_ & 0x4) != 0; }
+
+ AclConnectionState GetMode() const { return state_; }
bluetooth::hci::Role GetRole() const;
void SetRole(bluetooth::hci::Role role);
@@ -81,6 +94,7 @@ class AclConnection {
// State variables
bool encrypted_{false};
uint16_t link_policy_settings_{0};
+ AclConnectionState state_{kActiveMode};
bluetooth::hci::Role role_{bluetooth::hci::Role::CENTRAL};
std::chrono::steady_clock::time_point last_packet_timestamp_;
std::chrono::steady_clock::duration timeout_;
diff --git a/tools/rootcanal/model/controller/controller_properties.cc b/tools/rootcanal/model/controller/controller_properties.cc
index 94f59a4b5f..8105fd9007 100644
--- a/tools/rootcanal/model/controller/controller_properties.cc
+++ b/tools/rootcanal/model/controller/controller_properties.cc
@@ -1963,7 +1963,6 @@ ControllerProperties::ControllerProperties(
config.quirks().hardware_error_before_reset();
}
// TODO(b/270606199): support send_acl_data_before_connection_complete
- // TODO(b/274476773): support send_role_change_before_connection_complete
}
if (!CheckSupportedFeatures()) {
diff --git a/tools/rootcanal/model/controller/link_layer_controller.cc b/tools/rootcanal/model/controller/link_layer_controller.cc
index 0f84a8a160..b77bbf2e70 100644
--- a/tools/rootcanal/model/controller/link_layer_controller.cc
+++ b/tools/rootcanal/model/controller/link_layer_controller.cc
@@ -5065,32 +5065,39 @@ void LinkLayerController::IncomingPageResponsePacket(
WARNING(id_, "No free handles");
return;
}
+
CancelScheduledTask(page_timeout_task_id_);
ASSERT(link_manager_add_link(
lm_.get(), reinterpret_cast<const uint8_t(*)[6]>(peer.data())));
CheckExpiringConnection(handle);
- auto addr = incoming.GetSourceAddress();
+ AclConnection& connection = connections_.GetAclConnection(handle);
+ auto bd_addr = incoming.GetSourceAddress();
auto response = model::packets::PageResponseView::Create(incoming);
ASSERT(response.IsValid());
- /* Role change event before connection complete is a quirk commonly exists in
- * Android-capatable Bluetooth controllers.
- * On the initiator side, only connection in peripheral role should be
- * accompanied with a role change event */
- // TODO(b/274476773): Add a config option for this quirk
- if (connections_.IsRoleSwitchAllowedForPendingConnection() &&
- response.GetTryRoleSwitch()) {
- auto role = bluetooth::hci::Role::PERIPHERAL;
- connections_.SetAclRole(handle, role);
- if (IsEventUnmasked(EventCode::ROLE_CHANGE)) {
- send_event_(bluetooth::hci::RoleChangeBuilder::Create(ErrorCode::SUCCESS,
- addr, role));
- }
+
+ bluetooth::hci::Role role =
+ connections_.IsRoleSwitchAllowedForPendingConnection() &&
+ response.GetTryRoleSwitch()
+ ? bluetooth::hci::Role::PERIPHERAL
+ : bluetooth::hci::Role::CENTRAL;
+
+ connection.SetLinkPolicySettings(default_link_policy_settings_);
+ connection.SetRole(role);
+
+ // Role change event before connection complete generates an HCI Role Change
+ // event on the initiator side if accepted; the event is sent before the
+ // HCI Connection Complete event.
+ if (role == bluetooth::hci::Role::PERIPHERAL &&
+ IsEventUnmasked(EventCode::ROLE_CHANGE)) {
+ send_event_(bluetooth::hci::RoleChangeBuilder::Create(ErrorCode::SUCCESS,
+ bd_addr, role));
}
+
if (IsEventUnmasked(EventCode::CONNECTION_COMPLETE)) {
send_event_(bluetooth::hci::ConnectionCompleteBuilder::Create(
- ErrorCode::SUCCESS, handle, addr, bluetooth::hci::LinkType::ACL,
+ ErrorCode::SUCCESS, handle, bd_addr, bluetooth::hci::LinkType::ACL,
bluetooth::hci::Enable::DISABLED));
}
}
@@ -5223,42 +5230,49 @@ ErrorCode LinkLayerController::AcceptConnectionRequest(const Address& bd_addr,
return ErrorCode::UNKNOWN_CONNECTION;
}
-void LinkLayerController::MakePeripheralConnection(const Address& addr,
+void LinkLayerController::MakePeripheralConnection(const Address& bd_addr,
bool try_role_switch) {
- INFO(id_, "Sending page response to {}", addr);
+ INFO(id_, "Sending page response to {}", bd_addr);
SendLinkLayerPacket(model::packets::PageResponseBuilder::Create(
- GetAddress(), addr, try_role_switch));
+ GetAddress(), bd_addr, try_role_switch));
- uint16_t handle = connections_.CreateConnection(addr, GetAddress());
- if (handle == kReservedHandle) {
+ uint16_t connection_handle =
+ connections_.CreateConnection(bd_addr, GetAddress());
+ if (connection_handle == kReservedHandle) {
INFO(id_, "CreateConnection failed");
return;
}
+
ASSERT(link_manager_add_link(
- lm_.get(), reinterpret_cast<const uint8_t(*)[6]>(addr.data())));
+ lm_.get(), reinterpret_cast<const uint8_t(*)[6]>(bd_addr.data())));
- CheckExpiringConnection(handle);
+ CheckExpiringConnection(connection_handle);
- /* Role change event before connection complete is a quirk commonly exists in
- * Android-capatable Bluetooth controllers.
- * On the responder side, any connection should be accompanied with a role
- * change event */
- // TODO(b/274476773): Add a config option for this quirk
- auto role =
+ bluetooth::hci::Role role =
try_role_switch && connections_.IsRoleSwitchAllowedForPendingConnection()
? bluetooth::hci::Role::CENTRAL
: bluetooth::hci::Role::PERIPHERAL;
- connections_.SetAclRole(handle, role);
- if (IsEventUnmasked(EventCode::ROLE_CHANGE)) {
+
+ AclConnection& connection = connections_.GetAclConnection(connection_handle);
+
+ connection.SetLinkPolicySettings(default_link_policy_settings_);
+ connection.SetRole(role);
+
+ // Role change event before connection complete generates an HCI Role Change
+ // event on the acceptor side if accepted; the event is sent before the
+ // HCI Connection Complete event.
+ if (role == bluetooth::hci::Role::CENTRAL &&
+ IsEventUnmasked(EventCode::ROLE_CHANGE)) {
+ INFO(id_, "Role at connection setup accepted");
send_event_(bluetooth::hci::RoleChangeBuilder::Create(ErrorCode::SUCCESS,
- addr, role));
+ bd_addr, role));
}
- INFO(id_, "CreateConnection returned handle 0x{:x}", handle);
+ INFO(id_, "CreateConnection returned handle 0x{:x}", connection_handle);
if (IsEventUnmasked(EventCode::CONNECTION_COMPLETE)) {
send_event_(bluetooth::hci::ConnectionCompleteBuilder::Create(
- ErrorCode::SUCCESS, handle, addr, bluetooth::hci::LinkType::ACL,
- bluetooth::hci::Enable::DISABLED));
+ ErrorCode::SUCCESS, connection_handle, bd_addr,
+ bluetooth::hci::LinkType::ACL, bluetooth::hci::Enable::DISABLED));
}
}
@@ -5488,67 +5502,124 @@ ErrorCode LinkLayerController::RoleDiscovery(uint16_t handle,
if (!connections_.HasHandle(handle)) {
return ErrorCode::UNKNOWN_CONNECTION;
}
+
*role = connections_.GetAclRole(handle);
return ErrorCode::SUCCESS;
}
-ErrorCode LinkLayerController::SwitchRole(Address addr,
+ErrorCode LinkLayerController::SwitchRole(Address bd_addr,
bluetooth::hci::Role role) {
- auto handle = connections_.GetHandleOnlyAddress(addr);
- if (handle == rootcanal::kReservedHandle) {
+ // The BD_ADDR command parameter indicates for which connection
+ // the role switch is to be performed and shall specify a BR/EDR Controller
+ // for which a connection already exists.
+ uint16_t connection_handle = connections_.GetHandleOnlyAddress(bd_addr);
+ if (connection_handle == kReservedHandle) {
+ INFO(id_, "unknown connection address {}", bd_addr);
return ErrorCode::UNKNOWN_CONNECTION;
}
- // TODO(b/274248798): Reject role switch if disabled in link policy
- SendLinkLayerPacket(model::packets::RoleSwitchRequestBuilder::Create(
- GetAddress(), addr, static_cast<uint8_t>(role)));
+
+ AclConnection& connection = connections_.GetAclConnection(connection_handle);
+
+ // If there is an (e)SCO connection between the local device and the device
+ // identified by the BD_ADDR parameter, an attempt to perform a role switch
+ // shall be rejected by the local device.
+ if (connections_.GetScoHandle(bd_addr) != kReservedHandle) {
+ INFO(id_,
+ "role switch rejected because an Sco link is opened with"
+ " the target device");
+ return ErrorCode::COMMAND_DISALLOWED;
+ }
+
+ // If the connection between the local device and the device identified by the
+ // BD_ADDR parameter is placed in Sniff mode, an attempt to perform a role
+ // switch shall be rejected by the local device.
+ if (connection.GetMode() == AclConnectionState::kSniffMode) {
+ INFO(id_,
+ "role switch rejected because the acl connection is in sniff mode");
+ return ErrorCode::COMMAND_DISALLOWED;
+ }
+
+ if (role != connection.GetRole()) {
+ SendLinkLayerPacket(model::packets::RoleSwitchRequestBuilder::Create(
+ GetAddress(), bd_addr));
+ } else if (IsEventUnmasked(EventCode::ROLE_CHANGE)) {
+ // Note: the status is Success only if the role change procedure was
+ // actually performed, otherwise the status is >0.
+ ScheduleTask(kNoDelayMs, [this, bd_addr, role]() {
+ send_event_(bluetooth::hci::RoleChangeBuilder::Create(
+ ErrorCode::ROLE_SWITCH_FAILED, bd_addr, role));
+ });
+ }
+
return ErrorCode::SUCCESS;
}
void LinkLayerController::IncomingRoleSwitchRequest(
model::packets::LinkLayerPacketView incoming) {
- auto addr = incoming.GetSourceAddress();
- auto handle = connections_.GetHandleOnlyAddress(addr);
- auto request = model::packets::RoleSwitchRequestView::Create(incoming);
- ASSERT(request.IsValid());
+ auto bd_addr = incoming.GetSourceAddress();
+ auto connection_handle = connections_.GetHandleOnlyAddress(bd_addr);
+ auto switch_req = model::packets::RoleSwitchRequestView::Create(incoming);
+ ASSERT(switch_req.IsValid());
- // TODO(b/274248798): Reject role switch if disabled in link policy
- Role remote_role = static_cast<Role>(request.GetInitiatorNewRole());
- Role local_role =
- remote_role == Role::CENTRAL ? Role::PERIPHERAL : Role::CENTRAL;
- connections_.SetAclRole(handle, local_role);
- if (IsEventUnmasked(EventCode::ROLE_CHANGE)) {
- ScheduleTask(kNoDelayMs, [this, addr, local_role]() {
- send_event_(bluetooth::hci::RoleChangeBuilder::Create(ErrorCode::SUCCESS,
- addr, local_role));
- });
+ if (connection_handle == kReservedHandle) {
+ INFO(id_, "ignoring Switch Request received on unknown connection");
+ return;
}
- ScheduleTask(kNoDelayMs, [this, addr, remote_role]() {
+
+ AclConnection& connection = connections_.GetAclConnection(connection_handle);
+
+ if (!connection.IsRoleSwitchEnabled()) {
+ INFO(id_, "role switch disabled by local link policy settings");
SendLinkLayerPacket(model::packets::RoleSwitchResponseBuilder::Create(
- GetAddress(), addr, static_cast<uint8_t>(ErrorCode::SUCCESS),
- static_cast<uint8_t>(remote_role)));
- });
+ GetAddress(), bd_addr,
+ static_cast<uint8_t>(ErrorCode::ROLE_CHANGE_NOT_ALLOWED)));
+ } else {
+ INFO(id_, "role switch request accepted by local device");
+ SendLinkLayerPacket(model::packets::RoleSwitchResponseBuilder::Create(
+ GetAddress(), bd_addr, static_cast<uint8_t>(ErrorCode::SUCCESS)));
+
+ bluetooth::hci::Role new_role =
+ connection.GetRole() == bluetooth::hci::Role::CENTRAL
+ ? bluetooth::hci::Role::PERIPHERAL
+ : bluetooth::hci::Role::CENTRAL;
+
+ connection.SetRole(new_role);
+
+ if (IsEventUnmasked(EventCode::ROLE_CHANGE)) {
+ ScheduleTask(kNoDelayMs, [this, bd_addr, new_role]() {
+ send_event_(bluetooth::hci::RoleChangeBuilder::Create(
+ ErrorCode::SUCCESS, bd_addr, new_role));
+ });
+ }
+ }
}
void LinkLayerController::IncomingRoleSwitchResponse(
model::packets::LinkLayerPacketView incoming) {
- auto addr = incoming.GetSourceAddress();
- auto handle = connections_.GetHandleOnlyAddress(addr);
- auto response = model::packets::RoleSwitchResponseView::Create(incoming);
- ASSERT(response.IsValid());
+ auto bd_addr = incoming.GetSourceAddress();
+ auto connection_handle = connections_.GetHandleOnlyAddress(bd_addr);
+ auto switch_rsp = model::packets::RoleSwitchResponseView::Create(incoming);
+ ASSERT(switch_rsp.IsValid());
- // TODO(b/274248798): Reject role switch if disabled in link policy
- ErrorCode status = ErrorCode::SUCCESS;
- Role role = static_cast<Role>(response.GetInitiatorNewRole());
- if (response.GetStatus() == static_cast<uint8_t>(ErrorCode::SUCCESS)) {
- connections_.SetAclRole(handle, role);
- } else {
- status = static_cast<ErrorCode>(response.GetStatus());
+ if (connection_handle == kReservedHandle) {
+ INFO(id_, "ignoring Switch Response received on unknown connection");
+ return;
}
+ AclConnection& connection = connections_.GetAclConnection(connection_handle);
+ ErrorCode status = ErrorCode(switch_rsp.GetStatus());
+ bluetooth::hci::Role new_role =
+ status != ErrorCode::SUCCESS ? connection.GetRole()
+ : connection.GetRole() == bluetooth::hci::Role::CENTRAL
+ ? bluetooth::hci::Role::PERIPHERAL
+ : bluetooth::hci::Role::CENTRAL;
+
+ connection.SetRole(new_role);
+
if (IsEventUnmasked(EventCode::ROLE_CHANGE)) {
- ScheduleTask(kNoDelayMs, [this, status, addr, role]() {
+ ScheduleTask(kNoDelayMs, [this, status, bd_addr, new_role]() {
send_event_(
- bluetooth::hci::RoleChangeBuilder::Create(status, addr, role));
+ bluetooth::hci::RoleChangeBuilder::Create(status, bd_addr, new_role));
});
}
}
@@ -5558,6 +5629,7 @@ ErrorCode LinkLayerController::ReadLinkPolicySettings(uint16_t handle,
if (!connections_.HasHandle(handle)) {
return ErrorCode::UNKNOWN_CONNECTION;
}
+
*settings = connections_.GetAclLinkPolicySettings(handle);
return ErrorCode::SUCCESS;
}
@@ -5579,6 +5651,7 @@ ErrorCode LinkLayerController::WriteDefaultLinkPolicySettings(
if (settings > 7 /* Sniff + Hold + Role switch */) {
return ErrorCode::INVALID_HCI_COMMAND_PARAMETERS;
}
+
default_link_policy_settings_ = settings;
return ErrorCode::SUCCESS;
}
diff --git a/tools/rootcanal/packets/link_layer_packets.pdl b/tools/rootcanal/packets/link_layer_packets.pdl
index 131ebc3c22..39b4ed2f5e 100644
--- a/tools/rootcanal/packets/link_layer_packets.pdl
+++ b/tools/rootcanal/packets/link_layer_packets.pdl
@@ -356,12 +356,10 @@ packet PingResponse : LinkLayerPacket (type = PING_RESPONSE) {
}
packet RoleSwitchRequest : LinkLayerPacket (type = ROLE_SWITCH_REQUEST) {
- initiator_new_role: 8,
}
packet RoleSwitchResponse : LinkLayerPacket (type = ROLE_SWITCH_RESPONSE) {
status: 8,
- initiator_new_role: 8,
}
packet LlPhyReq : LinkLayerPacket (type = LL_PHY_REQ) {
diff --git a/tools/rootcanal/py/bluetooth.py b/tools/rootcanal/py/bluetooth.py
index 0244333722..a8a18c53ae 100644
--- a/tools/rootcanal/py/bluetooth.py
+++ b/tools/rootcanal/py/bluetooth.py
@@ -51,16 +51,19 @@ class Address:
@dataclass
class ClassOfDevice:
+ class_of_device: int = 0
def parse(span: bytes) -> Tuple['Address', bytes]:
- assert False
+ assert len(span) >= 3
+ return (ClassOfDevice(int.from_bytes(span[:3], byteorder='little')), span[3:])
def parse_all(span: bytes) -> 'Address':
- assert False
+ assert len(span) == 3
+ return ClassOfDevice(int.from_bytes(span, byteorder='little'))
def serialize(self) -> bytes:
- assert False
+ return int.to_bytes(self.class_of_device, length=3, byteorder='little')
@property
def size(self) -> int:
- assert False
+ return 3
diff --git a/tools/rootcanal/test/LMP/LIH/BV_01_C.py b/tools/rootcanal/test/LMP/LIH/BV_01_C.py
new file mode 100644
index 0000000000..ca92e44e30
--- /dev/null
+++ b/tools/rootcanal/test/LMP/LIH/BV_01_C.py
@@ -0,0 +1,63 @@
+from dataclasses import dataclass
+import hci_packets as hci
+import link_layer_packets as ll
+import unittest
+from hci_packets import ErrorCode
+from py.bluetooth import Address
+from py.controller import ControllerTest
+
+
+class Test(ControllerTest):
+
+ # LMP/LIH/BV-01-C [Initiate Role Switch]
+ async def test(self):
+ # Test parameters.
+ controller = self.controller
+ acl_connection_handle = 0xefe
+ peer_address = Address('11:22:33:44:55:66')
+
+ controller.send_cmd(hci.WriteScanEnable(scan_enable=hci.ScanEnable.PAGE_SCAN_ONLY))
+
+ await self.expect_evt(hci.WriteScanEnableComplete(status=ErrorCode.SUCCESS, num_hci_command_packets=1))
+
+ controller.send_ll(
+ ll.Page(source_address=peer_address, destination_address=controller.address, allow_role_switch=False))
+
+ await self.expect_evt(hci.ConnectionRequest(bd_addr=peer_address, link_type=hci.ConnectionRequestLinkType.ACL))
+
+ controller.send_cmd(
+ hci.AcceptConnectionRequest(bd_addr=peer_address, role=hci.AcceptConnectionRequestRole.REMAIN_PERIPHERAL))
+
+ await self.expect_evt(hci.AcceptConnectionRequestStatus(status=ErrorCode.SUCCESS, num_hci_command_packets=1))
+
+ await self.expect_ll(
+ ll.PageResponse(source_address=controller.address, destination_address=peer_address, try_role_switch=False))
+
+ await self.expect_evt(
+ hci.ConnectionComplete(status=ErrorCode.SUCCESS,
+ connection_handle=acl_connection_handle,
+ bd_addr=peer_address,
+ link_type=hci.LinkType.ACL,
+ encryption_enabled=hci.Enable.DISABLED))
+
+ controller.send_cmd(
+ hci.WriteLinkPolicySettings(connection_handle=acl_connection_handle,
+ link_policy_settings=hci.LinkPolicy.ENABLE_ROLE_SWITCH))
+
+ await self.expect_evt(
+ hci.WriteLinkPolicySettingsComplete(status=ErrorCode.SUCCESS,
+ num_hci_command_packets=1,
+ connection_handle=acl_connection_handle))
+
+ controller.send_cmd(hci.SwitchRole(bd_addr=peer_address, role=hci.Role.CENTRAL))
+
+ await self.expect_evt(hci.SwitchRoleStatus(status=ErrorCode.SUCCESS, num_hci_command_packets=1))
+
+ await self.expect_ll(ll.RoleSwitchRequest(source_address=controller.address, destination_address=peer_address))
+
+ controller.send_ll(
+ ll.RoleSwitchResponse(source_address=peer_address,
+ destination_address=controller.address,
+ status=ErrorCode.SUCCESS))
+
+ await self.expect_evt(hci.RoleChange(status=ErrorCode.SUCCESS, bd_addr=peer_address, new_role=hci.Role.CENTRAL))
diff --git a/tools/rootcanal/test/LMP/LIH/BV_02_C.py b/tools/rootcanal/test/LMP/LIH/BV_02_C.py
new file mode 100644
index 0000000000..375acdc1f6
--- /dev/null
+++ b/tools/rootcanal/test/LMP/LIH/BV_02_C.py
@@ -0,0 +1,66 @@
+from dataclasses import dataclass
+import hci_packets as hci
+import link_layer_packets as ll
+import unittest
+from hci_packets import ErrorCode
+from py.bluetooth import Address
+from py.controller import ControllerTest
+
+
+class Test(ControllerTest):
+
+ # LMP/LIH/BV-02-C [Accept Role Switch]
+ async def test(self):
+ # Test parameters.
+ controller = self.controller
+ acl_connection_handle = 0xefe
+ peer_address = Address('11:22:33:44:55:66')
+
+ controller.send_cmd(
+ hci.CreateConnection(bd_addr=peer_address,
+ packet_type=0x7fff,
+ page_scan_repetition_mode=hci.PageScanRepetitionMode.R0,
+ allow_role_switch=hci.CreateConnectionRoleSwitch.REMAIN_CENTRAL))
+
+ await self.expect_evt(hci.CreateConnectionStatus(status=ErrorCode.SUCCESS, num_hci_command_packets=1))
+
+ await self.expect_ll(
+ ll.Page(source_address=controller.address, destination_address=peer_address, allow_role_switch=False))
+
+ controller.send_ll(
+ ll.PageResponse(source_address=peer_address, destination_address=controller.address, try_role_switch=False))
+
+ await self.expect_evt(
+ hci.ConnectionComplete(status=ErrorCode.SUCCESS,
+ connection_handle=acl_connection_handle,
+ bd_addr=peer_address,
+ link_type=hci.LinkType.ACL,
+ encryption_enabled=hci.Enable.DISABLED))
+
+ controller.send_cmd(
+ hci.WriteLinkPolicySettings(connection_handle=acl_connection_handle,
+ link_policy_settings=hci.LinkPolicy.ENABLE_ROLE_SWITCH))
+
+ await self.expect_evt(
+ hci.WriteLinkPolicySettingsComplete(status=ErrorCode.SUCCESS,
+ num_hci_command_packets=1,
+ connection_handle=acl_connection_handle))
+
+ controller.send_ll(ll.RoleSwitchRequest(source_address=peer_address, destination_address=controller.address))
+
+ await self.expect_ll(
+ ll.RoleSwitchResponse(source_address=controller.address,
+ destination_address=peer_address,
+ status=ErrorCode.SUCCESS))
+
+ await self.expect_evt(
+ hci.RoleChange(status=ErrorCode.SUCCESS, bd_addr=peer_address, new_role=hci.Role.PERIPHERAL))
+
+ controller.send_ll(ll.RoleSwitchRequest(source_address=peer_address, destination_address=controller.address))
+
+ await self.expect_ll(
+ ll.RoleSwitchResponse(source_address=controller.address,
+ destination_address=peer_address,
+ status=ErrorCode.SUCCESS))
+
+ await self.expect_evt(hci.RoleChange(status=ErrorCode.SUCCESS, bd_addr=peer_address, new_role=hci.Role.CENTRAL))
diff --git a/tools/rootcanal/test/LMP/LIH/BV_142_C.py b/tools/rootcanal/test/LMP/LIH/BV_142_C.py
new file mode 100644
index 0000000000..605f1d717d
--- /dev/null
+++ b/tools/rootcanal/test/LMP/LIH/BV_142_C.py
@@ -0,0 +1,53 @@
+from dataclasses import dataclass
+import hci_packets as hci
+import link_layer_packets as ll
+import unittest
+from hci_packets import ErrorCode
+from py.bluetooth import Address
+from py.controller import ControllerTest
+
+
+class Test(ControllerTest):
+
+ # LMP/LIH/BV-142-C [Reject Role Switch Request]
+ async def test(self):
+ # Test parameters.
+ controller = self.controller
+ acl_connection_handle = 0xefe
+ peer_address = Address('11:22:33:44:55:66')
+
+ controller.send_cmd(
+ hci.CreateConnection(bd_addr=peer_address,
+ packet_type=0x7fff,
+ page_scan_repetition_mode=hci.PageScanRepetitionMode.R0,
+ allow_role_switch=hci.CreateConnectionRoleSwitch.REMAIN_CENTRAL))
+
+ await self.expect_evt(hci.CreateConnectionStatus(status=ErrorCode.SUCCESS, num_hci_command_packets=1))
+
+ await self.expect_ll(
+ ll.Page(source_address=controller.address, destination_address=peer_address, allow_role_switch=False))
+
+ controller.send_ll(
+ ll.PageResponse(source_address=peer_address, destination_address=controller.address, try_role_switch=False))
+
+ await self.expect_evt(
+ hci.ConnectionComplete(status=ErrorCode.SUCCESS,
+ connection_handle=acl_connection_handle,
+ bd_addr=peer_address,
+ link_type=hci.LinkType.ACL,
+ encryption_enabled=hci.Enable.DISABLED))
+
+ controller.send_cmd(hci.WriteLinkPolicySettings(connection_handle=acl_connection_handle,
+ link_policy_settings=0))
+
+ await self.expect_evt(
+ hci.WriteLinkPolicySettingsComplete(status=ErrorCode.SUCCESS,
+ num_hci_command_packets=1,
+ connection_handle=acl_connection_handle))
+
+ controller.send_ll(ll.RoleSwitchRequest(source_address=peer_address, destination_address=controller.address))
+
+ await self.expect_ll(
+ ll.RoleSwitchResponse(source_address=controller.address,
+ destination_address=peer_address,
+ status=ErrorCode.ROLE_CHANGE_NOT_ALLOWED))
diff --git a/tools/rootcanal/test/LMP/LIH/BV_143_C.py b/tools/rootcanal/test/LMP/LIH/BV_143_C.py
new file mode 100644
index 0000000000..0e6b38af9c
--- /dev/null
+++ b/tools/rootcanal/test/LMP/LIH/BV_143_C.py
@@ -0,0 +1,72 @@
+from dataclasses import dataclass
+import hci_packets as hci
+import link_layer_packets as ll
+import unittest
+from hci_packets import ErrorCode
+from py.bluetooth import Address
+from py.controller import ControllerTest
+
+
+class Test(ControllerTest):
+
+ # LMP/LIH/BV-143-C [Rejected Role Switch Request]
+ async def test(self):
+ # Test parameters.
+ controller = self.controller
+ acl_connection_handle = 0xefe
+ peer_address = Address('11:22:33:44:55:66')
+
+ controller.send_cmd(hci.WriteScanEnable(scan_enable=hci.ScanEnable.PAGE_SCAN_ONLY))
+
+ await self.expect_evt(hci.WriteScanEnableComplete(status=ErrorCode.SUCCESS, num_hci_command_packets=1))
+
+ controller.send_ll(
+ ll.Page(source_address=peer_address, destination_address=controller.address, allow_role_switch=False))
+
+ await self.expect_evt(hci.ConnectionRequest(bd_addr=peer_address, link_type=hci.ConnectionRequestLinkType.ACL))
+
+ controller.send_cmd(
+ hci.AcceptConnectionRequest(bd_addr=peer_address, role=hci.AcceptConnectionRequestRole.REMAIN_PERIPHERAL))
+
+ await self.expect_evt(hci.AcceptConnectionRequestStatus(status=ErrorCode.SUCCESS, num_hci_command_packets=1))
+
+ await self.expect_ll(
+ ll.PageResponse(source_address=controller.address, destination_address=peer_address, try_role_switch=False))
+
+ await self.expect_evt(
+ hci.ConnectionComplete(status=ErrorCode.SUCCESS,
+ connection_handle=acl_connection_handle,
+ bd_addr=peer_address,
+ link_type=hci.LinkType.ACL,
+ encryption_enabled=hci.Enable.DISABLED))
+
+ controller.send_cmd(
+ hci.WriteLinkPolicySettings(connection_handle=acl_connection_handle,
+ link_policy_settings=hci.LinkPolicy.ENABLE_ROLE_SWITCH))
+
+ await self.expect_evt(
+ hci.WriteLinkPolicySettingsComplete(status=ErrorCode.SUCCESS,
+ num_hci_command_packets=1,
+ connection_handle=acl_connection_handle))
+
+ controller.send_cmd(hci.SwitchRole(bd_addr=peer_address, role=hci.Role.CENTRAL))
+
+ await self.expect_evt(hci.SwitchRoleStatus(status=ErrorCode.SUCCESS, num_hci_command_packets=1))
+
+ await self.expect_ll(ll.RoleSwitchRequest(source_address=controller.address, destination_address=peer_address))
+
+ controller.send_ll(
+ ll.RoleSwitchResponse(source_address=peer_address,
+ destination_address=controller.address,
+ status=ErrorCode.ROLE_CHANGE_NOT_ALLOWED))
+
+ await self.expect_evt(
+ hci.RoleChange(status=ErrorCode.ROLE_CHANGE_NOT_ALLOWED, bd_addr=peer_address,
+ new_role=hci.Role.PERIPHERAL))
+
+ controller.send_cmd(hci.SwitchRole(bd_addr=peer_address, role=hci.Role.PERIPHERAL))
+
+ await self.expect_evt(hci.SwitchRoleStatus(status=ErrorCode.SUCCESS, num_hci_command_packets=1))
+
+ await self.expect_evt(
+ hci.RoleChange(status=ErrorCode.ROLE_SWITCH_FAILED, bd_addr=peer_address, new_role=hci.Role.PERIPHERAL))
diff --git a/tools/rootcanal/test/LMP/LIH/BV_144_C.py b/tools/rootcanal/test/LMP/LIH/BV_144_C.py
new file mode 100644
index 0000000000..fedd386f98
--- /dev/null
+++ b/tools/rootcanal/test/LMP/LIH/BV_144_C.py
@@ -0,0 +1,41 @@
+from dataclasses import dataclass
+import hci_packets as hci
+import link_layer_packets as ll
+import unittest
+from hci_packets import ErrorCode
+from py.bluetooth import Address
+from py.controller import ControllerTest
+
+
+class Test(ControllerTest):
+
+ # LMP/LIH/BV-144-C [Rejected Role Switch request at Setup, Peripheral]
+ async def test(self):
+ # Test parameters.
+ controller = self.controller
+ acl_connection_handle = 0xefe
+ peer_address = Address('11:22:33:44:55:66')
+
+ controller.send_cmd(hci.WriteScanEnable(scan_enable=hci.ScanEnable.PAGE_SCAN_ONLY))
+
+ await self.expect_evt(hci.WriteScanEnableComplete(status=ErrorCode.SUCCESS, num_hci_command_packets=1))
+
+ controller.send_ll(
+ ll.Page(source_address=peer_address, destination_address=controller.address, allow_role_switch=False))
+
+ await self.expect_evt(hci.ConnectionRequest(bd_addr=peer_address, link_type=hci.ConnectionRequestLinkType.ACL))
+
+ controller.send_cmd(
+ hci.AcceptConnectionRequest(bd_addr=peer_address, role=hci.AcceptConnectionRequestRole.BECOME_CENTRAL))
+
+ await self.expect_evt(hci.AcceptConnectionRequestStatus(status=ErrorCode.SUCCESS, num_hci_command_packets=1))
+
+ await self.expect_ll(
+ ll.PageResponse(source_address=controller.address, destination_address=peer_address, try_role_switch=True))
+
+ await self.expect_evt(
+ hci.ConnectionComplete(status=ErrorCode.SUCCESS,
+ connection_handle=acl_connection_handle,
+ bd_addr=peer_address,
+ link_type=hci.LinkType.ACL,
+ encryption_enabled=hci.Enable.DISABLED))
diff --git a/tools/rootcanal/test/LMP/LIH/BV_149_C.py b/tools/rootcanal/test/LMP/LIH/BV_149_C.py
new file mode 100644
index 0000000000..a12552dfd6
--- /dev/null
+++ b/tools/rootcanal/test/LMP/LIH/BV_149_C.py
@@ -0,0 +1,68 @@
+from dataclasses import dataclass
+import hci_packets as hci
+import link_layer_packets as ll
+import unittest
+from hci_packets import ErrorCode
+from py.bluetooth import Address
+from py.controller import ControllerTest
+
+
+class Test(ControllerTest):
+
+ # LMP/LIH/BV-149-C [Reject Role Switch]
+ async def test(self):
+ # Test parameters.
+ controller = self.controller
+ acl_connection_handle = 0xefe
+ peer_address = Address('11:22:33:44:55:66')
+
+ controller.send_cmd(
+ hci.CreateConnection(bd_addr=peer_address,
+ packet_type=0x7fff,
+ page_scan_repetition_mode=hci.PageScanRepetitionMode.R0,
+ allow_role_switch=hci.CreateConnectionRoleSwitch.REMAIN_CENTRAL))
+
+ await self.expect_evt(hci.CreateConnectionStatus(status=ErrorCode.SUCCESS, num_hci_command_packets=1))
+
+ await self.expect_ll(
+ ll.Page(source_address=controller.address, destination_address=peer_address, allow_role_switch=False))
+
+ controller.send_ll(
+ ll.PageResponse(source_address=peer_address, destination_address=controller.address, try_role_switch=False))
+
+ await self.expect_evt(
+ hci.ConnectionComplete(status=ErrorCode.SUCCESS,
+ connection_handle=acl_connection_handle,
+ bd_addr=peer_address,
+ link_type=hci.LinkType.ACL,
+ encryption_enabled=hci.Enable.DISABLED))
+
+ controller.send_cmd(
+ hci.WriteLinkPolicySettings(connection_handle=acl_connection_handle,
+ link_policy_settings=hci.LinkPolicy.ENABLE_ROLE_SWITCH))
+
+ await self.expect_evt(
+ hci.WriteLinkPolicySettingsComplete(status=ErrorCode.SUCCESS,
+ num_hci_command_packets=1,
+ connection_handle=acl_connection_handle))
+
+ controller.send_cmd(hci.SwitchRole(bd_addr=peer_address, role=hci.Role.CENTRAL))
+
+ await self.expect_evt(hci.SwitchRoleStatus(status=ErrorCode.SUCCESS, num_hci_command_packets=1))
+
+ await self.expect_evt(
+ hci.RoleChange(status=ErrorCode.ROLE_SWITCH_FAILED, bd_addr=peer_address, new_role=hci.Role.CENTRAL))
+
+ controller.send_cmd(hci.SwitchRole(bd_addr=peer_address, role=hci.Role.PERIPHERAL))
+
+ await self.expect_evt(hci.SwitchRoleStatus(status=ErrorCode.SUCCESS, num_hci_command_packets=1))
+
+ await self.expect_ll(ll.RoleSwitchRequest(source_address=controller.address, destination_address=peer_address))
+
+ controller.send_ll(
+ ll.RoleSwitchResponse(source_address=peer_address,
+ destination_address=controller.address,
+ status=ErrorCode.ROLE_CHANGE_NOT_ALLOWED))
+
+ await self.expect_evt(
+ hci.RoleChange(status=ErrorCode.ROLE_CHANGE_NOT_ALLOWED, bd_addr=peer_address, new_role=hci.Role.CENTRAL))
diff --git a/tools/rootcanal/test/LMP/LIH/BV_78_C.py b/tools/rootcanal/test/LMP/LIH/BV_78_C.py
new file mode 100644
index 0000000000..42903472e1
--- /dev/null
+++ b/tools/rootcanal/test/LMP/LIH/BV_78_C.py
@@ -0,0 +1,41 @@
+from dataclasses import dataclass
+import hci_packets as hci
+import link_layer_packets as ll
+import unittest
+from hci_packets import ErrorCode
+from py.bluetooth import Address
+from py.controller import ControllerTest
+
+
+class Test(ControllerTest):
+
+ # LMP/LIH/BV-78-C [Role Switch at Setup, Central]
+ async def test(self):
+ # Test parameters.
+ controller = self.controller
+ acl_connection_handle = 0xefe
+ peer_address = Address('11:22:33:44:55:66')
+
+ controller.send_cmd(
+ hci.CreateConnection(bd_addr=peer_address,
+ packet_type=0x7fff,
+ page_scan_repetition_mode=hci.PageScanRepetitionMode.R1,
+ allow_role_switch=hci.CreateConnectionRoleSwitch.ALLOW_ROLE_SWITCH))
+
+ await self.expect_evt(hci.CreateConnectionStatus(status=ErrorCode.SUCCESS, num_hci_command_packets=1))
+
+ await self.expect_ll(
+ ll.Page(source_address=controller.address, destination_address=peer_address, allow_role_switch=True))
+
+ controller.send_ll(
+ ll.PageResponse(source_address=peer_address, destination_address=controller.address, try_role_switch=True))
+
+ await self.expect_evt(
+ hci.RoleChange(status=ErrorCode.SUCCESS, bd_addr=peer_address, new_role=hci.Role.PERIPHERAL))
+
+ await self.expect_evt(
+ hci.ConnectionComplete(status=ErrorCode.SUCCESS,
+ connection_handle=acl_connection_handle,
+ bd_addr=peer_address,
+ link_type=hci.LinkType.ACL,
+ encryption_enabled=hci.Enable.DISABLED))
diff --git a/tools/rootcanal/test/LMP/LIH/BV_79_C.py b/tools/rootcanal/test/LMP/LIH/BV_79_C.py
new file mode 100644
index 0000000000..85a3bb0249
--- /dev/null
+++ b/tools/rootcanal/test/LMP/LIH/BV_79_C.py
@@ -0,0 +1,43 @@
+from dataclasses import dataclass
+import hci_packets as hci
+import link_layer_packets as ll
+import unittest
+from hci_packets import ErrorCode
+from py.bluetooth import Address
+from py.controller import ControllerTest
+
+
+class Test(ControllerTest):
+
+ # LMP/LIH/BV-79-C [Role Switch at Setup, Peripheral]
+ async def test(self):
+ # Test parameters.
+ controller = self.controller
+ acl_connection_handle = 0xefe
+ peer_address = Address('11:22:33:44:55:66')
+
+ controller.send_cmd(hci.WriteScanEnable(scan_enable=hci.ScanEnable.PAGE_SCAN_ONLY))
+
+ await self.expect_evt(hci.WriteScanEnableComplete(status=ErrorCode.SUCCESS, num_hci_command_packets=1))
+
+ controller.send_ll(
+ ll.Page(source_address=peer_address, destination_address=controller.address, allow_role_switch=True))
+
+ await self.expect_evt(hci.ConnectionRequest(bd_addr=peer_address, link_type=hci.ConnectionRequestLinkType.ACL))
+
+ controller.send_cmd(
+ hci.AcceptConnectionRequest(bd_addr=peer_address, role=hci.AcceptConnectionRequestRole.BECOME_CENTRAL))
+
+ await self.expect_evt(hci.AcceptConnectionRequestStatus(status=ErrorCode.SUCCESS, num_hci_command_packets=1))
+
+ await self.expect_ll(
+ ll.PageResponse(source_address=controller.address, destination_address=peer_address, try_role_switch=True))
+
+ await self.expect_evt(hci.RoleChange(status=ErrorCode.SUCCESS, bd_addr=peer_address, new_role=hci.Role.CENTRAL))
+
+ await self.expect_evt(
+ hci.ConnectionComplete(status=ErrorCode.SUCCESS,
+ connection_handle=acl_connection_handle,
+ bd_addr=peer_address,
+ link_type=hci.LinkType.ACL,
+ encryption_enabled=hci.Enable.DISABLED))
diff --git a/tools/rootcanal/test/main.py b/tools/rootcanal/test/main.py
index 66b8999c27..bbc6383800 100644
--- a/tools/rootcanal/test/main.py
+++ b/tools/rootcanal/test/main.py
@@ -41,6 +41,14 @@ tests = [
'LL.DDI.SCN.BV_18_C',
'LL.DDI.SCN.BV_19_C',
'LL.DDI.SCN.BV_79_C',
+ 'LMP.LIH.BV_01_C',
+ 'LMP.LIH.BV_02_C',
+ 'LMP.LIH.BV_78_C',
+ 'LMP.LIH.BV_79_C',
+ 'LMP.LIH.BV_142_C',
+ 'LMP.LIH.BV_143_C',
+ 'LMP.LIH.BV_144_C',
+ 'LMP.LIH.BV_149_C',
]
if __name__ == "__main__":