mirror of
https://github.com/kidfromjupiter/nearby.git
synced 2026-09-14 22:56:12 -04:00
Roll forward to cl/328359974
Change-Id: If2b57ecc852aecf7dea454648f485fd7c08e72a9
This commit is contained in:
+36
-41
@@ -5,7 +5,6 @@
|
||||
#include "platform_v2/base/byte_array.h"
|
||||
#include "platform_v2/base/input_stream.h"
|
||||
#include "platform_v2/base/output_stream.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
@@ -15,15 +14,17 @@ namespace api {
|
||||
// particular BLE device to connect to its GATT server.
|
||||
class BlePeripheral {
|
||||
public:
|
||||
virtual ~BlePeripheral() {}
|
||||
virtual ~BlePeripheral() = default;
|
||||
|
||||
// The returned reference lifetime matches BlePeripheral object.
|
||||
virtual BluetoothDevice& GetBluetoothDevice() = 0;
|
||||
virtual std::string GetName() const = 0;
|
||||
|
||||
virtual ByteArray GetAdvertisementBytes(
|
||||
const std::string& service_id) const = 0;
|
||||
};
|
||||
|
||||
class BleSocket {
|
||||
public:
|
||||
virtual ~BleSocket() {}
|
||||
virtual ~BleSocket() = default;
|
||||
|
||||
// Returns the InputStream of the BleSocket.
|
||||
// On error, returned stream will report Exception::kIo on any operation.
|
||||
@@ -45,64 +46,58 @@ class BleSocket {
|
||||
// Returns Exception::kIo on error, Exception::kSuccess otherwise.
|
||||
virtual Exception Close() = 0;
|
||||
|
||||
// The returned object is not owned by the caller, and can be invalidated once
|
||||
// the BleSocket object is destroyed.
|
||||
virtual BlePeripheral& GetRemotePeripheral() = 0;
|
||||
// Returns valid BlePeripheral pointer if there is a connection, and
|
||||
// nullptr otherwise.
|
||||
virtual BlePeripheral* GetRemotePeripheral() = 0;
|
||||
};
|
||||
|
||||
// Container of operations that can be performed over the BLE medium.
|
||||
class BleMedium {
|
||||
public:
|
||||
virtual ~BleMedium() {}
|
||||
virtual ~BleMedium() = default;
|
||||
|
||||
virtual bool StartAdvertising(absl::string_view service_id,
|
||||
const ByteArray& advertisement) = 0;
|
||||
virtual void StopAdvertising(absl::string_view service_id) = 0;
|
||||
virtual bool StartAdvertising(const std::string& service_id,
|
||||
const ByteArray& advertisement_bytes) = 0;
|
||||
virtual bool StopAdvertising(const std::string& service_id) = 0;
|
||||
|
||||
class DiscoveredPeripheralCallback {
|
||||
public:
|
||||
virtual ~DiscoveredPeripheralCallback() {}
|
||||
|
||||
// The BlePeripheral* is not owned by callbacks.
|
||||
// It is passed to give access to its non-const methods.
|
||||
// It is guaranteed to be valid for the duration of call.
|
||||
virtual void OnPeripheralDiscovered(BlePeripheral* ble_peripheral,
|
||||
absl::string_view service_id,
|
||||
const ByteArray& advertisement) = 0;
|
||||
virtual void OnPeripheralLost(BlePeripheral* ble_peripheral,
|
||||
absl::string_view service_id) = 0;
|
||||
// Callback that is invoked when a discovered peripheral is found or lost.
|
||||
struct DiscoveredPeripheralCallback {
|
||||
std::function<void(BlePeripheral& peripheral,
|
||||
const std::string& service_id)>
|
||||
peripheral_discovered_cb =
|
||||
DefaultCallback<BlePeripheral&, const std::string&>();
|
||||
std::function<void(BlePeripheral& peripheral,
|
||||
const std::string& service_id)>
|
||||
peripheral_lost_cb =
|
||||
DefaultCallback<BlePeripheral&, const std::string&>();
|
||||
};
|
||||
|
||||
// Returns true once the BLE scan has been initiated.
|
||||
virtual bool StartScanning(
|
||||
absl::string_view service_id,
|
||||
const DiscoveredPeripheralCallback& discovered_peripheral_callback) = 0;
|
||||
virtual bool StartScanning(const std::string& service_id,
|
||||
DiscoveredPeripheralCallback callback) = 0;
|
||||
|
||||
// Returns true once BLE scanning for service_id is well and truly stopped;
|
||||
// after this returns, there must be no more invocations of the
|
||||
// DiscoveredPeripheralCallback passed in to StartScanning() for service_id.
|
||||
virtual void StopScanning(absl::string_view service_id) = 0;
|
||||
virtual bool StopScanning(const std::string& service_id) = 0;
|
||||
|
||||
// Callback that is invoked when a new connection is accepted.
|
||||
class AcceptedConnectionCallback {
|
||||
public:
|
||||
virtual ~AcceptedConnectionCallback() {}
|
||||
|
||||
virtual void OnConnectionAccepted(std::unique_ptr<BleSocket> socket,
|
||||
absl::string_view service_id) = 0;
|
||||
struct AcceptedConnectionCallback {
|
||||
std::function<void(BleSocket& socket, const std::string& service_id)>
|
||||
accepted_cb = DefaultCallback<BleSocket&, const std::string&>();
|
||||
};
|
||||
|
||||
// Returns true once BLE socket connection requests to service_id can be
|
||||
// accepted.
|
||||
virtual bool StartAcceptingConnections(
|
||||
absl::string_view service_id,
|
||||
const AcceptedConnectionCallback& accepted_connection_callback) = 0;
|
||||
virtual void StopAcceptingConnections(const std::string& service_id) = 0;
|
||||
const std::string& service_id, AcceptedConnectionCallback callback) = 0;
|
||||
virtual bool StopAcceptingConnections(const std::string& service_id) = 0;
|
||||
|
||||
// BlePeripheral* is not owned by this call;
|
||||
// it must remain valid for the duration of a call.
|
||||
virtual std::unique_ptr<BleSocket> Connect(BlePeripheral* ble_peripheral,
|
||||
absl::string_view service_id) = 0;
|
||||
// Connects to a BLE peripheral.
|
||||
// On success, returns a new BleSocket.
|
||||
// On error, returns nullptr.
|
||||
virtual std::unique_ptr<BleSocket> Connect(BlePeripheral& peripheral,
|
||||
const std::string& service_id) = 0;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
|
||||
@@ -49,6 +49,9 @@ class BluetoothAdapter {
|
||||
virtual std::string GetName() const = 0;
|
||||
// https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#setName(java.lang.String)
|
||||
virtual bool SetName(absl::string_view name) = 0;
|
||||
|
||||
// Returns BT MAC address assigned to this adapter.
|
||||
virtual std::string GetMacAddress() const = 0;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
|
||||
@@ -21,6 +21,9 @@ class BluetoothDevice {
|
||||
|
||||
// https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#getName()
|
||||
virtual std::string GetName() const = 0;
|
||||
|
||||
// Returns BT MAC address assigned to this device.
|
||||
virtual std::string GetMacAddress() const = 0;
|
||||
};
|
||||
|
||||
// https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html.
|
||||
@@ -132,6 +135,8 @@ class BluetoothClassicMedium {
|
||||
// Returns nullptr error.
|
||||
virtual std::unique_ptr<BluetoothServerSocket> ListenForService(
|
||||
const std::string& service_name, const std::string& service_uuid) = 0;
|
||||
|
||||
virtual BluetoothDevice* FindRemoteDevice(const std::string& mac_address) = 0;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
|
||||
@@ -20,6 +20,10 @@ class WifiLanService {
|
||||
virtual ~WifiLanService() = default;
|
||||
|
||||
virtual std::string GetName() const = 0;
|
||||
|
||||
// Returns the local device's <IP address, port> as a pair.
|
||||
// IP address is in byte sequence, in network order.
|
||||
virtual std::pair<std::string, int> GetServiceAddress() const = 0;
|
||||
};
|
||||
|
||||
class WifiLanSocket {
|
||||
@@ -88,8 +92,7 @@ class WifiLanMedium {
|
||||
// Returns true once WifiLan socket connection requests to service_id can be
|
||||
// accepted.
|
||||
virtual bool StartAcceptingConnections(
|
||||
const std::string& service_id,
|
||||
AcceptedConnectionCallback callback) = 0;
|
||||
const std::string& service_id, AcceptedConnectionCallback callback) = 0;
|
||||
virtual bool StopAcceptingConnections(const std::string& service_id) = 0;
|
||||
|
||||
// Connects to a WifiLan service.
|
||||
@@ -97,6 +100,9 @@ class WifiLanMedium {
|
||||
// On error, returns nullptr.
|
||||
virtual std::unique_ptr<WifiLanSocket> Connect(
|
||||
WifiLanService& service, const std::string& service_id) = 0;
|
||||
|
||||
virtual WifiLanService* FindRemoteService(const std::string& ip_address,
|
||||
int port) = 0;
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
|
||||
@@ -4,10 +4,12 @@ cc_library(
|
||||
name = "base",
|
||||
srcs = [
|
||||
"base64_utils.cc",
|
||||
"bluetooth_utils.cc",
|
||||
"prng.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"base64_utils.h",
|
||||
"bluetooth_utils.h",
|
||||
"byte_array.h",
|
||||
"callable.h",
|
||||
"exception.h",
|
||||
@@ -28,6 +30,7 @@ cc_library(
|
||||
deps = [
|
||||
"//absl/meta:type_traits",
|
||||
"//absl/strings",
|
||||
"//absl/strings:str_format",
|
||||
"//absl/time",
|
||||
],
|
||||
)
|
||||
@@ -96,6 +99,7 @@ cc_library(
|
||||
cc_test(
|
||||
name = "platform_base_test",
|
||||
srcs = [
|
||||
"bluetooth_utils_test.cc",
|
||||
"byte_array_test.cc",
|
||||
"prng_test.cc",
|
||||
],
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
#include "platform_v2/base/bluetooth_utils.h"
|
||||
|
||||
#include "absl/strings/escaping.h"
|
||||
#include "absl/strings/str_format.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
|
||||
std::string BluetoothUtils::ToString(const ByteArray& bluetooth_mac_address) {
|
||||
std::string colon_delimited_string;
|
||||
|
||||
if (bluetooth_mac_address.size() != kBluetoothMacAddressLength)
|
||||
return colon_delimited_string;
|
||||
|
||||
if (IsBluetoothMacAddressUnset(bluetooth_mac_address))
|
||||
return colon_delimited_string;
|
||||
|
||||
for (auto byte : std::string(bluetooth_mac_address)) {
|
||||
if (!colon_delimited_string.empty())
|
||||
absl::StrAppend(&colon_delimited_string, ":");
|
||||
absl::StrAppend(&colon_delimited_string, absl::StrFormat("%02X", byte));
|
||||
}
|
||||
return colon_delimited_string;
|
||||
}
|
||||
|
||||
ByteArray BluetoothUtils::FromString(absl::string_view bluetooth_mac_address) {
|
||||
std::string bt_mac_address(bluetooth_mac_address);
|
||||
|
||||
// Remove the colon delimiters.
|
||||
bt_mac_address.erase(
|
||||
std::remove(bt_mac_address.begin(), bt_mac_address.end(), ':'),
|
||||
bt_mac_address.end());
|
||||
|
||||
// If the bluetooth mac address is invalid (wrong size), return a null byte
|
||||
// array.
|
||||
if (bt_mac_address.length() != kBluetoothMacAddressLength * 2) {
|
||||
return ByteArray();
|
||||
}
|
||||
|
||||
// Convert to bytes. If MAC Address bytes are unset, return a null byte array.
|
||||
auto bt_mac_address_string(absl::HexStringToBytes(bt_mac_address));
|
||||
auto bt_mac_address_bytes =
|
||||
ByteArray(bt_mac_address_string.data(), bt_mac_address_string.size());
|
||||
if (IsBluetoothMacAddressUnset(bt_mac_address_bytes)) {
|
||||
return ByteArray();
|
||||
}
|
||||
return bt_mac_address_bytes;
|
||||
}
|
||||
|
||||
bool BluetoothUtils::IsBluetoothMacAddressUnset(
|
||||
const ByteArray& bluetooth_mac_address_bytes) {
|
||||
for (int i = 0; i < bluetooth_mac_address_bytes.size(); i++) {
|
||||
if (bluetooth_mac_address_bytes.data()[i] != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,32 @@
|
||||
#ifndef PLATFORM_V2_BASE_BLUETOOTH_UTILS_H_
|
||||
#define PLATFORM_V2_BASE_BLUETOOTH_UTILS_H_
|
||||
|
||||
#include "platform_v2/base/byte_array.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
|
||||
class BluetoothUtils {
|
||||
public:
|
||||
static constexpr int kBluetoothMacAddressLength = 6;
|
||||
|
||||
// Converts a Bluetooth MAC address from byte array to String format. Returns
|
||||
// empty if input byte array is not of correct format.
|
||||
// e.g. {-84, 55, 67, -68, -87, 40} -> "AC:37:43:BC:A9:28".
|
||||
static std::string ToString(const ByteArray& bluetooth_mac_address);
|
||||
|
||||
// Converts a Bluetooth MAC address from String format to byte array. Returns
|
||||
// empty if input string is not of correct format.
|
||||
// e.g. "AC:37:43:BC:A9:28" -> {-84, 55, 67, -68, -87, 40}.
|
||||
static ByteArray FromString(absl::string_view bluetooth_mac_address);
|
||||
|
||||
// Checks if a Bluetooth MAC address is zero for every byte.
|
||||
static bool IsBluetoothMacAddressUnset(
|
||||
const ByteArray& bluetooth_mac_address);
|
||||
};
|
||||
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // PLATFORM_V2_BASE_BLUETOOTH_UTILS_H_
|
||||
@@ -0,0 +1,75 @@
|
||||
#include "platform_v2/base/bluetooth_utils.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
|
||||
constexpr absl::string_view kBluetoothMacAddress{"00:00:E6:88:64:13"};
|
||||
constexpr char kBluetoothMacAddressBytes[] = {0x00, 0x00, 0xe6,
|
||||
0x88, 0x64, 0x13};
|
||||
|
||||
TEST(BluetoothUtilsTest, ToStringWorks) {
|
||||
ByteArray bt_mac_address_bytes{
|
||||
kBluetoothMacAddressBytes, sizeof(kBluetoothMacAddressBytes)};
|
||||
|
||||
auto bt_mac_address = BluetoothUtils::ToString(bt_mac_address_bytes);
|
||||
|
||||
EXPECT_EQ(kBluetoothMacAddress, bt_mac_address);
|
||||
}
|
||||
|
||||
TEST(BluetoothUtilsTest, FromStringWorks) {
|
||||
ByteArray bt_mac_address_bytes{
|
||||
kBluetoothMacAddressBytes, sizeof(kBluetoothMacAddressBytes)};
|
||||
|
||||
auto bt_mac_address_bytes_result =
|
||||
BluetoothUtils::FromString(kBluetoothMacAddress);
|
||||
|
||||
EXPECT_EQ(bt_mac_address_bytes, bt_mac_address_bytes_result);
|
||||
}
|
||||
|
||||
TEST(BluetoothUtilsTest, InvalidBytesReturnsEmptyString) {
|
||||
std::string string_result;
|
||||
|
||||
char bad_bt_mac_address_1[] = {0x02, 0x20, 0x00};
|
||||
ByteArray bad_bt_mac_address_bytes_1{bad_bt_mac_address_1,
|
||||
sizeof(bad_bt_mac_address_1)};
|
||||
string_result = BluetoothUtils::ToString(bad_bt_mac_address_bytes_1);
|
||||
EXPECT_TRUE(string_result.empty());
|
||||
|
||||
char bad_bt_mac_address_2[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
ByteArray bad_bt_mac_address_bytes_2{bad_bt_mac_address_2,
|
||||
sizeof(bad_bt_mac_address_2)};
|
||||
string_result = BluetoothUtils::ToString(bad_bt_mac_address_bytes_2);
|
||||
EXPECT_TRUE(string_result.empty());
|
||||
|
||||
char bad_bt_mac_address_3[] = {0x11, 0x22, 0x33, 0x44, 0x55,
|
||||
0x66, 0x77, 0x88, 0x99};
|
||||
ByteArray bad_bt_mac_address_bytes_3{bad_bt_mac_address_3,
|
||||
sizeof(bad_bt_mac_address_3)};
|
||||
string_result = BluetoothUtils::ToString(bad_bt_mac_address_bytes_3);
|
||||
EXPECT_TRUE(string_result.empty());
|
||||
}
|
||||
|
||||
TEST(BluetoothUtilsTest, InvalidStringReturnsEmptyByteArray) {
|
||||
ByteArray bytes_result;
|
||||
|
||||
std::string bad_bt_mac_address_1 = "022:00";
|
||||
bytes_result = BluetoothUtils::FromString(bad_bt_mac_address_1);
|
||||
EXPECT_TRUE(bytes_result.Empty());
|
||||
|
||||
std::string bad_bt_mac_address_2 = "22:00:11:33:77:aa::bb::99";
|
||||
bytes_result = BluetoothUtils::FromString(bad_bt_mac_address_2);
|
||||
EXPECT_TRUE(bytes_result.Empty());
|
||||
|
||||
std::string bad_bt_mac_address_3 = "00:00:00:00:00:00";
|
||||
bytes_result = BluetoothUtils::FromString(bad_bt_mac_address_3);
|
||||
EXPECT_TRUE(bytes_result.Empty());
|
||||
|
||||
std::string bad_bt_mac_address_4 = "BLUETOOTHCHIP";
|
||||
bytes_result = BluetoothUtils::FromString(bad_bt_mac_address_4);
|
||||
EXPECT_TRUE(bytes_result.Empty());
|
||||
}
|
||||
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -74,7 +74,7 @@ class ByteArray {
|
||||
|
||||
// Moves string out of temporary ByteArray, allowing for a zero-copy
|
||||
// operation.
|
||||
explicit operator std::string() const&& { return std::move(data_); }
|
||||
explicit operator std::string() && { return std::move(data_); }
|
||||
|
||||
private:
|
||||
std::string data_;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <new>
|
||||
#include <type_traits>
|
||||
|
||||
#include "platform_v2/api/ble.h"
|
||||
#include "platform_v2/api/bluetooth_adapter.h"
|
||||
#include "platform_v2/api/bluetooth_classic.h"
|
||||
#include "platform_v2/api/wifi_lan.h"
|
||||
@@ -42,6 +43,7 @@ void MediumEnvironment::Reset() {
|
||||
NEARBY_LOG(INFO, "MediumEnvironment::Reset()");
|
||||
bluetooth_adapters_.clear();
|
||||
bluetooth_mediums_.clear();
|
||||
ble_mediums_.clear();
|
||||
wifi_lan_mediums_.clear();
|
||||
});
|
||||
Sync();
|
||||
@@ -154,6 +156,48 @@ void MediumEnvironment::OnBluetoothDeviceStateChanged(
|
||||
}
|
||||
}
|
||||
|
||||
api::BluetoothDevice* MediumEnvironment::FindBluetoothDevice(
|
||||
const std::string& mac_address) {
|
||||
api::BluetoothDevice* device = nullptr;
|
||||
CountDownLatch latch(1);
|
||||
RunOnMediumEnvironmentThread([this, &device, &latch, &mac_address](){
|
||||
for (auto& item : bluetooth_mediums_) {
|
||||
auto* adapter = item.second.adapter;
|
||||
if (!adapter) continue;
|
||||
if (adapter->GetMacAddress() == mac_address) {
|
||||
device = bluetooth_adapters_[adapter];
|
||||
break;
|
||||
}
|
||||
}
|
||||
latch.CountDown();
|
||||
});
|
||||
latch.Await();
|
||||
return device;
|
||||
}
|
||||
|
||||
void MediumEnvironment::OnBlePeripheralStateChanged(
|
||||
BleMediumContext& info, api::BlePeripheral& peripheral,
|
||||
const std::string& service_id, bool enabled) {
|
||||
if (!enabled_) return;
|
||||
NEARBY_LOG(INFO,
|
||||
"G3 OnBleServiceStateChanged [peripheral impl=%p]; context=%p; "
|
||||
"service_id=%s; notify=%d",
|
||||
&peripheral, &info, service_id.c_str(),
|
||||
enable_notifications_.load());
|
||||
if (!enable_notifications_) return;
|
||||
RunOnMediumEnvironmentThread([&info, enabled, &peripheral, service_id]() {
|
||||
NEARBY_LOG(INFO,
|
||||
"G3 [Run] OnBlePeripheralStateChanged [peripheral impl=%p]; "
|
||||
"context=%p; service_id=%s; enabled=%d",
|
||||
&peripheral, &info, service_id.c_str(), enabled);
|
||||
if (enabled) {
|
||||
info.discovery_callback.peripheral_discovered_cb(peripheral, service_id);
|
||||
} else {
|
||||
info.discovery_callback.peripheral_lost_cb(peripheral, service_id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void MediumEnvironment::OnWifiLanServiceStateChanged(
|
||||
WifiLanMediumContext& info, api::WifiLanService& service,
|
||||
const std::string& service_id, bool enabled) {
|
||||
@@ -164,6 +208,10 @@ void MediumEnvironment::OnWifiLanServiceStateChanged(
|
||||
&service, &info, service_id.c_str(), enable_notifications_.load());
|
||||
if (!enable_notifications_) return;
|
||||
RunOnMediumEnvironmentThread([&info, enabled, &service, service_id]() {
|
||||
NEARBY_LOG(INFO,
|
||||
"G3 [Run] OnWifiLanServiceStateChanged [service impl=%p]; "
|
||||
"context=%p; service_id=%s; enabled=%d",
|
||||
&service, &info, service_id.c_str(), enabled);
|
||||
auto service_id_context = info.services.find(service_id);
|
||||
if (service_id_context == info.services.end()) return;
|
||||
|
||||
@@ -246,6 +294,125 @@ void MediumEnvironment::UnregisterBluetoothMedium(
|
||||
});
|
||||
}
|
||||
|
||||
void MediumEnvironment::RegisterBleMedium(api::BleMedium& medium) {
|
||||
if (!enabled_) return;
|
||||
RunOnMediumEnvironmentThread([this, &medium]() {
|
||||
ble_mediums_.insert({&medium, BleMediumContext{}});
|
||||
NEARBY_LOG(INFO, "Registered: medium=%p", &medium);
|
||||
});
|
||||
}
|
||||
|
||||
void MediumEnvironment::UpdateBleMediumForAdvertising(
|
||||
api::BleMedium& medium, api::BlePeripheral& peripheral,
|
||||
const std::string& service_id, bool enabled) {
|
||||
if (!enabled_) return;
|
||||
RunOnMediumEnvironmentThread([this, &medium, &peripheral, service_id,
|
||||
enabled]() {
|
||||
auto item = ble_mediums_.find(&medium);
|
||||
if (item == ble_mediums_.end()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"UpdateBleMediumForAdvertising failed. There is no medium "
|
||||
"registered.");
|
||||
return;
|
||||
}
|
||||
auto& context = item->second;
|
||||
context.ble_peripheral = &peripheral;
|
||||
context.advertising = enabled;
|
||||
NEARBY_LOG(INFO,
|
||||
"Update Ble medium for advertising: this=%p; medium=%p; "
|
||||
"service_id=%s; name=%s; enabled=%d; ",
|
||||
this, &medium, service_id.c_str(), peripheral.GetName().c_str(),
|
||||
enabled);
|
||||
for (auto& medium_info : ble_mediums_) {
|
||||
auto& local_medium = medium_info.first;
|
||||
auto& info = medium_info.second;
|
||||
// Do not send notification to the same medium.
|
||||
if (local_medium == &medium) continue;
|
||||
OnBlePeripheralStateChanged(info, peripheral, service_id, enabled);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void MediumEnvironment::UpdateBleMediumForScanning(
|
||||
api::BleMedium& medium, const std::string& service_id,
|
||||
BleDiscoveredPeripheralCallback callback, bool enabled) {
|
||||
if (!enabled_) return;
|
||||
RunOnMediumEnvironmentThread([this, &medium, service_id,
|
||||
callback = std::move(callback), enabled]() {
|
||||
auto item = ble_mediums_.find(&medium);
|
||||
if (item == ble_mediums_.end()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"UpdateBleMediumFoScanning failed. There is no medium "
|
||||
"registered.");
|
||||
return;
|
||||
}
|
||||
auto& context = item->second;
|
||||
context.discovery_callback = std::move(callback);
|
||||
NEARBY_LOG(INFO,
|
||||
"Update Ble medium for scanning: this=%p; medium=%p; "
|
||||
"service_id=%s; enabled=%d ;",
|
||||
this, &medium, service_id.c_str(), enabled);
|
||||
for (auto& medium_info : ble_mediums_) {
|
||||
auto& local_medium = medium_info.first;
|
||||
auto& info = medium_info.second;
|
||||
// Do not send notification to the same medium.
|
||||
if (local_medium == &medium) continue;
|
||||
// Search advertising mediums and send notification.
|
||||
if (info.advertising && enabled) {
|
||||
OnBlePeripheralStateChanged(context, *(info.ble_peripheral), service_id,
|
||||
enabled);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void MediumEnvironment::UpdateBleMediumForAcceptedConnection(
|
||||
api::BleMedium& medium, const std::string& service_id,
|
||||
BleAcceptedConnectionCallback callback) {
|
||||
if (!enabled_) return;
|
||||
RunOnMediumEnvironmentThread([this, &medium, service_id,
|
||||
callback = std::move(callback)]() {
|
||||
auto item = ble_mediums_.find(&medium);
|
||||
if (item == ble_mediums_.end()) {
|
||||
NEARBY_LOG(
|
||||
INFO, "Update Ble medium failed. There is no medium registered.");
|
||||
return;
|
||||
}
|
||||
auto& context = item->second;
|
||||
context.accepted_connection_callback = std::move(callback);
|
||||
NEARBY_LOG(INFO,
|
||||
"Update Ble medium for accepted callback: this=%p; "
|
||||
"medium=%p; service_id=%s; ",
|
||||
this, &medium, service_id.c_str());
|
||||
});
|
||||
}
|
||||
|
||||
void MediumEnvironment::UnregisterBleMedium(api::BleMedium& medium) {
|
||||
if (!enabled_) return;
|
||||
RunOnMediumEnvironmentThread([this, &medium]() {
|
||||
auto item = ble_mediums_.extract(&medium);
|
||||
if (item.empty()) return;
|
||||
NEARBY_LOG(INFO, "Unregistered Ble medium");
|
||||
});
|
||||
}
|
||||
|
||||
void MediumEnvironment::CallBleAcceptedConnectionCallback(
|
||||
api::BleMedium& medium, api::BleSocket& socket,
|
||||
const std::string& service_id) {
|
||||
if (!enabled_) return;
|
||||
RunOnMediumEnvironmentThread([this, &medium, &socket, service_id]() {
|
||||
auto item = ble_mediums_.find(&medium);
|
||||
if (item == ble_mediums_.end()) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Call AcceptedConnectionCallback failed.. There is no medium "
|
||||
"registered.");
|
||||
return;
|
||||
}
|
||||
auto& info = item->second;
|
||||
info.accepted_connection_callback.accepted_cb(socket, service_id);
|
||||
});
|
||||
}
|
||||
|
||||
void MediumEnvironment::RegisterWebRtcSignalingMessenger(
|
||||
absl::string_view self_id, OnSignalingMessageCallback callback) {
|
||||
if (!enabled_) return;
|
||||
@@ -437,5 +604,26 @@ void MediumEnvironment::CallWifiLanAcceptedConnectionCallback(
|
||||
});
|
||||
}
|
||||
|
||||
api::WifiLanService* MediumEnvironment::FindWifiLanService(
|
||||
const std::string& ip_address, int port) {
|
||||
api::WifiLanService* remote_service = nullptr;
|
||||
CountDownLatch latch(1);
|
||||
RunOnMediumEnvironmentThread(
|
||||
[this, &remote_service, &ip_address, port, &latch]() {
|
||||
for (auto& item : wifi_lan_mediums_) {
|
||||
auto* service = item.second.wifi_lan_service;
|
||||
if (!service) continue;
|
||||
auto addr = remote_service->GetServiceAddress();
|
||||
if (addr.first == ip_address && addr.second == port) {
|
||||
remote_service = service;
|
||||
break;
|
||||
}
|
||||
}
|
||||
latch.CountDown();
|
||||
});
|
||||
latch.Await();
|
||||
return remote_service;
|
||||
}
|
||||
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
@@ -33,6 +33,10 @@ class MediumEnvironment {
|
||||
public:
|
||||
using BluetoothDiscoveryCallback =
|
||||
api::BluetoothClassicMedium::DiscoveryCallback;
|
||||
using BleDiscoveredPeripheralCallback =
|
||||
api::BleMedium::DiscoveredPeripheralCallback;
|
||||
using BleAcceptedConnectionCallback =
|
||||
api::BleMedium::AcceptedConnectionCallback;
|
||||
using OnSignalingMessageCallback =
|
||||
api::WebRtcSignalingMessenger::OnSignalingMessageCallback;
|
||||
using WifiLanDiscoveredServiceCallback =
|
||||
@@ -103,6 +107,9 @@ class MediumEnvironment {
|
||||
// Removes medium-related info. This should correspond to device power off.
|
||||
void UnregisterBluetoothMedium(api::BluetoothClassicMedium& medium);
|
||||
|
||||
// Returns a Bluetooth Device object matching given mac address to nullptr.
|
||||
api::BluetoothDevice* FindBluetoothDevice(const std::string& mac_address);
|
||||
|
||||
const EnvironmentConfig& GetEnvironmentConfig();
|
||||
|
||||
// Registers |callback| to receive messages sent to device with id |self_id|.
|
||||
@@ -116,6 +123,48 @@ class MediumEnvironment {
|
||||
// |peer_id|.
|
||||
void SendWebRtcSignalingMessage(absl::string_view peer_id,
|
||||
const ByteArray& message);
|
||||
|
||||
// Adds medium-related info to allow for scanning/advertising to work.
|
||||
// This provides acccess to this medium from other mediums, when protocol
|
||||
// expects they should communicate.
|
||||
void RegisterBleMedium(api::BleMedium& medium);
|
||||
|
||||
// Updates advertising info to indicate the current medium is exposing
|
||||
// advertising event.
|
||||
void UpdateBleMediumForAdvertising(api::BleMedium& medium,
|
||||
api::BlePeripheral& peripheral,
|
||||
const std::string& service_id,
|
||||
bool enabled);
|
||||
|
||||
// Updates discovery callback info to allow for dispatch of discovery events.
|
||||
//
|
||||
// Invokes callback asynchronously when any changes happen to discoverable
|
||||
// devices, or if the defice is turned off, whether or not it is discoverable,
|
||||
// if it was ever reported as discoverable.
|
||||
//
|
||||
// This should be called when discoverable state changes.
|
||||
// with user-specified callback when discovery is enabled, and with default
|
||||
// (empty) callback otherwise.
|
||||
void UpdateBleMediumForScanning(api::BleMedium& medium,
|
||||
const std::string& service_id,
|
||||
BleDiscoveredPeripheralCallback callback,
|
||||
bool enabled);
|
||||
|
||||
// Updates Accepted connection callback info to allow for dispatch of
|
||||
// advertising events.
|
||||
void UpdateBleMediumForAcceptedConnection(
|
||||
api::BleMedium& medium, const std::string& service_id,
|
||||
BleAcceptedConnectionCallback callback);
|
||||
|
||||
// Removes medium-related info. This should correspond to device power off.
|
||||
void UnregisterBleMedium(api::BleMedium& medium);
|
||||
|
||||
// Call back when advertising has created the server socket and is ready for
|
||||
// connect.
|
||||
void CallBleAcceptedConnectionCallback(api::BleMedium& medium,
|
||||
api::BleSocket& socket,
|
||||
const std::string& service_id);
|
||||
|
||||
// Adds medium-related info to allow for discovery/advertising to work.
|
||||
// This provides acccess to this medium from other mediums, when protocol
|
||||
// expects they should communicate.
|
||||
@@ -123,9 +172,10 @@ class MediumEnvironment {
|
||||
|
||||
// Updates advertising info to indicate the current medium is exposing
|
||||
// advertising event.
|
||||
void UpdateWifiLanMediumForAdvertising(
|
||||
api::WifiLanMedium& medium, api::WifiLanService& service,
|
||||
const std::string& service_id, bool enabled);
|
||||
void UpdateWifiLanMediumForAdvertising(api::WifiLanMedium& medium,
|
||||
api::WifiLanService& service,
|
||||
const std::string& service_id,
|
||||
bool enabled);
|
||||
|
||||
// Updates discovery callback info to allow for dispatch of discovery events.
|
||||
//
|
||||
@@ -155,6 +205,10 @@ class MediumEnvironment {
|
||||
api::WifiLanSocket& socket,
|
||||
const std::string& service_id);
|
||||
|
||||
// Returns WiFi LAN service matching IP address and port, or nullptr.
|
||||
api::WifiLanService* FindWifiLanService(const std::string& ip_address,
|
||||
int port);
|
||||
|
||||
private:
|
||||
struct BluetoothMediumContext {
|
||||
BluetoothDiscoveryCallback callback;
|
||||
@@ -163,6 +217,13 @@ class MediumEnvironment {
|
||||
absl::flat_hash_map<api::BluetoothDevice*, std::string> devices;
|
||||
};
|
||||
|
||||
struct BleMediumContext {
|
||||
BleDiscoveredPeripheralCallback discovery_callback;
|
||||
BleAcceptedConnectionCallback accepted_connection_callback;
|
||||
api::BlePeripheral* ble_peripheral = nullptr;
|
||||
bool advertising = false;
|
||||
};
|
||||
|
||||
struct WifiLanServiceIdContext {
|
||||
WifiLanDiscoveredServiceCallback discovery_callback;
|
||||
WifiLanAcceptedConnectionCallback accepted_connection_callback;
|
||||
@@ -187,6 +248,10 @@ class MediumEnvironment {
|
||||
api::BluetoothAdapter::ScanMode mode,
|
||||
bool enabled);
|
||||
|
||||
void OnBlePeripheralStateChanged(BleMediumContext& info,
|
||||
api::BlePeripheral& peripheral,
|
||||
const std::string& service_id, bool enabled);
|
||||
|
||||
void OnWifiLanServiceStateChanged(WifiLanMediumContext& info,
|
||||
api::WifiLanService& service,
|
||||
const std::string& service_id,
|
||||
@@ -207,6 +272,8 @@ class MediumEnvironment {
|
||||
absl::flat_hash_map<api::BluetoothClassicMedium*, BluetoothMediumContext>
|
||||
bluetooth_mediums_;
|
||||
|
||||
absl::flat_hash_map<api::BleMedium*, BleMediumContext> ble_mediums_;
|
||||
|
||||
// Maps peer id to callback for receiving signaling messages.
|
||||
absl::flat_hash_map<std::string, OnSignalingMessageCallback>
|
||||
webrtc_signaling_callback_;
|
||||
|
||||
@@ -39,12 +39,14 @@ cc_library(
|
||||
name = "comm",
|
||||
testonly = True,
|
||||
srcs = [
|
||||
"ble.cc",
|
||||
"bluetooth_adapter.cc",
|
||||
"bluetooth_classic.cc",
|
||||
"webrtc.cc",
|
||||
"wifi_lan.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"ble.h",
|
||||
"bluetooth_adapter.h",
|
||||
"bluetooth_classic.h",
|
||||
"webrtc.h",
|
||||
@@ -76,9 +78,7 @@ cc_library(
|
||||
srcs = [
|
||||
"crypto.cc",
|
||||
],
|
||||
visibility = [
|
||||
"//platform_v2/g3:__pkg__",
|
||||
],
|
||||
visibility = ["//visibility:private"],
|
||||
deps = [
|
||||
"//platform_v2/api:types",
|
||||
"//platform_v2/base",
|
||||
@@ -94,7 +94,6 @@ cc_library(
|
||||
"platform.cc",
|
||||
],
|
||||
visibility = [
|
||||
"//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__",
|
||||
"//core_v2:__subpackages__",
|
||||
"//platform_v2:__subpackages__",
|
||||
],
|
||||
|
||||
@@ -0,0 +1,341 @@
|
||||
#include "platform_v2/impl/g3/ble.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "platform_v2/api/ble.h"
|
||||
#include "platform_v2/base/logging.h"
|
||||
#include "platform_v2/base/medium_environment.h"
|
||||
#include "absl/synchronization/mutex.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace g3 {
|
||||
|
||||
BleSocket::~BleSocket() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
DoClose();
|
||||
}
|
||||
|
||||
void BleSocket::Connect(BleSocket& other) {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
remote_socket_ = &other;
|
||||
input_ = other.output_;
|
||||
}
|
||||
|
||||
InputStream& BleSocket::GetInputStream() {
|
||||
auto* remote_socket = GetRemoteSocket();
|
||||
CHECK(remote_socket != nullptr);
|
||||
return remote_socket->GetLocalInputStream();
|
||||
}
|
||||
|
||||
OutputStream& BleSocket::GetOutputStream() {
|
||||
return GetLocalOutputStream();
|
||||
}
|
||||
|
||||
BleSocket* BleSocket::GetRemoteSocket() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return remote_socket_;
|
||||
}
|
||||
|
||||
bool BleSocket::IsConnected() const {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return IsConnectedLocked();
|
||||
}
|
||||
|
||||
bool BleSocket::IsClosed() const {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return closed_;
|
||||
}
|
||||
|
||||
Exception BleSocket::Close() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
DoClose();
|
||||
return {Exception::kSuccess};
|
||||
}
|
||||
|
||||
BlePeripheral* BleSocket::GetRemotePeripheral() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return peripheral_;
|
||||
}
|
||||
|
||||
void BleSocket::DoClose() {
|
||||
if (!closed_) {
|
||||
remote_socket_ = nullptr;
|
||||
output_->GetOutputStream().Close();
|
||||
output_->GetInputStream().Close();
|
||||
if (IsConnectedLocked()) {
|
||||
input_->GetOutputStream().Close();
|
||||
input_->GetInputStream().Close();
|
||||
}
|
||||
closed_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool BleSocket::IsConnectedLocked() const { return input_ != nullptr; }
|
||||
|
||||
InputStream& BleSocket::GetLocalInputStream() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return output_->GetInputStream();
|
||||
}
|
||||
|
||||
OutputStream& BleSocket::GetLocalOutputStream() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return output_->GetOutputStream();
|
||||
}
|
||||
|
||||
std::unique_ptr<api::BleSocket> BleServerSocket::Accept(
|
||||
BlePeripheral* peripheral) {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
if (closed_) return {};
|
||||
while (pending_sockets_.empty()) {
|
||||
cond_.Wait(&mutex_);
|
||||
if (closed_) break;
|
||||
}
|
||||
if (closed_) return {};
|
||||
auto* remote_socket =
|
||||
pending_sockets_.extract(pending_sockets_.begin()).value();
|
||||
CHECK(remote_socket);
|
||||
auto local_socket = std::make_unique<BleSocket>(peripheral);
|
||||
local_socket->Connect(*remote_socket);
|
||||
remote_socket->Connect(*local_socket);
|
||||
cond_.SignalAll();
|
||||
return local_socket;
|
||||
}
|
||||
|
||||
bool BleServerSocket::Connect(BleSocket& socket) {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
if (closed_) return false;
|
||||
if (socket.IsConnected()) {
|
||||
NEARBY_LOG(ERROR,
|
||||
"Failed to connect to Ble server socket: already connected");
|
||||
return true; // already connected.
|
||||
}
|
||||
// add client socket to the pending list
|
||||
pending_sockets_.emplace(&socket);
|
||||
cond_.SignalAll();
|
||||
while (!socket.IsConnected()) {
|
||||
cond_.Wait(&mutex_);
|
||||
if (closed_) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void BleServerSocket::SetCloseNotifier(std::function<void()> notifier) {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
close_notifier_ = std::move(notifier);
|
||||
}
|
||||
|
||||
BleServerSocket::~BleServerSocket() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
DoClose();
|
||||
}
|
||||
|
||||
Exception BleServerSocket::Close() {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
return DoClose();
|
||||
}
|
||||
|
||||
Exception BleServerSocket::DoClose() {
|
||||
bool should_notify = !closed_;
|
||||
closed_ = true;
|
||||
if (should_notify) {
|
||||
cond_.SignalAll();
|
||||
if (close_notifier_) {
|
||||
auto notifier = std::move(close_notifier_);
|
||||
mutex_.Unlock();
|
||||
// Notifier may contain calls to public API, and may cause deadlock, if
|
||||
// mutex_ is held during the call.
|
||||
notifier();
|
||||
mutex_.Lock();
|
||||
}
|
||||
}
|
||||
return {Exception::kSuccess};
|
||||
}
|
||||
|
||||
BleMedium::BleMedium(api::BluetoothAdapter& adapter)
|
||||
: adapter_(static_cast<BluetoothAdapter*>(&adapter)) {
|
||||
adapter_->SetBleMedium(this);
|
||||
auto& env = MediumEnvironment::Instance();
|
||||
env.RegisterBleMedium(*this);
|
||||
}
|
||||
|
||||
BleMedium::~BleMedium() {
|
||||
adapter_->SetBleMedium(nullptr);
|
||||
auto& env = MediumEnvironment::Instance();
|
||||
env.UnregisterBleMedium(*this);
|
||||
|
||||
StopAdvertising(advertising_info_.service_id);
|
||||
StopScanning(scanning_info_.service_id);
|
||||
|
||||
accept_loops_runner_.Shutdown();
|
||||
NEARBY_LOG(INFO, "BleMedium dtor advertising_accept_thread_running_ = %d",
|
||||
acceptance_thread_running_.load());
|
||||
// If acceptance thread is still running, wait to finish.
|
||||
if (acceptance_thread_running_) {
|
||||
while (acceptance_thread_running_) {
|
||||
CountDownLatch latch(1);
|
||||
close_accept_loops_runner_.Execute([&latch]() { latch.CountDown(); });
|
||||
latch.Await();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool BleMedium::StartAdvertising(const std::string& service_id,
|
||||
const ByteArray& advertisement_bytes) {
|
||||
NEARBY_LOGS(INFO) << "G3 Ble StartAdvertising: service_id=" << service_id
|
||||
<< ", advertisement bytes=" << advertisement_bytes.data()
|
||||
<< "(" << advertisement_bytes.size() << ")";
|
||||
auto& env = MediumEnvironment::Instance();
|
||||
auto& peripheral = adapter_->GetPeripheral();
|
||||
peripheral.SetAdvertisementBytes(service_id, advertisement_bytes);
|
||||
env.UpdateBleMediumForAdvertising(*this, peripheral, service_id, true);
|
||||
|
||||
absl::MutexLock lock(&mutex_);
|
||||
if (server_socket_ != nullptr) server_socket_.release();
|
||||
server_socket_ = std::make_unique<BleServerSocket>();
|
||||
|
||||
acceptance_thread_running_.exchange(true);
|
||||
accept_loops_runner_.Execute([&env, this, service_id]() mutable {
|
||||
if (!accept_loops_runner_.InShutdown()) {
|
||||
while (true) {
|
||||
auto client_socket =
|
||||
server_socket_->Accept(&(this->adapter_->GetPeripheral()));
|
||||
if (client_socket == nullptr) break;
|
||||
env.CallBleAcceptedConnectionCallback(*this, *(client_socket.release()),
|
||||
service_id);
|
||||
}
|
||||
}
|
||||
acceptance_thread_running_.exchange(false);
|
||||
});
|
||||
advertising_info_.service_id = service_id;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BleMedium::StopAdvertising(const std::string& service_id) {
|
||||
NEARBY_LOGS(INFO) << "G3 Ble StopAdvertising: service_id=" << service_id;
|
||||
{
|
||||
absl::MutexLock lock(&mutex_);
|
||||
if (advertising_info_.Empty()) {
|
||||
NEARBY_LOGS(INFO) << "G3 Ble StopAdvertising: Can't stop advertising "
|
||||
"because we never started advertising.";
|
||||
return false;
|
||||
}
|
||||
advertising_info_.Clear();
|
||||
}
|
||||
|
||||
auto& env = MediumEnvironment::Instance();
|
||||
env.UpdateBleMediumForAdvertising(*this, adapter_->GetPeripheral(),
|
||||
service_id, false);
|
||||
accept_loops_runner_.Shutdown();
|
||||
if (server_socket_ == nullptr) {
|
||||
NEARBY_LOGS(ERROR) << "G3 Ble StopAdvertising: Failed to find Ble Server "
|
||||
"socket: service_id="
|
||||
<< service_id;
|
||||
// Fall through for server socket not found.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!server_socket_->Close().Ok()) {
|
||||
NEARBY_LOGS(INFO)
|
||||
<< "G3 Ble StopAdvertising: Failed to close Ble server socket for "
|
||||
<< service_id;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BleMedium::StartScanning(const std::string& service_id,
|
||||
DiscoveredPeripheralCallback callback) {
|
||||
NEARBY_LOGS(INFO) << "G3 Ble StartScanning: service_id=" << service_id;
|
||||
auto& env = MediumEnvironment::Instance();
|
||||
env.UpdateBleMediumForScanning(*this, service_id, std::move(callback), true);
|
||||
{
|
||||
absl::MutexLock lock(&mutex_);
|
||||
scanning_info_.service_id = service_id;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BleMedium::StopScanning(const std::string& service_id) {
|
||||
NEARBY_LOGS(INFO) << "G3 Ble StopScanning: service_id=" << service_id;
|
||||
{
|
||||
absl::MutexLock lock(&mutex_);
|
||||
if (scanning_info_.Empty()) {
|
||||
NEARBY_LOGS(INFO) << "G3 Ble StopDiscovery: Can't stop scanning because "
|
||||
"we never started scanning.";
|
||||
return false;
|
||||
}
|
||||
scanning_info_.Clear();
|
||||
}
|
||||
|
||||
auto& env = MediumEnvironment::Instance();
|
||||
env.UpdateBleMediumForScanning(*this, service_id, {}, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BleMedium::StartAcceptingConnections(const std::string& service_id,
|
||||
AcceptedConnectionCallback callback) {
|
||||
NEARBY_LOGS(INFO) << "G3 Ble StartAcceptingConnections: service_id="
|
||||
<< service_id;
|
||||
auto& env = MediumEnvironment::Instance();
|
||||
env.UpdateBleMediumForAcceptedConnection(*this, service_id, callback);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BleMedium::StopAcceptingConnections(const std::string& service_id) {
|
||||
NEARBY_LOGS(INFO) << "G3 Ble StopAcceptingConnections: service_id="
|
||||
<< service_id;
|
||||
auto& env = MediumEnvironment::Instance();
|
||||
env.UpdateBleMediumForAcceptedConnection(*this, service_id, {});
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<api::BleSocket> BleMedium::Connect(
|
||||
api::BlePeripheral& remote_peripheral, const std::string& service_id) {
|
||||
NEARBY_LOG(INFO,
|
||||
"G3 Ble Connect [self]: medium=%p, adapter=%p, peripheral=%p, "
|
||||
"service_id=%s",
|
||||
this, &GetAdapter(), &GetAdapter().GetPeripheral(),
|
||||
service_id.c_str());
|
||||
// First, find an instance of remote medium, that exposed this peripheral.
|
||||
auto& adapter = static_cast<BlePeripheral&>(remote_peripheral).GetAdapter();
|
||||
auto* medium = static_cast<BleMedium*>(adapter.GetBleMedium());
|
||||
|
||||
if (!medium) return {}; // Can't find medium. Bail out.
|
||||
|
||||
BleServerSocket* remote_server_socket = nullptr;
|
||||
NEARBY_LOG(INFO,
|
||||
"G3 Ble Connect [peer]: medium=%p, adapter=%p, peripheral=%p, "
|
||||
"service_id=%s",
|
||||
medium, &adapter, &remote_peripheral, service_id.c_str());
|
||||
// Then, find our server socket context in this medium.
|
||||
{
|
||||
absl::MutexLock medium_lock(&medium->mutex_);
|
||||
remote_server_socket = medium->server_socket_.get();
|
||||
if (remote_server_socket == nullptr) {
|
||||
NEARBY_LOGS(ERROR)
|
||||
<< "G3 Ble Connect: Failed to find Ble Server socket: service_id="
|
||||
<< service_id;
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
BlePeripheral peripheral = static_cast<BlePeripheral&>(remote_peripheral);
|
||||
auto socket = std::make_unique<BleSocket>(&peripheral);
|
||||
// Finally, Request to connect to this socket.
|
||||
if (!remote_server_socket->Connect(*socket)) {
|
||||
NEARBY_LOGS(ERROR) << "G3 Ble Connect: Failed to connect to existing Ble "
|
||||
"Server socket: service_id="
|
||||
<< service_id;
|
||||
return {};
|
||||
}
|
||||
|
||||
NEARBY_LOG(INFO, "G3 Ble Connect: connected: socket=%p", socket.get());
|
||||
return socket;
|
||||
}
|
||||
|
||||
} // namespace g3
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,213 @@
|
||||
#ifndef PLATFORM_V2_IMPL_G3_BLE_H_
|
||||
#define PLATFORM_V2_IMPL_G3_BLE_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "platform_v2/api/ble.h"
|
||||
#include "platform_v2/base/byte_array.h"
|
||||
#include "platform_v2/base/input_stream.h"
|
||||
#include "platform_v2/base/output_stream.h"
|
||||
#include "platform_v2/impl/g3/bluetooth_adapter.h"
|
||||
#include "platform_v2/impl/g3/bluetooth_classic.h"
|
||||
#include "platform_v2/impl/g3/multi_thread_executor.h"
|
||||
#include "platform_v2/impl/g3/pipe.h"
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/container/flat_hash_set.h"
|
||||
#include "absl/strings/escaping.h"
|
||||
#include "absl/synchronization/mutex.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace g3 {
|
||||
|
||||
class BleMedium;
|
||||
|
||||
class BleSocket : public api::BleSocket {
|
||||
public:
|
||||
BleSocket() = default;
|
||||
explicit BleSocket(BlePeripheral* peripheral) : peripheral_(peripheral) {}
|
||||
~BleSocket() override;
|
||||
|
||||
// Connect to another BleSocket, to form a functional low-level channel.
|
||||
// from this point on, and until Close is called, connection exists.
|
||||
void Connect(BleSocket& other) ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns the InputStream of this connected BleSocket.
|
||||
InputStream& GetInputStream() override ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns the OutputStream of this connected BleSocket.
|
||||
// This stream is for local side to write.
|
||||
OutputStream& GetOutputStream() override ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns address of a remote BleSocket or nullptr.
|
||||
BleSocket* GetRemoteSocket() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns true if connection exists to the (possibly closed) remote socket.
|
||||
bool IsConnected() const ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns true if socket is closed.
|
||||
bool IsClosed() const ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns Exception::kIo on error, Exception::kSuccess otherwise.
|
||||
Exception Close() override ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns valid BlePeripheral pointer if there is a connection, and
|
||||
// nullptr otherwise.
|
||||
BlePeripheral* GetRemotePeripheral() override
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
private:
|
||||
void DoClose() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Returns true if connection exists to the (possibly closed) remote socket.
|
||||
bool IsConnectedLocked() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Returns InputStream of our side of a connection.
|
||||
// This is what the remote side is supposed to read from.
|
||||
// This is a helper for GetInputStream() method.
|
||||
InputStream& GetLocalInputStream() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns OutputStream of our side of a connection.
|
||||
// This is what the local size is supposed to write to.
|
||||
// This is a helper for GetOutputStream() method.
|
||||
OutputStream& GetLocalOutputStream() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Output pipe is initialized by constructor, it remains always valid, until
|
||||
// it is closed. it represents output part of a local socket. Input part of a
|
||||
// local socket comes from the peer socket, after connection.
|
||||
std::shared_ptr<Pipe> output_ {new Pipe};
|
||||
std::shared_ptr<Pipe> input_;
|
||||
mutable absl::Mutex mutex_;
|
||||
BlePeripheral* peripheral_;
|
||||
BleSocket* remote_socket_ ABSL_GUARDED_BY(mutex_) = nullptr;
|
||||
bool closed_ ABSL_GUARDED_BY(mutex_) = false;
|
||||
};
|
||||
|
||||
class BleServerSocket {
|
||||
public:
|
||||
~BleServerSocket();
|
||||
|
||||
// Blocks until either:
|
||||
// - at least one incoming connection request is available, or
|
||||
// - ServerSocket is closed.
|
||||
// On success, returns connected socket, ready to exchange data.
|
||||
// Returns nullptr on error.
|
||||
// Once error is reported, it is permanent, and ServerSocket has to be closed.
|
||||
//
|
||||
// Called by the server side of a connection.
|
||||
// Returns BleSocket to the server side.
|
||||
// If not null, returned socket is connected to its remote (client-side) peer.
|
||||
std::unique_ptr<api::BleSocket> Accept(BlePeripheral* peripheral)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Blocks until either:
|
||||
// - connection is available, or
|
||||
// - server socket is closed, or
|
||||
// - error happens.
|
||||
//
|
||||
// Called by the client side of a connection.
|
||||
// Returns true, if socket is successfully connected.
|
||||
bool Connect(BleSocket& socket) ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Called by the server side of a connection before passing ownership of
|
||||
// BleServerSocker to user, to track validity of a pointer to this
|
||||
// server socket,
|
||||
void SetCloseNotifier(std::function<void()> notifier)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns Exception::kIo on error, Exception::kSuccess otherwise.
|
||||
// Calls close_notifier if it was previously set, and marks socket as closed.
|
||||
Exception Close() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
private:
|
||||
Exception DoClose() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
absl::Mutex mutex_;
|
||||
absl::CondVar cond_;
|
||||
absl::flat_hash_set<BleSocket*> pending_sockets_ ABSL_GUARDED_BY(mutex_);
|
||||
std::function<void()> close_notifier_ ABSL_GUARDED_BY(mutex_);
|
||||
bool closed_ ABSL_GUARDED_BY(mutex_) = false;
|
||||
};
|
||||
|
||||
// Container of operations that can be performed over the BLE medium.
|
||||
class BleMedium : public api::BleMedium {
|
||||
public:
|
||||
explicit BleMedium(api::BluetoothAdapter& adapter);
|
||||
~BleMedium() override;
|
||||
|
||||
// Returns true once the Ble advertising has been initiated.
|
||||
bool StartAdvertising(const std::string& service_id,
|
||||
const ByteArray& advertisement_bytes) override
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
bool StopAdvertising(const std::string& service_id) override
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns true once the Ble scanning has been initiated.
|
||||
bool StartScanning(const std::string& service_id,
|
||||
DiscoveredPeripheralCallback callback) override
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns true once Ble scanning for service_id is well and truly
|
||||
// stopped; after this returns, there must be no more invocations of the
|
||||
// DiscoveredPeripheralCallback passed in to StartScanning() for service_id.
|
||||
bool StopScanning(const std::string& service_id) override
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns true once Ble socket connection requests to service_id can be
|
||||
// accepted.
|
||||
bool StartAcceptingConnections(const std::string& service_id,
|
||||
AcceptedConnectionCallback callback)
|
||||
override ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
bool StopAcceptingConnections(const std::string& service_id) override
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Connects to existing remote Ble peripheral.
|
||||
//
|
||||
// On success, returns a new BleSocket.
|
||||
// On error, returns nullptr.
|
||||
std::unique_ptr<api::BleSocket> Connect(
|
||||
api::BlePeripheral& remote_peripheral,
|
||||
const std::string& service_id) override ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
BluetoothAdapter& GetAdapter() { return *adapter_; }
|
||||
|
||||
private:
|
||||
static constexpr int kMaxConcurrentAcceptLoops = 5;
|
||||
|
||||
struct AdvertisingInfo {
|
||||
bool Empty() const { return service_id.empty(); }
|
||||
void Clear() { service_id.clear(); }
|
||||
|
||||
std::string service_id;
|
||||
};
|
||||
|
||||
struct ScanningInfo {
|
||||
bool Empty() const { return service_id.empty(); }
|
||||
void Clear() { service_id.clear(); }
|
||||
|
||||
std::string service_id;
|
||||
};
|
||||
|
||||
absl::Mutex mutex_;
|
||||
BluetoothAdapter* adapter_; // Our device adapter; read-only.
|
||||
|
||||
// A thread pool dedicated to running all the accept loops from
|
||||
// StartAdvertising().
|
||||
MultiThreadExecutor accept_loops_runner_{kMaxConcurrentAcceptLoops};
|
||||
std::atomic_bool acceptance_thread_running_ = false;
|
||||
|
||||
// A thread pool dedicated to wait to complete the accept_loops_runner_.
|
||||
MultiThreadExecutor close_accept_loops_runner_{kMaxConcurrentAcceptLoops};
|
||||
|
||||
// A server socket is established when start advertising.
|
||||
std::unique_ptr<BleServerSocket> server_socket_;
|
||||
AdvertisingInfo advertising_info_ ABSL_GUARDED_BY(mutex_);
|
||||
ScanningInfo scanning_info_ ABSL_GUARDED_BY(mutex_);
|
||||
};
|
||||
|
||||
} // namespace g3
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // PLATFORM_V2_IMPL_G3_BLE_H_
|
||||
@@ -3,21 +3,57 @@
|
||||
#include <string>
|
||||
|
||||
#include "platform_v2/base/medium_environment.h"
|
||||
#include "platform_v2/base/prng.h"
|
||||
#include "platform_v2/impl/g3/bluetooth_classic.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace g3 {
|
||||
|
||||
BlePeripheral::BlePeripheral(BluetoothAdapter* adapter) : adapter_(*adapter) {}
|
||||
|
||||
std::string BlePeripheral::GetName() const { return adapter_.GetName(); }
|
||||
|
||||
ByteArray BlePeripheral::GetAdvertisementBytes(
|
||||
const std::string& service_id) const {
|
||||
return advertisement_bytes_;
|
||||
}
|
||||
|
||||
void BlePeripheral::SetAdvertisementBytes(
|
||||
const std::string& service_id, const ByteArray& advertisement_bytes) {
|
||||
advertisement_bytes_ = advertisement_bytes;
|
||||
}
|
||||
|
||||
BluetoothDevice::BluetoothDevice(BluetoothAdapter* adapter)
|
||||
: adapter_(*adapter) {}
|
||||
|
||||
std::string BluetoothDevice::GetName() const { return adapter_.GetName(); }
|
||||
std::string BluetoothDevice::GetMacAddress() const {
|
||||
return adapter_.GetMacAddress();
|
||||
}
|
||||
|
||||
BluetoothAdapter::BluetoothAdapter() {
|
||||
std::string mac_address;
|
||||
mac_address.resize(6);
|
||||
int64_t raw_mac_addr = Prng().NextInt64();
|
||||
mac_address[0] = static_cast<char>(raw_mac_addr >> 40);
|
||||
mac_address[1] = static_cast<char>(raw_mac_addr >> 32);
|
||||
mac_address[2] = static_cast<char>(raw_mac_addr >> 24);
|
||||
mac_address[3] = static_cast<char>(raw_mac_addr >> 16);
|
||||
mac_address[4] = static_cast<char>(raw_mac_addr >> 8);
|
||||
mac_address[5] = static_cast<char>(raw_mac_addr >> 0);
|
||||
SetMacAddress(mac_address);
|
||||
}
|
||||
|
||||
BluetoothAdapter::~BluetoothAdapter() { SetStatus(Status::kDisabled); }
|
||||
|
||||
void BluetoothAdapter::SetMedium(api::BluetoothClassicMedium* medium) {
|
||||
medium_ = medium;
|
||||
void BluetoothAdapter::SetBluetoothClassicMedium(
|
||||
api::BluetoothClassicMedium* medium) {
|
||||
bluetooth_classic_medium_ = medium;
|
||||
}
|
||||
|
||||
void BluetoothAdapter::SetBleMedium(api::BleMedium* medium) {
|
||||
ble_medium_ = medium;
|
||||
}
|
||||
|
||||
bool BluetoothAdapter::SetStatus(Status status) {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "platform_v2/api/ble.h"
|
||||
#include "platform_v2/api/bluetooth_adapter.h"
|
||||
#include "platform_v2/api/bluetooth_classic.h"
|
||||
#include "platform_v2/impl/g3/single_thread_executor.h"
|
||||
@@ -17,6 +18,28 @@ namespace g3 {
|
||||
// BluetoothDevice and BluetoothAdapter have a mutual dependency.
|
||||
class BluetoothAdapter;
|
||||
|
||||
// Opaque wrapper over a Ble peripheral. Must contain enough data about a
|
||||
// particular Ble device to connect to its GATT server.
|
||||
class BlePeripheral : public api::BlePeripheral {
|
||||
public:
|
||||
~BlePeripheral() override = default;
|
||||
|
||||
std::string GetName() const override;
|
||||
ByteArray GetAdvertisementBytes(const std::string& service_id) const override;
|
||||
void SetAdvertisementBytes(const std::string& service_id,
|
||||
const ByteArray& advertisement_bytes);
|
||||
BluetoothAdapter& GetAdapter() { return adapter_; }
|
||||
|
||||
private:
|
||||
// Only BluetoothAdapter may instantiate BlePeripheral.
|
||||
friend class BluetoothAdapter;
|
||||
|
||||
explicit BlePeripheral(BluetoothAdapter* adapter);
|
||||
|
||||
BluetoothAdapter& adapter_;
|
||||
ByteArray advertisement_bytes_;
|
||||
};
|
||||
|
||||
// https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html.
|
||||
class BluetoothDevice : public api::BluetoothDevice {
|
||||
public:
|
||||
@@ -24,6 +47,7 @@ class BluetoothDevice : public api::BluetoothDevice {
|
||||
|
||||
// https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#getName()
|
||||
std::string GetName() const override;
|
||||
std::string GetMacAddress() const override;
|
||||
BluetoothAdapter& GetAdapter() { return adapter_; }
|
||||
|
||||
private:
|
||||
@@ -41,7 +65,7 @@ class BluetoothAdapter : public api::BluetoothAdapter {
|
||||
using Status = api::BluetoothAdapter::Status;
|
||||
using ScanMode = api::BluetoothAdapter::ScanMode;
|
||||
|
||||
explicit BluetoothAdapter() = default;
|
||||
BluetoothAdapter();
|
||||
~BluetoothAdapter() override;
|
||||
|
||||
// Synchronously sets the status of the BluetoothAdapter to 'status', and
|
||||
@@ -68,15 +92,30 @@ class BluetoothAdapter : public api::BluetoothAdapter {
|
||||
// https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#setName(java.lang.String)
|
||||
bool SetName(absl::string_view name) override ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns BT MAC address assigned to this adapter.
|
||||
std::string GetMacAddress() const override { return mac_address_; }
|
||||
|
||||
BluetoothDevice& GetDevice() { return device_; }
|
||||
|
||||
void SetMedium(api::BluetoothClassicMedium* medium);
|
||||
api::BluetoothClassicMedium* GetMedium() { return medium_; }
|
||||
void SetBluetoothClassicMedium(api::BluetoothClassicMedium* medium);
|
||||
api::BluetoothClassicMedium* GetBluetoothClassicMedium() {
|
||||
return bluetooth_classic_medium_;
|
||||
}
|
||||
|
||||
BlePeripheral& GetPeripheral() { return peripheral_; }
|
||||
|
||||
void SetBleMedium(api::BleMedium* medium);
|
||||
api::BleMedium* GetBleMedium() { return ble_medium_; }
|
||||
|
||||
void SetMacAddress(std::string& mac_address) { mac_address_ = mac_address; }
|
||||
|
||||
private:
|
||||
mutable absl::Mutex mutex_;
|
||||
BluetoothDevice device_{this};
|
||||
api::BluetoothClassicMedium* medium_ = nullptr;
|
||||
BlePeripheral peripheral_{this};
|
||||
api::BluetoothClassicMedium* bluetooth_classic_medium_ = nullptr;
|
||||
api::BleMedium* ble_medium_ = nullptr;
|
||||
std::string mac_address_;
|
||||
ScanMode mode_ ABSL_GUARDED_BY(mutex_) = ScanMode::kNone;
|
||||
std::string name_ ABSL_GUARDED_BY(mutex_) = "unknown G3 BT device";
|
||||
bool enabled_ ABSL_GUARDED_BY(mutex_) = false;
|
||||
|
||||
@@ -34,9 +34,7 @@ bool BluetoothSocket::IsClosed() const {
|
||||
return closed_;
|
||||
}
|
||||
|
||||
bool BluetoothSocket::IsConnectedLocked() const {
|
||||
return input_ != nullptr;
|
||||
}
|
||||
bool BluetoothSocket::IsConnectedLocked() const { return input_ != nullptr; }
|
||||
|
||||
InputStream& BluetoothSocket::GetInputStream() {
|
||||
auto* remote_socket = GetRemoteSocket();
|
||||
@@ -163,13 +161,13 @@ Exception BluetoothServerSocket::DoClose() {
|
||||
BluetoothClassicMedium::BluetoothClassicMedium(api::BluetoothAdapter& adapter)
|
||||
// TODO(apolyudov): implement and use downcast<> with static assertions.
|
||||
: adapter_(static_cast<BluetoothAdapter*>(&adapter)) {
|
||||
adapter_->SetMedium(this);
|
||||
adapter_->SetBluetoothClassicMedium(this);
|
||||
auto& env = MediumEnvironment::Instance();
|
||||
env.RegisterBluetoothMedium(*this, GetAdapter());
|
||||
}
|
||||
|
||||
BluetoothClassicMedium::~BluetoothClassicMedium() {
|
||||
adapter_->SetMedium(nullptr);
|
||||
adapter_->SetBluetoothClassicMedium(nullptr);
|
||||
auto& env = MediumEnvironment::Instance();
|
||||
env.UnregisterBluetoothMedium(*this);
|
||||
}
|
||||
@@ -193,7 +191,8 @@ std::unique_ptr<api::BluetoothSocket> BluetoothClassicMedium::ConnectToService(
|
||||
this, &GetAdapter(), &GetAdapter().GetDevice());
|
||||
// First, find an instance of remote medium, that exposed this device.
|
||||
auto& adapter = static_cast<BluetoothDevice&>(remote_device).GetAdapter();
|
||||
auto* medium = static_cast<BluetoothClassicMedium*>(adapter.GetMedium());
|
||||
auto* medium =
|
||||
static_cast<BluetoothClassicMedium*>(adapter.GetBluetoothClassicMedium());
|
||||
|
||||
if (!medium) return {}; // Adapter is not bound to medium. Bail out.
|
||||
|
||||
@@ -241,6 +240,12 @@ BluetoothClassicMedium::ListenForService(const std::string& service_name,
|
||||
return socket;
|
||||
}
|
||||
|
||||
api::BluetoothDevice* BluetoothClassicMedium::FindRemoteDevice(
|
||||
const std::string& mac_address) {
|
||||
auto& env = MediumEnvironment::Instance();
|
||||
return env.FindBluetoothDevice(mac_address);
|
||||
}
|
||||
|
||||
} // namespace g3
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
@@ -82,7 +82,7 @@ class BluetoothSocket : public api::BluetoothSocket {
|
||||
// Output pipe is initialized by constructor, it remains always valid, until
|
||||
// it is closed. it represents output part of a local socket. Input part of a
|
||||
// local socket comes from the peer socket, after connection.
|
||||
std::shared_ptr<Pipe> output_ {new Pipe};
|
||||
std::shared_ptr<Pipe> output_{new Pipe};
|
||||
std::shared_ptr<Pipe> input_;
|
||||
mutable absl::Mutex mutex_;
|
||||
BluetoothAdapter* adapter_ = nullptr; // Our Adapter. Read only.
|
||||
@@ -207,6 +207,9 @@ class BluetoothClassicMedium : public api::BluetoothClassicMedium {
|
||||
const std::string& service_name, const std::string& service_uuid) override
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
api::BluetoothDevice* FindRemoteDevice(
|
||||
const std::string& mac_address) override;
|
||||
|
||||
private:
|
||||
absl::Mutex mutex_;
|
||||
BluetoothAdapter* adapter_; // Our device adapter; read-only.
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
#include "platform_v2/api/atomic_boolean.h"
|
||||
#include "platform_v2/api/atomic_reference.h"
|
||||
#include "platform_v2/api/ble.h"
|
||||
#include "platform_v2/api/ble_v2.h"
|
||||
#include "platform_v2/api/bluetooth_adapter.h"
|
||||
#include "platform_v2/api/bluetooth_classic.h"
|
||||
@@ -21,6 +20,7 @@
|
||||
#include "platform_v2/base/medium_environment.h"
|
||||
#include "platform_v2/impl/g3/atomic_boolean.h"
|
||||
#include "platform_v2/impl/g3/atomic_reference.h"
|
||||
#include "platform_v2/impl/g3/ble.h"
|
||||
#include "platform_v2/impl/g3/bluetooth_adapter.h"
|
||||
#include "platform_v2/impl/g3/bluetooth_classic.h"
|
||||
#include "platform_v2/impl/g3/condition_variable.h"
|
||||
@@ -112,7 +112,7 @@ ImplementationPlatform::CreateBluetoothClassicMedium(
|
||||
|
||||
std::unique_ptr<BleMedium> ImplementationPlatform::CreateBleMedium(
|
||||
api::BluetoothAdapter& adapter) {
|
||||
return std::unique_ptr<BleMedium>();
|
||||
return absl::make_unique<g3::BleMedium>(adapter);
|
||||
}
|
||||
|
||||
std::unique_ptr<ble_v2::BleMedium> ImplementationPlatform::CreateBleV2Medium(
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "platform_v2/api/wifi_lan.h"
|
||||
#include "platform_v2/base/logging.h"
|
||||
#include "platform_v2/base/medium_environment.h"
|
||||
#include "platform_v2/base/prng.h"
|
||||
#include "absl/synchronization/mutex.h"
|
||||
|
||||
namespace location {
|
||||
@@ -85,7 +86,8 @@ OutputStream& WifiLanSocket::GetLocalOutputStream() {
|
||||
return output_->GetOutputStream();
|
||||
}
|
||||
|
||||
std::unique_ptr<api::WifiLanSocket> WifiLanServerSocket::Accept() {
|
||||
std::unique_ptr<api::WifiLanSocket> WifiLanServerSocket::Accept(
|
||||
WifiLanService* service) {
|
||||
absl::MutexLock lock(&mutex_);
|
||||
if (closed_) return {};
|
||||
while (pending_sockets_.empty()) {
|
||||
@@ -96,7 +98,7 @@ std::unique_ptr<api::WifiLanSocket> WifiLanServerSocket::Accept() {
|
||||
auto* remote_socket =
|
||||
pending_sockets_.extract(pending_sockets_.begin()).value();
|
||||
CHECK(remote_socket);
|
||||
auto local_socket = std::make_unique<WifiLanSocket>();
|
||||
auto local_socket = std::make_unique<WifiLanSocket>(service);
|
||||
local_socket->Connect(*remote_socket);
|
||||
remote_socket->Connect(*local_socket);
|
||||
cond_.SignalAll();
|
||||
@@ -155,6 +157,15 @@ Exception WifiLanServerSocket::DoClose() {
|
||||
|
||||
WifiLanMedium::WifiLanMedium() {
|
||||
service_.SetMedium(this);
|
||||
std::string ip_address;
|
||||
ip_address.resize(4);
|
||||
uint32_t raw_ip_addr = Prng().NextUint32();
|
||||
uint16_t port = Prng().NextUint32();
|
||||
ip_address[0] = static_cast<char>(raw_ip_addr >> 24);
|
||||
ip_address[1] = static_cast<char>(raw_ip_addr >> 16);
|
||||
ip_address[2] = static_cast<char>(raw_ip_addr >> 8);
|
||||
ip_address[3] = static_cast<char>(raw_ip_addr >> 0);
|
||||
service_.SetServiceAddress(ip_address, port);
|
||||
auto& env = MediumEnvironment::Instance();
|
||||
env.RegisterWifiLanMedium(*this);
|
||||
}
|
||||
@@ -167,8 +178,7 @@ WifiLanMedium::~WifiLanMedium() {
|
||||
StopAdvertising(advertising_info_.service_id);
|
||||
StopDiscovery(discovering_info_.service_id);
|
||||
|
||||
NEARBY_LOG(INFO,
|
||||
"WifiLanMedium dtor advertising_accept_thread_running_ = %d",
|
||||
NEARBY_LOG(INFO, "WifiLanMedium dtor advertising_accept_thread_running_ = %d",
|
||||
acceptance_thread_running_.load());
|
||||
// If acceptance thread is still running, wait to finish.
|
||||
if (acceptance_thread_running_) {
|
||||
@@ -186,6 +196,7 @@ bool WifiLanMedium::StartAdvertising(const std::string& service_id,
|
||||
"G3 WifiLan StartAdvertising: service_id=%s, service_info_name=%s",
|
||||
service_id.c_str(), service_info_name.c_str());
|
||||
auto& env = MediumEnvironment::Instance();
|
||||
service_.SetName(service_info_name);
|
||||
env.UpdateWifiLanMediumForAdvertising(*this, service_, service_id, true);
|
||||
|
||||
absl::MutexLock lock(&mutex_);
|
||||
@@ -196,10 +207,10 @@ bool WifiLanMedium::StartAdvertising(const std::string& service_id,
|
||||
accept_loops_runner_.Execute([&env, this, service_id]() mutable {
|
||||
if (!accept_loops_runner_.InShutdown()) {
|
||||
while (true) {
|
||||
auto client_socket = server_socket_->Accept();
|
||||
auto client_socket = server_socket_->Accept(&service_);
|
||||
if (client_socket == nullptr) break;
|
||||
env.CallWifiLanAcceptedConnectionCallback(*this, *client_socket,
|
||||
service_id);
|
||||
env.CallWifiLanAcceptedConnectionCallback(
|
||||
*this, *(client_socket.release()), service_id);
|
||||
}
|
||||
}
|
||||
acceptance_thread_running_.exchange(false);
|
||||
@@ -227,8 +238,8 @@ bool WifiLanMedium::StopAdvertising(const std::string& service_id) {
|
||||
accept_loops_runner_.Shutdown();
|
||||
if (server_socket_ == nullptr) {
|
||||
NEARBY_LOGS(ERROR) << "G3 WifiLan StopAdvertising: failed to find WifiLan "
|
||||
"Server socket: service_id="
|
||||
<< service_id;
|
||||
"Server socket: service_id="
|
||||
<< service_id;
|
||||
// Fall through for server socket not found.
|
||||
return true;
|
||||
}
|
||||
@@ -296,8 +307,11 @@ bool WifiLanMedium::StopAcceptingConnections(const std::string& service_id) {
|
||||
|
||||
std::unique_ptr<api::WifiLanSocket> WifiLanMedium::Connect(
|
||||
api::WifiLanService& remote_service, const std::string& service_id) {
|
||||
NEARBY_LOG(INFO, "G3 WifiLan Connect: medium=%p, service=%p, service_id=%s",
|
||||
this, &service_, service_id.c_str());
|
||||
NEARBY_LOG(INFO,
|
||||
"G3 WifiLan Connect: medium=%p, service=%p, service_info_name=%s, "
|
||||
"service_id=%s",
|
||||
this, &service_, remote_service.GetName().c_str(),
|
||||
service_id.c_str());
|
||||
// First, find an instance of remote medium, that exposed this service.
|
||||
auto* medium = static_cast<WifiLanService&>(remote_service).GetMedium();
|
||||
|
||||
@@ -305,8 +319,10 @@ std::unique_ptr<api::WifiLanSocket> WifiLanMedium::Connect(
|
||||
|
||||
WifiLanServerSocket* remote_server_socket = nullptr;
|
||||
NEARBY_LOG(INFO,
|
||||
"G3 WifiLan Connect [peer]: medium=%p, service=%p, service_id=%s",
|
||||
medium, &remote_service, service_id.c_str());
|
||||
"G3 WifiLan Connect [peer]: medium=%p, service=%p, "
|
||||
"service_info_name=%s, service_id=%s",
|
||||
medium, &remote_service, remote_service.GetName().c_str(),
|
||||
service_id.c_str());
|
||||
// Then, find our server socket context in this medium.
|
||||
{
|
||||
absl::MutexLock medium_lock(&medium->mutex_);
|
||||
@@ -321,7 +337,8 @@ std::unique_ptr<api::WifiLanSocket> WifiLanMedium::Connect(
|
||||
}
|
||||
}
|
||||
|
||||
auto socket = std::make_unique<WifiLanSocket>();
|
||||
WifiLanService service = static_cast<WifiLanService&>(remote_service);
|
||||
auto socket = std::make_unique<WifiLanSocket>(&service);
|
||||
// Finally, Request to connect to this socket.
|
||||
if (!remote_server_socket->Connect(*socket)) {
|
||||
NEARBY_LOG(ERROR,
|
||||
@@ -335,6 +352,12 @@ std::unique_ptr<api::WifiLanSocket> WifiLanMedium::Connect(
|
||||
return socket;
|
||||
}
|
||||
|
||||
api::WifiLanService* WifiLanMedium::FindRemoteService(
|
||||
const std::string& ip_address, int port) {
|
||||
auto& env = MediumEnvironment::Instance();
|
||||
return env.FindWifiLanService(ip_address, port);
|
||||
}
|
||||
|
||||
} // namespace g3
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "platform_v2/api/wifi_lan.h"
|
||||
#include "platform_v2/base/byte_array.h"
|
||||
@@ -32,13 +33,23 @@ class WifiLanService : public api::WifiLanService {
|
||||
service_info_name_ = std::move(service_info_name);
|
||||
}
|
||||
std::string GetName() const override { return service_info_name_; }
|
||||
std::pair<std::string, int> GetServiceAddress() const override {
|
||||
return std::make_pair(ip_address_, port_);
|
||||
}
|
||||
|
||||
void SetMedium(WifiLanMedium* medium) { medium_ = medium; }
|
||||
WifiLanMedium* GetMedium() { return medium_; }
|
||||
|
||||
void SetServiceAddress(const std::string& ip_address, int port) {
|
||||
ip_address_ = ip_address;
|
||||
port_ = port;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string service_info_name_;
|
||||
WifiLanMedium* medium_ = nullptr;
|
||||
std::string ip_address_;
|
||||
int port_;
|
||||
};
|
||||
|
||||
class WifiLanSocket : public api::WifiLanSocket {
|
||||
@@ -94,7 +105,7 @@ class WifiLanSocket : public api::WifiLanSocket {
|
||||
// Output pipe is initialized by constructor, it remains always valid, until
|
||||
// it is closed. it represents output part of a local socket. Input part of a
|
||||
// local socket comes from the peer socket, after connection.
|
||||
std::shared_ptr<Pipe> output_ {new Pipe};
|
||||
std::shared_ptr<Pipe> output_{new Pipe};
|
||||
std::shared_ptr<Pipe> input_;
|
||||
mutable absl::Mutex mutex_;
|
||||
WifiLanService* service_;
|
||||
@@ -116,7 +127,8 @@ class WifiLanServerSocket {
|
||||
// Called by the server side of a connection.
|
||||
// Returns WifiLanSocket to the server side.
|
||||
// If not null, returned socket is connected to its remote (client-side) peer.
|
||||
std::unique_ptr<api::WifiLanSocket> Accept() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
std::unique_ptr<api::WifiLanSocket> Accept(WifiLanService* service)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Blocks until either:
|
||||
// - connection is available, or
|
||||
@@ -186,6 +198,9 @@ class WifiLanMedium : public api::WifiLanMedium {
|
||||
api::WifiLanService& remote_service,
|
||||
const std::string& service_id) override ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
api::WifiLanService* FindRemoteService(const std::string& ip_address,
|
||||
int port) override;
|
||||
|
||||
private:
|
||||
static constexpr int kMaxConcurrentAcceptLoops = 5;
|
||||
|
||||
|
||||
@@ -20,9 +20,7 @@ cc_library(
|
||||
hdrs = [
|
||||
"posix_condition_variable.h",
|
||||
],
|
||||
visibility = [
|
||||
"//platform_v2/impl:__subpackages__",
|
||||
],
|
||||
visibility = ["//visibility:private"],
|
||||
deps = [
|
||||
":posix_mutex",
|
||||
"//platform_v2/api:types",
|
||||
|
||||
@@ -45,10 +45,12 @@ cc_library(
|
||||
cc_library(
|
||||
name = "comm",
|
||||
srcs = [
|
||||
"ble.cc",
|
||||
"bluetooth_classic.cc",
|
||||
"wifi_lan.cc",
|
||||
],
|
||||
hdrs = [
|
||||
"ble.h",
|
||||
"bluetooth_adapter.h",
|
||||
"bluetooth_classic.h",
|
||||
"webrtc.h",
|
||||
@@ -91,6 +93,7 @@ cc_test(
|
||||
srcs = [
|
||||
"atomic_boolean_test.cc",
|
||||
"atomic_reference_test.cc",
|
||||
"ble_test.cc",
|
||||
"bluetooth_adapter_test.cc",
|
||||
"bluetooth_classic_test.cc",
|
||||
"cancelable_alarm_test.cc",
|
||||
@@ -115,6 +118,7 @@ cc_test(
|
||||
"//platform_v2/base:test_util",
|
||||
"//platform_v2/impl/g3", # build_cleaner: keep
|
||||
"//testing/base/public:gunit_main",
|
||||
"//absl/strings",
|
||||
"//absl/synchronization",
|
||||
"//absl/time",
|
||||
],
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
#include "platform_v2/public/ble.h"
|
||||
|
||||
#include "platform_v2/public/logging.h"
|
||||
#include "platform_v2/public/mutex_lock.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
|
||||
bool BleMedium::StartAdvertising(const std::string& service_id,
|
||||
const ByteArray& advertisement_bytes) {
|
||||
return impl_->StartAdvertising(service_id, advertisement_bytes);
|
||||
}
|
||||
|
||||
bool BleMedium::StopAdvertising(const std::string& service_id) {
|
||||
return impl_->StopAdvertising(service_id);
|
||||
}
|
||||
|
||||
bool BleMedium::StartScanning(const std::string& service_id,
|
||||
DiscoveredPeripheralCallback callback) {
|
||||
{
|
||||
MutexLock lock(&mutex_);
|
||||
discovered_peripheral_callback_ = std::move(callback);
|
||||
peripherals_.clear();
|
||||
}
|
||||
return impl_->StartScanning(
|
||||
service_id,
|
||||
{
|
||||
.peripheral_discovered_cb =
|
||||
[this](api::BlePeripheral& peripheral,
|
||||
const std::string& service_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
auto pair = peripherals_.emplace(
|
||||
&peripheral, absl::make_unique<ScanningInfo>());
|
||||
auto& context = *pair.first->second;
|
||||
if (!pair.second) {
|
||||
NEARBY_LOG(INFO,
|
||||
"Discovering (again) peripheral=%p, impl=%p, "
|
||||
"peripheral name=%s",
|
||||
&context.peripheral, &peripheral,
|
||||
peripheral.GetName().c_str());
|
||||
} else {
|
||||
context.peripheral = BlePeripheral(&peripheral);
|
||||
NEARBY_LOG(INFO,
|
||||
"Discovering peripheral=%p, impl=%p, "
|
||||
"peripheral name=%s",
|
||||
&context.peripheral, &peripheral,
|
||||
peripheral.GetName().c_str());
|
||||
discovered_peripheral_callback_.peripheral_discovered_cb(
|
||||
context.peripheral, service_id);
|
||||
}
|
||||
},
|
||||
.peripheral_lost_cb =
|
||||
[this](api::BlePeripheral& peripheral,
|
||||
const std::string& service_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
if (peripherals_.empty()) return;
|
||||
auto context = peripherals_.find(&peripheral);
|
||||
if (context == peripherals_.end()) return;
|
||||
NEARBY_LOG(INFO, "Removing peripheral=%p, impl=%p",
|
||||
&(context->second->peripheral), &peripheral);
|
||||
discovered_peripheral_callback_.peripheral_lost_cb(
|
||||
context->second->peripheral, service_id);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
bool BleMedium::StopScanning(const std::string& service_id) {
|
||||
{
|
||||
MutexLock lock(&mutex_);
|
||||
discovered_peripheral_callback_ = {};
|
||||
peripherals_.clear();
|
||||
NEARBY_LOG(INFO, "Ble Scanning disabled: impl=%p", &GetImpl());
|
||||
}
|
||||
return impl_->StopScanning(service_id);
|
||||
}
|
||||
|
||||
bool BleMedium::StartAcceptingConnections(const std::string& service_id,
|
||||
AcceptedConnectionCallback callback) {
|
||||
{
|
||||
MutexLock lock(&mutex_);
|
||||
accepted_connection_callback_ = std::move(callback);
|
||||
}
|
||||
return impl_->StartAcceptingConnections(
|
||||
service_id,
|
||||
{
|
||||
.accepted_cb =
|
||||
[this](api::BleSocket& socket, const std::string& service_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
auto pair = sockets_.emplace(
|
||||
&socket, absl::make_unique<AcceptedConnectionInfo>());
|
||||
auto& context = *pair.first->second;
|
||||
if (!pair.second) {
|
||||
NEARBY_LOG(INFO, "Accepting (again) socket=%p, impl=%p",
|
||||
&context.socket, &socket);
|
||||
} else {
|
||||
context.socket = BleSocket(&socket);
|
||||
NEARBY_LOG(INFO, "Accepting socket=%p, impl=%p",
|
||||
&context.socket, &socket);
|
||||
}
|
||||
accepted_connection_callback_.accepted_cb(context.socket,
|
||||
service_id);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
bool BleMedium::StopAcceptingConnections(const std::string& service_id) {
|
||||
{
|
||||
MutexLock lock(&mutex_);
|
||||
accepted_connection_callback_ = {};
|
||||
sockets_.clear();
|
||||
NEARBY_LOG(INFO, "Ble accepted connection disabled: impl=%p", &GetImpl());
|
||||
}
|
||||
return impl_->StopAcceptingConnections(service_id);
|
||||
}
|
||||
|
||||
BleSocket BleMedium::Connect(BlePeripheral& peripheral,
|
||||
const std::string& service_id) {
|
||||
{
|
||||
MutexLock lock(&mutex_);
|
||||
NEARBY_LOG(INFO, "BleMedium::Connect: peripheral=%p [impl=%p]", &peripheral,
|
||||
&peripheral.GetImpl());
|
||||
}
|
||||
return BleSocket(impl_->Connect(peripheral.GetImpl(), service_id));
|
||||
}
|
||||
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -0,0 +1,146 @@
|
||||
#ifndef PLATFORM_V2_PUBLIC_BLE_H_
|
||||
#define PLATFORM_V2_PUBLIC_BLE_H_
|
||||
|
||||
#include "platform_v2/api/ble.h"
|
||||
#include "platform_v2/api/platform.h"
|
||||
#include "platform_v2/base/byte_array.h"
|
||||
#include "platform_v2/base/input_stream.h"
|
||||
#include "platform_v2/base/output_stream.h"
|
||||
#include "platform_v2/public/bluetooth_adapter.h"
|
||||
#include "platform_v2/public/mutex.h"
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
|
||||
class BleSocket final {
|
||||
public:
|
||||
BleSocket() = default;
|
||||
BleSocket(const BleSocket&) = default;
|
||||
BleSocket& operator=(const BleSocket&) = default;
|
||||
explicit BleSocket(api::BleSocket* socket) : impl_(socket) {}
|
||||
explicit BleSocket(std::unique_ptr<api::BleSocket> socket)
|
||||
: impl_(socket.release()) {}
|
||||
~BleSocket() = default;
|
||||
|
||||
// Returns the InputStream of the BleSocket.
|
||||
// On error, returned stream will report Exception::kIo on any operation.
|
||||
//
|
||||
// The returned object is not owned by the caller, and can be invalidated once
|
||||
// the BleSocket object is destroyed.
|
||||
InputStream& GetInputStream() { return impl_->GetInputStream(); }
|
||||
|
||||
// Returns the OutputStream of the BleSocket.
|
||||
// On error, returned stream will report Exception::kIo on any operation.
|
||||
//
|
||||
// The returned object is not owned by the caller, and can be invalidated once
|
||||
// the BleSocket object is destroyed.
|
||||
OutputStream& GetOutputStream() { return impl_->GetOutputStream(); }
|
||||
|
||||
// Returns Exception::kIo on error, Exception::kSuccess otherwise.
|
||||
Exception Close() { return impl_->Close(); }
|
||||
|
||||
BlePeripheral GetRemotePeripheral() {
|
||||
return BlePeripheral(impl_->GetRemotePeripheral());
|
||||
}
|
||||
|
||||
// Returns true if a socket is usable. If this method returns false,
|
||||
// it is not safe to call any other method.
|
||||
// NOTE(socket validity):
|
||||
// Socket created by a default public constructor is not valid, because
|
||||
// it is missing platform implementation.
|
||||
// The only way to obtain a valid socket is through connection, such as
|
||||
// an object returned by BleMedium::Connect
|
||||
// These methods may also return an invalid socket if connection failed for
|
||||
// any reason.
|
||||
bool IsValid() const { return impl_ != nullptr; }
|
||||
|
||||
// Returns reference to platform implementation.
|
||||
// This is used to communicate with platform code, and for debugging purposes.
|
||||
// Returned reference will remain valid for while BleSocket object is
|
||||
// itself valid. Typically BleSocket lifetime matches duration of the
|
||||
// connection, and is controlled by end user, since they hold the instance.
|
||||
api::BleSocket& GetImpl() { return *impl_; }
|
||||
|
||||
private:
|
||||
std::shared_ptr<api::BleSocket> impl_;
|
||||
};
|
||||
|
||||
// Container of operations that can be performed over the BLE medium.
|
||||
class BleMedium final {
|
||||
public:
|
||||
using Platform = api::ImplementationPlatform;
|
||||
struct DiscoveredPeripheralCallback {
|
||||
std::function<void(BlePeripheral& peripheral,
|
||||
const std::string& service_id)>
|
||||
peripheral_discovered_cb =
|
||||
DefaultCallback<BlePeripheral&, const std::string&>();
|
||||
std::function<void(BlePeripheral& peripheral,
|
||||
const std::string& service_id)>
|
||||
peripheral_lost_cb =
|
||||
DefaultCallback<BlePeripheral&, const std::string&>();
|
||||
};
|
||||
struct ScanningInfo {
|
||||
BlePeripheral peripheral;
|
||||
};
|
||||
|
||||
struct AcceptedConnectionCallback {
|
||||
std::function<void(BleSocket& socket, const std::string& service_id)>
|
||||
accepted_cb = DefaultCallback<BleSocket&, const std::string&>();
|
||||
};
|
||||
struct AcceptedConnectionInfo {
|
||||
BleSocket socket;
|
||||
};
|
||||
|
||||
explicit BleMedium(BluetoothAdapter& adapter)
|
||||
: impl_(Platform::CreateBleMedium(adapter.GetImpl())),
|
||||
adapter_(adapter) {}
|
||||
~BleMedium() = default;
|
||||
|
||||
// Returns true once the BLE advertising has been initiated.
|
||||
bool StartAdvertising(const std::string& service_id,
|
||||
const ByteArray& advertisement_bytes);
|
||||
bool StopAdvertising(const std::string& service_id);
|
||||
|
||||
// Returns true once the BLE scan has been initiated.
|
||||
bool StartScanning(const std::string& service_id,
|
||||
DiscoveredPeripheralCallback callback);
|
||||
|
||||
// Returns true once BLE scanning for service_id is well and truly stopped;
|
||||
// after this returns, there must be no more invocations of the
|
||||
// DiscoveredPeripheralCallback passed in to StartScanning() for service_id.
|
||||
bool StopScanning(const std::string& service_id);
|
||||
|
||||
// Returns true once BLE socket connection requests to service_id can be
|
||||
// accepted.
|
||||
bool StartAcceptingConnections(const std::string& service_id,
|
||||
AcceptedConnectionCallback callback);
|
||||
bool StopAcceptingConnections(const std::string& service_id);
|
||||
|
||||
// Returns a new BleSocket. On Success, BleSocket::IsValid()
|
||||
// returns true.
|
||||
BleSocket Connect(BlePeripheral& peripheral, const std::string& service_id);
|
||||
|
||||
bool IsValid() const { return impl_ != nullptr; }
|
||||
|
||||
api::BleMedium& GetImpl() { return *impl_; }
|
||||
BluetoothAdapter& GetAdapter() { return adapter_; }
|
||||
|
||||
private:
|
||||
Mutex mutex_;
|
||||
std::unique_ptr<api::BleMedium> impl_;
|
||||
BluetoothAdapter& adapter_;
|
||||
absl::flat_hash_map<api::BlePeripheral*, std::unique_ptr<ScanningInfo>>
|
||||
peripherals_ ABSL_GUARDED_BY(mutex_);
|
||||
absl::flat_hash_map<api::BleSocket*, std::unique_ptr<AcceptedConnectionInfo>>
|
||||
sockets_ ABSL_GUARDED_BY(mutex_);
|
||||
DiscoveredPeripheralCallback discovered_peripheral_callback_
|
||||
ABSL_GUARDED_BY(mutex_);
|
||||
AcceptedConnectionCallback accepted_connection_callback_
|
||||
ABSL_GUARDED_BY(mutex_);
|
||||
};
|
||||
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
#endif // PLATFORM_V2_PUBLIC_BLE_H_
|
||||
@@ -0,0 +1,189 @@
|
||||
#include "platform_v2/public/ble.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "platform_v2/base/medium_environment.h"
|
||||
#include "platform_v2/public/count_down_latch.h"
|
||||
#include "platform_v2/public/logging.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace {
|
||||
|
||||
constexpr absl::Duration kWaitDuration = absl::Milliseconds(1000);
|
||||
constexpr absl::string_view kServiceID{"com.google.location.nearby.apps.test"};
|
||||
constexpr absl::string_view kAdvertisementString{"\x0a\x0b\x0c\x0d"};
|
||||
|
||||
class BleMediumTest : public ::testing::Test {
|
||||
protected:
|
||||
using DiscoveredPeripheralCallback = BleMedium::DiscoveredPeripheralCallback;
|
||||
using AcceptedConnectionCallback = BleMedium::AcceptedConnectionCallback;
|
||||
|
||||
BleMediumTest() { env_.Stop(); }
|
||||
|
||||
MediumEnvironment& env_{MediumEnvironment::Instance()};
|
||||
};
|
||||
|
||||
TEST_F(BleMediumTest, ConstructorDestructorWorks) {
|
||||
env_.Start();
|
||||
BluetoothAdapter adapter_a_;
|
||||
BluetoothAdapter adapter_b_;
|
||||
BleMedium ble_a{adapter_a_};
|
||||
BleMedium ble_b{adapter_b_};
|
||||
|
||||
// Make sure we can create functional mediums.
|
||||
ASSERT_TRUE(ble_a.IsValid());
|
||||
ASSERT_TRUE(ble_b.IsValid());
|
||||
|
||||
// Make sure we can create 2 distinct mediums.
|
||||
EXPECT_NE(&ble_a.GetImpl(), &ble_b.GetImpl());
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
TEST_F(BleMediumTest, CanStartAdvertising) {
|
||||
env_.Start();
|
||||
BluetoothAdapter adapter_a_;
|
||||
BluetoothAdapter adapter_b_;
|
||||
BleMedium ble_a{adapter_a_};
|
||||
BleMedium ble_b{adapter_b_};
|
||||
std::string service_id(kServiceID);
|
||||
ByteArray advertisement_bytes{std::string(kAdvertisementString)};
|
||||
CountDownLatch found_latch(1);
|
||||
|
||||
ble_a.StartAdvertising(service_id, advertisement_bytes);
|
||||
|
||||
EXPECT_TRUE(ble_b.StartScanning(
|
||||
service_id, DiscoveredPeripheralCallback{
|
||||
.peripheral_discovered_cb =
|
||||
[&found_latch](BlePeripheral& peripheral,
|
||||
const std::string& service_id) {
|
||||
found_latch.CountDown();
|
||||
},
|
||||
}));
|
||||
EXPECT_TRUE(found_latch.Await(kWaitDuration).result());
|
||||
EXPECT_TRUE(ble_a.StopAdvertising(service_id));
|
||||
EXPECT_TRUE(ble_b.StopScanning(service_id));
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
TEST_F(BleMediumTest, CanStartScanning) {
|
||||
env_.Start();
|
||||
BluetoothAdapter adapter_a_;
|
||||
BluetoothAdapter adapter_b_;
|
||||
BleMedium ble_a{adapter_a_};
|
||||
BleMedium ble_b{adapter_b_};
|
||||
std::string service_id(kServiceID);
|
||||
ByteArray advertisement_bytes{std::string(kAdvertisementString)};
|
||||
CountDownLatch found_latch(1);
|
||||
CountDownLatch lost_latch(1);
|
||||
|
||||
ble_a.StartScanning(service_id,
|
||||
DiscoveredPeripheralCallback{
|
||||
.peripheral_discovered_cb =
|
||||
[&found_latch](BlePeripheral& peripheral,
|
||||
const std::string& service_id) {
|
||||
found_latch.CountDown();
|
||||
},
|
||||
.peripheral_lost_cb =
|
||||
[&lost_latch](BlePeripheral& peripheral,
|
||||
const std::string& service_id) {
|
||||
lost_latch.CountDown();
|
||||
},
|
||||
});
|
||||
EXPECT_TRUE(ble_b.StartAdvertising(service_id, advertisement_bytes));
|
||||
EXPECT_TRUE(found_latch.Await(kWaitDuration).result());
|
||||
EXPECT_TRUE(ble_b.StopAdvertising(service_id));
|
||||
EXPECT_TRUE(lost_latch.Await(kWaitDuration).result());
|
||||
EXPECT_TRUE(ble_a.StopScanning(service_id));
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
TEST_F(BleMediumTest, CanStopDiscovery) {
|
||||
env_.Start();
|
||||
BluetoothAdapter adapter_a_;
|
||||
BluetoothAdapter adapter_b_;
|
||||
BleMedium ble_a{adapter_a_};
|
||||
BleMedium ble_b{adapter_b_};
|
||||
std::string service_id(kServiceID);
|
||||
ByteArray advertisement_bytes{std::string(kAdvertisementString)};
|
||||
CountDownLatch found_latch(1);
|
||||
CountDownLatch lost_latch(1);
|
||||
|
||||
ble_a.StartScanning(service_id,
|
||||
DiscoveredPeripheralCallback{
|
||||
.peripheral_discovered_cb =
|
||||
[&found_latch](BlePeripheral& peripheral,
|
||||
const std::string& service_id) {
|
||||
found_latch.CountDown();
|
||||
},
|
||||
.peripheral_lost_cb =
|
||||
[&lost_latch](BlePeripheral& peripheral,
|
||||
const std::string& service_id) {
|
||||
lost_latch.CountDown();
|
||||
},
|
||||
});
|
||||
EXPECT_TRUE(ble_b.StartAdvertising(service_id, advertisement_bytes));
|
||||
EXPECT_TRUE(found_latch.Await(kWaitDuration).result());
|
||||
EXPECT_TRUE(ble_a.StopScanning(service_id));
|
||||
EXPECT_TRUE(ble_b.StopAdvertising(service_id));
|
||||
EXPECT_FALSE(lost_latch.Await(kWaitDuration).result());
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
TEST_F(BleMediumTest, CanStartAcceptingConnectionsAndConnect) {
|
||||
env_.Start();
|
||||
BluetoothAdapter adapter_a_;
|
||||
BluetoothAdapter adapter_b_;
|
||||
BleMedium ble_a{adapter_a_};
|
||||
BleMedium ble_b{adapter_b_};
|
||||
std::string service_id(kServiceID);
|
||||
ByteArray advertisement_bytes{std::string(kAdvertisementString)};
|
||||
CountDownLatch found_latch(1);
|
||||
CountDownLatch accepted_latch(1);
|
||||
|
||||
BlePeripheral* discovered_peripheral = nullptr;
|
||||
ble_a.StartScanning(
|
||||
service_id,
|
||||
DiscoveredPeripheralCallback{
|
||||
.peripheral_discovered_cb =
|
||||
[&found_latch, &discovered_peripheral](
|
||||
BlePeripheral& peripheral, const std::string& service_id) {
|
||||
NEARBY_LOG(INFO, "Peripheral discovered: %s, %p",
|
||||
peripheral.GetName().c_str(), &peripheral);
|
||||
discovered_peripheral = &peripheral;
|
||||
found_latch.CountDown();
|
||||
},
|
||||
});
|
||||
ble_b.StartAdvertising(service_id, advertisement_bytes);
|
||||
ble_b.StartAcceptingConnections(
|
||||
service_id,
|
||||
AcceptedConnectionCallback{
|
||||
.accepted_cb = [&accepted_latch](BleSocket socket,
|
||||
const std::string& service_id) {
|
||||
NEARBY_LOG(INFO, "Connection accepted: socket=%p, service_id=%s",
|
||||
&socket, service_id.c_str());
|
||||
accepted_latch.CountDown();
|
||||
}});
|
||||
EXPECT_TRUE(found_latch.Await(kWaitDuration).result());
|
||||
|
||||
BleSocket socket_a;
|
||||
EXPECT_FALSE(socket_a.IsValid());
|
||||
{
|
||||
SingleThreadExecutor client_executor;
|
||||
client_executor.Execute(
|
||||
[&ble_a, &socket_a, discovered_peripheral, &service_id]() {
|
||||
socket_a = ble_a.Connect(*discovered_peripheral, service_id);
|
||||
});
|
||||
}
|
||||
EXPECT_TRUE(accepted_latch.Await(kWaitDuration).result());
|
||||
EXPECT_TRUE(socket_a.IsValid());
|
||||
ble_b.StopAdvertising(service_id);
|
||||
ble_a.StopScanning(service_id);
|
||||
env_.Stop();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
@@ -11,6 +11,29 @@
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
|
||||
// Opaque wrapper over a BLE peripheral. Must contain enough data about a
|
||||
// particular BLE peripheral to connect to its GATT server.
|
||||
class BlePeripheral final {
|
||||
public:
|
||||
BlePeripheral() = default;
|
||||
BlePeripheral(const BlePeripheral&) = default;
|
||||
BlePeripheral& operator=(const BlePeripheral&) = default;
|
||||
explicit BlePeripheral(api::BlePeripheral* peripheral) : impl_(peripheral) {}
|
||||
~BlePeripheral() = default;
|
||||
|
||||
std::string GetName() const { return impl_->GetName(); }
|
||||
|
||||
ByteArray GetAdvertisementBytes(const std::string& service_id) const {
|
||||
return impl_->GetAdvertisementBytes(service_id);
|
||||
}
|
||||
|
||||
api::BlePeripheral& GetImpl() { return *impl_; }
|
||||
bool IsValid() const { return impl_ != nullptr; }
|
||||
|
||||
private:
|
||||
api::BlePeripheral* impl_;
|
||||
};
|
||||
|
||||
// https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html.
|
||||
class BluetoothDevice final {
|
||||
public:
|
||||
|
||||
@@ -187,6 +187,9 @@ class BluetoothClassicMedium final {
|
||||
|
||||
api::BluetoothClassicMedium& GetImpl() { return *impl_; }
|
||||
BluetoothAdapter& GetAdapter() { return adapter_; }
|
||||
BluetoothDevice FindRemoteDevice(const std::string& mac_address) {
|
||||
return BluetoothDevice(impl_->FindRemoteDevice(mac_address));
|
||||
}
|
||||
|
||||
private:
|
||||
Mutex mutex_;
|
||||
|
||||
@@ -6,9 +6,8 @@
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
|
||||
bool WifiLanMedium::StartAdvertising(
|
||||
const std::string& service_id,
|
||||
const std::string& service_info_name) {
|
||||
bool WifiLanMedium::StartAdvertising(const std::string& service_id,
|
||||
const std::string& service_info_name) {
|
||||
return impl_->StartAdvertising(service_id, service_info_name);
|
||||
}
|
||||
|
||||
@@ -39,13 +38,13 @@ bool WifiLanMedium::StartDiscovery(const std::string& service_id,
|
||||
"service_info_name=%s",
|
||||
&context.service, &service,
|
||||
service.GetName().c_str());
|
||||
return;
|
||||
} else {
|
||||
context.service = WifiLanService(&service);
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"Discovering service=%p, impl=%p, service_info_name=%s",
|
||||
&context.service, &service, service.GetName().c_str());
|
||||
}
|
||||
context.service = WifiLanService(&service);
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"Discovering service=%p, impl=%p, service_info_name=%s",
|
||||
&context.service, &service, service.GetName().c_str());
|
||||
discovered_service_callback_.service_discovered_cb(
|
||||
context.service, service_id);
|
||||
},
|
||||
@@ -54,12 +53,12 @@ bool WifiLanMedium::StartDiscovery(const std::string& service_id,
|
||||
const std::string& service_id) {
|
||||
MutexLock lock(&mutex_);
|
||||
if (services_.empty()) return;
|
||||
auto item = services_.extract(&service);
|
||||
auto& context = *item.mapped();
|
||||
auto context = services_.find(&service);
|
||||
if (context == services_.end()) return;
|
||||
NEARBY_LOG(INFO, "Removing service=%p, impl=%p",
|
||||
&context.service, &service);
|
||||
discovered_service_callback_.service_lost_cb(context.service,
|
||||
service_id);
|
||||
&(context->second->service), &service);
|
||||
discovered_service_callback_.service_lost_cb(
|
||||
context->second->service, service_id);
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -93,8 +92,8 @@ bool WifiLanMedium::StartAcceptingConnections(
|
||||
if (!pair.second) {
|
||||
NEARBY_LOG(INFO, "Accepting (again) socket=%p, impl=%p",
|
||||
&context.socket, &socket);
|
||||
context.socket = WifiLanSocket(&socket);
|
||||
} else {
|
||||
context.socket = WifiLanSocket(&socket);
|
||||
NEARBY_LOG(INFO, "Accepting socket=%p, impl=%p",
|
||||
&context.socket, &socket);
|
||||
}
|
||||
@@ -117,10 +116,17 @@ bool WifiLanMedium::StopAcceptingConnections(const std::string& service_id) {
|
||||
|
||||
WifiLanSocket WifiLanMedium::Connect(WifiLanService& service,
|
||||
const std::string& service_id) {
|
||||
NEARBY_LOG(INFO, "WifiLanMedium::Connect: service=%p [impl=%p]", &service,
|
||||
&service.GetImpl());
|
||||
NEARBY_LOG(
|
||||
INFO,
|
||||
"WifiLanMedium::Connect: service=%p [impl=%p, service_info_name=%s]",
|
||||
&service, &service.GetImpl(), service.GetName().c_str());
|
||||
return WifiLanSocket(impl_->Connect(service.GetImpl(), service_id));
|
||||
}
|
||||
|
||||
WifiLanService WifiLanMedium::FindRemoteService(const std::string& ip_address,
|
||||
int port) {
|
||||
return WifiLanService(impl_->FindRemoteService(ip_address, port));
|
||||
}
|
||||
|
||||
} // namespace nearby
|
||||
} // namespace location
|
||||
|
||||
@@ -140,6 +140,8 @@ class WifiLanMedium final {
|
||||
|
||||
api::WifiLanMedium& GetImpl() { return *impl_; }
|
||||
|
||||
WifiLanService FindRemoteService(const std::string& ip_address, int port);
|
||||
|
||||
private:
|
||||
Mutex mutex_;
|
||||
std::unique_ptr<api::WifiLanMedium> impl_;
|
||||
|
||||
@@ -7,13 +7,14 @@
|
||||
#include "platform_v2/public/logging.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
|
||||
namespace location {
|
||||
namespace nearby {
|
||||
namespace {
|
||||
|
||||
constexpr absl::string_view kServiceID{"com.google.location.nearby.apps.test"};
|
||||
constexpr absl::string_view kServiceName{"service name"};
|
||||
constexpr absl::string_view kServiceInfoName{"Simulated service info name"};
|
||||
|
||||
class WifiLanMediumTest : public ::testing::Test {
|
||||
protected:
|
||||
@@ -44,10 +45,10 @@ TEST_F(WifiLanMediumTest, CanStartAdvertising) {
|
||||
WifiLanMedium wifi_a;
|
||||
WifiLanMedium wifi_b;
|
||||
std::string service_id(kServiceID);
|
||||
std::string service_name{kServiceName};
|
||||
std::string service_info_name{kServiceInfoName};
|
||||
CountDownLatch found_latch(1);
|
||||
|
||||
wifi_a.StartAdvertising(service_id, service_name);
|
||||
wifi_a.StartAdvertising(service_id, service_info_name);
|
||||
|
||||
EXPECT_TRUE(wifi_b.StartDiscovery(
|
||||
service_id, DiscoveredServiceCallback{
|
||||
@@ -68,7 +69,7 @@ TEST_F(WifiLanMediumTest, CanStartDiscovery) {
|
||||
WifiLanMedium wifi_a;
|
||||
WifiLanMedium wifi_b;
|
||||
std::string service_id(kServiceID);
|
||||
std::string service_name{kServiceName};
|
||||
std::string service_info_name{kServiceInfoName};
|
||||
CountDownLatch found_latch(1);
|
||||
CountDownLatch lost_latch(1);
|
||||
|
||||
@@ -76,16 +77,16 @@ TEST_F(WifiLanMediumTest, CanStartDiscovery) {
|
||||
DiscoveredServiceCallback{
|
||||
.service_discovered_cb =
|
||||
[&found_latch](WifiLanService& service,
|
||||
const std::string& service_id) {
|
||||
absl::string_view service_id) {
|
||||
found_latch.CountDown();
|
||||
},
|
||||
.service_lost_cb =
|
||||
[&lost_latch](WifiLanService& service,
|
||||
const std::string& service_id) {
|
||||
absl::string_view service_id) {
|
||||
lost_latch.CountDown();
|
||||
},
|
||||
});
|
||||
EXPECT_TRUE(wifi_b.StartAdvertising(service_id, service_name));
|
||||
EXPECT_TRUE(wifi_b.StartAdvertising(service_id, service_info_name));
|
||||
EXPECT_TRUE(found_latch.Await(absl::Milliseconds(1000)).result());
|
||||
EXPECT_TRUE(wifi_b.StopAdvertising(service_id));
|
||||
EXPECT_TRUE(lost_latch.Await(absl::Milliseconds(1000)).result());
|
||||
@@ -98,7 +99,7 @@ TEST_F(WifiLanMediumTest, CanStopDiscovery) {
|
||||
WifiLanMedium wifi_a;
|
||||
WifiLanMedium wifi_b;
|
||||
std::string service_id(kServiceID);
|
||||
std::string service_name{kServiceName};
|
||||
std::string service_info_name{kServiceInfoName};
|
||||
CountDownLatch found_latch(1);
|
||||
CountDownLatch lost_latch(1);
|
||||
|
||||
@@ -106,16 +107,16 @@ TEST_F(WifiLanMediumTest, CanStopDiscovery) {
|
||||
DiscoveredServiceCallback{
|
||||
.service_discovered_cb =
|
||||
[&found_latch](WifiLanService& service,
|
||||
const std::string& service_id) {
|
||||
absl::string_view service_id) {
|
||||
found_latch.CountDown();
|
||||
},
|
||||
.service_lost_cb =
|
||||
[&lost_latch](WifiLanService& service,
|
||||
const std::string& service_id) {
|
||||
absl::string_view service_id) {
|
||||
lost_latch.CountDown();
|
||||
},
|
||||
});
|
||||
EXPECT_TRUE(wifi_b.StartAdvertising(service_id, service_name));
|
||||
EXPECT_TRUE(wifi_b.StartAdvertising(service_id, service_info_name));
|
||||
EXPECT_TRUE(found_latch.Await(absl::Milliseconds(1000)).result());
|
||||
EXPECT_TRUE(wifi_a.StopDiscovery(service_id));
|
||||
EXPECT_TRUE(wifi_b.StopAdvertising(service_id));
|
||||
@@ -128,7 +129,7 @@ TEST_F(WifiLanMediumTest, CanStartAcceptingConnectionsAndConnect) {
|
||||
WifiLanMedium wifi_a;
|
||||
WifiLanMedium wifi_b;
|
||||
std::string service_id(kServiceID);
|
||||
std::string service_name{kServiceName};
|
||||
std::string service_info_name{kServiceInfoName};
|
||||
CountDownLatch found_latch(1);
|
||||
CountDownLatch accepted_latch(1);
|
||||
|
||||
@@ -145,7 +146,7 @@ TEST_F(WifiLanMediumTest, CanStartAcceptingConnectionsAndConnect) {
|
||||
found_latch.CountDown();
|
||||
},
|
||||
});
|
||||
wifi_b.StartAdvertising(service_id, service_name);
|
||||
wifi_b.StartAdvertising(service_id, service_info_name);
|
||||
wifi_b.StartAcceptingConnections(
|
||||
service_id,
|
||||
AcceptedConnectionCallback{
|
||||
@@ -168,6 +169,7 @@ TEST_F(WifiLanMediumTest, CanStartAcceptingConnectionsAndConnect) {
|
||||
}
|
||||
EXPECT_TRUE(accepted_latch.Await(absl::Milliseconds(1000)).result());
|
||||
EXPECT_TRUE(socket_a.IsValid());
|
||||
wifi_b.StopAcceptingConnections(service_id);
|
||||
wifi_b.StopAdvertising(service_id);
|
||||
wifi_a.StopDiscovery(service_id);
|
||||
env_.Stop();
|
||||
|
||||
Reference in New Issue
Block a user