From 980157f523abc95b02adb1139fcd73c6fbfe7f8c Mon Sep 17 00:00:00 2001 From: Lasan Mahaliyana Date: Sat, 20 Jun 2026 14:48:34 +0530 Subject: [PATCH] finished nearby sharing daemon --- sharing/linux/BUILD | 1 + sharing/linux/daemon/BUILD | 51 +++ sharing/linux/daemon/daemon_service.cc | 453 ++++++++++++++++++++ sharing/linux/daemon/daemon_service.h | 172 ++++++++ sharing/linux/daemon/daemon_service_test.cc | 306 +++++++++++++ sharing/linux/daemon/ipc_server.cc | 115 +++-- sharing/linux/daemon/ipc_server.h | 9 +- sharing/linux/daemon/ipc_server_test.cc | 36 +- sharing/linux/daemon/main.cc | 203 ++++++--- 9 files changed, 1254 insertions(+), 92 deletions(-) create mode 100644 sharing/linux/daemon/daemon_service.cc create mode 100644 sharing/linux/daemon/daemon_service.h create mode 100644 sharing/linux/daemon/daemon_service_test.cc diff --git a/sharing/linux/BUILD b/sharing/linux/BUILD index 3f2492b1..6942563b 100644 --- a/sharing/linux/BUILD +++ b/sharing/linux/BUILD @@ -26,6 +26,7 @@ refresh_compile_commands( targets = { ":fast_init": "", ":nearby_connections_manager": "", + ":nearby_sharing_cli": "", }, # No need to add flags already in .bazelrc. They're automatically picked up. # If you don't need flags, a list of targets is also okay, as is a single target string. diff --git a/sharing/linux/daemon/BUILD b/sharing/linux/daemon/BUILD index 5e9f98c7..bc32c26b 100644 --- a/sharing/linux/daemon/BUILD +++ b/sharing/linux/daemon/BUILD @@ -1,6 +1,55 @@ load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") load("@rules_cc//cc:cc_test.bzl", "cc_test") +cc_binary( + name = "nearby_sharing_daemon", + srcs = ["main.cc"], + deps = [ + ":daemon_service", + ":ipc_server", + "//connections/implementation/flags:connections_flags", + "//internal/flags:nearby_flags", + "//sharing:nearby_sharing_service", + "//sharing/flags/generated:generated_flags", + "//sharing/linux:linux_sharing_platform", + "//sharing/proto:enums_cc_proto", + "@com_google_absl//absl/time", + "@nlohmann_json//:json", + ], +) + +cc_library( + name = "daemon_service", + srcs = ["daemon_service.cc"], + hdrs = ["daemon_service.h"], + deps = [ + "//internal/base:file_path", + "//sharing:attachments", + "//sharing:nearby_sharing_service", + "//sharing:transfer_metadata", + "//sharing:types", + "@com_google_absl//absl/synchronization", + "@com_google_absl//absl/time", + "@nlohmann_json//:json", + ], +) + +cc_test( + name = "daemon_service_test", + srcs = ["daemon_service_test.cc"], + deps = [ + ":daemon_service", + "//internal/platform/implementation/linux:linux", + "//sharing:attachments", + "//sharing:nearby_sharing_service", + "//sharing:transfer_metadata", + "//sharing:types", + "@com_google_googletest//:gtest_main", + "@nlohmann_json//:json", + ], +) + cc_test( name = "ipc_server_test", srcs = ["ipc_server_test.cc"], @@ -8,6 +57,7 @@ cc_test( ":ipc_server", "@com_google_googletest//:gtest_main", "@com_google_absl//absl/synchronization", + "@nlohmann_json//:json", ], ) @@ -17,5 +67,6 @@ cc_library( srcs = ["ipc_server.cc"], deps = [ "@com_google_absl//absl/synchronization", + "@nlohmann_json//:json", ], ) diff --git a/sharing/linux/daemon/daemon_service.cc b/sharing/linux/daemon/daemon_service.cc new file mode 100644 index 00000000..58b6433c --- /dev/null +++ b/sharing/linux/daemon/daemon_service.cc @@ -0,0 +1,453 @@ +#include "sharing/linux/daemon/daemon_service.h" + +#include +#include +#include +#include + +#include "absl/time/time.h" +#include "internal/base/file_path.h" +#include "sharing/advertisement.h" +#include "sharing/file_attachment.h" + +namespace nearby::sharing::linux { +namespace { + +nlohmann::json CommandResult(std::string command, bool ok, + std::string message) { + return nlohmann::json{{"event", "command_result"}, + {"command", std::move(command)}, + {"ok", ok}, + {"message", std::move(message)}}; +} + +std::string StatusCodeToString(NearbySharingService::StatusCodes status) { + return NearbySharingService::StatusCodeToString(status); +} + +template +NearbySharingService::StatusCodes WaitForStatus(Invoker invoker) { + std::mutex mutex; + std::condition_variable cv; + std::optional status; + invoker([&](NearbySharingService::StatusCodes callback_status) { + { + std::lock_guard lock(mutex); + status = callback_status; + } + cv.notify_one(); + }); + + std::unique_lock lock(mutex); + cv.wait(lock, [&] { return status.has_value(); }); + return *status; +} + +std::unique_ptr CreateFileAttachments( + const std::string& file_path) { + AttachmentContainer::Builder builder; + builder.AddFileAttachment(FileAttachment(FilePath(file_path))); + return builder.Build(); +} + +} // namespace + +nlohmann::json ShareTargetToJson(const ShareTarget& share_target) { + return nlohmann::json{ + {"id", share_target.id}, + {"device_name", share_target.device_name}, + {"type", static_cast(share_target.type)}, + {"is_incoming", share_target.is_incoming}, + {"is_known", share_target.is_known}, + {"device_id", share_target.device_id}, + {"for_self_share", share_target.for_self_share}, + {"vendor_id", share_target.vendor_id}, + {"receive_disabled", share_target.receive_disabled}, + }; +} + +nlohmann::json TransferMetadataToJson( + const TransferMetadata& transfer_metadata, + const AttachmentContainer& attachment_container) { + nlohmann::json result{ + {"status", TransferMetadata::StatusToString(transfer_metadata.status())}, + {"progress", transfer_metadata.progress()}, + {"transferred_bytes", transfer_metadata.transferred_bytes()}, + {"total_bytes", attachment_container.GetTotalAttachmentsSize()}, + {"transfer_speed", transfer_metadata.transfer_speed()}, + {"estimated_time_remaining", + transfer_metadata.estimated_time_remaining()}, + {"total_attachments_count", transfer_metadata.total_attachments_count()}, + {"transferred_attachments_count", + transfer_metadata.transferred_attachments_count()}, + {"is_final_status", transfer_metadata.is_final_status()}, + {"is_self_share", transfer_metadata.is_self_share()}, + {"binding_id", transfer_metadata.binding_id()}, + }; + if (transfer_metadata.token().has_value()) { + result["token"] = *transfer_metadata.token(); + } + if (transfer_metadata.in_progress_attachment_id().has_value()) { + result["in_progress_attachment_id"] = + *transfer_metadata.in_progress_attachment_id(); + } + if (transfer_metadata.in_progress_attachment_transferred_bytes() + .has_value()) { + result["in_progress_attachment_transferred_bytes"] = + *transfer_metadata.in_progress_attachment_transferred_bytes(); + } + if (transfer_metadata.in_progress_attachment_total_bytes().has_value()) { + result["in_progress_attachment_total_bytes"] = + *transfer_metadata.in_progress_attachment_total_bytes(); + } + return result; +} + +DaemonService::DaemonService(NearbySharingService& service, + EventSink event_sink) + : service_(service), + event_sink_(std::move(event_sink)), + send_transfer_callback_(*this, /*receive_mode=*/false), + receive_transfer_callback_(*this, /*receive_mode=*/true), + discovery_callback_(*this) {} + +DaemonService::~DaemonService() { Shutdown(); } + +nlohmann::json DaemonService::HandleCommand(const nlohmann::json& request) { + if (!request.is_object() || !request.contains("command") || + !request["command"].is_string()) { + return CommandResult("", false, "missing string field: command"); + } + std::string command = request["command"].get(); + if (command == "status") { + return Status(); + } + if (command == "start_receive") { + return StartReceive(); + } + if (command == "stop_receive") { + return StopReceive(); + } + if (command == "start_discovery") { + return StartDiscovery(); + } + if (command == "stop_discovery") { + return StopDiscovery(); + } + if (command == "send_file") { + return SendFile(request); + } + if (command == "accept") { + return Accept(request); + } + if (command == "reject") { + return Reject(request); + } + if (command == "cancel") { + return Cancel(request); + } + if (command == "shutdown") { + Shutdown(); + return CommandResult(command, true, "daemon service shut down"); + } + return CommandResult(command, false, "unknown command"); +} + +void DaemonService::Shutdown() { + bool stop_receive = false; + bool stop_discovery = false; + { + absl::MutexLock lock(lock_); + if (shutdown_) { + return; + } + shutdown_ = true; + stop_receive = receive_registered_; + stop_discovery = discovery_registered_; + receive_registered_ = false; + discovery_registered_ = false; + targets_.clear(); + active_transfers_.clear(); + } + + if (stop_receive) { + WaitForStatus([&](auto callback) { + service_.UnregisterReceiveSurface(&receive_transfer_callback_, + std::move(callback)); + }); + } + if (stop_discovery) { + WaitForStatus([&](auto callback) { + service_.UnregisterSendSurface(&send_transfer_callback_, + std::move(callback)); + }); + } + WaitForStatus([&](auto callback) { service_.Shutdown(std::move(callback)); }); +} + +nlohmann::json DaemonService::StartReceive() { + { + absl::MutexLock lock(lock_); + if (receive_registered_) { + return CommandResult("start_receive", true, "receive already started"); + } + } + + auto result = InvokeStatusCommand("start_receive", [&](auto callback) { + service_.RegisterReceiveSurface( + &receive_transfer_callback_, + NearbySharingService::ReceiveSurfaceState::kForeground, + Advertisement::BlockedVendorId::kNone, std::move(callback)); + }); + if (result["ok"].get()) { + absl::MutexLock lock(lock_); + receive_registered_ = true; + } + return result; +} + +nlohmann::json DaemonService::StopReceive() { + { + absl::MutexLock lock(lock_); + if (!receive_registered_) { + return CommandResult("stop_receive", true, "receive already stopped"); + } + receive_registered_ = false; + } + + return InvokeStatusCommand("stop_receive", [&](auto callback) { + service_.UnregisterReceiveSurface(&receive_transfer_callback_, + std::move(callback)); + }); +} + +nlohmann::json DaemonService::StartDiscovery() { + { + absl::MutexLock lock(lock_); + if (discovery_registered_) { + return CommandResult("start_discovery", true, "discovery already started"); + } + } + + auto result = InvokeStatusCommand("start_discovery", [&](auto callback) { + service_.RegisterSendSurface( + &send_transfer_callback_, &discovery_callback_, + NearbySharingService::SendSurfaceState::kForeground, + Advertisement::BlockedVendorId::kNone, + /*disable_wifi_hotspot=*/false, std::move(callback)); + }); + if (result["ok"].get()) { + absl::MutexLock lock(lock_); + discovery_registered_ = true; + } + return result; +} + +nlohmann::json DaemonService::StopDiscovery() { + { + absl::MutexLock lock(lock_); + if (!discovery_registered_) { + return CommandResult("stop_discovery", true, "discovery already stopped"); + } + discovery_registered_ = false; + targets_.clear(); + } + + return InvokeStatusCommand("stop_discovery", [&](auto callback) { + service_.UnregisterSendSurface(&send_transfer_callback_, + std::move(callback)); + }); +} + +nlohmann::json DaemonService::SendFile(const nlohmann::json& request) { + nlohmann::json error; + std::optional share_target_id = GetRequiredTargetId(request, error); + if (!share_target_id.has_value()) { + error["command"] = "send_file"; + return error; + } + if (!request.contains("path") || !request["path"].is_string()) { + return CommandResult("send_file", false, "missing string field: path"); + } + + std::string path = request["path"].get(); + std::error_code file_error; + if (!std::filesystem::is_regular_file(path, file_error)) { + return CommandResult("send_file", false, "path is not a regular file"); + } + + { + absl::MutexLock lock(lock_); + if (targets_.find(*share_target_id) == targets_.end()) { + return CommandResult("send_file", false, "unknown share_target_id"); + } + } + + auto attachments = CreateFileAttachments(path); + return InvokeStatusCommand("send_file", [&](auto callback) { + service_.SendAttachments(*share_target_id, std::move(attachments), + std::move(callback)); + }); +} + +nlohmann::json DaemonService::Accept(const nlohmann::json& request) { + nlohmann::json error; + std::optional share_target_id = GetRequiredTargetId(request, error); + if (!share_target_id.has_value()) { + error["command"] = "accept"; + return error; + } + return InvokeStatusCommand("accept", [&](auto callback) { + service_.Accept(*share_target_id, std::move(callback)); + }); +} + +nlohmann::json DaemonService::Reject(const nlohmann::json& request) { + nlohmann::json error; + std::optional share_target_id = GetRequiredTargetId(request, error); + if (!share_target_id.has_value()) { + error["command"] = "reject"; + return error; + } + return InvokeStatusCommand("reject", [&](auto callback) { + service_.Reject(*share_target_id, std::move(callback)); + }); +} + +nlohmann::json DaemonService::Cancel(const nlohmann::json& request) { + nlohmann::json error; + std::optional share_target_id = GetRequiredTargetId(request, error); + if (!share_target_id.has_value()) { + error["command"] = "cancel"; + return error; + } + return InvokeStatusCommand("cancel", [&](auto callback) { + service_.Cancel(*share_target_id, std::move(callback)); + }); +} + +nlohmann::json DaemonService::Status() { + nlohmann::json targets = nlohmann::json::array(); + { + absl::MutexLock lock(lock_); + for (const auto& [id, target] : targets_) { + static_cast(id); + targets.push_back(ShareTargetToJson(target)); + } + return nlohmann::json{{"event", "command_result"}, + {"command", "status"}, + {"ok", true}, + {"receive_registered", receive_registered_}, + {"discovery_registered", discovery_registered_}, + {"is_transferring", service_.IsTransferring()}, + {"is_scanning", service_.IsScanning()}, + {"bluetooth_present", service_.IsBluetoothPresent()}, + {"bluetooth_powered", service_.IsBluetoothPowered()}, + {"lan_connected", service_.IsLanConnected()}, + {"targets", std::move(targets)}}; + } +} + +void DaemonService::OnTransferUpdate( + bool receive_mode, const ShareTarget& share_target, + const AttachmentContainer& attachment_container, + const TransferMetadata& transfer_metadata) { + { + absl::MutexLock lock(lock_); + active_transfers_[share_target.id] = share_target; + if (TransferMetadata::IsFinalStatus(transfer_metadata.status())) { + active_transfers_.erase(share_target.id); + } + } + + std::string event = + receive_mode && + transfer_metadata.status() == + TransferMetadata::Status::kAwaitingLocalConfirmation + ? "incoming_transfer" + : "transfer_update"; + Emit(nlohmann::json{ + {"event", event}, + {"direction", receive_mode ? "receive" : "send"}, + {"share_target", ShareTargetToJson(share_target)}, + {"transfer", + TransferMetadataToJson(transfer_metadata, attachment_container)}, + }); +} + +void DaemonService::OnShareTargetDiscovered(const ShareTarget& share_target) { + { + absl::MutexLock lock(lock_); + targets_[share_target.id] = share_target; + } + Emit(nlohmann::json{{"event", "target_discovered"}, + {"share_target", ShareTargetToJson(share_target)}}); +} + +void DaemonService::OnShareTargetUpdated(const ShareTarget& share_target) { + { + absl::MutexLock lock(lock_); + targets_[share_target.id] = share_target; + } + Emit(nlohmann::json{{"event", "target_updated"}, + {"share_target", ShareTargetToJson(share_target)}}); +} + +void DaemonService::OnShareTargetLost(const ShareTarget& share_target) { + { + absl::MutexLock lock(lock_); + targets_.erase(share_target.id); + } + Emit(nlohmann::json{{"event", "target_lost"}, + {"share_target", ShareTargetToJson(share_target)}}); +} + +nlohmann::json DaemonService::InvokeStatusCommand( + const std::string& command, + std::function)> + invoker) { + NearbySharingService::StatusCodes status = WaitForStatus(std::move(invoker)); + bool ok = status == NearbySharingService::StatusCodes::kOk; + return CommandResult(command, ok, StatusCodeToString(status)); +} + +std::optional DaemonService::GetRequiredTargetId( + const nlohmann::json& request, nlohmann::json& error) const { + if (!request.contains("share_target_id") || + !request["share_target_id"].is_number_integer()) { + error = CommandResult("", false, "missing integer field: share_target_id"); + return std::nullopt; + } + return request["share_target_id"].get(); +} + +void DaemonService::Emit(nlohmann::json event) { + if (event_sink_) { + event_sink_(std::move(event)); + } +} + +void DaemonService::TransferCallback::OnTransferUpdate( + const ShareTarget& share_target, + const AttachmentContainer& attachment_container, + const TransferMetadata& transfer_metadata) { + daemon_.OnTransferUpdate(receive_mode_, share_target, attachment_container, + transfer_metadata); +} + +void DaemonService::DiscoveryCallback::OnShareTargetDiscovered( + const ShareTarget& share_target) { + daemon_.OnShareTargetDiscovered(share_target); +} + +void DaemonService::DiscoveryCallback::OnShareTargetLost( + const ShareTarget& share_target) { + daemon_.OnShareTargetLost(share_target); +} + +void DaemonService::DiscoveryCallback::OnShareTargetUpdated( + const ShareTarget& share_target) { + daemon_.OnShareTargetUpdated(share_target); +} + +} // namespace nearby::sharing::linux diff --git a/sharing/linux/daemon/daemon_service.h b/sharing/linux/daemon/daemon_service.h new file mode 100644 index 00000000..5348d8b4 --- /dev/null +++ b/sharing/linux/daemon/daemon_service.h @@ -0,0 +1,172 @@ +#ifndef SHARING_LINUX_DAEMON_DAEMON_SERVICE_H_ +#define SHARING_LINUX_DAEMON_DAEMON_SERVICE_H_ + +#include +#include +#include +#include +#include + +#include "absl/synchronization/mutex.h" +#include "nlohmann/json.hpp" +#include "sharing/attachment_container.h" +#include "sharing/nearby_sharing_service.h" +#include "sharing/share_target.h" +#include "sharing/share_target_discovered_callback.h" +#include "sharing/transfer_metadata.h" +#include "sharing/transfer_update_callback.h" + +namespace nearby::sharing::linux { + +// Owns the daemon-facing state and translates JSON IPC commands into +// NearbySharingService operations. This class does not own `service`; callers +// must keep the service alive for the lifetime of DaemonService. +class DaemonService { + public: + // Receives asynchronous daemon events, such as discovered targets and + // transfer updates, serialized as JSON objects. + using EventSink = std::function; + + // Creates a daemon adapter around `service` and publishes asynchronous events + // to `event_sink`. + DaemonService(NearbySharingService& service, EventSink event_sink); + + // Stops registered surfaces and shuts down the wrapped Nearby service. + ~DaemonService(); + + DaemonService(const DaemonService&) = delete; + DaemonService& operator=(const DaemonService&) = delete; + + // Handles one JSON IPC command and returns a command_result JSON object. + // The request must contain a string `command` field. + nlohmann::json HandleCommand(const nlohmann::json& request); + + // Idempotently unregisters active surfaces, clears daemon state, and shuts + // down the wrapped Nearby service. + void Shutdown(); + + private: + // Receives send or receive transfer updates from NearbySharingService and + // forwards them back into DaemonService with direction context. + class TransferCallback final : public TransferUpdateCallback { + public: + // Creates a transfer callback. `receive_mode` distinguishes receive events + // from send events when serializing daemon events. + explicit TransferCallback(DaemonService& daemon, bool receive_mode) + : daemon_(daemon), receive_mode_(receive_mode) {} + + // Forwards a Nearby transfer update to the daemon event pipeline. + void OnTransferUpdate(const ShareTarget& share_target, + const AttachmentContainer& attachment_container, + const TransferMetadata& transfer_metadata) override; + + private: + DaemonService& daemon_; + bool receive_mode_; + }; + + // Receives discovered target lifecycle events from NearbySharingService and + // forwards them back into DaemonService. + class DiscoveryCallback final : public ShareTargetDiscoveredCallback { + public: + // Creates a discovery callback bound to `daemon`. + explicit DiscoveryCallback(DaemonService& daemon) : daemon_(daemon) {} + + // Records and emits a newly discovered share target. + void OnShareTargetDiscovered(const ShareTarget& share_target) override; + + // Removes and emits a lost share target. + void OnShareTargetLost(const ShareTarget& share_target) override; + + // Updates and emits an existing share target. + void OnShareTargetUpdated(const ShareTarget& share_target) override; + + private: + DaemonService& daemon_; + }; + + // Registers the foreground receive surface used for incoming transfers. + nlohmann::json StartReceive(); + + // Unregisters the foreground receive surface if it is active. + nlohmann::json StopReceive(); + + // Registers the foreground send surface used for discovery and outgoing + // transfer updates. + nlohmann::json StartDiscovery(); + + // Unregisters the foreground send surface and clears discovered targets. + nlohmann::json StopDiscovery(); + + // Sends the regular file named by request field `path` to request field + // `share_target_id`. + nlohmann::json SendFile(const nlohmann::json& request); + + // Accepts an incoming share identified by request field `share_target_id`. + nlohmann::json Accept(const nlohmann::json& request); + + // Rejects an incoming share identified by request field `share_target_id`. + nlohmann::json Reject(const nlohmann::json& request); + + // Cancels a transfer identified by request field `share_target_id`. + nlohmann::json Cancel(const nlohmann::json& request); + + // Returns current daemon/service state, including registered surfaces and + // discovered targets. + nlohmann::json Status(); + + // Updates active transfer state and emits either `incoming_transfer` or + // `transfer_update`. + void OnTransferUpdate(bool receive_mode, const ShareTarget& share_target, + const AttachmentContainer& attachment_container, + const TransferMetadata& transfer_metadata); + + // Stores and emits a newly discovered target. + void OnShareTargetDiscovered(const ShareTarget& share_target); + + // Stores and emits an updated target. + void OnShareTargetUpdated(const ShareTarget& share_target); + + // Removes and emits a lost target. + void OnShareTargetLost(const ShareTarget& share_target); + + // Runs an asynchronous Nearby operation synchronously and converts its status + // callback into a command_result JSON object. + nlohmann::json InvokeStatusCommand( + const std::string& command, + std::function)> + invoker); + + // Extracts request field `share_target_id`; writes a command_result-style + // error object and returns nullopt when the field is absent or not integral. + std::optional GetRequiredTargetId(const nlohmann::json& request, + nlohmann::json& error) const; + + // Publishes an asynchronous event through the configured EventSink. + void Emit(nlohmann::json event); + + NearbySharingService& service_; + EventSink event_sink_; + TransferCallback send_transfer_callback_; + TransferCallback receive_transfer_callback_; + DiscoveryCallback discovery_callback_; + mutable absl::Mutex lock_; + std::unordered_map targets_; + std::unordered_map active_transfers_; + bool receive_registered_ = false; + bool discovery_registered_ = false; + bool shutdown_ = false; +}; + +// Serializes the subset of ShareTarget fields exposed through daemon IPC. +nlohmann::json ShareTargetToJson(const ShareTarget& share_target); + +// Serializes transfer progress and attachment totals exposed through daemon +// IPC. +nlohmann::json TransferMetadataToJson( + const TransferMetadata& transfer_metadata, + const AttachmentContainer& attachment_container); + +} // namespace nearby::sharing::linux + +#endif // SHARING_LINUX_DAEMON_DAEMON_SERVICE_H_ diff --git a/sharing/linux/daemon/daemon_service_test.cc b/sharing/linux/daemon/daemon_service_test.cc new file mode 100644 index 00000000..77c86a7d --- /dev/null +++ b/sharing/linux/daemon/daemon_service_test.cc @@ -0,0 +1,306 @@ +#include "sharing/linux/daemon/daemon_service.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "absl/functional/any_invocable.h" +#include "absl/time/time.h" +#include "nlohmann/json.hpp" +#include "sharing/attachment_container.h" +#include "sharing/nearby_sharing_service.h" +#include "sharing/share_target.h" +#include "sharing/share_target_discovered_callback.h" +#include "sharing/transfer_metadata.h" +#include "sharing/transfer_metadata_builder.h" +#include "sharing/transfer_update_callback.h" + +namespace nearby::sharing::linux { +namespace { + +ShareTarget MakeTarget(int64_t id, std::string name = "Pixel") { + ShareTarget target; + target.id = id; + target.device_name = std::move(name); + return target; +} + +std::unique_ptr MakeAttachments() { + AttachmentContainer::Builder builder; + return builder.Build(); +} + +TransferMetadata MakeMetadata(TransferMetadata::Status status) { + return TransferMetadataBuilder() + .set_status(status) + .set_progress(status == TransferMetadata::Status::kComplete ? 100 : 25) + .set_total_attachments_count(1) + .build(); +} + +class FakeDaemonNearbySharingService final : public NearbySharingService { + public: + void AddObserver(Observer* observer) override { static_cast(observer); } + void RemoveObserver(Observer* observer) override { + static_cast(observer); + } + void Shutdown(std::function callback) override { + shutdown_called = true; + callback(StatusCodes::kOk); + } + void RegisterSendSurface( + TransferUpdateCallback* transfer_callback, + ShareTargetDiscoveredCallback* discovery_callback, SendSurfaceState state, + Advertisement::BlockedVendorId blocked_vendor_id, + bool disable_wifi_hotspot, + absl::AnyInvocable callback) override { + static_cast(blocked_vendor_id); + static_cast(disable_wifi_hotspot); + send_transfer_callback = transfer_callback; + this->discovery_callback = discovery_callback; + send_state = state; + callback(StatusCodes::kOk); + } + void UnregisterSendSurface( + TransferUpdateCallback* transfer_callback, + absl::AnyInvocable callback) override { + if (send_transfer_callback == transfer_callback) { + send_transfer_callback = nullptr; + discovery_callback = nullptr; + } + callback(StatusCodes::kOk); + } + void RegisterReceiveSurface( + TransferUpdateCallback* transfer_callback, ReceiveSurfaceState state, + Advertisement::BlockedVendorId vendor_id, + absl::AnyInvocable callback) override { + static_cast(vendor_id); + receive_transfer_callback = transfer_callback; + receive_state = state; + callback(StatusCodes::kOk); + } + void UnregisterReceiveSurface( + TransferUpdateCallback* transfer_callback, + absl::AnyInvocable callback) override { + if (receive_transfer_callback == transfer_callback) { + receive_transfer_callback = nullptr; + } + callback(StatusCodes::kOk); + } + void ClearForegroundReceiveSurfaces( + absl::AnyInvocable callback) override { + callback(StatusCodes::kOk); + } + bool IsTransferring() const override { return false; } + bool IsScanning() const override { return discovery_callback != nullptr; } + bool IsBluetoothPresent() const override { return true; } + bool IsBluetoothPowered() const override { return true; } + bool IsExtendedAdvertisingSupported() const override { return true; } + bool IsLanConnected() const override { return true; } + std::string GetQrCodeUrl() const override { return ""; } + void SendAttachments( + int64_t share_target_id, + std::unique_ptr attachment_container, + std::function callback) override { + last_send_target_id = share_target_id; + last_attachment_count = attachment_container == nullptr + ? 0 + : attachment_container->GetAttachmentCount(); + callback(StatusCodes::kOk); + } + void Accept(int64_t share_target_id, + std::function callback) override { + last_accept_target_id = share_target_id; + callback(StatusCodes::kOk); + } + void Reject(int64_t share_target_id, + std::function callback) override { + last_reject_target_id = share_target_id; + callback(StatusCodes::kOk); + } + void Cancel(int64_t share_target_id, + std::function callback) override { + last_cancel_target_id = share_target_id; + callback(StatusCodes::kOk); + } + void InitiatePairing( + int64_t share_target_id, + service::proto::BindingRequest::Type binding_type, + absl::AnyInvocable callback) override { + static_cast(share_target_id); + static_cast(binding_type); + std::move(callback)(StatusCodes::kOk); + } + void SetVisibility( + proto::DeviceVisibility visibility, absl::Duration expiration, + absl::AnyInvocable callback) override { + static_cast(visibility); + static_cast(expiration); + std::move(callback)(StatusCodes::kOk); + } + std::string Dump() const override { return ""; } + void UpdateFilePathsInProgress(bool update_file_paths) override { + static_cast(update_file_paths); + } + NearbyShareSettings* GetSettings() override { return nullptr; } + NearbyShareCertificateManager* GetCertificateManager() override { + return nullptr; + } + AccountManager* GetAccountManager() override { return nullptr; } + Clock& GetClock() override { throw std::logic_error("unused"); } + void SetAlternateServiceUuidForDiscovery(uint16_t uuid) override { + static_cast(uuid); + } + SyncManager& sync_manager() override { throw std::logic_error("unused"); } + OutgoingTargetsManager& outgoing_targets_manager() override { + throw std::logic_error("unused"); + } + void UpdateBackupSavePath( + absl::string_view binding_id, absl::string_view save_path, + absl::AnyInvocable callback) + override { + static_cast(binding_id); + static_cast(save_path); + callback(StatusCodes::kOk); + } + + void FireShareTargetDiscovered(const ShareTarget& target) { + discovery_callback->OnShareTargetDiscovered(target); + } + void FireShareTargetLost(const ShareTarget& target) { + discovery_callback->OnShareTargetLost(target); + } + void FireReceiveTransferUpdate(const ShareTarget& target, + const AttachmentContainer& attachments, + const TransferMetadata& metadata) { + receive_transfer_callback->OnTransferUpdate(target, attachments, metadata); + } + + TransferUpdateCallback* send_transfer_callback = nullptr; + TransferUpdateCallback* receive_transfer_callback = nullptr; + ShareTargetDiscoveredCallback* discovery_callback = nullptr; + SendSurfaceState send_state = SendSurfaceState::kUnknown; + ReceiveSurfaceState receive_state = ReceiveSurfaceState::kUnknown; + bool shutdown_called = false; + int64_t last_send_target_id = 0; + size_t last_attachment_count = 0; + int64_t last_accept_target_id = 0; + int64_t last_reject_target_id = 0; + int64_t last_cancel_target_id = 0; +}; + +class DaemonServiceTest : public ::testing::Test { + protected: + DaemonServiceTest() + : daemon_(service_, [this](const nlohmann::json& event) { + events_.push_back(event); + }) {} + + FakeDaemonNearbySharingService service_; + std::vector events_; + DaemonService daemon_; +}; + +TEST_F(DaemonServiceTest, StartDiscoveryPublishesDiscoveredTargets) { + nlohmann::json result = + daemon_.HandleCommand({{"command", "start_discovery"}}); + ASSERT_TRUE(result["ok"].get()); + + service_.FireShareTargetDiscovered(MakeTarget(42)); + + ASSERT_EQ(events_.size(), 1u); + EXPECT_EQ(events_[0]["event"], "target_discovered"); + EXPECT_EQ(events_[0]["share_target"]["id"], 42); + EXPECT_EQ(events_[0]["share_target"]["device_name"], "Pixel"); + + nlohmann::json status = daemon_.HandleCommand({{"command", "status"}}); + ASSERT_TRUE(status["ok"].get()); + ASSERT_EQ(status["targets"].size(), 1u); + EXPECT_EQ(status["targets"][0]["id"], 42); +} + +TEST_F(DaemonServiceTest, TargetLostRemovesTargetFromStatus) { + ASSERT_TRUE( + daemon_.HandleCommand({{"command", "start_discovery"}})["ok"].get()); + ShareTarget target = MakeTarget(7); + service_.FireShareTargetDiscovered(target); + service_.FireShareTargetLost(target); + + ASSERT_EQ(events_.size(), 2u); + EXPECT_EQ(events_[1]["event"], "target_lost"); + + nlohmann::json status = daemon_.HandleCommand({{"command", "status"}}); + ASSERT_TRUE(status["ok"].get()); + EXPECT_TRUE(status["targets"].empty()); +} + +TEST_F(DaemonServiceTest, ReceiveAwaitingConfirmationPublishesIncomingTransfer) { + ASSERT_TRUE( + daemon_.HandleCommand({{"command", "start_receive"}})["ok"].get()); + + auto attachments = MakeAttachments(); + service_.FireReceiveTransferUpdate( + MakeTarget(9), *attachments, + MakeMetadata(TransferMetadata::Status::kAwaitingLocalConfirmation)); + + ASSERT_EQ(events_.size(), 1u); + EXPECT_EQ(events_[0]["event"], "incoming_transfer"); + EXPECT_EQ(events_[0]["direction"], "receive"); + EXPECT_EQ(events_[0]["share_target"]["id"], 9); + EXPECT_EQ(events_[0]["transfer"]["status"], "kAwaitingLocalConfirmation"); +} + +TEST_F(DaemonServiceTest, AcceptRejectAndCancelReturnCommandResults) { + EXPECT_TRUE(daemon_.HandleCommand({{"command", "accept"}, + {"share_target_id", 1}})["ok"] + .get()); + EXPECT_EQ(service_.last_accept_target_id, 1); + EXPECT_TRUE(daemon_.HandleCommand({{"command", "reject"}, + {"share_target_id", 1}})["ok"] + .get()); + EXPECT_EQ(service_.last_reject_target_id, 1); + EXPECT_TRUE(daemon_.HandleCommand({{"command", "cancel"}, + {"share_target_id", 1}})["ok"] + .get()); + EXPECT_EQ(service_.last_cancel_target_id, 1); +} + +TEST_F(DaemonServiceTest, SendFileRejectsUnknownTarget) { + nlohmann::json result = daemon_.HandleCommand( + {{"command", "send_file"}, {"share_target_id", 404}, {"path", "/tmp/x"}}); + EXPECT_FALSE(result["ok"].get()); +} + +TEST_F(DaemonServiceTest, SendFileToKnownTargetReturnsOkForRegularFile) { + ASSERT_TRUE( + daemon_.HandleCommand({{"command", "start_discovery"}})["ok"].get()); + service_.FireShareTargetDiscovered(MakeTarget(12)); + + const std::string path = "/tmp/nearby_daemon_service_test_file"; + { + std::ofstream file(path); + file << "hello"; + } + + nlohmann::json result = daemon_.HandleCommand( + {{"command", "send_file"}, {"share_target_id", 12}, {"path", path}}); + EXPECT_TRUE(result["ok"].get()) << result.dump(); + EXPECT_EQ(service_.last_send_target_id, 12); + EXPECT_EQ(service_.last_attachment_count, 1u); + std::remove(path.c_str()); +} + +TEST_F(DaemonServiceTest, MalformedCommandReturnsError) { + nlohmann::json result = daemon_.HandleCommand({{"path", "/tmp/x"}}); + EXPECT_FALSE(result["ok"].get()); +} + +} // namespace +} // namespace nearby::sharing::linux diff --git a/sharing/linux/daemon/ipc_server.cc b/sharing/linux/daemon/ipc_server.cc index da9c777d..83c47b94 100644 --- a/sharing/linux/daemon/ipc_server.cc +++ b/sharing/linux/daemon/ipc_server.cc @@ -1,8 +1,21 @@ #include "ipc_server.h" -#include + #include #include #include +#include + +namespace { + +nlohmann::json CommandResult(std::string command, bool ok, + std::string message) { + return nlohmann::json{{"event", "command_result"}, + {"command", std::move(command)}, + {"ok", ok}, + {"message", std::move(message)}}; +} + +} // namespace std::string IPCServer::Read() { std::string cmd; @@ -21,16 +34,19 @@ std::string IPCServer::Read() { void IPCServer::Stop() { running_.store(false); - if (client_fd_ >= 0) { - shutdown(client_fd_, SHUT_RDWR); - close(client_fd_); - client_fd_ = -1; - } + { + absl::MutexLock write_lock(write_lock_); + if (client_fd_ >= 0) { + shutdown(client_fd_, SHUT_RDWR); + close(client_fd_); + client_fd_ = -1; + } - if (sock_fd_ >= 0) { - shutdown(sock_fd_, SHUT_RDWR); - close(sock_fd_); - sock_fd_ = -1; + if (sock_fd_ >= 0) { + shutdown(sock_fd_, SHUT_RDWR); + close(sock_fd_); + sock_fd_ = -1; + } } unlink(SOCK_PATH.data()); @@ -116,6 +132,7 @@ void IPCServer::Recv() { } void IPCServer::StartEventLoop() { + started_.store(true); running_.store(true); InitialiseSock(); @@ -146,30 +163,73 @@ void IPCServer::StartEventLoop() { break; } - client_fd_ = accepted_fd; + { + absl::MutexLock write_lock(write_lock_); + client_fd_ = accepted_fd; + } Recv(); - if (client_fd_ >= 0) { - close(client_fd_); - client_fd_ = -1; + { + absl::MutexLock write_lock(write_lock_); + if (client_fd_ >= 0) { + close(client_fd_); + client_fd_ = -1; + } } } Stop(); } -void IPCServer::DispatchOne(const std::string& line) { - auto space = line.find(' '); - - std::string command = - space == std::string::npos ? line : line.substr(0, space); - - std::string_view args; - if (space != std::string::npos) { - args = std::string_view(line).substr(space + 1); +bool IPCServer::SendLine(std::string_view line) { + absl::MutexLock write_lock(write_lock_); + if (client_fd_ < 0) { + return false; } + std::string data(line); + if (data.empty() || data.back() != '\n') { + data.push_back('\n'); + } + + size_t total_sent = 0; + while (total_sent < data.size()) { + ssize_t sent = + send(client_fd_, data.data() + total_sent, data.size() - total_sent, 0); + if (sent > 0) { + total_sent += static_cast(sent); + continue; + } + if (sent < 0 && errno == EINTR) { + continue; + } + return false; + } + + return true; +} + +bool IPCServer::SendJson(const nlohmann::json& message) { + return SendLine(message.dump()); +} + +void IPCServer::DispatchOne(const std::string& line) { + nlohmann::json request; + try { + request = nlohmann::json::parse(line); + } catch (const nlohmann::json::exception& e) { + SendJson(CommandResult("", false, std::string("malformed JSON: ") + e.what())); + return; + } + + if (!request.is_object() || !request.contains("command") || + !request["command"].is_string()) { + SendJson(CommandResult("", false, "missing string field: command")); + return; + } + std::string command = request["command"].get(); + Handler handler; { @@ -177,20 +237,23 @@ void IPCServer::DispatchOne(const std::string& line) { auto it = handlers_.find(command); if (it == handlers_.end()) { - std::cout << "Unknown command: " << command << "\n"; + SendJson(CommandResult(command, false, "unknown command")); return; } handler = it->second; } - handler(args); + handler(request); } void IPCServer::DispatchLoop() { - while (running_.load()) { + while (true) { std::string line = Read(); if (line.empty()) { + if (!running_.load() && started_.load()) { + return; + } std::this_thread::sleep_for(std::chrono::milliseconds(10)); continue; } diff --git a/sharing/linux/daemon/ipc_server.h b/sharing/linux/daemon/ipc_server.h index 1c954647..8c833710 100644 --- a/sharing/linux/daemon/ipc_server.h +++ b/sharing/linux/daemon/ipc_server.h @@ -1,6 +1,8 @@ #include +#include #include #include +#include #include #include @@ -9,6 +11,7 @@ #include "absl/synchronization/mutex.h" #include "gtest/gtest_prod.h" +#include "nlohmann/json.hpp" constexpr std::string_view SOCK_PATH = "/tmp/nearby_sharing_sock"; @@ -19,13 +22,15 @@ class IPCServer { ~IPCServer() { Stop(); } void Stop(); void StartEventLoop(); - using Handler = std::function; + using Handler = std::function; void RegisterHandler(std::string command, Handler handler) { absl::MutexLock lock(lock_); handlers_[std::move(command)] = std::move(handler); } + bool SendLine(std::string_view line); + bool SendJson(const nlohmann::json& message); void DispatchLoop(); private: @@ -41,6 +46,8 @@ class IPCServer { sockaddr_un addr{}; std::string read_buf; absl::Mutex lock_; + absl::Mutex write_lock_; std::unordered_map handlers_; std::atomic running_{false}; + std::atomic started_{false}; }; diff --git a/sharing/linux/daemon/ipc_server_test.cc b/sharing/linux/daemon/ipc_server_test.cc index bc22792f..793fb546 100644 --- a/sharing/linux/daemon/ipc_server_test.cc +++ b/sharing/linux/daemon/ipc_server_test.cc @@ -33,6 +33,7 @@ class IPCServerTest : public ::testing::Test { static void SetRunning(IPCServer& server, bool running) { server.running_.store(running); + server.started_.store(true); } static bool IsRunning(IPCServer& server) { @@ -322,11 +323,12 @@ TEST_F(IPCServerTest, DispatchOneCallsRegisteredHandler) { bool called = false; - server.RegisterHandler("PING", [&](std::string_view args) { + server.RegisterHandler("PING", [&](const nlohmann::json& request) { + EXPECT_EQ(request.value("command", ""), "PING"); called = true; }); - DispatchOne(server, "PING"); + DispatchOne(server, R"({"command":"PING"})"); EXPECT_TRUE(called); } @@ -336,11 +338,11 @@ TEST_F(IPCServerTest, DispatchOnePassesArguments) { std::string received_args; - server.RegisterHandler("ECHO", [&](std::string_view args) { - received_args = std::string(args); + server.RegisterHandler("ECHO", [&](const nlohmann::json& request) { + received_args = request.value("args", ""); }); - DispatchOne(server, "ECHO hello world"); + DispatchOne(server, R"({"command":"ECHO","args":"hello world"})"); EXPECT_EQ(received_args, "hello world"); } @@ -350,11 +352,11 @@ TEST_F(IPCServerTest, DispatchOneHandlesCommandWithoutArgs) { std::string received_args = "not empty"; - server.RegisterHandler("PING", [&](std::string_view args) { - received_args = std::string(args); + server.RegisterHandler("PING", [&](const nlohmann::json& request) { + received_args = request.value("args", ""); }); - DispatchOne(server, "PING"); + DispatchOne(server, R"({"command":"PING"})"); EXPECT_EQ(received_args, ""); } @@ -364,11 +366,11 @@ TEST_F(IPCServerTest, DispatchOneIgnoresUnknownCommand) { bool called = false; - server.RegisterHandler("PING", [&](std::string_view args) { + server.RegisterHandler("PING", [&](const nlohmann::json& request) { called = true; }); - DispatchOne(server, "UNKNOWN something"); + DispatchOne(server, R"({"command":"UNKNOWN","args":"something"})"); EXPECT_FALSE(called); } @@ -379,12 +381,13 @@ TEST_F(IPCServerTest, DispatchLoopDispatchesBufferedCommand) { std::atomic called{false}; std::string received_args; - server.RegisterHandler("ECHO", [&](std::string_view args) { - received_args = std::string(args); + server.RegisterHandler("ECHO", [&](const nlohmann::json& request) { + received_args = request.value("args", ""); called.store(true); }); - SetReadBuffer(server, "ECHO hello\n"); + SetReadBuffer(server, R"({"command":"ECHO","args":"hello"})" + "\n"); SetRunning(server, true); std::thread dispatch_thread([&server]() { @@ -408,8 +411,8 @@ TEST_F(IPCServerTest, SocketCommandReachesRegisteredHandler) { std::atomic called{false}; std::string received_args; - server.RegisterHandler("ECHO", [&](std::string_view args) { - received_args = std::string(args); + server.RegisterHandler("ECHO", [&](const nlohmann::json& request) { + received_args = request.value("args", ""); called.store(true); }); @@ -424,7 +427,8 @@ TEST_F(IPCServerTest, SocketCommandReachesRegisteredHandler) { server.DispatchLoop(); }); - SendAll(client_fd, "ECHO from socket\n"); + SendAll(client_fd, R"({"command":"ECHO","args":"from socket"})" + "\n"); ASSERT_TRUE(WaitUntilTrue([&]() { return called.load(); diff --git a/sharing/linux/daemon/main.cc b/sharing/linux/daemon/main.cc index 454b45a4..7ff4ef2f 100644 --- a/sharing/linux/daemon/main.cc +++ b/sharing/linux/daemon/main.cc @@ -1,69 +1,174 @@ -#include -#include +#include #include + +#include +#include +#include +#include +#include +#include #include -#include -#include -#include - +#include +#include "absl/time/time.h" #include "connections/implementation/flags/nearby_connections_feature_flags.h" -#include "internal/base/file_path.h" #include "internal/flags/nearby_flags.h" -#include "sharing/advertisement.h" -#include "sharing/attachment_container.h" -#include "sharing/common/nearby_share_enums.h" -#include "sharing/file_attachment.h" -#include "sharing/linux/platform/linux_sharing_platform.h" -#include "sharing/linux/nearby_noop_analytics_recorder.h" #include "sharing/flags/generated/nearby_sharing_feature_flags.h" +#include "sharing/linux/daemon/daemon_service.h" +#include "sharing/linux/daemon/ipc_server.h" +#include "sharing/linux/nearby_noop_analytics_recorder.h" +#include "sharing/linux/platform/linux_sharing_platform.h" #include "sharing/nearby_sharing_service.h" #include "sharing/nearby_sharing_service_factory.h" #include "sharing/nearby_sharing_settings.h" -#include "sharing/share_target.h" -#include "sharing/share_target_discovered_callback.h" -#include "sharing/transfer_metadata.h" -#include "sharing/transfer_update_callback.h" #include "sharing/proto/enums.pb.h" - namespace nearby::sharing::linux { +namespace { -class DiscoveryCallback final : public ShareTargetDiscoveredCallback { - public: - explicit DiscoveryCallback() {} +std::atomic g_interrupted = false; +IPCServer* g_ipc_server = nullptr; - void OnShareTargetDiscovered(const ShareTarget& share_target) override { - std::cout << "discovered target=\"" << share_target.device_name - << "\" id=" << share_target.id << std::endl; - if (share_target.receive_disabled) { - return; - } +void HandleSignal(int signal) { + static_cast(signal); + g_interrupted = true; + if (g_ipc_server != nullptr) { + g_ipc_server->Stop(); } - - void OnShareTargetLost(const ShareTarget& share_target) override { - std::cout << "lost target=\"" << share_target.device_name - << "\" id=" << share_target.id << std::endl; - } - - void OnShareTargetUpdated(const ShareTarget& share_target) override { - std::cout << "updated target=\"" << share_target.device_name - << "\" id=" << share_target.id << std::endl; - OnShareTargetDiscovered(share_target); - } - -}; } -int main() { +std::string GetHostname() { + char hostname[256] = {}; + if (gethostname(hostname, sizeof(hostname)) == 0 && hostname[0] != '\0') { + return std::string(hostname); + } + return "LinuxShare"; +} + +std::string GetDeviceName(int argc, char** argv) { + for (int i = 1; i < argc; ++i) { + std::string arg = argv[i]; + if (arg == "--name" && i + 1 < argc) { + return argv[i + 1]; + } + } + const char* env_name = std::getenv("NEARBY_DEVICE_NAME"); + if (env_name != nullptr && *env_name != '\0') { + return env_name; + } + return GetHostname(); +} + +template +NearbySharingService::StatusCodes WaitForStatus(Invoker invoker) { + std::mutex mutex; + std::condition_variable cv; + std::optional status; + invoker([&](NearbySharingService::StatusCodes callback_status) { + { + std::lock_guard lock(mutex); + status = callback_status; + } + cv.notify_one(); + }); + + std::unique_lock lock(mutex); + cv.wait(lock, [&] { return status.has_value(); }); + return *status; +} + +void ConfigureFlags() { + nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue( + nearby::sharing::config_package_nearby::nearby_sharing_feature:: + kEnableBleForTransfer, + true); + nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue( + nearby::connections::config_package_nearby::nearby_connections_feature:: + kEnableBleL2cap, + true); + nearby::NearbyFlags::GetInstance().OverrideBoolFlagValue( + nearby::connections::config_package_nearby::nearby_connections_feature:: + kRefactorBleL2cap, + true); +} + +bool ConfigureSharingService(NearbySharingService& service, + const std::string& device_name) { + service.GetSettings()->SetDataUsage(proto::WIFI_ONLY_DATA_USAGE); + service.GetSettings()->SetDeviceName( + device_name, [](DeviceNameValidationResult validation_result) { + static_cast(validation_result); + }); + auto status = WaitForStatus([&](auto callback) { + service.SetVisibility(proto::DEVICE_VISIBILITY_EVERYONE, + absl::ZeroDuration(), std::move(callback)); + }); + if (status != NearbySharingService::StatusCodes::kOk) { + std::cerr << "SetVisibility failed: " + << NearbySharingService::StatusCodeToString(status) << std::endl; + return false; + } + return true; +} + +} // namespace +} // namespace nearby::sharing::linux + +int main(int argc, char** argv) { + signal(SIGINT, nearby::sharing::linux::HandleSignal); + signal(SIGTERM, nearby::sharing::linux::HandleSignal); + + nearby::sharing::linux::ConfigureFlags(); + + const std::string device_name = + nearby::sharing::linux::GetDeviceName(argc, argv); auto analytics_recorder = nearby::sharing::linux::NoOpAnalyticsRecorder(); - auto linux_platform = nearby::sharing::linux::LinuxSharingPlatform("LinuxShare"); - auto service_ = nearby::sharing::NearbySharingServiceFactory::GetInstance()->CreateSharingService( - linux_platform, &analytics_recorder, /*event_logger=*/nullptr, - /*supports_file_sync=*/false); - if (service_ == nullptr) { - std::cerr << "failed to create NearbySharingService" << std::endl; - return 1; - } + auto linux_platform = + nearby::sharing::linux::LinuxSharingPlatform(device_name); + auto* service = + nearby::sharing::NearbySharingServiceFactory::GetInstance() + ->CreateSharingService(linux_platform, &analytics_recorder, + /*event_logger=*/nullptr, + /*supports_file_sync=*/false); + if (service == nullptr) { + std::cerr << "failed to create NearbySharingService" << std::endl; + return 1; + } + if (!nearby::sharing::linux::ConfigureSharingService(*service, device_name)) { + nearby::sharing::linux::WaitForStatus( + [&](auto callback) { service->Shutdown(std::move(callback)); }); + return 1; + } + IPCServer ipc_server; + nearby::sharing::linux::g_ipc_server = &ipc_server; + nearby::sharing::linux::DaemonService daemon( + *service, [&](const nlohmann::json& event) { ipc_server.SendJson(event); }); + + const std::string commands[] = { + "status", "start_receive", "stop_receive", "start_discovery", + "stop_discovery", "send_file", "accept", "reject", + "cancel", "shutdown", + }; + for (const std::string& command : commands) { + ipc_server.RegisterHandler(command, [&](const nlohmann::json& request) { + nlohmann::json result = daemon.HandleCommand(request); + ipc_server.SendJson(result); + if (request.value("command", "") == "shutdown") { + ipc_server.Stop(); + } + }); + } + + std::thread server_thread([&ipc_server]() { ipc_server.StartEventLoop(); }); + std::thread dispatch_thread([&ipc_server]() { ipc_server.DispatchLoop(); }); + + server_thread.join(); + ipc_server.Stop(); + if (dispatch_thread.joinable()) { + dispatch_thread.join(); + } + daemon.Shutdown(); + nearby::sharing::linux::g_ipc_server = nullptr; + return nearby::sharing::linux::g_interrupted ? 130 : 0; }