nearby sdk refactor

PiperOrigin-RevId: 425434260
This commit is contained in:
hais
2022-02-02 11:56:39 -08:00
committed by hai007
parent 287f0d7174
commit f5fcd35ced
1879 changed files with 2485 additions and 2332 deletions
+93
View File
@@ -0,0 +1,93 @@
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
licenses(["notice"])
cc_library(
name = "types",
hdrs = [
"atomic_boolean.h",
"atomic_reference.h",
"bluetooth_adapter.h",
"cancelable.h",
"condition_variable.h",
"count_down_latch.h",
"crypto.h",
"executor.h",
"future.h",
"input_file.h",
"listenable_future.h",
"log_message.h",
"mutex.h",
"output_file.h",
"scheduled_executor.h",
"settable_future.h",
"submittable_executor.h",
"system_clock.h",
],
visibility = [
"//internal/platform:__pkg__",
"//internal/platform/implementation:__subpackages__",
],
deps = [
"//internal/platform:base",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/time",
],
)
cc_library(
name = "comm",
hdrs = [
"ble.h",
"ble_v2.h",
"bluetooth_adapter.h",
"bluetooth_classic.h",
"server_sync.h",
"webrtc.h",
"wifi.h",
"wifi_lan.h",
],
visibility = [
"//connections/implementation:__subpackages__",
"//internal/platform:__pkg__",
"//internal/platform/implementation:__subpackages__",
],
deps = [
"//connections/implementation/proto:offline_wire_formats_portable_proto",
"//internal/platform:base",
"//internal/platform:cancellation_flag",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/types:optional",
"//webrtc/api:libjingle_peerconnection_api",
],
)
cc_library(
name = "platform",
hdrs = [
"platform.h",
],
visibility = [
"//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__",
"//internal/platform:__pkg__",
"//internal/platform/implementation:__subpackages__",
],
deps = [
":comm",
":types",
"//internal/platform:base",
"@com_google_absl//absl/strings",
],
)
@@ -0,0 +1,38 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_API_ATOMIC_BOOLEAN_H_
#define PLATFORM_API_ATOMIC_BOOLEAN_H_
namespace location {
namespace nearby {
namespace api {
// A boolean value that may be updated atomically.
class AtomicBoolean {
public:
virtual ~AtomicBoolean() = default;
// Atomically read and return current value.
virtual bool Get() const = 0;
// Atomically exchange original value with a new one. Return previous value.
virtual bool Set(bool value) = 0;
};
} // namespace api
} // namespace nearby
} // namespace location
#endif // PLATFORM_API_ATOMIC_BOOLEAN_H_
@@ -0,0 +1,40 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_API_ATOMIC_REFERENCE_H_
#define PLATFORM_API_ATOMIC_REFERENCE_H_
#include <cstdint>
namespace location {
namespace nearby {
namespace api {
// Type that allows 32-bit atomic reads and writes.
class AtomicUint32 {
public:
virtual ~AtomicUint32() = default;
// Atomically reads and returns stored value.
virtual std::uint32_t Get() const = 0;
// Atomically stores value.
virtual void Set(std::uint32_t value) = 0;
};
} // namespace api
} // namespace nearby
} // namespace location
#endif // PLATFORM_API_ATOMIC_REFERENCE_H_
+125
View File
@@ -0,0 +1,125 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_API_BLE_H_
#define PLATFORM_API_BLE_H_
#include "internal/platform/implementation/bluetooth_classic.h"
#include "internal/platform/byte_array.h"
#include "internal/platform/cancellation_flag.h"
#include "internal/platform/input_stream.h"
#include "internal/platform/output_stream.h"
namespace location {
namespace nearby {
namespace api {
// Opaque wrapper over a BLE peripheral. Must contain enough data about a
// particular BLE device to connect to its GATT server.
class BlePeripheral {
public:
virtual ~BlePeripheral() = default;
virtual std::string GetName() const = 0;
virtual ByteArray GetAdvertisementBytes(
const std::string& service_id) const = 0;
};
class BleSocket {
public:
virtual ~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.
virtual InputStream& GetInputStream() = 0;
// 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.
virtual OutputStream& GetOutputStream() = 0;
// Conforms to the same contract as
// https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html#close().
//
// Returns Exception::kIo on error, Exception::kSuccess otherwise.
virtual Exception Close() = 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() = default;
virtual bool StartAdvertising(
const std::string& service_id, const ByteArray& advertisement_bytes,
const std::string& fast_advertisement_service_uuid) = 0;
virtual bool StopAdvertising(const std::string& 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,
bool fast_advertisement)>
peripheral_discovered_cb =
DefaultCallback<BlePeripheral&, const std::string&, bool>();
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(const std::string& service_id,
const std::string& fast_advertisement_service_uuid,
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 bool StopScanning(const std::string& service_id) = 0;
// Callback that is invoked when a new connection is accepted.
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(
const std::string& service_id, AcceptedConnectionCallback callback) = 0;
virtual bool StopAcceptingConnections(const std::string& 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,
CancellationFlag* cancellation_flag) = 0;
};
} // namespace api
} // namespace nearby
} // namespace location
#endif // PLATFORM_API_BLE_H_
+406
View File
@@ -0,0 +1,406 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_API_BLE_V2_H_
#define PLATFORM_API_BLE_V2_H_
#include <cstdint>
#include <limits>
#include <map>
#include <memory>
#include <set>
#include <string>
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "internal/platform/byte_array.h"
#include "internal/platform/exception.h"
namespace location {
namespace nearby {
namespace api {
namespace ble_v2 {
// https://developer.android.com/reference/android/bluetooth/le/AdvertiseData
//
// Bundle of data found in a BLE advertisement.
//
// All service UUIDs will conform to the 16-bit Bluetooth base UUID,
// 0000xxxx-0000-1000-8000-00805F9B34FB. This makes it possible to store two
// byte service UUIDs in the advertisement.
struct BleAdvertisementData {
using TxPowerLevel = int8_t;
static const TxPowerLevel kUnspecifiedTxPowerLevel =
std::numeric_limits<TxPowerLevel>::min();
bool is_connectable;
// When set to kUnspecifiedTxPowerLevel, TX power should not be included in
// the advertisement data.
TxPowerLevel tx_power_level;
// When set to an empty string, local name should not be included in the
// advertisement data.
std::string local_name;
// When set to an empty vector, the set of 16-bit service class UUIDs should
// not be included in the advertisement data.
std::set<std::string> service_uuids;
// Maps service UUIDs to their service data.
std::map<std::string, ByteArray> service_data;
};
// Opaque wrapper over a BLE peripheral. Must be able to uniquely identify a
// peripheral so that we can connect to its GATT server.
class BlePeripheral {
public:
virtual ~BlePeripheral() {}
// https://developer.android.com/reference/android/bluetooth/BluetoothDevice#getAddress()
//
// This should be the MAC address when possible. If the implementation is
// unable to retrieve that, any unique identifier should suffice.
virtual std::string GetId() const = 0;
};
// https://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic
//
// Representation of a GATT characteristic.
class GattCharacteristic {
public:
virtual ~GattCharacteristic() {}
// Possible permissions of a GATT characteristic.
enum class Permission {
kUnknown = 0,
kRead = 1,
kWrite = 2,
kLast,
};
// Possible properties of a GATT characteristic.
enum class Property {
kUnknown = 0,
kRead = 1,
kWrite = 2,
kIndicate = 3,
kLast,
};
// Returns the UUID of this characteristic.
virtual std::string GetUuid() = 0;
// Returns the UUID of the containing GATT service.
virtual std::string GetServiceUuid() = 0;
};
// https://developer.android.com/reference/android/bluetooth/BluetoothGatt
//
// Representation of a client GATT connection to a remote GATT server.
class ClientGattConnection {
public:
virtual ~ClientGattConnection() {}
// https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#getDevice()
//
// Retrieves the BLE peripheral that this connection is tied to.
virtual BlePeripheral& GetPeripheral() = 0;
// https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#discoverServices()
//
// Discovers all available services and characteristics on this connection.
// Returns whether or not discovery finished successfully.
//
// This function should block until discovery has finished.
virtual bool DiscoverServices() = 0;
// https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#getService(java.util.UUID)
// https://developer.android.com/reference/android/bluetooth/BluetoothGattService.html#getCharacteristic(java.util.UUID)
//
// Retrieves a GATT characteristic. On error, does not return a value.
//
// DiscoverServices() should be called before this method to fetch all
// available services and characteristics first.
//
// It is okay for duplicate services to exist, as long as the specified
// characteristic UUID is unique among all services of the same UUID.
virtual absl::optional<GattCharacteristic> GetCharacteristic(
absl::string_view service_uuid,
absl::string_view characteristic_uuid) = 0;
// https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#readCharacteristic(android.bluetooth.BluetoothGattCharacteristic)
// https://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic.html#getValue()
//
// Reads a GATT characteristic. No value is returned upon error.
virtual absl::optional<ByteArray> ReadCharacteristic(
const GattCharacteristic& characteristic) = 0;
// https://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic.html#setValue(byte[])
// https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#writeCharacteristic(android.bluetooth.BluetoothGattCharacteristic)
//
// Sends a remote characteristic write request to the server and returns
// whether or not it was successful.
virtual bool WriteCharacteristic(const GattCharacteristic& characteristic,
const ByteArray& value) = 0;
// https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#disconnect()
//
// Disconnects a GATT connection.
virtual void Disconnect() = 0;
};
// https://developer.android.com/reference/android/bluetooth/BluetoothGattServer
//
// Representation of a server GATT connection to a remote GATT client.
class ServerGattConnection {
public:
virtual ~ServerGattConnection() {}
// https://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic.html#setValue(byte[])
// https://developer.android.com/reference/android/bluetooth/BluetoothGattServer.html#notifyCharacteristicChanged(android.bluetooth.BluetoothDevice,%20android.bluetooth.BluetoothGattCharacteristic,%20boolean)
//
// Sends a notification (via indication) to the client that a characteristic
// has changed with the given value. Returns whether or not it was successful.
//
// The value sent does not have to reflect the locally stored characteristic
// value. To update the local value, call GattServer::UpdateCharacteristic.
virtual bool SendCharacteristic(const GattCharacteristic& characteristic,
const ByteArray& value) = 0;
};
// Callback for asynchronous events on the client side of a GATT connection.
class ClientGattConnectionLifeCycleCallback {
public:
virtual ~ClientGattConnectionLifeCycleCallback() {}
// Called when the client is disconnected from the GATT server.
virtual void OnDisconnected(ClientGattConnection* connection) = 0;
};
// Callback for asynchronous events on the server side of a GATT connection.
class ServerGattConnectionLifeCycleCallback {
public:
virtual ~ServerGattConnectionLifeCycleCallback() {}
// Called when a remote peripheral connected to us and subscribed to one of
// our characteristics.
virtual void OnCharacteristicSubscription(
ServerGattConnection* connection,
const GattCharacteristic& characteristic) = 0;
// Called when a remote peripheral unsubscribed from one of our
// characteristics.
virtual void OnCharacteristicUnsubscription(
ServerGattConnection* connection,
const GattCharacteristic& characteristic) = 0;
};
// https://developer.android.com/reference/android/bluetooth/BluetoothGattServer
//
// Representation of a BLE GATT server.
class GattServer {
public:
virtual ~GattServer() {}
// Creates a characteristic and adds it to the GATT server under the given
// characteristic and service UUIDs. Returns no value upon error.
//
// Characteristics of the same service UUID should be put under one
// service rather than many services with the same UUID.
//
// If the INDICATE property is included, the characteristic should include the
// official Bluetooth Client Characteristic Configuration descriptor with UUID
// 0x2902 and a WRITE permission. This allows remote clients to write to this
// descriptor and subscribe for characteristic changes. For more information
// about this descriptor, please go to:
// https://www.bluetooth.com/specifications/Gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.Gatt.client_characteristic_configuration.xml
virtual absl::optional<GattCharacteristic> CreateCharacteristic(
absl::string_view service_uuid, absl::string_view characteristic_uuid,
const std::set<GattCharacteristic::Permission>& permissions,
const std::set<GattCharacteristic::Property>& properties) = 0;
// https://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic.html#setValue(byte[])
//
// Locally updates the value of a characteristic and returns whether or not it
// was successful.
// Takes ownership of (and is responsible for destroying) the passed-in
// 'value'.
virtual bool UpdateCharacteristic(const GattCharacteristic& characteristic,
const ByteArray& value) = 0;
// Stops a GATT server.
virtual void Stop() = 0;
};
// A BLE socket representation.
class BleSocket {
public:
virtual ~BleSocket() {}
// Returns the remote BLE peripheral tied to this socket.
virtual BlePeripheral& GetRemotePeripheral() = 0;
// Writes a message on the socket and blocks until finished. Returns
// Exception::kIo upon error, and Exception::kSuccess otherwise.
virtual Exception Write(const ByteArray& message) = 0;
// Closes the socket and blocks until finished. Returns Exception::kIo upon
// error, and Exception::kSuccess otherwise.
virtual Exception Close() = 0;
};
// Callback for asynchronous events on a BleSocket object.
class BleSocketLifeCycleCallback {
public:
virtual ~BleSocketLifeCycleCallback() {}
// Called when a message arrives on a socket.
virtual void OnMessageReceived(BleSocket* socket,
const ByteArray& message) = 0;
// Called when a socket gets disconnected.
virtual void OnDisconnected(BleSocket* socket) = 0;
};
// Callback for asynchronous events on the server side of a BleSocket object.
class ServerBleSocketLifeCycleCallback : public BleSocketLifeCycleCallback {
public:
~ServerBleSocketLifeCycleCallback() override {}
// Called when a new incoming socket has been established.
virtual void OnSocketEstablished(BleSocket* socket) = 0;
};
// The main BLE medium used inside of Nearby. This serves as the entry point for
// all BLE and GATT related operations.
class BleMedium {
public:
using Mtu = uint32_t;
virtual ~BleMedium() {}
// Coarse representation of power settings throughout all BLE operations.
enum class PowerMode {
kUnknown = 0,
kLow = 1,
kHigh = 2,
kLast,
};
// https://developer.android.com/reference/android/bluetooth/le/BluetoothLeAdvertiser.html#startAdvertising(android.bluetooth.le.AdvertiseSettings,%20android.bluetooth.le.AdvertiseData,%20android.bluetooth.le.AdvertiseData,%20android.bluetooth.le.AdvertiseCallback)
//
// Starts BLE advertising and returns whether or not it was successful.
//
// Power mode should be interpreted in the following way:
// LOW:
// - Advertising interval = ~1000ms
// - TX power = low
// HIGH:
// - Advertising interval = ~100ms
// - TX power = high
virtual bool StartAdvertising(const BleAdvertisementData& advertisement_data,
const BleAdvertisementData& scan_response,
PowerMode power_mode) = 0;
// https://developer.android.com/reference/android/bluetooth/le/BluetoothLeAdvertiser.html#stopAdvertising(android.bluetooth.le.AdvertiseCallback)
//
// Stops advertising.
virtual void StopAdvertising() = 0;
// https://developer.android.com/reference/android/bluetooth/le/ScanCallback
//
// Callback for BLE scan results.
class ScanCallback {
public:
virtual ~ScanCallback() {}
// https://developer.android.com/reference/android/bluetooth/le/ScanCallback.html#onScanResult(int,%20android.bluetooth.le.ScanResult)
//
// Called when a BLE advertisement is discovered.
//
// The passed in advertisement_data is the merged combination of both
// advertisement data and scan response.
//
// Every discovery of an advertisement should be reported, even if the
// advertisement was discovered before.
//
// Ownership of the BleAdvertisementData transfers to the caller at this
// point.
virtual void OnAdvertisementFound(
BlePeripheral* peripheral,
const BleAdvertisementData& advertisement_data) = 0;
};
// https://developer.android.com/reference/android/bluetooth/le/BluetoothLeScanner.html#startScan(java.util.List%3Candroid.bluetooth.le.ScanFilter%3E,%20android.bluetooth.le.ScanSettings,%20android.bluetooth.le.ScanCallback)
//
// Starts scanning and returns whether or not it was successful.
//
// Power mode should be interpreted in the following way:
// LOW:
// - Scan window = ~512ms
// - Scan interval = ~5120ms
// HIGH:
// - Scan window = ~4096ms
// - Scan interval = ~4096ms
virtual bool StartScanning(const std::set<std::string>& service_uuids,
PowerMode power_mode,
const ScanCallback& scan_callback) = 0;
// https://developer.android.com/reference/android/bluetooth/le/BluetoothLeScanner.html#stopScan(android.bluetooth.le.ScanCallback)
//
// Stops scanning.
virtual void StopScanning() = 0;
// https://developer.android.com/reference/android/bluetooth/BluetoothManager#openGattServer(android.content.Context,%20android.bluetooth.BluetoothGattServerCallback)
//
// Starts a GATT server. Returns a nullptr upon error.
virtual std::unique_ptr<GattServer> StartGattServer(
const ServerGattConnectionLifeCycleCallback& callback) = 0;
// Starts listening for incoming BLE sockets and returns false upon error.
virtual bool StartListeningForIncomingBleSockets(
const ServerBleSocketLifeCycleCallback& callback) = 0;
// Stops listening for incoming BLE sockets.
virtual void StopListeningForIncomingBleSockets() = 0;
// https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#connectGatt(android.content.Context,%20boolean,%20android.bluetooth.BluetoothGattCallback)
// https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#requestConnectionPriority(int)
// https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#requestMtu(int)
//
// Connects to a GATT server and negotiates the specified connection
// parameters. Returns nullptr upon error.
//
// Both connection interval and MTU can be negotiated on a best-effort basis.
//
// Power mode should be interpreted in the following way:
// LOW:
// - Connection interval = ~11.25ms - 15ms
// HIGH:
// - Connection interval = ~100ms - 125ms
virtual std::unique_ptr<ClientGattConnection> ConnectToGattServer(
BlePeripheral* peripheral, Mtu mtu, PowerMode power_mode,
const ClientGattConnectionLifeCycleCallback& callback) = 0;
// Establishes a BLE socket to the specified remote peripheral. Returns
// nullptr on error.
virtual std::unique_ptr<BleSocket> EstablishBleSocket(
BlePeripheral* peripheral,
const BleSocketLifeCycleCallback& callback) = 0;
};
} // namespace ble_v2
} // namespace api
} // namespace nearby
} // namespace location
#endif // PLATFORM_API_BLE_V2_H_
@@ -0,0 +1,75 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_API_BLUETOOTH_ADAPTER_H_
#define PLATFORM_API_BLUETOOTH_ADAPTER_H_
#include <string>
#include "absl/strings/string_view.h"
namespace location {
namespace nearby {
namespace api {
// https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html
class BluetoothAdapter {
public:
virtual ~BluetoothAdapter() = default;
// Eligible statuses of the BluetoothAdapter.
enum class Status {
kDisabled,
kEnabled,
};
// Synchronously sets the status of the BluetoothAdapter to 'status', and
// returns true if the operation was a success.
virtual bool SetStatus(Status status) = 0;
// Returns true if the BluetoothAdapter's current status is
// Status::Value::kEnabled.
virtual bool IsEnabled() const = 0;
// Scan modes of a BluetoothAdapter, as described at
// https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getScanMode().
enum class ScanMode {
kUnknown,
kNone,
kConnectable,
kConnectableDiscoverable,
};
// https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getScanMode()
//
// Returns ScanMode::kUnknown on error.
virtual ScanMode GetScanMode() const = 0;
// Synchronously sets the scan mode of the adapter, and returns true if the
// operation was a success.
virtual bool SetScanMode(ScanMode scan_mode) = 0;
// https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getName()
// Returns an empty string on error
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
} // namespace nearby
} // namespace location
#endif // PLATFORM_API_BLUETOOTH_ADAPTER_H_
@@ -0,0 +1,162 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_API_BLUETOOTH_CLASSIC_H_
#define PLATFORM_API_BLUETOOTH_CLASSIC_H_
#include <memory>
#include <string>
#include "internal/platform/byte_array.h"
#include "internal/platform/cancellation_flag.h"
#include "internal/platform/exception.h"
#include "internal/platform/input_stream.h"
#include "internal/platform/listeners.h"
#include "internal/platform/output_stream.h"
namespace location {
namespace nearby {
namespace api {
// https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html.
class BluetoothDevice {
public:
virtual ~BluetoothDevice() = default;
// 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.
class BluetoothSocket {
public:
virtual ~BluetoothSocket() = default;
// NOTE:
// It is an undefined behavior if GetInputStream() or GetOutputStream() is
// called for a not-connected BluetoothSocket, i.e. any object that is not
// returned by BluetoothClassicMedium::ConnectToService() for client side or
// BluetoothServerSocket::Accept() for server side of connection.
// Returns the InputStream of this connected BluetoothSocket.
virtual InputStream& GetInputStream() = 0;
// Returns the OutputStream of this connected BluetoothSocket.
virtual OutputStream& GetOutputStream() = 0;
// Closes both input and output streams, marks Socket as closed.
// After this call object should be treated as not connected.
// Returns Exception::kIo on error, Exception::kSuccess otherwise.
virtual Exception Close() = 0;
// https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html#getRemoteDevice()
// Returns valid BluetoothDevice pointer if there is a connection, and
// nullptr otherwise.
virtual BluetoothDevice* GetRemoteDevice() = 0;
};
// https://developer.android.com/reference/android/bluetooth/BluetoothServerSocket.html.
class BluetoothServerSocket {
public:
virtual ~BluetoothServerSocket() = default;
// https://developer.android.com/reference/android/bluetooth/BluetoothServerSocket.html#accept()
//
// 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.
virtual std::unique_ptr<BluetoothSocket> Accept() = 0;
// https://developer.android.com/reference/android/bluetooth/BluetoothServerSocket.html#close()
//
// Returns Exception::kIo on error, Exception::kSuccess otherwise.
virtual Exception Close() = 0;
};
// Container of operations that can be performed over the Bluetooth Classic
// medium.
class BluetoothClassicMedium {
public:
virtual ~BluetoothClassicMedium() = default;
struct DiscoveryCallback {
// BluetoothDevice is a proxy object created as a result of BT discovery.
// Its lifetime spans between calls to device_discovered_cb and
// device_lost_cb.
// It is safe to use BluetoothDevice in device_discovered_cb() callback
// and at any time afterwards, until device_lost_cb() is called.
// It is not safe to use BluetoothDevice after returning from
// device_lost_cb() callback.
std::function<void(BluetoothDevice& device)> device_discovered_cb =
DefaultCallback<BluetoothDevice&>();
std::function<void(BluetoothDevice& device)> device_name_changed_cb =
DefaultCallback<BluetoothDevice&>();
std::function<void(BluetoothDevice& device)> device_lost_cb =
DefaultCallback<BluetoothDevice&>();
};
// https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#startDiscovery()
//
// Returns true once the process of discovery has been initiated.
virtual bool StartDiscovery(DiscoveryCallback discovery_callback) = 0;
// https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#cancelDiscovery()
//
// Returns true once discovery is well and truly stopped; after this returns,
// there must be no more invocations of the DiscoveryCallback passed in to
// StartDiscovery().
virtual bool StopDiscovery() = 0;
// A combination of
// https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#createInsecureRfcommSocketToServiceRecord
// followed by
// https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html#connect().
//
// service_uuid is the canonical textual representation
// (https://en.wikipedia.org/wiki/Universally_unique_identifier#Format) of a
// type 3 name-based
// (https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based))
// UUID.
//
// On success, returns a new BluetoothSocket.
// On error, returns nullptr.
virtual std::unique_ptr<BluetoothSocket> ConnectToService(
BluetoothDevice& remote_device, const std::string& service_uuid,
CancellationFlag* cancellation_flag) = 0;
// https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#listenUsingInsecureRfcommWithServiceRecord
//
// service_uuid is the canonical textual representation
// (https://en.wikipedia.org/wiki/Universally_unique_identifier#Format) of a
// type 3 name-based
// (https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based))
// UUID.
//
// Returns nullptr error.
virtual std::unique_ptr<BluetoothServerSocket> ListenForService(
const std::string& service_name, const std::string& service_uuid) = 0;
virtual BluetoothDevice* GetRemoteDevice(const std::string& mac_address) = 0;
};
} // namespace api
} // namespace nearby
} // namespace location
#endif // PLATFORM_API_BLUETOOTH_CLASSIC_H_
@@ -0,0 +1,35 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_API_CANCELABLE_H_
#define PLATFORM_API_CANCELABLE_H_
namespace location {
namespace nearby {
namespace api {
// An interface to provide a cancellation mechanism for objects that represent
// long-running operations.
class Cancelable {
public:
virtual ~Cancelable() = default;
virtual bool Cancel() = 0;
};
} // namespace api
} // namespace nearby
} // namespace location
#endif // PLATFORM_API_CANCELABLE_H_
@@ -0,0 +1,51 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_API_CONDITION_VARIABLE_H_
#define PLATFORM_API_CONDITION_VARIABLE_H_
#include "absl/time/clock.h"
#include "internal/platform/exception.h"
namespace location {
namespace nearby {
namespace api {
// The ConditionVariable class is a synchronization primitive that can be used
// to block a thread, or multiple threads at the same time, until another thread
// both modifies a shared variable (the condition), and notifies the
// ConditionVariable.
class ConditionVariable {
public:
virtual ~ConditionVariable() {}
// Notifies all the waiters that condition state has changed.
virtual void Notify() = 0;
// Waits indefinitely for Notify to be called.
// May return prematurely in case of interrupt, if supported by platform.
// Returns kSuccess, or kInterrupted on interrupt.
virtual Exception Wait() = 0;
// Waits while timeout has not expired for Notify to be called.
// May return prematurely in case of interrupt, if supported by platform.
// Returns kSuccess, or kInterrupted on interrupt.
virtual Exception Wait(absl::Duration timeout) = 0;
};
} // namespace api
} // namespace nearby
} // namespace location
#endif // PLATFORM_API_CONDITION_VARIABLE_H_
@@ -0,0 +1,45 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_API_COUNT_DOWN_LATCH_H_
#define PLATFORM_API_COUNT_DOWN_LATCH_H_
#include <cstdint>
#include "absl/time/time.h"
#include "internal/platform/exception.h"
namespace location {
namespace nearby {
namespace api {
// A synchronization aid that allows one or more threads to wait until a set of
// operations being performed in other threads completes.
//
// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CountDownLatch.html
class CountDownLatch {
public:
virtual ~CountDownLatch() = default;
virtual Exception Await() = 0; // throws Exception::kInterrupted
virtual ExceptionOr<bool> Await(
absl::Duration timeout) = 0; // throws Exception::kInterrupted
virtual void CountDown() = 0;
};
} // namespace api
} // namespace nearby
} // namespace location
#endif // PLATFORM_API_COUNT_DOWN_LATCH_H_
+38
View File
@@ -0,0 +1,38 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_API_CRYPTO_H_
#define PLATFORM_API_CRYPTO_H_
#include "absl/strings/string_view.h"
#include "internal/platform/byte_array.h"
namespace location {
namespace nearby {
// A provider of standard hashing algorithms.
class Crypto {
public:
// Initialize global crypto state.
static void Init();
// Return MD5 hash of input.
static ByteArray Md5(absl::string_view input);
// Return SHA256 hash of input.
static ByteArray Sha256(absl::string_view input);
};
} // namespace nearby
} // namespace location
#endif // PLATFORM_API_CRYPTO_H_
@@ -0,0 +1,44 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_API_EXECUTOR_H_
#define PLATFORM_API_EXECUTOR_H_
#include "internal/platform/runnable.h"
namespace location {
namespace nearby {
namespace api {
int GetCurrentTid();
// This abstract class is the superclass of all classes representing an
// Executor.
class Executor {
public:
// Before returning from destructor, executor must wait for all pending
// jobs to finish.
virtual ~Executor() = default;
// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executor.html#execute-java.lang.Runnable-
virtual void Execute(Runnable&& runnable) = 0;
// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html#shutdown--
virtual void Shutdown() = 0;
};
} // namespace api
} // namespace nearby
} // namespace location
#endif // PLATFORM_API_EXECUTOR_H_
+46
View File
@@ -0,0 +1,46 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_API_FUTURE_H_
#define PLATFORM_API_FUTURE_H_
#include "absl/time/clock.h"
#include "internal/platform/exception.h"
namespace location {
namespace nearby {
namespace api {
// A Future represents the result of an asynchronous computation.
//
// https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Future.html
template <typename T>
class Future {
public:
virtual ~Future() = default;
// throws Exception::kInterrupted, Exception::kExecution
virtual ExceptionOr<T> Get() = 0;
// throws Exception::kInterrupted, Exception::kExecution
// throws Exception::kTimeout if timeout is exceeded while waiting for
// result.
virtual ExceptionOr<T> Get(absl::Duration timeout) = 0;
};
} // namespace api
} // namespace nearby
} // namespace location
#endif // PLATFORM_API_FUTURE_H_
+132
View File
@@ -0,0 +1,132 @@
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
licenses(["notice"])
cc_library(
name = "types",
testonly = True,
srcs = [
"log_message.cc",
"scheduled_executor.cc",
"system_clock.cc",
],
hdrs = [
"atomic_boolean.h",
"atomic_reference.h",
"condition_variable.h",
"log_message.h",
"multi_thread_executor.h",
"mutex.h",
"pipe.h",
"scheduled_executor.h",
"single_thread_executor.h",
],
visibility = ["//visibility:private"],
deps = [
"//base",
"//base:stringprintf",
"//internal/platform:base",
"//internal/platform:util",
"//internal/platform/implementation:platform",
"//internal/platform/implementation:types",
"//internal/platform/implementation/shared:count_down_latch",
"//internal/platform/implementation/shared:posix_mutex",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/synchronization",
"@com_google_absl//absl/time",
"@com_google_nisaba//nisaba/port:thread_pool",
],
)
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",
"wifi_lan.h",
],
visibility = ["//visibility:private"],
deps = [
":types",
"//internal/platform:base",
"//internal/platform:cancellation_flag",
"//internal/platform:logging",
"//internal/platform:test_util",
"//internal/platform/implementation:comm",
"//internal/platform/implementation/shared:count_down_latch",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/container:flat_hash_set",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/strings:str_format",
"@com_google_absl//absl/synchronization",
"//webrtc/api:create_peerconnection_factory", #buildcleaner: keep
"//webrtc/api:libjingle_peerconnection_api",
"//webrtc/api/task_queue:default_task_queue_factory",
],
)
cc_library(
name = "crypto",
testonly = True,
srcs = [
"crypto.cc",
],
visibility = ["//visibility:private"],
deps = [
"//internal/platform:base",
"//internal/platform/implementation:types",
"@com_google_absl//absl/strings",
"@boringssl//:crypto",
],
)
cc_library(
name = "g3",
testonly = True,
srcs = [
"platform.cc",
],
visibility = [
"//connections:__subpackages__",
"//internal/analytics:__subpackages__",
"//internal/platform:__subpackages__",
"//internal/proto/analytics:__subpackages__",
],
deps = [
":comm",
":crypto", # build_cleaner: keep
":types",
"//internal/platform:test_util",
"//internal/platform/implementation:comm",
"//internal/platform/implementation:platform",
"//internal/platform/implementation:types",
"//internal/platform/implementation/shared:count_down_latch",
"//internal/platform/implementation/shared:file",
"@com_google_absl//absl/base:core_headers",
"@com_google_absl//absl/memory",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/time",
],
)
@@ -0,0 +1,44 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_IMPL_G3_ATOMIC_BOOLEAN_H_
#define PLATFORM_IMPL_G3_ATOMIC_BOOLEAN_H_
#include <atomic>
#include "internal/platform/implementation/atomic_boolean.h"
namespace location {
namespace nearby {
namespace g3 {
// See documentation in
// cpp/platform/api/atomic_boolean.h
class AtomicBoolean : public api::AtomicBoolean {
public:
explicit AtomicBoolean(bool initial_value) : value_(initial_value) {}
~AtomicBoolean() override = default;
bool Get() const override { return value_.load(); }
bool Set(bool value) override { return value_.exchange(value); }
private:
std::atomic_bool value_;
};
} // namespace g3
} // namespace nearby
} // namespace location
#endif // PLATFORM_IMPL_G3_ATOMIC_BOOLEAN_H_
@@ -0,0 +1,43 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_IMPL_G3_ATOMIC_REFERENCE_H_
#define PLATFORM_IMPL_G3_ATOMIC_REFERENCE_H_
#include <atomic>
#include <cstdint>
#include "internal/platform/implementation/atomic_reference.h"
namespace location {
namespace nearby {
namespace g3 {
class AtomicUint32 : public api::AtomicUint32 {
public:
explicit AtomicUint32(std::int32_t value) : value_(value) {}
~AtomicUint32() override = default;
std::uint32_t Get() const override { return value_; }
void Set(std::uint32_t value) override { value_ = value; }
private:
std::atomic<std::uint32_t> value_;
};
} // namespace g3
} // namespace nearby
} // namespace location
#endif // PLATFORM_IMPL_G3_ATOMIC_REFERENCE_H_
+378
View File
@@ -0,0 +1,378 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "internal/platform/implementation/g3/ble.h"
#include <iostream>
#include <memory>
#include <string>
#include "absl/synchronization/mutex.h"
#include "internal/platform/implementation/ble.h"
#include "internal/platform/cancellation_flag_listener.h"
#include "internal/platform/logging.h"
#include "internal/platform/medium_environment.h"
#include "internal/platform/implementation/shared/count_down_latch.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_) {
shared::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,
const std::string& fast_advertisement_service_uuid) {
NEARBY_LOGS(INFO) << "G3 Ble StartAdvertising: service_id=" << service_id
<< ", advertisement bytes=" << advertisement_bytes.data()
<< "(" << advertisement_bytes.size() << "),"
<< " fast advertisement service uuid="
<< fast_advertisement_service_uuid;
auto& env = MediumEnvironment::Instance();
auto& peripheral = adapter_->GetPeripheral();
peripheral.SetAdvertisementBytes(service_id, advertisement_bytes);
bool fast_advertisement = !fast_advertisement_service_uuid.empty();
env.UpdateBleMediumForAdvertising(*this, peripheral, service_id,
fast_advertisement, 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, /*fast_advertisement=*/false,
/*enabled=*/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,
const std::string& fast_advertisement_service_uuid,
DiscoveredPeripheralCallback callback) {
NEARBY_LOGS(INFO) << "G3 Ble StartScanning: service_id=" << service_id;
auto& env = MediumEnvironment::Instance();
env.UpdateBleMediumForScanning(*this, service_id,
fast_advertisement_service_uuid,
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,
CancellationFlag* cancellation_flag) {
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 {};
}
}
if (cancellation_flag->Cancelled()) {
NEARBY_LOGS(ERROR) << "G3 BLE Connect: Has been cancelled: "
"service_id="
<< service_id;
return {};
}
CancellationFlagListener listener(cancellation_flag, [this]() {
NEARBY_LOGS(INFO) << "G3 BLE Cancel Connect.";
if (server_socket_ != nullptr) server_socket_->Close();
});
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
+228
View File
@@ -0,0 +1,228 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_IMPL_G3_BLE_H_
#define PLATFORM_IMPL_G3_BLE_H_
#include <memory>
#include <string>
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
#include "absl/strings/escaping.h"
#include "absl/synchronization/mutex.h"
#include "internal/platform/implementation/ble.h"
#include "internal/platform/byte_array.h"
#include "internal/platform/input_stream.h"
#include "internal/platform/output_stream.h"
#include "internal/platform/implementation/g3/bluetooth_adapter.h"
#include "internal/platform/implementation/g3/bluetooth_classic.h"
#include "internal/platform/implementation/g3/multi_thread_executor.h"
#include "internal/platform/implementation/g3/pipe.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,
const std::string& fast_advertisement_service_uuid) 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,
const std::string& fast_advertisement_service_uuid,
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,
CancellationFlag* cancellation_flag) 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_IMPL_G3_BLE_H_
@@ -0,0 +1,135 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "internal/platform/implementation/g3/bluetooth_adapter.h"
#include <string>
#include "internal/platform/medium_environment.h"
#include "internal/platform/prng.h"
#include "internal/platform/implementation/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::SetBluetoothClassicMedium(
api::BluetoothClassicMedium* medium) {
bluetooth_classic_medium_ = medium;
}
void BluetoothAdapter::SetBleMedium(api::BleMedium* medium) {
ble_medium_ = medium;
}
bool BluetoothAdapter::SetStatus(Status status) {
BluetoothAdapter::ScanMode mode;
bool enabled = status == Status::kEnabled;
std::string name;
{
absl::MutexLock lock(&mutex_);
enabled_ = enabled;
name = name_;
mode = mode_;
}
auto& env = MediumEnvironment::Instance();
env.OnBluetoothAdapterChangedState(*this, device_, name, enabled, mode);
return true;
}
bool BluetoothAdapter::IsEnabled() const {
absl::MutexLock lock(&mutex_);
return enabled_;
}
BluetoothAdapter::ScanMode BluetoothAdapter::GetScanMode() const {
absl::MutexLock lock(&mutex_);
return mode_;
}
bool BluetoothAdapter::SetScanMode(BluetoothAdapter::ScanMode mode) {
bool enabled;
std::string name;
{
absl::MutexLock lock(&mutex_);
mode_ = mode;
name = name_;
enabled = enabled_;
}
auto& env = MediumEnvironment::Instance();
env.OnBluetoothAdapterChangedState(*this, device_, std::move(name), enabled,
mode);
return true;
}
std::string BluetoothAdapter::GetName() const {
absl::MutexLock lock(&mutex_);
return name_;
}
bool BluetoothAdapter::SetName(absl::string_view name) {
BluetoothAdapter::ScanMode mode;
bool enabled;
{
absl::MutexLock lock(&mutex_);
name_ = name;
enabled = enabled_;
mode = mode_;
}
auto& env = MediumEnvironment::Instance();
env.OnBluetoothAdapterChangedState(*this, device_, std::string(name), enabled,
mode);
return true;
}
} // namespace g3
} // namespace nearby
} // namespace location
@@ -0,0 +1,142 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_IMPL_G3_BLUETOOTH_ADAPTER_H_
#define PLATFORM_IMPL_G3_BLUETOOTH_ADAPTER_H_
#include <string>
#include "absl/base/thread_annotations.h"
#include "absl/strings/string_view.h"
#include "absl/synchronization/mutex.h"
#include "internal/platform/implementation/ble.h"
#include "internal/platform/implementation/bluetooth_adapter.h"
#include "internal/platform/implementation/bluetooth_classic.h"
#include "internal/platform/implementation/g3/single_thread_executor.h"
namespace location {
namespace nearby {
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:
~BluetoothDevice() override = default;
// 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:
// Only BluetoothAdapter may instantiate BluetoothDevice.
friend class BluetoothAdapter;
explicit BluetoothDevice(BluetoothAdapter* adapter);
BluetoothAdapter& adapter_;
};
// https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html
class BluetoothAdapter : public api::BluetoothAdapter {
public:
using Status = api::BluetoothAdapter::Status;
using ScanMode = api::BluetoothAdapter::ScanMode;
BluetoothAdapter();
~BluetoothAdapter() override;
// Synchronously sets the status of the BluetoothAdapter to 'status', and
// returns true if the operation was a success.
bool SetStatus(Status status) override ABSL_LOCKS_EXCLUDED(mutex_);
// Returns true if the BluetoothAdapter's current status is
// Status::Value::kEnabled.
bool IsEnabled() const override ABSL_LOCKS_EXCLUDED(mutex_);
// https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getScanMode()
//
// Returns ScanMode::kUnknown on error.
ScanMode GetScanMode() const override ABSL_LOCKS_EXCLUDED(mutex_);
// Synchronously sets the scan mode of the adapter, and returns true if the
// operation was a success.
bool SetScanMode(ScanMode mode) override ABSL_LOCKS_EXCLUDED(mutex_);
// https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getName()
// Returns an empty string on error
std::string GetName() const override ABSL_LOCKS_EXCLUDED(mutex_);
// 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 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};
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;
};
} // namespace g3
} // namespace nearby
} // namespace location
#endif // PLATFORM_IMPL_G3_BLUETOOTH_ADAPTER_H_
@@ -0,0 +1,278 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "internal/platform/implementation/g3/bluetooth_classic.h"
#include <memory>
#include <string>
#include "absl/synchronization/mutex.h"
#include "internal/platform/implementation/bluetooth_classic.h"
#include "internal/platform/cancellation_flag_listener.h"
#include "internal/platform/logging.h"
#include "internal/platform/medium_environment.h"
#include "internal/platform/implementation/g3/bluetooth_adapter.h"
namespace location {
namespace nearby {
namespace g3 {
BluetoothSocket::~BluetoothSocket() {
absl::MutexLock lock(&mutex_);
DoClose();
}
void BluetoothSocket::Connect(BluetoothSocket& other) {
absl::MutexLock lock(&mutex_);
remote_socket_ = &other;
input_ = other.output_;
}
bool BluetoothSocket::IsConnected() const {
absl::MutexLock lock(&mutex_);
return IsConnectedLocked();
}
bool BluetoothSocket::IsClosed() const {
absl::MutexLock lock(&mutex_);
return closed_;
}
bool BluetoothSocket::IsConnectedLocked() const { return input_ != nullptr; }
InputStream& BluetoothSocket::GetInputStream() {
auto* remote_socket = GetRemoteSocket();
CHECK(remote_socket != nullptr);
return remote_socket->GetLocalInputStream();
}
OutputStream& BluetoothSocket::GetOutputStream() {
return GetLocalOutputStream();
}
InputStream& BluetoothSocket::GetLocalInputStream() {
absl::MutexLock lock(&mutex_);
return output_->GetInputStream();
}
OutputStream& BluetoothSocket::GetLocalOutputStream() {
absl::MutexLock lock(&mutex_);
return output_->GetOutputStream();
}
Exception BluetoothSocket::Close() {
absl::MutexLock lock(&mutex_);
DoClose();
return {Exception::kSuccess};
}
void BluetoothSocket::DoClose() {
if (!closed_) {
remote_socket_ = nullptr;
output_->GetOutputStream().Close();
output_->GetInputStream().Close();
input_->GetOutputStream().Close();
input_->GetInputStream().Close();
closed_ = true;
}
}
BluetoothSocket* BluetoothSocket::GetRemoteSocket() {
absl::MutexLock lock(&mutex_);
return remote_socket_;
}
BluetoothDevice* BluetoothSocket::GetRemoteDevice() {
BluetoothAdapter* remote_adapter = nullptr;
{
absl::MutexLock lock(&mutex_);
if (remote_socket_ == nullptr || remote_socket_->adapter_ == nullptr) {
return nullptr;
}
remote_adapter = remote_socket_->adapter_;
}
return remote_adapter ? &remote_adapter->GetDevice() : nullptr;
}
std::unique_ptr<api::BluetoothSocket> BluetoothServerSocket::Accept() {
absl::MutexLock lock(&mutex_);
while (!closed_ && pending_sockets_.empty()) {
cond_.Wait(&mutex_);
}
// whether or not we were running in the wait loop, return early if closed.
if (closed_) return {};
auto* remote_socket =
pending_sockets_.extract(pending_sockets_.begin()).value();
CHECK(remote_socket);
auto local_socket = std::make_unique<BluetoothSocket>(adapter_);
local_socket->Connect(*remote_socket);
remote_socket->Connect(*local_socket);
cond_.SignalAll();
return local_socket;
}
bool BluetoothServerSocket::Connect(BluetoothSocket& socket) {
absl::MutexLock lock(&mutex_);
if (closed_) return false;
if (socket.IsConnected()) {
NEARBY_LOGS(ERROR)
<< "Failed to connect to BT 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 BluetoothServerSocket::SetCloseNotifier(std::function<void()> notifier) {
absl::MutexLock lock(&mutex_);
close_notifier_ = std::move(notifier);
}
BluetoothServerSocket::~BluetoothServerSocket() {
absl::MutexLock lock(&mutex_);
DoClose();
}
Exception BluetoothServerSocket::Close() {
absl::MutexLock lock(&mutex_);
return DoClose();
}
Exception BluetoothServerSocket::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};
}
BluetoothClassicMedium::BluetoothClassicMedium(api::BluetoothAdapter& adapter)
// TODO(apolyudov): implement and use downcast<> with static assertions.
: adapter_(static_cast<BluetoothAdapter*>(&adapter)) {
adapter_->SetBluetoothClassicMedium(this);
auto& env = MediumEnvironment::Instance();
env.RegisterBluetoothMedium(*this, GetAdapter());
}
BluetoothClassicMedium::~BluetoothClassicMedium() {
adapter_->SetBluetoothClassicMedium(nullptr);
auto& env = MediumEnvironment::Instance();
env.UnregisterBluetoothMedium(*this);
}
bool BluetoothClassicMedium::StartDiscovery(DiscoveryCallback callback) {
auto& env = MediumEnvironment::Instance();
env.UpdateBluetoothMedium(*this, std::move(callback));
return true;
}
bool BluetoothClassicMedium::StopDiscovery() {
auto& env = MediumEnvironment::Instance();
env.UpdateBluetoothMedium(*this, {});
return true;
}
std::unique_ptr<api::BluetoothSocket> BluetoothClassicMedium::ConnectToService(
api::BluetoothDevice& remote_device, const std::string& service_uuid,
CancellationFlag* cancellation_flag) {
NEARBY_LOGS(INFO) << "G3 ConnectToService [self]: medium=" << this
<< ", adapter=" << &GetAdapter()
<< ", device=" << &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.GetBluetoothClassicMedium());
if (!medium) return {}; // Adapter is not bound to medium. Bail out.
BluetoothServerSocket* server_socket = nullptr;
NEARBY_LOGS(INFO) << "G3 ConnectToService [peer]: medium=" << medium
<< ", adapter=" << &adapter << ", device=" << &remote_device
<< ", uuid=" << service_uuid.c_str();
// Then, find our server socket context in this medium.
{
absl::MutexLock medium_lock(&medium->mutex_);
auto item = medium->sockets_.find(service_uuid);
server_socket = item != sockets_.end() ? item->second : nullptr;
if (server_socket == nullptr) {
NEARBY_LOGS(ERROR) << "Failed to find BT Server socket: uuid="
<< service_uuid;
return {};
}
}
if (cancellation_flag->Cancelled()) {
NEARBY_LOGS(ERROR) << "G3 Bluetooth Connect: Has been cancelled: "
"service_uuid="
<< service_uuid;
return {};
}
CancellationFlagListener listener(cancellation_flag, [&server_socket]() {
NEARBY_LOGS(INFO) << "G3 Bluetooth Cancel Connect.";
if (server_socket != nullptr) server_socket->Close();
});
auto socket = std::make_unique<BluetoothSocket>(&GetAdapter());
// Finally, Request to connect to this socket.
if (!server_socket->Connect(*socket)) {
NEARBY_LOGS(ERROR)
<< "Failed to connect to existing BT Server socket: uuid="
<< service_uuid;
return {};
}
NEARBY_LOGS(INFO) << "G3 ConnectToService: connected: socket="
<< socket.get();
return socket;
}
std::unique_ptr<api::BluetoothServerSocket>
BluetoothClassicMedium::ListenForService(const std::string& service_name,
const std::string& service_uuid) {
auto socket = std::make_unique<BluetoothServerSocket>(GetAdapter());
socket->SetCloseNotifier([this, uuid = service_uuid]() {
absl::MutexLock lock(&mutex_);
sockets_.erase(uuid);
});
NEARBY_LOGS(INFO) << "Adding service: medium=" << this
<< ", uuid=" << service_uuid;
absl::MutexLock lock(&mutex_);
sockets_.emplace(service_uuid, socket.get());
return socket;
}
api::BluetoothDevice* BluetoothClassicMedium::GetRemoteDevice(
const std::string& mac_address) {
auto& env = MediumEnvironment::Instance();
return env.FindBluetoothDevice(mac_address);
}
} // namespace g3
} // namespace nearby
} // namespace location
@@ -0,0 +1,238 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_IMPL_G3_BLUETOOTH_CLASSIC_H_
#define PLATFORM_IMPL_G3_BLUETOOTH_CLASSIC_H_
#include <memory>
#include <string>
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
#include "absl/synchronization/mutex.h"
#include "internal/platform/implementation/bluetooth_classic.h"
#include "internal/platform/byte_array.h"
#include "internal/platform/exception.h"
#include "internal/platform/input_stream.h"
#include "internal/platform/listeners.h"
#include "internal/platform/output_stream.h"
#include "internal/platform/implementation/g3/bluetooth_adapter.h"
#include "internal/platform/implementation/g3/pipe.h"
namespace location {
namespace nearby {
namespace g3 {
// https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html.
class BluetoothSocket : public api::BluetoothSocket {
public:
BluetoothSocket() = default;
explicit BluetoothSocket(BluetoothAdapter* adapter) : adapter_(adapter) {}
~BluetoothSocket() override;
// Connects to another BluetoothSocket, to form a functional low-level
// channel. From this point on, and until Close is called, connection exists.
void Connect(BluetoothSocket& other);
// NOTE:
// It is an undefined behavior if GetInputStream() or GetOutputStream() is
// called for a not-connected BluetoothSocket, i.e. any object that is not
// returned by BluetoothClassicMedium::ConnectToService() for client side or
// BluetoothServerSocket::Accept() for server side of connection.
// Returns the InputStream of this connected BluetoothSocket.
InputStream& GetInputStream() override;
// Returns the OutputStream of this connected BluetoothSocket.
// This stream is for local side to write.
OutputStream& GetOutputStream() override;
// Returns address of a remote BluetoothSocket or nullptr.
BluetoothSocket* 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_);
// Closes both input and output streams, marks Socket as closed.
// After this call object should be treated as not connected.
// Returns Exception::kIo on error, Exception::kSuccess otherwise.
Exception Close() override ABSL_LOCKS_EXCLUDED(mutex_);
// https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html#getRemoteDevice()
// Returns valid BluetoothDevice pointer if there is a connection, and
// nullptr otherwise.
BluetoothDevice* GetRemoteDevice() 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_;
BluetoothAdapter* adapter_ = nullptr; // Our Adapter. Read only.
BluetoothSocket* remote_socket_ ABSL_GUARDED_BY(mutex_) = nullptr;
bool closed_ ABSL_GUARDED_BY(mutex_) = false;
};
// https://developer.android.com/reference/android/bluetooth/BluetoothServerSocket.html.
class BluetoothServerSocket : public api::BluetoothServerSocket {
public:
explicit BluetoothServerSocket(BluetoothAdapter& adapter)
: adapter_(&adapter) {}
~BluetoothServerSocket() override;
// 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 BluetoothSocket to the server side.
// If not null, returned socket is connected to its remote (client-side) peer.
std::unique_ptr<api::BluetoothSocket> Accept() override
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.
// socket is an initialized BluetoothSocket, associated with a client
// BluetoothAdapter.
// Returns true, if socket is successfully connected.
bool Connect(BluetoothSocket& socket) ABSL_LOCKS_EXCLUDED(mutex_);
// Called by the server side of a connection before passing ownership of
// BluetoothServerSocker 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() override ABSL_LOCKS_EXCLUDED(mutex_);
private:
Exception DoClose() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
absl::Mutex mutex_;
absl::CondVar cond_;
BluetoothAdapter* adapter_ = nullptr; // Our Adapter. Read only.
absl::flat_hash_set<BluetoothSocket*> 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 Bluetooth Classic
// medium.
class BluetoothClassicMedium : public api::BluetoothClassicMedium {
public:
explicit BluetoothClassicMedium(api::BluetoothAdapter& adapter);
~BluetoothClassicMedium() override;
// NOTE(DiscoveryCallback):
// BluetoothDevice is a proxy object created as a result of BT discovery.
// Its lifetime spans between calls to device_discovered_cb and
// device_lost_cb.
// It is safe to use BluetoothDevice in device_discovered_cb() callback
// and at any time afterwards, until device_lost_cb() is called.
// It is not safe to use BluetoothDevice after returning from
// device_lost_cb() callback.
// https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#startDiscovery()
//
// Returns true once the process of discovery has been initiated.
bool StartDiscovery(DiscoveryCallback callback) override
ABSL_LOCKS_EXCLUDED(mutex_);
// https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#cancelDiscovery()
//
// Returns true once discovery is well and truly stopped; after this returns,
// there must be no more invocations of the DiscoveryCallback passed in to
// StartDiscovery().
bool StopDiscovery() override ABSL_LOCKS_EXCLUDED(mutex_);
// Connects to existing remote BT service.
//
// A combination of
// https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#createInsecureRfcommSocketToServiceRecord
// followed by
// https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html#connect().
//
// service_uuid is the canonical textual representation
// (https://en.wikipedia.org/wiki/Universally_unique_identifier#Format) of a
// type 3 name-based
// (https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based))
// UUID.
//
// On success, returns a new BluetoothSocket.
// On error, returns nullptr.
std::unique_ptr<api::BluetoothSocket> ConnectToService(
api::BluetoothDevice& remote_device, const std::string& service_uuid,
CancellationFlag* cancellation_flag) override ABSL_LOCKS_EXCLUDED(mutex_);
BluetoothAdapter& GetAdapter() { return *adapter_; }
// Creates BT service, and begins listening for remote attempts to connect.
//
// https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#listenUsingInsecureRfcommWithServiceRecord
//
// service_uuid is the canonical textual representation
// (https://en.wikipedia.org/wiki/Universally_unique_identifier#Format) of a
// type 3 name-based
// (https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based))
// UUID.
//
// Returns nullptr on error.
std::unique_ptr<api::BluetoothServerSocket> ListenForService(
const std::string& service_name, const std::string& service_uuid) override
ABSL_LOCKS_EXCLUDED(mutex_);
api::BluetoothDevice* GetRemoteDevice(
const std::string& mac_address) override;
private:
absl::Mutex mutex_;
BluetoothAdapter* adapter_; // Our device adapter; read-only.
absl::flat_hash_map<std::string, BluetoothServerSocket*> sockets_
ABSL_GUARDED_BY(mutex_);
};
} // namespace g3
} // namespace nearby
} // namespace location
#endif // PLATFORM_IMPL_G3_BLUETOOTH_CLASSIC_H_
@@ -0,0 +1,51 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_IMPL_G3_CONDITION_VARIABLE_H_
#define PLATFORM_IMPL_G3_CONDITION_VARIABLE_H_
#include "absl/synchronization/mutex.h"
#include "internal/platform/implementation/condition_variable.h"
#include "internal/platform/exception.h"
#include "internal/platform/implementation/g3/mutex.h"
namespace location {
namespace nearby {
namespace g3 {
class ConditionVariable : public api::ConditionVariable {
public:
explicit ConditionVariable(g3::Mutex* mutex) : mutex_(&mutex->mutex_) {}
~ConditionVariable() override = default;
Exception Wait() override {
cond_var_.Wait(mutex_);
return {Exception::kSuccess};
}
Exception Wait(absl::Duration timeout) override {
cond_var_.WaitWithTimeout(mutex_, timeout);
return {Exception::kSuccess};
}
void Notify() override { cond_var_.SignalAll(); }
private:
absl::Mutex* mutex_;
absl::CondVar cond_var_;
};
} // namespace g3
} // namespace nearby
} // namespace location
#endif // PLATFORM_IMPL_G3_CONDITION_VARIABLE_H_
@@ -0,0 +1,53 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "internal/platform/implementation/crypto.h"
#include <cstdint>
#include <string>
#include "absl/strings/string_view.h"
#include "internal/platform/byte_array.h"
#include "src/include/openssl/digest.h"
namespace location {
namespace nearby {
// Initialize global crypto state.
void Crypto::Init() {}
static ByteArray Hash(absl::string_view input, const EVP_MD* algo) {
unsigned int md_out_size = EVP_MAX_MD_SIZE;
uint8_t digest_buffer[EVP_MAX_MD_SIZE];
if (input.empty()) return {};
if (!EVP_Digest(input.data(), input.size(), digest_buffer, &md_out_size, algo,
nullptr))
return {};
return ByteArray{reinterpret_cast<char*>(digest_buffer), md_out_size};
}
// Return MD5 hash of input.
ByteArray Crypto::Md5(absl::string_view input) {
return Hash(input, EVP_md5());
}
// Return SHA256 hash of input.
ByteArray Crypto::Sha256(absl::string_view input) {
return Hash(input, EVP_sha256());
}
} // namespace nearby
} // namespace location
@@ -0,0 +1,74 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "internal/platform/implementation/g3/log_message.h"
#include <algorithm>
#include "base/stringprintf.h"
namespace location {
namespace nearby {
namespace g3 {
api::LogMessage::Severity g_min_log_severity = api::LogMessage::Severity::kInfo;
inline absl::LogSeverity ConvertSeverity(api::LogMessage::Severity severity) {
switch (severity) {
// api::LogMessage::Severity kVerbose and kInfo is mapped to
// absl::LogSeverity kInfo since absl::LogSeverity doesn't have kVerbose
// level.
case api::LogMessage::Severity::kVerbose:
case api::LogMessage::Severity::kInfo:
return absl::LogSeverity::kInfo;
case api::LogMessage::Severity::kWarning:
return absl::LogSeverity::kWarning;
case api::LogMessage::Severity::kError:
return absl::LogSeverity::kError;
case api::LogMessage::Severity::kFatal:
return absl::LogSeverity::kFatal;
}
}
LogMessage::LogMessage(const char* file, int line, Severity severity)
: log_streamer_(ConvertSeverity(severity), file, line) {}
LogMessage::~LogMessage() = default;
void LogMessage::Print(const char* format, ...) {
va_list ap;
va_start(ap, format);
std::string result;
StringAppendV(&result, format, ap);
log_streamer_.stream() << result;
va_end(ap);
}
std::ostream& LogMessage::Stream() { return log_streamer_.stream(); }
} // namespace g3
namespace api {
void LogMessage::SetMinLogSeverity(Severity severity) {
g3::g_min_log_severity = severity;
}
bool LogMessage::ShouldCreateLogMessage(Severity severity) {
return severity >= g3::g_min_log_severity;
}
} // namespace api
} // namespace nearby
} // namespace location
@@ -0,0 +1,44 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_IMPL_G3_LOG_MESSAGE_H_
#define PLATFORM_IMPL_G3_LOG_MESSAGE_H_
#include "glog/logging.h"
#include "internal/platform/implementation/log_message.h"
namespace location {
namespace nearby {
namespace g3 {
// See documentation in
// cpp/platform/api/log_message.h
class LogMessage : public api::LogMessage {
public:
LogMessage(const char* file, int line, Severity severity);
~LogMessage() override;
void Print(const char* format, ...) override;
std::ostream& Stream() override;
private:
google::LogMessage log_streamer_;
};
} // namespace g3
} // namespace nearby
} // namespace location
#endif // PLATFORM_IMPL_G3_LOG_MESSAGE_H_
@@ -0,0 +1,66 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_IMPL_G3_MULTI_THREAD_EXECUTOR_H_
#define PLATFORM_IMPL_G3_MULTI_THREAD_EXECUTOR_H_
#include <atomic>
#include "absl/time/clock.h"
#include "internal/platform/implementation/submittable_executor.h"
#include "internal/platform/implementation/shared/count_down_latch.h"
#include "nisaba/port/thread_pool.h"
namespace location {
namespace nearby {
namespace g3 {
// An Executor that reuses a fixed number of threads operating off a shared
// unbounded queue.
class MultiThreadExecutor : public api::SubmittableExecutor {
public:
explicit MultiThreadExecutor(int max_parallelism)
: thread_pool_(max_parallelism) {
thread_pool_.StartWorkers();
}
void Execute(Runnable&& runnable) override {
if (!shutdown_) {
thread_pool_.Schedule(std::move(runnable));
}
}
bool DoSubmit(Runnable&& runnable) override {
if (shutdown_) return false;
thread_pool_.Schedule(std::move(runnable));
return true;
}
void Shutdown() override { DoShutdown(); }
~MultiThreadExecutor() override { DoShutdown(); }
void ScheduleAfter(absl::Duration delay, Runnable&& runnable) {
if (shutdown_) return;
thread_pool_.ScheduleAt(absl::Now() + delay, std::move(runnable));
}
bool InShutdown() const { return shutdown_; }
private:
void DoShutdown() { shutdown_ = true; }
std::atomic_bool shutdown_ = false;
ThreadPool thread_pool_;
};
} // namespace g3
} // namespace nearby
} // namespace location
#endif // PLATFORM_IMPL_G3_MULTI_THREAD_EXECUTOR_H_
@@ -0,0 +1,61 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_IMPL_G3_MUTEX_H_
#define PLATFORM_IMPL_G3_MUTEX_H_
#include "absl/synchronization/mutex.h"
#include "internal/platform/implementation/mutex.h"
#include "internal/platform/implementation/shared/posix_mutex.h"
namespace location {
namespace nearby {
namespace g3 {
class ABSL_LOCKABLE Mutex : public api::Mutex {
public:
explicit Mutex(bool check) : check_(check) {}
~Mutex() override = default;
Mutex(Mutex&&) = delete;
Mutex& operator=(Mutex&&) = delete;
Mutex(const Mutex&) = delete;
Mutex& operator=(const Mutex&) = delete;
void Lock() ABSL_EXCLUSIVE_LOCK_FUNCTION() override {
mutex_.Lock();
if (!check_) mutex_.ForgetDeadlockInfo();
}
void Unlock() ABSL_UNLOCK_FUNCTION() override { mutex_.Unlock(); }
private:
friend class ConditionVariable;
absl::Mutex mutex_;
bool check_;
};
class ABSL_LOCKABLE RecursiveMutex : public posix::Mutex {
public:
~RecursiveMutex() override = default;
RecursiveMutex() = default;
RecursiveMutex(RecursiveMutex&&) = delete;
RecursiveMutex& operator=(RecursiveMutex&&) = delete;
RecursiveMutex(const RecursiveMutex&) = delete;
RecursiveMutex& operator=(const RecursiveMutex&) = delete;
};
} // namespace g3
} // namespace nearby
} // namespace location
#endif // PLATFORM_IMPL_G3_MUTEX_H_
@@ -0,0 +1,44 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_IMPL_G3_PIPE_H_
#define PLATFORM_IMPL_G3_PIPE_H_
#include <memory>
#include "internal/platform/base_pipe.h"
#include "internal/platform/implementation/g3/condition_variable.h"
#include "internal/platform/implementation/g3/mutex.h"
namespace location {
namespace nearby {
namespace g3 {
class Pipe : public BasePipe {
public:
Pipe() {
auto mutex = std::make_unique<g3::Mutex>(/*check=*/true);
auto cond = std::make_unique<g3::ConditionVariable>(mutex.get());
Setup(std::move(mutex), std::move(cond));
}
~Pipe() override = default;
Pipe(Pipe&&) = delete;
Pipe& operator=(Pipe&&) = delete;
};
} // namespace g3
} // namespace nearby
} // namespace location
#endif // PLATFORM_IMPL_G3_PIPE_H_
@@ -0,0 +1,172 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "internal/platform/implementation/platform.h"
#include <atomic>
#include <cstdint>
#include <memory>
#include "absl/memory/memory.h"
#include "absl/strings/str_cat.h"
#include "absl/time/time.h"
#include "internal/platform/implementation/atomic_boolean.h"
#include "internal/platform/implementation/atomic_reference.h"
#include "internal/platform/implementation/ble_v2.h"
#include "internal/platform/implementation/bluetooth_adapter.h"
#include "internal/platform/implementation/bluetooth_classic.h"
#include "internal/platform/implementation/condition_variable.h"
#include "internal/platform/implementation/shared/count_down_latch.h"
#include "internal/platform/implementation/log_message.h"
#include "internal/platform/implementation/mutex.h"
#include "internal/platform/implementation/scheduled_executor.h"
#include "internal/platform/implementation/server_sync.h"
#include "internal/platform/implementation/submittable_executor.h"
#include "internal/platform/implementation/webrtc.h"
#include "internal/platform/implementation/wifi.h"
#include "internal/platform/medium_environment.h"
#include "internal/platform/implementation/g3/atomic_boolean.h"
#include "internal/platform/implementation/g3/atomic_reference.h"
#include "internal/platform/implementation/g3/ble.h"
#include "internal/platform/implementation/g3/bluetooth_adapter.h"
#include "internal/platform/implementation/g3/bluetooth_classic.h"
#include "internal/platform/implementation/g3/condition_variable.h"
#include "internal/platform/implementation/g3/log_message.h"
#include "internal/platform/implementation/g3/multi_thread_executor.h"
#include "internal/platform/implementation/g3/mutex.h"
#include "internal/platform/implementation/g3/scheduled_executor.h"
#include "internal/platform/implementation/g3/single_thread_executor.h"
#include "internal/platform/implementation/g3/webrtc.h"
#include "internal/platform/implementation/g3/wifi_lan.h"
#include "internal/platform/implementation/shared/file.h"
namespace location {
namespace nearby {
namespace api {
namespace {
std::string GetPayloadPath(PayloadId payload_id) {
return absl::StrCat("/tmp/", payload_id);
}
} // namespace
int GetCurrentTid() {
const LiveThread* my = Thread_GetMyLiveThread();
return LiveThread_Pthread_TID(my);
}
std::unique_ptr<SubmittableExecutor>
ImplementationPlatform::CreateSingleThreadExecutor() {
return absl::make_unique<g3::SingleThreadExecutor>();
}
std::unique_ptr<SubmittableExecutor>
ImplementationPlatform::CreateMultiThreadExecutor(int max_concurrency) {
return absl::make_unique<g3::MultiThreadExecutor>(max_concurrency);
}
std::unique_ptr<ScheduledExecutor>
ImplementationPlatform::CreateScheduledExecutor() {
return absl::make_unique<g3::ScheduledExecutor>();
}
std::unique_ptr<AtomicUint32> ImplementationPlatform::CreateAtomicUint32(
std::uint32_t value) {
return absl::make_unique<g3::AtomicUint32>(value);
}
std::unique_ptr<BluetoothAdapter>
ImplementationPlatform::CreateBluetoothAdapter() {
return absl::make_unique<g3::BluetoothAdapter>();
}
std::unique_ptr<CountDownLatch> ImplementationPlatform::CreateCountDownLatch(
std::int32_t count) {
return absl::make_unique<shared::CountDownLatch>(count);
}
std::unique_ptr<AtomicBoolean> ImplementationPlatform::CreateAtomicBoolean(
bool initial_value) {
return absl::make_unique<g3::AtomicBoolean>(initial_value);
}
std::unique_ptr<InputFile> ImplementationPlatform::CreateInputFile(
PayloadId payload_id, std::int64_t total_size) {
return absl::make_unique<shared::InputFile>(GetPayloadPath(payload_id),
total_size);
}
std::unique_ptr<OutputFile> ImplementationPlatform::CreateOutputFile(
PayloadId payload_id) {
return absl::make_unique<shared::OutputFile>(GetPayloadPath(payload_id));
}
std::unique_ptr<LogMessage> ImplementationPlatform::CreateLogMessage(
const char* file, int line, LogMessage::Severity severity) {
return absl::make_unique<g3::LogMessage>(file, line, severity);
}
std::unique_ptr<BluetoothClassicMedium>
ImplementationPlatform::CreateBluetoothClassicMedium(
api::BluetoothAdapter& adapter) {
return absl::make_unique<g3::BluetoothClassicMedium>(adapter);
}
std::unique_ptr<BleMedium> ImplementationPlatform::CreateBleMedium(
api::BluetoothAdapter& adapter) {
return absl::make_unique<g3::BleMedium>(adapter);
}
std::unique_ptr<ble_v2::BleMedium> ImplementationPlatform::CreateBleV2Medium(
api::BluetoothAdapter& adapter) {
return std::unique_ptr<ble_v2::BleMedium>();
}
std::unique_ptr<ServerSyncMedium>
ImplementationPlatform::CreateServerSyncMedium() {
return std::unique_ptr<ServerSyncMedium>(/*new ServerSyncMediumImpl()*/);
}
std::unique_ptr<WifiMedium> ImplementationPlatform::CreateWifiMedium() {
return std::unique_ptr<WifiMedium>();
}
std::unique_ptr<WifiLanMedium> ImplementationPlatform::CreateWifiLanMedium() {
return absl::make_unique<g3::WifiLanMedium>();
}
std::unique_ptr<WebRtcMedium> ImplementationPlatform::CreateWebRtcMedium() {
if (MediumEnvironment::Instance().GetEnvironmentConfig().webrtc_enabled) {
return absl::make_unique<g3::WebRtcMedium>();
} else {
return nullptr;
}
}
std::unique_ptr<Mutex> ImplementationPlatform::CreateMutex(Mutex::Mode mode) {
if (mode == Mutex::Mode::kRecursive)
return absl::make_unique<g3::RecursiveMutex>();
else
return absl::make_unique<g3::Mutex>(mode == Mutex::Mode::kRegular);
}
std::unique_ptr<ConditionVariable>
ImplementationPlatform::CreateConditionVariable(Mutex* mutex) {
return std::unique_ptr<ConditionVariable>(
new g3::ConditionVariable(static_cast<g3::Mutex*>(mutex)));
}
} // namespace api
} // namespace nearby
} // namespace location
@@ -0,0 +1,79 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "internal/platform/implementation/g3/scheduled_executor.h"
#include <atomic>
#include <memory>
#include "absl/time/clock.h"
#include "internal/platform/implementation/cancelable.h"
#include "internal/platform/runnable.h"
namespace location {
namespace nearby {
namespace g3 {
namespace {
class ScheduledCancelable : public api::Cancelable {
public:
bool Cancel() override {
Status expected = kNotRun;
while (expected == kNotRun) {
if (status_.compare_exchange_strong(expected, kCanceled)) {
return true;
}
}
return false;
}
bool MarkExecuted() {
Status expected = kNotRun;
while (expected == kNotRun) {
if (status_.compare_exchange_strong(expected, kExecuted)) {
return true;
}
}
return false;
}
private:
enum Status {
kNotRun,
kExecuted,
kCanceled,
};
std::atomic<Status> status_ = kNotRun;
};
} // namespace
std::shared_ptr<api::Cancelable> ScheduledExecutor::Schedule(
Runnable&& runnable, absl::Duration delay) {
auto scheduled_cancelable = std::make_shared<ScheduledCancelable>();
if (executor_.InShutdown()) {
return scheduled_cancelable;
}
executor_.ScheduleAfter(
delay, [this, scheduled_cancelable, runnable(std::move(runnable))]() {
if (!executor_.InShutdown() && scheduled_cancelable->MarkExecuted()) {
runnable();
}
});
return scheduled_cancelable;
}
} // namespace g3
} // namespace nearby
} // namespace location
@@ -0,0 +1,54 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_IMPL_G3_SCHEDULED_EXECUTOR_H_
#define PLATFORM_IMPL_G3_SCHEDULED_EXECUTOR_H_
#include <atomic>
#include <memory>
#include "absl/time/clock.h"
#include "internal/platform/implementation/cancelable.h"
#include "internal/platform/implementation/scheduled_executor.h"
#include "internal/platform/runnable.h"
#include "internal/platform/implementation/g3/single_thread_executor.h"
#include "nisaba/port/thread_pool.h"
namespace location {
namespace nearby {
namespace g3 {
// An Executor that reuses a fixed number of threads operating off a shared
// unbounded queue.
class ScheduledExecutor final : public api::ScheduledExecutor {
public:
ScheduledExecutor() = default;
~ScheduledExecutor() override { executor_.Shutdown(); }
void Execute(Runnable&& runnable) override {
executor_.Execute(std::move(runnable));
}
std::shared_ptr<api::Cancelable> Schedule(Runnable&& runnable,
absl::Duration delay) override;
void Shutdown() override { executor_.Shutdown(); }
private:
SingleThreadExecutor executor_;
};
} // namespace g3
} // namespace nearby
} // namespace location
#endif // PLATFORM_IMPL_G3_SCHEDULED_EXECUTOR_H_
@@ -0,0 +1,36 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_IMPL_G3_SINGLE_THREAD_EXECUTOR_H_
#define PLATFORM_IMPL_G3_SINGLE_THREAD_EXECUTOR_H_
#include "internal/platform/implementation/g3/multi_thread_executor.h"
namespace location {
namespace nearby {
namespace g3 {
// An Executor that uses a single worker thread operating off an unbounded
// queue.
class SingleThreadExecutor final : public MultiThreadExecutor {
public:
SingleThreadExecutor() : MultiThreadExecutor(1) {}
~SingleThreadExecutor() override = default;
};
} // namespace g3
} // namespace nearby
} // namespace location
#endif // PLATFORM_IMPL_G3_SINGLE_THREAD_EXECUTOR_H_
@@ -0,0 +1,30 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "internal/platform/implementation/system_clock.h"
#include "absl/time/clock.h"
#include "internal/platform/exception.h"
namespace location {
namespace nearby {
absl::Time SystemClock::ElapsedRealtime() { return absl::Now(); }
Exception SystemClock::Sleep(absl::Duration duration) {
absl::SleepFor(duration);
return {Exception::kSuccess};
}
} // namespace nearby
} // namespace location
@@ -0,0 +1,97 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "internal/platform/implementation/g3/webrtc.h"
#include <memory>
#include "internal/platform/medium_environment.h"
#include "webrtc/api/task_queue/default_task_queue_factory.h"
namespace location {
namespace nearby {
namespace g3 {
WebRtcSignalingMessenger::WebRtcSignalingMessenger(
absl::string_view self_id, const connections::LocationHint& location_hint)
: self_id_(self_id), location_hint_(location_hint) {}
bool WebRtcSignalingMessenger::SendMessage(absl::string_view peer_id,
const ByteArray& message) {
auto& env = MediumEnvironment::Instance();
env.SendWebRtcSignalingMessage(peer_id, message);
return true;
}
bool WebRtcSignalingMessenger::StartReceivingMessages(
OnSignalingMessageCallback on_message_callback,
OnSignalingCompleteCallback on_complete_callback) {
auto& env = MediumEnvironment::Instance();
env.RegisterWebRtcSignalingMessenger(self_id_, on_message_callback,
on_complete_callback);
return true;
}
void WebRtcSignalingMessenger::StopReceivingMessages() {
auto& env = MediumEnvironment::Instance();
env.UnregisterWebRtcSignalingMessenger(self_id_);
}
WebRtcMedium::~WebRtcMedium() { single_thread_executor_.Shutdown(); }
const std::string WebRtcMedium::GetDefaultCountryCode() { return "US"; }
void WebRtcMedium::CreatePeerConnection(
webrtc::PeerConnectionObserver* observer, PeerConnectionCallback callback) {
auto& env = MediumEnvironment::Instance();
if (!env.GetUseValidPeerConnection()) {
callback(nullptr);
return;
}
webrtc::PeerConnectionInterface::RTCConfiguration rtc_config;
rtc_config.sdp_semantics = webrtc::SdpSemantics::kUnifiedPlan;
webrtc::PeerConnectionDependencies dependencies(observer);
std::unique_ptr<rtc::Thread> signaling_thread = rtc::Thread::Create();
signaling_thread->SetName("signaling_thread", nullptr);
RTC_CHECK(signaling_thread->Start()) << "Failed to start thread";
webrtc::PeerConnectionFactoryDependencies factory_dependencies;
factory_dependencies.task_queue_factory =
webrtc::CreateDefaultTaskQueueFactory();
factory_dependencies.signaling_thread = signaling_thread.release();
rtc::scoped_refptr<webrtc::PeerConnectionInterface> peer_connection =
webrtc::CreateModularPeerConnectionFactory(
std::move(factory_dependencies))
->CreatePeerConnection(rtc_config, std::move(dependencies));
single_thread_executor_.Execute(
[&env, callback = std::move(callback),
peer_connection = std::move(peer_connection)]() {
absl::SleepFor(env.GetPeerConnectionLatency());
callback(peer_connection);
});
}
std::unique_ptr<api::WebRtcSignalingMessenger>
WebRtcMedium::GetSignalingMessenger(
absl::string_view self_id, const connections::LocationHint& location_hint) {
return std::make_unique<WebRtcSignalingMessenger>(self_id, location_hint);
}
} // namespace g3
} // namespace nearby
} // namespace location
@@ -0,0 +1,81 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_IMPL_G3_WEBRTC_H_
#define PLATFORM_IMPL_G3_WEBRTC_H_
#include <memory>
#include "absl/strings/string_view.h"
#include "internal/platform/implementation/webrtc.h"
#include "internal/platform/implementation/g3/single_thread_executor.h"
#include "webrtc/api/peer_connection_interface.h"
namespace location {
namespace nearby {
namespace g3 {
class WebRtcSignalingMessenger : public api::WebRtcSignalingMessenger {
public:
using OnSignalingMessageCallback =
api::WebRtcSignalingMessenger::OnSignalingMessageCallback;
using OnSignalingCompleteCallback =
api::WebRtcSignalingMessenger::OnSignalingCompleteCallback;
explicit WebRtcSignalingMessenger(
absl::string_view self_id,
const connections::LocationHint& location_hint);
~WebRtcSignalingMessenger() override = default;
bool SendMessage(absl::string_view peer_id,
const ByteArray& message) override;
bool StartReceivingMessages(
OnSignalingMessageCallback on_message_callback,
OnSignalingCompleteCallback on_complete_callback) override;
void StopReceivingMessages() override;
private:
std::string self_id_;
connections::LocationHint location_hint_;
};
class WebRtcMedium : public api::WebRtcMedium {
public:
using PeerConnectionCallback = api::WebRtcMedium::PeerConnectionCallback;
WebRtcMedium() = default;
~WebRtcMedium() override;
const std::string GetDefaultCountryCode() override;
// Creates and returns a new webrtc::PeerConnectionInterface object via
// |callback|.
void CreatePeerConnection(webrtc::PeerConnectionObserver* observer,
PeerConnectionCallback callback) override;
// Returns a signaling messenger for sending WebRTC signaling messages.
std::unique_ptr<api::WebRtcSignalingMessenger> GetSignalingMessenger(
absl::string_view self_id,
const connections::LocationHint& location_hint) override;
private:
// Executor for handling calls to create a peer connection.
SingleThreadExecutor single_thread_executor_;
};
} // namespace g3
} // namespace nearby
} // namespace location
#endif // PLATFORM_IMPL_G3_WEBRTC_H_
@@ -0,0 +1,371 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "internal/platform/implementation/g3/wifi_lan.h"
#include <iostream>
#include <memory>
#include <string>
#include <utility>
#include "absl/strings/escaping.h"
#include "absl/strings/str_format.h"
#include "absl/synchronization/mutex.h"
#include "internal/platform/implementation/wifi_lan.h"
#include "internal/platform/cancellation_flag_listener.h"
#include "internal/platform/logging.h"
#include "internal/platform/medium_environment.h"
#include "internal/platform/nsd_service_info.h"
namespace location {
namespace nearby {
namespace g3 {
WifiLanSocket::~WifiLanSocket() {
absl::MutexLock lock(&mutex_);
DoClose();
}
void WifiLanSocket::Connect(WifiLanSocket& other) {
absl::MutexLock lock(&mutex_);
remote_socket_ = &other;
input_ = other.output_;
}
InputStream& WifiLanSocket::GetInputStream() {
auto* remote_socket = GetRemoteSocket();
CHECK(remote_socket != nullptr);
return remote_socket->GetLocalInputStream();
}
OutputStream& WifiLanSocket::GetOutputStream() {
return GetLocalOutputStream();
}
WifiLanSocket* WifiLanSocket::GetRemoteSocket() {
absl::MutexLock lock(&mutex_);
return remote_socket_;
}
bool WifiLanSocket::IsConnected() const {
absl::MutexLock lock(&mutex_);
return IsConnectedLocked();
}
bool WifiLanSocket::IsClosed() const {
absl::MutexLock lock(&mutex_);
return closed_;
}
Exception WifiLanSocket::Close() {
absl::MutexLock lock(&mutex_);
DoClose();
return {Exception::kSuccess};
}
void WifiLanSocket::DoClose() {
if (!closed_) {
remote_socket_ = nullptr;
output_->GetOutputStream().Close();
output_->GetInputStream().Close();
input_->GetOutputStream().Close();
input_->GetInputStream().Close();
closed_ = true;
}
}
bool WifiLanSocket::IsConnectedLocked() const { return input_ != nullptr; }
InputStream& WifiLanSocket::GetLocalInputStream() {
absl::MutexLock lock(&mutex_);
return output_->GetInputStream();
}
OutputStream& WifiLanSocket::GetLocalOutputStream() {
absl::MutexLock lock(&mutex_);
return output_->GetOutputStream();
}
std::string WifiLanServerSocket::GetName(const std::string& ip_address,
int port) {
std::string dot_delimited_string;
if (!ip_address.empty()) {
for (auto byte : ip_address) {
if (!dot_delimited_string.empty())
absl::StrAppend(&dot_delimited_string, ".");
absl::StrAppend(&dot_delimited_string, absl::StrFormat("%d", byte));
}
}
std::string out = absl::StrCat(dot_delimited_string, ":", port);
return out;
}
std::unique_ptr<api::WifiLanSocket> WifiLanServerSocket::Accept() {
absl::MutexLock lock(&mutex_);
while (!closed_ && pending_sockets_.empty()) {
cond_.Wait(&mutex_);
}
// whether or not we were running in the wait loop, return early if closed.
if (closed_) return {};
auto* remote_socket =
pending_sockets_.extract(pending_sockets_.begin()).value();
CHECK(remote_socket);
auto local_socket = std::make_unique<WifiLanSocket>();
local_socket->Connect(*remote_socket);
remote_socket->Connect(*local_socket);
cond_.SignalAll();
return local_socket;
}
bool WifiLanServerSocket::Connect(WifiLanSocket& socket) {
absl::MutexLock lock(&mutex_);
if (closed_) return false;
if (socket.IsConnected()) {
NEARBY_LOGS(ERROR)
<< "Failed to connect to WifiLan server socket: already connected";
return true; // already connected.
}
// add client socket to the pending list
pending_sockets_.insert(&socket);
cond_.SignalAll();
while (!socket.IsConnected()) {
cond_.Wait(&mutex_);
if (closed_) return false;
}
return true;
}
void WifiLanServerSocket::SetCloseNotifier(std::function<void()> notifier) {
absl::MutexLock lock(&mutex_);
close_notifier_ = std::move(notifier);
}
WifiLanServerSocket::~WifiLanServerSocket() {
absl::MutexLock lock(&mutex_);
DoClose();
}
Exception WifiLanServerSocket::Close() {
absl::MutexLock lock(&mutex_);
return DoClose();
}
Exception WifiLanServerSocket::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};
}
WifiLanMedium::WifiLanMedium() {
auto& env = MediumEnvironment::Instance();
env.RegisterWifiLanMedium(*this);
}
WifiLanMedium::~WifiLanMedium() {
auto& env = MediumEnvironment::Instance();
env.UnregisterWifiLanMedium(*this);
}
bool WifiLanMedium::StartAdvertising(const NsdServiceInfo& nsd_service_info) {
std::string service_type = nsd_service_info.GetServiceType();
NEARBY_LOGS(INFO) << "G3 WifiLan StartAdvertising: nsd_service_info="
<< &nsd_service_info
<< ", service_name=" << nsd_service_info.GetServiceName()
<< ", service_type=" << service_type;
{
absl::MutexLock lock(&mutex_);
if (advertising_info_.Existed(service_type)) {
NEARBY_LOGS(INFO)
<< "G3 WifiLan StartAdvertising: Can't start advertising because "
"service_type="
<< service_type << ", has started already.";
return false;
}
}
auto& env = MediumEnvironment::Instance();
env.UpdateWifiLanMediumForAdvertising(*this, nsd_service_info,
/*enabled=*/true);
{
absl::MutexLock lock(&mutex_);
advertising_info_.Add(service_type);
}
return true;
}
bool WifiLanMedium::StopAdvertising(const NsdServiceInfo& nsd_service_info) {
std::string service_type = nsd_service_info.GetServiceType();
NEARBY_LOGS(INFO) << "G3 WifiLan StopAdvertising: nsd_service_info="
<< &nsd_service_info
<< ", service_name=" << nsd_service_info.GetServiceName()
<< ", service_type=" << service_type;
{
absl::MutexLock lock(&mutex_);
if (!advertising_info_.Existed(service_type)) {
NEARBY_LOGS(INFO)
<< "G3 WifiLan StopAdvertising: Can't stop advertising because "
"we never started advertising for service_type="
<< service_type;
return false;
}
advertising_info_.Remove(service_type);
}
auto& env = MediumEnvironment::Instance();
env.UpdateWifiLanMediumForAdvertising(*this, nsd_service_info,
/*enabled=*/false);
return true;
}
bool WifiLanMedium::StartDiscovery(const std::string& service_type,
DiscoveredServiceCallback callback) {
NEARBY_LOGS(INFO) << "G3 WifiLan StartDiscovery: service_type="
<< service_type;
{
absl::MutexLock lock(&mutex_);
if (discovering_info_.Existed(service_type)) {
NEARBY_LOGS(INFO)
<< "G3 WifiLan StartDiscovery: Can't start discovery because "
"service_type="
<< service_type << " has started already.";
return false;
}
}
auto& env = MediumEnvironment::Instance();
env.UpdateWifiLanMediumForDiscovery(*this, std::move(callback), service_type,
true);
{
absl::MutexLock lock(&mutex_);
discovering_info_.Add(service_type);
}
return true;
}
bool WifiLanMedium::StopDiscovery(const std::string& service_type) {
NEARBY_LOGS(INFO) << "G3 WifiLan StopDiscovery: service_type="
<< service_type;
{
absl::MutexLock lock(&mutex_);
if (!discovering_info_.Existed(service_type)) {
NEARBY_LOGS(INFO)
<< "G3 WifiLan StopDiscovery: Can't stop discovering because we "
"never started discovering.";
return false;
}
discovering_info_.Remove(service_type);
}
auto& env = MediumEnvironment::Instance();
env.UpdateWifiLanMediumForDiscovery(*this, {}, service_type, false);
return true;
}
std::unique_ptr<api::WifiLanSocket> WifiLanMedium::ConnectToService(
const NsdServiceInfo& remote_service_info,
CancellationFlag* cancellation_flag) {
std::string service_type = remote_service_info.GetServiceType();
NEARBY_LOGS(INFO) << "G3 WifiLan ConnectToService [self]: medium=" << this
<< ", service_type=" << service_type;
return ConnectToService(remote_service_info.GetIPAddress(),
remote_service_info.GetPort(), cancellation_flag);
}
std::unique_ptr<api::WifiLanSocket> WifiLanMedium::ConnectToService(
const std::string& ip_address, int port,
CancellationFlag* cancellation_flag) {
std::string socket_name = WifiLanServerSocket::GetName(ip_address, port);
NEARBY_LOGS(INFO) << "G3 WifiLan ConnectToService [self]: medium=" << this
<< ", ip address + port=" << socket_name;
// First, find an instance of remote medium, that exposed this service.
auto& env = MediumEnvironment::Instance();
auto* remote_medium =
static_cast<WifiLanMedium*>(env.GetWifiLanMedium(ip_address, port));
if (!remote_medium) {
return {};
}
WifiLanServerSocket* server_socket = nullptr;
NEARBY_LOGS(INFO) << "G3 WifiLan ConnectToService [peer]: medium="
<< remote_medium
<< ", remote ip address + port=" << socket_name;
// Then, find our server socket context in this medium.
{
absl::MutexLock medium_lock(&remote_medium->mutex_);
auto item = remote_medium->server_sockets_.find(socket_name);
server_socket = item != server_sockets_.end() ? item->second : nullptr;
if (server_socket == nullptr) {
NEARBY_LOGS(ERROR)
<< "G3 WifiLan Failed to find WifiLan Server socket: socket_name="
<< socket_name;
return {};
}
}
if (cancellation_flag->Cancelled()) {
NEARBY_LOGS(ERROR) << "G3 WifiLan Connect: Has been cancelled: socket_name="
<< socket_name;
return {};
}
CancellationFlagListener listener(cancellation_flag, [&server_socket]() {
NEARBY_LOGS(INFO) << "G3 WifiLan Cancel Connect.";
if (server_socket != nullptr) {
server_socket->Close();
}
});
auto socket = std::make_unique<WifiLanSocket>();
// Finally, Request to connect to this socket.
if (!server_socket->Connect(*socket)) {
NEARBY_LOGS(ERROR) << "G3 WifiLan Failed to connect to existing WifiLan "
"Server socket: name="
<< socket_name;
return {};
}
NEARBY_LOGS(INFO) << "G3 WifiLan ConnectToService: connected: socket="
<< socket.get();
return socket;
}
std::unique_ptr<api::WifiLanServerSocket> WifiLanMedium::ListenForService(
int port) {
auto& env = MediumEnvironment::Instance();
auto server_socket = std::make_unique<WifiLanServerSocket>();
server_socket->SetIPAddress(env.GetFakeIPAddress());
server_socket->SetPort(port == 0 ? env.GetFakePort() : port);
std::string socket_name = WifiLanServerSocket::GetName(
server_socket->GetIPAddress(), server_socket->GetPort());
server_socket->SetCloseNotifier([this, socket_name]() {
absl::MutexLock lock(&mutex_);
server_sockets_.erase(socket_name);
});
NEARBY_LOGS(INFO) << "G3 WifiLan Adding server socket: medium=" << this
<< ", socket_name=" << socket_name;
absl::MutexLock lock(&mutex_);
server_sockets_.insert({socket_name, server_socket.get()});
return server_socket;
}
} // namespace g3
} // namespace nearby
} // namespace location
@@ -0,0 +1,285 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_IMPL_G3_WIFI_LAN_H_
#define PLATFORM_IMPL_G3_WIFI_LAN_H_
#include <memory>
#include <string>
#include <utility>
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
#include "absl/synchronization/mutex.h"
#include "internal/platform/implementation/wifi_lan.h"
#include "internal/platform/byte_array.h"
#include "internal/platform/input_stream.h"
#include "internal/platform/nsd_service_info.h"
#include "internal/platform/output_stream.h"
#include "internal/platform/implementation/g3/multi_thread_executor.h"
#include "internal/platform/implementation/g3/pipe.h"
namespace location {
namespace nearby {
namespace g3 {
class WifiLanMedium;
class WifiLanSocket : public api::WifiLanSocket {
public:
WifiLanSocket() = default;
~WifiLanSocket() override;
// Connect to another WifiLanSocket, to form a functional low-level channel.
// from this point on, and until Close is called, connection exists.
void Connect(WifiLanSocket& other) ABSL_LOCKS_EXCLUDED(mutex_);
// Returns the InputStream of this connected WifiLanSocket.
InputStream& GetInputStream() override ABSL_LOCKS_EXCLUDED(mutex_);
// Returns the OutputStream of this connected WifiLanSocket.
// This stream is for local side to write.
OutputStream& GetOutputStream() override ABSL_LOCKS_EXCLUDED(mutex_);
// Returns address of a remote WifiLanSocket or nullptr.
WifiLanSocket* 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_);
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_;
WifiLanSocket* remote_socket_ ABSL_GUARDED_BY(mutex_) = nullptr;
bool closed_ ABSL_GUARDED_BY(mutex_) = false;
};
class WifiLanServerSocket : public api::WifiLanServerSocket {
public:
static std::string GetName(const std::string& ip_address, int port);
~WifiLanServerSocket() override;
// Gets ip address.
std::string GetIPAddress() const override ABSL_LOCKS_EXCLUDED(mutex_) {
absl::MutexLock lock(&mutex_);
return ip_address_;
}
// Sets the ip address.
void SetIPAddress(const std::string& ip_address) ABSL_LOCKS_EXCLUDED(mutex_) {
absl::MutexLock lock(&mutex_);
ip_address_ = ip_address;
}
// Gets the port.
int GetPort() const override ABSL_LOCKS_EXCLUDED(mutex_) {
absl::MutexLock lock(&mutex_);
return port_;
}
// Sets the port.
void SetPort(int port) ABSL_LOCKS_EXCLUDED(mutex_) {
absl::MutexLock lock(&mutex_);
port_ = port;
}
// 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 WifiLanSocket to the server side.
// If not null, returned socket is connected to its remote (client-side) peer.
std::unique_ptr<api::WifiLanSocket> Accept() override
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(WifiLanSocket& socket) ABSL_LOCKS_EXCLUDED(mutex_);
// Called by the server side of a connection before passing ownership of
// WifiLanServerSocker 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() override ABSL_LOCKS_EXCLUDED(mutex_);
private:
Exception DoClose() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
mutable absl::Mutex mutex_;
std::string ip_address_ ABSL_GUARDED_BY(mutex_);
int port_ ABSL_GUARDED_BY(mutex_);
absl::CondVar cond_;
absl::flat_hash_set<WifiLanSocket*> 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 WifiLan medium.
class WifiLanMedium : public api::WifiLanMedium {
public:
WifiLanMedium();
~WifiLanMedium() override;
// Starts WifiLan advertising.
//
// nsd_service_info - NsdServiceInfo data that's advertised through mDNS
// service.
// On success if the service is now advertising.
// On error if the service cannot start to advertise or the service type in
// NsdServiceInfo has been passed previously which StopAdvertising is not
// been called.
bool StartAdvertising(const NsdServiceInfo& nsd_service_info) override
ABSL_LOCKS_EXCLUDED(mutex_);
// Stops WifiLan advertising.
//
// nsd_service_info - NsdServiceInfo data that's advertised through mDNS
// service.
// On success if the service stops advertising.
// On error if the service cannot stop advertising or the service type in
// NsdServiceInfo cannot be found.
bool StopAdvertising(const NsdServiceInfo& nsd_service_info) override
ABSL_LOCKS_EXCLUDED(mutex_);
// Starts the discovery of nearby WifiLan services.
//
// Returns true once the WifiLan discovery has been initiated. The
// service_type is associated with callback.
bool StartDiscovery(const std::string& service_type,
DiscoveredServiceCallback callback) override
ABSL_LOCKS_EXCLUDED(mutex_);
// Stops the discovery of nearby WifiLan services.
//
// service_type - The one assigend in StartDiscovery.
// On success if service_type is matched to the callback and will be removed
// from the list. If list is empty then stops the WifiLan discovery
// service.
// On error if the service_type is not existed, then return immediately.
bool StopDiscovery(const std::string& service_type) override
ABSL_LOCKS_EXCLUDED(mutex_);
// Connects to a WifiLan service.
// On success, returns a new WifiLanSocket.
// On error, returns nullptr.
std::unique_ptr<api::WifiLanSocket> ConnectToService(
const NsdServiceInfo& remote_service_info,
CancellationFlag* cancellation_flag) override ABSL_LOCKS_EXCLUDED(mutex_);
// Connects to a WifiLan service by ip address and port.
// On success, returns a new WifiLanSocket.
// On error, returns nullptr.
std::unique_ptr<api::WifiLanSocket> ConnectToService(
const std::string& ip_address, int port,
CancellationFlag* cancellation_flag) override ABSL_LOCKS_EXCLUDED(mutex_);
// Listens for incoming connection.
//
// port - A port number.
// 0 : use a random port.
// 1~65536 : open a server socket on that exact port.
// On success, returns a new WifiLanServerSocket.
// On error, returns nullptr.
std::unique_ptr<api::WifiLanServerSocket> ListenForService(int port) override
ABSL_LOCKS_EXCLUDED(mutex_);
// Returns the port range as a pair of min and max port.
absl::optional<std::pair<std::int32_t, std::int32_t>> GetDynamicPortRange()
override {
return std::make_pair(49152, 65535);
}
private:
struct AdvertisingInfo {
bool Empty() const { return service_types.empty(); }
void Clear() { service_types.clear(); }
void Add(const std::string& service_type) {
service_types.insert(service_type);
}
void Remove(const std::string& service_type) {
service_types.erase(service_type);
}
bool Existed(const std::string& service_type) const {
return service_types.contains(service_type);
}
absl::flat_hash_set<std::string> service_types;
};
struct DiscoveringInfo {
bool Empty() const { return service_types.empty(); }
void Clear() { service_types.clear(); }
void Add(const std::string& service_type) {
service_types.insert(service_type);
}
void Remove(const std::string& service_type) {
service_types.erase(service_type);
}
bool Existed(const std::string& service_type) const {
return service_types.contains(service_type);
}
absl::flat_hash_set<std::string> service_types;
};
absl::Mutex mutex_;
AdvertisingInfo advertising_info_ ABSL_GUARDED_BY(mutex_);
DiscoveringInfo discovering_info_ ABSL_GUARDED_BY(mutex_);
absl::flat_hash_map<std::string, WifiLanServerSocket*> server_sockets_
ABSL_GUARDED_BY(mutex_);
};
} // namespace g3
} // namespace nearby
} // namespace location
#endif // PLATFORM_IMPL_G3_WIFI_LAN_H_
@@ -0,0 +1,40 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_API_INPUT_FILE_H_
#define PLATFORM_API_INPUT_FILE_H_
#include <cstdint>
#include "internal/platform/byte_array.h"
#include "internal/platform/exception.h"
#include "internal/platform/input_stream.h"
namespace location {
namespace nearby {
namespace api {
// An InputFile represents a readable file on the system.
class InputFile : public InputStream {
public:
~InputFile() override = default;
virtual std::string GetFilePath() const = 0;
virtual std::int64_t GetTotalSize() const = 0;
};
} // namespace api
} // namespace nearby
} // namespace location
#endif // PLATFORM_API_INPUT_FILE_H_
@@ -0,0 +1,72 @@
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
load("//tools/build_defs/apple:ios.bzl", "ios_static_framework")
licenses(["notice"])
package(default_visibility = ["//visibility:public"])
objc_library(
name = "Connections",
srcs = [
"Source/Internal/GNCAdvertiser.mm",
"Source/Internal/GNCCore.mm",
"Source/Internal/GNCCoreConnection.mm",
"Source/Internal/GNCDiscoverer.mm",
"Source/Internal/GNCPayload.mm",
"Source/Internal/GNCPayloadListener.mm",
"Source/Internal/GNCUtils.mm",
"Source/Internal/platform.mm",
],
hdrs = [
"Source/GNCAdvertiser.h",
"Source/GNCConnection.h",
"Source/GNCConnections.h",
"Source/GNCDiscoverer.h",
"Source/GNCPayload.h",
"Source/Internal/GNCCore.h",
"Source/Internal/GNCCoreConnection.h",
"Source/Internal/GNCPayload+Internal.h",
"Source/Internal/GNCPayloadListener.h",
"Source/Internal/GNCUtils.h",
],
deps = [
"//connections:core",
"//connections:core_types",
"//internal/platform/implementation/ios/Source/Mediums",
"//internal/platform/implementation/ios/Source/Platform",
"//internal/platform/implementation/ios/Source/Shared",
"@com_google_absl//absl/functional:bind_front",
"//third_party/objective_c/google_toolbox_for_mac:GTM_Logger",
],
)
MIN_IOS_VERSION = "12.0"
HDRS_POD = [
"Source/GNCAdvertiser.h",
"Source/GNCConnection.h",
"Source/GNCDiscoverer.h",
"Source/GNCPayload.h",
]
ios_static_framework(
name = "NearbyConnections_framework",
hdrs = HDRS_POD,
bundle_name = "NearbyConnections",
minimum_os_version = MIN_IOS_VERSION,
deps = [
":Connections",
],
)
Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

@@ -0,0 +1,355 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objects = {
/* Begin PBXBuildFile section */
41B7A2C3208F900100EBA53E /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 41B7A2C2208F900100EBA53E /* AppDelegate.m */; };
41B7A2C6208F900100EBA53E /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 41B7A2C5208F900100EBA53E /* ViewController.m */; };
41B7A2C9208F900100EBA53E /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 41B7A2C7208F900100EBA53E /* Main.storyboard */; };
41B7A2CB208F900200EBA53E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 41B7A2CA208F900200EBA53E /* Assets.xcassets */; };
41B7A2CE208F900200EBA53E /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 41B7A2CC208F900200EBA53E /* LaunchScreen.storyboard */; };
41B7A2D1208F900200EBA53E /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 41B7A2D0208F900200EBA53E /* main.m */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
41B7A2BE208F900100EBA53E /* NearbyConnectionsExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NearbyConnectionsExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
41B7A2C1208F900100EBA53E /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
41B7A2C2208F900100EBA53E /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
41B7A2C4208F900100EBA53E /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = "<group>"; };
41B7A2C5208F900100EBA53E /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = "<group>"; };
41B7A2C8208F900100EBA53E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
41B7A2CA208F900200EBA53E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
41B7A2CD208F900200EBA53E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
41B7A2CF208F900200EBA53E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
41B7A2D0208F900200EBA53E /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
41B7A2BB208F900100EBA53E /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
41B7A2B5208F900000EBA53E = {
isa = PBXGroup;
children = (
41B7A2C0208F900100EBA53E /* NearbyConnectionsExample */,
41B7A2BF208F900100EBA53E /* Products */,
);
sourceTree = "<group>";
};
41B7A2BF208F900100EBA53E /* Products */ = {
isa = PBXGroup;
children = (
41B7A2BE208F900100EBA53E /* NearbyConnectionsExample.app */,
);
name = Products;
sourceTree = "<group>";
};
41B7A2C0208F900100EBA53E /* NearbyConnectionsExample */ = {
isa = PBXGroup;
children = (
41B7A2C1208F900100EBA53E /* AppDelegate.h */,
41B7A2C2208F900100EBA53E /* AppDelegate.m */,
41B7A2C4208F900100EBA53E /* ViewController.h */,
41B7A2C5208F900100EBA53E /* ViewController.m */,
41B7A2C7208F900100EBA53E /* Main.storyboard */,
41B7A2CA208F900200EBA53E /* Assets.xcassets */,
41B7A2CC208F900200EBA53E /* LaunchScreen.storyboard */,
41B7A2CF208F900200EBA53E /* Info.plist */,
41B7A2D0208F900200EBA53E /* main.m */,
);
path = NearbyConnectionsExample;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
41B7A2BD208F900100EBA53E /* NearbyConnectionsExample */ = {
isa = PBXNativeTarget;
buildConfigurationList = 41B7A2D4208F900200EBA53E /* Build configuration list for PBXNativeTarget "NearbyConnectionsExample" */;
buildPhases = (
41B7A2BA208F900100EBA53E /* Sources */,
41B7A2BB208F900100EBA53E /* Frameworks */,
41B7A2BC208F900100EBA53E /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = NearbyConnectionsExample;
productName = NearbyConnectionsExample;
productReference = 41B7A2BE208F900100EBA53E /* NearbyConnectionsExample.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
41B7A2B6208F900000EBA53E /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0930;
ORGANIZATIONNAME = Google;
TargetAttributes = {
41B7A2BD208F900100EBA53E = {
CreatedOnToolsVersion = 9.3;
};
};
};
buildConfigurationList = 41B7A2B9208F900000EBA53E /* Build configuration list for PBXProject "NearbyConnectionsExample" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 41B7A2B5208F900000EBA53E;
productRefGroup = 41B7A2BF208F900100EBA53E /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
41B7A2BD208F900100EBA53E /* NearbyConnectionsExample */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
41B7A2BC208F900100EBA53E /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
41B7A2CE208F900200EBA53E /* LaunchScreen.storyboard in Resources */,
41B7A2CB208F900200EBA53E /* Assets.xcassets in Resources */,
41B7A2C9208F900100EBA53E /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
41B7A2BA208F900100EBA53E /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
41B7A2C6208F900100EBA53E /* ViewController.m in Sources */,
41B7A2D1208F900200EBA53E /* main.m in Sources */,
41B7A2C3208F900100EBA53E /* AppDelegate.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
41B7A2C7208F900100EBA53E /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
41B7A2C8208F900100EBA53E /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
41B7A2CC208F900200EBA53E /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
41B7A2CD208F900200EBA53E /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
41B7A2D2208F900200EBA53E /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_BITCODE = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.3;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
name = Debug;
};
41B7A2D3208F900200EBA53E /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_BITCODE = NO;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.3;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
41B7A2D5208F900200EBA53E /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)",
);
INFOPLIST_FILE = NearbyConnectionsExample/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.google.NearbyConnectionsExample;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE = "";
PROVISIONING_PROFILE_SPECIFIER = "";
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
41B7A2D6208F900200EBA53E /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = "";
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)",
);
INFOPLIST_FILE = NearbyConnectionsExample/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.google.NearbyConnectionsExample;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
41B7A2B9208F900000EBA53E /* Build configuration list for PBXProject "NearbyConnectionsExample" */ = {
isa = XCConfigurationList;
buildConfigurations = (
41B7A2D2208F900200EBA53E /* Debug */,
41B7A2D3208F900200EBA53E /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
41B7A2D4208F900200EBA53E /* Build configuration list for PBXNativeTarget "NearbyConnectionsExample" */ = {
isa = XCConfigurationList;
buildConfigurations = (
41B7A2D5208F900200EBA53E /* Debug */,
41B7A2D6208F900200EBA53E /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 41B7A2B6208F900000EBA53E /* Project object */;
}
@@ -0,0 +1,34 @@
//
// Copyright (c) 2021 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// AppDelegate.h
// NearbyConnectionsExample
//
#import <UIKit/UIKit.h>
/**
* A delegate for NSApplication to handle notifications about app launch and
* shutdown. Owned by the application object.
*/
@interface AppDelegate : UIResponder <UIApplicationDelegate>
/**
* Main screen window displayed to the user which contains any active view
* hierarchy.
*/
@property(nonatomic) UIWindow *window;
@end
@@ -0,0 +1,23 @@
//
// Copyright (c) 2021 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// AppDelegate.m
// NearbyConnectionsExample
//
#import "AppDelegate.h"
@implementation AppDelegate
@end
@@ -0,0 +1,98 @@
{
"images" : [
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "3x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "1x"
},
{
"idiom" : "ipad",
"size" : "76x76",
"scale" : "2x"
},
{
"idiom" : "ipad",
"size" : "83.5x83.5",
"scale" : "2x"
},
{
"idiom" : "ios-marketing",
"size" : "1024x1024",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
@@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" systemVersion="17A277" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
</document>
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14113" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="fos-Uz-R9B">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="BVh-fg-s4x">
<objects>
<viewController id="Tuj-3T-cB2" customClass="ViewController" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="9NC-2J-1xS">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<viewLayoutGuide key="safeArea" id="wwJ-gb-kZn"/>
</view>
<navigationItem key="navigationItem" id="ega-4i-ebx"/>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="xJQ-q8-wWg" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="433" y="-133"/>
</scene>
<!--Navigation Controller-->
<scene sceneID="Jcj-tp-czJ">
<objects>
<navigationController id="fos-Uz-R9B" sceneMemberID="viewController">
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="x6x-US-muI">
<rect key="frame" x="0.0" y="20" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<connections>
<segue destination="Tuj-3T-cB2" kind="relationship" relationship="rootViewController" id="Xa2-HD-mUn"/>
</connections>
</navigationController>
<placeholder placeholderIdentifier="IBFirstResponder" id="99I-fh-ObP" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-455" y="-132"/>
</scene>
</scenes>
</document>
@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>NSLocalNetworkUsageDescription</key>
<string>Exchange data with nearby devices running the NearbyConnectionsExmaple app.</string>
<key>NSBonjourServices</key>
<array>
<string>_54167B379724._tcp</string>
</array>
</dict>
</plist>
@@ -0,0 +1,24 @@
//
// Copyright (c) 2021 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ViewController.h
// NearbyConnectionsExample
//
#import <UIKit/UIKit.h>
/** View controller for demo by loading NearbyConnections lib for advertiser and discoverer. */
@interface ViewController : UIViewController
@end
@@ -0,0 +1,291 @@
//
// Copyright (c) 2021 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// ViewController.m
// NearbyConnectionsExample
//
#import "ViewController.h"
#import <NearbyConnections/NearbyConnections.h>
NS_ASSUME_NONNULL_BEGIN
static NSString *kServiceId = @"com.google.NearbyConnectionsExample";
static NSString *kCellIdentifier = @"endpointCell";
// Simplified version of dispatch_after.
void delay(NSTimeInterval delay, dispatch_block_t block) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)),
dispatch_get_main_queue(), block);
}
// This class contains info about a discovered endpoint.
@interface EndpointInfo : NSObject
@property(nonatomic, readonly) id<GNCDiscoveredEndpointInfo> discInfo;
@property(nonatomic, nullable) id<GNCConnection> connection;
@end
@implementation EndpointInfo
- (instancetype)initWithDiscoveredInfo:(id<GNCDiscoveredEndpointInfo>)discInfo {
self = [super init];
if (self) {
_discInfo = discInfo;
}
return self;
}
@end
@interface ViewController () <UITableViewDataSource, UITableViewDelegate>
@property(nonatomic) GNCAdvertiser *advertiser;
@property(nonatomic) GNCDiscoverer *discoverer;
@property(nonatomic) NSMutableDictionary<GNCEndpointId, EndpointInfo *> *endpoints;
@property(nonatomic, readonly) NSData *ping;
@property(nonatomic, readonly) NSData *pong;
@property(nonatomic) UITableView *tableView;
@property(nonatomic) UITextView *statusView;
@property(nonatomic) NSMutableDictionary<GNCEndpointId, id<GNCConnection> > *incomingConnections;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self makeViews];
// Enable "info" log messages in the release build.
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"GTMVerboseLogging"];
self.title = [[UIDevice currentDevice] name];
NSData *endpointInfo = [self.title dataUsingEncoding:NSUTF8StringEncoding];
_endpoints = [NSMutableDictionary dictionary];
_ping = [@"ping" dataUsingEncoding:NSUTF8StringEncoding];
_pong = [@"pong" dataUsingEncoding:NSUTF8StringEncoding];
_incomingConnections = [NSMutableDictionary dictionary];
// The advertiser.
_advertiser = [GNCAdvertiser
advertiserWithEndpointInfo:endpointInfo
serviceId:kServiceId
strategy:GNCStrategyCluster
connectionInitiationHandler:^(GNCEndpointId endpointId,
id<GNCAdvertiserConnectionInfo> advConnInfo,
GNCConnectionResponseHandler responseHandler) {
// Show a status that a discoverer has requested a connection.
[self logStatus:@"Accepting connection request" final:NO];
// Accept the connection request.
responseHandler(GNCConnectionResponseAccept);
return [GNCConnectionResultHandlers
successHandler:^(id<GNCConnection> connection) {
// Save the connection until the ping-pong sequence is done.
self.incomingConnections[endpointId] = connection;
__block BOOL receivedPong = NO;
// Send a ping, expecting the remote endpoint to send a pong.
[self logStatus:@"Connection established; sending ping" final:NO];
[connection sendBytesPayload:[GNCBytesPayload payloadWithBytes:self.ping]
completion:^(GNCPayloadResult result) {
if (result == GNCPayloadResultSuccess) {
[self logStatus:@"Sent ping; waiting for pong" final:NO];
// Show an error if the pong isn't received in the expected
// timeframe.
delay(3.0, ^{
if (receivedPong) {
[self logStatus:@"Error: Didn't receive pong" final:YES];
[self.incomingConnections
removeObjectForKey:endpointId]; // close the connection
}
});
} else {
[self logStatus:@"Error: Failed to send ping" final:YES];
}
}];
// Return handlers for incoming payloads.
return [GNCConnectionHandlers handlersWithBuilder:^(GNCConnectionHandlers *handlers) {
handlers.bytesPayloadHandler = ^(GNCBytesPayload *payload) {
receivedPong = NO;
[self.incomingConnections removeObjectForKey:endpointId]; // close the connection
// Show a status of whether the pong was received.
[self logStatus:[payload.bytes isEqual:self.pong] ? @"Received pong"
: @"Error: Payload is not pong"
final:YES];
};
}];
}
failureHandler:^(GNCConnectionFailure result) {
[self
logStatus:(result == GNCConnectionFailureRejected) ? @"Error: Connection rejected"
: @"Error: Connection failed"
final:YES];
}];
}];
// The discoverer.
__weak typeof(self) weakSelf = self;
_discoverer = [GNCDiscoverer
discovererWithServiceId:kServiceId
strategy:GNCStrategyCluster
endpointFoundHandler:^(GNCEndpointId endpointId,
id<GNCDiscoveredEndpointInfo> endpointInfo) {
typeof(self) self = weakSelf;
// An endpoint was discovered; add it to the endpoint list and UI.
self.endpoints[endpointId] = [[EndpointInfo alloc] initWithDiscoveredInfo:endpointInfo];
[self.tableView reloadData];
// Return the lost handler for this endpoint.
return ^{
typeof(self) self = weakSelf; // shadow
// Endpoint disappeared; remove it from the endpoint list and UI.
[self.endpoints removeObjectForKey:endpointId];
[self.tableView reloadData];
};
}];
}
#pragma mark - UITableViewDelegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// The user tapped on a cell; request a connection with it.
EndpointInfo *info = _endpoints[_endpoints.allKeys[indexPath.row]];
if (!info) return;
void (^deselectRow)(void) = ^{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
};
[self logStatus:@"Requesting connection" final:NO];
info.discInfo.requestConnection(
self.title,
^(id<GNCDiscovererConnectionInfo> discConnInfo,
GNCConnectionResponseHandler responseHandler) {
// Accept the auth token.
[self logStatus:@"Accepting auth token" final:NO];
responseHandler(GNCConnectionResponseAccept);
return ^(id<GNCConnection> connection) {
// Save the connection until the ping-pong sequence is done.
info.connection = connection;
__block BOOL receivedPing = NO;
[self logStatus:@"Connection established; waiting for ping" final:NO];
// Show an error if the ping isn't received in the expected timeframe.
delay(3.0, ^{
if (!receivedPing) {
deselectRow();
[self logStatus:@"Error: Didn't receive ping" final:YES];
info.connection = nil; // close the connection
}
});
// Return handlers for incoming payloads.
__weak id<GNCConnection> weakConnection = connection; // avoid a retain cycle
return [GNCConnectionHandlers handlersWithBuilder:^(GNCConnectionHandlers *handlers) {
handlers.bytesPayloadHandler = ^(GNCBytesPayload *payload) {
receivedPing = YES;
// If a ping was received, send a pong back to the advertiser.
if ([payload.bytes isEqual:self.ping]) {
[self logStatus:@"Received ping; sending pong" final:NO];
[weakConnection sendBytesPayload:[GNCBytesPayload payloadWithBytes:self.pong]
completion:^(GNCPayloadResult result) {
deselectRow();
[self logStatus:(result == GNCPayloadResultSuccess)
? @"Sent pong"
: @"Error: Failed to send pong"
final:YES];
// Pong was sent, so close the connection.
info.connection = nil;
}];
} else {
deselectRow();
[self logStatus:@"Error: Payload is not ping" final:YES];
}
};
}];
};
},
^(GNCConnectionFailure result) {
// Connection failed.
deselectRow();
[self logStatus:(result == GNCConnectionFailureRejected) ? @"Connection rejected"
: @"Connection failed"
final:YES];
});
}
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellIdentifier
forIndexPath:indexPath];
cell.textLabel.text = _endpoints[_endpoints.allKeys[indexPath.row]].discInfo.name;
return cell;
}
#pragma mark - UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [_endpoints.allKeys count];
}
#pragma mark - Private
- (void)makeViews {
_tableView = [[UITableView alloc] initWithFrame:self.view.frame style:UITableViewStylePlain];
[_tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:kCellIdentifier];
_tableView.delegate = self;
_tableView.dataSource = self;
_tableView.rowHeight = 48;
_tableView.scrollEnabled = YES;
_tableView.showsVerticalScrollIndicator = YES;
_tableView.userInteractionEnabled = YES;
_tableView.bounces = YES;
_tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.view addSubview:_tableView];
// Make the status view.
UITextView * (^newTextView)(CGRect) = ^(CGRect frame) {
UITextView *textView = [[UITextView alloc] initWithFrame:frame];
textView.autoresizingMask =
UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
textView.layer.borderColor = [UIColor blackColor].CGColor;
textView.layer.borderWidth = 1;
textView.editable = NO;
textView.textContainerInset = UIEdgeInsetsZero;
return textView;
};
CGRect selfFrame = self.view.frame;
static const int kStatusHeight = 144;
CGRect statusFrame = (CGRect){{selfFrame.origin.x + 4, selfFrame.size.height - kStatusHeight},
{selfFrame.size.width - 8, kStatusHeight - 4}};
_statusView = newTextView(statusFrame);
[self.view addSubview:_statusView];
}
- (void)logStatus:(NSString *)status final:(BOOL)final {
_statusView.text = [NSString stringWithFormat:@"%@\n%@%@", _statusView.text, status,
final ? @"\n–––––––––––––––––––––––––" : @""];
[_statusView scrollRangeToVisible:NSMakeRange(_statusView.text.length - 1, 1)];
}
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,27 @@
//
// Copyright (c) 2021 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// main.m
// NearbyConnectionsExample
//
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
@@ -0,0 +1,198 @@
# Nearby Connections Sample App for iOS
This is a sample app for third party developers using the Nearby Connections
library. On startup, it advertises and discovers. Discovered advertisers are
added to the list in the UI. When the user taps on an advertiser in the list,
the discoverer requests a connection with it. When the connection is
established, the advertiser sends a "ping" payload to the discoverer, which
sends "pong" payload back to the advertiser. The connection is then closed.
## Setup
1.Get the NearbyConnections_framework.zip from https://github.com/google/nearby/releases/tag/v0.0.1-ios, unzip it,
and put your unzipped folder under your project folder. The directory structure
looks like:
```
/NearbyConnectionsExample
/NearbyConnectionsExample
NearbyConnectionsExample.xcodeproj
README.md
/NearbyConnections.framework
```
2.Import NearbyConnections.framework
- In Xcode, click the NearbyConnectionsExample in the left pane. And click the one in TARGETS-NearbyConnectionsExample at the left of right pane and the Build Phases at the right.
- See the **Link Binary With Libraries**, and press **+** to import the file - **libc++.tbd**.
- In Add **Other…**, import the NearbyConnections.framework folder which was unzipped.
![Import framework in Xcode](./XcodeSetup.png)
3.Update info.plist:
- add **NSLocalNetworkUsageDescription** key with a description of your usage of Nearby Connections.
- add **NSBonjourServices** key.
for NSBonjourServices key, add the bonjour type name: `_54167B379724._tcp`
> **54167B379724** is the 6 byte hash of service id **com.google.NearbyConnectionsExample**
```
// NearbyConnectionsExample/info.plist
...
<key>NSLocalNetworkUsageDescription</key>
<string>Exchange data with nearby devices running the NearbyConnectionsExmaple app.</string>
<key>NSBonjourServices</key>
<array>
<string>_54167B379724._tcp</string>
</array>
...
```
4.Add service id and import `<NearbyConnections/NearbyConnections.h>` in your main view controller.
```
// NearbyConnectionsExample/ViewController.m
static NSString *kServiceId = @"com.google.NearbyConnectionsExample";
#import <NearbyConnections/NearbyConnections.h>
```
## Code Snippets
Note: All of the callbacks in this library use blocks rather than delegates. Be careful to avoid retain cycles in your block implementations. See the [Apple documentation](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmPractical.html#//apple_ref/doc/uid/TP40004447-SW1) describing how to avoid retain cycles.
Here is the skeleton code for an advertiser:
```objc
_advertiser = [GNCAdvertiser
advertiserWithEndpointInfo:endpointInfo
serviceId:myServiceId
strategy:GNCStrategyCluster
connectionInitiationHandler:^(GNCEndpointId endpointId,
id<GNCAdvertiserConnectionInfo> connectionInfo,
GNCConnectionResponseHandler responseHandler) {
// Decide whether to accept or reject the connection. The following code would normally
// exist in the callback for an alert, for instance.
if (/* user rejected */) {
responseHandler(GNCConnectionResponseReject); // the user rejected the invitation
} else {
responseHandler(GNCConnectionResponseAccept); // the user accepted the invitation
// Return connection result handlers, one of which is called depending on
// whether a successful connection was made.
return [GNCConnectionResultHandlers successHandler:^(id<GNCConnection> connection) {
// A successful connection was made. Save the connection somewhere, which can
// be used to send payloads to the remote endpoint.
// Return the incoming payload and disconnect handlers.
return [GNCConnectionHandlers handlersWithBuilder:^(GNCConnectionHandlers *handlers) {
// Optionally set the Bytes payload handler.
handlers.bytesPayloadHandler = ^(GNCBytesPayload *payload) {
// Process the payload received from the remote endpoint.
};
// Optionally set the Stream payload handler.
handlers.streamPayloadHandler = ^(GNCStreamPayload *payload, NSProgress *progress) {
// Receipt of a Stream payload has started. Input can be read from the payloads
// NSInputStream, and progress/cancellation is handled via the NSProgress object.
return ^(GNCPayloadResult result) {
if (result == GNCPayloadResultSuccess) {
// The payload has been successfully received.
}
};
};
// Optionally set the disconnected handler.
handlers.disconnectedHandler = ^(GNCDisconnectedReason reason) {
// The connection was severed by either endpoint or lost.
};
}
failureHandler:^(GNCConnectionFailure result) {
// Failed to make the connection.
}]);
}
}];
```
Here is the skeleton code for a discoverer:
```objc
_discoverer =
[GNCDiscoverer discovererWithServiceId:myServiceId
strategy:GNCStrategyCluster
endpointFoundHandler:^(GNCEndpointId endpointId,
id<GNCDiscoveredEndpointInfo> discEndpointInfo) {
// An endpoint was found. Typically you would add it to a list of nearby endpoints
// displayed in a UITableView, for instance.
// The following code shows how to request a connection with the endpoint. This code
// would normally exist in the -didSelectRowAtIndexPath: of UITableViewDelegate.
if (/* user wants to request a connection */) {
requestHandler(myName,
// This block is called once an authentication string is generated between the endpoints.
^(id<GNCDiscovererConnectionInfo> discConnInfo, GNCConnectionResponseHandler responseHandler) {
// Ask the user to confirm the authentication string.
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Accept auth?" ...];
[alert addAction:[UIAlertAction actionWithTitle:@"OK"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
responseHandler(GNCConnectionResponseAccept);
}]];
[alert addAction:[UIAlertAction actionWithTitle:@"Cancel"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
responseHandler(GNCConnectionResponseReject);
}]];
[self presentViewController:alert animated:YES completion:^{}];
// Return a block that's called if the connection was successful.
return ^(id<GNCConnection> connection) {
// A successful connection was made. Save the connection somewhere, which can
// be used to send payloads to the remote endpoint.
// Return incoming data handlers as in the advertiser example above.
return [GNCConnectionHandlers handlersWithBuilder:^(GNCConnectionHandlers *handlers) {
// Set up payload and disconnect handlers here as in the advertiser example above.
}];
};
},
// This block is called if the connection failed for any reason.
^(GNCConnectionFailure result) {
// Typically an alert would be shown here explaining why the connection failed.
});
}
// Return the endpoint-lost handler, which is called when the endpoint goes out of range
// or stops advertising.
return ^{
// The endpoint disappeared.
};
}];
```
Here is an example of how to send a Bytes payload. The returned NSProgress object can be passed to UIProgressView to display a progress bar.
```objc
NSProgress *progress = [connection
sendBytesPayload:[GNCBytesPayload payloadWithBytes:someData]
completion:^(GNCPayloadResult result) {
// Check status to see if it was successfully sent.
}];
```
## Build and run
![Sucessful running screenshot](./NearbyConnectionsExample.png)
If you meet the following error in the debug panel of Xcode, you likely need to set up the keys listed in step 3 **Update info.plist**, as well as the service type.
```
1970-01-01 00:00:00.000 NearbyConnectionsExample[1383/0x16d87b000] [lvl=1] -[GNCMBonjourService netService:didNotPublish:] Error publishing: service: <NSNetService 0x283a18920> local _307BEAB11028._tcp. IjFQWEUwe-oAAA 51898, errorDic: {
NSNetServicesErrorCode = "-72008";
NSNetServicesErrorDomain = 10;
}
```
---
NOTE: The iOS simulator is unstable when advertising. We recommend using an real iOS device.
Binary file not shown.

After

Width:  |  Height:  |  Size: 698 KiB

@@ -0,0 +1,84 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
#import "GNCConnection.h"
NS_ASSUME_NONNULL_BEGIN
/** This contains info about a discoverer endpoint intitiating a connection with an advertiser. */
@protocol GNCAdvertiserConnectionInfo <NSObject>
/** This is a human readable name of the discoverer. */
@property(nonatomic, readonly, copy) NSString *name;
/** This token can be used to verify the identity of the discoverer. */
@property(nonatomic, readonly, copy) NSString *authToken;
@end
/** This class contains success and failure handlers for the connection request. */
@interface GNCConnectionResultHandlers : NSObject
/**
* This factory method creates a pair of handlers for a successful or failed connection.
*
* @param successHandler This handler is called if both endpoints accept the connection.
* A @c GNCConnection object is passed, meaning that the connection has
* been established and you may start sending and receiving payloads.
* @param failureHandler This handler is called if either endpoint rejects the connection.
*/
+ (instancetype)successHandler:(GNCConnectionHandler)successHandler
failureHandler:(GNCConnectionFailureHandler)failureHandler;
@end
/**
* This handler is called when a discoverer requests a connection with an advertiser. In
* response, the advertiser should accept or reject via @c responseHandler.
*
* @param endpointId The ID of the endpoint.
* @param connectionInfo Information about the discoverer.
* @param responseHandler Handler for the connection response, which is either an acceptance or
* rejection of the connection request.
* @return Handlers for the final connection result. This will be called as soon as the final
* connection result is known, when either side rejects or both sides accept.
*/
typedef GNCConnectionResultHandlers *_Nonnull (^GNCAdvertiserConnectionInitiationHandler)(
GNCEndpointId endpointId, id<GNCAdvertiserConnectionInfo> connectionInfo,
GNCConnectionResponseHandler responseHandler);
/**
* An advertiser broadcasts a service that can be seen by discoverers, which can then make
* requests to connect to it. Release the advertiser object to stop advertising.
*/
@interface GNCAdvertiser : NSObject
/**
* Factory method that creates an advertiser.
*
* @param endpointInfo A data for endpoint info which contains readable name of this endpoint,
* to be displayed on other endpoints.
* @param serviceId A string that uniquely identifies the advertised service.
* @param strategy The connection topology to use.
* @param connectionInitiationHandler A handler that is called when a discoverer requests a
* connection with this endpoint.
*/
+ (instancetype)advertiserWithEndpointInfo:(NSData *)endpointInfo
serviceId:(NSString *)serviceId
strategy:(GNCStrategy)strategy
connectionInitiationHandler:
(GNCAdvertiserConnectionInitiationHandler)connectionInitiationHandler;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,167 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class GNCBytesPayload, GNCStreamPayload, GNCFilePayload;
/** Response to a connection request. */
typedef NS_ENUM(NSInteger, GNCConnectionResponse) {
GNCConnectionResponseReject, // reject the connection request
GNCConnectionResponseAccept, // accept the connection request
};
/** Reason for a failed connection request. */
typedef NS_ENUM(NSInteger, GNCConnectionFailure) {
GNCConnectionFailureRejected, // an endpoint rejected the connection request
GNCConnectionFailureUnknown, // there was an error while attempting to make the connection
};
/** Handler for a @c GNCConnectionFailure value. */
typedef void (^GNCConnectionFailureHandler)(GNCConnectionFailure);
/** Reasons that a connection can be severed by either endpoint. */
typedef NS_ENUM(NSInteger, GNCDisconnectedReason) {
GNCDisconnectedReasonUnknown, // the endpoint can no longer be reached
};
/** Handler for a @c GNCDisconnectedReason value. */
typedef void (^GNCDisconnectedHandler)(GNCDisconnectedReason);
/** Result of a payload transfer. */
typedef NS_ENUM(NSInteger, GNCPayloadResult) {
GNCPayloadResultSuccess, // Payload delivery was successful.
GNCPayloadResultFailure, // An error occurred during payload delivery.
GNCPayloadResultCanceled, // Payload delivery was canceled.
};
/** Handler for a @c GNCPayloadResult value. */
typedef void (^GNCPayloadResultHandler)(GNCPayloadResult);
/** Connection topology. See https://developers.google.com/nearby/connections/strategies. */
typedef NS_ENUM(NSInteger, GNCStrategy) {
GNCStrategyCluster, // M-to-N
GNCStrategyStar, // 1-to-N
GNCStrategyPointToPoint, // 1-to-1
};
/** Every endpoint has a unique identifier. */
typedef NSString *GNCEndpointId;
/** This handler receives a Bytes payload. It is called when the payload data is fully received. */
typedef void (^GNCBytesPayloadHandler)(GNCBytesPayload *payload);
/**
* This handler receives a Stream payload, signifying the start of receipt of a stream. The payload
* data should be read from the supplied input stream. The progress object can be used to monitor
* progress or cancel the operation. This handler must return a completion handler, which is
* called when the operation is finished.
*/
typedef GNCPayloadResultHandler _Nonnull (^GNCStreamPayloadHandler)(GNCStreamPayload *payload,
NSProgress *progress);
/**
* This handler receives a File payload, signifying the start of receipt of a file. The
* progress object can be used to monitor progress or cancel the operation. This handler must
* return a completion handler, which is called when the operation finishes successfully or if
* there is an error. The file will be stored in a temporary location. If an error occurs or the
* operation is canceled, the file will contain all data that was received. It is the client's
* responsibility to delete the file when it is no longer needed.
*/
typedef GNCPayloadResultHandler _Nonnull (^GNCFilePayloadHandler)(GNCFilePayload *payload,
NSProgress *progress);
/** This class contains optional handlers for a connection. */
@interface GNCConnectionHandlers : NSObject
/**
* This handler receives Bytes payloads. It is optional; apps that don't send and receive Bytes
* payloads need not supply this handler.
*/
@property(nonatomic, nullable) GNCBytesPayloadHandler bytesPayloadHandler;
/**
* This handler receives a stream that delivers a payload in chunks. It is optional; apps that
* don't send and receive Stream payloads need not supply this handler.
*/
@property(nonatomic, nullable) GNCStreamPayloadHandler streamPayloadHandler;
/**
* This handler receives a File payload. It is optional; apps that don't send and receive File
* payloads need not supply this handler.
* Note: File payloads are not yet supported.
*/
@property(nonatomic, nullable) GNCFilePayloadHandler filePayloadHandler;
/**
* This handler is called when the connection is ended, whether due to the endpoint disconnecting
* or moving out of range. It is optional.
*/
@property(nonatomic, nullable) GNCDisconnectedHandler disconnectedHandler;
/**
* This factory method lets you specify a subset of the connection handlers in a single expression.
*
* @param builderBlock Set up the handlers in this block.
*/
+ (instancetype)handlersWithBuilder:(void (^)(GNCConnectionHandlers *))builderBlock;
@end
/**
* This represents a connection with an endpoint. Use it to send payloads to the endpoint, and
* release it to disconnect.
*/
@protocol GNCConnection <NSObject>
/**
* Send a Bytes payload. A progress object is returned, which can be used to monitor
* progress or cancel the operation. |completion| will be called when the operation completes
* (in all cases, even if failed or was canceled).
*/
- (NSProgress *)sendBytesPayload:(GNCBytesPayload *)payload
completion:(GNCPayloadResultHandler)completion;
/**
* Send a Stream payload. A progress object is returned, which can be used to monitor
* progress or cancel the operation. The stream data is read from the supplied NSInputStream.
* |completion| will be called when the operation completes.
*/
- (NSProgress *)sendStreamPayload:(GNCStreamPayload *)payload
completion:(GNCPayloadResultHandler)completion;
/**
* Send a File payload. A progress object is returned, which can be used to monitor progress or
* cancel the operation. |completion| will be called when the operation completes.
* Note: File payloads are not yet supported.
*/
- (NSProgress *)sendFilePayload:(GNCFilePayload *)payload
completion:(GNCPayloadResultHandler)completion;
@end
/**
* This handler takes a @c GNCConnection object and returns a @c GNCConnectionHandlers
* object containing the desired payload and connection-ended handlers.
*/
typedef GNCConnectionHandlers *_Nonnull (^GNCConnectionHandler)(id<GNCConnection> connection);
/**
* This handler takes a response to a connection request. Pass @c GNCConnectionResponseAccept to
* accept the request and @c GNCConnectionResponseReject to reject it.
*/
typedef void (^GNCConnectionResponseHandler)(GNCConnectionResponse response);
NS_ASSUME_NONNULL_END
@@ -0,0 +1,20 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Umbrella header file for Nearby Connections library.
#import "GNCAdvertiser.h"
#import "GNCConnection.h"
#import "GNCDiscoverer.h"
#import "GNCPayload.h"
@@ -0,0 +1,95 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
#import "GNCConnection.h"
NS_ASSUME_NONNULL_BEGIN
/** This is info about an advertiser endpoint with which the discoverer has requested a connection.
*/
@protocol GNCDiscovererConnectionInfo <NSObject>
/** This token can be used to verify the identity of the advertiser. */
@property(nonatomic, readonly, copy) NSString *authToken;
@end
/**
* This handler is called to establish authorization with the advertiser. In response,
* @c responseHandler should be called to accept or reject the connection.
*
* @param connectionInfo Information about the advertiser.
* @param responseHandler Handler for the connection response, which is either an acceptance or
* rejection of the connection request.
* @return Handler for the connection if it was successful.
*/
typedef GNCConnectionHandler _Nonnull (^GNCDiscovererConnectionInitializationHandler)(
id<GNCDiscovererConnectionInfo> connectionInfo, GNCConnectionResponseHandler responseHandler);
/**
* This handler should be called to request a connection with an advertiser.
*
* @param endpointInfo A data for endpoint info which contains readable name of this endpoint,
* to be displayed on other endpoints.
* @param authorizationHandler This handler is called to establish authorization.
* @param failureHandler This handler is called if there was an error making the connection.
*/
typedef void (^GNCConnectionRequester)(
NSData *endpointInfo, GNCDiscovererConnectionInitializationHandler connectionAuthorizationHandler,
GNCConnectionFailureHandler failureHandler);
/** Information about an endpoint when it's discovered. */
@protocol GNCDiscoveredEndpointInfo <NSObject>
/** The human readable name of the remote endpoint. */
@property(nonatomic, readonly, copy) NSString *endpointName;
/** Information advertised by the remote endpoint. */
@property(nonatomic, readonly, copy) NSData *endpointInfo;
/** Call this block to request a connection with the advertiser. */
@property(nonatomic, readonly) GNCConnectionRequester requestConnection;
@end
/** This handler is called when a previously discovered advertiser endpoint is lost. */
typedef void (^GNCEndpointLostHandler)(void);
/**
* This handler is called when an advertiser endpoint is discovered.
*
* @param endpointId The ID of the endpoint.
* @param connectionInfo Information about the endpoint.
* @return Block that is called when the endpoint is lost.
*/
typedef GNCEndpointLostHandler _Nonnull (^GNCEndpointFoundHandler)(
GNCEndpointId endpointId, id<GNCDiscoveredEndpointInfo> endpointInfo);
/**
* A discoverer searches for endpoints advertising the specified service, and allows connection
* requests to be sent to them. Release the discoverer object to stop discovering.
*/
@interface GNCDiscoverer : NSObject
/**
* Factory method that creates a discoverer.
*
* @param serviceId A string that uniquely identifies the advertised service to search for.
* @param strategy The connection topology to use.
* @param endpointFoundHandler This handler is called when an endpoint advertising the service is
* discovered.
*/
+ (instancetype)discovererWithServiceId:(NSString *)serviceId
strategy:(GNCStrategy)strategy
endpointFoundHandler:(GNCEndpointFoundHandler)endpointFoundHandler;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,64 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/** This class encapsulates a Bytes payload. */
@interface GNCBytesPayload : NSObject
/** The unique identifier of the payload. */
@property(nonatomic, readonly) int64_t identifier;
/** The content of the payload. */
@property(nonatomic, readonly) NSData *bytes;
/**
* Creates a Bytes payload object.
* Note: To maximize performance, @c bytes is strongly referenced, not copied.
*/
+ (instancetype)payloadWithBytes:(NSData *)bytes;
@end
/** This class encapsulates a Stream payload. */
@interface GNCStreamPayload : NSObject
/** The unique identifier of the payload. */
@property(nonatomic, readonly) int64_t identifier;
/** The payload data is read from this input stream. */
@property(nonatomic, readonly) NSInputStream *stream;
+ (instancetype)payloadWithStream:(NSInputStream *)stream;
@end
/** This class encapsulates a File payload. */
@interface GNCFilePayload : NSObject
/** The unique identifier of the payload. */
@property(nonatomic, readonly) int64_t identifier;
/** A URL that identifies the file. */
@property(nonatomic, readonly, copy) NSURL *fileURL;
+ (instancetype)payloadWithFileURL:(NSURL *)fileURL;
+ (instancetype)payloadWithFileURL:(NSURL *)fileURL identifier:(int64_t)identifier;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,317 @@
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "internal/platform/implementation/ios/Source/GNCAdvertiser.h"
#include <string>
#include "absl/functional/bind_front.h"
#include "connections/advertising_options.h"
#include "connections/core.h"
#include "connections/listeners.h"
#include "connections/params.h"
#include "connections/status.h"
#include "internal/platform/byte_array.h"
#import "internal/platform/implementation/ios/Source/GNCConnection.h"
#import "internal/platform/implementation/ios/Source/Internal/GNCCore.h"
#import "internal/platform/implementation/ios/Source/Internal/GNCCoreConnection.h"
#import "internal/platform/implementation/ios/Source/Internal/GNCPayloadListener.h"
#import "internal/platform/implementation/ios/Source/Internal/GNCUtils.h"
#import "internal/platform/implementation/ios/Source/Platform/utils.h"
#import "GoogleToolboxForMac/GTMLogger.h"
NS_ASSUME_NONNULL_BEGIN
using ::location::nearby::ByteArrayFromNSData;
using ::location::nearby::CppStringFromObjCString;
using ::location::nearby::ObjCStringFromCppString;
using ::location::nearby::connections::ConnectionListener;
using ::location::nearby::connections::AdvertisingOptions;
using ::location::nearby::connections::ConnectionRequestInfo;
using ::location::nearby::connections::ConnectionResponseInfo;
using ::location::nearby::connections::GNCStrategyToStrategy;
using ::location::nearby::connections::Medium;
using ResultListener = ::location::nearby::connections::ResultCallback;
using ::location::nearby::connections::Status;
/** This is a GNCAdvertiserConnectionInfo that provides storage for its properties. */
@interface GNCAdvertiserConnectionInfo : NSObject
@property(nonatomic, readonly) NSString *name;
@property(nonatomic, readonly) NSString *authToken;
- (instancetype)initWithName:(NSString *)name authToken:(NSString *)authToken;
@end
@implementation GNCAdvertiserConnectionInfo
- (instancetype)initWithName:(NSString *)name authToken:(NSString *)authToken {
self = [super init];
if (self) {
_name = [name copy];
_authToken = [authToken copy];
}
return self;
}
@end
/** Information retained about an endpoint before and after requesting a connection. */
@interface GNCAdvertiserEndpointInfo : NSObject
@property(nonatomic) GNCAdvertiserConnectionInfo *connectionInfo;
@property(nonatomic) GNCConnectionResponse clientResponse;
@property(nonatomic) BOOL clientResponseReceived; // whether the client response has been received
@property(nonatomic, nullable) GNCConnectionResultHandlers *connectionResultHandlers;
@property(nonatomic, weak) GNCCoreConnection *connection;
@property(nonatomic) GNCConnectionHandlers *connectionHandlers;
@end
@implementation GNCAdvertiserEndpointInfo
+ (instancetype)infoWithEndpointConnectionInfo:(GNCAdvertiserConnectionInfo *)connInfo {
GNCAdvertiserEndpointInfo *info = [[GNCAdvertiserEndpointInfo alloc] init];
info.connectionInfo = connInfo;
return info;
}
@end
/** GNCAdvertiser members. */
@interface GNCAdvertiser ()
@property(nonatomic) GNCCore *core;
@property(nonatomic) GNCAdvertiserConnectionInitiationHandler initiationHandler;
@property(nonatomic, assign) Status status;
@property(nonatomic) NSMutableDictionary<GNCEndpointId, GNCAdvertiserEndpointInfo *> *endpoints;
@end
/** C++ classes passed to the core library by GNCAdvertiser. */
namespace location {
namespace nearby {
namespace connections {
/** This class contains the callbacks for establishing and severing a connection. */
class GNCAdvertiserConnectionListener {
public:
explicit GNCAdvertiserConnectionListener(GNCAdvertiser *advertiser) : advertiser_(advertiser) {}
void OnInitiated(const std::string &endpoint_id, const ConnectionResponseInfo &info) {
GNCAdvertiser *advertiser = advertiser_; // strongify
if (!advertiser) return;
NSString *endpointId = ObjCStringFromCppString(endpoint_id);
GNCAdvertiserEndpointInfo *endpointInfo = advertiser.endpoints[endpointId];
if (endpointInfo) {
GTMLoggerError(@"Connection already initiated for endpoint: %@", endpointId);
} else {
// TODO(b/169292092): endpointInfo is an advertisement byte array. Need to implement to
// extract the endpoint name not just force to cast string.
NSString *name = ObjCStringFromCppString(std::string(info.remote_endpoint_info));
NSString *authToken = ObjCStringFromCppString(info.authentication_token);
GNCAdvertiserConnectionInfo *connInfo =
[[GNCAdvertiserConnectionInfo alloc] initWithName:name authToken:authToken];
endpointInfo = [GNCAdvertiserEndpointInfo infoWithEndpointConnectionInfo:connInfo];
// Call the connection initiation handler. Synchronous because it returns the connection
// result handlers.
dispatch_sync(dispatch_get_main_queue(), ^{
__weak __typeof__(advertiser) weakAdvertiser = advertiser;
endpointInfo.connectionResultHandlers = advertiser.initiationHandler(
endpointId, (id<GNCAdvertiserConnectionInfo>)connInfo,
^(GNCConnectionResponse response) {
__strong __typeof__(advertiser) strongAdvertiser = weakAdvertiser;
endpointInfo.clientResponse = response;
endpointInfo.clientResponseReceived = YES;
if (response == GNCConnectionResponseAccept) {
// The connection was accepted by the client.
if (payload_listener_ == nullptr) {
payload_listener_ = std::make_unique<GNCPayloadListener>(
advertiser.core,
^{
return endpointInfo.connectionHandlers;
},
^{
return endpointInfo.connection.payloads;
});
}
advertiser.core->_core->AcceptConnection(
CppStringFromObjCString(endpointId),
PayloadListener{
.payload_cb = absl::bind_front(&GNCPayloadListener::OnPayload,
payload_listener_.get()),
.payload_progress_cb = absl::bind_front(
&GNCPayloadListener::OnPayloadProgress, payload_listener_.get()),
},
ResultListener{});
} else {
// The connection was rejected by the client.
advertiser.core->_core->RejectConnection(CppStringFromObjCString(endpointId),
ResultListener{});
}
});
});
advertiser.endpoints[endpointId] = endpointInfo;
}
}
void OnAccepted(const std::string &endpoint_id) {
GNCAdvertiser *advertiser = advertiser_; // strongify
if (!advertiser) return;
NSString *endpointId = ObjCStringFromCppString(endpoint_id);
GNCAdvertiserEndpointInfo *endpointInfo = advertiser.endpoints[endpointId];
if (!endpointInfo) {
GTMLoggerInfo(@"Connection result for unknown endpoint: %@", endpointId);
return;
}
// The connection has been accepted by both endpoints, so create the GNCConnection object
// and pass it to |successHandler| for the client to use. It will be removed from |endpoints|
// when the client disconnects (on dealloc of GNCConnection).
// Note: Use a local strong reference to the connection object; don't just assign to
// |endpointInfo.connection|. Without a strong reference, the connection object can be
// deallocated before |successHandler| is called in the Release build.
__weak __typeof__(advertiser) weakAdvertiser = advertiser;
id<GNCConnection> connection = [GNCCoreConnection
connectionWithEndpointId:endpointId
core:advertiser.core
deallocHandler:^{
__strong __typeof__(advertiser) strongAdvertiser = weakAdvertiser;
if (!strongAdvertiser) return;
[strongAdvertiser.endpoints removeObjectForKey:endpointId];
}];
endpointInfo.connection = connection;
// Callback is synchronous because it returns the connection handlers.
dispatch_sync(dispatch_get_main_queue(), ^{
endpointInfo.connectionHandlers =
endpointInfo.connectionResultHandlers.successHandler(connection);
});
}
void OnRejected(const std::string &endpoint_id, Status status) {
GNCAdvertiser *advertiser = advertiser_; // strongify
if (!advertiser) return;
NSString *endpointId = ObjCStringFromCppString(endpoint_id);
GNCAdvertiserEndpointInfo *endpointInfo = advertiser.endpoints[endpointId];
if (!endpointInfo) {
GTMLoggerInfo(@"Connection result for unknown endpoint: %@", endpointId);
return;
}
// One side rejected, so call failureHandler with the connection status (we do this in all
// cases), and forget the endpoint.
dispatch_async(dispatch_get_main_queue(), ^{
endpointInfo.connectionResultHandlers.failureHandler(GNCConnectionFailureRejected);
});
[advertiser.endpoints removeObjectForKey:endpointId];
}
void OnDisconnected(const std::string &endpoint_id) {
GNCAdvertiser *advertiser = advertiser_; // strongify
if (!advertiser) return;
NSString *endpointId = ObjCStringFromCppString(endpoint_id);
GNCAdvertiserEndpointInfo *endpointInfo = advertiser.endpoints[endpointId];
if (endpointInfo) {
if (endpointInfo.connection) {
GNCDisconnectedHandler disconnectedHandler =
endpointInfo.connectionHandlers.disconnectedHandler;
dispatch_async(dispatch_get_main_queue(), ^{
if (disconnectedHandler) disconnectedHandler(GNCDisconnectedReasonUnknown);
});
} else {
GTMLoggerInfo(@"Disconnect for unconnected endpoint: %@", endpointId);
}
[advertiser.endpoints removeObjectForKey:endpointId];
} else {
GTMLoggerInfo(@"Disconnect for unknown endpoint: %@", endpointId);
}
}
void OnBandwidthChanged(const std::string &endpoint_id, Medium medium) {
GNCAdvertiser *advertiser = advertiser_; // strongify
if (!advertiser) return;
// TODO(b/169292092): Implement.
}
private:
__weak GNCAdvertiser *advertiser_;
std::unique_ptr<GNCPayloadListener> payload_listener_;
};
} // namespace connections
} // namespace nearby
} // namespace location
using ::location::nearby::connections::GNCAdvertiserConnectionListener;
@interface GNCAdvertiser () {
std::unique_ptr<GNCAdvertiserConnectionListener> advertiserListener;
};
@end
@implementation GNCAdvertiser
+ (instancetype)advertiserWithEndpointInfo:(NSData *)endpointInfo
serviceId:(NSString *)serviceId
strategy:(GNCStrategy)strategy
connectionInitiationHandler:
(GNCAdvertiserConnectionInitiationHandler)initiationHandler {
GNCAdvertiser *advertiser = [[GNCAdvertiser alloc] init];
advertiser.initiationHandler = initiationHandler;
advertiser.endpoints = [[NSMutableDictionary alloc] init];
advertiser.core = GNCGetCore();
advertiser->advertiserListener = std::make_unique<GNCAdvertiserConnectionListener>(advertiser);
ConnectionListener listener = {
.initiated_cb = absl::bind_front(&GNCAdvertiserConnectionListener::OnInitiated,
advertiser->advertiserListener.get()),
.accepted_cb = absl::bind_front(&GNCAdvertiserConnectionListener::OnAccepted,
advertiser->advertiserListener.get()),
.rejected_cb = absl::bind_front(&GNCAdvertiserConnectionListener::OnRejected,
advertiser->advertiserListener.get()),
.disconnected_cb = absl::bind_front(&GNCAdvertiserConnectionListener::OnDisconnected,
advertiser->advertiserListener.get()),
};
advertiser.core->_core->StartAdvertising(
CppStringFromObjCString(serviceId),
AdvertisingOptions{
{
GNCStrategyToStrategy(strategy), // .strategy
location::nearby::connections::BooleanMediumSelector(), // .allowed
},
true, // .auto_upgrade_bandwidth
true, // .enforce_topology_constraints
},
ConnectionRequestInfo{
.endpoint_info = ByteArrayFromNSData(endpointInfo),
.listener = std::move(listener),
},
ResultListener{});
return advertiser;
}
- (void)dealloc {
GTMLoggerInfo(@"GNCAdvertiser deallocated");
_core->_core->StopAdvertising(ResultListener{});
}
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,55 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
#include <memory>
#include "connections/core.h"
#include "connections/implementation/service_controller_router.h"
#include "internal/platform/payload_id.h"
NS_ASSUME_NONNULL_BEGIN
/** This class contains the C++ Core object. */
@interface GNCCore : NSObject {
@public
std::unique_ptr<::location::nearby::connections::Core> _core;
std::unique_ptr<::location::nearby::connections::ServiceControllerRouter>
_service_controller_router;
}
/**
* These functions are the utilities to manipulate the InputFile in ImplementationPlatform for
* sending File payload.
*
* Inserts the URL to the map, keyed by payloadID. The element will not be inserted if there
* already is an element with the key in the map.
*/
- (void)insertURLToMapWithPayloadID:(::location::nearby::PayloadId)payloadId urlToSend:(NSURL *)url;
/**
* Returns the URL with the payloadID and removes the entry from the map. Returns nil if
* payloadID is not found.
*/
- (nullable NSURL *)extractURLWithPayloadID:(::location::nearby::PayloadId)payloadId;
- (void)clearSendingURLMaps;
@end
/** This function returns the Core singleton, wrapped in an Obj-C object for lifetime management. */
GNCCore *GNCGetCore();
NS_ASSUME_NONNULL_END
@@ -0,0 +1,94 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "internal/platform/implementation/ios/Source/Internal/GNCCore.h"
#include <utility>
#include "absl/container/flat_hash_map.h"
#include "absl/container/internal/common.h"
#include "connections/core.h"
#include "connections/implementation/service_controller_router.h"
#include "internal/platform/payload_id.h"
#import "GoogleToolboxForMac/GTMLogger.h"
using ::location::nearby::connections::Core;
using ::location::nearby::PayloadId;
using ::location::nearby::connections::ServiceControllerRouter;
@implementation GNCCore {
// A map to store the NSURL object with PayloadId for sendFilePayload in GNCConnection.
// This is the place to store the NSURL for InputFile creation in ImplementationPlatform.
absl::flat_hash_map<PayloadId, NSURL *> _sending_urls;
}
- (instancetype)init {
GTMLoggerInfo(@"GNCCore created");
self = [super init];
if (self) {
_service_controller_router = std::make_unique<ServiceControllerRouter>();
_core = std::make_unique<Core>(_service_controller_router.get());
}
return self;
}
- (void)dealloc {
_core.reset();
_service_controller_router.reset();
GTMLoggerInfo(@"GNCCore deallocated");
}
- (void)insertURLToMapWithPayloadID:(PayloadId)payloadId urlToSend:(NSURL *)url {
_sending_urls.emplace(payloadId, url);
}
- (nullable NSURL *)extractURLWithPayloadID:(PayloadId)payloadId {
NSURL *url;
auto it = _sending_urls.find(payloadId);
if (it != _sending_urls.end()) {
auto pair = _sending_urls.extract(it);
url = pair.mapped();
}
return url;
}
- (void)clearSendingURLMaps {
_sending_urls.clear();
}
@end
GNCCore *GNCGetCore() {
static NSObject *syncSingleton;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
syncSingleton = [[NSObject alloc] init];
});
// The purpose of keeping a weak reference to the GNCCore object is to ensure that it will be
// released when all external strong references are gone. I.e., when the app is no longer doing
// any NC operations, the core will be released.
static __weak GNCCore *core;
// Strongly reference the GNCCore object for the duration of this function to ensure it isn't
// prematurely deallocated by ARC after being created (which can happen in optimized builds).
GNCCore *strongCore = core;
@synchronized(syncSingleton) {
if (!strongCore) {
strongCore = [[GNCCore alloc] init];
core = strongCore;
}
}
return strongCore;
}
@@ -0,0 +1,44 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
#import "internal/platform/implementation/ios/Source/GNCConnection.h"
#import "internal/platform/implementation/ios/Source/Internal/GNCCore.h"
NS_ASSUME_NONNULL_BEGIN
/** This holds the progress and completion for a pending payload. */
@interface GNCPayloadInfo : NSObject
@property(nonatomic, nullable) NSProgress *progress;
@property(nonatomic, nullable) GNCPayloadResultHandler completion;
+ (instancetype)infoWithProgress:(nullable NSProgress *)progress
completion:(GNCPayloadResultHandler)completion;
- (void)callCompletion:(GNCPayloadResult)result;
@end
/** GNCConnection that interfaces with the Core library. */
@interface GNCCoreConnection : NSObject <GNCConnection>
@property(nonatomic) GNCCore *core;
@property(nonatomic, copy) GNCEndpointId endpointId;
@property(nonatomic) dispatch_block_t deallocHandler;
@property(nonatomic) NSMutableDictionary<NSNumber *, GNCPayloadInfo *> *payloads;
+ (instancetype)connectionWithEndpointId:(GNCEndpointId)endpointId
core:(GNCCore *)core
deallocHandler:(dispatch_block_t)deallocHandler;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,188 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "internal/platform/implementation/ios/Source/Internal/GNCCoreConnection.h"
#include "connections/core.h"
#include "connections/payload.h"
#include "internal/platform/exception.h"
#include "internal/platform/file.h"
#include "internal/platform/implementation/input_file.h"
#import "internal/platform/implementation/ios/Source/GNCConnection.h"
#import "internal/platform/implementation/ios/Source/GNCPayload.h"
#import "internal/platform/implementation/ios/Source/Internal/GNCCore.h"
#import "internal/platform/implementation/ios/Source/Platform/utils.h"
#include "internal/platform/input_stream.h"
#include "internal/platform/payload_id.h"
using ::location::nearby::ByteArrayFromNSData;
using ::location::nearby::CppStringFromObjCString;
using ::location::nearby::InputFile;
using ::location::nearby::InputStream;
using ::location::nearby::connections::Payload;
using ::location::nearby::PayloadId;
using ResultListener = ::location::nearby::connections::ResultCallback;
namespace location {
namespace nearby {
namespace connections {
/**
* This InputStream subclass takes input from an NSInputStream. The update handler is called for
* each chunk of data sent, giving the client an opportunity to handle cancelation.
*/
class GNCInputStreamFromNSStream : public InputStream {
public:
explicit GNCInputStreamFromNSStream(NSInputStream *nsStream) : nsStream_(nsStream) {
[nsStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[nsStream open];
}
~GNCInputStreamFromNSStream() override { Close(); }
ExceptionOr<ByteArray> Read() { return Read(kMaxChunkSize); }
ExceptionOr<ByteArray> Read(std::int64_t size) override {
uint8_t *bytesRead = new uint8_t[size];
NSUInteger numberOfBytesToRead = [[NSNumber numberWithLongLong:size] unsignedIntegerValue];
NSInteger numberOfBytesRead = [nsStream_ read:bytesRead maxLength:numberOfBytesToRead];
if (numberOfBytesRead == 0) {
// Reached end of stream.
return ExceptionOr<ByteArray>();
} else if (numberOfBytesRead < 0) {
// Stream error.
return ExceptionOr<ByteArray>{Exception::kIo};
}
return ExceptionOr<ByteArray>(ByteArrayFromNSData([NSData dataWithBytes:bytesRead
length:numberOfBytesRead]));
}
Exception Close() override {
[nsStream_ close];
return {Exception::kSuccess};
}
private:
static const size_t kMaxChunkSize = 32 * 1024;
NSInputStream *nsStream_;
// dispatch_block_t update_handler_;
};
} // namespace connections
} // namespace nearby
} // namespace location
@implementation GNCPayloadInfo
+ (instancetype)infoWithProgress:(nullable NSProgress *)progress
completion:(GNCPayloadResultHandler)completion {
GNCPayloadInfo *info = [[GNCPayloadInfo alloc] init];
info.progress = progress;
info.completion = completion;
return info;
}
- (void)callCompletion:(GNCPayloadResult)result {
if (_completion) _completion(result);
_completion = nil;
}
@end
@implementation GNCCoreConnection
+ (instancetype)connectionWithEndpointId:(GNCEndpointId)endpointId
core:(GNCCore *)core
deallocHandler:(dispatch_block_t)deallocHandler {
GNCCoreConnection *connection = [[GNCCoreConnection alloc] init];
connection.endpointId = endpointId;
connection.core = core;
connection.deallocHandler = deallocHandler;
connection.payloads = [[NSMutableDictionary alloc] init];
return connection;
}
- (void)dealloc {
_core->_core->DisconnectFromEndpoint(CppStringFromObjCString(_endpointId), ResultListener{});
_deallocHandler();
}
- (NSProgress *)sendBytesPayload:(GNCBytesPayload *)payload
completion:(GNCPayloadResultHandler)completion {
Payload corePayload(ByteArrayFromNSData(payload.bytes));
NSUInteger length = payload.bytes.length;
PayloadId payloadId = corePayload.GetId();
NSProgress *progress = [NSProgress progressWithTotalUnitCount:length];
__weak __typeof__(self) weakSelf = self;
progress.cancellationHandler = ^{
[weakSelf cancelPayloadWithId:payloadId];
};
return [self sendPayload:std::move(corePayload)
size:length
progress:progress
completion:completion];
}
- (NSProgress *)sendStreamPayload:(GNCStreamPayload *)payload
completion:(GNCPayloadResultHandler)completion {
NSProgress *progress = [NSProgress progressWithTotalUnitCount:-1];
PayloadId payloadId = payload.identifier;
Payload corePayload(payloadId, [payload]() -> InputStream & {
location::nearby::connections::GNCInputStreamFromNSStream *stream =
new location::nearby::connections::GNCInputStreamFromNSStream(payload.stream);
return *stream;
});
return [self sendPayload:std::move(corePayload) size:-1 progress:progress completion:completion];
}
- (NSProgress *)sendFilePayload:(GNCFilePayload *)payload
completion:(GNCPayloadResultHandler)completion {
NSProgress *progress = [NSProgress progressWithTotalUnitCount:0];
std::int64_t fileSize = 0;
NSURL *fileURL = payload.fileURL;
NSNumber *fileSizeValue = nil;
BOOL result = [fileURL getResourceValue:&fileSizeValue forKey:NSURLFileSizeKey error:nil];
if (result == YES) {
fileSize = fileSizeValue.longValue;
}
PayloadId payloadId = payload.identifier;
// Add the pair of payloadId and fileURL to the map in the GNCCore.
[_core insertURLToMapWithPayloadID:payloadId urlToSend:fileURL];
Payload corePayload(payloadId, InputFile(payloadId, fileSize));
progress.totalUnitCount = fileSize;
return [self sendPayload:std::move(corePayload)
size:fileSize
progress:progress
completion:completion];
}
#pragma mark Private
- (NSProgress *)sendPayload:(Payload)payload
size:(uint64_t)size
progress:(NSProgress *)progress
completion:(GNCPayloadResultHandler)completion {
_payloads[@(payload.GetId())] = [GNCPayloadInfo infoWithProgress:progress completion:completion];
_core->_core->SendPayload(std::vector<std::string>(1, CppStringFromObjCString(_endpointId)),
std::move(payload), ResultListener{});
return progress;
}
- (void)cancelPayloadWithId:(PayloadId)payloadId {
_core->_core->CancelPayload(payloadId, ResultListener{});
}
@end
@@ -0,0 +1,401 @@
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "internal/platform/implementation/ios/Source/GNCDiscoverer.h"
#include <string>
#include <utility>
#include "absl/functional/bind_front.h"
#include "connections/connection_options.h"
#include "connections/core.h"
#include "connections/discovery_options.h"
#include "connections/listeners.h"
#include "connections/status.h"
#include "internal/platform/byte_array.h"
#import "internal/platform/implementation/ios/Source/GNCConnection.h"
#import "internal/platform/implementation/ios/Source/Internal/GNCCore.h"
#import "internal/platform/implementation/ios/Source/Internal/GNCCoreConnection.h"
#import "internal/platform/implementation/ios/Source/Internal/GNCPayloadListener.h"
#import "internal/platform/implementation/ios/Source/Internal/GNCUtils.h"
#import "internal/platform/implementation/ios/Source/Platform/utils.h"
#import "GoogleToolboxForMac/GTMLogger.h"
NS_ASSUME_NONNULL_BEGIN
using ::location::nearby::ByteArray;
using ::location::nearby::CppStringFromObjCString;
using ::location::nearby::connections::DiscoveryOptions;
using ::location::nearby::connections::ConnectionOptions;
using ::location::nearby::connections::DiscoveryListener;
using ::location::nearby::connections::DistanceInfo;
using ::location::nearby::connections::GNCStrategyToStrategy;
using ResultListener = ::location::nearby::connections::ResultCallback;
using ::location::nearby::connections::Status;
/** This is a GNCDiscovererConnectionInfo that provides storage for its properties. */
@interface GNCDiscovererConnectionInfo : NSObject <GNCDiscovererConnectionInfo>
@property(nonatomic, copy) NSString *authToken;
/** Creates a GNCDiscovererConnectionInfo object. */
+ (instancetype)infoWithAuthToken:(NSString *)authToken;
@end
@implementation GNCDiscovererConnectionInfo
+ (instancetype)infoWithAuthToken:(NSString *)authToken {
GNCDiscovererConnectionInfo *info = [[GNCDiscovererConnectionInfo alloc] init];
info.authToken = authToken;
return info;
}
@end
/** This is a GNCDiscoveredEndpointInfo that provides storage for its properties. */
@interface GNCDiscoveredEndpointInfo : NSObject <GNCDiscoveredEndpointInfo>
@property(nonatomic, copy) NSString *endpointName;
@property(nonatomic, copy) NSData *endpointInfo;
@end
@implementation GNCDiscoveredEndpointInfo
@synthesize requestConnection = _requestConnection;
+ (instancetype)infoWithName:(NSString *)endpointName
endpointInfo:(NSData *)endpointInfo
requestConnection:(GNCConnectionRequester)requestConnection {
GNCDiscoveredEndpointInfo *info = [[GNCDiscoveredEndpointInfo alloc] init];
info.endpointName = endpointName;
info.endpointInfo = endpointInfo;
info->_requestConnection = requestConnection;
return info;
}
@end
/** Information retained by the discoverer about each discovered endpoint. */
@interface GNCDiscovererEndpointInfo : NSObject
/** Handles lostHandler once |onEndpointLost| has been callback. */
@property(nonatomic) GNCEndpointLostHandler lostHandler;
/** The connInitHandler is stored after requestConnection. */
@property(nonatomic, nullable) GNCDiscovererConnectionInitializationHandler connInitHandler;
/** The connFailureHandler is stored after requestConnection. */
@property(nonatomic, nullable) GNCConnectionFailureHandler connFailureHandler;
/** Client responses Accept or Reject. */
@property(nonatomic) GNCConnectionResponse clientResponse;
/** Whether the client response has been received. */
@property(nonatomic) BOOL clientResponseReceived;
/**
* The connectionhandler returned by connInitHandler. Stored here if the connection is accepted.
*/
@property(nonatomic, nullable) GNCConnectionHandler connectionHandler;
/** @c GNCCoreConnection is created and stored if connection is accepted. */
@property(nonatomic, weak) GNCCoreConnection *connection;
/** @c GNCConnectionHandlers object is returned by connectionHandler and stored here. */
@property(nonatomic) GNCConnectionHandlers *connectionHandlers;
@end
@implementation GNCDiscovererEndpointInfo
@end
/** GNCDiscoverer members. */
@interface GNCDiscoverer ()
@property(nonatomic) GNCCore *core;
@property(nonatomic) GNCEndpointFoundHandler endpointFoundHandler;
@property(nonatomic, assign) Status status;
@property(nonatomic) NSMapTable<GNCEndpointId, GNCDiscovererEndpointInfo *> *endpoints;
@end
/** C++ classes passed to the core library by GNCDiscoverer. */
namespace location {
namespace nearby {
namespace connections {
/** This class contains the discoverer callbacks related to a connection. */
class GNCDiscovererConnectionListener {
public:
GNCDiscovererConnectionListener(GNCCore *core,
NSMapTable<GNCEndpointId, GNCDiscovererEndpointInfo *> *endpoints)
: core_(core), endpoints_(endpoints) {}
void OnInitiated(const std::string &endpoint_id, const ConnectionResponseInfo &info) {
NSString *endpointId = ObjCStringFromCppString(endpoint_id);
GNCDiscovererEndpointInfo *endpointInfo = [endpoints_ objectForKey:endpointId];
if (!endpointInfo) {
return;
}
// Call the connection initiation handler. Synchronous because it returns the connection
// handler.
NSString *authToken = ObjCStringFromCppString(info.authentication_token);
GNCCore *core = core_; // don't capture |this|
dispatch_sync(dispatch_get_main_queue(), ^{
endpointInfo.connectionHandler = endpointInfo.connInitHandler(
[GNCDiscovererConnectionInfo infoWithAuthToken:authToken],
^(GNCConnectionResponse response) {
endpointInfo.clientResponse = response;
endpointInfo.clientResponseReceived = YES;
if (response == GNCConnectionResponseAccept) {
// The connect was accepted by the client.
if (payload_listener_ == nullptr) {
payload_listener_ = std::make_unique<GNCPayloadListener>(
core,
^{
return endpointInfo.connectionHandlers;
},
^{
return endpointInfo.connection.payloads;
});
}
core->_core->AcceptConnection(
CppStringFromObjCString(endpointId),
PayloadListener{
.payload_cb =
absl::bind_front(&GNCPayloadListener::OnPayload, payload_listener_.get()),
.payload_progress_cb = absl::bind_front(
&GNCPayloadListener::OnPayloadProgress, payload_listener_.get()),
},
ResultListener{});
} else {
// The connect was rejected by the client.
core->_core->RejectConnection(CppStringFromObjCString(endpointId), ResultListener{});
}
});
});
}
void OnAccepted(const std::string &endpoint_id) {
NSString *endpointId = ObjCStringFromCppString(endpoint_id);
GNCDiscovererEndpointInfo *endpointInfo = [endpoints_ objectForKey:endpointId];
if (!endpointInfo) {
return;
}
// The connection has been accepted by both endpoints, so create the GNCConnection object
// and pass it to |successHandler| for the client to use.
// Note: Use a local strong reference to the connection object; don't just assign to
// |endpointInfo.connection|. Without a strong reference, the connection object can be
// deallocated before |successHandler| is called in the Release build.
id<GNCConnection> connection = [GNCCoreConnection
connectionWithEndpointId:endpointId
core:core_
deallocHandler:^{
// Don't remove the remote endpoint (like GNCAdvertiser does) because that's
// done when the endpoint is lost.
}];
endpointInfo.connection = connection;
// Callback is synchronous because it returns the connection handlers.
dispatch_sync(dispatch_get_main_queue(), ^{
endpointInfo.connectionHandlers = endpointInfo.connectionHandler(endpointInfo.connection);
});
endpointInfo.clientResponseReceived = NO; // support reconnection after disconnection
}
void OnRejected(const std::string &endpoint_id, Status status) {
NSString *endpointId = ObjCStringFromCppString(endpoint_id);
GNCDiscovererEndpointInfo *endpointInfo = [endpoints_ objectForKey:endpointId];
if (!endpointInfo) {
return;
}
// If either side rejected, call failureHandler with the connection status.
dispatch_async(dispatch_get_main_queue(), ^{
endpointInfo.connFailureHandler(GNCConnectionFailureRejected);
});
endpointInfo.clientResponseReceived = NO; // support reconnection after disconnection
}
void OnDisconnected(const std::string &endpoint_id) {
NSString *endpointId = ObjCStringFromCppString(endpoint_id);
GNCDiscovererEndpointInfo *endpointInfo = [endpoints_ objectForKey:endpointId];
if (!endpointInfo) {
return;
}
if (endpointInfo.connection) {
GNCDisconnectedHandler disconnectedHandler =
endpointInfo.connectionHandlers.disconnectedHandler;
dispatch_async(dispatch_get_main_queue(), ^{
if (disconnectedHandler) disconnectedHandler(GNCDisconnectedReasonUnknown);
});
}
}
void OnBandwidthChanged(const std::string &endpoint_id, Medium medium) {
// TODO(b/169292092): Implement.
}
private:
GNCCore *core_;
NSMapTable<GNCEndpointId, GNCDiscovererEndpointInfo *> *endpoints_;
std::unique_ptr<GNCPayloadListener> payload_listener_;
};
class GNCDiscoveryListener {
public:
explicit GNCDiscoveryListener(GNCDiscoverer *discoverer) : discoverer_(discoverer) {}
void OnEndpointFound(const std::string &endpoint_id, const ByteArray &endpoint_info,
const std::string &service_id) {
GNCDiscoverer *discoverer = discoverer_; // strongify
if (!discoverer) {
return;
}
NSString *endpointId = ObjCStringFromCppString(endpoint_id);
if ([discoverer.endpoints objectForKey:endpointId] != nil) {
GTMLoggerError(@"Endpoint already discovered: %@", endpointId);
} else {
// The GNCDiscoveredEndpointInfo object created here lives as long as the client has strong
// reference to it. Here's the chain of strong references maintained here:
// client -> GNCDiscoveredEndpointInfo -> RequestConnection block ->
// GNCDiscovererEndpointInfo (stored weakly in the |endpoints| map table)
GNCDiscovererEndpointInfo *endpointInfo = [[GNCDiscovererEndpointInfo alloc] init];
[discoverer.endpoints setObject:endpointInfo forKey:endpointId];
NSString *name = ObjCStringFromCppString(std::string(endpoint_info));
NSData *info = NSDataFromByteArray(endpoint_info);
GNCCore *core = discoverer.core; // don't capture |this| or |discoverer|
NSMapTable<GNCEndpointId, GNCDiscovererEndpointInfo *> *endpoints = discoverer.endpoints;
GNCDiscoveredEndpointInfo *discEndpointInfo = [GNCDiscoveredEndpointInfo
infoWithName:name
endpointInfo:info
requestConnection:^(NSData *info,
GNCDiscovererConnectionInitializationHandler connInitHandler,
GNCConnectionFailureHandler connFailureHandler) {
endpointInfo.connInitHandler = connInitHandler;
endpointInfo.connFailureHandler = connFailureHandler;
if (discoverer_connection_listener_ == nullptr) {
discoverer_connection_listener_ =
std::make_unique<GNCDiscovererConnectionListener>(core, endpoints);
}
ConnectionListener listener = {
.initiated_cb = absl::bind_front(&GNCDiscovererConnectionListener::OnInitiated,
discoverer_connection_listener_.get()),
.accepted_cb = absl::bind_front(&GNCDiscovererConnectionListener::OnAccepted,
discoverer_connection_listener_.get()),
.rejected_cb = absl::bind_front(&GNCDiscovererConnectionListener::OnRejected,
discoverer_connection_listener_.get()),
.disconnected_cb =
absl::bind_front(&GNCDiscovererConnectionListener::OnDisconnected,
discoverer_connection_listener_.get()),
};
core->_core->RequestConnection(
CppStringFromObjCString(endpointId),
ConnectionRequestInfo{.endpoint_info = ByteArrayFromNSData(info),
.listener = std::move(listener)},
ConnectionOptions{},
ResultListener{.result_cb = [&connFailureHandler](Status status) {
if (!status.Ok()) {
dispatch_sync(dispatch_get_main_queue(), ^{
connFailureHandler(GNCConnectionFailureUnknown);
});
}
}});
}];
// Call the client endpoint-found handler. Tail call for reentrancy.
dispatch_sync(dispatch_get_main_queue(), ^{
endpointInfo.lostHandler = discoverer.endpointFoundHandler(endpointId, discEndpointInfo);
});
}
}
void OnEndpointLost(const std::string &endpoint_id) {
GNCDiscoverer *discoverer = discoverer_; // strongify
if (!discoverer) {
return;
}
NSString *endpointId = ObjCStringFromCppString(endpoint_id);
GNCDiscovererEndpointInfo *info = [discoverer.endpoints objectForKey:endpointId];
if (!info) {
GTMLoggerError(@"Endpoint already lost: %@", endpointId);
} else {
dispatch_async(dispatch_get_main_queue(), ^{
info.lostHandler();
});
}
[discoverer.endpoints removeObjectForKey:endpointId];
}
void OnEndpointDistanceChanged_cb(const std::string &endpoint_id, DistanceInfo info) {
// TODO(b/169292092): Implement.
}
private:
__weak GNCDiscoverer *discoverer_;
std::unique_ptr<GNCDiscovererConnectionListener> discoverer_connection_listener_;
};
} // namespace connections
} // namespace nearby
} // namespace location
using ::location::nearby::connections::GNCDiscoveryListener;
@interface GNCDiscoverer () {
std::unique_ptr<GNCDiscoveryListener> discoveryListener;
};
@end
@implementation GNCDiscoverer
+ (instancetype)discovererWithServiceId:(NSString *)serviceId
strategy:(GNCStrategy)strategy
endpointFoundHandler:(GNCEndpointFoundHandler)endpointFoundHandler {
GNCDiscoverer *discoverer = [[GNCDiscoverer alloc] init];
discoverer.endpointFoundHandler = endpointFoundHandler;
discoverer.endpoints = [NSMapTable strongToWeakObjectsMapTable];
discoverer.core = GNCGetCore();
discoverer->discoveryListener = std::make_unique<GNCDiscoveryListener>(discoverer);
DiscoveryListener listener = {
.endpoint_found_cb = absl::bind_front(&GNCDiscoveryListener::OnEndpointFound,
discoverer->discoveryListener.get()),
.endpoint_lost_cb = absl::bind_front(&GNCDiscoveryListener::OnEndpointLost,
discoverer->discoveryListener.get()),
};
discoverer.core->_core->StartDiscovery(CppStringFromObjCString(serviceId),
DiscoveryOptions{
{
GNCStrategyToStrategy(strategy),
},
},
std::move(listener), ResultListener{});
return discoverer;
}
- (void)dealloc {
GTMLoggerInfo(@"GNCDiscoverer deallocated");
_core->_core->StopDiscovery(ResultListener{});
}
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,36 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "internal/platform/implementation/ios/Source/GNCPayload.h"
NS_ASSUME_NONNULL_BEGIN
/** This category adds the ability to specify a payload ID. */
@interface GNCBytesPayload (Internal)
+ (instancetype)payloadWithBytes:(NSData *)bytes identifier:(int64_t)identifier;
@end
/** This category adds the ability to specify a payload ID. */
@interface GNCStreamPayload (Internal)
+ (instancetype)payloadWithStream:(NSInputStream *)stream identifier:(int64_t)identifier;
@end
/** This category adds the ability to specify a payload ID. */
@interface GNCFilePayload (Internal)
+ (instancetype)payloadWithFileURL:(NSURL *)fileURL identifier:(int64_t)identifier;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,94 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "internal/platform/implementation/ios/Source/GNCPayload.h"
#include "connections/payload.h"
#include "internal/platform/payload_id.h"
#include <stdlib.h>
using ::location::nearby::connections::Payload;
using ::location::nearby::PayloadId;
NS_ASSUME_NONNULL_BEGIN
PayloadId GenerateId() {
return Payload::GenerateId();
}
@implementation GNCBytesPayload
- (instancetype)initWithBytes:(NSData *)bytes identifier:(int64_t)identifier {
self = [super init];
if (self) {
_identifier = identifier;
_bytes = bytes;
}
return self;
}
+ (instancetype)payloadWithBytes:(NSData *)bytes {
return [[self alloc] initWithBytes:bytes identifier:GenerateId()];
}
+ (instancetype)payloadWithBytes:(NSData *)bytes identifier:(int64_t)identifier {
return [[self alloc] initWithBytes:bytes identifier:identifier];
}
@end
@implementation GNCStreamPayload
- (instancetype)initWithStream:(NSInputStream *)stream identifier:(int64_t)identifier {
self = [super init];
if (self) {
_identifier = identifier;
_stream = stream;
}
return self;
}
+ (instancetype)payloadWithStream:(NSInputStream *)stream {
return [[self alloc] initWithStream:stream identifier:GenerateId()];
}
+ (instancetype)payloadWithStream:(NSInputStream *)stream identifier:(int64_t)identifier {
return [[self alloc] initWithStream:stream identifier:identifier];
}
@end
@implementation GNCFilePayload
- (instancetype)initWithFileURL:(NSURL *)fileURL identifier:(int64_t)identifier {
self = [super init];
if (self) {
_identifier = identifier;
_fileURL = [fileURL copy];
}
return self;
}
+ (instancetype)payloadWithFileURL:(NSURL *)fileURL {
return [[self alloc] initWithFileURL:fileURL identifier:GenerateId()];
}
+ (instancetype)payloadWithFileURL:(NSURL *)fileURL identifier:(int64_t)identifier {
return [[self alloc] initWithFileURL:fileURL identifier:identifier];
}
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,55 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
#import "internal/platform/implementation/ios/Source/GNCConnection.h"
#import "internal/platform/implementation/ios/Source/Internal/GNCCore.h"
NS_ASSUME_NONNULL_BEGIN
@class GNCPayloadInfo;
namespace location {
namespace nearby {
namespace connections {
/** This fetches a GNCConnectionHandlers object. */
typedef GNCConnectionHandlers *_Nonnull (^GNCConnectionHandlersProvider)();
/** This fetches a payload dictionary. */
typedef NSMutableDictionary<NSNumber *, GNCPayloadInfo *> *_Nonnull (^GNCPayloadsProvider)();
/** This is the payload handler for an advertiser or discoverer. */
class GNCPayloadListener : public PayloadListener {
public:
GNCPayloadListener(GNCCore *core, GNCConnectionHandlersProvider handlersProvider,
GNCPayloadsProvider payloadsProvider)
: core_(core), handlers_provider_(handlersProvider), payloads_provider_(payloadsProvider) {}
void OnPayload(const std::string& endpoint_id, Payload payload);
void OnPayloadProgress(const std::string& endpoint_id,
const PayloadProgressInfo& info);
private:
GNCCore *core_;
GNCConnectionHandlersProvider handlers_provider_;
GNCPayloadsProvider payloads_provider_;
};
} // namespace connections
} // namespace nearby
} // namespace location
NS_ASSUME_NONNULL_END
@@ -0,0 +1,218 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "internal/platform/implementation/ios/Source/Internal/GNCPayloadListener.h"
#include <string>
#include "connections/core.h"
#include "connections/listeners.h"
#include "connections/payload.h"
#include "internal/platform/byte_array.h"
#include "internal/platform/exception.h"
#include "internal/platform/file.h"
#import "internal/platform/implementation/ios/Source/GNCConnection.h"
#import "internal/platform/implementation/ios/Source/GNCPayload.h"
#import "internal/platform/implementation/ios/Source/Internal/GNCCore.h"
#import "internal/platform/implementation/ios/Source/Internal/GNCCoreConnection.h"
#import "internal/platform/implementation/ios/Source/Internal/GNCPayload+Internal.h"
#include "internal/platform/implementation/ios/Source/Platform/utils.h"
#include "internal/platform/input_stream.h"
NS_ASSUME_NONNULL_BEGIN
namespace location {
namespace nearby {
namespace connections {
void GNCPayloadListener::OnPayload(const std::string &endpoint_id, Payload payload) {
GNCConnectionHandlers *handlers = handlers_provider_();
int64_t payloadId = payload.GetId();
// Note: The payload must be destroyed by each individual payload type handler below, because in
// the Stream payload case, it runs an asynchronous read-write loop, which needs the payload
// and its stream to live until the stream ends.
NSMutableDictionary<NSNumber *, GNCPayloadInfo *> *payloads = payloads_provider_();
switch (payload.GetType()) {
case Payload::Type::kBytes: {
NSData *data = NSDataFromByteArray(payload.AsBytes()); // don't capture C++ object
// Wait for the payload transfer update to arrive before calling the Bytes payload handler.
GNCPayloadInfo *info = [GNCPayloadInfo
infoWithProgress:nil
completion:^(GNCPayloadResult result) {
NSCAssert(result == GNCPayloadResultSuccess, @"Expected success");
if (handlers.bytesPayloadHandler) {
// Call the Bytes payload handler.
dispatch_async(dispatch_get_main_queue(), ^{
handlers.bytesPayloadHandler([GNCBytesPayload payloadWithBytes:data
identifier:payloadId]);
});
}
}];
payloads[@(payloadId)] = info;
break;
}
case Payload::Type::kStream:
if (handlers.streamPayloadHandler) {
// Make a pair of bound streams so data pumped into the output stream becomes
// available for reading from the input stream.
NSInputStream *clientInputStream;
NSOutputStream *clientOutputStream;
// TODO(b/169292092): Base on medium's bandwidth?
[NSStream getBoundStreamsWithBufferSize:1024
inputStream:&clientInputStream
outputStream:&clientOutputStream];
NSProgress *progress = [NSProgress progressWithTotalUnitCount:-1]; // indeterminate
progress.cancellable = YES;
// Pass the payload to the stream payload handler, receiving the completion handler from it.
// Since it returns a value, it must be called synchronously.
__block GNCPayloadResultHandler completion;
dispatch_sync(dispatch_get_main_queue(), ^{
completion = handlers.streamPayloadHandler(
[GNCStreamPayload payloadWithStream:clientInputStream identifier:payloadId],
progress);
});
GNCPayloadInfo *info = [GNCPayloadInfo infoWithProgress:progress completion:completion];
payloads[@(payloadId)] = info;
// This is a loop that reads data from the C++ input stream and writes it to the output
// stream that feeds it to the client input stream.
__block InputStream *payloadInputStream = payload.AsStream();
dispatch_queue_t queue =
dispatch_queue_create("StreamReceiverQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
[clientOutputStream open];
while (true) {
if (progress.isCancelled) {
// Payload was canceled by the client.
core_->_core->CancelPayload(payloadId, ResultCallback{.result_cb = [](Status status) {
// TODO(b/148640962): Implement.
}});
break;
}
ExceptionOr<ByteArray> readResult = payloadInputStream->Read(1024);
if (!readResult.ok()) {
// Error reading from stream.
// TODO(b/169292092): Tell core an error has occurred?
dispatch_async(dispatch_get_main_queue(), ^{
[info callCompletion:GNCPayloadResultFailure];
});
break;
}
ByteArray byteArray = readResult.GetResult();
if (byteArray.Empty()) {
// End of stream.
break;
}
// Loop until it's all been consumed by the client output stream.
NSData *data = NSDataFromByteArray(byteArray);
NSUInteger totalLength = data.length;
NSUInteger totalNumberWritten = 0;
while (totalNumberWritten < totalLength) {
NSInteger numberWritten =
[clientOutputStream write:&((const uint8_t *)data.bytes)[totalNumberWritten]
maxLength:totalLength - totalNumberWritten];
if (numberWritten <= 0) { // stream error or reached end of stream
// TODO(b/169292092): Tell core an error has occurred?
dispatch_async(dispatch_get_main_queue(), ^{
[info callCompletion:GNCPayloadResultFailure];
});
break;
}
totalNumberWritten += numberWritten;
}
}
});
}
break;
case Payload::Type::kFile:
if (handlers.filePayloadHandler) {
InputFile *payloadInputFile = payload.AsFile();
NSURL *fileURL =
[NSURL URLWithString:ObjCStringFromCppString(payloadInputFile->GetFilePath())];
int64_t fileSize = payloadInputFile->GetTotalSize();
NSProgress *progress = [NSProgress progressWithTotalUnitCount:fileSize];
progress.cancellable = YES;
progress.cancellationHandler = ^{
// Payload was canceled by the client.
core_->_core->CancelPayload(payloadId, ResultCallback{.result_cb = [](Status status) {
// TODO(b/148640962): Implement.
}});
};
// Pass the payload to the file payload handler, receiving the completion handler from it.
// Since it returns a value, it must be called synchronously.
__block GNCPayloadResultHandler completion;
void (^passPayloadBlock)(void) = ^{
completion = handlers.filePayloadHandler(
[GNCFilePayload payloadWithFileURL:fileURL identifier:payloadId], progress);
};
if ([NSThread isMainThread]) {
passPayloadBlock();
} else {
dispatch_sync(dispatch_get_main_queue(), passPayloadBlock);
}
GNCPayloadInfo *info = [GNCPayloadInfo infoWithProgress:progress completion:completion];
payloads[@(payloadId)] = info;
}
break;
default:
;// fall through
}
}
void GNCPayloadListener::OnPayloadProgress(const std::string &endpoint_id,
const PayloadProgressInfo &info) {
// Note: The logic in this callback for handling progress updates and payload completion is
// identical for Bytes, Stream and File payloads.
NSMutableDictionary<NSNumber *, GNCPayloadInfo *> *payloads = payloads_provider_();
NSNumber *payloadId = @(info.payload_id);
GNCPayloadInfo *payloadInfo = payloads[payloadId];
if (payloadInfo) {
// Update the progress.
if (payloadInfo.progress) {
payloadInfo.progress.completedUnitCount = info.bytes_transferred;
}
// Call the completion handler for success/failure/canceled, but not in-progress.
if (info.status == PayloadProgressInfo::Status::kInProgress) {
return;
}
GNCPayloadResult result =
(info.status == PayloadProgressInfo::Status::kSuccess) ? GNCPayloadResultSuccess
: (info.status == PayloadProgressInfo::Status::kCanceled) ? GNCPayloadResultCanceled
: GNCPayloadResultFailure;
dispatch_async(dispatch_get_main_queue(), ^{
payloadInfo.completion(result);
});
// Release the payload info.
[payloads removeObjectForKey:payloadId];
}
}
} // namespace connections
} // namespace nearby
} // namespace location
NS_ASSUME_NONNULL_END
@@ -0,0 +1,42 @@
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
#include <string>
#include "connections/listeners.h"
#import "internal/platform/implementation/ios/Source/GNCAdvertiser.h"
#import "internal/platform/implementation/ios/Source/GNCConnection.h"
NS_ASSUME_NONNULL_BEGIN
namespace location {
namespace nearby {
namespace connections {
/** Converts GNCStrategy to Strategy. */
const Strategy& GNCStrategyToStrategy(GNCStrategy strategy);
} // namespace connections
} // namespace nearby
} // namespace location
/** Internal-only properties of the connection result handlers class. */
@interface GNCConnectionResultHandlers ()
@property(nonatomic) GNCConnectionHandler successHandler;
@property(nonatomic) GNCConnectionFailureHandler failureHandler;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,77 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "internal/platform/implementation/ios/Source/Internal/GNCUtils.h"
#include "connections/strategy.h"
#import "internal/platform/implementation/ios/Source/GNCAdvertiser.h"
#import "internal/platform/implementation/ios/Source/GNCConnection.h"
NS_ASSUME_NONNULL_BEGIN
namespace location {
namespace nearby {
namespace connections {
const Strategy& GNCStrategyToStrategy(GNCStrategy strategy) {
switch (strategy) {
case GNCStrategyCluster:
return Strategy::kP2pCluster;
case GNCStrategyStar:
return Strategy::kP2pStar;
case GNCStrategyPointToPoint:
return Strategy::kP2pPointToPoint;
}
}
} // namespace connections
} // namespace nearby
} // namespace location
@implementation GNCConnectionHandlers
- (instancetype)initWithBuilderBlock:(void (^)(GNCConnectionHandlers*))builderBlock {
self = [super init];
if (self) {
builderBlock(self);
}
return self;
}
+ (instancetype)handlersWithBuilder:(void (^)(GNCConnectionHandlers * _Nonnull))builderBlock {
return [[self alloc] initWithBuilderBlock:builderBlock];
}
@end
@implementation GNCConnectionResultHandlers
- (instancetype)initWithSuccessHandler:(GNCConnectionHandler)successHandler
failureHandler:(GNCConnectionFailureHandler)failureHandler {
self = [super init];
if (self) {
_successHandler = successHandler;
_failureHandler = failureHandler;
}
return self;
}
+ (instancetype)successHandler:(GNCConnectionHandler)successHandler
failureHandler:(GNCConnectionFailureHandler)failureHandler {
return [[self alloc] initWithSuccessHandler:successHandler failureHandler:failureHandler];
}
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,151 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "internal/platform/implementation/platform.h"
#include <string>
#import "internal/platform/implementation/ios/Source/Internal/GNCCore.h"
#include "internal/platform/implementation/ios/Source/Platform/atomic_boolean.h"
#include "internal/platform/implementation/ios/Source/Platform/atomic_uint32.h"
#include "internal/platform/implementation/ios/Source/Platform/condition_variable.h"
#include "internal/platform/implementation/ios/Source/Platform/count_down_latch.h"
#include "internal/platform/implementation/ios/Source/Platform/input_file.h"
#import "internal/platform/implementation/ios/Source/Platform/log_message.h"
#import "internal/platform/implementation/ios/Source/Platform/multi_thread_executor.h"
#include "internal/platform/implementation/ios/Source/Platform/mutex.h"
#import "internal/platform/implementation/ios/Source/Platform/scheduled_executor.h"
#import "internal/platform/implementation/ios/Source/Platform/single_thread_executor.h"
#import "internal/platform/implementation/ios/Source/Platform/utils.h"
#include "internal/platform/implementation/ios/Source/Platform/wifi_lan.h"
#include "internal/platform/implementation/mutex.h"
#include "internal/platform/implementation/shared/file.h"
#include "internal/platform/payload_id.h"
namespace location {
namespace nearby {
namespace api {
namespace {
std::string GetPayloadPath(PayloadId payload_id) {
// This is to get a file path, e.g. /tmp/[payload_id], for the storage of payload file.
// NOTE: Per
// https://developer.apple.com/library/content/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html
// Files saved in the /tmp directory will be deleted by the system. Callers should be responsible
// for copying the files to the permanent storage.
NSString* payloadIdString = ObjCStringFromCppString(std::to_string(payload_id));
return CppStringFromObjCString(
[NSTemporaryDirectory() stringByAppendingPathComponent:payloadIdString]);
}
} // namespace
// Atomics:
std::unique_ptr<AtomicBoolean> ImplementationPlatform::CreateAtomicBoolean(bool initial_value) {
return std::make_unique<ios::AtomicBoolean>(initial_value);
}
std::unique_ptr<AtomicUint32> ImplementationPlatform::CreateAtomicUint32(
std::uint32_t initial_value) {
return std::make_unique<ios::AtomicUint32>(initial_value);
}
std::unique_ptr<CountDownLatch> ImplementationPlatform::CreateCountDownLatch(std::int32_t count) {
return std::make_unique<ios::CountDownLatch>(count);
}
std::unique_ptr<Mutex> ImplementationPlatform::CreateMutex(Mutex::Mode mode) {
// iOS does not support unchecked Mutex in debug mode, therefore
// ios::Mutex is used for both kRegular and kRegularNoCheck.
if (mode == Mutex::Mode::kRecursive) {
return absl::make_unique<ios::RecursiveMutex>();
} else {
return absl::make_unique<ios::Mutex>();
}
}
std::unique_ptr<ConditionVariable> ImplementationPlatform::CreateConditionVariable(Mutex* mutex) {
return std::make_unique<ios::ConditionVariable>(static_cast<ios::Mutex*>(mutex));
}
std::unique_ptr<InputFile> ImplementationPlatform::CreateInputFile(PayloadId payload_id,
std::int64_t total_size) {
// Extract the NSURL object with payload_id from |GNCCore| which stores the maps. If the retrieved
// NSURL object is not nil, we create InputFile by ios::InputFile. The difference is
// that ios::InputFile implements to read bytes from local real file for sending.
GNCCore* core = GNCGetCore();
NSURL* url = [core extractURLWithPayloadID:payload_id];
if (url != nil) {
return absl::make_unique<ios::InputFile>(url);
} else {
return absl::make_unique<shared::InputFile>(GetPayloadPath(payload_id), total_size);
}
}
std::unique_ptr<OutputFile> ImplementationPlatform::CreateOutputFile(PayloadId payload_id) {
return absl::make_unique<shared::OutputFile>(GetPayloadPath(payload_id));
}
std::unique_ptr<LogMessage> ImplementationPlatform::CreateLogMessage(
const char* file, int line, LogMessage::Severity severity) {
return absl::make_unique<ios::LogMessage>(file, line, severity);
}
// Java-like Executors
std::unique_ptr<SubmittableExecutor> ImplementationPlatform::CreateSingleThreadExecutor() {
return std::make_unique<ios::SingleThreadExecutor>();
}
std::unique_ptr<SubmittableExecutor> ImplementationPlatform::CreateMultiThreadExecutor(
int max_concurrency) {
return std::make_unique<ios::MultiThreadExecutor>(max_concurrency);
}
std::unique_ptr<ScheduledExecutor> ImplementationPlatform::CreateScheduledExecutor() {
return std::make_unique<ios::ScheduledExecutor>();
}
// Mediums
std::unique_ptr<BluetoothAdapter> ImplementationPlatform::CreateBluetoothAdapter() {
return nullptr;
}
std::unique_ptr<BluetoothClassicMedium> ImplementationPlatform::CreateBluetoothClassicMedium(
api::BluetoothAdapter& adapter) {
return nullptr;
}
std::unique_ptr<BleMedium> ImplementationPlatform::CreateBleMedium(api::BluetoothAdapter& adapter) {
return nullptr;
}
std::unique_ptr<ble_v2::BleMedium> ImplementationPlatform::CreateBleV2Medium(
api::BluetoothAdapter& adapter) {
return nullptr;
}
std::unique_ptr<ServerSyncMedium> ImplementationPlatform::CreateServerSyncMedium() {
return nullptr;
}
std::unique_ptr<WifiMedium> ImplementationPlatform::CreateWifiMedium() { return nullptr; }
std::unique_ptr<WifiLanMedium> ImplementationPlatform::CreateWifiLanMedium() {
return std::make_unique<ios::WifiLanMedium>();
}
std::unique_ptr<WebRtcMedium> ImplementationPlatform::CreateWebRtcMedium() { return nullptr; }
} // namespace api
} // namespace nearby
} // namespace location
@@ -0,0 +1,56 @@
load("//tools/build_defs/apple:objc.bzl", "objc_proto_library")
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
licenses(["notice"])
package(default_visibility = ["//internal/platform/implementation/ios:__subpackages__"])
objc_library(
name = "Mediums",
srcs = [
"GNCLeaks.m",
"GNCMConnection.m",
"WifiLan/GNCMBonjourBrowser.m",
"WifiLan/GNCMBonjourConnection.m",
"WifiLan/GNCMBonjourService.m",
"WifiLan/GNCMBonjourUtils.m",
],
hdrs = [
"GNCLeaks.h",
"GNCMConnection.h",
"WifiLan/GNCMBonjourBrowser.h",
"WifiLan/GNCMBonjourConnection.h",
"WifiLan/GNCMBonjourService.h",
"WifiLan/GNCMBonjourUtils.h",
],
deps = [
":ObjCProtos",
"//internal/platform/implementation/ios/Source/Shared",
"@com_google_absl//absl/numeric:int128",
"//third_party/objective_c/google_toolbox_for_mac:GTM_Logger",
],
)
objc_proto_library(
name = "ObjCProtos",
deps = [":Protos"],
)
proto_library(
name = "Protos",
deps = [
"//connections/implementation/proto:offline_wire_formats_proto",
],
)
@@ -0,0 +1,18 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
// Verifies that an object has been deallocated after the given time period.
void GNCVerifyDealloc(id object, NSTimeInterval timeInterval);
@@ -0,0 +1,27 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "internal/platform/implementation/ios/Source/Mediums/GNCLeaks.h"
void GNCVerifyDealloc(id object, NSTimeInterval timeInterval) {
#if DEBUG
__weak id weakObj = object;
NSCAssert(weakObj != nil, @"Pointer to %@ is already nil", weakObj);
NSLog(@"Verifying deallocation of %@", NSStringFromClass([weakObj class]));
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeInterval * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
NSCAssert(weakObj == nil, @"%@ not deallocated.", weakObj);
});
#endif
}
@@ -0,0 +1,101 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/** Result of a medium payload transfer. */
typedef NS_ENUM(NSInteger, GNCMPayloadResult) {
GNCMPayloadResultSuccess, // Payload delivery was successful.
GNCMPayloadResultFailure, // An error occurred during payload delivery.
GNCMPayloadResultCanceled, // Payload delivery was canceled.
};
/** Handler for a @c GNCMPayloadResult value. */
typedef void (^GNCMPayloadResultHandler)(GNCMPayloadResult);
/**
* A progress handler is periodically called during payload delivery. It is passed a value
* ranging from 0 (when the operation has just started) to the total size (when the operation is
* finished).
*/
typedef void (^GNCMProgressHandler)(size_t count);
/** This handler is called when data is received from a remote endpoint. */
typedef void (^GNCMPayloadHandler)(NSData *data);
/**
* This represents a connection with a remote endpoint at the medium level. Use it to send
* payloads to the remote endpoint, and release it to disconnect.
*/
@protocol GNCMConnection <NSObject>
/**
* Sends data to the remote endpoint. Wait for the completion to be called before sending another
* payload.
*
* @param payload The data to send.
* @param progressHandler Called repeatedly for progress feedback while the data is being sent.
* @param completion Callback called when the data has been fully sent,
* or it has failed to be sent (not connected or disconnected).
*/
- (void)sendData:(NSData *)payload
progressHandler:(GNCMProgressHandler)progressHandler
completion:(GNCMPayloadResultHandler)completion;
@end
/** This class contains optional handlers for a connection. */
@interface GNCMConnectionHandlers : NSObject
/** This handler is called when data is sent from the remote endpoint. */
@property(nonatomic) GNCMPayloadHandler payloadHandler;
/** This handler is called when the connection is ended. */
@property(nonatomic) dispatch_block_t disconnectedHandler;
/** This method creates a GNCMConnectionHandlers object from payload and disconnect handlers. */
+ (instancetype)payloadHandler:(GNCMPayloadHandler)payloadHandler
disconnectedHandler:(dispatch_block_t)disconnectedHandler;
@end
/**
* This handler takes a GNCMConnection object and returns a GNCMConnectionHandlers object. It is
* called when a connection is successfully made with a remote endpoint. If |connection| is nil,
* the connection couldn't be established; in this case, return nil.
*/
typedef GNCMConnectionHandlers *_Nullable (^GNCMConnectionHandler)(
id<GNCMConnection> __nullable connection);
/**
* This handler is called by a discovering endpoint to request a connection with an an advertising
* endpoint.
*/
typedef void (^GNCMConnectionRequester)(GNCMConnectionHandler connectionHandler);
/** This handler is called when a previously discovered advertising endpoint is lost. */
typedef void (^GNCMEndpointLostHandler)(void);
/**
* This handler is called on a discoverer when a nearby advertising endpoint is
* discovered. Calls |requestConnection| to request a connection with the advertiser.
*/
typedef GNCMEndpointLostHandler _Nonnull (^GNCMEndpointFoundHandler)(
NSString *endpointId, NSString *serviceType, NSString *serviceName,
NSDictionary<NSString *, NSData *> *_Nullable TXTRecordData,
GNCMConnectionRequester requestConnection);
NS_ASSUME_NONNULL_END
@@ -0,0 +1,31 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "internal/platform/implementation/ios/Source/Mediums/GNCMConnection.h"
NS_ASSUME_NONNULL_BEGIN
@implementation GNCMConnectionHandlers
+ (instancetype)payloadHandler:(GNCMPayloadHandler)payloadHandler
disconnectedHandler:(dispatch_block_t)disconnectedHandler {
GNCMConnectionHandlers *handlers = [[GNCMConnectionHandlers alloc] init];
handlers.payloadHandler = payloadHandler;
handlers.disconnectedHandler = disconnectedHandler;
return handlers;
}
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,42 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
#import "internal/platform/implementation/ios/Source/Mediums/GNCMConnection.h"
NS_ASSUME_NONNULL_BEGIN
/**
* GNCMBonjourBrowser browses mDNS services publishing the specified mDNS type and domain. The mDNS
* type is a string formatted as "_[serviceIdHash]._tcp." in which [serviceIdHash] is generated as
* a SHA-256 hash from the service ID and taken the 6 first bytes of string in upper case.
* Calls the specififed endpoint found handler when one is found. The endpoint found handler
* supplies a requester block, which can be called to establish a socket to the service found.
*
* Don't hold the strong reference of caller self in endpointFoundHandler to ensure there is no
* retain cycle between them.
*
* @param serviceType An mDNS type that uniquely identifies the published service to search for.
* @param endpointFoundHandler The handler that is called when an endpoint publishing the service
* ID is discovered.
*/
@interface GNCMBonjourBrowser : NSObject
- (instancetype)initWithServiceType:(NSString *)serviceType
endpointFoundHandler:(GNCMEndpointFoundHandler)handler;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,176 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "internal/platform/implementation/ios/Source/Mediums/WifiLan/GNCMBonjourBrowser.h"
#import "internal/platform/implementation/ios/Source/Mediums/GNCMConnection.h"
#import "internal/platform/implementation/ios/Source/Mediums/WifiLan/GNCMBonjourConnection.h"
#import "internal/platform/implementation/ios/Source/Mediums/WifiLan/GNCMBonjourUtils.h"
#import "GoogleToolboxForMac/GTMLogger.h"
typedef NSString *GNCEndpointId;
@interface GNCMNetServiceInfo : NSObject
@property(nonatomic) NSNetService *service;
@property(nonatomic, copy) GNCMEndpointLostHandler endpointLostHandler;
@property(nonatomic, copy, nullable) GNCMConnectionHandler connectionHandler;
@end
@implementation GNCMNetServiceInfo
+ (instancetype)infoWithService:(NSNetService *)service {
GNCMNetServiceInfo *info = [[GNCMNetServiceInfo alloc] init];
info.service = service;
return info;
}
- (BOOL)isEqual:(GNCMNetServiceInfo *)object {
return [_service isEqual:object.service];
}
- (NSUInteger)hash {
return [_service hash];
}
@end
@interface GNCMBonjourBrowser () <NSNetServiceBrowserDelegate, NSNetServiceDelegate>
@property(nonatomic, copy) GNCMEndpointFoundHandler endpointFoundHandler;
@property(nonatomic) NSNetServiceBrowser *netBrowser;
@property(nonatomic) NSMutableDictionary<GNCEndpointId, GNCMNetServiceInfo *> *endpoints;
@end
@implementation GNCMBonjourBrowser
- (instancetype)initWithServiceType:(NSString *)serviceType
endpointFoundHandler:(GNCMEndpointFoundHandler)handler {
self = [super init];
if (self) {
_endpointFoundHandler = handler;
_netBrowser = [[NSNetServiceBrowser alloc] init];
_netBrowser.delegate = self;
[_netBrowser scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[_netBrowser searchForServicesOfType:serviceType inDomain:GNCMBonjourDomain];
_endpoints = [NSMutableDictionary dictionary];
}
return self;
}
#pragma mark NSNetServiceBrowserDelegate
- (void)netServiceBrowserWillSearch:(NSNetServiceBrowser *)browser {
GTMLoggerDebug(@"Browsing");
}
- (void)netServiceBrowserDidStopSearch:(NSNetServiceBrowser *)browser {
GTMLoggerDebug(@"Stop browsing");
}
- (void)netServiceBrowser:(NSNetServiceBrowser *)browser
didNotSearch:(NSDictionary<NSString *, NSNumber *> *)errorDict {
GTMLoggerDebug(@"Not browsing with errorDict: %@", errorDict);
}
- (void)netServiceBrowser:(NSNetServiceBrowser *)browser
didFindService:(NSNetService *)service
moreComing:(BOOL)moreComing {
GTMLoggerDebug(@"Found service: %@", service);
GNCMNetServiceInfo *info = [GNCMNetServiceInfo infoWithService:service];
// Just to be safe, check if the service is already known and deal with it accordingly.
NSArray<GNCEndpointId> *endpointIds = [_endpoints allKeysForObject:info];
if (endpointIds.count > 0) return;
// Add the newly discovered service to the list of services.
GNCEndpointId endpointId = [[NSUUID UUID] UUIDString];
_endpoints[endpointId] = info;
service.delegate = self;
[service resolveWithTimeout:0];
}
- (void)netServiceBrowser:(NSNetServiceBrowser *)browser
didRemoveService:(NSNetService *)service
moreComing:(BOOL)moreComing {
GTMLoggerDebug(@"Lost service: %@", service);
NSArray<GNCEndpointId> *endpointIds =
[_endpoints allKeysForObject:[GNCMNetServiceInfo infoWithService:service]];
NSAssert(endpointIds.count <= 1, @"Unexpected duplicate service");
if (endpointIds.count > 0) {
GNCEndpointId endpointId = endpointIds[0];
GNCMNetServiceInfo *info = _endpoints[endpointId];
[_endpoints removeObjectForKey:endpointId];
// Tail call to preserve reentrancy.
if (info.endpointLostHandler) {
info.endpointLostHandler();
}
}
}
#pragma mark NSNetServiceDelegate
- (void)netServiceDidResolveAddress:(NSNetService *)service {
GTMLoggerDebug(@"Resolved service: %@ addresses: %@", service, service.addresses);
GNCMNetServiceInfo *info = [GNCMNetServiceInfo infoWithService:service];
NSArray<GNCEndpointId> *endpointIds = [_endpoints allKeysForObject:info];
if (endpointIds.count > 0) {
// Get TXTRecord data.
NSData *data = [service TXTRecordData];
NSDictionary<NSString *, NSData *> *TXTRecordData =
[NSNetService dictionaryFromTXTRecordData:data];
// The endpointLostHandler is returned from the endpointFoundHandler. The main benefit of this
// is that it allows rejection from the remote endpoint to be received by the local endpoint
// before the local endpoint has accepted or rejected.
info.endpointLostHandler = _endpointFoundHandler(
endpointIds[0], service.type, service.name, TXTRecordData,
^(GNCMConnectionHandler connectionHandler) {
// A connection is requested, so resolve the service to get the I/O streams.
info.connectionHandler = connectionHandler;
if (info.connectionHandler) {
dispatch_sync(dispatch_get_main_queue(), ^{
NSInputStream *inputStream;
NSOutputStream *outputStream;
[info.service scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[info.service getInputStream:&inputStream outputStream:&outputStream];
GNCMBonjourConnection *connection =
[[GNCMBonjourConnection alloc] initWithInputStream:inputStream
outputStream:outputStream
queue:nil];
connection.connectionHandlers = info.connectionHandler(connection);
});
}
});
_endpoints[endpointIds[0]] = info;
}
}
- (void)netService:(NSNetService *)service
didNotResolve:(NSDictionary<NSString *, NSNumber *> *)errorDict {
GTMLoggerDebug(@"Did not resolve service: %@", service);
NSArray<GNCEndpointId> *endpointIds =
[_endpoints allKeysForObject:[GNCMNetServiceInfo infoWithService:service]];
if (endpointIds.count > 0) {
_endpoints[endpointIds[0]].connectionHandler = nil;
}
}
@end
@@ -0,0 +1,38 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
#import "internal/platform/implementation/ios/Source/Mediums/GNCMConnection.h"
NS_ASSUME_NONNULL_BEGIN
/**
* This medium connection sends and receives payloads over the NSInputStream and NSOutputStream
* passed to it.
*
* @param inputStream The input stream to read from.
* @param outputStream The output stream to write to.
* @param queue The queue on which the GNCMConnection callbacks will be called. If nil, the main
* queue is used.
*/
@interface GNCMBonjourConnection : NSObject <GNCMConnection>
@property(nonatomic) GNCMConnectionHandlers *connectionHandlers;
- (instancetype)initWithInputStream:(NSInputStream *)inputStream
outputStream:(NSOutputStream *)outputStream
queue:(nullable dispatch_queue_t)queue;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,224 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "internal/platform/implementation/ios/Source/Mediums/WifiLan/GNCMBonjourConnection.h"
#import "internal/platform/implementation/ios/Source/Mediums/GNCMConnection.h"
#import "GoogleToolboxForMac/GTMLogger.h"
enum { kMaxPacketSize = 32 * 1024 };
@interface GNCMBonjourConnection () <NSStreamDelegate>
@property(nonatomic) NSInputStream *inputStream;
@property(nonatomic) NSOutputStream *outputStream;
@property(nonatomic) dispatch_queue_t callbackQueue;
@property(nonatomic, copy, nullable) NSData *dataBeingWritten;
@property(nonatomic) NSInteger numberOfBytesLeftToWrite;
@property(nonatomic, copy, nullable) GNCMProgressHandler progressHandler;
@property(nonatomic, copy, nullable) GNCMPayloadResultHandler completion;
@property(nonatomic) BOOL inputStreamOpen;
@property(nonatomic) BOOL outputStreamOpen;
@end
@implementation GNCMBonjourConnection
- (instancetype)initWithInputStream:(NSInputStream *)inputStream
outputStream:(NSOutputStream *)outputStream
queue:(nullable dispatch_queue_t)queue {
self = [super init];
if (self) {
_inputStreamOpen = NO;
_outputStreamOpen = NO;
_inputStream = inputStream;
_inputStream.delegate = self;
[_inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[_inputStream open];
_outputStream = outputStream;
_outputStream.delegate = self;
[_outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[_outputStream open];
_callbackQueue = queue;
}
return self;
}
- (void)dealloc {
[self closeStreams];
}
- (void)sendData:(NSData *)payload
progressHandler:(GNCMProgressHandler)progressHandler
completion:(GNCMPayloadResultHandler)completion {
if (_dataBeingWritten) {
GTMLoggerInfo(@"Attempting to send payload while one is already in flight");
[self dispatchCallback:^{
completion(GNCMPayloadResultFailure);
}];
return;
}
self.dataBeingWritten = payload;
self.numberOfBytesLeftToWrite = payload.length;
self.progressHandler = progressHandler;
self.completion = completion;
[self writeChunk];
}
#pragma mark NSStreamDelegate
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)event {
switch (event) {
case NSStreamEventHasBytesAvailable: {
// Data has arrived on the input stream.
NSAssert(stream == _inputStream, @"Error: Expected input stream");
if (_connectionHandlers.payloadHandler) {
uint8_t bytesRead[kMaxPacketSize];
NSInteger numberOfBytesRead;
@synchronized(self.inputStream) {
numberOfBytesRead = [self.inputStream read:bytesRead maxLength:kMaxPacketSize];
}
NSData *data = nil;
if (numberOfBytesRead > 0) {
GTMLoggerInfo(@"Read %lu bytes", (u_long)numberOfBytesRead);
data = [NSData dataWithBytes:bytesRead length:numberOfBytesRead];
}
[self dispatchCallback:^{
self.connectionHandlers.payloadHandler(data ?: [NSData data]);
}];
}
break;
}
case NSStreamEventHasSpaceAvailable:
// There is space available on the output stream.
NSAssert(stream == _outputStream, @"Error: Expected output stream");
// Schedule this in a future runloop cycle because -writeChunk, which can cause this event
// to be received synchronously, is not reentrant.
[self performSelector:@selector(writeChunk) withObject:nil afterDelay:0.0];
break;
case NSStreamEventErrorOccurred:
GTMLoggerInfo(@"Stream error: %@", [stream streamError]);
// Fall through.
case NSStreamEventEndEncountered: {
GTMLoggerInfo(@"Stream closing");
[self closeStreams];
if (_connectionHandlers.disconnectedHandler) {
[self dispatchCallback:^{
self.connectionHandlers.disconnectedHandler();
}];
}
break;
}
case NSStreamEventOpenCompleted:
if (stream == _inputStream) {
_inputStreamOpen = YES;
}
if (stream == _outputStream) {
_outputStreamOpen = YES;
}
// Schedule this in a future runloop cycle because -writeChunk, which can cause this event
// to be received synchronously, is not reentrant.
[self performSelector:@selector(writeChunk) withObject:nil afterDelay:0.0];
break;
case NSStreamEventNone:
default:
break;
}
}
#pragma mark Private
// Calls a block on the callback queue.
- (void)dispatchCallback:(dispatch_block_t)block {
dispatch_async(_callbackQueue ?: dispatch_get_main_queue(), block);
}
// Writes a chunk of the outgoing data to the output stream, calling the progress and completion
// handlers as needed.
- (void)writeChunk {
void (^reportProgress)(size_t) = ^(size_t count) {
// Captures the progress handler because the property is nilled out below.
GNCMProgressHandler progressHandler = _progressHandler;
if (progressHandler != nil) {
[self dispatchCallback:^{
progressHandler(count);
}];
}
};
void (^completed)(GNCMPayloadResult) = ^(GNCMPayloadResult result) {
reportProgress(_dataBeingWritten.length);
_progressHandler = nil;
_dataBeingWritten = nil;
// Captures the completion because the property is nilled out below.
GNCMPayloadResultHandler completion = _completion;
if (completion != nil) {
[self dispatchCallback:^{
completion(result);
}];
}
_completion = nil;
};
@synchronized(_outputStream) {
if (_inputStreamOpen && _outputStreamOpen && _numberOfBytesLeftToWrite) {
NSUInteger dataLength = (UInt32)_dataBeingWritten.length;
if (_numberOfBytesLeftToWrite == dataLength) {
GTMLoggerInfo(@"Starting a write operation of length %lu", (u_long)dataLength);
}
NSInteger numberOfPayloadBytesWritten =
[_outputStream write:&_dataBeingWritten.bytes[dataLength - _numberOfBytesLeftToWrite]
maxLength:_numberOfBytesLeftToWrite];
GTMLoggerInfo(@"Wrote %lu bytes", (u_long)numberOfPayloadBytesWritten);
if (numberOfPayloadBytesWritten >= 0) {
_numberOfBytesLeftToWrite -= numberOfPayloadBytesWritten;
reportProgress(_dataBeingWritten.length - _numberOfBytesLeftToWrite);
if (_numberOfBytesLeftToWrite < 0) {
GTMLoggerInfo(@"Unexpected number of bytes written");
_numberOfBytesLeftToWrite = 0;
}
if (_numberOfBytesLeftToWrite == 0) completed(GNCMPayloadResultSuccess);
} else {
GTMLoggerInfo(@"Error writing to output stream");
completed(GNCMPayloadResultFailure);
}
}
}
}
- (void)closeStreams {
@synchronized(_inputStream) {
[_inputStream close];
_inputStream = nil;
}
@synchronized(_outputStream) {
[_outputStream close];
_outputStream = nil;
}
}
@end
@@ -0,0 +1,48 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
#import "internal/platform/implementation/ios/Source/Mediums/GNCMConnection.h"
NS_ASSUME_NONNULL_BEGIN
/**
* GNCMBonjourService publishes mDNS type and domain with the specified service ID via Apple
* Bonjour service. The mDNS type is a string formatted as "_[serviceIdHash]._tcp." in which
* [serviceIdHash] is generated as a SHA-256 hash from the service ID and taken the 6 first bytes
* of string in upper case.
* When the service connects, the specified |endpointConnectedHandler| is called, which establishes
* a connection to the browser.
*
* Don't hold the strong reference of caller self in endpointConnectedHandler to ensure there is no
* retain cycle between them.
*
* @param serviceName A service name that embeds the |WifiLanServiceInfo| information.
* @param serviceType An mDNS type that uniquely identifies the published service to search for.
* @param port The requesting socket port number.
* @param txtRecordData The TXTRecord data.
* @param endpointConnectedHandler The handler that is called when a browser connects.
*/
@interface GNCMBonjourService : NSObject
- (instancetype)initWithServiceName:(NSString *)serviceName
serviceType:(NSString *)serviceType
port:(NSInteger)port
TXTRecordData:(NSDictionary<NSString *, NSData *> *)TXTRecordData
endpointConnectedHandler:(GNCMConnectionHandler)handler;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,88 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "internal/platform/implementation/ios/Source/Mediums/WifiLan/GNCMBonjourService.h"
#import "internal/platform/implementation/ios/Source/Mediums/GNCMConnection.h"
#import "internal/platform/implementation/ios/Source/Mediums/WifiLan/GNCMBonjourConnection.h"
#import "internal/platform/implementation/ios/Source/Mediums/WifiLan/GNCMBonjourUtils.h"
#import "GoogleToolboxForMac/GTMLogger.h"
@interface GNCMBonjourService () <NSNetServiceDelegate>
@property(nonatomic, copy) NSString *serviceName;
@property(nonatomic, copy) NSString *serviceType;
@property(nonatomic, copy) GNCMConnectionHandler endpointConnectedHandler;
@property(nonatomic) NSNetService *netService;
@end
@implementation GNCMBonjourService
- (instancetype)initWithServiceName:(NSString *)serviceName
serviceType:(NSString *)serviceType
port:(NSInteger)port
TXTRecordData:(NSDictionary<NSString *, NSData *> *)TXTRecordData
endpointConnectedHandler:(GNCMConnectionHandler)handler {
self = [super init];
if (self) {
_serviceName = [serviceName copy];
_serviceType = [serviceType copy];
_endpointConnectedHandler = handler;
_netService = [[NSNetService alloc] initWithDomain:GNCMBonjourDomain
type:_serviceType
name:_serviceName
port:port];
[_netService setTXTRecordData:[NSNetService dataFromTXTRecordDictionary:TXTRecordData]];
_netService.delegate = self;
[_netService scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[_netService publishWithOptions:NSNetServiceListenForConnections];
}
return self;
}
- (void)dealloc {
[_netService stop];
}
#pragma mark NSNetServiceDelegate
- (void)netServiceDidPublish:(NSNetService *)service {
GTMLoggerDebug(@"Did publish service: %@", service);
}
- (void)netService:(NSNetService *)service
didNotPublish:(NSDictionary<NSString *, NSNumber *> *)errorDict {
GTMLoggerDebug(@"Error publishing: service: %@, errorDic: %@", service, errorDict);
}
- (void)netServiceDidStop:(NSNetService *)service {
GTMLoggerDebug(@"Stopped publishing service: %@", service);
}
- (void)netService:(NSNetService *)service
didAcceptConnectionWithInputStream:(NSInputStream *)inputStream
outputStream:(NSOutputStream *)outputStream {
GTMLoggerDebug(@"Accepted connection, service: %@", service);
GNCMBonjourConnection *connection =
[[GNCMBonjourConnection alloc] initWithInputStream:inputStream
outputStream:outputStream
queue:nil];
connection.connectionHandlers = _endpointConnectedHandler(connection);
}
@end
@@ -0,0 +1,18 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
// mDNS domain.
FOUNDATION_EXPORT NSString *_Nonnull const GNCMBonjourDomain;
@@ -0,0 +1,17 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "internal/platform/implementation/ios/Source/Mediums/WifiLan/GNCMBonjourUtils.h"
NSString *const GNCMBonjourDomain = @"local";
@@ -0,0 +1,93 @@
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
licenses(["notice"])
package(default_visibility = ["//internal/platform/implementation/ios:__subpackages__"])
objc_library(
name = "Platform",
srcs = [
"crypto.mm",
"input_file.mm",
"log_message.mm",
"multi_thread_executor.mm",
"scheduled_executor.mm",
"utils.mm",
"wifi_lan.mm",
],
hdrs = [
"input_file.h",
"log_message.h",
"multi_thread_executor.h",
"scheduled_executor.h",
"single_thread_executor.h",
"utils.h",
"wifi_lan.h",
],
sdk_frameworks = [
"CoreBluetooth",
"CoreFoundation",
],
deps = [
":Platform_cc",
"//internal/platform/implementation:platform",
"//internal/platform/implementation:types",
"//internal/platform/implementation/ios/Source/Mediums",
"//internal/platform/implementation/ios/Source/Shared",
"//internal/platform/implementation/shared:file",
"//third_party/objective_c/google_toolbox_for_mac:GTM_Logger",
],
)
cc_library(
name = "Platform_cc",
srcs = [
"condition_variable.cc",
"count_down_latch.cc",
"system_clock.cc",
],
hdrs = [
"atomic_boolean.h",
"atomic_uint32.h",
"condition_variable.h",
"count_down_latch.h",
"mutex.h",
],
deps = [
"//internal/platform/implementation:platform",
"//internal/platform/implementation:types",
"@com_google_absl//absl/strings:str_format",
"@com_google_absl//absl/synchronization",
"@com_google_absl//absl/time",
],
)
cc_test(
name = "Platform_cc_test",
srcs = [
"atomic_boolean_test.cc",
"atomic_uint32_test.cc",
"condition_variable_test.cc",
"count_down_latch_test.cc",
"mutex_test.cc",
],
shard_count = 16,
deps = [
":Platform_cc",
"@com_google_googletest//:gtest_main","@com_github_protobuf_matchers//protobuf-matchers:protobuf-matchers",
"@com_google_absl//absl/synchronization",
"@com_google_absl//absl/time",
"@com_google_nisaba//nisaba/port:thread_pool/fiber",
],
)
@@ -0,0 +1,46 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_IMPL_IOS_ATOMIC_BOOLEAN_H_
#define PLATFORM_IMPL_IOS_ATOMIC_BOOLEAN_H_
#include <atomic>
#include "internal/platform/implementation/atomic_boolean.h"
namespace location {
namespace nearby {
namespace ios {
// Concrete AtomicBoolean implementation.
class AtomicBoolean : public api::AtomicBoolean {
public:
explicit AtomicBoolean(bool initial_value) : value_(initial_value) {}
~AtomicBoolean() override = default;
AtomicBoolean(const AtomicBoolean&) = delete;
AtomicBoolean& operator=(const AtomicBoolean&) = delete;
bool Get() const override { return value_.load(); }
bool Set(bool value) override { return value_.exchange(value); }
private:
std::atomic_bool value_;
};
} // namespace ios
} // namespace nearby
} // namespace location
#endif // PLATFORM_IMPL_IOS_ATOMIC_BOOLEAN_H_
@@ -0,0 +1,78 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "internal/platform/implementation/ios/Source/Platform/atomic_boolean.h"
#include "gtest/gtest.h"
#include "thread/fiber/fiber.h"
namespace location {
namespace nearby {
namespace ios {
namespace {
TEST(AtomicBooleanTest, SetOnSameThread) {
AtomicBoolean atomic_boolean_{false};
EXPECT_EQ(false, atomic_boolean_.Get());
atomic_boolean_.Set(true);
EXPECT_EQ(true, atomic_boolean_.Get());
}
TEST(AtomicBooleanTest, MultipleSetGetOnSameThread) {
AtomicBoolean atomic_boolean_{false};
EXPECT_EQ(false, atomic_boolean_.Get());
atomic_boolean_.Set(true);
EXPECT_EQ(true, atomic_boolean_.Get());
atomic_boolean_.Set(true);
EXPECT_EQ(true, atomic_boolean_.Get());
atomic_boolean_.Set(false);
EXPECT_EQ(false, atomic_boolean_.Get());
atomic_boolean_.Set(true);
EXPECT_EQ(true, atomic_boolean_.Get());
}
TEST(AtomicBooleanTest, SetOnNewThread) {
AtomicBoolean atomic_boolean_{false};
EXPECT_EQ(false, atomic_boolean_.Get());
thread::Fiber f([&] { atomic_boolean_.Set(true); });
f.Join();
EXPECT_EQ(true, atomic_boolean_.Get());
}
TEST(AtomicBooleanTest, GetOnNewThread) {
AtomicBoolean atomic_boolean_{false};
EXPECT_EQ(false, atomic_boolean_.Get());
atomic_boolean_.Set(true);
EXPECT_EQ(true, atomic_boolean_.Get());
thread::Fiber f([&] { EXPECT_EQ(true, atomic_boolean_.Get()); });
f.Join();
}
} // namespace
} // namespace ios
} // namespace nearby
} // namespace location
@@ -0,0 +1,47 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_IMPL_IOS_ATOMIC_UINT32_H_
#define PLATFORM_IMPL_IOS_ATOMIC_UINT32_H_
#include <atomic>
#include <cstdint>
#include "internal/platform/implementation/atomic_reference.h"
namespace location {
namespace nearby {
namespace ios {
// Concrete AtomicUint32 implementation.
class AtomicUint32 : public api::AtomicUint32 {
public:
explicit AtomicUint32(std::uint32_t initial_value) : value_(initial_value) {}
~AtomicUint32() override = default;
AtomicUint32(const AtomicUint32&) = delete;
AtomicUint32& operator=(const AtomicUint32&) = delete;
std::uint32_t Get() const override { return value_; }
void Set(std::uint32_t value) override { value_ = value; }
private:
std::atomic<std::uint32_t> value_;
};
} // namespace ios
} // namespace nearby
} // namespace location
#endif // PLATFORM_IMPL_IOS_ATOMIC_UINT32_H_
@@ -0,0 +1,64 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "internal/platform/implementation/ios/Source/Platform/atomic_uint32.h"
#include "gtest/gtest.h"
#include "thread/fiber/fiber.h"
namespace location {
namespace nearby {
namespace ios {
namespace {
TEST(AtomicUint32Test, GetOnSameThread) {
std::uint32_t initial_value = 1450;
AtomicUint32 atomic_reference_{initial_value};
EXPECT_EQ(initial_value, atomic_reference_.Get());
}
TEST(AtomicUint32Test, SetGetOnSameThread) {
std::uint32_t initial_value_ = 1450;
AtomicUint32 atomic_reference_{initial_value_};
std::uint32_t new_value = 28;
atomic_reference_.Set(new_value);
EXPECT_EQ(new_value, atomic_reference_.Get());
}
TEST(AtomicUint32Test, SetOnNewThread) {
std::uint32_t initial_value_ = 1450;
AtomicUint32 atomic_reference_{initial_value_};
std::uint32_t new_thread_value = 28;
thread::Fiber f([&] { atomic_reference_.Set(new_thread_value); });
f.Join();
EXPECT_EQ(new_thread_value, atomic_reference_.Get());
}
TEST(AtomicUint32Test, GetOnNewThread) {
std::uint32_t initial_value_ = 1450;
AtomicUint32 atomic_reference_{initial_value_};
std::uint32_t new_value = 28;
atomic_reference_.Set(new_value);
thread::Fiber f([&] { EXPECT_EQ(new_value, atomic_reference_.Get()); });
f.Join();
}
} // namespace
} // namespace ios
} // namespace nearby
} // namespace location
@@ -0,0 +1,37 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "internal/platform/implementation/ios/Source/Platform/condition_variable.h"
#include "internal/platform/implementation/ios/Source/Platform/mutex.h"
namespace location {
namespace nearby {
namespace ios {
Exception ConditionVariable::Wait() {
condition_variable_.Wait(mutex_);
return {Exception::kSuccess};
}
Exception ConditionVariable::Wait(absl::Duration timeout) {
condition_variable_.WaitWithTimeout(mutex_, timeout);
return {Exception::kSuccess};
}
void ConditionVariable::Notify() { condition_variable_.SignalAll(); }
} // namespace ios
} // namespace nearby
} // namespace location
@@ -0,0 +1,48 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_IMPL_IOS_CONDITION_VARIABLE_H_
#define PLATFORM_IMPL_IOS_CONDITION_VARIABLE_H_
#include "absl/synchronization/mutex.h"
#include "internal/platform/implementation/condition_variable.h"
#include "internal/platform/implementation/ios/Source/Platform/mutex.h"
namespace location {
namespace nearby {
namespace ios {
// Concrete ConditionVariable implementation.
class ConditionVariable : public api::ConditionVariable {
public:
explicit ConditionVariable(ios::Mutex* mutex) : mutex_(&mutex->mutex_) {}
~ConditionVariable() override = default;
ConditionVariable(const ConditionVariable&) = delete;
ConditionVariable& operator=(const ConditionVariable&) = delete;
Exception Wait() override;
Exception Wait(absl::Duration timeout) override;
void Notify() override;
private:
absl::Mutex* mutex_;
absl::CondVar condition_variable_;
};
} // namespace ios
} // namespace nearby
} // namespace location
#endif // PLATFORM_IMPL_IOS_CONDITION_VARIABLE_H_
@@ -0,0 +1,84 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "internal/platform/implementation/ios/Source/Platform/condition_variable.h"
#include "gtest/gtest.h"
#include "absl/time/clock.h"
#include "internal/platform/implementation/ios/Source/Platform/mutex.h"
#include "thread/fiber/fiber.h"
namespace location {
namespace nearby {
namespace ios {
namespace {
TEST(ConditionVariableTest, CanCreate) {
Mutex mutex{};
ConditionVariable cond{&mutex};
}
TEST(ConditionVariableTest, CanWakeupWaiter) {
Mutex mutex{};
ConditionVariable cond{&mutex};
bool done = false;
bool waiting = false;
{
thread::Fiber f([&cond, &mutex, &done, &waiting] {
mutex.Lock();
waiting = true;
cond.Wait();
waiting = false;
done = true;
mutex.Unlock();
});
while (true) {
{
mutex.Lock();
if (waiting) {
mutex.Unlock();
break;
}
mutex.Unlock();
}
absl::SleepFor(absl::Milliseconds(100));
}
{
mutex.Lock();
cond.Notify();
EXPECT_FALSE(done);
mutex.Unlock();
}
f.Join();
}
EXPECT_TRUE(done);
}
TEST(ConditionVariableTest, WaitTerminatesOnTimeoutWithoutNotify) {
Mutex mutex{};
ConditionVariable cond{&mutex};
mutex.Lock();
const absl::Duration kWaitTime = absl::Milliseconds(100);
absl::Time start = absl::Now();
cond.Wait(kWaitTime);
absl::Duration duration = absl::Now() - start;
EXPECT_GE(duration, kWaitTime);
mutex.Unlock();
}
} // namespace
} // namespace ios
} // namespace nearby
} // namespace location
@@ -0,0 +1,40 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "internal/platform/implementation/ios/Source/Platform/count_down_latch.h"
namespace location {
namespace nearby {
namespace ios {
Exception CountDownLatch::Await() {
absl::MutexLock lock(&mutex_, absl::Condition(IsZeroOrNegative, &count_));
return {Exception::kSuccess};
}
ExceptionOr<bool> CountDownLatch::Await(absl::Duration timeout) {
bool condition = mutex_.LockWhenWithTimeout(
absl::Condition(IsZeroOrNegative, &count_), timeout);
mutex_.Unlock();
return ExceptionOr<bool>(condition);
}
void CountDownLatch::CountDown() {
absl::MutexLock lock(&mutex_);
count_--;
}
} // namespace ios
} // namespace nearby
} // namespace location
@@ -0,0 +1,49 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_IMPL_IOS_COUNT_DOWN_LATCH_H_
#define PLATFORM_IMPL_IOS_COUNT_DOWN_LATCH_H_
#include "absl/synchronization/mutex.h"
#include "internal/platform/implementation/count_down_latch.h"
namespace location {
namespace nearby {
namespace ios {
// Concrete CountDownLatch implementation.
class CountDownLatch : public api::CountDownLatch {
public:
explicit CountDownLatch(int count) : count_(count) {}
~CountDownLatch() override = default;
CountDownLatch(const CountDownLatch&) = delete;
CountDownLatch& operator=(const CountDownLatch&) = delete;
Exception Await() override;
ExceptionOr<bool> Await(absl::Duration timeout) override;
void CountDown() override;
private:
static bool IsZeroOrNegative(int* count) { return 0 >= *count; }
absl::Mutex mutex_;
int count_ ABSL_GUARDED_BY(mutex_);
};
} // namespace ios
} // namespace nearby
} // namespace location
#endif // PLATFORM_IMPL_IOS_COUNT_DOWN_LATCH_H_
@@ -0,0 +1,83 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "internal/platform/implementation/ios/Source/Platform/count_down_latch.h"
#include "gtest/gtest.h"
#include "thread/fiber/fiber.h"
namespace location {
namespace nearby {
namespace ios {
namespace {
TEST(CountDownLatchTest, LatchAwaitCanWait) {
CountDownLatch latch(1);
std::atomic_bool done = false;
thread::Fiber f([&done, &latch] {
done = true;
latch.CountDown();
});
f.Join();
latch.Await();
EXPECT_TRUE(done);
}
TEST(CountDownLatchTest, LatchExtraCountDownIgnored) {
CountDownLatch latch(1);
std::atomic_bool done = false;
thread::Fiber f([&done, &latch] {
done = true;
latch.CountDown();
latch.CountDown();
latch.CountDown();
});
f.Join();
latch.Await();
EXPECT_TRUE(done);
}
TEST(CountDownLatchTest, LatchAwaitWithTimeoutCanExpire) {
CountDownLatch latch(1);
auto response = latch.Await(absl::Milliseconds(100));
EXPECT_TRUE(response.ok());
EXPECT_FALSE(response.result());
}
TEST(CountDownLatchTest, InitialCountZero_AwaitDoesNotBlock) {
CountDownLatch latch(0);
auto response = latch.Await();
EXPECT_TRUE(response.Ok());
}
TEST(CountDownLatchTest, InitialCountNegative_AwaitDoesNotBlock) {
CountDownLatch latch(-1);
auto response = latch.Await();
EXPECT_TRUE(response.Ok());
}
} // namespace
} // namespace ios
} // namespace nearby
} // namespace location
@@ -0,0 +1,39 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "internal/platform/implementation/crypto.h"
#import "absl/strings/string_view.h"
#import "internal/platform/implementation/ios/Source/Platform/utils.h"
#import "internal/platform/implementation/ios/Source/Shared/GNCUtils.h"
namespace location {
namespace nearby {
void Crypto::Init() {}
ByteArray Crypto::Md5(absl::string_view input) {
if (input.empty()) return ByteArray();
return ByteArrayFromNSData(GNCMd5String(ObjCStringFromCppString(input)));
}
ByteArray Crypto::Sha256(absl::string_view input) {
if (input.empty()) return ByteArray();
return ByteArrayFromNSData(GNCSha256String(ObjCStringFromCppString(input)));
}
} // namespace nearby
} // namespace location
@@ -0,0 +1,48 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_IMPL_IOS_INPUT_FILE_H_
#define PLATFORM_IMPL_IOS_INPUT_FILE_H_
#import <Foundation/Foundation.h>
#include "internal/platform/implementation/input_file.h"
namespace location {
namespace nearby {
namespace ios {
/** This InputFile subclass takes input from an NSURL. */
class InputFile : public api::InputFile {
public:
explicit InputFile(NSURL *nsURL);
~InputFile() override = default;
InputFile(InputFile &&) = default;
InputFile &operator=(InputFile &&) = default;
ExceptionOr<ByteArray> Read(std::int64_t size) override;
std::string GetFilePath() const override;
std::int64_t GetTotalSize() const override;
Exception Close() override;
private:
NSURL *nsURL_;
NSInputStream *nsStream_;
};
} // namespace ios
} // namespace nearby
} // namespace location
#endif // PLATFORM_IMPL_IOS_INPUT_FILE_H_
@@ -0,0 +1,69 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import "internal/platform/implementation/ios/Source/Platform/input_file.h"
#include <string>
#import "internal/platform/exception.h"
#import "internal/platform/implementation/ios/Source/Platform/utils.h"
namespace location {
namespace nearby {
namespace ios {
InputFile::InputFile(NSURL *nsURL) : nsURL_(nsURL) {
std::string string = CppStringFromObjCString([nsURL_ absoluteString]);
nsStream_ = [NSInputStream inputStreamWithURL:nsURL_];
[nsStream_ scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[nsStream_ open];
}
ExceptionOr<ByteArray> InputFile::Read(std::int64_t size) {
uint8_t *bytes_read = new uint8_t[size];
NSUInteger numberOfBytesToRead = [[NSNumber numberWithLongLong:size] unsignedIntegerValue];
NSInteger numberOfBytesRead = [nsStream_ read:bytes_read maxLength:numberOfBytesToRead];
if (numberOfBytesRead == 0) {
// Reached end of stream.
return ExceptionOr<ByteArray>();
} else if (numberOfBytesRead < 0) {
// Stream error.
return ExceptionOr<ByteArray>(Exception::kIo);
}
return ExceptionOr<ByteArray>(ByteArrayFromNSData([NSData dataWithBytes:bytes_read
length:numberOfBytesRead]));
}
std::string InputFile::GetFilePath() const {
return CppStringFromObjCString([nsURL_ absoluteString]);
}
std::int64_t InputFile::GetTotalSize() const {
NSNumber *fileSizeValue = nil;
BOOL result = [nsURL_ getResourceValue:&fileSizeValue forKey:NSURLFileSizeKey error:nil];
if (result) {
return fileSizeValue.longValue;
} else {
return 0;
}
}
Exception InputFile::Close() {
[nsStream_ close];
return {Exception::kSuccess};
}
} // namespace ios
} // namespace nearby
} // namespace location
@@ -0,0 +1,47 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_IMPL_IOS_LOG_MESSAGE_H_
#define PLATFORM_IMPL_IOS_LOG_MESSAGE_H_
#include "glog/logging.h"
#include "internal/platform/implementation/log_message.h"
namespace location {
namespace nearby {
namespace ios {
// Concrete LogMessage implementation
class LogMessage : public api::LogMessage {
public:
LogMessage(const char* file, int line, Severity severity);
~LogMessage() override = default;
LogMessage(const LogMessage&) = delete;
LogMessage& operator=(const LogMessage&) = delete;
void Print(const char* format, ...) override;
std::ostream& Stream() override;
private:
google::LogMessage log_streamer_;
api::LogMessage::Severity severity_;
};
} // namespace ios
} // namespace nearby
} // namespace location
#endif // IPHONE_SHARED_NEARBY_CONNECTIONS_SOURCE_PLATFORM_LOG_MESSAGE_H_

Some files were not shown because too many files have changed in this diff Show More