diff --git a/sharing/linux/BUILD b/sharing/linux/BUILD index 6705b1b2..f00141cd 100644 --- a/sharing/linux/BUILD +++ b/sharing/linux/BUILD @@ -163,9 +163,11 @@ cc_binary( deps = [ ":nearby_sharing_service_linux", "//internal/platform:base", + "//internal/platform/implementation:account_manager", "//sharing:attachments", "//sharing:types", "@com_google_absl//absl/strings", + "@com_google_absl//absl/time", ], ) diff --git a/sharing/linux/nearby_sharing_app.cc b/sharing/linux/nearby_sharing_app.cc index d70236d1..dc114aa9 100644 --- a/sharing/linux/nearby_sharing_app.cc +++ b/sharing/linux/nearby_sharing_app.cc @@ -19,14 +19,16 @@ #include #include +#include "absl/time/time.h" +#include "internal/platform/implementation/account_manager.h" #include "sharing/linux/nearby_sharing_service_linux.h" #include "sharing/attachment_container.h" #include "sharing/file_attachment.h" -#include "sharing/text_attachment.h" #include "sharing/share_target.h" +#include "sharing/text_attachment.h" #include "sharing/transfer_metadata.h" -#include "sharing/transfer_update_callback.h" #include "sharing/share_target_discovered_callback.h" +#include "sharing/transfer_update_callback.h" #include "internal/base/file_path.h" #include "internal/base/files.h" @@ -94,6 +96,9 @@ class MyTransferUpdateCallback : public TransferUpdateCallback { std::cout << "║ 3. List devices │ 8. Cancel transfer ║" << std::endl; std::cout << "║ 4. Send file │ 9. Print status ║" << std::endl; std::cout << "║ 5. Send text │ 0. Exit ║" << std::endl; + std::cout << "║10. Sync credentials │11. Credential status ║" << std::endl; + std::cout << "║12. Visibility: Everyone │13. Visibility: Contacts ║" << std::endl; + std::cout << "║14. Visibility: Hidden │ ║" << std::endl; std::cout << "╚═════════════════════════════════════════════════════════╝" << std::endl; std::cout << "Choice: "; } @@ -155,6 +160,9 @@ class MyShareTargetDiscoveredCallback : public ShareTargetDiscoveredCallback { std::cout << "║ 3. List devices │ 8. Cancel transfer ║" << std::endl; std::cout << "║ 4. Send file │ 9. Print status ║" << std::endl; std::cout << "║ 5. Send text │ 0. Exit ║" << std::endl; + std::cout << "║10. Sync credentials │11. Credential status ║" << std::endl; + std::cout << "║12. Visibility: Everyone │13. Visibility: Contacts ║" << std::endl; + std::cout << "║14. Visibility: Hidden │ ║" << std::endl; std::cout << "╚═════════════════════════════════════════════════════════╝" << std::endl; std::cout << "Choice: "; } @@ -175,6 +183,8 @@ class NearbySharingApp { } void StartAsReceiver() { + PrepareCredentialFlow(/*for_receiver=*/true); + std::cout << "\n=== Starting as Receiver (Foreground) ===" << std::endl; service_->RegisterReceiveSurface( @@ -183,6 +193,8 @@ class NearbySharingApp { Advertisement::BlockedVendorId::kNone, [this](NearbySharingService::StatusCodes status) { if (status == NearbySharingService::StatusCodes::kOk) { + receive_surface_state_ = + NearbySharingService::ReceiveSurfaceState::kForeground; std::cout << "Successfully registered as receiver!" << std::endl; // Display QR code URL @@ -200,6 +212,7 @@ class NearbySharingApp { std::cout << "│ Scan this with your phone to connect! │" << std::endl; std::cout << "└──────────────────────────────────────────────────────────────┘" << std::endl; } + ForceCredentialSync("receiver surface registration"); } else { std::cout << "Failed to register as receiver: " << NearbySharingService::StatusCodeToString(status) << std::endl; @@ -210,6 +223,8 @@ class NearbySharingApp { } void StartAsSender() { + PrepareCredentialFlow(/*for_receiver=*/false); + std::cout << "\n=== Starting as Sender (Foreground) ===" << std::endl; service_->RegisterSendSurface( @@ -220,6 +235,7 @@ class NearbySharingApp { false, // disable_wifi_hotspot [this](NearbySharingService::StatusCodes status) { if (status == NearbySharingService::StatusCodes::kOk) { + send_surface_state_ = NearbySharingService::SendSurfaceState::kForeground; std::cout << "Successfully registered as sender!" << std::endl; // Display QR code URL @@ -237,6 +253,7 @@ class NearbySharingApp { std::cout << "│ Scan this with your phone to connect! │" << std::endl; std::cout << "└──────────────────────────────────────────────────────────────┘" << std::endl; } + ForceCredentialSync("sender surface registration"); } else { std::cout << "Failed to register as sender: " << NearbySharingService::StatusCodeToString(status) << std::endl; @@ -411,6 +428,46 @@ class NearbySharingApp { std::cout << "======================" << std::endl; } + void SyncCredentialsNow() { ForceCredentialSync("manual menu request"); } + + void PrintCredentialStatus() { + std::cout << "\n=== Credential Flow Status ===" << std::endl; + auto* account_manager = service_->GetAccountManager(); + if (account_manager == nullptr) { + std::cout << "Account manager: unavailable on this service implementation." + << std::endl; + } else if (account_manager->GetCurrentAccount().has_value()) { + const auto& account = *account_manager->GetCurrentAccount(); + std::cout << "Signed in account: " << account.email << std::endl; + std::cout << "Account id: " << account.id << std::endl; + } else { + std::cout << "Signed in account: none" << std::endl; + } + + std::cout << "Certificate manager pointer: " + << (service_->GetCertificateManager() != nullptr ? "available" + : "null") + << std::endl; + + std::cout << "Service dump:\n" << service_->Dump() << std::endl; + std::cout << "==============================" << std::endl; + } + + void SetVisibilityEveryone() { + ApplyVisibility(proto::DeviceVisibility::DEVICE_VISIBILITY_EVERYONE, + absl::Minutes(15), "EVERYONE"); + } + + void SetVisibilityContacts() { + ApplyVisibility(proto::DeviceVisibility::DEVICE_VISIBILITY_ALL_CONTACTS, + absl::ZeroDuration(), "ALL_CONTACTS"); + } + + void SetVisibilityHidden() { + ApplyVisibility(proto::DeviceVisibility::DEVICE_VISIBILITY_HIDDEN, + absl::ZeroDuration(), "HIDDEN"); + } + void Shutdown() { std::cout << "\n=== Shutting Down ===" << std::endl; service_->Shutdown([](NearbySharingService::StatusCodes status) { @@ -420,9 +477,84 @@ class NearbySharingApp { } private: + void ApplyVisibility(proto::DeviceVisibility visibility, + absl::Duration expiration, + const std::string& label) { + service_->SetVisibility( + visibility, expiration, + [label](NearbySharingService::StatusCodes status) mutable { + if (status == NearbySharingService::StatusCodes::kOk) { + std::cout << "Visibility updated to " << label << std::endl; + } else { + std::cout << "Failed to set visibility to " << label << ": " + << NearbySharingService::StatusCodeToString(status) + << std::endl; + } + }); + } + + void PrepareCredentialFlow(bool for_receiver) { + auto* account_manager = service_->GetAccountManager(); + bool has_account = account_manager != nullptr && + account_manager->GetCurrentAccount().has_value(); + + // Match NearbySharingServiceImpl behavior: outgoing paths are contacts-based + // when account data is available; receiver mode generally uses everyone. + if (for_receiver) { + SetVisibilityEveryone(); + } else if (has_account) { + SetVisibilityContacts(); + } else { + SetVisibilityEveryone(); + } + } + + void ForceCredentialSync(const std::string& reason) { + std::cout << "[Credential flow] Requesting sync via service hooks (" + << reason << ")." << std::endl; + + // Keep visibility explicitly valid for cert-backed advertising. + SetVisibilityEveryone(); + + // NearbySharingServiceImpl forces private cert upload from + // RegisterReceiveSurface when visibility is not hidden. Re-registering + // the current receive surface is enough to trigger that path. + auto state = receive_surface_state_.value_or( + NearbySharingService::ReceiveSurfaceState::kBackground); + service_->RegisterReceiveSurface( + transfer_callback_.get(), state, Advertisement::BlockedVendorId::kNone, + [](NearbySharingService::StatusCodes status) { + if (status == NearbySharingService::StatusCodes::kOk) { + std::cout << "[Credential flow] Receive surface refreshed for sync." + << std::endl; + } else { + std::cout << "[Credential flow] Failed to refresh receive surface: " + << NearbySharingService::StatusCodeToString(status) + << std::endl; + } + }); + + // Discovery-triggered public cert download is internal to the full service. + if (send_surface_state_.has_value()) { + service_->RegisterSendSurface( + transfer_callback_.get(), discovery_callback_.get(), + *send_surface_state_, Advertisement::BlockedVendorId::kNone, + /*disable_wifi_hotspot=*/false, + [](NearbySharingService::StatusCodes status) { + if (status == NearbySharingService::StatusCodes::kOk) { + std::cout << "[Credential flow] Send surface refreshed for scan " + "side sync." + << std::endl; + } + }); + } + } + std::unique_ptr service_; std::unique_ptr transfer_callback_; std::unique_ptr discovery_callback_; + std::optional receive_surface_state_; + std::optional send_surface_state_; }; void PrintMenu() { @@ -434,6 +566,9 @@ void PrintMenu() { std::cout << "║ 3. List devices │ 8. Cancel transfer ║" << std::endl; std::cout << "║ 4. Send file │ 9. Print status ║" << std::endl; std::cout << "║ 5. Send text │ 0. Exit ║" << std::endl; + std::cout << "║10. Sync credentials │11. Credential status ║" << std::endl; + std::cout << "║12. Visibility: Everyone │13. Visibility: Contacts ║" << std::endl; + std::cout << "║14. Visibility: Hidden │ ║" << std::endl; std::cout << "╚═════════════════════════════════════════════════════════╝" << std::endl; std::cout << "Choice: "; } @@ -522,6 +657,26 @@ int main(int argc, char* argv[]) { case 9: app.PrintStatus(); break; + + case 10: + app.SyncCredentialsNow(); + break; + + case 11: + app.PrintCredentialStatus(); + break; + + case 12: + app.SetVisibilityEveryone(); + break; + + case 13: + app.SetVisibilityContacts(); + break; + + case 14: + app.SetVisibilityHidden(); + break; case 0: running = false; diff --git a/sharing/linux/nearby_sharing_service_linux.cc b/sharing/linux/nearby_sharing_service_linux.cc index c33b1e7d..f3b0a9d9 100644 --- a/sharing/linux/nearby_sharing_service_linux.cc +++ b/sharing/linux/nearby_sharing_service_linux.cc @@ -16,10 +16,12 @@ #include "sharing/proto/enums.pb.h" #include +#include #include #include #include #include +#include #include "absl/strings/escaping.h" #include "absl/strings/string_view.h" @@ -37,6 +39,7 @@ #include "internal/platform/file.h" #include "internal/platform/logging.h" #include "sharing/certificates/common.h" +#include "sharing/proto/wire_format.pb.h" #include #include #include @@ -86,6 +89,231 @@ TransferMetadata::Status StatusFromPayloadStatus( return TransferMetadata::Status::kUnknown; } +using Frame = nearby::sharing::service::proto::Frame; +using V1Frame = nearby::sharing::service::proto::V1Frame; +using IntroductionFrame = nearby::sharing::service::proto::IntroductionFrame; +using ConnectionResponseFrame = + nearby::sharing::service::proto::ConnectionResponseFrame; +using PairedKeyEncryptionFrame = + nearby::sharing::service::proto::PairedKeyEncryptionFrame; +using PairedKeyResultFrame = nearby::sharing::service::proto::PairedKeyResultFrame; + +constexpr size_t kPairedKeySignedDataSize = 72; +constexpr size_t kPairedKeySecretIdHashSize = 6; + +bool IsControlFrameType(V1Frame::FrameType type) { + switch (type) { + case V1Frame::INTRODUCTION: + case V1Frame::RESPONSE: + case V1Frame::PAIRED_KEY_ENCRYPTION: + case V1Frame::PAIRED_KEY_RESULT: + case V1Frame::CANCEL: + return true; + default: + return false; + } +} + +bool TryParseControlFrame(const connections::Payload& payload, Frame* frame) { + if (!frame || payload.GetType() != connections::PayloadType::kBytes) { + return false; + } + std::string bytes = payload.AsBytes().string_data(); + if (!frame->ParseFromString(bytes) || !frame->has_v1() || + frame->version() != Frame::V1 || !frame->v1().has_type()) { + return false; + } + return IsControlFrameType(frame->v1().type()); +} + +bool SendFramePayload(connections::Core* core, const std::string& endpoint_id, + auto& transfer_state, const Frame& frame) { + if (core == nullptr) { + return false; + } + + std::string serialized; + if (!frame.SerializeToString(&serialized)) { + LOG(ERROR) << "Failed to serialize protocol frame."; + return false; + } + + connections::Payload payload{nearby::ByteArray(serialized)}; + transfer_state.control_payload_ids.insert(payload.GetId()); + std::vector endpoints{endpoint_id}; + core->SendPayload(endpoints, std::move(payload), [](connections::Status) {}); + return true; +} + +Frame BuildPairedKeyEncryptionFrame() { + Frame frame; + frame.set_version(Frame::V1); + V1Frame* v1_frame = frame.mutable_v1(); + v1_frame->set_type(V1Frame::PAIRED_KEY_ENCRYPTION); + PairedKeyEncryptionFrame* encryption_frame = + v1_frame->mutable_paired_key_encryption(); + std::vector secret_hash = GenerateRandomBytes(kPairedKeySecretIdHashSize); + std::vector signed_data = GenerateRandomBytes(kPairedKeySignedDataSize); + encryption_frame->set_secret_id_hash(secret_hash.data(), secret_hash.size()); + encryption_frame->set_signed_data(signed_data.data(), signed_data.size()); + return frame; +} + +Frame BuildPairedKeyResultFrame(PairedKeyResultFrame::Status status) { + Frame frame; + frame.set_version(Frame::V1); + V1Frame* v1_frame = frame.mutable_v1(); + v1_frame->set_type(V1Frame::PAIRED_KEY_RESULT); + PairedKeyResultFrame* result_frame = v1_frame->mutable_paired_key_result(); + result_frame->set_status(status); + result_frame->set_os_type( + location::nearby::proto::sharing::OSType::UNKNOWN_OS_TYPE); + return frame; +} + +Frame BuildConnectionResponseFrame(ConnectionResponseFrame::Status status) { + Frame frame; + frame.set_version(Frame::V1); + V1Frame* v1_frame = frame.mutable_v1(); + v1_frame->set_type(V1Frame::RESPONSE); + v1_frame->mutable_connection_response()->set_status(status); + return frame; +} + +bool PrepareOutgoingIntroductionAndPayloads(auto& transfer_state, + IntroductionFrame* introduction) { + if (introduction == nullptr) { + return false; + } + + transfer_state.pending_outgoing_payloads.clear(); + transfer_state.attachment_payload_ids.clear(); + transfer_state.completed_attachment_payload_ids.clear(); + transfer_state.expected_attachment_payload_count = 0; + introduction->set_start_transfer(true); + + const AttachmentContainer& attachments = transfer_state.attachments; + for (const auto& file : attachments.GetFileAttachments()) { + if (!file.file_path().has_value()) { + continue; + } + int64_t payload_id = connections::Payload::GenerateId(); + auto* metadata = introduction->add_file_metadata(); + metadata->set_id(file.id()); + metadata->set_name(std::string(file.file_name())); + metadata->set_type(file.type()); + metadata->set_payload_id(payload_id); + metadata->set_size(file.size()); + metadata->set_mime_type(std::string(file.mime_type())); + metadata->set_parent_folder(std::string(file.parent_folder())); + + nearby::InputFile input_file(file.file_path()->ToString()); + transfer_state.pending_outgoing_payloads.emplace_back( + payload_id, std::string(file.parent_folder()), + std::string(file.file_name()), std::move(input_file)); + transfer_state.attachment_payload_ids.insert(payload_id); + } + + for (const auto& text : attachments.GetTextAttachments()) { + int64_t payload_id = connections::Payload::GenerateId(); + auto* metadata = introduction->add_text_metadata(); + metadata->set_id(text.id()); + metadata->set_text_title(std::string(text.text_title())); + metadata->set_type(text.type()); + metadata->set_payload_id(payload_id); + metadata->set_size(text.size()); + + std::string body = std::string(text.text_body()); + transfer_state.pending_outgoing_payloads.emplace_back( + payload_id, nearby::ByteArray(body)); + transfer_state.attachment_payload_ids.insert(payload_id); + } + + transfer_state.expected_attachment_payload_count = + transfer_state.attachment_payload_ids.size(); + return true; +} + +void PopulateIncomingAttachmentsFromIntroduction( + auto& transfer_state, const IntroductionFrame& introduction) { + AttachmentContainer::Builder builder; + builder.ReserveAttachmentsCount( + introduction.text_metadata_size(), introduction.file_metadata_size(), + introduction.wifi_credentials_metadata_size()); + + transfer_state.text_attachment_id_by_payload_id.clear(); + transfer_state.file_attachment_id_by_payload_id.clear(); + transfer_state.attachment_payload_ids.clear(); + transfer_state.completed_attachment_payload_ids.clear(); + transfer_state.expected_attachment_payload_count = 0; + + for (const auto& file : introduction.file_metadata()) { + builder.AddFileAttachment(FileAttachment( + file.id(), file.size(), file.name(), file.mime_type(), file.type(), + file.parent_folder())); + transfer_state.file_attachment_id_by_payload_id[file.payload_id()] = + file.id(); + transfer_state.attachment_payload_ids.insert(file.payload_id()); + } + + for (const auto& text : introduction.text_metadata()) { + builder.AddTextAttachment(TextAttachment( + text.id(), text.type(), text.text_title(), text.size())); + transfer_state.text_attachment_id_by_payload_id[text.payload_id()] = + text.id(); + transfer_state.attachment_payload_ids.insert(text.payload_id()); + } + + transfer_state.expected_attachment_payload_count = + transfer_state.attachment_payload_ids.size(); + std::unique_ptr incoming = builder.Build(); + transfer_state.attachments = std::move(*incoming); +} + +void UpdateIncomingAttachmentPayload(auto& transfer_state, + connections::Payload& payload) { + if (payload.GetType() == connections::PayloadType::kBytes) { + auto text_it = transfer_state.text_attachment_id_by_payload_id.find( + payload.GetId()); + if (text_it == transfer_state.text_attachment_id_by_payload_id.end()) { + return; + } + + std::string body = payload.AsBytes().string_data(); + auto& texts = transfer_state.attachments.GetTextAttachments(); + for (int i = 0; i < texts.size(); ++i) { + if (texts[i].id() == text_it->second) { + transfer_state.attachments.GetMutableTextAttachment(i).set_text_body( + body); + return; + } + } + return; + } + + if (payload.GetType() != connections::PayloadType::kFile) { + return; + } + auto file_it = + transfer_state.file_attachment_id_by_payload_id.find(payload.GetId()); + if (file_it == transfer_state.file_attachment_id_by_payload_id.end()) { + return; + } + nearby::InputFile* input_file = payload.AsFile(); + if (input_file == nullptr) { + return; + } + + auto& files = transfer_state.attachments.GetFileAttachments(); + for (int i = 0; i < files.size(); ++i) { + if (files[i].id() == file_it->second) { + transfer_state.attachments.GetMutableFileAttachment(i).set_file_path( + nearby::FilePath(input_file->GetFilePath())); + return; + } + } +} + } // namespace NearbySharingServiceLinux::NearbySharingServiceLinux() @@ -614,7 +842,7 @@ void NearbySharingServiceLinux::StopAdvertising() { return; } is_advertising_ = false; - core_->StopAdvertising([this](connections::Status status) { + core_->StopAdvertising([](connections::Status status) { static_cast(status); }); } @@ -944,45 +1172,27 @@ void NearbySharingServiceLinux::HandleConnectionAccepted( return; } + TransferState& transfer_state = transfer_it->second; auto share_target = GetShareTarget(endpoint_id); if (share_target) { - TransferMetadata metadata = TransferMetadataBuilder() - .set_status(TransferMetadata::Status::kInProgress) - .set_progress(0) - .set_total_attachments_count( - transfer_it->second.attachments.GetAttachmentCount()) - .build(); - NotifyTransferUpdate(*share_target, transfer_it->second, metadata); + TransferMetadata::Status status = is_incoming + ? TransferMetadata::Status::kInProgress + : TransferMetadata::Status::kAwaitingRemoteAcceptance; + TransferMetadata metadata = + TransferMetadataBuilder() + .set_status(status) + .set_progress(0) + .set_total_attachments_count( + transfer_state.attachments.GetAttachmentCount()) + .build(); + NotifyTransferUpdate(*share_target, transfer_state, metadata); } - if (!is_incoming) { - const AttachmentContainer& attachments = transfer_it->second.attachments; - std::unique_ptr payload; - if (!attachments.GetTextAttachments().empty()) { - std::string text = - std::string(attachments.GetTextAttachments()[0].text_body()); - payload = std::make_unique(ByteArray(text)); - } else if (!attachments.GetFileAttachments().empty()) { - const auto& file_attachment = attachments.GetFileAttachments()[0]; - if (file_attachment.file_path().has_value()) { - std::string file_path = file_attachment.file_path()->ToString(); - nearby::InputFile input_file(file_path); - payload = std::make_unique( - std::string(file_attachment.parent_folder()), - std::string(file_attachment.file_name()), std::move(input_file)); - } - } - - if (payload) { - std::vector endpoints; - endpoints.push_back(endpoint_id); - core_->SendPayload( - endpoints, std::move(*payload), - [this](connections::Status status) { - if (!status.Ok()) { - is_transferring_ = false; - } - }); + // Start minimal Nearby Share frame exchange: PAIRED_KEY_ENCRYPTION. + if (!transfer_state.paired_key_encryption_sent) { + Frame frame = BuildPairedKeyEncryptionFrame(); + if (SendFramePayload(core_.get(), endpoint_id, transfer_state, frame)) { + transfer_state.paired_key_encryption_sent = true; } } @@ -1023,8 +1233,7 @@ connections::PayloadListener NearbySharingServiceLinux::MakePayloadListener( static_cast(is_incoming); connections::PayloadListener listener; listener.payload_cb = - [this, is_incoming](absl::string_view endpoint_id, - connections::Payload payload) { + [this](absl::string_view endpoint_id, connections::Payload payload) { auto transfer_it = active_transfers_.find(std::string(endpoint_id)); if (transfer_it == active_transfers_.end()) { return; @@ -1034,17 +1243,145 @@ connections::PayloadListener NearbySharingServiceLinux::MakePayloadListener( return; } - TransferMetadata metadata = TransferMetadataBuilder() - .set_status(TransferMetadata::Status::kInProgress) - .set_progress(0) - .build(); - NotifyTransferUpdate(*share_target, transfer_it->second, metadata); + TransferState& transfer_state = transfer_it->second; + + Frame frame; + if (TryParseControlFrame(payload, &frame)) { + transfer_state.control_payload_ids.insert(payload.GetId()); + const V1Frame& v1 = frame.v1(); + switch (v1.type()) { + case V1Frame::PAIRED_KEY_ENCRYPTION: { + transfer_state.paired_key_encryption_received = true; + if (!transfer_state.paired_key_encryption_sent) { + Frame local_encryption = BuildPairedKeyEncryptionFrame(); + if (SendFramePayload(core_.get(), std::string(endpoint_id), + transfer_state, local_encryption)) { + transfer_state.paired_key_encryption_sent = true; + } + } + if (!transfer_state.paired_key_result_sent) { + Frame result = BuildPairedKeyResultFrame( + PairedKeyResultFrame::UNABLE); + if (SendFramePayload(core_.get(), std::string(endpoint_id), + transfer_state, result)) { + transfer_state.paired_key_result_sent = true; + } + } + break; + } + case V1Frame::PAIRED_KEY_RESULT: + transfer_state.paired_key_result_received = true; + break; + case V1Frame::INTRODUCTION: { + transfer_state.introduction_received = true; + if (transfer_state.is_incoming && v1.has_introduction()) { + PopulateIncomingAttachmentsFromIntroduction( + transfer_state, v1.introduction()); + if (!transfer_state.connection_response_sent) { + Frame response = + BuildConnectionResponseFrame(ConnectionResponseFrame::ACCEPT); + if (SendFramePayload(core_.get(), std::string(endpoint_id), + transfer_state, response)) { + transfer_state.connection_response_sent = true; + transfer_state.connection_response_accepted = true; + } + } + TransferMetadata metadata = + TransferMetadataBuilder() + .set_status(TransferMetadata::Status::kInProgress) + .set_progress(0) + .set_total_attachments_count( + transfer_state.attachments.GetAttachmentCount()) + .build(); + NotifyTransferUpdate(*share_target, transfer_state, metadata); + } + break; + } + case V1Frame::RESPONSE: { + if (!transfer_state.is_incoming && v1.has_connection_response()) { + transfer_state.connection_response_received = true; + transfer_state.connection_response_accepted = + v1.connection_response().status() == + ConnectionResponseFrame::ACCEPT; + if (!transfer_state.connection_response_accepted) { + TransferMetadata::Status rejected_status = + TransferMetadata::Status::kRejected; + TransferMetadata metadata = + TransferMetadataBuilder() + .set_status(rejected_status) + .set_progress(0) + .build(); + NotifyTransferUpdate(*share_target, transfer_state, metadata); + active_transfers_.erase(transfer_it); + if (active_transfers_.empty()) { + is_transferring_ = false; + } + return; + } + } + break; + } + case V1Frame::CANCEL: { + TransferMetadata metadata = + TransferMetadataBuilder() + .set_status(TransferMetadata::Status::kCancelled) + .set_progress(0) + .build(); + NotifyTransferUpdate(*share_target, transfer_state, metadata); + active_transfers_.erase(transfer_it); + if (active_transfers_.empty()) { + is_transferring_ = false; + } + return; + } + default: + break; + } + + // Sender-side progression: + if (!transfer_state.is_incoming && + transfer_state.paired_key_encryption_sent && + transfer_state.paired_key_encryption_received && + transfer_state.paired_key_result_sent && + transfer_state.paired_key_result_received && + !transfer_state.introduction_sent) { + Frame introduction_frame; + introduction_frame.set_version(Frame::V1); + V1Frame* out_v1 = introduction_frame.mutable_v1(); + out_v1->set_type(V1Frame::INTRODUCTION); + if (PrepareOutgoingIntroductionAndPayloads( + transfer_state, out_v1->mutable_introduction()) && + SendFramePayload(core_.get(), std::string(endpoint_id), + transfer_state, introduction_frame)) { + transfer_state.introduction_sent = true; + } + } + + if (!transfer_state.is_incoming && transfer_state.introduction_sent && + transfer_state.connection_response_received && + transfer_state.connection_response_accepted && + !transfer_state.attachment_payloads_sent) { + transfer_state.attachment_payloads_sent = true; + std::vector endpoints{std::string(endpoint_id)}; + for (auto& data_payload : transfer_state.pending_outgoing_payloads) { + transfer_state.attachment_payload_ids.insert(data_payload.GetId()); + core_->SendPayload(endpoints, std::move(data_payload), + [](connections::Status) {}); + } + transfer_state.pending_outgoing_payloads.clear(); + } + return; + } + + // Non-control payloads are attachment payloads. + UpdateIncomingAttachmentPayload(transfer_state, payload); }; listener.payload_progress_cb = - [this, is_incoming](absl::string_view endpoint_id, - const connections::PayloadProgressInfo& info) { - auto transfer_it = active_transfers_.find(std::string(endpoint_id)); + [this](absl::string_view endpoint_id, + const connections::PayloadProgressInfo& info) { + std::string endpoint_id_string(endpoint_id); + auto transfer_it = active_transfers_.find(endpoint_id_string); if (transfer_it == active_transfers_.end()) { return; } @@ -1053,22 +1390,61 @@ connections::PayloadListener NearbySharingServiceLinux::MakePayloadListener( return; } - float progress = 0.0f; - if (info.total_bytes > 0) { - progress = static_cast(info.bytes_transferred) / - static_cast(info.total_bytes); + TransferState& transfer_state = transfer_it->second; + if (transfer_state.control_payload_ids.contains(info.payload_id)) { + return; + } + if (transfer_state.expected_attachment_payload_count == 0) { + return; + } + if (!transfer_state.attachment_payload_ids.contains(info.payload_id)) { + return; + } + + size_t completed = transfer_state.completed_attachment_payload_ids.size(); + float progress = static_cast(completed) / + static_cast( + transfer_state.expected_attachment_payload_count); + TransferMetadata::Status status = TransferMetadata::Status::kInProgress; + + if (info.status == connections::PayloadProgressInfo::Status::kInProgress) { + if (info.total_bytes > 0) { + float payload_progress = static_cast(info.bytes_transferred) / + static_cast(info.total_bytes); + progress = + (static_cast(completed) + payload_progress) / + static_cast(transfer_state.expected_attachment_payload_count); + } + } else if (info.status == + connections::PayloadProgressInfo::Status::kSuccess) { + transfer_state.completed_attachment_payload_ids.insert(info.payload_id); + completed = transfer_state.completed_attachment_payload_ids.size(); + progress = static_cast(completed) / + static_cast( + transfer_state.expected_attachment_payload_count); + status = completed >= transfer_state.expected_attachment_payload_count + ? TransferMetadata::Status::kComplete + : TransferMetadata::Status::kInProgress; + } else { + status = StatusFromPayloadStatus(info.status); } TransferMetadata metadata = TransferMetadataBuilder() - .set_status(StatusFromPayloadStatus(info.status)) - .set_progress(progress) + .set_status(status) + .set_progress(std::min(progress, 1.0f)) .set_transferred_bytes(info.bytes_transferred) + .set_total_attachments_count( + static_cast( + transfer_state.expected_attachment_payload_count)) .build(); - - NotifyTransferUpdate(*share_target, transfer_it->second, metadata); + NotifyTransferUpdate(*share_target, transfer_state, metadata); if (TransferMetadata::IsFinalStatus(metadata.status())) { + if (metadata.status() == TransferMetadata::Status::kComplete) { + core_->DisconnectFromEndpoint(endpoint_id_string, + [](connections::Status) {}); + } active_transfers_.erase(transfer_it); if (active_transfers_.empty()) { is_transferring_ = false; diff --git a/sharing/linux/nearby_sharing_service_linux.h b/sharing/linux/nearby_sharing_service_linux.h index 3a0d9bc3..f99d6b5a 100644 --- a/sharing/linux/nearby_sharing_service_linux.h +++ b/sharing/linux/nearby_sharing_service_linux.h @@ -15,6 +15,7 @@ #ifndef THIRD_PARTY_NEARBY_SHARING_LINUX_NEARBY_SHARING_SERVICE_LINUX_H_ #define THIRD_PARTY_NEARBY_SHARING_LINUX_NEARBY_SHARING_SERVICE_LINUX_H_ +#include #include #include #include @@ -145,6 +146,28 @@ class NearbySharingServiceLinux : public NearbySharingService { nearby::sharing::AttachmentContainer attachments; TransferUpdateCallback* callback = nullptr; bool is_incoming = false; + + // Minimal Nearby Share frame protocol state. + bool paired_key_encryption_sent = false; + bool paired_key_encryption_received = false; + bool paired_key_result_sent = false; + bool paired_key_result_received = false; + bool introduction_sent = false; + bool introduction_received = false; + bool connection_response_sent = false; + bool connection_response_received = false; + bool connection_response_accepted = false; + bool attachment_payloads_sent = false; + + // Payload routing state. + std::unordered_set control_payload_ids; + std::unordered_set attachment_payload_ids; + std::unordered_set completed_attachment_payload_ids; + std::unordered_map text_attachment_id_by_payload_id; + std::unordered_map file_attachment_id_by_payload_id; + size_t expected_attachment_payload_count = 0; + bool transfer_complete_notified = false; + std::vector pending_outgoing_payloads; }; struct ParsedAdvertisement {