finished nearby sharing daemon

This commit is contained in:
Lasan Mahaliyana
2026-06-20 14:48:34 +05:30
parent 3dab8ae03b
commit 980157f523
9 changed files with 1254 additions and 92 deletions
+1
View File
@@ -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.
+51
View File
@@ -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",
],
)
+453
View File
@@ -0,0 +1,453 @@
#include "sharing/linux/daemon/daemon_service.h"
#include <condition_variable>
#include <filesystem>
#include <mutex>
#include <utility>
#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 <typename Invoker>
NearbySharingService::StatusCodes WaitForStatus(Invoker invoker) {
std::mutex mutex;
std::condition_variable cv;
std::optional<NearbySharingService::StatusCodes> status;
invoker([&](NearbySharingService::StatusCodes callback_status) {
{
std::lock_guard<std::mutex> lock(mutex);
status = callback_status;
}
cv.notify_one();
});
std::unique_lock<std::mutex> lock(mutex);
cv.wait(lock, [&] { return status.has_value(); });
return *status;
}
std::unique_ptr<AttachmentContainer> 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<int>(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<std::string>();
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<bool>()) {
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<bool>()) {
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<int64_t> 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::string>();
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<int64_t> 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<int64_t> 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<int64_t> 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<void>(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<void(std::function<void(NearbySharingService::StatusCodes)>)>
invoker) {
NearbySharingService::StatusCodes status = WaitForStatus(std::move(invoker));
bool ok = status == NearbySharingService::StatusCodes::kOk;
return CommandResult(command, ok, StatusCodeToString(status));
}
std::optional<int64_t> 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<int64_t>();
}
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
+172
View File
@@ -0,0 +1,172 @@
#ifndef SHARING_LINUX_DAEMON_DAEMON_SERVICE_H_
#define SHARING_LINUX_DAEMON_DAEMON_SERVICE_H_
#include <cstdint>
#include <functional>
#include <optional>
#include <string>
#include <unordered_map>
#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<void(const nlohmann::json& event)>;
// 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<void(std::function<void(NearbySharingService::StatusCodes)>)>
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<int64_t> 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<int64_t, ShareTarget> targets_;
std::unordered_map<int64_t, ShareTarget> 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_
+306
View File
@@ -0,0 +1,306 @@
#include "sharing/linux/daemon/daemon_service.h"
#include <gtest/gtest.h>
#include <functional>
#include <fstream>
#include <memory>
#include <stdexcept>
#include <string>
#include <utility>
#include <cstdio>
#include <vector>
#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<AttachmentContainer> 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<void>(observer); }
void RemoveObserver(Observer* observer) override {
static_cast<void>(observer);
}
void Shutdown(std::function<void(StatusCodes)> 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<void(StatusCodes)> callback) override {
static_cast<void>(blocked_vendor_id);
static_cast<void>(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<void(StatusCodes)> 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<void(StatusCodes)> callback) override {
static_cast<void>(vendor_id);
receive_transfer_callback = transfer_callback;
receive_state = state;
callback(StatusCodes::kOk);
}
void UnregisterReceiveSurface(
TransferUpdateCallback* transfer_callback,
absl::AnyInvocable<void(StatusCodes)> callback) override {
if (receive_transfer_callback == transfer_callback) {
receive_transfer_callback = nullptr;
}
callback(StatusCodes::kOk);
}
void ClearForegroundReceiveSurfaces(
absl::AnyInvocable<void(StatusCodes)> 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<AttachmentContainer> attachment_container,
std::function<void(StatusCodes)> 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<void(StatusCodes)> callback) override {
last_accept_target_id = share_target_id;
callback(StatusCodes::kOk);
}
void Reject(int64_t share_target_id,
std::function<void(StatusCodes)> callback) override {
last_reject_target_id = share_target_id;
callback(StatusCodes::kOk);
}
void Cancel(int64_t share_target_id,
std::function<void(StatusCodes)> 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<void(StatusCodes status_codes) &&> callback) override {
static_cast<void>(share_target_id);
static_cast<void>(binding_type);
std::move(callback)(StatusCodes::kOk);
}
void SetVisibility(
proto::DeviceVisibility visibility, absl::Duration expiration,
absl::AnyInvocable<void(StatusCodes status_code) &&> callback) override {
static_cast<void>(visibility);
static_cast<void>(expiration);
std::move(callback)(StatusCodes::kOk);
}
std::string Dump() const override { return ""; }
void UpdateFilePathsInProgress(bool update_file_paths) override {
static_cast<void>(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<void>(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<void(NearbySharingService::StatusCodes)> callback)
override {
static_cast<void>(binding_id);
static_cast<void>(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<nlohmann::json> events_;
DaemonService daemon_;
};
TEST_F(DaemonServiceTest, StartDiscoveryPublishesDiscoveredTargets) {
nlohmann::json result =
daemon_.HandleCommand({{"command", "start_discovery"}});
ASSERT_TRUE(result["ok"].get<bool>());
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<bool>());
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<bool>());
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<bool>());
EXPECT_TRUE(status["targets"].empty());
}
TEST_F(DaemonServiceTest, ReceiveAwaitingConfirmationPublishesIncomingTransfer) {
ASSERT_TRUE(
daemon_.HandleCommand({{"command", "start_receive"}})["ok"].get<bool>());
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<bool>());
EXPECT_EQ(service_.last_accept_target_id, 1);
EXPECT_TRUE(daemon_.HandleCommand({{"command", "reject"},
{"share_target_id", 1}})["ok"]
.get<bool>());
EXPECT_EQ(service_.last_reject_target_id, 1);
EXPECT_TRUE(daemon_.HandleCommand({{"command", "cancel"},
{"share_target_id", 1}})["ok"]
.get<bool>());
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<bool>());
}
TEST_F(DaemonServiceTest, SendFileToKnownTargetReturnsOkForRegularFile) {
ASSERT_TRUE(
daemon_.HandleCommand({{"command", "start_discovery"}})["ok"].get<bool>());
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<bool>()) << 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<bool>());
}
} // namespace
} // namespace nearby::sharing::linux
+89 -26
View File
@@ -1,8 +1,21 @@
#include "ipc_server.h"
#include <thread>
#include <cerrno>
#include <cstring>
#include <iostream>
#include <thread>
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<size_t>(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<std::string>();
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;
}
+8 -1
View File
@@ -1,6 +1,8 @@
#include <atomic>
#include <functional>
#include <string>
#include <string_view>
#include <unordered_map>
#include <poll.h>
#include <sys/socket.h>
@@ -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<void(std::string_view args)>;
using Handler = std::function<void(const nlohmann::json& request)>;
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<std::string, Handler> handlers_;
std::atomic<bool> running_{false};
std::atomic<bool> started_{false};
};
+20 -16
View File
@@ -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<bool> 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<bool> 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();
+154 -49
View File
@@ -1,69 +1,174 @@
#include <sys/socket.h>
#include <sys/un.h>
#include <signal.h>
#include <unistd.h>
#include <atomic>
#include <condition_variable>
#include <cstdlib>
#include <iostream>
#include <mutex>
#include <optional>
#include <string>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <thread>
#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<bool> 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<void>(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 <typename Invoker>
NearbySharingService::StatusCodes WaitForStatus(Invoker invoker) {
std::mutex mutex;
std::condition_variable cv;
std::optional<NearbySharingService::StatusCodes> status;
invoker([&](NearbySharingService::StatusCodes callback_status) {
{
std::lock_guard<std::mutex> lock(mutex);
status = callback_status;
}
cv.notify_one();
});
std::unique_lock<std::mutex> 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<void>(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;
}