Add ScanManager class for bookkeeping BLE v2 scans

PiperOrigin-RevId: 485675999
This commit is contained in:
Anay Wadhera
2022-11-02 13:15:31 -07:00
committed by Copybara-Service
parent 4935a2ffdb
commit c4dc132f50
8 changed files with 386 additions and 8 deletions
+15 -4
View File
@@ -24,13 +24,24 @@ namespace nearby {
namespace presence {
// Holds the callback of stop scan for client to invoke later.
struct ScanSession {
// TODO(b/254895067) Rework status for absl::Status
class ScanSession {
public:
ScanSession()
: stop_scan_callback_(
[]() { return Status{Status::Value::kNotImplemented}; }) {}
explicit ScanSession(std::function<Status(void)> stop_scan_callback)
: stop_scan_callback_(stop_scan_callback) {}
Status StopScan() {
return stop_scan_callback_();
}
private:
// Nearby library would provide the implementation of this callback in
// runtime. Assigning with a default value NotImplemented to surface potential
// issue where library failed to provide the implementation.
std::function<Status(void)> stop_scan_callback = []() {
return Status{Status::Value::kNotImplemented};
};
std::function<Status(void)> stop_scan_callback_;
};
// Callers would provide the implementation of these callbacks. If callers
+22
View File
@@ -24,6 +24,7 @@ cc_library(
"encryption.cc",
"ldt.cc",
"np_ldt.c",
"scan_manager.cc",
"service_controller_impl.cc",
],
hdrs = [
@@ -58,10 +59,14 @@ cc_library(
"//presence/implementation/mediums",
"//third_party/tink/cc/subtle",
"@boringssl//:crypto",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/random",
"@com_google_absl//absl/random:distributions",
"@com_google_absl//absl/status",
"@com_google_absl//absl/status:statusor",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/strings:str_format",
"@com_google_absl//absl/synchronization",
"@com_google_absl//absl/time",
"@com_google_absl//absl/types:optional",
"@com_google_absl//absl/types:variant",
@@ -184,3 +189,20 @@ cc_test(
"@com_google_googletest//:gtest_main",
],
)
cc_test(
name = "scan_manager_test",
size = "small",
srcs = ["scan_manager_test.cc"],
deps = [
":internal",
"//internal/platform:comm",
"//internal/platform:test_util",
"//internal/platform:types",
"//internal/platform/implementation/g3", # build_cleaner: keep
"//presence/implementation/mediums",
"@com_github_protobuf_matchers//protobuf-matchers",
"@com_google_absl//absl/random",
"@com_google_googletest//:gtest_main",
],
)
@@ -24,6 +24,7 @@
#include "internal/proto/credential.pb.h"
#include "presence/data_element.h"
#include "presence/implementation/base_broadcast_request.h"
#include "presence/implementation/mediums/ble.h"
namespace nearby {
namespace presence {
@@ -31,9 +32,7 @@ namespace presence {
using ::nearby::internal::IdentityType;
namespace {
constexpr uint8_t kBaseVersion = 0;
constexpr location::nearby::Uuid kServiceData(0xFCF1ULL << 32, 0);
constexpr size_t kMaxBaseNpAdvSize = 26;
absl::StatusOr<uint8_t> CreateDataElementHeader(size_t length,
@@ -175,7 +174,7 @@ AdvertisementFactory::CreateBaseNpAdvertisement(
}
}
advert.service_data.insert(
{kServiceData, location::nearby::ByteArray(payload)});
{kPresenceServiceUuid, location::nearby::ByteArray(payload)});
return advert;
}
+1 -1
View File
@@ -28,7 +28,7 @@ namespace presence {
class Mediums {
public:
// Returns a handle to the Ble medium.
Ble& GetBle();
Ble& GetBle() { return ble_; }
private:
location::nearby::BluetoothAdapter adapter_;
+117
View File
@@ -0,0 +1,117 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "presence/implementation/scan_manager.h"
#include <algorithm>
#include <functional>
#include <memory>
#include <string>
#include <utility>
#include "absl/random/random.h"
#include "absl/random/uniform_int_distribution.h"
#include "absl/types/variant.h"
#include "internal/platform/implementation/ble_v2.h"
#include "internal/platform/uuid.h"
#include "presence/data_types.h"
#include "presence/implementation/advertisement_decoder.h"
#include "presence/implementation/mediums/ble.h"
#include "presence/presence_device.h"
#include "presence/scan_request.h"
#include "presence/status.h"
namespace {
using BleAdvertisementData =
::location::nearby::api::ble_v2::BleAdvertisementData;
using BlePeripheral = ::location::nearby::api::ble_v2::BlePeripheral;
using BleOperationStatus = ::location::nearby::api::ble_v2::BleOperationStatus;
using ScanningSession =
::location::nearby::api::ble_v2::BleMedium::ScanningSession;
using ScanningCallback =
::location::nearby::api::ble_v2::BleMedium::ScanningCallback;
} // namespace
namespace nearby {
namespace presence {
ScanSession ScanManager::StartScan(ScanRequest scan_request, ScanCallback cb) {
absl::BitGen gen;
uint64_t id = absl::uniform_int_distribution<uint64_t>(0, UINT64_MAX)(gen);
ScanningCallback callback = ScanningCallback{
.start_scanning_result =
[start_scan_client =
std::move(cb.start_scan_cb)](BleOperationStatus ble_status) {
Status status;
if (ble_status == BleOperationStatus::kSucceeded) {
status = Status{.value = Status::Value::kSuccess};
} else {
status = Status{.value = Status::Value::kError};
}
start_scan_client(status);
},
// TODO(b/256686710): Track known devices
.advertisement_found_cb =
[this](BlePeripheral& peripheral, BleAdvertisementData data) {
NotifyFoundBle(data, peripheral);
}};
std::unique_ptr<ScanningSession> scanning_session =
mediums_->GetBle().StartScanning(scan_request, std::move(callback));
auto modified_scanning_session = ScanSession(
[scanning_session_cb = std::move(scanning_session->stop_scanning), this,
id]() {
absl::MutexLock lock(&mutex_);
int erased = absl::erase_if(
scanning_callbacks_,
[id](const auto& entry) { return id == entry.first; });
if (erased == 0) return Status{.value = Status::Value::kError};
BleOperationStatus st = scanning_session_cb();
if (st != BleOperationStatus::kSucceeded) {
return Status{.value = Status::Value::kError};
}
return Status{.value = Status::Value::kSuccess};
});
absl::MutexLock lock(&mutex_);
// We will not be needing the start_scan_cb anymore, so cb is ok to use here.
scanning_callbacks_.emplace(id, MapElement{
.request = scan_request,
.callback = cb,
.decoder = AdvertisementDecoder(
credential_manager_, scan_request),
});
return modified_scanning_session;
}
void ScanManager::NotifyFoundBle(BleAdvertisementData data,
const BlePeripheral& peripheral) {
absl::MutexLock lock(&mutex_);
auto advertisement_data =
data.service_data[kPresenceServiceUuid].AsStringView();
for (const auto& entry : scanning_callbacks_) {
auto candidate = entry.second;
auto advert = candidate.decoder.DecodeAdvertisement(advertisement_data);
if (!advert.ok()) {
// This advertisement is not relevant to the current element, skip.
continue;
}
if (candidate.decoder.MatchesScanFilter(advert.value())) {
// TODO(b/256913915): Provide more information in PresenceDevice once
// fully implemented
candidate.callback.on_discovered_cb(PresenceDevice());
}
}
}
} // namespace presence
} // namespace nearby
+30
View File
@@ -15,8 +15,18 @@
#ifndef THIRD_PARTY_NEARBY_PRESENCE_IMPLEMENTATION_SCAN_MANAGER_H_
#define THIRD_PARTY_NEARBY_PRESENCE_IMPLEMENTATION_SCAN_MANAGER_H_
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include "absl/container/flat_hash_map.h"
#include "absl/synchronization/mutex.h"
#include "presence/data_types.h"
#include "presence/implementation/advertisement_decoder.h"
#include "presence/implementation/credential_manager.h"
#include "presence/implementation/mediums/mediums.h"
#include "presence/scan_request.h"
namespace nearby {
namespace presence {
@@ -32,9 +42,29 @@ class ScanManager {
}
~ScanManager() = default;
ScanSession StartScan(ScanRequest scan_request, ScanCallback cb)
ABSL_LOCKS_EXCLUDED(mutex_);
// Below functions are test only.
// Reference: go/totw/135#augmenting-the-public-api-for-tests
int ScanningCallbacksLengthForTest() ABSL_LOCKS_EXCLUDED(mutex_) {
absl::MutexLock lock(&mutex_);
return scanning_callbacks_.size();
}
private:
struct MapElement {
ScanRequest request;
ScanCallback callback;
AdvertisementDecoder decoder;
};
mutable absl::Mutex mutex_;
Mediums* mediums_;
CredentialManager* credential_manager_;
absl::flat_hash_map<uint64_t, MapElement> scanning_callbacks_
ABSL_GUARDED_BY(mutex_);
void NotifyFoundBle(
location::nearby::api::ble_v2::BleAdvertisementData data,
const location::nearby::api::ble_v2::BlePeripheral& peripheral);
};
} // namespace presence
@@ -0,0 +1,196 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "presence/implementation/scan_manager.h"
#include <math.h>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "gtest/gtest.h"
#include "absl/random/random.h"
#include "internal/platform/bluetooth_adapter.h"
#include "internal/platform/count_down_latch.h"
#include "internal/platform/medium_environment.h"
#include "presence/implementation/advertisement_factory.h"
#include "presence/implementation/base_broadcast_request.h"
#include "presence/implementation/credential_manager_impl.h"
#include "presence/implementation/mediums/ble.h"
#include "presence/implementation/mediums/mediums.h"
namespace nearby {
namespace presence {
namespace {
using BleOperationStatus = location::nearby::api::ble_v2::BleOperationStatus;
class ScanManagerTest : public testing::Test {
protected:
void SetUp() override { env_.Start(); }
void TearDown() override { env_.Stop(); }
std::vector<nearby::internal::IdentityType> identity_types_ = {
nearby::internal::IdentityType::IDENTITY_TYPE_PUBLIC,
};
std::vector<DataElement> extended_properties_ = {
DataElement(ActionBit::kPresenceManagerAction)};
std::vector<absl::variant<PresenceScanFilter, LegacyPresenceScanFilter>>
filters_ = {PresenceScanFilter{
.scan_type = ScanType::kPresenceScan,
.extended_properties = extended_properties_,
}};
ScanRequest scan_request_ = {
.account_name = "Test account",
.identity_types = identity_types_,
.scan_filters = filters_,
.use_ble = true,
.scan_type = ScanType::kPresenceScan,
.power_mode = PowerMode::kBalanced,
.scan_only_when_screen_on = true,
};
CredentialManagerImpl credential_manager_;
location::nearby::MediumEnvironment& env_ = {
location::nearby::MediumEnvironment::Instance()};
location::nearby::api::ble_v2::AdvertiseParameters params_ = {
.tx_power_level = Ble::TxPowerLevel::kHigh,
.is_connectable = true,
};
location::nearby::CountDownLatch start_latch_{1};
location::nearby::CountDownLatch found_latch_{1};
};
TEST_F(ScanManagerTest, CanStartThenStopScanning) {
Mediums mediums;
ScanManager manager(mediums, credential_manager_);
ScanCallback scanning_callback = {
.start_scan_cb =
[this](Status status) {
if (status.Ok()) {
start_latch_.CountDown();
}
},
.on_discovered_cb =
[this](PresenceDevice pd) { found_latch_.CountDown(); }};
// Set up advertiser
// Create BroadcastRequest
PresenceBroadcast::BroadcastSection section = {
.identity = internal::IDENTITY_TYPE_PUBLIC,
.extended_properties = extended_properties_,
.account_name = "Test account"};
PresenceBroadcast presence_request = {.sections = {section}};
BroadcastRequest input = {.tx_power = 30, .variant = presence_request};
absl::StatusOr<BaseBroadcastRequest> request =
BaseBroadcastRequest::Create(input);
AdvertisementFactory factory(&credential_manager_);
absl::StatusOr<BleAdvertisementData> advertisement =
factory.CreateAdvertisement(request.ValueOrDie());
location::nearby::BluetoothAdapter adapter2;
location::nearby::BleV2Medium ble2(adapter2);
ASSERT_TRUE(ble2.StartAdvertising(advertisement.ValueOrDie(), params_));
// Start scanning
ScanSession scan_session =
manager.StartScan(scan_request_, std::move(scanning_callback));
// Ensure that we are in a good state.
env_.Sync();
EXPECT_EQ(manager.ScanningCallbacksLengthForTest(), 1);
ASSERT_TRUE(ble2.IsValid());
ASSERT_TRUE(mediums.GetBle().IsAvailable());
EXPECT_TRUE(start_latch_.Await(absl::Milliseconds(500)).result());
EXPECT_TRUE(found_latch_.Await(absl::Milliseconds(1500)).result());
EXPECT_TRUE(scan_session.StopScan().Ok());
EXPECT_EQ(manager.ScanningCallbacksLengthForTest(), 0);
}
TEST_F(ScanManagerTest, CannotStopScanTwice) {
Mediums mediums;
ScanManager manager(mediums, credential_manager_);
ScanCallback scanning_callback = ScanCallback{
.start_scan_cb =
[this](Status status) {
if (status.Ok()) {
start_latch_.CountDown();
}
},
};
auto scan_session =
manager.StartScan(scan_request_, std::move(scanning_callback));
NEARBY_LOGS(INFO) << "Start scan";
EXPECT_TRUE(start_latch_.Await(absl::Milliseconds(1000)).result());
// Ensure that we have started scanning before we try to stop.
env_.Sync();
NEARBY_LOGS(INFO) << "Stop scan";
EXPECT_TRUE(scan_session.StopScan().Ok());
NEARBY_LOGS(INFO) << "Stop scan again";
EXPECT_FALSE(scan_session.StopScan().Ok());
}
TEST_F(ScanManagerTest, TestNoFilter) {
Mediums mediums;
ScanManager manager(mediums, credential_manager_);
ScanCallback scanning_callback = {
.start_scan_cb =
[this](Status status) {
if (status.Ok()) {
start_latch_.CountDown();
}
},
.on_discovered_cb =
[this](PresenceDevice pd) { found_latch_.CountDown(); }};
// Set up advertiser
// Create BroadcastRequest
PresenceBroadcast::BroadcastSection section = {
.identity = internal::IDENTITY_TYPE_PUBLIC,
.extended_properties = {},
.account_name = "Test account"};
PresenceBroadcast presence_request = {.sections = {section}};
BroadcastRequest input = {.tx_power = 30, .variant = presence_request};
absl::StatusOr<BaseBroadcastRequest> request =
BaseBroadcastRequest::Create(input);
AdvertisementFactory factory(&credential_manager_);
absl::StatusOr<BleAdvertisementData> advertisement =
factory.CreateAdvertisement(request.ValueOrDie());
location::nearby::BluetoothAdapter adapter2;
location::nearby::BleV2Medium ble2(adapter2);
ASSERT_TRUE(ble2.StartAdvertising(advertisement.ValueOrDie(), params_));
// Start scanning
ScanRequest scan_request = {
.account_name = "Test account",
.identity_types = identity_types_,
.scan_filters = {},
.use_ble = true,
.scan_type = ScanType::kPresenceScan,
.power_mode = PowerMode::kBalanced,
.scan_only_when_screen_on = true,
};
ScanSession scan_session =
manager.StartScan(scan_request, std::move(scanning_callback));
EXPECT_EQ(manager.ScanningCallbacksLengthForTest(), 1);
ASSERT_TRUE(ble2.IsValid());
ASSERT_TRUE(mediums.GetBle().IsAvailable());
EXPECT_TRUE(start_latch_.Await(absl::Milliseconds(500)).result());
EXPECT_TRUE(found_latch_.Await(absl::Milliseconds(1500)).result());
EXPECT_TRUE(scan_session.StopScan().Ok());
EXPECT_EQ(manager.ScanningCallbacksLengthForTest(), 0);
}
} // namespace
} // namespace presence
} // namespace nearby
@@ -21,6 +21,7 @@
#include "presence/data_types.h"
#include "presence/implementation/credential_manager_impl.h"
#include "presence/implementation/mediums/mediums.h"
#include "presence/implementation/scan_manager.h"
#include "presence/implementation/service_controller.h"
#include "presence/scan_request.h"
/*
@@ -42,6 +43,8 @@ class ServiceControllerImpl : public ServiceController {
Mediums mediums_; // NOLINT: further impl will use it.
CredentialManagerImpl
credential_manager_; // NOLINT: further impl will use it.
ScanManager scan_manager_{
mediums_, credential_manager_}; // NOLINT: further impl will use it.
};
} // namespace presence