// 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. #include "hci/bluetooth_facade.h" #include #include #include #include #include #include #include #include #include #include #include #include "hci/address.h" #include "hci/hci_packet_transport.h" #include "model/setup/async_manager.h" #include "model/setup/test_command_handler.h" #include "model/setup/test_model.h" #include "netsim-daemon/src/ffi.rs.h" #include "netsim/config.pb.h" #include "rust/cxx.h" #include "util/filesystem.h" #include "util/log.h" #ifndef NETSIM_ANDROID_EMULATOR #include "net/posix/posix_async_socket_server.h" #endif namespace rootcanal::log { void SetLogColorEnable(bool); } namespace netsim::hci::facade { int8_t SimComputeRssi(int send_id, int recv_id, int8_t tx_power); void IncrTx(uint32_t send_id, rootcanal::Phy::Type phy_type); void IncrRx(uint32_t receive_id, rootcanal::Phy::Type phy_type); using namespace std::literals; using namespace rootcanal; using rootcanal::PhyDevice; using rootcanal::PhyLayer; class SimPhyLayer : public PhyLayer { // for constructor inheritance using PhyLayer::PhyLayer; // Overrides ComputeRssi in PhyLayerFactory to provide // simulated RSSI information using actual spatial // device positions. int8_t ComputeRssi(PhyDevice::Identifier sender_id, PhyDevice::Identifier receiver_id, int8_t tx_power) override { return SimComputeRssi(sender_id, receiver_id, tx_power); } // Check if the device is present in the phy_devices static bool Contains( PhyDevice::Identifier device_id, const std::list> &phy_devices) { return std::any_of( phy_devices.begin(), phy_devices.end(), [device_id](const auto &device) { return device->id == device_id; }); } // Overrides Send in PhyLayerFactory to add Rx/Tx statistics. void Send(std::vector const &packet, int8_t tx_power, PhyDevice::Identifier sender_id) override { // Skip if the sender's phy is in the "down" state. Prevents all outgoing // messages including advertisements occurring when the radio is down. if (!Contains(sender_id, phy_devices_)) { return; } IncrTx(sender_id, type); for (const auto &device : phy_devices_) { if (sender_id != device->id) { IncrRx(device->id, type); device->Receive(packet, type, ComputeRssi(sender_id, device->id, tx_power)); } } } }; class SimTestModel : public rootcanal::TestModel { // for constructor inheritance using rootcanal::TestModel::TestModel; std::unique_ptr CreatePhyLayer( PhyLayer::Identifier id, rootcanal::Phy::Type type) override { return std::make_unique(id, type); } }; size_t phy_low_energy_index_; size_t phy_classic_index_; bool gStarted = false; std::shared_ptr gAsyncManager; rootcanal::AsyncUserId gSocketUserId{}; std::shared_ptr gTestModel; std::shared_ptr controller_proto_; #ifndef NETSIM_ANDROID_EMULATOR // test port std::unique_ptr gTestChannel; std::unique_ptr gTestChannelTransport; std::shared_ptr gTestSocketServer; bool gTestChannelOpen{false}; constexpr int kDefaultTestPort = 7500; #endif namespace { #ifndef NETSIM_ANDROID_EMULATOR using ::android::net::PosixAsyncSocketServer; void SetUpTestChannel(uint16_t instance_num) { gTestSocketServer = std::make_shared( kDefaultTestPort + instance_num - 1, gAsyncManager.get()); gTestChannel = std::make_unique(*gTestModel); gTestChannelTransport = std::make_unique(); gTestChannelTransport->RegisterCommandHandler( [](const std::string &name, const std::vector &args) { gAsyncManager->ExecAsync(gSocketUserId, std::chrono::milliseconds(0), [name, args]() { std::string args_str = ""; for (auto arg : args) args_str += " " + arg; if (name == "END_SIMULATION") { } else { gTestChannel->HandleCommand(name, args); } }); }); bool transport_configured = gTestChannelTransport->SetUp( gTestSocketServer, [](std::shared_ptr conn_fd, AsyncDataChannelServer *server) { BtsLogInfo("Test channel connection accepted."); server->StartListening(); if (gTestChannelOpen) { BtsLogWarn("Only one connection at a time is supported"); rootcanal::TestChannelTransport::SendResponse( conn_fd, "The connection is broken"); return false; } gTestChannelOpen = true; gTestChannel->RegisterSendResponse( [conn_fd](const std::string &response) { rootcanal::TestChannelTransport::SendResponse(conn_fd, response); }); conn_fd->WatchForNonBlockingRead([](AsyncDataChannel *conn_fd) { gTestChannelTransport->OnCommandReady( conn_fd, []() { gTestChannelOpen = false; }); }); return false; }); gTestChannel->SetTimerPeriod({"5"}); gTestChannel->StartTimer({}); if (!transport_configured) { BtsLogError("Failed to set up test channel."); return; } BtsLogInfo("Set up test channel."); } #endif } // namespace // Initialize the rootcanal library. void Start(const rust::Slice<::std::uint8_t const> proto_bytes, uint16_t instance_num) { if (gStarted) return; // output is to a file, so no color wanted rootcanal::log::SetLogColorEnable(false); config::Bluetooth config; config.ParseFromArray(proto_bytes.data(), proto_bytes.size()); controller_proto_ = std::make_shared( config.properties()); // When emulators restore from a snapshot the PacketStreamer connection to // netsim is recreated with a new (uninitialized) Rootcanal device. However // the Android Bluetooth Stack does not re-initialize the controller. Our // solution is for Rootcanal to recognize that it is receiving HCI commands // before a HCI Reset. The flag below causes a hardware error event that // triggers the Reset from the Bluetooth Stack. controller_proto_->mutable_quirks()->set_hardware_error_before_reset(true); gAsyncManager = std::make_shared(); // Get a user ID for tasks scheduled within the test environment. gSocketUserId = gAsyncManager->GetNextUserId(); gTestModel = std::make_unique( std::bind(&rootcanal::AsyncManager::GetNextUserId, gAsyncManager), std::bind(&rootcanal::AsyncManager::ExecAsync, gAsyncManager, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), std::bind(&rootcanal::AsyncManager::ExecAsyncPeriodically, gAsyncManager, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4), std::bind(&rootcanal::AsyncManager::CancelAsyncTasksFromUser, gAsyncManager, std::placeholders::_1), std::bind(&rootcanal::AsyncManager::CancelAsyncTask, gAsyncManager, std::placeholders::_1), [](const std::string & /* server */, int /* port */, rootcanal::Phy::Type /* phy_type */) { return nullptr; }); // Disable Address Reuse if '--disable_address_reuse' flag is true // Enable Address Reuse if 'address_reuse' is true if (config.has_disable_address_reuse()) { gTestModel->SetReuseDeviceAddresses(!config.disable_address_reuse()); } // NOTE: 0:BR_EDR, 1:LOW_ENERGY. The order is used by bluetooth CTS. phy_classic_index_ = gTestModel->AddPhy(rootcanal::Phy::Type::BR_EDR); phy_low_energy_index_ = gTestModel->AddPhy(rootcanal::Phy::Type::LOW_ENERGY); // TODO: Remove test channel. #ifdef NETSIM_ANDROID_EMULATOR auto testCommands = rootcanal::TestCommandHandler(*gTestModel); testCommands.RegisterSendResponse([](const std::string &) {}); testCommands.SetTimerPeriod({"5"}); testCommands.StartTimer({}); #else SetUpTestChannel(instance_num); #endif gStarted = true; }; // Resets the root canal library. void Stop() { // TODO: Fix TestModel::Reset() in test_model.cc. // gTestModel->Reset(); gStarted = false; } void AddDeviceToPhy(uint32_t rootcanal_id, bool isLowEnergy) { auto phy_index = (isLowEnergy) ? phy_low_energy_index_ : phy_classic_index_; gTestModel->AddDeviceToPhy(rootcanal_id, phy_index); } void RemoveDeviceFromPhy(uint32_t rootcanal_id, bool isLowEnergy) { auto phy_index = (isLowEnergy) ? phy_low_energy_index_ : phy_classic_index_; gTestModel->RemoveDeviceFromPhy(rootcanal_id, phy_index); } class ChipInfo { public: uint32_t chip_id; std::shared_ptr model; int le_tx_count = 0; int classic_tx_count = 0; int le_rx_count = 0; int classic_rx_count = 0; std::shared_ptr controller_proto; std::unique_ptr controller_properties; ChipInfo(uint32_t chip_id, std::shared_ptr model) : chip_id(chip_id), model(model) {} ChipInfo( uint32_t chip_id, std::shared_ptr model, std::shared_ptr controller_proto, std::unique_ptr controller_properties) : chip_id(chip_id), model(model), controller_proto(std::move(controller_proto)), controller_properties(std::move(controller_properties)) {} }; std::unordered_map> id_to_chip_info_; model::Chip::Bluetooth Get(uint32_t id) { model::Chip::Bluetooth model; if (id_to_chip_info_.find(id) != id_to_chip_info_.end()) { model.CopyFrom(*id_to_chip_info_[id]->model.get()); auto chip_info = id_to_chip_info_[id]; model.mutable_classic()->set_tx_count(chip_info->classic_tx_count); model.mutable_classic()->set_rx_count(chip_info->classic_rx_count); model.mutable_low_energy()->set_tx_count(chip_info->le_tx_count); model.mutable_low_energy()->set_rx_count(chip_info->le_rx_count); if (chip_info->controller_proto) { model.mutable_bt_properties()->CopyFrom(*chip_info->controller_proto); } } return model; } void Reset(uint32_t id) { if (auto it = id_to_chip_info_.find(id); it != id_to_chip_info_.end()) { auto chip_info = it->second; chip_info->le_tx_count = 0; chip_info->le_rx_count = 0; chip_info->classic_tx_count = 0; chip_info->classic_rx_count = 0; } // First remove LOW_ENERGY and BR_EDR Phy RemoveDeviceFromPhy(id, true); RemoveDeviceFromPhy(id, false); // Add to LOW_ENERGY and BR_EDR Phy AddDeviceToPhy(id, true); AddDeviceToPhy(id, false); } void Remove(uint32_t id) { BtsLogInfo("Removing HCI chip rootcanal_id: %d.", id); id_to_chip_info_.erase(id); // Call the transport close callback. This invokes HciDevice::Close and // TestModel close callback. gAsyncManager->ExecAsync(gSocketUserId, std::chrono::milliseconds(0), [id]() { // rootcanal will call HciPacketTransport::Close(). HciPacketTransport::Remove(id); }); } // Rename AddChip(model::Chip, device, transport) uint32_t Add(uint32_t chip_id, const std::string &address_string, const rust::Slice<::std::uint8_t const> controller_proto_bytes) { auto transport = std::make_shared(chip_id, gAsyncManager); std::shared_ptr controller_proto = controller_proto_; // If the Bluetooth Controller protobuf is provided, we use the provided if (controller_proto_bytes.size() != 0) { rootcanal::configuration::Controller custom_proto; custom_proto.ParseFromArray(controller_proto_bytes.data(), controller_proto_bytes.size()); BtsLogInfo("chip_id: %d has rootcanal Controller configuration: %s", chip_id, custom_proto.ShortDebugString().c_str()); // When emulators restore from a snapshot the PacketStreamer connection to // netsim is recreated with a new (uninitialized) Rootcanal device. However // the Android Bluetooth Stack does not re-initialize the controller. Our // solution is for Rootcanal to recognize that it is receiving HCI commands // before a HCI Reset. The flag below causes a hardware error event that // triggers the Reset from the Bluetooth Stack. custom_proto.mutable_quirks()->set_hardware_error_before_reset(true); controller_proto = std::make_shared(custom_proto); } std::unique_ptr controller_properties = std::make_unique(*controller_proto); auto hci_device = std::make_shared(transport, *controller_properties); // Pass netsim::hci::facade::ReportInvalidPacket signature into hci_device hci_device->RegisterInvalidPacketHandler( [](uint32_t rootcanal_id, rootcanal::InvalidPacketReason reason, std::string description, const std::vector &packet) { netsim::hci::facade::ReportInvalidPacket( rootcanal_id, static_cast(reason), description, packet); }); // Use the `AsyncManager` to ensure that the `AddHciConnection` method is // invoked atomically, preventing data races. std::promise rootcanal_id_promise; auto rootcanal_id_future = rootcanal_id_promise.get_future(); std::optional
address_option; if (address_string != "") { address_option = rootcanal::Address::FromString(address_string); } gAsyncManager->ExecAsync( gSocketUserId, std::chrono::milliseconds(0), [hci_device, &rootcanal_id_promise, address_option]() { rootcanal_id_promise.set_value( gTestModel->AddHciConnection(hci_device, address_option)); }); auto rootcanal_id = rootcanal_id_future.get(); HciPacketTransport::Add(rootcanal_id, transport); BtsLogInfo("Creating HCI rootcanal_id: %d for chip_id: %d", rootcanal_id, chip_id); auto model = std::make_shared(); model->mutable_classic()->set_state(true); model->mutable_low_energy()->set_state(true); id_to_chip_info_.emplace(rootcanal_id, std::make_shared( chip_id, model, controller_proto, std::move(controller_properties))); return rootcanal_id; } void RemoveRustDevice(uint32_t rootcanal_id) { gTestModel->RemoveDevice(rootcanal_id); } rust::Box AddRustDevice( uint32_t chip_id, rust::Box callbacks, const std::string &type, const std::string &address) { auto rust_device = std::make_shared(std::move(callbacks), type, address); // TODO: Use the `AsyncManager` to ensure that the `AddDevice` and // `AddDeviceToPhy` methods are invoked atomically, preventing data races. // For unknown reason, use `AsyncManager` hangs. auto rootcanal_id = gTestModel->AddDevice(rust_device); gTestModel->AddDeviceToPhy(rootcanal_id, phy_low_energy_index_); auto model = std::make_shared(); // Only enable ble for beacon. model->mutable_low_energy()->set_state(true); id_to_chip_info_.emplace(rootcanal_id, std::make_shared(chip_id, model)); return CreateAddRustDeviceResult( rootcanal_id, std::make_unique(rust_device)); } void SetRustDeviceAddress( uint32_t rootcanal_id, std::array address) { uint8_t addr[rootcanal::Address::kLength]; std::memcpy(addr, address.data(), rootcanal::Address::kLength); gTestModel->SetDeviceAddress(rootcanal_id, rootcanal::Address(addr)); } void IncrTx(uint32_t id, rootcanal::Phy::Type phy_type) { if (auto it = id_to_chip_info_.find(id); it != id_to_chip_info_.end()) { auto chip_info = it->second; if (phy_type == rootcanal::Phy::Type::LOW_ENERGY) { chip_info->le_tx_count++; } else { chip_info->classic_tx_count++; } } } void IncrRx(uint32_t id, rootcanal::Phy::Type phy_type) { if (auto it = id_to_chip_info_.find(id); it != id_to_chip_info_.end()) { auto chip_info = it->second; if (phy_type == rootcanal::Phy::Type::LOW_ENERGY) { chip_info->le_rx_count++; } else { chip_info->classic_rx_count++; } } } // TODO: Make SimComputeRssi invoke netsim::device::GetDistanceRust with dev // flag int8_t SimComputeRssi(int send_id, int recv_id, int8_t tx_power) { if (id_to_chip_info_.find(send_id) == id_to_chip_info_.end() || id_to_chip_info_.find(recv_id) == id_to_chip_info_.end()) { #ifdef NETSIM_ANDROID_EMULATOR // NOTE: Ignore log messages in Cuttlefish for beacon devices created by // test channel. BtsLogWarn("Missing chip_info"); #endif return tx_power; } auto a = id_to_chip_info_[send_id]->chip_id; auto b = id_to_chip_info_[recv_id]->chip_id; auto distance = netsim::device::GetDistanceCxx(a, b); return netsim::DistanceToRssi(tx_power, distance); } rust::Vec<::std::uint8_t> GetCxx(uint32_t id) { auto bluetooth = Get(id); std::vector proto_bytes(bluetooth.ByteSizeLong()); bluetooth.SerializeToArray(proto_bytes.data(), proto_bytes.size()); rust::Vec proto_rust_bytes; std::copy(proto_bytes.begin(), proto_bytes.end(), std::back_inserter(proto_rust_bytes)); return proto_rust_bytes; } } // namespace netsim::hci::facade