Files
nearby/connections/implementation/mediums/ble/ble_socket.h
T

349 lines
14 KiB
C++

// Copyright 2025 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 CORE_INTERNAL_MEDIUMS_BLE_BLE_SOCKET_H_
#define CORE_INTERNAL_MEDIUMS_BLE_BLE_SOCKET_H_
#include <cstdint>
#include <memory>
#include <utility>
#include "absl/base/thread_annotations.h"
#include "internal/platform/ble.h"
#include "internal/platform/byte_array.h"
#include "internal/platform/exception.h"
#include "internal/platform/input_stream.h"
#include "internal/platform/mutex.h"
#include "internal/platform/output_stream.h"
namespace nearby {
namespace connections {
namespace mediums {
/**
* A decorator for an `InputStream` that filters protocol-specific data
* for BLE connections.
*
* This class wraps a raw `InputStream` from an underlying BLE socket (either
* GATT or L2CAP). Its primary function is to intercept the incoming byte stream
* and filter out the `service_id_hash` before passing the data to the upper
* layers of the Nearby Connections protocol. This ensures that clients of
* `BleSocket` receive only the application payload.
*
* This stream does not own the underlying `source_` stream; it holds a
* reference and depends on the owner of the `source_` to manage its
* lifetime. It is intended for internal use by the `BleSocket` class.
*/
class BleInputStream : public InputStream {
public:
explicit BleInputStream(InputStream& source) : source_(source) {}
ExceptionOr<ByteArray> Read(std::int64_t size) override;
Exception Close() override;
private:
InputStream& source_;
};
/**
* A decorator for an `OutputStream` that prepends protocol-specific
* data for BLE connections.
*
* This class wraps a raw `OutputStream` from an underlying BLE socket (GATT or
* L2CAP). Its main responsibility is to prepend the `service_id_hash` to every
* outgoing data packet before it is written to the physical socket. This is a
* requirement of the Nearby Connections protocol to ensure that the remote
* device can identify the service and demultiplex the connection.
*
* This stream does not own the underlying `source_` stream; it holds a
* reference and relies on the owner of the `source_` to manage its lifetime.
* It is used internally by the `BleSocket` class to handle the low-level
* details of packet formatting.
*/
class BleOutputStream : public OutputStream {
public:
BleOutputStream(OutputStream& source, const ByteArray& service_id_hash)
: source_(source), service_id_hash_(service_id_hash) {}
/**
* Writes data to the stream by first creating a self-delimited packet.
*
* This method packetizes the given `data` before writing it to the underlying
* `source_` output stream. The packetization process is a requirement of the
* Nearby Connections protocol to ensure the remote device can correctly
* parse streamed data.
*
* Internally, it uses `BlePacket::CreateDataPacket` to construct a data
* packet. This involves prepending the data's length and the
* `service_id_hash_` to the actual payload.
*
* Prepending the length allows the receiver to determine message boundaries
* when reading from the stream, which is a common practice for streaming
* protocols. The `service_id_hash_` allows the remote device to identify the
* service and demultiplex the connection.
*
* The resulting serialized `BlePacket` is then written to the `source_`
* stream.
*
* @param data The raw `ByteArray` payload to write to the stream.
* @return `Exception::kSuccess` if the write operation succeeds, or an
* exception code indicating the type of error.
*/
Exception Write(const ByteArray& data) override;
Exception Flush() override;
Exception Close() override;
private:
OutputStream& source_;
const ByteArray service_id_hash_;
int payload_length_ = 0;
};
/**
* A decorator for BLE (GATT) and BLE L2CAP sockets that centralizes
* connection and packet-handling logic for Nearby Connections.
*
* This class wraps an underlying `BleSocket` (for GATT) or `BleL2capSocket`
* to provide a unified interface for data transfer. Its primary responsibility
* is to centralize BLE and L2CAP packet validation within the Nearby
* Connections protocol layer. This includes handling `service_id_hash`
* validation and managing packet prefixing and formatting.
*
* It intercepts the raw input and output streams of the underlying socket using
* custom `BleInputStream` and `BleOutputStream` wrappers to inject
* protocol-specific logic. This includes sending introduction frames, handling
* disconnections, and validating L2CAP packets before they are sent or
* received.
*
* All operations on this socket are serialized through an internal
* `SingleThreadExecutor` to ensure thread safety and correct ordering of
* asynchronous I/O operations.
*
* Instances of this class must be created using one of the static factory
* methods: `CreateWithBleSocket()` or `CreateWithL2capSocket()`.
*/
class BleSocket final {
public:
// Factory methods for creating a BleSocket instance.
static std::unique_ptr<BleSocket> CreateWithBleSocket(
nearby::BleSocket ble_socket, const ByteArray& service_id_hash) {
return std::unique_ptr<BleSocket>(new BleSocket(
service_id_hash,
std::make_unique<BleInputStream>(ble_socket.GetInputStream()),
std::make_unique<BleOutputStream>(ble_socket.GetOutputStream(),
service_id_hash),
std::move(ble_socket)));
}
static std::unique_ptr<BleSocket> CreateWithL2capSocket(
nearby::BleL2capSocket l2cap_socket, const ByteArray& service_id_hash) {
return std::unique_ptr<BleSocket>(new BleSocket(
service_id_hash,
std::make_unique<BleInputStream>(l2cap_socket.GetInputStream()),
std::make_unique<BleOutputStream>(l2cap_socket.GetOutputStream(),
service_id_hash),
std::move(l2cap_socket)));
}
~BleSocket();
InputStream& GetInputStream() ABSL_LOCKS_EXCLUDED(mutex_);
OutputStream& GetOutputStream() ABSL_LOCKS_EXCLUDED(mutex_);
Exception Close() ABSL_LOCKS_EXCLUDED(mutex_);
nearby::BlePeripheral& GetRemotePeripheral() ABSL_LOCKS_EXCLUDED(mutex_);
bool IsValid() const ABSL_LOCKS_EXCLUDED(mutex_);
/**
* Returns the medium used by this socket.
*
* @return The medium used by this socket.
*/
::location::nearby::proto::connections::Medium GetMedium() const
ABSL_LOCKS_EXCLUDED(mutex_);
/**
* Dispatches the next logical packet from the socket by routing it
* based on its type.
*
* This is the primary entry point for reading from the socket and is
* responsible for parsing the underlying byte stream to identify the next
* packet.
*
* The function handles protocol-level demultiplexing by inspecting the
* packet's service ID hash to differentiate control packets from data
* packets.
*
* For data packets, this function reads and returns only the
* application-level payload, not the full BLE packet. For control packets, it
* fully consumes and processes the packet by delegating to an internal
* handler, and the returned `ByteArray` may be empty as some control packets
* carry no payload.
*
* @return An `ExceptionOr` containing the `ByteArray` payload of a data
* packet on success. The `ByteArray` may be empty for certain control
* packets that have no payload. Returns an `Exception` if a protocol
* error occurs or the read operation fails.
*/
ExceptionOr<ByteArray> DispatchPacket() ABSL_LOCKS_EXCLUDED(mutex_);
/**
* Reads the length of the next data payload from the socket.
*
* This method is used to read the length of the next data payload from the
* socket. It is intended to be called immediately after `DispatchPacket()`
* to retrieve the length of the payload that was just read.
*
* @return An `ExceptionOr` containing the length of the next data payload on
* success. Returns an `Exception` if a protocol error occurs or the read
* operation fails.
*/
ExceptionOr<std::int32_t> ReadPayloadLength() ABSL_LOCKS_EXCLUDED(mutex_);
/**
* Sends the length of a data payload to the remote endpoint.
*
* This method prepares the socket for an upcoming data payload by sending its
* length. It handles the necessary protocol formatting, including prepending
* the `service_id_hash` to the length information.
*
* This method should be called immediately before sending the corresponding
* payload.
*
* @param payload_length The length, in bytes, of the upcoming payload.
* @return An `Exception` object indicating the status of the write
* operation. `{Exception::kSuccess}` on success.
*/
Exception WritePayloadLength(int payload_length) ABSL_LOCKS_EXCLUDED(mutex_);
/**
* Sends the initial introduction packet to the remote endpoint.
*
* This function is called immediately after a new BLE connection is
* established. Its purpose is to initiate the Nearby Connections protocol
* handshake by sending a control packet containing the `service_id_hash`.
* This allows the remote device to validate that the connection is intended
* for the correct service.
*
* This operation is part of the pre-connection setup and is typically the
* first packet sent over a new socket.
*
* @return An `Exception` object indicating the status of the write
* operation. `{Exception::kSuccess}` on success.
*/
Exception SendIntroduction() ABSL_LOCKS_EXCLUDED(mutex_);
/**
* Sends a disconnection packet to the remote endpoint.
*
* This function is used to gracefully terminate the connection at the
* protocol level. It sends an explicit control packet to inform the remote
* device that the connection is being closed intentionally.
*
* This is typically called as part of the teardown process for a connection,
* ensuring the remote endpoint is aware of the state change.
*
* @return An `Exception` object indicating the status of the write
* operation. `{Exception::kSuccess}` on success.
*/
Exception SendDisconnection() ABSL_LOCKS_EXCLUDED(mutex_);
/**
* Sends a packet acknowledgement to the remote endpoint.
*
* This function is used to confirm the receipt of a data packet at the
* protocol level. After successfully receiving a data packet, this method
* should be called to send a control packet back to the sender, which
* includes the size of the packet that was received.
*
* This acknowledgement mechanism allows the sender to verify that its data
* was successfully delivered.
*
* @param received_size The size, in bytes, of the data packet that was just
* received and is being acknowledged.
* @return An `Exception` object indicating the status of the write
* operation. `{Exception::kSuccess}` on success.
*/
Exception SendPacketAcknowledgement(int received_size)
ABSL_LOCKS_EXCLUDED(mutex_);
/**
* Processes the server-side L2CAP connection validation handshake.
*
* Its primary role is to manage the handshake required to
* establish a dedicated data channel after an initial L2CAP connection has
* been made.
*
* The function waits to receive a `Command::kRequestDataConnection` packet
* from the remote device. Upon receiving this request, it proceeds to
* validate the connection and, if successful, responds by sending a
* `Command::kResponseDataConnectionReady` packet to the initiator to signal
* that the data channel is established and ready for use.
*
* @return An `Exception` object indicating the status of the validation
* process. `{Exception::kSuccess}` is returned if the handshake completes
* successfully.
*/
Exception ProcessIncomingL2capPacketValidation() ABSL_LOCKS_EXCLUDED(mutex_);
/**
* Processes the client-side L2CAP connection validation handshake.
*
* Its primary role is to initiate the handshake required to establish a
* dedicated data channel after an initial L2CAP connection has been made.
*
* The function begins by sending a `Command::kRequestDataConnection` packet
* to the remote device to request the creation of a data channel. It then
* waits to receive a `Command::kResponseDataConnectionReady` packet from the
* acceptor, which signals that the data channel has been successfully
* established and is ready for use.
*
* @return An `Exception` object indicating the status of the validation
* process. `{Exception::kSuccess}` is returned if the handshake completes
* successfully.
*/
Exception ProcessOutgoingL2capPacketValidation() ABSL_LOCKS_EXCLUDED(mutex_);
private:
BleSocket(const ByteArray& service_id_hash,
std::unique_ptr<BleInputStream> ble_input_stream,
std::unique_ptr<BleOutputStream> ble_output_stream,
nearby::BleSocket ble_socket);
BleSocket(const ByteArray& service_id_hash,
std::unique_ptr<BleInputStream> ble_input_stream,
std::unique_ptr<BleOutputStream> ble_output_stream,
nearby::BleL2capSocket l2cap_socket);
::location::nearby::proto::connections::Medium GetMediumLocked() const
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
Exception CloseLocked() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
mutable Mutex mutex_;
const ByteArray service_id_hash_;
std::unique_ptr<mediums::BleInputStream> ble_input_stream_
ABSL_GUARDED_BY(mutex_) = nullptr;
std::unique_ptr<mediums::BleOutputStream> ble_output_stream_
ABSL_GUARDED_BY(mutex_) = nullptr;
nearby::BleSocket ble_socket_ ABSL_GUARDED_BY(mutex_) = nearby::BleSocket();
nearby::BleL2capSocket l2cap_socket_ ABSL_GUARDED_BY(mutex_) =
nearby::BleL2capSocket();
};
} // namespace mediums
} // namespace connections
} // namespace nearby
#endif // CORE_INTERNAL_MEDIUMS_BLE_BLE_SOCKET_H_