diff --git a/sharing/linux/README.md b/sharing/linux/README.md index 9911653f..932b8f3d 100644 --- a/sharing/linux/README.md +++ b/sharing/linux/README.md @@ -1,373 +1,105 @@ -# Nearby Sharing Linux Implementation +# Nearby Sharing for Linux -This directory contains the Linux-specific implementation of Nearby Sharing and a sample application demonstrating its usage. +This directory contains: +- A Linux-facing Nearby Sharing library (`NearbySharingApi`) +- A sample CLI app (`nearby_sharing_app`) +- A Qt/QML tray sample app (`qml_tray_app`) -## ⚠️ Important Compatibility Notice +## Scope and Intended Use -**This is a simplified implementation for Linux-to-Linux transfers only.** +This project is primarily intended to be used as a reusable library/API. -- ✅ **Works**: Linux ↔ Linux device transfers -- ❌ **Does NOT work**: Linux ↔ Android/ChromeOS (authentication failure) +The sample applications are still supported and will continue to be supported because they are used in real day-to-day device sharing workflows. -**Reason**: The full Nearby Sharing protocol requires certificate-based authentication, introduction frame exchange, and protocol frame handling which are not implemented in this simplified version. +## Current Status -**For Android/ChromeOS compatibility**, you need the full implementation in `sharing/nearby_sharing_service_impl.{h,cc}` which requires additional platform support. See [ANDROID_COMPATIBILITY.md](ANDROID_COMPATIBILITY.md) for details. +The current implementation works with the reverse-engineered certificate manager currently used in this project. -## Overview +Compatibility can still break if Google changes certificate manager behavior/protocol details. That component is closed source, so upstream changes can be difficult to inspect and adapt to quickly. -Nearby Sharing is a feature that allows users to share files, text, and other content between nearby devices using Bluetooth Low Energy (BLE) and Wi-Fi Direct. This implementation provides a simplified Linux interface built on top of the Nearby Connections API. +## Test Coverage and Session Notes -## Components +- Verified: single-file sharing flow. +- Not fully verified: multiple transfers in one app lifetime. +- Current practical testing pattern: restart the application before each new transfer. -### NearbySharingServiceLinux +For this README, a "session" means one process lifetime (app start to app exit). -The main service class that provides nearby sharing functionality: +After one transfer, some endpoints may close and internal state can reset/change. Multi-transfer handling in one live session is still under investigation. -- **Discovery & Advertising**: Find nearby devices and advertise your device's availability -- **File Transfer**: Send and receive files -- **Text Transfer**: Send and receive text messages -- **Connection Management**: Handle connection lifecycle (accept, reject, cancel) +## Known Issues -### NearbySharingApi +- Linux hotspot startup can be slow. +- Connecting to a hotspot started on another device can be slow on Linux. +- Android-initiated connection formation can be very slow. -For external/Linux app consumers (for example Qt/CMake apps), use -`nearby::sharing::linux::NearbySharingApi` from: +The Android/Linux connection latency issue still needs deeper investigation. One possible cause is connection/negotiation behavior that Linux does not currently handle well. -- `sharing/linux/nearby_sharing_api.h` -- `libnearby_sharing_api_shared.so` +## Wi-Fi Direct Status -Install artifacts with: +Wi-Fi Direct is theoretically possible but not implemented yet. + +Reason: NetworkManager does not natively support creating Wi-Fi Direct Group Owners in the way this project needs. + +## Installation + +### 1. Install the shared library + +From the repository root: ```bash ./sharing/linux/install_nearby_sharing_service.sh ``` -### Key Concepts +This installs: +- `libnearby_sharing_api_shared.so` +- `sharing/linux/nearby_sharing_api.h` -#### 1. Send Surface -Represents the sending side of a transfer: -- **Foreground**: Actively scans for nearby devices -- **Background**: Only listens for transfer updates without scanning +### 2. Build and run the CLI sample app (optional) -#### 2. Receive Surface -Represents the receiving side of a transfer: -- **Foreground**: Advertises to everyone, visible to all nearby devices -- **Background**: Advertises only to contacts (limited visibility) - -#### 3. Callbacks - -**TransferUpdateCallback**: Receives updates about ongoing transfers -- Status changes (connecting, in progress, complete, failed) -- Progress updates -- Transfer metadata (speed, bytes transferred, etc.) - -**ShareTargetDiscoveredCallback**: Receives notifications about discovered devices -- Device discovered -- Device lost (out of range) -- Device updated - -#### 4. Attachments - -**FileAttachment**: Represents a file to be transferred -- Requires a file path -- Automatically determines MIME type and size - -**TextAttachment**: Represents text content to be transferred -- Supports plain text, URLs, addresses, and phone numbers -- Includes optional title and MIME type - -## Sample Application - -The `nearby_sharing_app.cc` demonstrates how to use the service: - -### Building +From the repository root: ```bash -# Build the sample application bazel build //sharing/linux:nearby_sharing_app +./bazel-bin/sharing/linux/nearby_sharing_app +# Optional custom device name +./bazel-bin/sharing/linux/nearby_sharing_app "MyDeviceName" ``` -### Running +### 3. Build and run the tray sample app (optional) + +From `sharing/linux/qml_tray_app`: ```bash -# Run with default device name -./bazel-bin/sharing/linux/nearby_sharing_app - -# Run with custom device name -./bazel-bin/sharing/linux/nearby_sharing_app "MyCustomName" +cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DNEARBY_PREFIX=/usr/local +cmake --build build -j +./build/nearby_qml_file_tray_app ``` -### Features +### 4. Install launcher entry (`.desktop`) for the tray app -1. **Start as Receiver**: Advertise your device to receive files -2. **Start as Sender**: Discover nearby devices to send files -3. **List Discovered Devices**: View all devices found during scanning -4. **Send File**: Transfer a file to a discovered device -5. **Send Text**: Send text content to a discovered device -6. **Accept/Reject**: Handle incoming transfer requests -7. **Cancel Transfer**: Cancel an ongoing transfer -8. **Status Info**: View Bluetooth and service status +From `sharing/linux/qml_tray_app`: -## Usage Examples - -### Example 1: Send a File - -**Device A (Sender)**: -```cpp -NearbySharingApp app("Sender-Device"); - -// Start scanning for devices -app.StartAsSender(); - -// Wait for discovery... -std::this_thread::sleep_for(std::chrono::seconds(3)); - -// List discovered devices -app.ListDiscoveredDevices(); - -// Send file to target with ID 1 -app.SendFile(1, "/path/to/file.txt"); +```bash +mkdir -p "$HOME/.local/share/applications" +install -m 0644 nearby-file-share.desktop "$HOME/.local/share/applications/nearby-file-share.desktop" +sed -i "s|^Exec=.*|Exec=$(pwd)/build/nearby_qml_file_tray_app|" "$HOME/.local/share/applications/nearby-file-share.desktop" +sed -i "s|^Icon=.*|Icon=$(pwd)/tray_icon.png|" "$HOME/.local/share/applications/nearby-file-share.desktop" +update-desktop-database "$HOME/.local/share/applications" 2>/dev/null || true ``` -**Device B (Receiver)**: -```cpp -NearbySharingApp app("Receiver-Device"); +After this, search for `Nearby File Share` in your desktop launcher. -// Start advertising -app.StartAsReceiver(); +## Documentation -// When connection is initiated (via callback), accept it -// This happens automatically when you see OnTransferUpdate with -// Status::kAwaitingLocalConfirmation -app.AcceptIncomingShare(target_id); -``` +Technical deep dives are being moved from README content to the wiki. -### Example 2: Send Text +- Wiki: https://github.com/kidfromjupiter/nearby/wiki -```cpp -NearbySharingApp app("Text-Sender"); +This README stays focused on status, installation, and known limitations. -// Start as sender -app.StartAsSender(); +## Demo Assets (Planned) -// Wait for device discovery -std::this_thread::sleep_for(std::chrono::seconds(2)); +- Video demo of end-to-end sharing flow. +- GIF showing the sharing process. -// Send text to discovered device -app.SendText(1, "Hello from Nearby Sharing!"); -``` - -### Example 3: Custom Callbacks - -```cpp -class CustomTransferCallback : public TransferUpdateCallback { - public: - void OnTransferUpdate(const ShareTarget& share_target, - const AttachmentContainer& attachment_container, - const TransferMetadata& transfer_metadata) override { - switch (transfer_metadata.status()) { - case TransferMetadata::Status::kAwaitingLocalConfirmation: - // Auto-accept incoming transfers - service_->Accept(share_target.id, [](auto status) { - std::cout << "Auto-accepted" << std::endl; - }); - break; - - case TransferMetadata::Status::kComplete: - std::cout << "Transfer completed!" << std::endl; - // Handle completed files from attachment_container - break; - - case TransferMetadata::Status::kFailed: - std::cout << "Transfer failed!" << std::endl; - break; - - default: - break; - } - } -}; -``` - -## Architecture - -### Service Initialization - -```cpp -NearbySharingServiceLinux service("DeviceName"); -``` - -The service initializes: -1. **Device Info**: Gets OS device name and type -2. **Bluetooth Adapter**: Checks BT availability -3. **Connections Core**: Sets up the Nearby Connections layer -4. **Service Controller Router**: Manages connection routing - -### Discovery Flow - -1. **Register Send Surface** (Foreground) -2. Service starts **scanning** for nearby devices -3. When device found: **OnShareTargetDiscovered** callback -4. Advertisement is **parsed** to extract device info -5. **ShareTarget** created with device details -6. User can **select target** and initiate transfer - -### Advertising Flow - -1. **Register Receive Surface** (Foreground/Background) -2. Service **builds advertisement** with device info -3. Service starts **advertising** via Bluetooth/Wi-Fi -4. When connection requested: **OnTransferUpdate** callback -5. User can **accept/reject** the incoming transfer - -### Transfer Flow - -#### Sending: -1. **SendAttachments()** with target ID and attachments -2. Service creates **connection request** -3. **Connection initiated** → Status: kConnecting -4. Connection **accepted** → Sends file/text payloads -5. **Payload transfer** → Status: kInProgress -6. **Transfer complete** → Status: kComplete - -#### Receiving: -1. **Incoming connection** → Status: kAwaitingLocalConfirmation -2. **Accept()** called → Connection accepted -3. **Receive payloads** → Status: kInProgress -4. **Payloads saved** to local storage -5. **Transfer complete** → Status: kComplete - -## Implementation Details - -### Advertisement Format - -The service uses a custom advertisement format: -- **Header byte**: Version, visibility, device type -- **Salt**: 2 random bytes -- **Metadata key**: 14 bytes (for encryption) -- **TLV fields**: Vendor ID, QR code, etc. -- **Device name** (optional): UTF-8 device name - -### Connection Strategy - -Uses `P2P_POINT_TO_POINT` strategy: -- Direct peer-to-peer connections -- Supports Bluetooth and Wi-Fi Direct -- Automatic medium selection based on availability - -### Medium Selection - -The service attempts to use available media: -1. **Bluetooth LE**: For discovery and initial connection -2. **Wi-Fi Direct**: For high-speed file transfer -3. **Wi-Fi LAN**: If devices on same network - -### Payload Types - -1. **BYTES**: For text content and metadata -2. **FILE**: For file transfers -3. **STREAM**: For real-time data - -## API Reference - -### Core Methods - -#### RegisterSendSurface -```cpp -void RegisterSendSurface( - TransferUpdateCallback* transfer_callback, - ShareTargetDiscoveredCallback* discovery_callback, - SendSurfaceState state, - Advertisement::BlockedVendorId blocked_vendor_id, - bool disable_wifi_hotspot, - std::function status_codes_callback); -``` - -#### RegisterReceiveSurface -```cpp -void RegisterReceiveSurface( - TransferUpdateCallback* transfer_callback, - ReceiveSurfaceState state, - Advertisement::BlockedVendorId vendor_id, - std::function status_codes_callback); -``` - -#### SendAttachments -```cpp -void SendAttachments( - int64_t share_target_id, - std::unique_ptr attachment_container, - std::function status_codes_callback); -``` - -#### Accept/Reject/Cancel -```cpp -void Accept(int64_t share_target_id, - std::function status_codes_callback); - -void Reject(int64_t share_target_id, - std::function status_codes_callback); - -void Cancel(int64_t share_target_id, - std::function status_codes_callback); -``` - -### Status Codes - -- **kOk**: Operation successful -- **kError**: General error -- **kOutOfOrderApiCall**: API called in wrong order -- **kTransferAlreadyInProgress**: Transfer already active -- **kNoAvailableConnectionMedium**: No BT/Wi-Fi available -- **kInvalidArgument**: Invalid parameter provided - -## Limitations - -Current implementation limitations: -- No settings persistence -- No contact management -- No certificate management -- No account integration -- Limited visibility control -- No Wi-Fi LAN detection -- No extended advertising support - -## Future Enhancements - -Potential improvements: -1. Add settings persistence (device name, visibility) -2. Implement contact management -3. Add certificate-based authentication -4. Support visibility time limits -5. Add Wi-Fi LAN connectivity detection -6. Implement file path updates during transfer -7. Add QR code generation for pairing -8. Support for extended advertising - -## Troubleshooting - -### Bluetooth Issues -```cpp -if (!service.IsBluetoothPresent()) { - std::cout << "Bluetooth adapter not found" << std::endl; -} -if (!service.IsBluetoothPowered()) { - std::cout << "Bluetooth is disabled" << std::endl; -} -``` - -### Discovery Not Working -- Ensure Bluetooth is enabled -- Check that sender is in foreground mode -- Verify receiver is advertising -- Check for Bluetooth permissions - -### Transfer Failures -- Verify file paths are accessible -- Check available disk space -- Ensure stable Bluetooth connection -- Monitor transfer callbacks for errors - -## License - -Copyright 2025 Google LLC. Licensed under Apache 2.0. diff --git a/sharing/linux/nearby_sharing_api.cc b/sharing/linux/nearby_sharing_api.cc index c0829316..4386a3b4 100644 --- a/sharing/linux/nearby_sharing_api.cc +++ b/sharing/linux/nearby_sharing_api.cc @@ -1,10 +1,13 @@ #include "sharing/linux/nearby_sharing_api.h" +#include #include +#include #include #include "connections/implementation/flags/nearby_connections_feature_flags.h" #include "internal/base/file_path.h" +#include "internal/base/files.h" #include "internal/flags/nearby_flags.h" #include "sharing/attachment_container.h" #include "sharing/file_attachment.h" @@ -307,9 +310,21 @@ void NearbySharingApi::SendFile(int64_t share_target_id, return; } + FilePath path(file_path); + std::optional file_size = nearby::Files::GetFileSize(path); + if (!file_size.has_value() || *file_size == 0 || + *file_size > + static_cast(std::numeric_limits::max())) { + if (callback) { + callback(StatusCode::kInvalidArgument); + } + return; + } + nearby::sharing::AttachmentContainer::Builder builder; - builder.AddFileAttachment( - nearby::sharing::FileAttachment(FilePath(file_path))); + nearby::sharing::FileAttachment attachment(path); + attachment.set_size(static_cast(*file_size)); + builder.AddFileAttachment(std::move(attachment)); std::unique_ptr attachments = builder.Build(); if (!attachments || !attachments->HasAttachments()) { diff --git a/sharing/linux/nearby_sharing_service_linux.cc b/sharing/linux/nearby_sharing_service_linux.cc index f3b0a9d9..0dc32986 100644 --- a/sharing/linux/nearby_sharing_service_linux.cc +++ b/sharing/linux/nearby_sharing_service_linux.cc @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -34,6 +35,7 @@ #include "connections/payload.h" #include "connections/status.h" #include "connections/strategy.h" +#include "internal/base/files.h" #include "internal/crypto_cros/ec_private_key.h" #include "internal/platform/byte_array.h" #include "internal/platform/file.h" @@ -192,10 +194,28 @@ bool PrepareOutgoingIntroductionAndPayloads(auto& transfer_state, transfer_state.expected_attachment_payload_count = 0; introduction->set_start_transfer(true); - const AttachmentContainer& attachments = transfer_state.attachments; - for (const auto& file : attachments.GetFileAttachments()) { + AttachmentContainer& attachments = transfer_state.attachments; + for (int i = 0; i < attachments.GetFileAttachments().size(); ++i) { + FileAttachment& file = attachments.GetMutableFileAttachment(i); if (!file.file_path().has_value()) { - continue; + LOG(WARNING) << "Outgoing file attachment missing path: " + << std::string(file.file_name()); + return false; + } + std::optional file_size = Files::GetFileSize(*file.file_path()); + if (file_size.has_value()) { + if (*file_size > + static_cast(std::numeric_limits::max())) { + LOG(WARNING) << "Outgoing file attachment size overflow: " + << std::string(file.file_name()); + return false; + } + file.set_size(static_cast(*file_size)); + } + if (file.size() <= 0) { + LOG(WARNING) << "Outgoing file attachment has invalid size: " + << std::string(file.file_name()) << " size=" << file.size(); + return false; } int64_t payload_id = connections::Payload::GenerateId(); auto* metadata = introduction->add_file_metadata(); @@ -1349,11 +1369,41 @@ connections::PayloadListener NearbySharingServiceLinux::MakePayloadListener( introduction_frame.set_version(Frame::V1); V1Frame* out_v1 = introduction_frame.mutable_v1(); out_v1->set_type(V1Frame::INTRODUCTION); - if (PrepareOutgoingIntroductionAndPayloads( - transfer_state, out_v1->mutable_introduction()) && - SendFramePayload(core_.get(), std::string(endpoint_id), + if (!PrepareOutgoingIntroductionAndPayloads( + transfer_state, out_v1->mutable_introduction())) { + LOG(ERROR) << "Failed to prepare outgoing introduction frame."; + TransferMetadata metadata = + TransferMetadataBuilder() + .set_status(TransferMetadata::Status::kFailed) + .set_progress(0) + .build(); + NotifyTransferUpdate(*share_target, transfer_state, metadata); + core_->DisconnectFromEndpoint(std::string(endpoint_id), + [](connections::Status) {}); + active_transfers_.erase(transfer_it); + if (active_transfers_.empty()) { + is_transferring_ = false; + } + return; + } + if (SendFramePayload(core_.get(), std::string(endpoint_id), transfer_state, introduction_frame)) { transfer_state.introduction_sent = true; + } else { + LOG(ERROR) << "Failed to send outgoing introduction frame."; + TransferMetadata metadata = + TransferMetadataBuilder() + .set_status(TransferMetadata::Status::kFailed) + .set_progress(0) + .build(); + NotifyTransferUpdate(*share_target, transfer_state, metadata); + core_->DisconnectFromEndpoint(std::string(endpoint_id), + [](connections::Status) {}); + active_transfers_.erase(transfer_it); + if (active_transfers_.empty()) { + is_transferring_ = false; + } + return; } }