aboutsummaryrefslogtreecommitdiff
path: root/src/hci/bluetooth_facade.cc
blob: 2838afeb9dd5e2e71d9b73f48a6d9fbe31febc01 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
// 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 <sys/types.h>

#include <array>
#include <cassert>
#include <chrono>
#include <cstdint>
#include <cstring>
#include <future>
#include <iostream>
#include <memory>
#include <unordered_map>
#include <utility>

#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);
}

using netsim::model::State;

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<std::shared_ptr<rootcanal::PhyDevice>> &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<uint8_t> 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<rootcanal::PhyLayer> CreatePhyLayer(
      PhyLayer::Identifier id, rootcanal::Phy::Type type) override {
    return std::make_unique<SimPhyLayer>(id, type);
  }
};

size_t phy_low_energy_index_;
size_t phy_classic_index_;

bool gStarted = false;
std::shared_ptr<rootcanal::AsyncManager> gAsyncManager;
rootcanal::AsyncUserId gSocketUserId{};
std::shared_ptr<SimTestModel> gTestModel;
std::shared_ptr<rootcanal::configuration::Controller> controller_proto_;

#ifndef NETSIM_ANDROID_EMULATOR
// test port
std::unique_ptr<rootcanal::TestCommandHandler> gTestChannel;
std::unique_ptr<rootcanal::TestChannelTransport> gTestChannelTransport;
std::shared_ptr<AsyncDataChannelServer> 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<PosixAsyncSocketServer>(
      kDefaultTestPort + instance_num - 1, gAsyncManager.get());

  gTestChannel = std::make_unique<rootcanal::TestCommandHandler>(*gTestModel);

  gTestChannelTransport = std::make_unique<rootcanal::TestChannelTransport>();
  gTestChannelTransport->RegisterCommandHandler(
      [](const std::string &name, const std::vector<std::string> &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<AsyncDataChannel> 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);

  // TODO(b/323226412): Pass netsim::hci::facade::ReportInvalidPacket signature
  // into rootcanal

  config::Bluetooth config;
  config.ParseFromArray(proto_bytes.data(), proto_bytes.size());
  controller_proto_ = std::make_shared<rootcanal::configuration::Controller>(
      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<rootcanal::AsyncManager>();
  // Get a user ID for tasks scheduled within the test environment.
  gSocketUserId = gAsyncManager->GetNextUserId();

  gTestModel = std::make_unique<SimTestModel>(
      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::Chip::Bluetooth> model;
  int le_tx_count = 0;
  int classic_tx_count = 0;
  int le_rx_count = 0;
  int classic_rx_count = 0;
  std::shared_ptr<rootcanal::configuration::Controller> controller_proto;
  std::unique_ptr<rootcanal::ControllerProperties> controller_properties;

  ChipInfo(uint32_t chip_id, std::shared_ptr<model::Chip::Bluetooth> model)
      : chip_id(chip_id), model(model) {}
  ChipInfo(
      uint32_t chip_id, std::shared_ptr<model::Chip::Bluetooth> model,
      std::shared_ptr<rootcanal::configuration::Controller> controller_proto,
      std::unique_ptr<rootcanal::ControllerProperties> controller_properties)
      : chip_id(chip_id),
        model(model),
        controller_proto(std::move(controller_proto)),
        controller_properties(std::move(controller_properties)) {}
};

std::unordered_map<uint32_t, std::shared_ptr<ChipInfo>> 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<HciPacketTransport>(chip_id, gAsyncManager);

  std::shared_ptr<rootcanal::configuration::Controller> 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<rootcanal::configuration::Controller>(custom_proto);
  }
  std::unique_ptr<rootcanal::ControllerProperties> controller_properties =
      std::make_unique<rootcanal::ControllerProperties>(*controller_proto);

  auto hci_device =
      std::make_shared<rootcanal::HciDevice>(transport, *controller_properties);

  // Use the `AsyncManager` to ensure that the `AddHciConnection` method is
  // invoked atomically, preventing data races.
  std::promise<uint32_t> rootcanal_id_promise;
  auto rootcanal_id_future = rootcanal_id_promise.get_future();

  std::optional<Address> 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::Chip::Bluetooth>();
  model->mutable_classic()->set_state(model::State::ON);
  model->mutable_low_energy()->set_state(model::State::ON);

  id_to_chip_info_.emplace(rootcanal_id, std::make_shared<ChipInfo>(
                                             chip_id, model, controller_proto,
                                             std::move(controller_properties)));
  return rootcanal_id;
}

void RemoveRustDevice(uint32_t rootcanal_id) {
  gTestModel->RemoveDevice(rootcanal_id);
}

rust::Box<AddRustDeviceResult> AddRustDevice(
    uint32_t chip_id, rust::Box<DynRustBluetoothChipCallbacks> callbacks,
    const std::string &type, const std::string &address) {
  auto rust_device =
      std::make_shared<RustDevice>(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<model::Chip::Bluetooth>();
  // Only enable ble for beacon.
  model->mutable_low_energy()->set_state(model::State::ON);
  id_to_chip_info_.emplace(rootcanal_id,
                           std::make_shared<ChipInfo>(chip_id, model));
  return CreateAddRustDeviceResult(
      rootcanal_id, std::make_unique<RustBluetoothChip>(rust_device));
}

void SetRustDeviceAddress(
    uint32_t rootcanal_id,
    std::array<uint8_t, rootcanal::Address::kLength> 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<uint8_t> proto_bytes(bluetooth.ByteSizeLong());
  bluetooth.SerializeToArray(proto_bytes.data(), proto_bytes.size());
  rust::Vec<uint8_t> 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