mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-14 14:46:12 -04:00
Add ScanManager class for bookkeeping BLE v2 scans
PiperOrigin-RevId: 485675999
This commit is contained in:
committed by
Copybara-Service
parent
4935a2ffdb
commit
c4dc132f50
+15
-4
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user