From 0842559f49df0745bd9d44b561e1a06b103c0f32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20B=C3=B6ttger?= Date: Mon, 28 Jul 2025 11:05:02 +0200 Subject: [PATCH 01/20] Increment build --- QuickDrop.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/QuickDrop.xcodeproj/project.pbxproj b/QuickDrop.xcodeproj/project.pbxproj index 19ca9bf..6f5e99c 100644 --- a/QuickDrop.xcodeproj/project.pbxproj +++ b/QuickDrop.xcodeproj/project.pbxproj @@ -840,7 +840,7 @@ MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; - SHARED_BUILD = 56; + SHARED_BUILD = 57; SHARED_VERSION = 1.11; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_EMIT_LOC_STRINGS = YES; @@ -901,7 +901,7 @@ MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = macosx; - SHARED_BUILD = 56; + SHARED_BUILD = 57; SHARED_VERSION = 1.11; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_EMIT_LOC_STRINGS = YES; From d2fc94df7588196c64230861bf2e581fbcd9aa12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20B=C3=B6ttger?= Date: Mon, 28 Jul 2025 11:36:04 +0200 Subject: [PATCH 02/20] Refactoring --- .../{Connection => Model}/DataModel.swift | 0 .../device_to_device_messages.pb.swift | 0 .../Protobuf/offline_wire_formats.pb.swift | 0 .../{ => Model}/Protobuf/securegcm.pb.swift | 0 .../Protobuf/securemessage.pb.swift | 0 .../{ => Model}/Protobuf/ukey.pb.swift | 0 .../{ => Model}/Protobuf/wire_format.pb.swift | 0 .../device_to_device_messages.proto | 0 .../ProtobufSource/offline_wire_formats.proto | 0 .../ProtobufSource/securegcm.proto | 0 .../ProtobufSource/securemessage.proto | 0 .../{ => Model}/ProtobufSource/ukey.proto | 0 .../ProtobufSource/wire_format.proto | 0 .../Connection/DataModel.swift | 188 ++++++ .../Connection/InboundNearbyConnection.swift | 534 ++++++++++++++++ .../Connection/NearbyConnection.swift | 592 ++++++++++++++++++ .../Connection/NearbyConnectionManager.swift | 323 ++++++++++ .../Connection/OutboundNearbyConnection.swift | 497 +++++++++++++++ .../device_to_device_messages.proto | 81 +++ .../ProtobufSource/offline_wire_formats.proto | 403 ++++++++++++ .../ProtobufSource/securegcm.proto | 308 +++++++++ .../ProtobufSource/securemessage.proto | 126 ++++ .../ProtobufSource/ukey.proto | 105 ++++ .../ProtobufSource/wire_format.proto | 236 +++++++ .../Util/ConnectionFailureTracker.swift | 41 ++ NearbyShareFramework/Util/Helpers.swift | 100 +++ NearbyShareFramework/Util/Logger.swift | 133 ++++ .../Util/SaveFilesManager.swift | 220 +++++++ NearbyShareFramework/Util/SleepManager.swift | 59 ++ QuickDrop.xcodeproj/project.pbxproj | 280 +++------ QuickDrop/AppDelegate.swift | 1 - QuickDrop/Plus/PlusView.swift | 1 - QuickDrop/Util/ErrorAlertHandler.swift | 1 - QuickDrop/View/SettingsView.swift | 1 - QuickDrop/View/TutorialView.swift | 1 - QuickDrop/View/WelcomeScreen.swift | 1 - ShareExtension/ShareViewController.swift | 1 - 37 files changed, 4047 insertions(+), 186 deletions(-) rename NearbyShare/{Connection => Model}/DataModel.swift (100%) rename NearbyShare/{ => Model}/Protobuf/device_to_device_messages.pb.swift (100%) rename NearbyShare/{ => Model}/Protobuf/offline_wire_formats.pb.swift (100%) rename NearbyShare/{ => Model}/Protobuf/securegcm.pb.swift (100%) rename NearbyShare/{ => Model}/Protobuf/securemessage.pb.swift (100%) rename NearbyShare/{ => Model}/Protobuf/ukey.pb.swift (100%) rename NearbyShare/{ => Model}/Protobuf/wire_format.pb.swift (100%) rename NearbyShare/{ => Model}/ProtobufSource/device_to_device_messages.proto (100%) rename NearbyShare/{ => Model}/ProtobufSource/offline_wire_formats.proto (100%) rename NearbyShare/{ => Model}/ProtobufSource/securegcm.proto (100%) rename NearbyShare/{ => Model}/ProtobufSource/securemessage.proto (100%) rename NearbyShare/{ => Model}/ProtobufSource/ukey.proto (100%) rename NearbyShare/{ => Model}/ProtobufSource/wire_format.proto (100%) create mode 100644 NearbyShareFramework/Connection/DataModel.swift create mode 100644 NearbyShareFramework/Connection/InboundNearbyConnection.swift create mode 100644 NearbyShareFramework/Connection/NearbyConnection.swift create mode 100644 NearbyShareFramework/Connection/NearbyConnectionManager.swift create mode 100644 NearbyShareFramework/Connection/OutboundNearbyConnection.swift create mode 100644 NearbyShareFramework/ProtobufSource/device_to_device_messages.proto create mode 100644 NearbyShareFramework/ProtobufSource/offline_wire_formats.proto create mode 100644 NearbyShareFramework/ProtobufSource/securegcm.proto create mode 100644 NearbyShareFramework/ProtobufSource/securemessage.proto create mode 100644 NearbyShareFramework/ProtobufSource/ukey.proto create mode 100644 NearbyShareFramework/ProtobufSource/wire_format.proto create mode 100644 NearbyShareFramework/Util/ConnectionFailureTracker.swift create mode 100644 NearbyShareFramework/Util/Helpers.swift create mode 100644 NearbyShareFramework/Util/Logger.swift create mode 100644 NearbyShareFramework/Util/SaveFilesManager.swift create mode 100644 NearbyShareFramework/Util/SleepManager.swift diff --git a/NearbyShare/Connection/DataModel.swift b/NearbyShare/Model/DataModel.swift similarity index 100% rename from NearbyShare/Connection/DataModel.swift rename to NearbyShare/Model/DataModel.swift diff --git a/NearbyShare/Protobuf/device_to_device_messages.pb.swift b/NearbyShare/Model/Protobuf/device_to_device_messages.pb.swift similarity index 100% rename from NearbyShare/Protobuf/device_to_device_messages.pb.swift rename to NearbyShare/Model/Protobuf/device_to_device_messages.pb.swift diff --git a/NearbyShare/Protobuf/offline_wire_formats.pb.swift b/NearbyShare/Model/Protobuf/offline_wire_formats.pb.swift similarity index 100% rename from NearbyShare/Protobuf/offline_wire_formats.pb.swift rename to NearbyShare/Model/Protobuf/offline_wire_formats.pb.swift diff --git a/NearbyShare/Protobuf/securegcm.pb.swift b/NearbyShare/Model/Protobuf/securegcm.pb.swift similarity index 100% rename from NearbyShare/Protobuf/securegcm.pb.swift rename to NearbyShare/Model/Protobuf/securegcm.pb.swift diff --git a/NearbyShare/Protobuf/securemessage.pb.swift b/NearbyShare/Model/Protobuf/securemessage.pb.swift similarity index 100% rename from NearbyShare/Protobuf/securemessage.pb.swift rename to NearbyShare/Model/Protobuf/securemessage.pb.swift diff --git a/NearbyShare/Protobuf/ukey.pb.swift b/NearbyShare/Model/Protobuf/ukey.pb.swift similarity index 100% rename from NearbyShare/Protobuf/ukey.pb.swift rename to NearbyShare/Model/Protobuf/ukey.pb.swift diff --git a/NearbyShare/Protobuf/wire_format.pb.swift b/NearbyShare/Model/Protobuf/wire_format.pb.swift similarity index 100% rename from NearbyShare/Protobuf/wire_format.pb.swift rename to NearbyShare/Model/Protobuf/wire_format.pb.swift diff --git a/NearbyShare/ProtobufSource/device_to_device_messages.proto b/NearbyShare/Model/ProtobufSource/device_to_device_messages.proto similarity index 100% rename from NearbyShare/ProtobufSource/device_to_device_messages.proto rename to NearbyShare/Model/ProtobufSource/device_to_device_messages.proto diff --git a/NearbyShare/ProtobufSource/offline_wire_formats.proto b/NearbyShare/Model/ProtobufSource/offline_wire_formats.proto similarity index 100% rename from NearbyShare/ProtobufSource/offline_wire_formats.proto rename to NearbyShare/Model/ProtobufSource/offline_wire_formats.proto diff --git a/NearbyShare/ProtobufSource/securegcm.proto b/NearbyShare/Model/ProtobufSource/securegcm.proto similarity index 100% rename from NearbyShare/ProtobufSource/securegcm.proto rename to NearbyShare/Model/ProtobufSource/securegcm.proto diff --git a/NearbyShare/ProtobufSource/securemessage.proto b/NearbyShare/Model/ProtobufSource/securemessage.proto similarity index 100% rename from NearbyShare/ProtobufSource/securemessage.proto rename to NearbyShare/Model/ProtobufSource/securemessage.proto diff --git a/NearbyShare/ProtobufSource/ukey.proto b/NearbyShare/Model/ProtobufSource/ukey.proto similarity index 100% rename from NearbyShare/ProtobufSource/ukey.proto rename to NearbyShare/Model/ProtobufSource/ukey.proto diff --git a/NearbyShare/ProtobufSource/wire_format.proto b/NearbyShare/Model/ProtobufSource/wire_format.proto similarity index 100% rename from NearbyShare/ProtobufSource/wire_format.proto rename to NearbyShare/Model/ProtobufSource/wire_format.proto diff --git a/NearbyShareFramework/Connection/DataModel.swift b/NearbyShareFramework/Connection/DataModel.swift new file mode 100644 index 0000000..1d0927f --- /dev/null +++ b/NearbyShareFramework/Connection/DataModel.swift @@ -0,0 +1,188 @@ +// +// DataModel.swift +// QuickDrop +// +// Created by Leon Böttger on 26.07.25. +// + +import Foundation +import Network + + +protocol InboundNearbyConnectionDelegate { + func obtainUserConsent(for transfer: TransferMetadata, from device: RemoteDeviceInfo, connection: InboundNearbyConnection) + func connectionWasTerminated(connection: InboundNearbyConnection, error: Error?) +} + + +protocol OutboundNearbyConnectionDelegate { + func outboundConnectionWasEstablished(connection: OutboundNearbyConnection) + func outboundConnection(connection: OutboundNearbyConnection, transferProgress: Double) + func outboundConnectionTransferAccepted(connection: OutboundNearbyConnection) + func outboundConnection(connection: OutboundNearbyConnection, failedWithError: Error) + func outboundConnectionTransferFinished(connection: OutboundNearbyConnection) +} + + +public protocol MainAppDelegate { + func obtainUserConsent(for transfer: TransferMetadata, from device: RemoteDeviceInfo) + func incomingTransfer(id: String, didFinishWith error: Error?) + func showFirewallAlert() + func showCopiedToClipboardAlert() + func showUnsupportedFileAlert(for: RemoteDeviceInfo?) +} + + +public protocol ShareExtensionDelegate: AnyObject { + func addDevice(device: RemoteDeviceInfo) + func removeDevice(id: String) + func connectionWasEstablished(pinCode: String) + func connectionFailed(with error: Error) + func transferAccepted() + func transferProgress(progress: Double) + func transferFinished() +} + + +public struct RemoteDeviceInfo { + public let name: String + public let type: DeviceType + public var id: String? + + init(name: String, type: DeviceType, id: String? = nil) { + self.name = name + self.type = type + self.id = id + } + + init(info: EndpointInfo) { + name = info.name + type = info.deviceType + } + + + public enum DeviceType: Int32 { + case unknown = 0 + case phone + case tablet + case computer + + public static func fromRawValue(value: Int) -> DeviceType { + switch value { + case 0: + return .unknown + case 1: + return .phone + case 2: + return .tablet + case 3: + return .computer + default: + return .unknown + } + } + } +} + + +public struct TransferMetadata { + public let files: [FileMetadata] + public let id: String + public let pinCode: String? + public let textDescription: String? + public let type: TransferType + + init(files: [FileMetadata], id: String, pinCode: String?, textDescription: String? = nil, transferType: TransferType = .file) { + self.files = files + self.id = id + self.pinCode = pinCode + self.textDescription = textDescription + self.type = transferType + } + + public enum TransferType { + case file + case text + case url + } +} + + +public struct FileMetadata { + public let name: String + public let size: Int64 + public let mimeType: String +} + + +struct EndpointInfo { + let name: String + let deviceType: RemoteDeviceInfo.DeviceType + + + init(name: String, deviceType: RemoteDeviceInfo.DeviceType) { + self.name = name + self.deviceType = deviceType + } + + + init?(data: Data) { + guard data.count > 17 else { return nil } + let deviceNameLength = Int(data[17]) + guard data.count >= deviceNameLength + 18 else { return nil } + guard let deviceName = String(data: data[18 ..< (18 + deviceNameLength)], encoding: .utf8) else { return nil } + let rawDeviceType = Int(data[0] & 7) >> 1 + name = deviceName + deviceType = RemoteDeviceInfo.DeviceType.fromRawValue(value: rawDeviceType) + } + + + func serialize() -> Data { + // 1 byte: Version(3 bits)|Visibility(1 bit)|Device Type(3 bits)|Reserved(1 bits) + // Device types: unknown=0, phone=1, tablet=2, laptop=3 + var endpointInfo: [UInt8] = [UInt8(deviceType.rawValue << 1)] + // 16 bytes: unknown random bytes + for _ in 0 ... 15 { + endpointInfo.append(UInt8.random(in: 0 ... 255)) + } + // Device name in UTF-8 prefixed with 1-byte length + var nameChars = [UInt8](name.utf8) + if nameChars.count > 255 { + nameChars = [UInt8](nameChars[0 ..< 255]) + } + endpointInfo.append(UInt8(nameChars.count)) + for ch in nameChars { + endpointInfo.append(UInt8(ch)) + } + return Data(endpointInfo) + } +} + + +public enum NearbyError: Error { + case protocolError(_ message: String) + case requiredFieldMissing(_ message: String) + case ukey2 + case packetFilterError + case inputOutput + case canceled(reason: CancellationReason) + + public enum CancellationReason { + case userRejected, userCanceled, notEnoughSpace, unsupportedType, timedOut + + public func localizedDescription() -> String { + switch self { + case .userRejected: + "TransferDeclined".localized() + case .userCanceled: + "TransferCanceled".localized() + case .notEnoughSpace: + "NotEnoughSpace".localized() + case .unsupportedType: + "UnsupportedType".localized() + case .timedOut: + "TransferTimedOut".localized() + } + } + } +} diff --git a/NearbyShareFramework/Connection/InboundNearbyConnection.swift b/NearbyShareFramework/Connection/InboundNearbyConnection.swift new file mode 100644 index 0000000..dd3c832 --- /dev/null +++ b/NearbyShareFramework/Connection/InboundNearbyConnection.swift @@ -0,0 +1,534 @@ +// +// InboundNearbyConnection.swift +// QuickDrop +// +// Created by Grishka on 08.04.2023. +// + +import AppKit +import CommonCrypto +import CryptoKit +import Foundation +import Network +import System +import BigInt +import SwiftECC + +class InboundNearbyConnection: NearbyConnection { + + private var filesToBeReceived: [Int64: InternalFileInfo] = [:] + private var currentState: State = .initial + private var cipherCommitment: Data? + private var textPayloadID: Int64 = 0 + private var isPlainTextTransfer = false + + public var wasRejected = false + public var delegate: InboundNearbyConnectionDelegate? + + enum State { + case initial, receivedConnectionRequest, sentUkeyServerInit, receivedUkeyClientFinish, sentConnectionResponse, sentPairedKeyResult, receivedPairedKeyResult, waitingForUserConsent, receivingFiles, disconnected + } + + + override init(connection: NWConnection, id: String) { + super.init(connection: connection, id: id) + } + + + override func disconnect() { + super.disconnect() + currentState = .disconnected + deletePartiallyReceivedFiles() + + DispatchQueue.main.async { + self.delegate?.connectionWasTerminated(connection: self, error: self.lastError) + + SaveFilesManager.shared.movePendingFilesToTarget() + SaveFilesManager.shared.stopAccessingSecurityScopedResource() + } + } + + + override func processReceivedFrame(frameData: Data) { + + if currentState != .receivingFiles { + log("[InboundNearbyConnection] Received frame in state \(currentState)...") + } + + do { + switch currentState { + case .initial: + let frame = try Location_Nearby_Connections_OfflineFrame(serializedBytes: frameData) + try processConnectionRequestFrame(frame) + case .receivedConnectionRequest: + let msg = try Securegcm_Ukey2Message(serializedBytes: frameData) + ukeyClientInitMsgData = frameData + try processUkey2ClientInit(msg) + case .sentUkeyServerInit: + let msg = try Securegcm_Ukey2Message(serializedBytes: frameData) + try processUkey2ClientFinish(msg, raw: frameData) + case .receivedUkeyClientFinish: + let frame = try Location_Nearby_Connections_OfflineFrame(serializedBytes: frameData) + try processConnectionResponseFrame(frame) + default: + + var smsg: Securemessage_SecureMessage? = nil + + do { + smsg = try Securemessage_SecureMessage(serializedBytes: frameData) + } catch { + log("[InboundNearbyConnection] Error deserializing secure message (probably due to packet filter)") + lastError = NearbyError.packetFilterError + protocolError() + } + + if let smsg = smsg { + try decryptAndProcessReceivedSecureMessage(smsg) + } + } + } catch { + lastError = error + log("[InboundNearbyConnection] Error receiving frame: \(error) in state \(currentState).") + protocolError() + } + } + + + override func processTransferSetupFrame(_ frame: Sharing_Nearby_Frame) throws { + if frame.hasV1 && frame.v1.hasType, case .cancel = frame.v1.type { + log("[InboundNearbyConnection] Transfer canceled") + try sendDisconnectionAndDisconnect() + return + } + switch currentState { + case .sentConnectionResponse: + try processPairedKeyEncryptionFrame(frame) + case .sentPairedKeyResult: + try processPairedKeyResultFrame(frame) + case .receivedPairedKeyResult: + try processIntroductionFrame(frame) + default: + log("[InboundNearbyConnection] Unexpected connection state in processTransferSetupFrame: \(currentState)") + log(frame.debugDescription) + } + } + + + override func isServer() -> Bool { + return true + } + + + override func processFileChunk(frame: Location_Nearby_Connections_PayloadTransferFrame) throws { + + let id = frame.payloadHeader.id + + guard let fileInfo = filesToBeReceived[id] else { throw NearbyError.protocolError("File payload ID \(id) is not known") } + + let currentOffset = fileInfo.bytesTransferred + + guard frame.payloadChunk.offset == currentOffset else { throw NearbyError.protocolError("Invalid offset into file \(frame.payloadChunk.offset), expected \(currentOffset)") } + + guard currentOffset + Int64(frame.payloadChunk.body.count) <= fileInfo.meta.size else { throw NearbyError.protocolError("Transferred file size exceeds previously specified value") } + + if frame.payloadChunk.body.count > 0 { + do { + try fileInfo.fileHandle?.write(contentsOf: frame.payloadChunk.body) + filesToBeReceived[id]!.bytesTransferred += Int64(frame.payloadChunk.body.count) + fileInfo.progress?.completedUnitCount = filesToBeReceived[id]!.bytesTransferred + + // only for logging + self.bytesTransferred += Int64(frame.payloadChunk.body.count) + } catch { + log("[InboundNearbyConnection] Error occurred during writing file: \(error.localizedDescription)") + + throw NearbyError.protocolError(error.localizedDescription) + } + } + else if (frame.payloadChunk.flags & 1) == 1 { + try fileInfo.fileHandle?.close() + filesToBeReceived[id]!.fileHandle = nil + fileInfo.progress?.unpublish() + SaveFilesManager.shared.registerFileFinishedDownloading(fileInfo.destinationURL) + + filesToBeReceived.removeValue(forKey: id) + + if filesToBeReceived.isEmpty { + log("[InboundNearbyConnection] All files received, sending disconnection frame and disconnecting.") + try sendDisconnectionAndDisconnect() + } + } + } + + + override func processBytesPayload(payload: Data, id: Int64) throws -> Bool { + if id == textPayloadID { + if let urlStr = String(data: payload, encoding: .utf8) { + + if isPlainTextTransfer { + // paste to clipboard + let pasteboard = NSPasteboard.general + pasteboard.clearContents() + pasteboard.setString(urlStr, forType: .string) + + NearbyConnectionManager.shared.mainAppDelegate?.showCopiedToClipboardAlert() + } + else if let url = URL(string: urlStr) { + NSWorkspace.shared.open(url) + } + } + + log("[InboundNearbyConnection] Received text payload. Disconnecting...") + try sendDisconnectionAndDisconnect() + return true + } + else if let fileInfo = filesToBeReceived[id] { + fileInfo.fileHandle?.write(payload) + filesToBeReceived[id]!.bytesTransferred += Int64(payload.count) + fileInfo.progress?.completedUnitCount = filesToBeReceived[id]!.bytesTransferred + try fileInfo.fileHandle?.close() + filesToBeReceived[id]!.fileHandle = nil + fileInfo.progress?.unpublish() + filesToBeReceived.removeValue(forKey: id) + SaveFilesManager.shared.registerFileFinishedDownloading(fileInfo.destinationURL) + + log("[InboundNearbyConnection] Received file payload. Disconnecting...") + try sendDisconnectionAndDisconnect() + return true + } + return false + } + + + private func processConnectionRequestFrame(_ frame: Location_Nearby_Connections_OfflineFrame) throws { + + guard frame.hasV1 && frame.v1.hasConnectionRequest && frame.v1.connectionRequest.hasEndpointInfo else { throw NearbyError.requiredFieldMissing("connectionRequest.endpointInfo") } + + guard case .connectionRequest = frame.v1.type else { throw NearbyError.protocolError("Unexpected frame type \(frame.v1.type)") } + + let endpointInfo = frame.v1.connectionRequest.endpointInfo + + guard endpointInfo.count > 17 else { throw NearbyError.protocolError("Endpoint info too short") } + + let deviceNameLength = Int(endpointInfo[17]) + + guard endpointInfo.count >= deviceNameLength + 18 else { throw NearbyError.protocolError("Endpoint info too short to contain the device name") } + + guard let deviceName = String(data: endpointInfo[18 ..< (18 + deviceNameLength)], encoding: .utf8) else { throw NearbyError.protocolError("Device name is not valid UTF-8") } + + let rawDeviceType = Int(endpointInfo[0] & 7) >> 1 + + remoteDeviceInfo = RemoteDeviceInfo(name: deviceName, type: RemoteDeviceInfo.DeviceType.fromRawValue(value: rawDeviceType)) + currentState = .receivedConnectionRequest + } + + + private func processUkey2ClientInit(_ msg: Securegcm_Ukey2Message) throws { + guard msg.hasMessageType, msg.hasMessageData else { throw NearbyError.requiredFieldMissing("clientInit ukey2message.type|data") } + guard case .clientInit = msg.messageType else { + sendUkey2Alert(type: .badMessageType) + log("[InboundNearbyConnection] Unsupported message type: \(msg.messageType)") + throw NearbyError.ukey2 + } + let clientInit: Securegcm_Ukey2ClientInit + do { + clientInit = try Securegcm_Ukey2ClientInit(serializedBytes: msg.messageData) + } catch { + sendUkey2Alert(type: .badMessageData) + log("[InboundNearbyConnection] Failed to parse clientInit: \(error)") + throw NearbyError.ukey2 + } + guard clientInit.version == 1 else { + sendUkey2Alert(type: .badVersion) + log("[InboundNearbyConnection] Unsupported clientInit version: \(clientInit.version)") + throw NearbyError.ukey2 + } + guard clientInit.random.count == 32 else { + sendUkey2Alert(type: .badRandom) + log("[InboundNearbyConnection] Unsupported clientInit random: \(clientInit.random.count)") + throw NearbyError.ukey2 + } + var found = false + for commitment in clientInit.cipherCommitments { + if case .p256Sha512 = commitment.handshakeCipher { + found = true + cipherCommitment = commitment.commitment + break + } + } + guard found else { + sendUkey2Alert(type: .badHandshakeCipher) + log("[InboundNearbyConnection] Unsupported clientInit handshakeCipher: \(clientInit.cipherCommitments)") + throw NearbyError.ukey2 + } + guard clientInit.nextProtocol == "AES_256_CBC-HMAC_SHA256" else { + sendUkey2Alert(type: .badNextProtocol) + log("[InboundNearbyConnection] Unsupported clientInit nextProtocol: \(clientInit.nextProtocol)") + throw NearbyError.ukey2 + } + + let domain = Domain.instance(curve: .EC256r1) + let (pubKey, privKey) = domain.makeKeyPair() + publicKey = pubKey + privateKey = privKey + + var serverInit = Securegcm_Ukey2ServerInit() + serverInit.version = 1 + serverInit.random = Data.randomData(length: 32) + serverInit.handshakeCipher = .p256Sha512 + + var pkey = Securemessage_GenericPublicKey() + pkey.type = .ecP256 + pkey.ecP256PublicKey = Securemessage_EcP256PublicKey() + pkey.ecP256PublicKey.x = Data(pubKey.w.x.asSignedBytes()) + pkey.ecP256PublicKey.y = Data(pubKey.w.y.asSignedBytes()) + serverInit.publicKey = try pkey.serializedData() + + var serverInitMsg = Securegcm_Ukey2Message() + serverInitMsg.messageType = .serverInit + serverInitMsg.messageData = try serverInit.serializedData() + let serverInitData = try serverInitMsg.serializedData() + ukeyServerInitMsgData = serverInitData + sendFrameAsync(serverInitData) + currentState = .sentUkeyServerInit + } + + + private func processUkey2ClientFinish(_ msg: Securegcm_Ukey2Message, raw: Data) throws { + guard msg.hasMessageType, msg.hasMessageData else { throw NearbyError.requiredFieldMissing("clientFinish ukey2message.type|data") } + guard case .clientFinish = msg.messageType else { + log("[InboundNearbyConnection] Unexpected message type \(msg.messageType)") + throw NearbyError.ukey2 + } + + var sha = SHA512() + sha.update(data: raw) + guard cipherCommitment == Data(sha.finalize()) else { + log("[InboundNearbyConnection] Invalid cipherCommitment in clientFinish") + throw NearbyError.ukey2 + } + + let clientFinish = try Securegcm_Ukey2ClientFinished(serializedBytes: msg.messageData) + guard clientFinish.hasPublicKey else { throw NearbyError.requiredFieldMissing("ukey2clientFinish.publicKey") } + let clientKey = try Securemessage_GenericPublicKey(serializedBytes: clientFinish.publicKey) + + try finalizeKeyExchange(peerKey: clientKey) + + currentState = .receivedUkeyClientFinish + } + + + private func processConnectionResponseFrame(_ frame: Location_Nearby_Connections_OfflineFrame) throws { + guard frame.hasV1, frame.v1.hasType else { throw NearbyError.requiredFieldMissing("offlineFrame.v1.type") } + if case .connectionResponse = frame.v1.type { + var resp = Location_Nearby_Connections_OfflineFrame() + resp.version = .v1 + resp.v1 = Location_Nearby_Connections_V1Frame() + resp.v1.type = .connectionResponse + resp.v1.connectionResponse = Location_Nearby_Connections_ConnectionResponseFrame() + resp.v1.connectionResponse.response = .accept + resp.v1.connectionResponse.status = 0 + resp.v1.connectionResponse.osInfo = Location_Nearby_Connections_OsInfo() + resp.v1.connectionResponse.osInfo.type = .apple + try sendFrameAsync(resp.serializedData()) + + encryptionDone = true + + var pairedEncryption = Sharing_Nearby_Frame() + pairedEncryption.version = .v1 + pairedEncryption.v1 = Sharing_Nearby_V1Frame() + pairedEncryption.v1.type = .pairedKeyEncryption + pairedEncryption.v1.pairedKeyEncryption = Sharing_Nearby_PairedKeyEncryptionFrame() + // Presumably used for all the phone number stuff that no one needs anyway + pairedEncryption.v1.pairedKeyEncryption.secretIDHash = Data.randomData(length: 6) + pairedEncryption.v1.pairedKeyEncryption.signedData = Data.randomData(length: 72) + try sendTransferSetupFrame(pairedEncryption) + currentState = .sentConnectionResponse + } else { + log("[InboundNearbyConnection] Unhandled offline frame plaintext: \(frame)") + } + } + + + private func processPairedKeyEncryptionFrame(_ frame: Sharing_Nearby_Frame) throws { + guard frame.hasV1, frame.v1.hasPairedKeyEncryption else { throw NearbyError.requiredFieldMissing("shareNearbyFrame.v1.pairedKeyEncryption") } + var pairedResult = Sharing_Nearby_Frame() + pairedResult.version = .v1 + pairedResult.v1 = Sharing_Nearby_V1Frame() + pairedResult.v1.type = .pairedKeyResult + pairedResult.v1.pairedKeyResult = Sharing_Nearby_PairedKeyResultFrame() + pairedResult.v1.pairedKeyResult.status = .unable + try sendTransferSetupFrame(pairedResult) + currentState = .sentPairedKeyResult + } + + + private func processPairedKeyResultFrame(_ frame: Sharing_Nearby_Frame) throws { + guard frame.hasV1, frame.v1.hasPairedKeyResult else { throw NearbyError.requiredFieldMissing("shareNearbyFrame.v1.pairedKeyResult") } + currentState = .receivedPairedKeyResult + } + + + private func makeFileDestinationURL(_ initialDest: URL) -> URL { + var dest = initialDest + if FileManager.default.fileExists(atPath: dest.path) { + var counter = 1 + var path: String + let ext = dest.pathExtension + let baseUrl = dest.deletingPathExtension() + repeat { + path = "\(baseUrl.path) (\(counter))" + if !ext.isEmpty { + path += ".\(ext)" + } + counter += 1 + } while FileManager.default.fileExists(atPath: path) + dest = URL(fileURLWithPath: path) + } + return dest + } + + + private func processIntroductionFrame(_ frame: Sharing_Nearby_Frame) throws { + guard frame.hasV1, frame.v1.hasIntroduction else { throw NearbyError.requiredFieldMissing("shareNearbyFrame.v1.introduction") } + currentState = .waitingForUserConsent + + if frame.v1.introduction.fileMetadata.count > 0 && frame.v1.introduction.textMetadata.isEmpty { + let saveDirectory = SaveFilesManager.shared.getSaveDirectory() + + for file in frame.v1.introduction.fileMetadata { + let dest = makeFileDestinationURL(saveDirectory.appendingPathComponent(file.name)) + let info = InternalFileInfo(meta: FileMetadata(name: file.name, size: file.size, mimeType: file.mimeType), + payloadID: file.payloadID, + destinationURL: dest) + filesToBeReceived[file.payloadID] = info + } + let metadata = TransferMetadata(files: filesToBeReceived.map { $0.value.meta }, id: id, pinCode: pinCode) + DispatchQueue.main.async { + self.delegate?.obtainUserConsent(for: metadata, from: self.remoteDeviceInfo!, connection: self) + } + } + else if let textMetadata = frame.v1.introduction.textMetadata.first { + + if textMetadata.type == .url || textMetadata.type == .text { + + let isClipboardText = textMetadata.type == .text + + let metadata = TransferMetadata(files: [], id: id, pinCode: pinCode, textDescription: textMetadata.textTitle, transferType: isClipboardText ? .text : .url) + textPayloadID = textMetadata.payloadID + + if isClipboardText{ + isPlainTextTransfer = true + } + + DispatchQueue.main.async { + self.delegate?.obtainUserConsent(for: metadata, from: self.remoteDeviceInfo!, connection: self) + } + } + else { + rejectDueToUnsupportedFileType(frame) + } + } else { + rejectDueToUnsupportedFileType(frame) + } + } + + + func rejectDueToUnsupportedFileType(_ frame: Sharing_Nearby_Frame) { + + log("[InboundNearbyConnection] Rejecting transfer due to unsupported file type. Frame is \(frame.debugDescription)") + + NearbyConnectionManager.shared.mainAppDelegate?.showUnsupportedFileAlert(for: remoteDeviceInfo) + rejectTransfer(with: .unsupportedAttachmentType) + } + + + func submitUserConsent(accepted: Bool, storeInTemp: Bool = false) { + DispatchQueue.global(qos: .utility).async { + if accepted { + self.acceptTransfer(storeInTemp: storeInTemp) + } else { + self.rejectTransfer() + } + } + } + + + private func acceptTransfer(storeInTemp: Bool) { + if currentState == .disconnected { + log("[InboundNearbyConnection] Detected timeout, not accepting transfer") + return + } + + do { + if storeInTemp { + try FileManager.default.createDirectory(at: SaveFilesManager.shared.tempDirectory, withIntermediateDirectories: true) + } + + for (id, file) in filesToBeReceived { + let targetURL = storeInTemp ? SaveFilesManager.shared.tempDirectory.appendingPathComponent(file.destinationURL.lastPathComponent) : file.destinationURL + + FileManager.default.createFile(atPath: targetURL.path, contents: nil) + let handle = try FileHandle(forWritingTo: targetURL) + filesToBeReceived[id]!.fileHandle = handle + + let progress = Progress() + progress.fileURL = targetURL + progress.totalUnitCount = file.meta.size + progress.kind = .file + progress.isPausable = false + progress.publish() + filesToBeReceived[id]!.progress = progress + filesToBeReceived[id]!.created = true + } + + var frame = Sharing_Nearby_Frame() + frame.version = .v1 + frame.v1.type = .response + frame.v1.connectionResponse.status = .accept + currentState = .receivingFiles + try sendTransferSetupFrame(frame) + } catch { + lastError = error + protocolError() + } + } + + + private func rejectTransfer(with reason: Sharing_Nearby_ConnectionResponseFrame.Status = .reject) { + + self.wasRejected = true + + log("[InboundNearbyConnection] Rejecting transfer because of \( reason)") + + var frame = Sharing_Nearby_Frame() + frame.version = .v1 + frame.v1.type = .response + frame.v1.connectionResponse.status = reason + do { + try sendTransferSetupFrame(frame) + try sendDisconnectionAndDisconnect() + } catch { + log("[InboundNearbyConnection] Error \(error)") + protocolError() + } + } + + + private func deletePartiallyReceivedFiles() { + for (_, file) in filesToBeReceived { + + if let progress = file.progress { + progress.unpublish() + } + + guard file.created else { continue } + do { + try FileManager.default.removeItem(at: file.destinationURL) + } + catch { + // if it fails, we don't care. Could be because file was not created yet + } + } + } +} diff --git a/NearbyShareFramework/Connection/NearbyConnection.swift b/NearbyShareFramework/Connection/NearbyConnection.swift new file mode 100644 index 0000000..4a60e38 --- /dev/null +++ b/NearbyShareFramework/Connection/NearbyConnection.swift @@ -0,0 +1,592 @@ +// +// NearbyConnection.swift +// QuickDrop +// +// Created by Grishka on 09.04.2023. +// + +import CommonCrypto +import CryptoKit +import Foundation +import Network +import System + +import BigInt +import SwiftECC + +class NearbyConnection { + + private let maxFrameLength = 5 * 1024 * 1024 + private static let dispatchQueue = DispatchQueue(label: "com.leonboettger.quickdrop.queue", qos: .utility) + + let id: String + let connection: NWConnection + + var remoteDeviceInfo: RemoteDeviceInfo? + var encryptionDone: Bool = false + var lastError: Error? + var bytesTransferred: Int64 = 0 + + private var payloadBuffers: [Int64: NSMutableData] = [:] + private var connectionClosed: Bool = false + + private var inactivityTimer: DispatchSourceTimer? + private let timeoutInterval: TimeInterval = 50 + + // UKEY2-related state + var publicKey: ECPublicKey? + var privateKey: ECPrivateKey? + var ukeyClientInitMsgData: Data? + var ukeyServerInitMsgData: Data? + + // SecureMessage encryption keys + var decryptKey: [UInt8]? + var encryptKey: [UInt8]? + var recvHmacKey: SymmetricKey? + var sendHmacKey: SymmetricKey? + + // SecureMessage sequence numbers + private var serverSeq: Int32 = 0 + private var clientSeq: Int32 = 0 + + private(set) var pinCode: String? + + + init(connection: NWConnection, id: String) { + self.connection = connection + self.id = id + } + + + func start() { + connection.stateUpdateHandler = { state in + + if !self.connectionClosed { + if case .ready = state { + self.connectionReady() + self.receiveFrameAsync() + } else if case let .failed(err) = state { + + // If connection reset by peer, it could still be a valid file transfer that has not been processed yet. => Wait + if err == .posix(.ECONNRESET) { + NearbyConnection.dispatchQueue.asyncAfter(deadline: .now() + 0.5) { + + // If already closed, ignore the error, as file transfer has been processed + if !self.connectionClosed { + recordErrorAndDisconnect(err: err) + } + } + } + else { + recordErrorAndDisconnect(err: err) + } + } + } + else { + log("[NearbyConnection] Connection already closed, ignoring state update: \(state)") + } + } + + connection.start(queue: NearbyConnection.dispatchQueue) + + func recordErrorAndDisconnect(err: NWError) { + self.lastError = err + log("[NearbyConnection] Connection Error: \(err)") + + // If the error is a connection reset, it could be due to firewall issues. + if err == .posix(.ENOTCONN) { + ConnectionFailureTracker.shared.recordFailure { + NearbyConnectionManager.shared.mainAppDelegate?.showFirewallAlert() + } + } + + if err == .posix(.ENETDOWN) { + self.lastError = NearbyError.protocolError("Error.NetworkDown".localized()) + } + + self.disconnect() + } + } + + + func connectionReady() {} + + + func protocolError() { + log("[NearbyConnection] Protocol error: \(String(describing: lastError))") + disconnect() + } + + + func processReceivedFrame(frameData _: Data) { + fatalError() + } + + + func processTransferSetupFrame(_: Sharing_Nearby_Frame) throws { + fatalError() + } + + + func isServer() -> Bool { + fatalError() + } + + + func processFileChunk(frame _: Location_Nearby_Connections_PayloadTransferFrame) throws { + protocolError() + } + + + func processBytesPayload(payload _: Data, id _: Int64) throws -> Bool { + return false + } + + + private func receiveFrameAsync() { + connection.receive(minimumIncompleteLength: 4, maximumLength: 4) { content, _, isComplete, error in + if self.connectionClosed { + return + } + if isComplete { + log("[NearbyConnection] Connection closed by peer during receiveFrameAsync") + self.lastError = NearbyError.protocolError("Error.ClosedByPeer".localized()) + self.disconnect() + return + } + if !(error == nil) { + self.lastError = error + self.protocolError() + return + } + guard let content = content else { + assertionFailure() + return + } + let frameLength = UInt32(content[0]) << 24 | UInt32(content[1]) << 16 | UInt32(content[2]) << 8 | UInt32(content[3]) + guard frameLength < self.maxFrameLength else { + self.lastError = NearbyError.protocolError("Unexpected packet length") + self.protocolError() + return + } + + self.startInactivityTimer() + self.receiveFrameAsync(length: frameLength) + } + } + + + private func receiveFrameAsync(length: UInt32) { + connection.receive(minimumIncompleteLength: Int(length), maximumLength: Int(length)) { content, _, isComplete, _ in + if self.connectionClosed { + return + } + if isComplete { + log("[NearbyConnection] Connection closed by peer during receiveFrameAsync") + self.lastError = NearbyError.protocolError("Error.ClosedByPeer".localized()) + self.disconnect() + return + } + guard let content = content else { + self.protocolError() + return + } + + self.startInactivityTimer() + self.processReceivedFrame(frameData: content) + self.receiveFrameAsync() + } + } + + + func sendFrameAsync(_ frame: Data, completion: (() -> Void)? = nil) { + if connectionClosed { + return + } + var lengthPrefixedData = Data(capacity: frame.count + 4) + let length: Int = frame.count + lengthPrefixedData.append(contentsOf: [ + UInt8(truncatingIfNeeded: length >> 24), + UInt8(truncatingIfNeeded: length >> 16), + UInt8(truncatingIfNeeded: length >> 8), + UInt8(truncatingIfNeeded: length), + ]) + lengthPrefixedData.append(frame) + connection.send(content: lengthPrefixedData, completion: .contentProcessed { _ in + if let completion = completion { + completion() + } + }) + } + + + func encryptAndSendOfflineFrame(_ frame: Location_Nearby_Connections_OfflineFrame, completion: (() -> Void)? = nil) throws { + var d2dMsg = Securegcm_DeviceToDeviceMessage() + serverSeq += 1 + d2dMsg.sequenceNumber = serverSeq + d2dMsg.message = try frame.serializedData() + + let serializedMsg = try [UInt8](d2dMsg.serializedData()) + let iv = Data.randomData(length: 16) + var encryptedData = Data(count: serializedMsg.count + 16) + var encryptedLength: size_t = 0 + encryptedData.withUnsafeMutableBytes { + let status = CCCrypt( + CCOperation(kCCEncrypt), + CCAlgorithm(kCCAlgorithmAES128), + CCOptions(kCCOptionPKCS7Padding), + encryptKey, kCCKeySizeAES256, + [UInt8](iv), + serializedMsg, serializedMsg.count, + $0.baseAddress, $0.count, + &encryptedLength + ) + guard status == kCCSuccess else { fatalError("CCCrypt error: \(status)") } + } + + var hb = Securemessage_HeaderAndBody() + hb.body = encryptedData.prefix(encryptedLength) + hb.header = Securemessage_Header() + hb.header.encryptionScheme = .aes256Cbc + hb.header.signatureScheme = .hmacSha256 + hb.header.iv = iv + var md = Securegcm_GcmMetadata() + md.type = .deviceToDeviceMessage + md.version = 1 + hb.header.publicMetadata = try md.serializedData() + + var smsg = Securemessage_SecureMessage() + smsg.headerAndBody = try hb.serializedData() + smsg.signature = Data(HMAC.authenticationCode(for: smsg.headerAndBody, using: sendHmacKey!)) + try sendFrameAsync(smsg.serializedData(), completion: completion) + } + + + func sendTransferSetupFrame(_ frame: Sharing_Nearby_Frame) throws { + log("[NearbyConnection] Sending transfer setup frame.") + try sendBytesPayload(data: frame.serializedData(), id: Int64.random(in: Int64.min ... Int64.max)) + } + + + func sendBytesPayload(data: Data, id: Int64) throws { + + var transfer = Location_Nearby_Connections_PayloadTransferFrame() + transfer.packetType = .data + transfer.payloadChunk.offset = 0 + transfer.payloadChunk.flags = 0 + transfer.payloadChunk.body = data + transfer.payloadHeader.id = id + transfer.payloadHeader.type = .bytes + transfer.payloadHeader.totalSize = Int64(transfer.payloadChunk.body.count) + transfer.payloadHeader.isSensitive = false + + var wrapper = Location_Nearby_Connections_OfflineFrame() + wrapper.version = .v1 + wrapper.v1 = Location_Nearby_Connections_V1Frame() + wrapper.v1.type = .payloadTransfer + wrapper.v1.payloadTransfer = transfer + try encryptAndSendOfflineFrame(wrapper) + + transfer.payloadChunk.flags = 1 // .lastChunk + transfer.payloadChunk.offset = Int64(transfer.payloadChunk.body.count) + transfer.payloadChunk.clearBody() + wrapper.v1.payloadTransfer = transfer + try encryptAndSendOfflineFrame(wrapper) + } + + + func decryptAndProcessReceivedSecureMessage(_ smsg: Securemessage_SecureMessage) throws { + guard smsg.hasSignature, smsg.hasHeaderAndBody else { throw NearbyError.requiredFieldMissing("secureMessage.signature|headerAndBody") } + + let hmac = Data(HMAC.authenticationCode(for: smsg.headerAndBody, using: recvHmacKey!)) + guard hmac == smsg.signature else { throw NearbyError.protocolError("hmac!=signature: Expected \(hmac), got \(smsg.signature)") } + + let headerAndBody = try Securemessage_HeaderAndBody(serializedBytes: smsg.headerAndBody) + var decryptedData = Data(count: headerAndBody.body.count) + + var decryptedLength = 0 + decryptedData.withUnsafeMutableBytes { + let status = CCCrypt( + CCOperation(kCCDecrypt), + CCAlgorithm(kCCAlgorithmAES128), + CCOptions(kCCOptionPKCS7Padding), + decryptKey, kCCKeySizeAES256, + [UInt8](headerAndBody.header.iv), + [UInt8](headerAndBody.body), headerAndBody.body.count, + $0.baseAddress, $0.count, + &decryptedLength + ) + guard status == kCCSuccess else { fatalError("CCCrypt error: \(status)") } + } + + decryptedData = decryptedData.prefix(decryptedLength) + let d2dMsg = try Securegcm_DeviceToDeviceMessage(serializedBytes: decryptedData) + + guard d2dMsg.hasMessage, d2dMsg.hasSequenceNumber else { throw NearbyError.requiredFieldMissing("d2dMessage.message|sequenceNumber") } + clientSeq += 1 + + guard d2dMsg.sequenceNumber == clientSeq else { throw NearbyError.protocolError("Wrong sequence number. Expected \(clientSeq), got \(d2dMsg.sequenceNumber)") } + let offlineFrame = try Location_Nearby_Connections_OfflineFrame(serializedBytes: d2dMsg.message) + + if offlineFrame.hasV1, offlineFrame.v1.hasType, case .payloadTransfer = offlineFrame.v1.type { + + guard offlineFrame.v1.hasPayloadTransfer else { throw NearbyError.requiredFieldMissing("offlineFrame.v1.payloadTransfer") } + + let payloadTransfer = offlineFrame.v1.payloadTransfer + let header = payloadTransfer.payloadHeader + let chunk = payloadTransfer.payloadChunk + + guard header.hasType, header.hasID else { throw NearbyError.requiredFieldMissing("payloadHeader.type|id") } + guard payloadTransfer.hasPayloadChunk, chunk.hasOffset, chunk.hasFlags else { + log("[NearbyConnection] Payload transfer chunk is missing offset or flags, likely canceled.") + throw NearbyError.canceled(reason: .userCanceled) + } + + if case .bytes = header.type { + + let payloadID = header.id + + if header.totalSize > maxFrameLength { + + payloadBuffers.removeValue(forKey: payloadID) + throw NearbyError.protocolError("Payload too large (\(header.totalSize) bytes)") + } + + if payloadBuffers[payloadID] == nil { + + payloadBuffers[payloadID] = NSMutableData(capacity: Int(header.totalSize)) + } + + let buffer = payloadBuffers[payloadID]! + + guard chunk.offset == buffer.count else { + payloadBuffers.removeValue(forKey: payloadID) + throw NearbyError.protocolError("Unexpected chunk offset \(chunk.offset), expected \(buffer.count)") + } + + if chunk.hasBody { + buffer.append(chunk.body) + } + + if (chunk.flags & 1) == 1 { + payloadBuffers.removeValue(forKey: payloadID) + if try !processBytesPayload(payload: Data(buffer), id: payloadID) { + let innerFrame = try Sharing_Nearby_Frame(serializedBytes: buffer as Data) + try processTransferSetupFrame(innerFrame) + } + } + + } else if case .file = header.type { + + try processFileChunk(frame: payloadTransfer) + } + } + else if offlineFrame.hasV1, offlineFrame.v1.hasType, case .keepAlive = offlineFrame.v1.type { + + log("[NearbyConnection] Sent keep-alive, \(self.bytesTransferred) bytes sent") + sendKeepAlive(ack: true) + } else { + + log("[NearbyConnection] Unhandled offline frame encrypted: \(offlineFrame)") + } + } + + + static func pinCodeFromAuthKey(_ key: SymmetricKey) -> String { + var hash = 0 + var multiplier = 1 + let keyBytes: [UInt8] = key.withUnsafeBytes { + [UInt8]($0) + } + + for _byte in keyBytes { + let byte = Int(Int8(bitPattern: _byte)) + hash = (hash + byte * multiplier) % 9973 + multiplier = (multiplier * 31) % 9973 + } + + return String(format: "%04d", abs(hash)) + } + + + static func hkdfExtract(salt: Data, ikm: Data) -> Data { + return HMAC.authenticationCode(for: ikm, using: SymmetricKey(data: salt)).withUnsafeBytes { Data(bytes: $0.baseAddress!, count: $0.count) } + } + + + static func hkdfExpand(prk: Data, info: Data, length: Int) -> Data { + var okm = Data() + var t = Data() + var i = 0 + while okm.count < length { + i = i + 1 + let toDigest = t + info + Data([UInt8(truncatingIfNeeded: i)]) + t = HMAC.authenticationCode(for: toDigest, using: SymmetricKey(data: prk)).withUnsafeBytes { Data(bytes: $0.baseAddress!, count: $0.count) } + okm = okm + t + } + return okm.subdata(in: 0 ..< length) + } + + + static func hkdf(inputKeyMaterial: SymmetricKey, salt: Data, info: Data, outputByteCount: Int) -> SymmetricKey { + return HKDF.deriveKey(inputKeyMaterial: inputKeyMaterial, salt: salt, info: info, outputByteCount: outputByteCount) + } + + + func finalizeKeyExchange(peerKey: Securemessage_GenericPublicKey) throws { + guard peerKey.hasEcP256PublicKey else { throw NearbyError.requiredFieldMissing("peerKey.ecP256PublicKey") } + + let domain = Domain.instance(curve: .EC256r1) + var clientX = peerKey.ecP256PublicKey.x + var clientY = peerKey.ecP256PublicKey.y + if clientX.count > 32 { + clientX = clientX.suffix(32) + } + if clientY.count > 32 { + clientY = clientY.suffix(32) + } + let key = try ECPublicKey(domain: domain, w: Point(BInt(magnitude: [UInt8](clientX)), BInt(magnitude: [UInt8](clientY)))) + + let dhs = try (privateKey?.domain.multiplyPoint(key.w, privateKey!.s).x.asMagnitudeBytes())! + var sha = SHA256() + sha.update(data: dhs) + let derivedSecretKey = Data(sha.finalize()) + + var ukeyInfo = Data() + ukeyInfo.append(ukeyClientInitMsgData!) + ukeyInfo.append(ukeyServerInitMsgData!) + let authString = NearbyConnection.hkdf(inputKeyMaterial: SymmetricKey(data: derivedSecretKey), salt: "UKEY2 v1 auth".data(using: .utf8)!, info: ukeyInfo, outputByteCount: 32) + let nextSecret = NearbyConnection.hkdf(inputKeyMaterial: SymmetricKey(data: derivedSecretKey), salt: "UKEY2 v1 next".data(using: .utf8)!, info: ukeyInfo, outputByteCount: 32) + + pinCode = NearbyConnection.pinCodeFromAuthKey(authString) + + let salt = Data([0x82, 0xAA, 0x55, 0xA0, 0xD3, 0x97, 0xF8, 0x83, 0x46, 0xCA, 0x1C, + 0xEE, 0x8D, 0x39, 0x09, 0xB9, 0x5F, 0x13, 0xFA, 0x7D, 0xEB, 0x1D, + 0x4A, 0xB3, 0x83, 0x76, 0xB8, 0x25, 0x6D, 0xA8, 0x55, 0x10]) + + let d2dClientKey = NearbyConnection.hkdf(inputKeyMaterial: nextSecret, salt: salt, info: "client".data(using: .utf8)!, outputByteCount: 32) + let d2dServerKey = NearbyConnection.hkdf(inputKeyMaterial: nextSecret, salt: salt, info: "server".data(using: .utf8)!, outputByteCount: 32) + + sha = SHA256() + sha.update(data: "SecureMessage".data(using: .utf8)!) + let smsgSalt = Data(sha.finalize()) + + let clientKey = NearbyConnection.hkdf(inputKeyMaterial: d2dClientKey, salt: smsgSalt, info: "ENC:2".data(using: .utf8)!, outputByteCount: 32).withUnsafeBytes { [UInt8]($0) } + let clientHmacKey = NearbyConnection.hkdf(inputKeyMaterial: d2dClientKey, salt: smsgSalt, info: "SIG:1".data(using: .utf8)!, outputByteCount: 32) + let serverKey = NearbyConnection.hkdf(inputKeyMaterial: d2dServerKey, salt: smsgSalt, info: "ENC:2".data(using: .utf8)!, outputByteCount: 32).withUnsafeBytes { [UInt8]($0) } + let serverHmacKey = NearbyConnection.hkdf(inputKeyMaterial: d2dServerKey, salt: smsgSalt, info: "SIG:1".data(using: .utf8)!, outputByteCount: 32) + + if isServer() { + decryptKey = clientKey + recvHmacKey = clientHmacKey + encryptKey = serverKey + sendHmacKey = serverHmacKey + } else { + decryptKey = serverKey + recvHmacKey = serverHmacKey + encryptKey = clientKey + sendHmacKey = clientHmacKey + } + } + + + func disconnect() { + log("[NearbyConnection] Disconnecting from connection.") + + connection.stateUpdateHandler = nil + inactivityTimer?.cancel() + inactivityTimer = nil + connectionClosed = true + connection.send(content: nil, isComplete: true, completion: .contentProcessed { _ in }) + } + + + func sendDisconnectionAndDisconnect() throws { + var offlineFrame = Location_Nearby_Connections_OfflineFrame() + offlineFrame.version = .v1 + offlineFrame.v1.type = .disconnection + offlineFrame.v1.disconnection = Location_Nearby_Connections_DisconnectionFrame() + + if encryptionDone { + try encryptAndSendOfflineFrame(offlineFrame) + } else { + try sendFrameAsync(offlineFrame.serializedData()) + } + + log("[NearbyConnection] Sent disconnection frame during sendDisconnectionAndDisconnect") + disconnect() + } + + + func sendUkey2Alert(type: Securegcm_Ukey2Alert.AlertType) { + var alert = Securegcm_Ukey2Alert() + alert.type = type + var msg = Securegcm_Ukey2Message() + msg.messageType = .alert + msg.messageData = try! alert.serializedData() + sendFrameAsync(try! msg.serializedData()) + + log("[NearbyConnection] Sent UKEY2 alert: \(type)") + disconnect() + } + + + func sendKeepAlive(ack: Bool) { + var offlineFrame = Location_Nearby_Connections_OfflineFrame() + offlineFrame.version = .v1 + offlineFrame.v1.type = .keepAlive + offlineFrame.v1.keepAlive.ack = ack + + do { + if encryptionDone { + try encryptAndSendOfflineFrame(offlineFrame) + } else { + try sendFrameAsync(offlineFrame.serializedData()) + } + } catch { + log("[NearbyConnection] Error sending KEEP_ALIVE: \(error)") + } + } + + + func startInactivityTimer() { + + // Cancel previous timer if any + inactivityTimer?.cancel() + + inactivityTimer = DispatchSource.makeTimerSource(queue: NearbyConnection.dispatchQueue) + inactivityTimer?.schedule(deadline: .now() + timeoutInterval) + inactivityTimer?.setEventHandler { [weak self] in + + guard let self = self else { return } + + if !self.connectionClosed { + log("[NearbyConnection] Connection timeout: No message received in \(self.timeoutInterval) seconds") + self.lastError = NearbyError.canceled(reason: .timedOut) + self.disconnect() + } + } + + inactivityTimer?.resume() + } + + + // -- MARK: - Internal Data Model + + struct InternalFileInfo { + let meta: FileMetadata + let payloadID: Int64 + let destinationURL: URL + var bytesTransferred: Int64 = 0 + var fileHandle: FileHandle? + var progress: Progress? + var created: Bool = false + } +} + +public extension String { + func localized() -> String { + let localizedString = NSLocalizedString(self, comment: "") + return localizedString + } +} diff --git a/NearbyShareFramework/Connection/NearbyConnectionManager.swift b/NearbyShareFramework/Connection/NearbyConnectionManager.swift new file mode 100644 index 0000000..c1f29ee --- /dev/null +++ b/NearbyShareFramework/Connection/NearbyConnectionManager.swift @@ -0,0 +1,323 @@ +// +// NearbyConnectionManager.swift +// QuickDrop +// +// Created by Grishka on 08.04.2023. +// + +import Foundation +import Network +import System + + +public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearbyConnectionDelegate, OutboundNearbyConnectionDelegate { + + private let sleepManager = SleepManager.shared + private var tcpListener: NWListener + private var mdnsServices: [NetService] = [] + private var incomingConnections: [String: InboundNearbyConnection] = [:] + private var foundServices: [String: FoundServiceInfo] = [:] + private var shareExtensionDelegates: [ShareExtensionDelegate] = [] + private var outgoingTransfers: [String: OutgoingTransferInfo] = [:] + private var startedDeviceDiscovery = false + private var browsers: [NWBrowser] = [] + private let serviceTypes = ["_FC9F5ED42C8A._tcp."] + + public let endpointID: [UInt8] = generateEndpointID() + public var mainAppDelegate: (any MainAppDelegate)? + public static let shared = NearbyConnectionManager() + + + override init() { + tcpListener = try! NWListener(using: NWParameters(tls: .none)) + super.init() + } + + + public func becomeVisible() { + startTCPListener() + } + + + private func startTCPListener() { + tcpListener.stateUpdateHandler = { (state: NWListener.State) in + if case .ready = state { + self.initMDNS() + } + } + tcpListener.newConnectionHandler = { (connection: NWConnection) in + let id = UUID().uuidString + let conn = InboundNearbyConnection(connection: connection, id: id) + self.incomingConnections[id] = conn + conn.delegate = self + conn.start() + } + tcpListener.start(queue: .global(qos: .utility)) + } + + + private static func generateEndpointID() -> [UInt8] { + let userDefaultsKey = UserDefaultsKeys.endpointID.rawValue + + // Try to retrieve from UserDefaults + if let savedString = UserDefaults.standard.string(forKey: userDefaultsKey), + let savedData = savedString.data(using: .utf8) + { + return [UInt8](savedData) + } + + // Generate a new random ID + var id: [UInt8] = [] + let alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".compactMap { UInt8($0.asciiValue!) } + for _ in 0 ..< 4 { + id.append(alphabet[Int.random(in: 0 ..< alphabet.count)]) + } + + // Save to UserDefaults as String + let idString = String(bytes: id, encoding: .utf8) ?? "" + UserDefaults.standard.set(idString, forKey: userDefaultsKey) + + return id + } + + + private func initMDNS() { + let nameBytes: [UInt8] = [ + 0x23, // PCP + endpointID[0], endpointID[1], endpointID[2], endpointID[3], + 0xFC, 0x9F, 0x5E, // Service ID hash + 0, 0, + ] + + let name = Data(nameBytes).urlSafeBase64EncodedString() + let endpointInfo = EndpointInfo(name: Host.current().localizedName!, deviceType: .computer) + + let port = Int32(tcpListener.port!.rawValue) + + mdnsServices = serviceTypes.map { serviceType in + let service = NetService(domain: "", type: serviceType, name: name, port: port) + service.delegate = self + service.setTXTRecord(NetService.data(fromTXTRecord: [ + "n": endpointInfo.serialize().urlSafeBase64EncodedString().data(using: .utf8)!, + ])) + service.publish() + return service + } + } + + + func obtainUserConsent(for transfer: TransferMetadata, from device: RemoteDeviceInfo, connection _: InboundNearbyConnection) { + mainAppDelegate?.obtainUserConsent(for: transfer, from: device) + } + + + func connectionWasTerminated(connection: InboundNearbyConnection, error: Error?) { + incomingConnections.removeValue(forKey: connection.id) + + if !connection.wasRejected { + mainAppDelegate?.incomingTransfer(id: connection.id, didFinishWith: error) + } + } + + + public func submitUserConsent(transferID: String, accept: Bool, storeInTemp: Bool = false) { + guard let conn = incomingConnections[transferID] else { return } + + log("[NearbyConnectionManager] Submitting user consent for transfer, accepted: \(accept), store in temp: \(storeInTemp)") + conn.submitUserConsent(accepted: accept, storeInTemp: storeInTemp) + } + + + public func startDeviceDiscovery() { + + if !startedDeviceDiscovery { + startedDeviceDiscovery = true + foundServices.removeAll() + + log("[NearbyConnectionManager] Starting device discovery") + + if browsers.isEmpty { + for type in serviceTypes { + log("[NearbyConnectionManager] Starting browser for type \(type)") + + let browser = NWBrowser(for: .bonjourWithTXTRecord(type: type, domain: nil), using: .tcp) + browser.browseResultsChangedHandler = { _, changes in + for change in changes { + switch change { + case let .added(res): + self.addFoundDevice(service: res) + case let .removed(res): + self.removeFoundDevice(service: res) + default: + log("[NearbyConnectionManager] Ignoring change \(change)") + } + } + } + browser.start(queue: .main) + browsers.append(browser) + } + } + } + } + + + public func stopDeviceDiscovery() { + if startedDeviceDiscovery { + + log("Stopping device discovery") + + for browser in browsers { + browser.cancel() + } + + browsers.removeAll() + startedDeviceDiscovery = false + } + } + + + public func addShareExtensionDelegate(_ delegate: ShareExtensionDelegate) { + shareExtensionDelegates.append(delegate) + for service in foundServices.values { + guard let device = service.device else { continue } + delegate.addDevice(device: device) + } + } + + + public func removeShareExtensionDelegate(_ delegate: ShareExtensionDelegate) { + shareExtensionDelegates.removeAll(where: { $0 === delegate }) + } + + + public func cancelOutgoingTransfer(id: String) { + guard let transfer = outgoingTransfers[id] else { return } + transfer.connection.cancel() + } + + + private func endpointID(for service: NWBrowser.Result) -> String? { + guard case let NWEndpoint.service(name: serviceName, type: _, domain: _, interface: _) = service.endpoint else { return nil } + guard let nameData = Data.dataFromUrlSafeBase64(serviceName) else { return nil } + guard nameData.count >= 10 else { return nil } + let pcp = nameData[0] + guard pcp == 0x23 else { return nil } + let endpointID = String(data: nameData.subdata(in: 1 ..< 5), encoding: .ascii)! + let serviceIDHash = nameData.subdata(in: 5 ..< 8) + guard serviceIDHash == Data([0xFC, 0x9F, 0x5E]) else { return nil } + return endpointID + } + + + private func addFoundDevice(service: NWBrowser.Result) { + log("[NearbyConnectionManager] Found Service \(service)") + for interface in service.interfaces { + if case .loopback = interface.type { + return + } + } + guard let endpointID = endpointID(for: service) else { return } + var foundService = FoundServiceInfo(service: service) + + guard case let NWBrowser.Result.Metadata.bonjour(txtRecord) = service.metadata else { return } + guard let endpointInfoEncoded = txtRecord.dictionary["n"] else { return } + guard let endpointInfo = Data.dataFromUrlSafeBase64(endpointInfoEncoded) else { return } + guard endpointInfo.count >= 19 else { return } + let deviceType = RemoteDeviceInfo.DeviceType.fromRawValue(value: (Int(endpointInfo[0]) >> 1) & 7) + let deviceNameLength = Int(endpointInfo[17]) + guard endpointInfo.count >= deviceNameLength + 17 else { return } + guard let deviceName = String(data: endpointInfo.subdata(in: 18 ..< (18 + deviceNameLength)), encoding: .utf8) else { return } + + let deviceInfo = RemoteDeviceInfo(name: deviceName, type: deviceType, id: endpointID) + foundService.device = deviceInfo + foundServices[endpointID] = foundService + for delegate in shareExtensionDelegates { + delegate.addDevice(device: deviceInfo) + } + } + + + private func removeFoundDevice(service: NWBrowser.Result) { + guard let endpointID = endpointID(for: service) else { return } + guard let _ = foundServices.removeValue(forKey: endpointID) else { return } + for delegate in shareExtensionDelegates { + delegate.removeDevice(id: endpointID) + } + } + + + public func startOutgoingTransfer(deviceID: String, delegate: ShareExtensionDelegate, urls: [URL], textToSend: String?) { + guard let info = foundServices[deviceID] else { return } + let tcp = NWProtocolTCP.Options() + tcp.noDelay = true + let nwconn = NWConnection(to: info.service.endpoint, using: NWParameters(tls: .none, tcp: tcp)) + let conn = OutboundNearbyConnection(connection: nwconn, id: deviceID, urlsToSend: urls, textToSend: textToSend) + conn.delegate = self + let transfer = OutgoingTransferInfo(service: info.service, device: info.device!, connection: conn, delegate: delegate) + outgoingTransfers[deviceID] = transfer + conn.start() + } + + + func outboundConnectionWasEstablished(connection: OutboundNearbyConnection) { + guard let transfer = outgoingTransfers[connection.id] else { return } + DispatchQueue.main.async { + transfer.delegate.connectionWasEstablished(pinCode: connection.pinCode!) + } + } + + + func outboundConnectionTransferAccepted(connection: OutboundNearbyConnection) { + guard let transfer = outgoingTransfers[connection.id] else { return } + DispatchQueue.main.async { + transfer.delegate.transferAccepted() + } + } + + + func outboundConnection(connection: OutboundNearbyConnection, transferProgress: Double) { + guard let transfer = outgoingTransfers[connection.id] else { return } + DispatchQueue.main.async { + transfer.delegate.transferProgress(progress: transferProgress) + } + } + + + func outboundConnection(connection: OutboundNearbyConnection, failedWithError: Error) { + guard let transfer = outgoingTransfers[connection.id] else { return } + DispatchQueue.main.async { + transfer.delegate.connectionFailed(with: failedWithError) + } + outgoingTransfers.removeValue(forKey: connection.id) + } + + + func outboundConnectionTransferFinished(connection: OutboundNearbyConnection) { + guard let transfer = outgoingTransfers[connection.id] else { return } + DispatchQueue.main.async { + transfer.delegate.transferFinished() + } + outgoingTransfers.removeValue(forKey: connection.id) + } + + + public func getActiveConnectionsCount() -> Int { + return incomingConnections.count + outgoingTransfers.count + } + + + // -- MARK: - Internal Data Model + + private struct FoundServiceInfo { + let service: NWBrowser.Result + var device: RemoteDeviceInfo? + } + + + private struct OutgoingTransferInfo { + let service: NWBrowser.Result + let device: RemoteDeviceInfo + let connection: OutboundNearbyConnection + let delegate: ShareExtensionDelegate + } +} diff --git a/NearbyShareFramework/Connection/OutboundNearbyConnection.swift b/NearbyShareFramework/Connection/OutboundNearbyConnection.swift new file mode 100644 index 0000000..7c83375 --- /dev/null +++ b/NearbyShareFramework/Connection/OutboundNearbyConnection.swift @@ -0,0 +1,497 @@ +// +// OutboundNearbyConnection.swift +// NearbyShare +// +// Created by Grishka on 23.09.2023. +// + +import CommonCrypto +import CryptoKit +import Foundation +import Network +import System +import UniformTypeIdentifiers +import BigInt +import SwiftECC + + +class OutboundNearbyConnection: NearbyConnection { + private var currentState: State = .initial + private let urlsToSend: [URL] + private let textToSend: String? + private var ukeyClientFinishMsgData: Data? + private var queue: [OutgoingFileTransfer] = [] + private var currentTransfer: OutgoingFileTransfer? + private var totalBytesToSend: Int64 = 0 + private var totalBytesSent: Int64 = 0 + private var cancelled: Bool = false + private var textPayloadID: Int64 = 0 + + public var delegate: OutboundNearbyConnectionDelegate? + + enum State { + case initial, sentUkeyClientInit, sentUkeyClientFinish, sentPairedKeyEncryption, sentPairedKeyResult, sentIntroduction, sendingFiles + } + + init(connection: NWConnection, id: String, urlsToSend: [URL], textToSend: String?) { + + self.urlsToSend = urlsToSend + self.textToSend = textToSend + + super.init(connection: connection, id: id) + + if hasURL() || textToSend != nil { + textPayloadID = Int64.random(in: Int64.min ... Int64.max) + } + } + + + deinit { + if let transfer = currentTransfer, let handle = transfer.handle { + try? handle.close() + } + for transfer in queue { + if let handle = transfer.handle { + try? handle.close() + } + } + } + + + override func disconnect() { + super.disconnect() + + if let error = lastError { + DispatchQueue.main.async { + self.delegate?.outboundConnection(connection: self, failedWithError: error) + } + } + } + + + public func cancel() { + cancelled = true + if encryptionDone { + var cancel = Sharing_Nearby_Frame() + cancel.version = .v1 + cancel.v1 = Sharing_Nearby_V1Frame() + cancel.v1.type = .cancel + try? sendTransferSetupFrame(cancel) + } + try? sendDisconnectionAndDisconnect() + } + + + override func connectionReady() { + super.connectionReady() + do { + try sendConnectionRequest() + try sendUkey2ClientInit() + } catch { + lastError = error + protocolError() + } + } + + + override func isServer() -> Bool { + return false + } + + + override func processReceivedFrame(frameData: Data) { + + if currentState != .sendingFiles { + log("[OutboundNearbyConnection] Received frame in state \(currentState)...") + } + + do { + switch currentState { + case .initial: + protocolError() + case .sentUkeyClientInit: + try processUkey2ServerInit(frame: Securegcm_Ukey2Message(serializedBytes: frameData), raw: frameData) + case .sentUkeyClientFinish: + try processConnectionResponse(frame: Location_Nearby_Connections_OfflineFrame(serializedBytes: frameData)) + default: + let smsg = try Securemessage_SecureMessage(serializedBytes: frameData) + try decryptAndProcessReceivedSecureMessage(smsg) + } + } catch { + + log("[OutboundNearbyConnection] Error occured while processing frame with data \(frameData.hex): \(error)") + + if case NearbyError.ukey2 = error { + // do nothing + } + else if currentState == .sentUkeyClientInit { + sendUkey2Alert(type: .badMessage) + } + lastError = error + protocolError() + } + } + + + override func processTransferSetupFrame(_ frame: Sharing_Nearby_Frame) throws { + + if frame.hasV1 && frame.v1.hasType, case .cancel = frame.v1.type { + + log("[OutboundNearbyConnection] Transfer canceled") + try sendDisconnectionAndDisconnect() + delegate?.outboundConnection(connection: self, failedWithError: NearbyError.canceled(reason: .userCanceled)) + return + } + + switch currentState { + case .sentPairedKeyEncryption: + try processPairedKeyEncryption(frame: frame) + case .sentPairedKeyResult: + try processPairedKeyResult(frame: frame) + case .sentIntroduction: + try processConsent(frame: frame) + case .sendingFiles: + break + default: + assertionFailure("[OutboundNearbyConnection] Unexpected state \(currentState)") + } + } + + + override func protocolError() { + super.protocolError() + delegate?.outboundConnection(connection: self, failedWithError: lastError!) + } + + + private func sendConnectionRequest() throws { + var frame = Location_Nearby_Connections_OfflineFrame() + frame.version = .v1 + frame.v1 = Location_Nearby_Connections_V1Frame() + frame.v1.type = .connectionRequest + frame.v1.connectionRequest = Location_Nearby_Connections_ConnectionRequestFrame() + frame.v1.connectionRequest.endpointID = String(bytes: NearbyConnectionManager.shared.endpointID, encoding: .ascii)! + frame.v1.connectionRequest.endpointName = Host.current().localizedName! + let endpointInfo = EndpointInfo(name: Host.current().localizedName!, deviceType: .computer) + frame.v1.connectionRequest.endpointInfo = endpointInfo.serialize() + frame.v1.connectionRequest.mediums = [.wifiLan] + try sendFrameAsync(frame.serializedData()) + } + + + private func sendUkey2ClientInit() throws { + let domain = Domain.instance(curve: .EC256r1) + let (pubKey, privKey) = domain.makeKeyPair() + publicKey = pubKey + privateKey = privKey + + var finishFrame = Securegcm_Ukey2Message() + finishFrame.messageType = .clientFinish + var finish = Securegcm_Ukey2ClientFinished() + var pkey = Securemessage_GenericPublicKey() + pkey.type = .ecP256 + pkey.ecP256PublicKey = Securemessage_EcP256PublicKey() + pkey.ecP256PublicKey.x = Data(pubKey.w.x.asSignedBytes()) + pkey.ecP256PublicKey.y = Data(pubKey.w.y.asSignedBytes()) + finish.publicKey = try pkey.serializedData() + finishFrame.messageData = try finish.serializedData() + ukeyClientFinishMsgData = try finishFrame.serializedData() + + var frame = Securegcm_Ukey2Message() + frame.messageType = .clientInit + + var clientInit = Securegcm_Ukey2ClientInit() + clientInit.version = 1 + clientInit.random = Data.randomData(length: 32) + clientInit.nextProtocol = "AES_256_CBC-HMAC_SHA256" + var sha = SHA512() + sha.update(data: ukeyClientFinishMsgData!) + var commitment = Securegcm_Ukey2ClientInit.CipherCommitment() + commitment.commitment = Data(sha.finalize()) + commitment.handshakeCipher = .p256Sha512 + clientInit.cipherCommitments.append(commitment) + frame.messageData = try clientInit.serializedData() + + ukeyClientInitMsgData = try frame.serializedData() + sendFrameAsync(ukeyClientInitMsgData!) + currentState = .sentUkeyClientInit + } + + + private func processUkey2ServerInit(frame: Securegcm_Ukey2Message, raw: Data) throws { + ukeyServerInitMsgData = raw + guard frame.messageType == .serverInit else { + sendUkey2Alert(type: .badMessageType) + log("[OutboundNearbyConnection] Invalid message type: \(frame.messageType)") + throw NearbyError.ukey2 + } + let serverInit = try Securegcm_Ukey2ServerInit(serializedBytes: frame.messageData) + guard serverInit.version == 1 else { + sendUkey2Alert(type: .badVersion) + log("[OutboundNearbyConnection] Invalid version: \(serverInit.version)") + throw NearbyError.ukey2 + } + guard serverInit.random.count == 32 else { + sendUkey2Alert(type: .badRandom) + log("[OutboundNearbyConnection] Invalid random: \(serverInit.random.count)") + throw NearbyError.ukey2 + } + guard serverInit.handshakeCipher == .p256Sha512 else { + sendUkey2Alert(type: .badHandshakeCipher) + log("[OutboundNearbyConnection] Invalid handshake cipher: \(serverInit.handshakeCipher)") + throw NearbyError.ukey2 + } + + let serverKey = try Securemessage_GenericPublicKey(serializedBytes: serverInit.publicKey) + try finalizeKeyExchange(peerKey: serverKey) + sendFrameAsync(ukeyClientFinishMsgData!) + currentState = .sentUkeyClientFinish + + var resp = Location_Nearby_Connections_OfflineFrame() + resp.version = .v1 + resp.v1 = Location_Nearby_Connections_V1Frame() + resp.v1.type = .connectionResponse + resp.v1.connectionResponse = Location_Nearby_Connections_ConnectionResponseFrame() + resp.v1.connectionResponse.response = .accept + resp.v1.connectionResponse.status = 0 + resp.v1.connectionResponse.osInfo = Location_Nearby_Connections_OsInfo() + resp.v1.connectionResponse.osInfo.type = .apple + try sendFrameAsync(resp.serializedData()) + + encryptionDone = true + delegate?.outboundConnectionWasEstablished(connection: self) + } + + + private func processConnectionResponse(frame: Location_Nearby_Connections_OfflineFrame) throws { + guard frame.version == .v1 else { throw NearbyError.protocolError("Unexpected offline frame version \(frame.version)") } + guard frame.v1.type == .connectionResponse else { throw NearbyError.protocolError("Unexpected frame type \(frame.v1.type)") } + guard frame.v1.connectionResponse.response == .accept else { throw NearbyError.protocolError("Connection was rejected by recipient") } + + var pairedEncryption = Sharing_Nearby_Frame() + pairedEncryption.version = .v1 + pairedEncryption.v1 = Sharing_Nearby_V1Frame() + pairedEncryption.v1.type = .pairedKeyEncryption + pairedEncryption.v1.pairedKeyEncryption = Sharing_Nearby_PairedKeyEncryptionFrame() + pairedEncryption.v1.pairedKeyEncryption.secretIDHash = Data.randomData(length: 6) + pairedEncryption.v1.pairedKeyEncryption.signedData = Data.randomData(length: 72) + try sendTransferSetupFrame(pairedEncryption) + + currentState = .sentPairedKeyEncryption + } + + + private func processPairedKeyEncryption(frame: Sharing_Nearby_Frame) throws { + guard frame.hasV1, frame.v1.hasPairedKeyEncryption else { throw NearbyError.requiredFieldMissing("sharingNearbyFrame.v1.pairedKeyEncryption") } + var pairedResult = Sharing_Nearby_Frame() + pairedResult.version = .v1 + pairedResult.v1 = Sharing_Nearby_V1Frame() + pairedResult.v1.type = .pairedKeyResult + pairedResult.v1.pairedKeyResult = Sharing_Nearby_PairedKeyResultFrame() + pairedResult.v1.pairedKeyResult.status = .unable + try sendTransferSetupFrame(pairedResult) + currentState = .sentPairedKeyResult + } + + + private func processPairedKeyResult(frame: Sharing_Nearby_Frame) throws { + guard frame.hasV1, frame.v1.hasPairedKeyResult else { throw NearbyError.requiredFieldMissing("sharingNearbyFrame.v1.pairedKeyResult") } + + var introduction = Sharing_Nearby_Frame() + introduction.version = .v1 + introduction.v1.type = .introduction + + if let textToSend = textToSend { + var meta = Sharing_Nearby_TextMetadata() + meta.type = .text + meta.textTitle = textToSend + meta.size = Int64(textToSend.utf8.count) + meta.payloadID = textPayloadID + introduction.v1.introduction.textMetadata.append(meta) + + } else if hasURL() { + var meta = Sharing_Nearby_TextMetadata() + meta.type = .url + meta.textTitle = urlsToSend[0].host ?? "URL" + meta.size = Int64(urlsToSend[0].absoluteString.utf8.count) + meta.payloadID = textPayloadID + introduction.v1.introduction.textMetadata.append(meta) + } else { + for url in urlsToSend { + guard url.isFileURL else { continue } + var meta = Sharing_Nearby_FileMetadata() + meta.name = OutboundNearbyConnection.sanitizeFileName(name: url.lastPathComponent) + let attrs = try FileManager.default.attributesOfItem(atPath: url.path) + meta.size = (attrs[FileAttributeKey.size] as! NSNumber).int64Value + let typeID = try? url.resourceValues(forKeys: [.typeIdentifierKey]).typeIdentifier + meta.mimeType = "application/octet-stream" + if let typeID = typeID { + let type = UTType(typeID) + if let type = type, let mimeType = type.preferredMIMEType { + meta.mimeType = mimeType + } + } + if meta.mimeType.starts(with: "image/") { + meta.type = .image + } else if meta.mimeType.starts(with: "video/") { + meta.type = .video + } else if meta.mimeType.starts(with: "audio/") { + meta.type = .audio + } else if url.pathExtension.lowercased() == "apk" { + meta.type = .app + } else { + meta.type = .unknown + } + meta.payloadID = Int64.random(in: Int64.min ... Int64.max) + try queue.append(OutgoingFileTransfer(url: url, payloadID: meta.payloadID, handle: FileHandle(forReadingFrom: url), totalBytes: meta.size, currentOffset: 0)) + introduction.v1.introduction.fileMetadata.append(meta) + totalBytesToSend += meta.size + } + } + log("[OutboundNearbyConnection] Sent introduction: \(introduction)") + try sendTransferSetupFrame(introduction) + + currentState = .sentIntroduction + } + + + private func processConsent(frame: Sharing_Nearby_Frame) throws { + guard frame.version == .v1, frame.v1.type == .response else { throw NearbyError.requiredFieldMissing("sharingNearbyFrame.v1.type==response") } + switch frame.v1.connectionResponse.status { + case .accept: + currentState = .sendingFiles + delegate?.outboundConnectionTransferAccepted(connection: self) + + if let textToSend = textToSend { + try sendText(text: textToSend) + } + if hasURL() { + try sendURL() + } else { + try sendNextFileChunk() + } + case .reject, .unknown: + delegate?.outboundConnection(connection: self, failedWithError: NearbyError.canceled(reason: .userRejected)) + try sendDisconnectionAndDisconnect() + case .notEnoughSpace: + delegate?.outboundConnection(connection: self, failedWithError: NearbyError.canceled(reason: .notEnoughSpace)) + try sendDisconnectionAndDisconnect() + case .timedOut: + delegate?.outboundConnection(connection: self, failedWithError: NearbyError.canceled(reason: .timedOut)) + try sendDisconnectionAndDisconnect() + case .unsupportedAttachmentType: + delegate?.outboundConnection(connection: self, failedWithError: NearbyError.canceled(reason: .unsupportedType)) + try sendDisconnectionAndDisconnect() + } + } + + + private func hasURL() -> Bool { + urlsToSend.count == 1 && !urlsToSend[0].isFileURL + } + + + private func sendURL() throws { + try sendText(text: urlsToSend[0].absoluteString) + } + + + private func sendText(text: String) throws { + try sendBytesPayload(data: Data(text.utf8), id: textPayloadID) + delegate?.outboundConnectionTransferFinished(connection: self) + try sendDisconnectionAndDisconnect() + } + + + private func sendNextFileChunk() throws { + if cancelled { + return + } + if currentTransfer == nil || currentTransfer?.currentOffset == currentTransfer?.totalBytes { + if currentTransfer != nil && currentTransfer?.handle != nil { + try currentTransfer?.handle?.close() + } + if queue.isEmpty { + log("[OutboundNearbyConnection] Disconnecting because all files have been transferred") + try sendDisconnectionAndDisconnect() + delegate?.outboundConnectionTransferFinished(connection: self) + return + } + currentTransfer = queue.removeFirst() + } + + let fileBuffer: Data + guard let _fileBuffer = try currentTransfer!.handle!.read(upToCount: 512 * 1024) else { + throw NearbyError.inputOutput + } + fileBuffer = _fileBuffer + + var transfer = Location_Nearby_Connections_PayloadTransferFrame() + transfer.packetType = .data + transfer.payloadChunk.offset = currentTransfer!.currentOffset + transfer.payloadChunk.flags = 0 + transfer.payloadChunk.body = fileBuffer + transfer.payloadHeader.id = currentTransfer!.payloadID + transfer.payloadHeader.type = .file + transfer.payloadHeader.totalSize = Int64(currentTransfer!.totalBytes) + transfer.payloadHeader.isSensitive = false + currentTransfer!.currentOffset += Int64(fileBuffer.count) + + var wrapper = Location_Nearby_Connections_OfflineFrame() + wrapper.version = .v1 + wrapper.v1 = Location_Nearby_Connections_V1Frame() + wrapper.v1.type = .payloadTransfer + wrapper.v1.payloadTransfer = transfer + try encryptAndSendOfflineFrame(wrapper, completion: { + do { + try self.sendNextFileChunk() + } catch { + self.lastError = error + self.protocolError() + } + }) + totalBytesSent += Int64(fileBuffer.count) + + // only for logging + self.bytesTransferred += Int64(fileBuffer.count) + + startInactivityTimer() + delegate?.outboundConnection(connection: self, transferProgress: Double(totalBytesSent) / Double(totalBytesToSend)) + + if currentTransfer!.currentOffset == currentTransfer!.totalBytes { + // Signal end of file (yes, all this for one bit) + var transfer = Location_Nearby_Connections_PayloadTransferFrame() + transfer.packetType = .data + transfer.payloadChunk.offset = currentTransfer!.currentOffset + transfer.payloadChunk.flags = 1 // <- this one here + transfer.payloadHeader.id = currentTransfer!.payloadID + transfer.payloadHeader.type = .file + transfer.payloadHeader.totalSize = Int64(currentTransfer!.totalBytes) + transfer.payloadHeader.isSensitive = false + + var wrapper = Location_Nearby_Connections_OfflineFrame() + wrapper.version = .v1 + wrapper.v1 = Location_Nearby_Connections_V1Frame() + wrapper.v1.type = .payloadTransfer + wrapper.v1.payloadTransfer = transfer + try encryptAndSendOfflineFrame(wrapper) + log("[OutboundNearbyConnection] Sent EOF, current transfer: \(String(describing: currentTransfer))") + } + } + + + private static func sanitizeFileName(name: String) -> String { + return name.replacingOccurrences(of: "[\\/\\\\?%\\*:\\|\"<>=]", with: "_", options: .regularExpression) + } + + + // -- MARK: - Internal Data Model + + private struct OutgoingFileTransfer { + let url: URL + let payloadID: Int64 + let handle: FileHandle? + let totalBytes: Int64 + var currentOffset: Int64 + } +} diff --git a/NearbyShareFramework/ProtobufSource/device_to_device_messages.proto b/NearbyShareFramework/ProtobufSource/device_to_device_messages.proto new file mode 100644 index 0000000..5600373 --- /dev/null +++ b/NearbyShareFramework/ProtobufSource/device_to_device_messages.proto @@ -0,0 +1,81 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto2"; + +package securegcm; + +import "securemessage.proto"; + +option optimize_for = LITE_RUNTIME; +option java_package = "com.google.security.cryptauth.lib.securegcm"; +option java_outer_classname = "DeviceToDeviceMessagesProto"; +option objc_class_prefix = "SGCM"; + +// Used by protocols between devices +message DeviceToDeviceMessage { + // the payload of the message + optional bytes message = 1; + + // the sequence number of the message - must be increasing. + optional int32 sequence_number = 2; +} + +// sent as the first message from initiator to responder +// in an unauthenticated Diffie-Hellman Key Exchange +message InitiatorHello { + // The session public key to send to the responder + optional securemessage.GenericPublicKey public_dh_key = 1; + + // The protocol version + optional int32 protocol_version = 2 [default = 0]; +} + +// sent inside the header of the first message from the responder to the +// initiator in an unauthenticated Diffie-Hellman Key Exchange +message ResponderHello { + // The session public key to send to the initiator + optional securemessage.GenericPublicKey public_dh_key = 1; + + // The protocol version + optional int32 protocol_version = 2 [default = 0]; +} + +// Type of curve +enum Curve { ED_25519 = 1; } + +// A convenience proto for encoding curve points in affine representation +message EcPoint { + required Curve curve = 1; + + // x and y are encoded in big-endian two's complement + // client MUST verify (x,y) is a valid point on the specified curve + required bytes x = 2; + required bytes y = 3; +} + +message SpakeHandshakeMessage { + // Each flow in the protocol bumps this counter + optional int32 flow_number = 1; + + // Some (but not all) SPAKE flows send a point on an elliptic curve + optional EcPoint ec_point = 2; + + // Some (but not all) SPAKE flows send a hash value + optional bytes hash_value = 3; + + // The last flow of a SPAKE protocol can send an optional payload, + // since the key exchange is already complete on the sender's side. + optional bytes payload = 4; +} diff --git a/NearbyShareFramework/ProtobufSource/offline_wire_formats.proto b/NearbyShareFramework/ProtobufSource/offline_wire_formats.proto new file mode 100644 index 0000000..c112004 --- /dev/null +++ b/NearbyShareFramework/ProtobufSource/offline_wire_formats.proto @@ -0,0 +1,403 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto2"; + +package location.nearby.connections; + +option optimize_for = LITE_RUNTIME; +option java_outer_classname = "OfflineWireFormatsProto"; +option java_package = "com.google.location.nearby.connections.proto"; +option objc_class_prefix = "GNCP"; + +message OfflineFrame { + enum Version { + UNKNOWN_VERSION = 0; + V1 = 1; + } + optional Version version = 1; + + // Right now there's only 1 version, but if there are more, exactly one of + // the following fields will be set. + optional V1Frame v1 = 2; +} + +message V1Frame { + enum FrameType { + UNKNOWN_FRAME_TYPE = 0; + CONNECTION_REQUEST = 1; + CONNECTION_RESPONSE = 2; + PAYLOAD_TRANSFER = 3; + BANDWIDTH_UPGRADE_NEGOTIATION = 4; + KEEP_ALIVE = 5; + DISCONNECTION = 6; + PAIRED_KEY_ENCRYPTION = 7; + } + optional FrameType type = 1; + + // Exactly one of the following fields will be set. + optional ConnectionRequestFrame connection_request = 2; + optional ConnectionResponseFrame connection_response = 3; + optional PayloadTransferFrame payload_transfer = 4; + optional BandwidthUpgradeNegotiationFrame bandwidth_upgrade_negotiation = 5; + optional KeepAliveFrame keep_alive = 6; + optional DisconnectionFrame disconnection = 7; + optional PairedKeyEncryptionFrame paired_key_encryption = 8; +} + +message ConnectionRequestFrame { + // Should always match cs/symbol:location.nearby.proto.connections.Medium + // LINT.IfChange + enum Medium { + UNKNOWN_MEDIUM = 0; + MDNS = 1 [deprecated = true]; + BLUETOOTH = 2; + WIFI_HOTSPOT = 3; + BLE = 4; + WIFI_LAN = 5; + WIFI_AWARE = 6; + NFC = 7; + WIFI_DIRECT = 8; + WEB_RTC = 9; + BLE_L2CAP = 10; + USB = 11; + } + // LINT.ThenChange(//depot/google3/third_party/nearby/proto/connections_enums.proto) + + optional string endpoint_id = 1; + optional string endpoint_name = 2; + optional bytes handshake_data = 3; + // A random number generated for each outgoing connection that is presently + // used to act as a tiebreaker when 2 devices connect to each other + // simultaneously; this can also be used for other initialization-scoped + // things in the future. + optional int32 nonce = 4; + // The mediums this device supports upgrading to. This list should be filtered + // by both the strategy and this device's individual limitations. + repeated Medium mediums = 5; + optional bytes endpoint_info = 6; + optional MediumMetadata medium_metadata = 7; + optional int32 keep_alive_interval_millis = 8; + optional int32 keep_alive_timeout_millis = 9; + // The type of {@link Device} object. + optional int32 device_type = 10 [default = 0]; + // The bytes of serialized {@link Device} object. + optional bytes device_info = 11; +} + +message ConnectionResponseFrame { + // This doesn't need to send back endpoint_id and endpoint_name (like + // the ConnectionRequestFrame does) because those have already been + // transmitted out-of-band, at the time this endpoint was discovered. + + // One of: + // + // - ConnectionsStatusCodes.STATUS_OK + // - ConnectionsStatusCodes.STATUS_CONNECTION_REJECTED. + optional int32 status = 1 [deprecated = true]; + optional bytes handshake_data = 2; + + // Used to replace the status integer parameter with a meaningful enum item. + // Map ConnectionsStatusCodes.STATUS_OK to ACCEPT and + // ConnectionsStatusCodes.STATUS_CONNECTION_REJECTED to REJECT. + // Flag: connection_replace_status_with_response_connectionResponseFrame + enum ResponseStatus { + UNKNOWN_RESPONSE_STATUS = 0; + ACCEPT = 1; + REJECT = 2; + } + optional ResponseStatus response = 3; + optional OsInfo os_info = 4; + // A bitmask value to indicate which medium supports Multiplex transmission + // feature. Each supporting medium could utilize one bit starting from the + // least significant bit in this field. eq. BT utilizes the LSB bit which 0x01 + // means bt supports multiplex while 0x00 means not. Refer to ClientProxy.java + // for the bit usages. + optional int32 multiplex_socket_bitmask = 5; + optional int32 nearby_connections_version = 6; +} + +message PayloadTransferFrame { + enum PacketType { + UNKNOWN_PACKET_TYPE = 0; + DATA = 1; + CONTROL = 2; + } + + message PayloadHeader { + enum PayloadType { + UNKNOWN_PAYLOAD_TYPE = 0; + BYTES = 1; + FILE = 2; + STREAM = 3; + } + optional int64 id = 1; + optional PayloadType type = 2; + optional int64 total_size = 3; + optional bool is_sensitive = 4; + optional string file_name = 5; + optional string parent_folder = 6; + } + + // Accompanies DATA packets. + message PayloadChunk { + enum Flags { + LAST_CHUNK = 0x1; + } + optional int32 flags = 1; + optional int64 offset = 2; + optional bytes body = 3; + } + + // Accompanies CONTROL packets. + message ControlMessage { + enum EventType { + UNKNOWN_EVENT_TYPE = 0; + PAYLOAD_ERROR = 1; + PAYLOAD_CANCELED = 2; + PAYLOAD_RECEIVED_ACK = 3; + } + + optional EventType event = 1; + optional int64 offset = 2; + } + + optional PacketType packet_type = 1; + optional PayloadHeader payload_header = 2; + + // Exactly one of the following fields will be set, depending on the type. + optional PayloadChunk payload_chunk = 3; + optional ControlMessage control_message = 4; +} + +message BandwidthUpgradeNegotiationFrame { + enum EventType { + UNKNOWN_EVENT_TYPE = 0; + UPGRADE_PATH_AVAILABLE = 1; + LAST_WRITE_TO_PRIOR_CHANNEL = 2; + SAFE_TO_CLOSE_PRIOR_CHANNEL = 3; + CLIENT_INTRODUCTION = 4; + UPGRADE_FAILURE = 5; + CLIENT_INTRODUCTION_ACK = 6; + } + + // Accompanies UPGRADE_PATH_AVAILABLE and UPGRADE_FAILURE events. + message UpgradePathInfo { + // Should always match cs/symbol:location.nearby.proto.connections.Medium + enum Medium { + UNKNOWN_MEDIUM = 0; + MDNS = 1 [deprecated = true]; + BLUETOOTH = 2; + WIFI_HOTSPOT = 3; + BLE = 4; + WIFI_LAN = 5; + WIFI_AWARE = 6; + NFC = 7; + WIFI_DIRECT = 8; + WEB_RTC = 9; + // 10 is reserved. + USB = 11; + } + + // Accompanies Medium.WIFI_HOTSPOT. + message WifiHotspotCredentials { + optional string ssid = 1; + optional string password = 2; + optional int32 port = 3; + optional string gateway = 4 [default = "0.0.0.0"]; + // This field can be a band or frequency + optional int32 frequency = 5 [default = -1]; + } + + // Accompanies Medium.WIFI_LAN. + message WifiLanSocket { + optional bytes ip_address = 1; + optional int32 wifi_port = 2; + } + + // Accompanies Medium.BLUETOOTH. + message BluetoothCredentials { + optional string service_name = 1; + optional string mac_address = 2; + } + + // Accompanies Medium.WIFI_AWARE. + message WifiAwareCredentials { + optional string service_id = 1; + optional bytes service_info = 2; + optional string password = 3; + } + + // Accompanies Medium.WIFI_DIRECT. + message WifiDirectCredentials { + optional string ssid = 1; + optional string password = 2; + optional int32 port = 3; + optional int32 frequency = 4; + optional string gateway = 5 [default = "0.0.0.0"]; + } + + // Accompanies Medium.WEB_RTC + message WebRtcCredentials { + optional string peer_id = 1; + optional LocationHint location_hint = 2; + } + + optional Medium medium = 1; + + // Exactly one of the following fields will be set. + optional WifiHotspotCredentials wifi_hotspot_credentials = 2; + optional WifiLanSocket wifi_lan_socket = 3; + optional BluetoothCredentials bluetooth_credentials = 4; + optional WifiAwareCredentials wifi_aware_credentials = 5; + optional WifiDirectCredentials wifi_direct_credentials = 6; + optional WebRtcCredentials web_rtc_credentials = 8; + + // Disable Encryption for this upgrade medium to improve throughput. + optional bool supports_disabling_encryption = 7; + + // An ack will be sent after the CLIENT_INTRODUCTION frame. + optional bool supports_client_introduction_ack = 9; + } + + // Accompanies CLIENT_INTRODUCTION events. + message ClientIntroduction { + optional string endpoint_id = 1; + optional bool supports_disabling_encryption = 2; + } + + // Accompanies CLIENT_INTRODUCTION_ACK events. + message ClientIntroductionAck {} + + optional EventType event_type = 1; + + // Exactly one of the following fields will be set. + optional UpgradePathInfo upgrade_path_info = 2; + optional ClientIntroduction client_introduction = 3; + optional ClientIntroductionAck client_introduction_ack = 4; +} + +message KeepAliveFrame { + // And ack will be sent after receiving KEEP_ALIVE frame. + optional bool ack = 1; +} + +// Informs the remote side to immediately severe the socket connection. +// Used in bandwidth upgrades to get around a race condition, but may be used +// in other situations to trigger a faster disconnection event than waiting for +// socket closed on the remote side. +message DisconnectionFrame { + // Apply safe-to-disconnect protocol if true. + optional bool request_safe_to_disconnect = 1; + + // Ack of receiving Disconnection frame will be sent to the sender + // frame. + optional bool ack_safe_to_disconnect = 2; +} + +// A paired key encryption packet sent between devices, contains signed data. +message PairedKeyEncryptionFrame { + // The encrypted data (raw authentication token for the established + // connection) in byte array format. + optional bytes signed_data = 1; +} + +message MediumMetadata { + // True if local device supports 5GHz. + optional bool supports_5_ghz = 1; + // WiFi LAN BSSID, in the form of a six-byte MAC address: XX:XX:XX:XX:XX:XX + optional string bssid = 2; + // IP address, in network byte order: the highest order byte of the address is + // in byte[0]. + optional bytes ip_address = 3; + // True if local device supports 6GHz. + optional bool supports_6_ghz = 4; + // True if local device has mobile radio. + optional bool mobile_radio = 5; + // The frequency of the WiFi LAN AP(in MHz). Or -1 is not associated with an + // AP over WiFi, -2 represents the active network uses an Ethernet transport. + optional int32 ap_frequency = 6 [default = -1]; + // Available channels on the local device. + optional AvailableChannels available_channels = 7; + // Usable WiFi Direct client channels on the local device. + optional WifiDirectCliUsableChannels wifi_direct_cli_usable_channels = 8; + // Usable WiFi LAN channels on the local device. + optional WifiLanUsableChannels wifi_lan_usable_channels = 9; + // Usable WiFi Aware channels on the local device. + optional WifiAwareUsableChannels wifi_aware_usable_channels = 10; + // Usable WiFi Hotspot STA channels on the local device. + optional WifiHotspotStaUsableChannels wifi_hotspot_sta_usable_channels = 11; +} + +// Available channels on the local device. +message AvailableChannels { + repeated int32 channels = 1 [packed = true]; +} + +// Usable WiFi Direct client channels on the local device. +message WifiDirectCliUsableChannels { + repeated int32 channels = 1 [packed = true]; +} + +// Usable WiFi LAN channels on the local device. +message WifiLanUsableChannels { + repeated int32 channels = 1 [packed = true]; +} + +// Usable WiFi Aware channels on the local device. +message WifiAwareUsableChannels { + repeated int32 channels = 1 [packed = true]; +} + +// Usable WiFi Hotspot STA channels on the local device. +message WifiHotspotStaUsableChannels { + repeated int32 channels = 1 [packed = true]; +} + +// LocationHint is used to specify a location as well as format. +message LocationHint { + // Location is the location, provided in the format specified by format. + optional string location = 1; + + // the format of location. + optional LocationStandard.Format format = 2; +} + +message LocationStandard { + enum Format { + UNKNOWN = 0; + // E164 country codes: + // https://en.wikipedia.org/wiki/List_of_country_calling_codes + // e.g. +1 for USA + E164_CALLING = 1; + + // ISO 3166-1 alpha-2 country codes: + // https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 + ISO_3166_1_ALPHA_2 = 2; + } +} + +// Device capability for OS information. +message OsInfo { + enum OsType { + UNKNOWN_OS_TYPE = 0; + ANDROID = 1; + CHROME_OS = 2; + WINDOWS = 3; + APPLE = 4; + LINUX = 100; // g3 test environment + } + + optional OsType type = 1; +} diff --git a/NearbyShareFramework/ProtobufSource/securegcm.proto b/NearbyShareFramework/ProtobufSource/securegcm.proto new file mode 100644 index 0000000..0325f06 --- /dev/null +++ b/NearbyShareFramework/ProtobufSource/securegcm.proto @@ -0,0 +1,308 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto2"; + +package securegcm; + +option optimize_for = LITE_RUNTIME; +option java_package = "com.google.security.cryptauth.lib.securegcm"; +option java_outer_classname = "SecureGcmProto"; +option objc_class_prefix = "SGCM"; + +// Message used only during enrollment +// Field numbers should be kept in sync with DeviceInfo in: +// java/com/google/security/cryptauth/backend/services/common/common.proto +message GcmDeviceInfo { + // This field's name does not match the one in DeviceInfo for legacy reasons. + // Consider using long_device_id and device_type instead when enrolling + // non-android devices. + optional fixed64 android_device_id = 1; + + // Used for device_address of DeviceInfo field 2, but for GCM capable devices. + optional bytes gcm_registration_id = 102; + + // Used for device_address of DeviceInfo field 2, but for iOS devices. + optional bytes apn_registration_id = 202; + + // Does the user have notifications enabled for the given device address. + optional bool notification_enabled = 203 [default = true]; + + // Used for device_address of DeviceInfo field 2, a Bluetooth Mac address for + // the device (e.g., to be used with EasyUnlock) + optional string bluetooth_mac_address = 302; + + // SHA-256 hash of the device master key (from the key exchange). + // Differs from DeviceInfo field 3, which contains the actual master key. + optional bytes device_master_key_hash = 103; + + // A SecureMessage.EcP256PublicKey + required bytes user_public_key = 4; + + // device's model name + // (e.g., an android.os.Build.MODEL or UIDevice.model) + optional string device_model = 7; + + // device's locale + optional string locale = 8; + + // The handle for user_public_key (and implicitly, a master key) + optional bytes key_handle = 9; + + // The initial counter value for the device, sent by the device + optional int64 counter = 12 [default = 0]; + + // The Operating System version on the device + // (e.g., an android.os.Build.DISPLAY or UIDevice.systemVersion) + optional string device_os_version = 13; + + // The Operating System version number on the device + // (e.g., an android.os.Build.VERSION.SDK_INT) + optional int64 device_os_version_code = 14; + + // The Operating System release on the device + // (e.g., an android.os.Build.VERSION.RELEASE) + optional string device_os_release = 15; + + // The Operating System codename on the device + // (e.g., an android.os.Build.VERSION.CODENAME or UIDevice.systemName) + optional string device_os_codename = 16; + + // The software version running on the device + // (e.g., Authenticator app version string) + optional string device_software_version = 17; + + // The software version number running on the device + // (e.g., Authenticator app version code) + optional int64 device_software_version_code = 18; + + // Software package information if applicable + // (e.g., com.google.android.apps.authenticator2) + optional string device_software_package = 19; + + // Size of the display in thousandths of an inch (e.g., 7000 mils = 7 in) + optional int32 device_display_diagonal_mils = 22; + + // For Authzen capable devices, their Authzen protocol version + optional int32 device_authzen_version = 24; + + // Not all devices have device identifiers that fit in 64 bits. + optional bytes long_device_id = 29; + + // The device manufacturer name + // (e.g., android.os.Build.MANUFACTURER) + optional string device_manufacturer = 31; + + // Used to indicate which type of device this is. + optional DeviceType device_type = 32 [default = ANDROID]; + + // Fields corresponding to screenlock type/features and hardware features + // should be numbered in the 400 range. + + // Is this device using a secure screenlock (e.g., pattern or pin unlock) + optional bool using_secure_screenlock = 400 [default = false]; + + // Is auto-unlocking the screenlock (e.g., when at "home") supported? + optional bool auto_unlock_screenlock_supported = 401 [default = false]; + + // Is auto-unlocking the screenlock (e.g., when at "home") enabled? + optional bool auto_unlock_screenlock_enabled = 402 [default = false]; + + // Does the device have a Bluetooth (classic) radio? + optional bool bluetooth_radio_supported = 403 [default = false]; + + // Is the Bluetooth (classic) radio on? + optional bool bluetooth_radio_enabled = 404 [default = false]; + + // Does the device hardware support a mobile data connection? + optional bool mobile_data_supported = 405 [default = false]; + + // Does the device support tethering? + optional bool tethering_supported = 406 [default = false]; + + // Does the device have a BLE radio? + optional bool ble_radio_supported = 407 [default = false]; + + // Is the device a "Pixel Experience" Android device? + optional bool pixel_experience = 408 [default = false]; + + // Is the device running in the ARC++ container on a chromebook? + optional bool arc_plus_plus = 409 [default = false]; + + // Is the value set in |using_secure_screenlock| reliable? On some Android + // devices, the platform API to get the screenlock state is not trustworthy. + // See b/32212161. + optional bool is_screenlock_state_flaky = 410 [default = false]; + + // A list of multi-device software features supported by the device. + repeated SoftwareFeature supported_software_features = 411; + + // A list of multi-device software features currently enabled (active) on the + // device. + repeated SoftwareFeature enabled_software_features = 412; + + // The enrollment session id this is sent with + optional bytes enrollment_session_id = 1000; + + // A copy of the user's OAuth token + optional string oauth_token = 1001; +} + +// This enum is used by iOS devices as values for device_display_diagonal_mils +// in GcmDeviceInfo. There is no good way to calculate it on those devices. +enum AppleDeviceDiagonalMils { + // This is the mils diagonal on an iPhone 5. + APPLE_PHONE = 4000; + // This is the mils diagonal on an iPad mini. + APPLE_PAD = 7900; +} + +// This should be kept in sync with DeviceType in: +// java/com/google/security/cryptauth/backend/services/common/common_enums.proto +enum DeviceType { + UNKNOWN = 0; + ANDROID = 1; + CHROME = 2; + IOS = 3; + BROWSER = 4; + OSX = 5; +} + +// MultiDevice features which may be supported and enabled on a device. See +enum SoftwareFeature { + UNKNOWN_FEATURE = 0; + BETTER_TOGETHER_HOST = 1; + BETTER_TOGETHER_CLIENT = 2; + EASY_UNLOCK_HOST = 3; + EASY_UNLOCK_CLIENT = 4; + MAGIC_TETHER_HOST = 5; + MAGIC_TETHER_CLIENT = 6; + SMS_CONNECT_HOST = 7; + SMS_CONNECT_CLIENT = 8; +} + +// A list of "reasons" that can be provided for calling server-side APIs. +// This is particularly important for calls that can be triggered by different +// kinds of events. Please try to keep reasons as generic as possible, so that +// codes can be re-used by various callers in a sensible fashion. +enum InvocationReason { + REASON_UNKNOWN = 0; + // First run of the software package invoking this call + REASON_INITIALIZATION = 1; + // Ordinary periodic actions (e.g. monthly master key rotation) + REASON_PERIODIC = 2; + // Slow-cycle periodic action (e.g. yearly keypair rotation???) + REASON_SLOW_PERIODIC = 3; + // Fast-cycle periodic action (e.g. daily sync for Smart Lock users) + REASON_FAST_PERIODIC = 4; + // Expired state (e.g. expired credentials, or cached entries) was detected + REASON_EXPIRATION = 5; + // An unexpected protocol failure occurred (so attempting to repair state) + REASON_FAILURE_RECOVERY = 6; + // A new account has been added to the device + REASON_NEW_ACCOUNT = 7; + // An existing account on the device has been changed + REASON_CHANGED_ACCOUNT = 8; + // The user toggled the state of a feature (e.g. Smart Lock enabled via BT) + REASON_FEATURE_TOGGLED = 9; + // A "push" from the server caused this action (e.g. a sync tickle) + REASON_SERVER_INITIATED = 10; + // A local address change triggered this (e.g. GCM registration id changed) + REASON_ADDRESS_CHANGE = 11; + // A software update has triggered this + REASON_SOFTWARE_UPDATE = 12; + // A manual action by the user triggered this (e.g. commands sent via adb) + REASON_MANUAL = 13; + // A custom key has been invalidated on the device (e.g. screen lock is + // disabled). + REASON_CUSTOM_KEY_INVALIDATION = 14; + // Periodic action triggered by auth_proximity + REASON_PROXIMITY_PERIODIC = 15; +} + +enum Type { + ENROLLMENT = 0; + TICKLE = 1; + TX_REQUEST = 2; + TX_REPLY = 3; + TX_SYNC_REQUEST = 4; + TX_SYNC_RESPONSE = 5; + TX_PING = 6; + DEVICE_INFO_UPDATE = 7; + TX_CANCEL_REQUEST = 8; + + // DEPRECATED (can be re-used after Aug 2015) + PROXIMITYAUTH_PAIRING = 10; + + // The kind of identity assertion generated by a "GCM V1" device (i.e., + // an Android phone that has registered with us a public and a symmetric + // key) + GCMV1_IDENTITY_ASSERTION = 11; + + // Device-to-device communications are protected by an unauthenticated + // Diffie-Hellman exchange. The InitiatorHello message is simply the + // initiator's public DH key, and is not encoded as a SecureMessage, so + // it doesn't have a tag. + // The ResponderHello message (which is sent by the responder + // to the initiator), on the other hand, carries a payload that is protected + // by the derived shared key. It also contains the responder's + // public DH key. ResponderHelloAndPayload messages have the + // DEVICE_TO_DEVICE_RESPONDER_HELLO tag. + DEVICE_TO_DEVICE_RESPONDER_HELLO_PAYLOAD = 12; + + // Device-to-device communications are protected by an unauthenticated + // Diffie-Hellman exchange. Once the initiator and responder + // agree on a shared key (through Diffie-Hellman), they will use messages + // tagged with DEVICE_TO_DEVICE_MESSAGE to exchange data. + DEVICE_TO_DEVICE_MESSAGE = 13; + + // Notification to let a device know it should contact a nearby device. + DEVICE_PROXIMITY_CALLBACK = 14; + + // Device-to-device communications are protected by an unauthenticated + // Diffie-Hellman exchange. During device-to-device authentication, the first + // message from initiator (the challenge) is signed and put into the payload + // of the message sent back to the initiator. + UNLOCK_KEY_SIGNED_CHALLENGE = 15; + + // Specialty (corp only) features + LOGIN_NOTIFICATION = 101; +} + +message GcmMetadata { + required Type type = 1; + optional int32 version = 2 [default = 0]; +} + +message Tickle { + // Time after which this tickle should expire + optional fixed64 expiry_time = 1; +} + +message LoginNotificationInfo { + // Time at which the server received the login notification request. + optional fixed64 creation_time = 2; + + // Must correspond to user_id in LoginNotificationRequest, if set. + optional string email = 3; + + // Host where the user's credentials were used to login, if meaningful. + optional string host = 4; + + // Location from where the user's credentials were used, if meaningful. + optional string source = 5; + + // Type of login, e.g. ssh, gnome-screensaver, or web. + optional string event_type = 6; +} diff --git a/NearbyShareFramework/ProtobufSource/securemessage.proto b/NearbyShareFramework/ProtobufSource/securemessage.proto new file mode 100644 index 0000000..5118d35 --- /dev/null +++ b/NearbyShareFramework/ProtobufSource/securemessage.proto @@ -0,0 +1,126 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Proto definitions for SecureMessage format + +syntax = "proto2"; + +package securemessage; + +option optimize_for = LITE_RUNTIME; +option java_package = "com.google.security.cryptauth.lib.securemessage"; +option java_outer_classname = "SecureMessageProto"; +option objc_class_prefix = "SMSG"; + +message SecureMessage { + // Must contain a HeaderAndBody message + required bytes header_and_body = 1; + // Signature of header_and_body + required bytes signature = 2; +} + +// Supported "signature" schemes (both symmetric key and public key based) +enum SigScheme { + HMAC_SHA256 = 1; + ECDSA_P256_SHA256 = 2; + // Not recommended -- use ECDSA_P256_SHA256 instead + RSA2048_SHA256 = 3; +} + +// Supported encryption schemes +enum EncScheme { + // No encryption + NONE = 1; + AES_256_CBC = 2; +} + +message Header { + required SigScheme signature_scheme = 1; + required EncScheme encryption_scheme = 2; + // Identifies the verification key + optional bytes verification_key_id = 3; + // Identifies the decryption key + optional bytes decryption_key_id = 4; + // Encryption may use an IV + optional bytes iv = 5; + // Arbitrary per-protocol public data, to be sent with the plain-text header + optional bytes public_metadata = 6; + // The length of some associated data this is not sent in this SecureMessage, + // but which will be bound to the signature. + optional uint32 associated_data_length = 7 [default = 0]; +} + +message HeaderAndBody { + // Public data about this message (to be bound in the signature) + required Header header = 1; + // Payload data + required bytes body = 2; +} + +// Must be kept wire-format compatible with HeaderAndBody. Provides the +// SecureMessage code with a consistent wire-format representation that +// remains stable irrespective of protobuf implementation choices. This +// low-level representation of a HeaderAndBody should not be used by +// any code outside of the SecureMessage library implementation/tests. +message HeaderAndBodyInternal { + // A raw (wire-format) byte encoding of a Header, suitable for hashing + required bytes header = 1; + // Payload data + required bytes body = 2; +} + +// ------- +// The remainder of the messages defined here are provided only for +// convenience. They are not needed for SecureMessage proper, but are +// commonly useful wherever SecureMessage might be applied. +// ------- + +// A list of supported public key types +enum PublicKeyType { + EC_P256 = 1; + RSA2048 = 2; + // 2048-bit MODP group 14, from RFC 3526 + DH2048_MODP = 3; +} + +// A convenience proto for encoding NIST P-256 elliptic curve public keys +message EcP256PublicKey { + // x and y are encoded in big-endian two's complement (slightly wasteful) + // Client MUST verify (x,y) is a valid point on NIST P256 + required bytes x = 1; + required bytes y = 2; +} + +// A convenience proto for encoding RSA public keys with small exponents +message SimpleRsaPublicKey { + // Encoded in big-endian two's complement + required bytes n = 1; + optional int32 e = 2 [default = 65537]; +} + +// A convenience proto for encoding Diffie-Hellman public keys, +// for use only when Elliptic Curve based key exchanges are not possible. +// (Note that the group parameters must be specified separately) +message DhPublicKey { + // Big-endian two's complement encoded group element + required bytes y = 1; +} + +message GenericPublicKey { + required PublicKeyType type = 1; + optional EcP256PublicKey ec_p256_public_key = 2; + optional SimpleRsaPublicKey rsa2048_public_key = 3; + // Use only as a last resort + optional DhPublicKey dh2048_public_key = 4; +} diff --git a/NearbyShareFramework/ProtobufSource/ukey.proto b/NearbyShareFramework/ProtobufSource/ukey.proto new file mode 100644 index 0000000..327d8d3 --- /dev/null +++ b/NearbyShareFramework/ProtobufSource/ukey.proto @@ -0,0 +1,105 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto2"; + +package securegcm; + +option optimize_for = LITE_RUNTIME; +option java_package = "com.google.security.cryptauth.lib.securegcm"; +option java_outer_classname = "UkeyProto"; + +message Ukey2Message { + enum Type { + UNKNOWN_DO_NOT_USE = 0; + ALERT = 1; + CLIENT_INIT = 2; + SERVER_INIT = 3; + CLIENT_FINISH = 4; + } + + optional Type message_type = 1; // Identifies message type + optional bytes message_data = 2; // Actual message, to be parsed according to + // message_type +} + +message Ukey2Alert { + enum AlertType { + // Framing errors + BAD_MESSAGE = 1; // The message could not be deserialized + BAD_MESSAGE_TYPE = 2; // message_type has an undefined value + INCORRECT_MESSAGE = 3; // message_type received does not correspond to + // expected type at this stage of the protocol + BAD_MESSAGE_DATA = 4; // Could not deserialize message_data as per + // value inmessage_type + + // ClientInit and ServerInit errors + BAD_VERSION = 100; // version is invalid; server cannot find + // suitable version to speak with client. + BAD_RANDOM = 101; // Random data is missing or of incorrect + // length + BAD_HANDSHAKE_CIPHER = 102; // No suitable handshake ciphers were found + BAD_NEXT_PROTOCOL = 103; // The next protocol is missing, unknown, or + // unsupported + BAD_PUBLIC_KEY = 104; // The public key could not be parsed + + // Other errors + INTERNAL_ERROR = 200; // An internal error has occurred. error_message + // may contain additional details for logging + // and debugging. + } + + optional AlertType type = 1; + optional string error_message = 2; +} + +enum Ukey2HandshakeCipher { + RESERVED = 0; + P256_SHA512 = 100; // NIST P-256 used for ECDH, SHA512 used for + // commitment + CURVE25519_SHA512 = 200; // Curve 25519 used for ECDH, SHA512 used for + // commitment +} + +message Ukey2ClientInit { + optional int32 version = 1; // highest supported version for rollback + // protection + optional bytes random = 2; // random bytes for replay/reuse protection + + // One commitment (hash of ClientFinished containing public key) per supported + // cipher + message CipherCommitment { + optional Ukey2HandshakeCipher handshake_cipher = 1; + optional bytes commitment = 2; + } + repeated CipherCommitment cipher_commitments = 3; + + // Next protocol that the client wants to speak. + optional string next_protocol = 4; +} + +message Ukey2ServerInit { + optional int32 version = 1; // highest supported version for rollback + // protection + optional bytes random = 2; // random bytes for replay/reuse protection + + // Selected Cipher and corresponding public key + optional Ukey2HandshakeCipher handshake_cipher = 3; + optional bytes public_key = 4; +} + +message Ukey2ClientFinished { + optional bytes public_key = 1; // public key matching selected handshake + // cipher +} diff --git a/NearbyShareFramework/ProtobufSource/wire_format.proto b/NearbyShareFramework/ProtobufSource/wire_format.proto new file mode 100644 index 0000000..6853e22 --- /dev/null +++ b/NearbyShareFramework/ProtobufSource/wire_format.proto @@ -0,0 +1,236 @@ +// Copyright 2020 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Brought from: //depot/google3/location/nearby/sharing/proto/wire_format.proto +// At CL 317565061 + +syntax = "proto2"; + +package sharing.nearby; + +// Required in Chrome. +option optimize_for = LITE_RUNTIME; + +// File metadata. Does not include the actual bytes of the file. +// NEXT_ID=6 +message FileMetadata { + enum Type { + UNKNOWN = 0; + IMAGE = 1; + VIDEO = 2; + APP = 3; + AUDIO = 4; + } + + // The human readable name of this file (eg. 'Cookbook.pdf'). + optional string name = 1; + + // The type of file (eg. 'IMAGE' from 'dog.jpg'). Specifying a type helps + // provide a richer experience on the receiving side. + optional Type type = 2 [default = UNKNOWN]; + + // The FILE payload id that will be sent as a follow up containing the actual + // bytes of the file. + optional int64 payload_id = 3; + + // The total size of the file. + optional int64 size = 4; + + // The mimeType of file (eg. 'image/jpeg' from 'dog.jpg'). Specifying a + // mimeType helps provide a richer experience on receiving side. + optional string mime_type = 5 [default = "application/octet-stream"]; + + // A uuid for the attachment. Should be unique across all attachments. + optional int64 id = 6; +} + +// NEXT_ID=5 +message TextMetadata { + enum Type { + UNKNOWN = 0; + TEXT = 1; + // Open with browsers. + URL = 2; + // Open with map apps. + ADDRESS = 3; + // Dial. + PHONE_NUMBER = 4; + } + + // The title of the text content. + optional string text_title = 2; + + // The type of text (phone number, url, address, or plain text). + optional Type type = 3 [default = UNKNOWN]; + + // The BYTE payload id that will be sent as a follow up containing the actual + // bytes of the text. + optional int64 payload_id = 4; + + // The size of the text content. + optional int64 size = 5; + + // A uuid for the attachment. Should be unique across all attachments. + optional int64 id = 6; +} + +// NEXT_ID=5 +message WifiCredentialsMetadata { + enum SecurityType { + UNKNOWN_SECURITY_TYPE = 0; + OPEN = 1; + WPA_PSK = 2; + WEP = 3; + } + + // The Wifi network name. This will be sent in introduction. + optional string ssid = 2; + + // The security type of network (OPEN, WPA_PSK, WEP). + optional SecurityType security_type = 3 [default = UNKNOWN_SECURITY_TYPE]; + + // The BYTE payload id that will be sent as a follow up containing the + // password. + optional int64 payload_id = 4; + + // A uuid for the attachment. Should be unique across all attachments. + optional int64 id = 5; +} + +// A frame used when sending messages over the wire. +// NEXT_ID=3 +message Frame { + enum Version { + UNKNOWN_VERSION = 0; + V1 = 1; + } + optional Version version = 1; + + // Right now there's only 1 version, but if there are more, exactly one of + // the following fields will be set. + optional V1Frame v1 = 2; +} + +// NEXT_ID=7 +message V1Frame { + enum FrameType { + UNKNOWN_FRAME_TYPE = 0; + INTRODUCTION = 1; + RESPONSE = 2; + PAIRED_KEY_ENCRYPTION = 3; + PAIRED_KEY_RESULT = 4; + CERTIFICATE_INFO = 5; + CANCEL = 6; + } + + optional FrameType type = 1; + + // Exactly one of the following fields will be set. + optional IntroductionFrame introduction = 2; + optional ConnectionResponseFrame connection_response = 3; + optional PairedKeyEncryptionFrame paired_key_encryption = 4; + optional PairedKeyResultFrame paired_key_result = 5; + optional CertificateInfoFrame certificate_info = 6; +} + +// An introduction packet sent by the sending side. Contains a list of files +// they'd like to share. +// NEXT_ID=4 +message IntroductionFrame { + repeated FileMetadata file_metadata = 1; + repeated TextMetadata text_metadata = 2; + // The required app package to open the content. May be null. + optional string required_package = 3; + repeated WifiCredentialsMetadata wifi_credentials_metadata = 4; +} + +// A response packet sent by the receiving side. Accepts or rejects the list of +// files. +// NEXT_ID=2 +message ConnectionResponseFrame { + enum Status { + UNKNOWN = 0; + ACCEPT = 1; + REJECT = 2; + NOT_ENOUGH_SPACE = 3; + UNSUPPORTED_ATTACHMENT_TYPE = 4; + TIMED_OUT = 5; + } + + // The receiving side's response. + optional Status status = 1; +} + +// A paired key encryption packet sent between devices, contains signed data. +// NEXT_ID=3 +message PairedKeyEncryptionFrame { + // The encrypted data in byte array format. + optional bytes signed_data = 1; + + // The hash of a certificate id. + optional bytes secret_id_hash = 2; + + // An optional encrypted data in byte array format. + optional bytes optional_signed_data = 3; +} + +// A paired key verification result packet sent between devices. +// NEXT_ID=2 +message PairedKeyResultFrame { + enum Status { + UNKNOWN = 0; + SUCCESS = 1; + FAIL = 2; + UNABLE = 3; + } + + // The verification result. + optional Status status = 1; +} + +// A package containing certificate info to be shared to remote device offline. +// NEXT_ID=2 +message CertificateInfoFrame { + // The public certificates to be shared with remote devices. + repeated PublicCertificate public_certificate = 1; +} + +// A public certificate from the local device. +// NEXT_ID=8 +message PublicCertificate { + // The unique id of the public certificate. + optional bytes secret_id = 1; + + // A bytes representation of a Secret Key owned by contact, to decrypt the + // metadata_key stored within the advertisement. + optional bytes authenticity_key = 2; + + // A bytes representation a public key of X509Certificate, owned by contact, + // to decrypt encrypted UKEY2 (from Nearby Connections API) as a hand shake in + // contact verification phase. + optional bytes public_key = 3; + + // The time in millis from epoch when this certificate becomes effective. + optional int64 start_time = 4; + + // The time in millis from epoch when this certificate expires. + optional int64 end_time = 5; + + // The encrypted metadata in bytes, contains personal information of the + // device/user who created this certificate. Needs to be decrypted into bytes, + // and converted back to EncryptedMetadata object to access fields. + optional bytes encrypted_metadata_bytes = 6; + + // The tag for verifying metadata_encryption_key. + optional bytes metadata_encryption_key_tag = 7; +} + +// NEXT_ID=3 +message WifiCredentials { + // Wi-Fi password. + optional string password = 1; + // True if the network is a hidden network that is not broadcasting its SSID. + // Default is false. + optional bool hidden_ssid = 2 [default = false]; +} \ No newline at end of file diff --git a/NearbyShareFramework/Util/ConnectionFailureTracker.swift b/NearbyShareFramework/Util/ConnectionFailureTracker.swift new file mode 100644 index 0000000..d728a21 --- /dev/null +++ b/NearbyShareFramework/Util/ConnectionFailureTracker.swift @@ -0,0 +1,41 @@ +// +// ConnectionFailureTracker.swift +// QuickDrop +// +// Created by Leon Böttger on 15.05.25. +// + +import Foundation + +class ConnectionFailureTracker { + static let shared = ConnectionFailureTracker() + + private var failureTimestamps: [Date] = [] + private var lastAlertShownAt: Date? + + private let queue = DispatchQueue(label: "ConnectionFailureTrackerQueue") + + private init() {} + + func recordFailure(onTrigger: @escaping () -> Void) { + queue.async { + let now = Date() + + // Remove old timestamps older than 10 seconds + self.failureTimestamps = self.failureTimestamps.filter { now.timeIntervalSince($0) < 10 } + self.failureTimestamps.append(now) + + // Check if alert can be shown + let canShowAlert = self.failureTimestamps.count >= 2 && + (self.lastAlertShownAt == nil || now.timeIntervalSince(self.lastAlertShownAt!) >= 60) + + if canShowAlert { + self.lastAlertShownAt = now + self.failureTimestamps.removeAll() + DispatchQueue.main.async { + onTrigger() + } + } + } + } +} diff --git a/NearbyShareFramework/Util/Helpers.swift b/NearbyShareFramework/Util/Helpers.swift new file mode 100644 index 0000000..4ac8d26 --- /dev/null +++ b/NearbyShareFramework/Util/Helpers.swift @@ -0,0 +1,100 @@ +// +// Helpers.swift +// QuickDrop +// +// Created by Leon Böttger on 04.04.25. +// + +import Foundation + +extension Data { + func urlSafeBase64EncodedString() -> String { + return String(base64EncodedString().replacingOccurrences(of: "=", with: "").map { + if $0 == "/" { + return "_" + } else if $0 == "+" { + return "-" + } else { + return $0 + } + }) + } + + + static func randomData(length: Int) -> Data { + var data = Data(count: length) + data.withUnsafeMutableBytes { + guard SecRandomCopyBytes(kSecRandomDefault, length, $0.baseAddress!) == 0 else { fatalError() } + } + return data + } + + + static func dataFromUrlSafeBase64(_ str: String) -> Data? { + var regularB64 = String(str.map { + if $0 == "_" { + return "/" + } else if $0 == "-" { + return "+" + } else { + return $0 + } + }) + while (regularB64.count % 4) != 0 { + regularB64 = regularB64 + "=" + } + return Data(base64Encoded: regularB64, options: .ignoreUnknownCharacters) + } + + + var hex: String { + return map { String(format: "%02hhx", $0) }.joined() + } +} + + +extension String { + var dataFromHex: Data { + var data = Data(capacity: count / 2) + var index = startIndex + while index < endIndex { + let nextIndex = self.index(index, offsetBy: 2) + if nextIndex <= endIndex, + let byte = UInt8(self[index ..< nextIndex], radix: 16) + { + data.append(byte) + } else { + break // or handle invalid hex gracefully + } + index = nextIndex + } + return data + } +} + + +public enum UserDefaultsKeys: String, CaseIterable { + case isEligibleForIap = "isEligibleForIap" + case appLaunchedBefore = "ShowedWelcomeScreen" + case plusVersion = "isPlusVersion" + case transmissionCount = "reviewRequestCountKey" + case automaticallyAcceptFiles = "automaticallyAcceptFiles" + case saveFolderBookmark = "saveFolderBookmark" + case openFinderAfterReceiving = "openFinderAfterReceiving" + case endpointID = "endpointID" +} + + +public func isPlusVersion() -> Bool { + return UserDefaults.standard.bool(forKey: UserDefaultsKeys.plusVersion.rawValue) +} + + +public func isFileTransferRestricted() -> Bool { + (!isPlusVersion() && transmissionCount() > 1) +} + + +public func transmissionCount() -> Int { + return UserDefaults.standard.integer(forKey: UserDefaultsKeys.transmissionCount.rawValue) +} diff --git a/NearbyShareFramework/Util/Logger.swift b/NearbyShareFramework/Util/Logger.swift new file mode 100644 index 0000000..6d35119 --- /dev/null +++ b/NearbyShareFramework/Util/Logger.swift @@ -0,0 +1,133 @@ +// +// Logger.swift +// QuickDrop +// +// Created by Leon Böttger on 26.02.25. +// + +import Foundation +import os + +fileprivate let loggerQueue = DispatchQueue(label: "loggerQueue", qos: .utility) + +/// Manages logging functionality +final class LogManager { + + /// The URL of the log file + let logFileURL: URL? + + /// Shared instance + public static let sharedInstance = LogManager() + + @Published var loggingEnabled = true + + + /// Private initializer + private init() { + // Use the App Group container + let directory = getAppGroupDirectory() + + // Create file name from date + let formatter = DateFormatter() + formatter.dateFormat = "dd-MM-yyyy" + let dateString = formatter.string(from: Date()) + let fileName = "\(dateString).log" + + // Full path in app group container + self.logFileURL = directory?.appendingPathComponent(fileName) + } + + + /// Writes a specified string to the log file. + func writeToFile(string: String) { + guard let logFileURL = logFileURL else { return } + + let formatter = DateFormatter() + formatter.dateFormat = "HH:mm:ss" + let timestamp = formatter.string(from: Date()) + guard let data = (timestamp + " - " + string + "\n").data(using: .utf8) else { return } + + loggerQueue.async { + let fileManager = FileManager.default + + if !fileManager.fileExists(atPath: logFileURL.path) { + // Clean up older logs + self.deleteLogs() + // Create file atomically + try? data.write(to: logFileURL, options: .atomicWrite) + return + } + + // Atomic append with file locking using FileHandle + do { + let fileHandle = try FileHandle(forWritingTo: logFileURL) + defer { try? fileHandle.close() } + + // Locking is handled by the serial dispatch queue + try fileHandle.seekToEnd() + try fileHandle.write(contentsOf: data) + } catch { + print("[Logger] Failed to write to log file: \(error.localizedDescription)") + } + } + } + + + /// Gets the contents of the log file. + func getLogString() -> String { + guard let logFileURL = logFileURL else { return "" } + do { + return try String(contentsOf: logFileURL, encoding: .utf8) + } catch { + log("[LUI] Error reading file: \(error.localizedDescription)") + return "" + } + } + + + /// Deletes all log files in the shared container. + func deleteLogs() { + log("[LUI] Deleting all logs...") + + guard let directory = getAppGroupDirectory() else { return } + let fileManager = FileManager.default + + guard let allFiles = try? fileManager.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil) else { + return + } + + let logFiles = allFiles.filter { $0.pathExtension == "log" } + for file in logFiles { + do { + log("[LUI] Removing \(file.lastPathComponent)") + try fileManager.removeItem(at: file) + } catch { + print("[Logger] Failed to delete file: \(error.localizedDescription)") + } + } + } +} + + +/// Logs a specified string +public func log(_ text: String) { + let logger = LogManager.sharedInstance + + if logger.loggingEnabled { + loggerQueue.async { + print(text) +#if !os(tvOS) + let isAppExtension = Bundle.main.bundlePath.hasSuffix(".appex") + let prefix = isAppExtension ? "[EXT]" : "[APP]" + logger.writeToFile(string: "\(prefix) \(text)") +#endif + } + } +} + + +/// Returns the shared App Group directory +fileprivate func getAppGroupDirectory() -> URL? { + let groupIdentifier = "group.com.leonboettger.neardrop" + return FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: groupIdentifier) +} diff --git a/NearbyShareFramework/Util/SaveFilesManager.swift b/NearbyShareFramework/Util/SaveFilesManager.swift new file mode 100644 index 0000000..73b3e71 --- /dev/null +++ b/NearbyShareFramework/Util/SaveFilesManager.swift @@ -0,0 +1,220 @@ +// +// SaveFilesManager.swift +// QuickDrop +// +// Created by Leon Böttger on 26.07.25. +// + +import Foundation +import AppKit +import ImageIO + +public class SaveFilesManager { + + private init() { + // remove old temp directory + + let tempPath = FileManager.default.temporaryDirectory + let fileManager = FileManager.default + + do { + let contents = try fileManager.contentsOfDirectory(at: tempPath, includingPropertiesForKeys: nil) + + var didSomething = false + + for item in contents { + didSomething = true + try fileManager.removeItem(at: item) + } + + if didSomething { + log("Temporary directory cleared.") + } + } catch { + log("Failed to list contents of temp directory: \(error)") + } + } + + public static let shared = SaveFilesManager() + + let tempDirectory: URL = FileManager.default.temporaryDirectory.appendingPathComponent("Pending") + private var securityScopeUrl: URL? + + private var filesFinishedDownloading = [URL]() + private var filesFinishedDownloadingSinceLastRun = [URL]() + + + public func registerFileFinishedDownloading(_ fileURL: URL) { + filesFinishedDownloading.append(fileURL) + filesFinishedDownloadingSinceLastRun.append(fileURL) + + // read out EXIF timestamp and manually set file creation date to it + applyEXIFTimestamps(at: fileURL) + } + + + public func movePendingFilesToTarget() { + + if isPlusVersion() { + do { + let fileManager = FileManager.default + let files = try fileManager.contentsOfDirectory(at: tempDirectory, includingPropertiesForKeys: nil) + + let target = getSaveDirectory() + var loggedExecution = false + + for file in files { + + if !loggedExecution { + log("Moving pending files to target directory") + loggedExecution = true + } + + let fileName = file.lastPathComponent + let destinationURL = target.appendingPathComponent(fileName) + + if !filesFinishedDownloading.contains(destinationURL) { + log("File \(file) not finished downloading, skipping") + continue + } + + log("Moving file: \(file.lastPathComponent) to \(destinationURL.lastPathComponent)") + + do { + try fileManager.copyItem(at: file, to: destinationURL) + + let progress = Progress() + progress.fileURL = destinationURL + progress.totalUnitCount = 10 + progress.kind = .file + progress.isPausable = false + progress.publish() + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + progress.completedUnitCount = 10 + progress.unpublish() + } + + try fileManager.removeItem(at: file) + } + catch { + log("Error moving file: \(error)") + } + } + log("Moved all pending files to target directory") + } + catch { + // Pending directory doesn't exist or is empty + } + } + + if !filesFinishedDownloadingSinceLastRun.isEmpty && !isFileTransferRestricted() { + + if UserDefaults.standard.bool(forKey: UserDefaultsKeys.openFinderAfterReceiving.rawValue) { + log("Opening \(filesFinishedDownloadingSinceLastRun.count) file(s) in Finder.") + NSWorkspace.shared.activateFileViewerSelecting(filesFinishedDownloadingSinceLastRun) + } + + // Clear the list of finished files + filesFinishedDownloadingSinceLastRun.removeAll() + } + + stopAccessingSecurityScopedResource() + } + + + public func stopAccessingSecurityScopedResource() { + guard let url = securityScopeUrl else { + return + } + + log("Stopping access to security scoped resource: \(url)") + url.stopAccessingSecurityScopedResource() + securityScopeUrl = nil + } + + + public func getSaveDirectory() -> URL { + if let securityScopeUrl = securityScopeUrl { + log("Using existing security scope URL: \(securityScopeUrl)") + return securityScopeUrl + } + + if let bookmarkData = UserDefaults.standard.data(forKey: UserDefaultsKeys.saveFolderBookmark.rawValue) { + var isStale = false + + do { + let url = try URL(resolvingBookmarkData: bookmarkData, options: .withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale) + + if !isStale { + if url.startAccessingSecurityScopedResource() { + log("Successfully accessed security scoped resource: \(url)") + + securityScopeUrl = url + return url + } + } else { + log("Bookmark is stale, using default downloads folder.") + } + + } catch { + log("Failed to resolve bookmark: \(error), using default downloads folder.") + } + } + + do { + return try FileManager.default.url(for: .downloadsDirectory, in: .userDomainMask, appropriateFor: nil, create: true).resolvingSymlinksInPath() + } catch { + fatalError("Failed to get downloads directory: \(error)") + } + } + + + /// Returns the EXIF original date of an image file, adjusted to the current time zone. + private func exifOriginalDate(from url: URL) -> Date? { + guard let imageSource = CGImageSourceCreateWithURL(url as CFURL, nil), + let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? [CFString: Any], + let exif = properties[kCGImagePropertyExifDictionary] as? [CFString: Any], + let dateString = exif[kCGImagePropertyExifDateTimeOriginal] as? String + else { + return nil + } + + let formatter = DateFormatter() + formatter.dateFormat = "yyyy:MM:dd HH:mm:ss" + formatter.timeZone = TimeZone.current // Use the current device time zone + return formatter.date(from: dateString) + } + + + /// Applies EXIF timestamps to a given file or directory. + private func applyEXIFTimestamps(at url: URL) { + let fm = FileManager.default + var isDir: ObjCBool = false + guard fm.fileExists(atPath: url.path, isDirectory: &isDir) else { return } + + if isDir.boolValue { + // It's a directory — enumerate files + if let enumerator = fm.enumerator(at: url, includingPropertiesForKeys: [.isRegularFileKey]) { + for case let fileURL as URL in enumerator { + guard (try? fileURL.resourceValues(forKeys: [.isRegularFileKey]))?.isRegularFile == true else { continue } + applyEXIFTimestamp(to: fileURL) + } + } + } else { + // It's a single file + applyEXIFTimestamp(to: url) + } + } + + + /// Applies EXIF timestamp (if available) to a single file + private func applyEXIFTimestamp(to fileURL: URL) { + guard let exifDate = exifOriginalDate(from: fileURL) else { + return + } + + // Set the "creation date" + try? FileManager.default.setAttributes([.creationDate: exifDate], ofItemAtPath: fileURL.path) + } +} diff --git a/NearbyShareFramework/Util/SleepManager.swift b/NearbyShareFramework/Util/SleepManager.swift new file mode 100644 index 0000000..01da003 --- /dev/null +++ b/NearbyShareFramework/Util/SleepManager.swift @@ -0,0 +1,59 @@ +// +// SleepManager.swift +// QuickDrop +// +// Created by Leon Böttger on 03.05.25. +// + +import Foundation +import IOKit +import IOKit.pwr_mgt + +class SleepManager { + public static let shared = SleepManager() + private var assertionID: IOPMAssertionID = 0 + private var timer: Timer? + + private init() { + // Start periodic check + timer = Timer.scheduledTimer(withTimeInterval: 5.0, repeats: true) { [weak self] _ in + self?.updateSleepState() + } + + RunLoop.main.add(timer!, forMode: .common) + } + + private func disableSleep() { + if assertionID != 0 { + return + } + + log("[SleepManager] Enabling Wakelock") + + IOPMAssertionCreateWithName( + kIOPMAssertionTypePreventUserIdleDisplaySleep as CFString, + IOPMAssertionLevel(kIOPMAssertionLevelOn), + "QuickDrop Data Transfer" as CFString, + &assertionID + ) + } + + private func enableSleep() { + if assertionID == 0 { + return + } + + log("[SleepManager] Disabling Wakelock") + IOPMAssertionRelease(assertionID) + assertionID = 0 + } + + private func updateSleepState() { + let activeConnections = NearbyConnectionManager.shared.getActiveConnectionsCount() + if activeConnections > 0 { + disableSleep() + } else { + enableSleep() + } + } +} diff --git a/QuickDrop.xcodeproj/project.pbxproj b/QuickDrop.xcodeproj/project.pbxproj index 6f5e99c..5dc89e5 100644 --- a/QuickDrop.xcodeproj/project.pbxproj +++ b/QuickDrop.xcodeproj/project.pbxproj @@ -9,43 +9,55 @@ /* Begin PBXBuildFile section */ 691F53BB2ABB70840089FD92 /* DeviceListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 691F53B92ABB70840089FD92 /* DeviceListCell.swift */; }; 691F53BC2ABB70840089FD92 /* DeviceListCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 691F53BA2ABB70840089FD92 /* DeviceListCell.xib */; }; - 691F53BE2ABF03820089FD92 /* OutboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 691F53BD2ABF03820089FD92 /* OutboundNearbyConnection.swift */; }; 699DEBA62AB0573200115D22 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 699DEBA52AB0573200115D22 /* ShareViewController.swift */; }; 699DEBA92AB0573200115D22 /* ShareViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 699DEBA72AB0573200115D22 /* ShareViewController.xib */; }; 699DEBAE2AB0573200115D22 /* QuickDropShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 699DEBA12AB0573200115D22 /* QuickDropShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 69D2C32F29E7898C00EC7E30 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 69D2C32E29E7898C00EC7E30 /* MainMenu.xib */; }; 69DA9A1229E0BF5100A442DA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A1129E0BF5100A442DA /* AppDelegate.swift */; }; 69DA9A1429E0BF5200A442DA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 69DA9A1329E0BF5200A442DA /* Assets.xcassets */; }; - 69DCF48A2AB70E8C00CBE2CC /* wire_format.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2929E18CB500A442DA /* wire_format.pb.swift */; }; - 69DCF48B2AB70E8C00CBE2CC /* ukey.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2829E18CB500A442DA /* ukey.pb.swift */; }; - 69DCF48C2AB70E8C00CBE2CC /* device_to_device_messages.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2A29E18CB500A442DA /* device_to_device_messages.pb.swift */; }; - 69DCF48D2AB70E8C00CBE2CC /* offline_wire_formats.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2B29E18CB500A442DA /* offline_wire_formats.pb.swift */; }; - 69DCF48E2AB70E8C00CBE2CC /* securegcm.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2C29E18CB500A442DA /* securegcm.pb.swift */; }; - 69DCF48F2AB70E8C00CBE2CC /* securemessage.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2D29E18CB500A442DA /* securemessage.pb.swift */; }; - 69DCF4902AB70E9700CBE2CC /* InboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2229E17F0400A442DA /* InboundNearbyConnection.swift */; }; - 69DCF4912AB70E9700CBE2CC /* NearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 698DFAE529E2F91A0064F247 /* NearbyConnection.swift */; }; - 69DCF4922AB70E9700CBE2CC /* NearbyConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A1E29E0C0B300A442DA /* NearbyConnectionManager.swift */; }; - 69DCF4942AB8BF7B00CBE2CC /* libNearbyShare.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 69DCF4812AB70D0600CBE2CC /* libNearbyShare.dylib */; }; - 69DCF4952AB8BF7B00CBE2CC /* libNearbyShare.dylib in Embed Libraries */ = {isa = PBXBuildFile; fileRef = 69DCF4812AB70D0600CBE2CC /* libNearbyShare.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; - 69DCF49A2AB8C58500CBE2CC /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = 69DCF4992AB8C58500CBE2CC /* SwiftProtobuf */; }; - 69DCF49C2AB8C58500CBE2CC /* SwiftECC in Frameworks */ = {isa = PBXBuildFile; productRef = 69DCF49B2AB8C58500CBE2CC /* SwiftECC */; }; - 69DCF49D2AB8C9A500CBE2CC /* libNearbyShare.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 69DCF4812AB70D0600CBE2CC /* libNearbyShare.dylib */; }; - 69DCF49E2AB8C9A500CBE2CC /* libNearbyShare.dylib in Embed Libraries */ = {isa = PBXBuildFile; fileRef = 69DCF4812AB70D0600CBE2CC /* libNearbyShare.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; - EB2B41E42DC63ACC0070FC39 /* SleepManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB2B41E32DC63AC80070FC39 /* SleepManager.swift */; }; - EB2F9BFB2DA04B69004010A2 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB2F9BFA2DA04B67004010A2 /* Helpers.swift */; }; EB2F9BFE2DA05F48004010A2 /* BezelNotification in Frameworks */ = {isa = PBXBuildFile; productRef = EB2F9BFD2DA05F48004010A2 /* BezelNotification */; }; EB3643602D73A2A2009CDFF8 /* AudioManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB36435F2D73A29F009CDFF8 /* AudioManager.swift */; }; EB5C36932E34EBBD0052EFD7 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = EB5C36922E34EBBD0052EFD7 /* Localizable.xcstrings */; }; EB5C36942E34EBBD0052EFD7 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = EB5C36922E34EBBD0052EFD7 /* Localizable.xcstrings */; }; EB5C36E42E34EC0E0052EFD7 /* License.txt in Resources */ = {isa = PBXBuildFile; fileRef = EB5C36E32E34EC0E0052EFD7 /* License.txt */; }; EB5C36E52E34EC0E0052EFD7 /* License.txt in Resources */ = {isa = PBXBuildFile; fileRef = EB5C36E32E34EC0E0052EFD7 /* License.txt */; }; - EB5C36E72E3544FF0052EFD7 /* SaveFilesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C36E62E3544FB0052EFD7 /* SaveFilesManager.swift */; }; - EB5C36E92E3546120052EFD7 /* DataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C36E82E3546100052EFD7 /* DataModel.swift */; }; - EB6E743E2DD5C77700331466 /* ConnectionFailureTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB6E743D2DD5C76F00331466 /* ConnectionFailureTracker.swift */; }; + EB5C37B72E377A6F0052EFD7 /* ConnectionFailureTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB6E743D2DD5C76F00331466 /* ConnectionFailureTracker.swift */; }; + EB5C37B82E377A6F0052EFD7 /* SleepManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB2B41E32DC63AC80070FC39 /* SleepManager.swift */; }; + EB5C37B92E377A6F0052EFD7 /* SaveFilesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C36E62E3544FB0052EFD7 /* SaveFilesManager.swift */; }; + EB5C37BA2E377A6F0052EFD7 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB2F9BFA2DA04B67004010A2 /* Helpers.swift */; }; + EB5C37BB2E377A700052EFD7 /* ConnectionFailureTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB6E743D2DD5C76F00331466 /* ConnectionFailureTracker.swift */; }; + EB5C37BC2E377A700052EFD7 /* SleepManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB2B41E32DC63AC80070FC39 /* SleepManager.swift */; }; + EB5C37BD2E377A700052EFD7 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB2F9BFA2DA04B67004010A2 /* Helpers.swift */; }; + EB5C37BE2E377A700052EFD7 /* SaveFilesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C36E62E3544FB0052EFD7 /* SaveFilesManager.swift */; }; + EB5C37BF2E377A780052EFD7 /* ukey.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2829E18CB500A442DA /* ukey.pb.swift */; }; + EB5C37C02E377A780052EFD7 /* securemessage.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2D29E18CB500A442DA /* securemessage.pb.swift */; }; + EB5C37C12E377A780052EFD7 /* offline_wire_formats.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2B29E18CB500A442DA /* offline_wire_formats.pb.swift */; }; + EB5C37C22E377A780052EFD7 /* device_to_device_messages.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2A29E18CB500A442DA /* device_to_device_messages.pb.swift */; }; + EB5C37C32E377A780052EFD7 /* wire_format.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2929E18CB500A442DA /* wire_format.pb.swift */; }; + EB5C37C42E377A780052EFD7 /* securegcm.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2C29E18CB500A442DA /* securegcm.pb.swift */; }; + EB5C37C52E377A780052EFD7 /* securemessage.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2D29E18CB500A442DA /* securemessage.pb.swift */; }; + EB5C37C62E377A780052EFD7 /* securegcm.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2C29E18CB500A442DA /* securegcm.pb.swift */; }; + EB5C37C72E377A780052EFD7 /* wire_format.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2929E18CB500A442DA /* wire_format.pb.swift */; }; + EB5C37C82E377A780052EFD7 /* ukey.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2829E18CB500A442DA /* ukey.pb.swift */; }; + EB5C37C92E377A780052EFD7 /* device_to_device_messages.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2A29E18CB500A442DA /* device_to_device_messages.pb.swift */; }; + EB5C37CA2E377A780052EFD7 /* offline_wire_formats.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2B29E18CB500A442DA /* offline_wire_formats.pb.swift */; }; + EB5C37CB2E377A7F0052EFD7 /* NearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 698DFAE529E2F91A0064F247 /* NearbyConnection.swift */; }; + EB5C37CC2E377A7F0052EFD7 /* DataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C36E82E3546100052EFD7 /* DataModel.swift */; }; + EB5C37CD2E377A7F0052EFD7 /* OutboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 691F53BD2ABF03820089FD92 /* OutboundNearbyConnection.swift */; }; + EB5C37CE2E377A7F0052EFD7 /* InboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2229E17F0400A442DA /* InboundNearbyConnection.swift */; }; + EB5C37CF2E377A7F0052EFD7 /* NearbyConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A1E29E0C0B300A442DA /* NearbyConnectionManager.swift */; }; + EB5C37D02E377A7F0052EFD7 /* OutboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 691F53BD2ABF03820089FD92 /* OutboundNearbyConnection.swift */; }; + EB5C37D12E377A7F0052EFD7 /* DataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C36E82E3546100052EFD7 /* DataModel.swift */; }; + EB5C37D22E377A7F0052EFD7 /* NearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 698DFAE529E2F91A0064F247 /* NearbyConnection.swift */; }; + EB5C37D32E377A7F0052EFD7 /* InboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2229E17F0400A442DA /* InboundNearbyConnection.swift */; }; + EB5C37D42E377A7F0052EFD7 /* NearbyConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A1E29E0C0B300A442DA /* NearbyConnectionManager.swift */; }; + EB5C37D62E377ACF0052EFD7 /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = EB5C37D52E377ACF0052EFD7 /* SwiftProtobuf */; }; + EB5C37D82E377ACF0052EFD7 /* SwiftECC in Frameworks */ = {isa = PBXBuildFile; productRef = EB5C37D72E377ACF0052EFD7 /* SwiftECC */; }; + EB5C37DA2E377AD60052EFD7 /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = EB5C37D92E377AD60052EFD7 /* SwiftProtobuf */; }; + EB5C37DC2E377AD60052EFD7 /* SwiftECC in Frameworks */ = {isa = PBXBuildFile; productRef = EB5C37DB2E377AD60052EFD7 /* SwiftECC */; }; EB6F62D72D91F4AF00534EC2 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB6F62D62D91F4AC00534EC2 /* SettingsView.swift */; }; EB7CD1452D6FB0C300CB0CB5 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */; }; EB7CD1462D6FB0E800CB0CB5 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */; }; - EB7CD1472D6FB0E800CB0CB5 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */; }; EB7F54122D78E96000989564 /* TutorialView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7F54112D78E95C00989564 /* TutorialView.swift */; }; EB7F54142D78E9E700989564 /* LicenseWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7F54132D78E9E400989564 /* LicenseWindow.swift */; }; EB96ED8B2D7F7CF7004B3FF1 /* SmallSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB96ED8A2D7F7CF4004B3FF1 /* SmallSheetView.swift */; }; @@ -53,7 +65,6 @@ EB96ED8D2D7F7EA3004B3FF1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 69DA9A1329E0BF5200A442DA /* Assets.xcassets */; }; EB96ED8F2D7F8105004B3FF1 /* Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB96ED8E2D7F8103004B3FF1 /* Localized.swift */; }; EB96ED902D7F8114004B3FF1 /* Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB96ED8E2D7F8103004B3FF1 /* Localized.swift */; }; - EBC05C592E32C22200EECC18 /* Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB96ED8E2D7F8103004B3FF1 /* Localized.swift */; }; EBC05C5B2E33937200EECC18 /* ErrorAlertHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C5A2E33936C00EECC18 /* ErrorAlertHandler.swift */; }; EBC05C5C2E3394EF00EECC18 /* ErrorAlertHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C5A2E33936C00EECC18 /* ErrorAlertHandler.swift */; }; EBC05C712E33B57D00EECC18 /* Converter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C6D2E33B57D00EECC18 /* Converter.swift */; }; @@ -85,20 +96,6 @@ remoteGlobalIDString = 699DEBA02AB0573200115D22; remoteInfo = ShareExtension; }; - 69DCF4962AB8BF7B00CBE2CC /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 69DA9A0629E0BF5100A442DA /* Project object */; - proxyType = 1; - remoteGlobalIDString = 69DCF4802AB70D0600CBE2CC; - remoteInfo = NearbyShare; - }; - 69DCF49F2AB8C9A500CBE2CC /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 69DA9A0629E0BF5100A442DA /* Project object */; - proxyType = 1; - remoteGlobalIDString = 69DCF4802AB70D0600CBE2CC; - remoteInfo = NearbyShare; - }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -129,7 +126,6 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - 69DCF4952AB8BF7B00CBE2CC /* libNearbyShare.dylib in Embed Libraries */, ); name = "Embed Libraries"; runOnlyForDeploymentPostprocessing = 0; @@ -140,7 +136,6 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - 69DCF49E2AB8C9A500CBE2CC /* libNearbyShare.dylib in Embed Libraries */, ); name = "Embed Libraries"; runOnlyForDeploymentPostprocessing = 0; @@ -172,7 +167,6 @@ 69DA9A2B29E18CB500A442DA /* offline_wire_formats.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = offline_wire_formats.pb.swift; sourceTree = ""; }; 69DA9A2C29E18CB500A442DA /* securegcm.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = securegcm.pb.swift; sourceTree = ""; }; 69DA9A2D29E18CB500A442DA /* securemessage.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = securemessage.pb.swift; sourceTree = ""; }; - 69DCF4812AB70D0600CBE2CC /* libNearbyShare.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libNearbyShare.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; EB2B41E32DC63AC80070FC39 /* SleepManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SleepManager.swift; sourceTree = ""; }; EB2F9BFA2DA04B67004010A2 /* Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = ""; }; EB36435F2D73A29F009CDFF8 /* AudioManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioManager.swift; sourceTree = ""; }; @@ -215,7 +209,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 69DCF49D2AB8C9A500CBE2CC /* libNearbyShare.dylib in Frameworks */, + EB5C37D82E377ACF0052EFD7 /* SwiftECC in Frameworks */, + EB5C37D62E377ACF0052EFD7 /* SwiftProtobuf in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -224,16 +219,8 @@ buildActionMask = 2147483647; files = ( EB2F9BFE2DA05F48004010A2 /* BezelNotification in Frameworks */, - 69DCF4942AB8BF7B00CBE2CC /* libNearbyShare.dylib in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 69DCF47F2AB70D0600CBE2CC /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 69DCF49C2AB8C58500CBE2CC /* SwiftECC in Frameworks */, - 69DCF49A2AB8C58500CBE2CC /* SwiftProtobuf in Frameworks */, + EB5C37DC2E377AD60052EFD7 /* SwiftECC in Frameworks */, + EB5C37DA2E377AD60052EFD7 /* SwiftProtobuf in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -278,7 +265,6 @@ children = ( 69DA9A0E29E0BF5100A442DA /* QuickDrop.app */, 699DEBA12AB0573200115D22 /* QuickDropShareExtension.appex */, - 69DCF4812AB70D0600CBE2CC /* libNearbyShare.dylib */, ); name = Products; sourceTree = ""; @@ -314,9 +300,8 @@ 69DCF4822AB70D0600CBE2CC /* NearbyShare */ = { isa = PBXGroup; children = ( + EB5C37DD2E377B430052EFD7 /* Model */, EB5C371C2E3669010052EFD7 /* Util */, - EB3B73F92DC8FF3A00369951 /* ProtobufSource */, - 69DA9A2729E18CB500A442DA /* Protobuf */, EB5C371D2E3669150052EFD7 /* Connection */, ); path = NearbyShare; @@ -352,7 +337,6 @@ children = ( 69DA9A1E29E0C0B300A442DA /* NearbyConnectionManager.swift */, 69DA9A2229E17F0400A442DA /* InboundNearbyConnection.swift */, - EB5C36E82E3546100052EFD7 /* DataModel.swift */, 691F53BD2ABF03820089FD92 /* OutboundNearbyConnection.swift */, 698DFAE529E2F91A0064F247 /* NearbyConnection.swift */, ); @@ -378,6 +362,16 @@ path = Util; sourceTree = ""; }; + EB5C37DD2E377B430052EFD7 /* Model */ = { + isa = PBXGroup; + children = ( + EB3B73F92DC8FF3A00369951 /* ProtobufSource */, + 69DA9A2729E18CB500A442DA /* Protobuf */, + EB5C36E82E3546100052EFD7 /* DataModel.swift */, + ); + path = Model; + sourceTree = ""; + }; EBC05C6A2E33B55100EECC18 /* View */ = { isa = PBXGroup; children = ( @@ -443,16 +437,6 @@ }; /* End PBXGroup section */ -/* Begin PBXHeadersBuildPhase section */ - 69DCF47D2AB70D0600CBE2CC /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXHeadersBuildPhase section */ - /* Begin PBXNativeTarget section */ 699DEBA02AB0573200115D22 /* QuickDropShareExtension */ = { isa = PBXNativeTarget; @@ -466,7 +450,6 @@ buildRules = ( ); dependencies = ( - 69DCF4A02AB8C9A500CBE2CC /* PBXTargetDependency */, ); name = QuickDropShareExtension; productName = ShareExtension; @@ -488,37 +471,17 @@ ); dependencies = ( 699DEBAD2AB0573200115D22 /* PBXTargetDependency */, - 69DCF4972AB8BF7B00CBE2CC /* PBXTargetDependency */, ); name = QuickDrop; packageProductDependencies = ( EB2F9BFD2DA05F48004010A2 /* BezelNotification */, + EB5C37D92E377AD60052EFD7 /* SwiftProtobuf */, + EB5C37DB2E377AD60052EFD7 /* SwiftECC */, ); productName = NearDrop; productReference = 69DA9A0E29E0BF5100A442DA /* QuickDrop.app */; productType = "com.apple.product-type.application"; }; - 69DCF4802AB70D0600CBE2CC /* NearbyShare */ = { - isa = PBXNativeTarget; - buildConfigurationList = 69DCF4892AB70D0600CBE2CC /* Build configuration list for PBXNativeTarget "NearbyShare" */; - buildPhases = ( - 69DCF47D2AB70D0600CBE2CC /* Headers */, - 69DCF47E2AB70D0600CBE2CC /* Sources */, - 69DCF47F2AB70D0600CBE2CC /* Frameworks */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = NearbyShare; - packageProductDependencies = ( - 69DCF4992AB8C58500CBE2CC /* SwiftProtobuf */, - 69DCF49B2AB8C58500CBE2CC /* SwiftECC */, - ); - productName = NearbyShare; - productReference = 69DCF4812AB70D0600CBE2CC /* libNearbyShare.dylib */; - productType = "com.apple.product-type.library.dynamic"; - }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -536,9 +499,6 @@ CreatedOnToolsVersion = 14.3; LastSwiftMigration = 1430; }; - 69DCF4802AB70D0600CBE2CC = { - CreatedOnToolsVersion = 14.3.1; - }; }; }; buildConfigurationList = 69DA9A0929E0BF5100A442DA /* Build configuration list for PBXProject "QuickDrop" */; @@ -577,7 +537,6 @@ targets = ( 69DA9A0D29E0BF5100A442DA /* QuickDrop */, 699DEBA02AB0573200115D22 /* QuickDropShareExtension */, - 69DCF4802AB70D0600CBE2CC /* NearbyShare */, ); }; /* End PBXProject section */ @@ -623,10 +582,25 @@ EB96ED8B2D7F7CF7004B3FF1 /* SmallSheetView.swift in Sources */, EBCC27F92D80C9F90079C478 /* Zip.swift in Sources */, EB7CD1462D6FB0E800CB0CB5 /* Logger.swift in Sources */, + EB5C37C52E377A780052EFD7 /* securemessage.pb.swift in Sources */, + EB5C37C62E377A780052EFD7 /* securegcm.pb.swift in Sources */, + EB5C37C72E377A780052EFD7 /* wire_format.pb.swift in Sources */, + EB5C37C82E377A780052EFD7 /* ukey.pb.swift in Sources */, + EB5C37C92E377A780052EFD7 /* device_to_device_messages.pb.swift in Sources */, + EB5C37CA2E377A780052EFD7 /* offline_wire_formats.pb.swift in Sources */, EBC05C5C2E3394EF00EECC18 /* ErrorAlertHandler.swift in Sources */, EBC05C7B2E33B58100EECC18 /* ActiveNetworkInterface.swift in Sources */, EBC05C7C2E33B58100EECC18 /* ApIsolationTester.swift in Sources */, 691F53BB2ABB70840089FD92 /* DeviceListCell.swift in Sources */, + EB5C37BB2E377A700052EFD7 /* ConnectionFailureTracker.swift in Sources */, + EB5C37BC2E377A700052EFD7 /* SleepManager.swift in Sources */, + EB5C37D02E377A7F0052EFD7 /* OutboundNearbyConnection.swift in Sources */, + EB5C37D12E377A7F0052EFD7 /* DataModel.swift in Sources */, + EB5C37D22E377A7F0052EFD7 /* NearbyConnection.swift in Sources */, + EB5C37D32E377A7F0052EFD7 /* InboundNearbyConnection.swift in Sources */, + EB5C37D42E377A7F0052EFD7 /* NearbyConnectionManager.swift in Sources */, + EB5C37BD2E377A700052EFD7 /* Helpers.swift in Sources */, + EB5C37BE2E377A700052EFD7 /* SaveFilesManager.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -636,11 +610,26 @@ files = ( EBD2A2172D2860C100BCB8E2 /* WelcomeScreen.swift in Sources */, EB3643602D73A2A2009CDFF8 /* AudioManager.swift in Sources */, + EB5C37B72E377A6F0052EFD7 /* ConnectionFailureTracker.swift in Sources */, + EB5C37B82E377A6F0052EFD7 /* SleepManager.swift in Sources */, + EB5C37B92E377A6F0052EFD7 /* SaveFilesManager.swift in Sources */, + EB5C37BA2E377A6F0052EFD7 /* Helpers.swift in Sources */, EBC05C5B2E33937200EECC18 /* ErrorAlertHandler.swift in Sources */, EBC05C712E33B57D00EECC18 /* Converter.swift in Sources */, EBC05C722E33B57D00EECC18 /* Helper.swift in Sources */, EBC05C732E33B57D00EECC18 /* PlusView.swift in Sources */, + EB5C37BF2E377A780052EFD7 /* ukey.pb.swift in Sources */, + EB5C37C02E377A780052EFD7 /* securemessage.pb.swift in Sources */, + EB5C37C12E377A780052EFD7 /* offline_wire_formats.pb.swift in Sources */, + EB5C37C22E377A780052EFD7 /* device_to_device_messages.pb.swift in Sources */, + EB5C37C32E377A780052EFD7 /* wire_format.pb.swift in Sources */, + EB5C37C42E377A780052EFD7 /* securegcm.pb.swift in Sources */, EBC05C782E33B58100EECC18 /* ActiveNetworkInterface.swift in Sources */, + EB5C37CB2E377A7F0052EFD7 /* NearbyConnection.swift in Sources */, + EB5C37CC2E377A7F0052EFD7 /* DataModel.swift in Sources */, + EB5C37CD2E377A7F0052EFD7 /* OutboundNearbyConnection.swift in Sources */, + EB5C37CE2E377A7F0052EFD7 /* InboundNearbyConnection.swift in Sources */, + EB5C37CF2E377A7F0052EFD7 /* NearbyConnectionManager.swift in Sources */, EBC05C792E33B58100EECC18 /* ApIsolation6Tester.swift in Sources */, EBC05C7A2E33B58100EECC18 /* ApIsolationTester.swift in Sources */, EB96ED8C2D7F7E7B004B3FF1 /* SmallSheetView.swift in Sources */, @@ -657,30 +646,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 69DCF47E2AB70D0600CBE2CC /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 69DCF48D2AB70E8C00CBE2CC /* offline_wire_formats.pb.swift in Sources */, - EBC05C592E32C22200EECC18 /* Localized.swift in Sources */, - EB2F9BFB2DA04B69004010A2 /* Helpers.swift in Sources */, - 69DCF48F2AB70E8C00CBE2CC /* securemessage.pb.swift in Sources */, - 69DCF4912AB70E9700CBE2CC /* NearbyConnection.swift in Sources */, - EB6E743E2DD5C77700331466 /* ConnectionFailureTracker.swift in Sources */, - 69DCF4922AB70E9700CBE2CC /* NearbyConnectionManager.swift in Sources */, - 69DCF48E2AB70E8C00CBE2CC /* securegcm.pb.swift in Sources */, - 691F53BE2ABF03820089FD92 /* OutboundNearbyConnection.swift in Sources */, - 69DCF48C2AB70E8C00CBE2CC /* device_to_device_messages.pb.swift in Sources */, - EB7CD1472D6FB0E800CB0CB5 /* Logger.swift in Sources */, - 69DCF4902AB70E9700CBE2CC /* InboundNearbyConnection.swift in Sources */, - 69DCF48B2AB70E8C00CBE2CC /* ukey.pb.swift in Sources */, - EB2B41E42DC63ACC0070FC39 /* SleepManager.swift in Sources */, - EB5C36E92E3546120052EFD7 /* DataModel.swift in Sources */, - EB5C36E72E3544FF0052EFD7 /* SaveFilesManager.swift in Sources */, - 69DCF48A2AB70E8C00CBE2CC /* wire_format.pb.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -689,16 +654,6 @@ target = 699DEBA02AB0573200115D22 /* QuickDropShareExtension */; targetProxy = 699DEBAC2AB0573200115D22 /* PBXContainerItemProxy */; }; - 69DCF4972AB8BF7B00CBE2CC /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 69DCF4802AB70D0600CBE2CC /* NearbyShare */; - targetProxy = 69DCF4962AB8BF7B00CBE2CC /* PBXContainerItemProxy */; - }; - 69DCF4A02AB8C9A500CBE2CC /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 69DCF4802AB70D0600CBE2CC /* NearbyShare */; - targetProxy = 69DCF49F2AB8C9A500CBE2CC /* PBXContainerItemProxy */; - }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -990,40 +945,6 @@ }; name = Release; }; - 69DCF4872AB70D0600CBE2CC /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - DEAD_CODE_STRIPPING = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@executable_path/../Frameworks"; - EXECUTABLE_PREFIX = lib; - LD_DYLIB_INSTALL_NAME = "$(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(EXECUTABLE_PATH)"; - MACOSX_DEPLOYMENT_TARGET = 11.0; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - SWIFT_VERSION = 5.0; - }; - name = Debug; - }; - 69DCF4882AB70D0600CBE2CC /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_STYLE = Automatic; - DEAD_CODE_STRIPPING = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@executable_path/../Frameworks"; - EXECUTABLE_PREFIX = lib; - LD_DYLIB_INSTALL_NAME = "$(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(EXECUTABLE_PATH)"; - MACOSX_DEPLOYMENT_TARGET = 11.0; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - SWIFT_VERSION = 5.0; - }; - name = Release; - }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -1054,15 +975,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 69DCF4892AB70D0600CBE2CC /* Build configuration list for PBXNativeTarget "NearbyShare" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 69DCF4872AB70D0600CBE2CC /* Debug */, - 69DCF4882AB70D0600CBE2CC /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ @@ -1093,21 +1005,31 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 69DCF4992AB8C58500CBE2CC /* SwiftProtobuf */ = { - isa = XCSwiftPackageProductDependency; - package = 69DA9A2429E189EF00A442DA /* XCRemoteSwiftPackageReference "swift-protobuf" */; - productName = SwiftProtobuf; - }; - 69DCF49B2AB8C58500CBE2CC /* SwiftECC */ = { - isa = XCSwiftPackageProductDependency; - package = 69DA9A3429E1994C00A442DA /* XCRemoteSwiftPackageReference "SwiftECC" */; - productName = SwiftECC; - }; EB2F9BFD2DA05F48004010A2 /* BezelNotification */ = { isa = XCSwiftPackageProductDependency; package = EB2F9BFC2DA05F48004010A2 /* XCRemoteSwiftPackageReference "BezelNotification" */; productName = BezelNotification; }; + EB5C37D52E377ACF0052EFD7 /* SwiftProtobuf */ = { + isa = XCSwiftPackageProductDependency; + package = 69DA9A2429E189EF00A442DA /* XCRemoteSwiftPackageReference "swift-protobuf" */; + productName = SwiftProtobuf; + }; + EB5C37D72E377ACF0052EFD7 /* SwiftECC */ = { + isa = XCSwiftPackageProductDependency; + package = 69DA9A3429E1994C00A442DA /* XCRemoteSwiftPackageReference "SwiftECC" */; + productName = SwiftECC; + }; + EB5C37D92E377AD60052EFD7 /* SwiftProtobuf */ = { + isa = XCSwiftPackageProductDependency; + package = 69DA9A2429E189EF00A442DA /* XCRemoteSwiftPackageReference "swift-protobuf" */; + productName = SwiftProtobuf; + }; + EB5C37DB2E377AD60052EFD7 /* SwiftECC */ = { + isa = XCSwiftPackageProductDependency; + package = 69DA9A3429E1994C00A442DA /* XCRemoteSwiftPackageReference "SwiftECC" */; + productName = SwiftECC; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 69DA9A0629E0BF5100A442DA /* Project object */; diff --git a/QuickDrop/AppDelegate.swift b/QuickDrop/AppDelegate.swift index 46b70c9..7f47ed1 100644 --- a/QuickDrop/AppDelegate.swift +++ b/QuickDrop/AppDelegate.swift @@ -8,7 +8,6 @@ import AudioToolbox import BezelNotification import Cocoa -import NearbyShare import Network import StoreKit import SwiftUI diff --git a/QuickDrop/Plus/PlusView.swift b/QuickDrop/Plus/PlusView.swift index 6858df0..41e674b 100644 --- a/QuickDrop/Plus/PlusView.swift +++ b/QuickDrop/Plus/PlusView.swift @@ -7,7 +7,6 @@ import SwiftUI import StoreKit -import NearbyShare let plusViewWidth: CGFloat = 440 let plusViewHeight: CGFloat = 200 diff --git a/QuickDrop/Util/ErrorAlertHandler.swift b/QuickDrop/Util/ErrorAlertHandler.swift index 78ebc96..4bedf22 100644 --- a/QuickDrop/Util/ErrorAlertHandler.swift +++ b/QuickDrop/Util/ErrorAlertHandler.swift @@ -8,7 +8,6 @@ import AudioToolbox import BezelNotification import Cocoa -import NearbyShare import Network import StoreKit import SwiftUI diff --git a/QuickDrop/View/SettingsView.swift b/QuickDrop/View/SettingsView.swift index 8e6bade..f301601 100644 --- a/QuickDrop/View/SettingsView.swift +++ b/QuickDrop/View/SettingsView.swift @@ -7,7 +7,6 @@ import SwiftUI import AppKit -import NearbyShare struct SettingsView: View { diff --git a/QuickDrop/View/TutorialView.swift b/QuickDrop/View/TutorialView.swift index 2494dd1..34970fd 100644 --- a/QuickDrop/View/TutorialView.swift +++ b/QuickDrop/View/TutorialView.swift @@ -6,7 +6,6 @@ // import SwiftUI -import NearbyShare struct TutorialView: View { diff --git a/QuickDrop/View/WelcomeScreen.swift b/QuickDrop/View/WelcomeScreen.swift index 0dd95aa..e0fd570 100644 --- a/QuickDrop/View/WelcomeScreen.swift +++ b/QuickDrop/View/WelcomeScreen.swift @@ -6,7 +6,6 @@ // import SwiftUI -import NearbyShare import UniformTypeIdentifiers struct WelcomeScreen: View { diff --git a/ShareExtension/ShareViewController.swift b/ShareExtension/ShareViewController.swift index a5fce8f..ffa358a 100644 --- a/ShareExtension/ShareViewController.swift +++ b/ShareExtension/ShareViewController.swift @@ -7,7 +7,6 @@ import Cocoa import Foundation -import NearbyShare import SwiftUI class ShareViewController: NSViewController, ShareExtensionDelegate { From 273b7459d81fe7f1a486121d0b05754a9e33ee34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20B=C3=B6ttger?= Date: Mon, 28 Jul 2025 11:57:50 +0200 Subject: [PATCH 03/20] Fix compilation for iOS --- .../Connection/InboundNearbyConnection.swift | 33 ++- .../Connection/NearbyConnectionManager.swift | 18 +- .../Connection/OutboundNearbyConnection.swift | 6 +- .../Util/Localized.swift | 0 NearbyShare/Util/SaveFilesManager.swift | 13 +- NearbyShare/Util/SleepManager.swift | 30 ++- QuickDrop.xcodeproj/project.pbxproj | 197 +++++++++++++++++- .../xcshareddata/xcschemes/QuickDrop.xcscheme | 2 +- .../xcschemes/ShareExtension.xcscheme | 2 +- .../AccentColor.colorset/Contents.json | 11 + .../AppIcon.appiconset/Contents.json | 35 ++++ QuickDropiOS/Assets.xcassets/Contents.json | 6 + QuickDropiOS/ContentView.swift | 24 +++ QuickDropiOS/QuickDropiOSApp.swift | 17 ++ 14 files changed, 367 insertions(+), 27 deletions(-) rename {QuickDrop => NearbyShare}/Util/Localized.swift (100%) create mode 100644 QuickDropiOS/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 QuickDropiOS/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 QuickDropiOS/Assets.xcassets/Contents.json create mode 100644 QuickDropiOS/ContentView.swift create mode 100644 QuickDropiOS/QuickDropiOSApp.swift diff --git a/NearbyShare/Connection/InboundNearbyConnection.swift b/NearbyShare/Connection/InboundNearbyConnection.swift index dd3c832..bb32b03 100644 --- a/NearbyShare/Connection/InboundNearbyConnection.swift +++ b/NearbyShare/Connection/InboundNearbyConnection.swift @@ -5,7 +5,6 @@ // Created by Grishka on 08.04.2023. // -import AppKit import CommonCrypto import CryptoKit import Foundation @@ -14,6 +13,12 @@ import System import BigInt import SwiftECC +#if os(macOS) +import AppKit +#else +import UIKit +#endif + class InboundNearbyConnection: NearbyConnection { private var filesToBeReceived: [Int64: InternalFileInfo] = [:] @@ -148,7 +153,9 @@ class InboundNearbyConnection: NearbyConnection { else if (frame.payloadChunk.flags & 1) == 1 { try fileInfo.fileHandle?.close() filesToBeReceived[id]!.fileHandle = nil + #if os(macOS) fileInfo.progress?.unpublish() + #endif SaveFilesManager.shared.registerFileFinishedDownloading(fileInfo.destinationURL) filesToBeReceived.removeValue(forKey: id) @@ -166,15 +173,27 @@ class InboundNearbyConnection: NearbyConnection { if let urlStr = String(data: payload, encoding: .utf8) { if isPlainTextTransfer { - // paste to clipboard + #if os(macOS) + // macOS clipboard let pasteboard = NSPasteboard.general pasteboard.clearContents() pasteboard.setString(urlStr, forType: .string) NearbyConnectionManager.shared.mainAppDelegate?.showCopiedToClipboardAlert() - } - else if let url = URL(string: urlStr) { + + #elseif os(iOS) + // iOS clipboard + UIPasteboard.general.string = urlStr + + // Optionally show an alert (requires a way to present it) + // For example, post a notification or use a delegate to show a toast or alert + #endif + } else if let url = URL(string: urlStr) { + #if os(macOS) NSWorkspace.shared.open(url) + #elseif os(iOS) + UIApplication.shared.open(url, options: [:], completionHandler: nil) + #endif } } @@ -188,7 +207,9 @@ class InboundNearbyConnection: NearbyConnection { fileInfo.progress?.completedUnitCount = filesToBeReceived[id]!.bytesTransferred try fileInfo.fileHandle?.close() filesToBeReceived[id]!.fileHandle = nil + #if os(macOS) fileInfo.progress?.unpublish() + #endif filesToBeReceived.removeValue(forKey: id) SaveFilesManager.shared.registerFileFinishedDownloading(fileInfo.destinationURL) @@ -477,7 +498,9 @@ class InboundNearbyConnection: NearbyConnection { progress.totalUnitCount = file.meta.size progress.kind = .file progress.isPausable = false + #if os(macOS) progress.publish() + #endif filesToBeReceived[id]!.progress = progress filesToBeReceived[id]!.created = true } @@ -518,9 +541,11 @@ class InboundNearbyConnection: NearbyConnection { private func deletePartiallyReceivedFiles() { for (_, file) in filesToBeReceived { + #if os(macOS) if let progress = file.progress { progress.unpublish() } + #endif guard file.created else { continue } do { diff --git a/NearbyShare/Connection/NearbyConnectionManager.swift b/NearbyShare/Connection/NearbyConnectionManager.swift index c1f29ee..2ebfe39 100644 --- a/NearbyShare/Connection/NearbyConnectionManager.swift +++ b/NearbyShare/Connection/NearbyConnectionManager.swift @@ -9,6 +9,9 @@ import Foundation import Network import System +#if os(iOS) +import UIKit +#endif public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearbyConnectionDelegate, OutboundNearbyConnectionDelegate { @@ -90,20 +93,29 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb ] let name = Data(nameBytes).urlSafeBase64EncodedString() - let endpointInfo = EndpointInfo(name: Host.current().localizedName!, deviceType: .computer) - let port = Int32(tcpListener.port!.rawValue) mdnsServices = serviceTypes.map { serviceType in let service = NetService(domain: "", type: serviceType, name: name, port: port) service.delegate = self service.setTXTRecord(NetService.data(fromTXTRecord: [ - "n": endpointInfo.serialize().urlSafeBase64EncodedString().data(using: .utf8)!, + "n": getEndpointInfo().serialize().urlSafeBase64EncodedString().data(using: .utf8)!, ])) service.publish() return service } } + + + func getEndpointInfo() -> EndpointInfo { + #if os(macOS) + let endpointInfo = EndpointInfo(name: Host.current().localizedName!, deviceType: .computer) + #else + let endpointInfo = EndpointInfo(name: UIDevice.current.name, deviceType: .phone) + #endif + + return endpointInfo + } func obtainUserConsent(for transfer: TransferMetadata, from device: RemoteDeviceInfo, connection _: InboundNearbyConnection) { diff --git a/NearbyShare/Connection/OutboundNearbyConnection.swift b/NearbyShare/Connection/OutboundNearbyConnection.swift index 7c83375..649b940 100644 --- a/NearbyShare/Connection/OutboundNearbyConnection.swift +++ b/NearbyShare/Connection/OutboundNearbyConnection.swift @@ -165,14 +165,16 @@ class OutboundNearbyConnection: NearbyConnection { private func sendConnectionRequest() throws { + + let endpointInfo = NearbyConnectionManager.shared.getEndpointInfo() + var frame = Location_Nearby_Connections_OfflineFrame() frame.version = .v1 frame.v1 = Location_Nearby_Connections_V1Frame() frame.v1.type = .connectionRequest frame.v1.connectionRequest = Location_Nearby_Connections_ConnectionRequestFrame() frame.v1.connectionRequest.endpointID = String(bytes: NearbyConnectionManager.shared.endpointID, encoding: .ascii)! - frame.v1.connectionRequest.endpointName = Host.current().localizedName! - let endpointInfo = EndpointInfo(name: Host.current().localizedName!, deviceType: .computer) + frame.v1.connectionRequest.endpointName = endpointInfo.name frame.v1.connectionRequest.endpointInfo = endpointInfo.serialize() frame.v1.connectionRequest.mediums = [.wifiLan] try sendFrameAsync(frame.serializedData()) diff --git a/QuickDrop/Util/Localized.swift b/NearbyShare/Util/Localized.swift similarity index 100% rename from QuickDrop/Util/Localized.swift rename to NearbyShare/Util/Localized.swift diff --git a/NearbyShare/Util/SaveFilesManager.swift b/NearbyShare/Util/SaveFilesManager.swift index 73b3e71..f6239b3 100644 --- a/NearbyShare/Util/SaveFilesManager.swift +++ b/NearbyShare/Util/SaveFilesManager.swift @@ -6,8 +6,10 @@ // import Foundation -import AppKit import ImageIO +#if os(macOS) +import AppKit +#endif public class SaveFilesManager { @@ -88,11 +90,15 @@ public class SaveFilesManager { progress.totalUnitCount = 10 progress.kind = .file progress.isPausable = false + #if os(macOS) progress.publish() + #endif DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { progress.completedUnitCount = 10 + #if os(macOS) progress.unpublish() + #endif } try fileManager.removeItem(at: file) @@ -110,10 +116,12 @@ public class SaveFilesManager { if !filesFinishedDownloadingSinceLastRun.isEmpty && !isFileTransferRestricted() { + #if os(macOS) if UserDefaults.standard.bool(forKey: UserDefaultsKeys.openFinderAfterReceiving.rawValue) { log("Opening \(filesFinishedDownloadingSinceLastRun.count) file(s) in Finder.") NSWorkspace.shared.activateFileViewerSelecting(filesFinishedDownloadingSinceLastRun) } + #endif // Clear the list of finished files filesFinishedDownloadingSinceLastRun.removeAll() @@ -143,6 +151,8 @@ public class SaveFilesManager { if let bookmarkData = UserDefaults.standard.data(forKey: UserDefaultsKeys.saveFolderBookmark.rawValue) { var isStale = false + // Not supported on iOS + #if os(macOS) do { let url = try URL(resolvingBookmarkData: bookmarkData, options: .withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale) @@ -160,6 +170,7 @@ public class SaveFilesManager { } catch { log("Failed to resolve bookmark: \(error), using default downloads folder.") } + #endif } do { diff --git a/NearbyShare/Util/SleepManager.swift b/NearbyShare/Util/SleepManager.swift index 01da003..b56a537 100644 --- a/NearbyShare/Util/SleepManager.swift +++ b/NearbyShare/Util/SleepManager.swift @@ -6,12 +6,21 @@ // import Foundation + +#if os(macOS) import IOKit import IOKit.pwr_mgt +#elseif os(iOS) +import UIKit +#endif class SleepManager { public static let shared = SleepManager() + + #if os(macOS) private var assertionID: IOPMAssertionID = 0 + #endif + private var timer: Timer? private init() { @@ -24,28 +33,35 @@ class SleepManager { } private func disableSleep() { - if assertionID != 0 { - return - } + #if os(macOS) + if assertionID != 0 { return } log("[SleepManager] Enabling Wakelock") - + IOPMAssertionCreateWithName( kIOPMAssertionTypePreventUserIdleDisplaySleep as CFString, IOPMAssertionLevel(kIOPMAssertionLevelOn), "QuickDrop Data Transfer" as CFString, &assertionID ) + #elseif os(iOS) + log("[SleepManager] Keeping screen awake (iOS)") + UIApplication.shared.isIdleTimerDisabled = true + #endif } private func enableSleep() { - if assertionID == 0 { - return - } + #if os(macOS) + if assertionID == 0 { return } log("[SleepManager] Disabling Wakelock") + IOPMAssertionRelease(assertionID) assertionID = 0 + #elseif os(iOS) + log("[SleepManager] Allowing screen to sleep (iOS)") + UIApplication.shared.isIdleTimerDisabled = false + #endif } private func updateSleepState() { diff --git a/QuickDrop.xcodeproj/project.pbxproj b/QuickDrop.xcodeproj/project.pbxproj index 5dc89e5..5a034a4 100644 --- a/QuickDrop.xcodeproj/project.pbxproj +++ b/QuickDrop.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 60; + objectVersion = 70; objects = { /* Begin PBXBuildFile section */ @@ -55,6 +55,25 @@ EB5C37D82E377ACF0052EFD7 /* SwiftECC in Frameworks */ = {isa = PBXBuildFile; productRef = EB5C37D72E377ACF0052EFD7 /* SwiftECC */; }; EB5C37DA2E377AD60052EFD7 /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = EB5C37D92E377AD60052EFD7 /* SwiftProtobuf */; }; EB5C37DC2E377AD60052EFD7 /* SwiftECC in Frameworks */ = {isa = PBXBuildFile; productRef = EB5C37DB2E377AD60052EFD7 /* SwiftECC */; }; + EB5C37EE2E377C3F0052EFD7 /* SwiftProtobuf in Frameworks */ = {isa = PBXBuildFile; productRef = EB5C37ED2E377C3F0052EFD7 /* SwiftProtobuf */; }; + EB5C37F02E377C3F0052EFD7 /* SwiftECC in Frameworks */ = {isa = PBXBuildFile; productRef = EB5C37EF2E377C3F0052EFD7 /* SwiftECC */; }; + EB5C37F12E377CE70052EFD7 /* ukey.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2829E18CB500A442DA /* ukey.pb.swift */; }; + EB5C37F22E377CE70052EFD7 /* NearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 698DFAE529E2F91A0064F247 /* NearbyConnection.swift */; }; + EB5C37F32E377CE70052EFD7 /* SaveFilesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C36E62E3544FB0052EFD7 /* SaveFilesManager.swift */; }; + EB5C37F42E377CE70052EFD7 /* NearbyConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A1E29E0C0B300A442DA /* NearbyConnectionManager.swift */; }; + EB5C37F52E377CE70052EFD7 /* securemessage.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2D29E18CB500A442DA /* securemessage.pb.swift */; }; + EB5C37F62E377CE70052EFD7 /* device_to_device_messages.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2A29E18CB500A442DA /* device_to_device_messages.pb.swift */; }; + EB5C37F72E377CE70052EFD7 /* ConnectionFailureTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB6E743D2DD5C76F00331466 /* ConnectionFailureTracker.swift */; }; + EB5C37F82E377CE70052EFD7 /* SleepManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB2B41E32DC63AC80070FC39 /* SleepManager.swift */; }; + EB5C37F92E377CE70052EFD7 /* securegcm.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2C29E18CB500A442DA /* securegcm.pb.swift */; }; + EB5C37FA2E377CE70052EFD7 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB2F9BFA2DA04B67004010A2 /* Helpers.swift */; }; + EB5C37FB2E377CE70052EFD7 /* InboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2229E17F0400A442DA /* InboundNearbyConnection.swift */; }; + EB5C37FC2E377CE70052EFD7 /* OutboundNearbyConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 691F53BD2ABF03820089FD92 /* OutboundNearbyConnection.swift */; }; + EB5C37FD2E377CE70052EFD7 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */; }; + EB5C37FE2E377CE70052EFD7 /* offline_wire_formats.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2B29E18CB500A442DA /* offline_wire_formats.pb.swift */; }; + EB5C37FF2E377CE70052EFD7 /* wire_format.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2929E18CB500A442DA /* wire_format.pb.swift */; }; + EB5C38002E377CE70052EFD7 /* DataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C36E82E3546100052EFD7 /* DataModel.swift */; }; + EB5C38012E377DE60052EFD7 /* Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB96ED8E2D7F8103004B3FF1 /* Localized.swift */; }; EB6F62D72D91F4AF00534EC2 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB6F62D62D91F4AC00534EC2 /* SettingsView.swift */; }; EB7CD1452D6FB0C300CB0CB5 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */; }; EB7CD1462D6FB0E800CB0CB5 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */; }; @@ -181,6 +200,7 @@ EB5C36E32E34EC0E0052EFD7 /* License.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = License.txt; sourceTree = ""; }; EB5C36E62E3544FB0052EFD7 /* SaveFilesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveFilesManager.swift; sourceTree = ""; }; EB5C36E82E3546100052EFD7 /* DataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataModel.swift; sourceTree = ""; }; + EB5C37E22E377C2E0052EFD7 /* QuickDropiOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = QuickDropiOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; EB6E743D2DD5C76F00331466 /* ConnectionFailureTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionFailureTracker.swift; sourceTree = ""; }; EB6F62D62D91F4AC00534EC2 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; @@ -204,6 +224,10 @@ EBD2A2162D2860BE00BCB8E2 /* WelcomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeScreen.swift; sourceTree = ""; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedRootGroup section */ + EB5C37E32E377C2E0052EFD7 /* QuickDropiOS */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = QuickDropiOS; sourceTree = ""; }; +/* End PBXFileSystemSynchronizedRootGroup section */ + /* Begin PBXFrameworksBuildPhase section */ 699DEB9E2AB0573200115D22 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; @@ -224,6 +248,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + EB5C37DF2E377C2E0052EFD7 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + EB5C37F02E377C3F0052EFD7 /* SwiftECC in Frameworks */, + EB5C37EE2E377C3F0052EFD7 /* SwiftProtobuf in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -254,6 +287,7 @@ 69DA9A1029E0BF5100A442DA /* QuickDrop */, 699DEBA22AB0573200115D22 /* ShareExtension */, 69DCF4822AB70D0600CBE2CC /* NearbyShare */, + EB5C37E32E377C2E0052EFD7 /* QuickDropiOS */, 698DFAEC29E353220064F247 /* Frameworks */, 69DA9A0F29E0BF5100A442DA /* Products */, EBC05C8B2E33B59E00EECC18 /* Resources */, @@ -265,6 +299,7 @@ children = ( 69DA9A0E29E0BF5100A442DA /* QuickDrop.app */, 699DEBA12AB0573200115D22 /* QuickDropShareExtension.appex */, + EB5C37E22E377C2E0052EFD7 /* QuickDropiOS.app */, ); name = Products; sourceTree = ""; @@ -323,6 +358,7 @@ EB5C371C2E3669010052EFD7 /* Util */ = { isa = PBXGroup; children = ( + EB96ED8E2D7F8103004B3FF1 /* Localized.swift */, EB2B41E32DC63AC80070FC39 /* SleepManager.swift */, EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */, EB5C36E62E3544FB0052EFD7 /* SaveFilesManager.swift */, @@ -390,7 +426,6 @@ children = ( EB36435F2D73A29F009CDFF8 /* AudioManager.swift */, EBC05C5A2E33936C00EECC18 /* ErrorAlertHandler.swift */, - EB96ED8E2D7F8103004B3FF1 /* Localized.swift */, ); path = Util; sourceTree = ""; @@ -482,6 +517,30 @@ productReference = 69DA9A0E29E0BF5100A442DA /* QuickDrop.app */; productType = "com.apple.product-type.application"; }; + EB5C37E12E377C2E0052EFD7 /* QuickDropiOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = EB5C37EA2E377C310052EFD7 /* Build configuration list for PBXNativeTarget "QuickDropiOS" */; + buildPhases = ( + EB5C37DE2E377C2E0052EFD7 /* Sources */, + EB5C37DF2E377C2E0052EFD7 /* Frameworks */, + EB5C37E02E377C2E0052EFD7 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + EB5C37E32E377C2E0052EFD7 /* QuickDropiOS */, + ); + name = QuickDropiOS; + packageProductDependencies = ( + EB5C37ED2E377C3F0052EFD7 /* SwiftProtobuf */, + EB5C37EF2E377C3F0052EFD7 /* SwiftECC */, + ); + productName = QuickDropiOS; + productReference = EB5C37E22E377C2E0052EFD7 /* QuickDropiOS.app */; + productType = "com.apple.product-type.application"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -489,8 +548,8 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = 1; - LastSwiftUpdateCheck = 1430; - LastUpgradeCheck = 1620; + LastSwiftUpdateCheck = 1640; + LastUpgradeCheck = 1640; TargetAttributes = { 699DEBA02AB0573200115D22 = { CreatedOnToolsVersion = 14.3.1; @@ -499,6 +558,9 @@ CreatedOnToolsVersion = 14.3; LastSwiftMigration = 1430; }; + EB5C37E12E377C2E0052EFD7 = { + CreatedOnToolsVersion = 16.4; + }; }; }; buildConfigurationList = 69DA9A0929E0BF5100A442DA /* Build configuration list for PBXProject "QuickDrop" */; @@ -537,6 +599,7 @@ targets = ( 69DA9A0D29E0BF5100A442DA /* QuickDrop */, 699DEBA02AB0573200115D22 /* QuickDropShareExtension */, + EB5C37E12E377C2E0052EFD7 /* QuickDropiOS */, ); }; /* End PBXProject section */ @@ -566,6 +629,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + EB5C37E02E377C2E0052EFD7 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -646,6 +716,30 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + EB5C37DE2E377C2E0052EFD7 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + EB5C37F12E377CE70052EFD7 /* ukey.pb.swift in Sources */, + EB5C37F22E377CE70052EFD7 /* NearbyConnection.swift in Sources */, + EB5C37F32E377CE70052EFD7 /* SaveFilesManager.swift in Sources */, + EB5C37F42E377CE70052EFD7 /* NearbyConnectionManager.swift in Sources */, + EB5C37F52E377CE70052EFD7 /* securemessage.pb.swift in Sources */, + EB5C37F62E377CE70052EFD7 /* device_to_device_messages.pb.swift in Sources */, + EB5C37F72E377CE70052EFD7 /* ConnectionFailureTracker.swift in Sources */, + EB5C37F82E377CE70052EFD7 /* SleepManager.swift in Sources */, + EB5C38012E377DE60052EFD7 /* Localized.swift in Sources */, + EB5C37F92E377CE70052EFD7 /* securegcm.pb.swift in Sources */, + EB5C37FA2E377CE70052EFD7 /* Helpers.swift in Sources */, + EB5C37FB2E377CE70052EFD7 /* InboundNearbyConnection.swift in Sources */, + EB5C37FC2E377CE70052EFD7 /* OutboundNearbyConnection.swift in Sources */, + EB5C37FD2E377CE70052EFD7 /* Logger.swift in Sources */, + EB5C37FE2E377CE70052EFD7 /* offline_wire_formats.pb.swift in Sources */, + EB5C37FF2E377CE70052EFD7 /* wire_format.pb.swift in Sources */, + EB5C38002E377CE70052EFD7 /* DataModel.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -679,7 +773,6 @@ COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = "${SHARED_BUILD}"; DEAD_CODE_STRIPPING = YES; - DEVELOPMENT_TEAM = 92SDAWLN76; ENABLE_HARDENED_RUNTIME = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = ShareExtension/Info.plist; @@ -712,7 +805,6 @@ COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = "${SHARED_BUILD}"; DEAD_CODE_STRIPPING = YES; - DEVELOPMENT_TEAM = 92SDAWLN76; ENABLE_HARDENED_RUNTIME = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = ShareExtension/Info.plist; @@ -772,6 +864,7 @@ COPY_PHASE_STRIP = NO; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 92SDAWLN76; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -840,6 +933,7 @@ COPY_PHASE_STRIP = NO; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 92SDAWLN76; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; @@ -877,7 +971,6 @@ COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = "${SHARED_BUILD}"; DEAD_CODE_STRIPPING = YES; - DEVELOPMENT_TEAM = 92SDAWLN76; ENABLE_HARDENED_RUNTIME = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = QuickDrop/Info.plist; @@ -918,7 +1011,6 @@ COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = "${SHARED_BUILD}"; DEAD_CODE_STRIPPING = YES; - DEVELOPMENT_TEAM = 92SDAWLN76; ENABLE_HARDENED_RUNTIME = YES; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = QuickDrop/Info.plist; @@ -945,6 +1037,76 @@ }; name = Release; }; + EB5C37EB2E377C310052EFD7 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_PREVIEWS = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 18.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.leonboettger.neardrop; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + EB5C37EC2E377C310052EFD7 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + ENABLE_PREVIEWS = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 18.5; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.leonboettger.neardrop; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -975,6 +1137,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + EB5C37EA2E377C310052EFD7 /* Build configuration list for PBXNativeTarget "QuickDropiOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + EB5C37EB2E377C310052EFD7 /* Debug */, + EB5C37EC2E377C310052EFD7 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ @@ -1030,6 +1201,16 @@ package = 69DA9A3429E1994C00A442DA /* XCRemoteSwiftPackageReference "SwiftECC" */; productName = SwiftECC; }; + EB5C37ED2E377C3F0052EFD7 /* SwiftProtobuf */ = { + isa = XCSwiftPackageProductDependency; + package = 69DA9A2429E189EF00A442DA /* XCRemoteSwiftPackageReference "swift-protobuf" */; + productName = SwiftProtobuf; + }; + EB5C37EF2E377C3F0052EFD7 /* SwiftECC */ = { + isa = XCSwiftPackageProductDependency; + package = 69DA9A3429E1994C00A442DA /* XCRemoteSwiftPackageReference "SwiftECC" */; + productName = SwiftECC; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 69DA9A0629E0BF5100A442DA /* Project object */; diff --git a/QuickDrop.xcodeproj/xcshareddata/xcschemes/QuickDrop.xcscheme b/QuickDrop.xcodeproj/xcshareddata/xcschemes/QuickDrop.xcscheme index 10ab85a..6e539b4 100644 --- a/QuickDrop.xcodeproj/xcshareddata/xcschemes/QuickDrop.xcscheme +++ b/QuickDrop.xcodeproj/xcshareddata/xcschemes/QuickDrop.xcscheme @@ -1,6 +1,6 @@ Date: Mon, 28 Jul 2025 12:29:22 +0200 Subject: [PATCH 04/20] WIP: Add SwiftUI browse view --- NearbyShare/Model/DataModel.swift | 2 +- NearbyShare/Util/SleepManager.swift | 15 +++--- QuickDropiOS/ContentView.swift | 50 +++++++++++++++++-- QuickDropiOS/Model.swift | 75 +++++++++++++++++++++++++++++ 4 files changed, 130 insertions(+), 12 deletions(-) create mode 100644 QuickDropiOS/Model.swift diff --git a/NearbyShare/Model/DataModel.swift b/NearbyShare/Model/DataModel.swift index 1d0927f..4586d21 100644 --- a/NearbyShare/Model/DataModel.swift +++ b/NearbyShare/Model/DataModel.swift @@ -44,7 +44,7 @@ public protocol ShareExtensionDelegate: AnyObject { } -public struct RemoteDeviceInfo { +public struct RemoteDeviceInfo: Identifiable, Equatable { public let name: String public let type: DeviceType public var id: String? diff --git a/NearbyShare/Util/SleepManager.swift b/NearbyShare/Util/SleepManager.swift index b56a537..ac026a2 100644 --- a/NearbyShare/Util/SleepManager.swift +++ b/NearbyShare/Util/SleepManager.swift @@ -19,6 +19,8 @@ class SleepManager { #if os(macOS) private var assertionID: IOPMAssertionID = 0 + #else + private var assertionID: Int = 0 #endif private var timer: Timer? @@ -33,11 +35,12 @@ class SleepManager { } private func disableSleep() { - #if os(macOS) + if assertionID != 0 { return } log("[SleepManager] Enabling Wakelock") - + + #if os(macOS) IOPMAssertionCreateWithName( kIOPMAssertionTypePreventUserIdleDisplaySleep as CFString, IOPMAssertionLevel(kIOPMAssertionLevelOn), @@ -45,23 +48,23 @@ class SleepManager { &assertionID ) #elseif os(iOS) - log("[SleepManager] Keeping screen awake (iOS)") UIApplication.shared.isIdleTimerDisabled = true + assertionID = 1 #endif } private func enableSleep() { - #if os(macOS) if assertionID == 0 { return } log("[SleepManager] Disabling Wakelock") + #if os(macOS) IOPMAssertionRelease(assertionID) - assertionID = 0 #elseif os(iOS) - log("[SleepManager] Allowing screen to sleep (iOS)") UIApplication.shared.isIdleTimerDisabled = false #endif + + assertionID = 0 } private func updateSleepState() { diff --git a/QuickDropiOS/ContentView.swift b/QuickDropiOS/ContentView.swift index b60dec9..387dbce 100644 --- a/QuickDropiOS/ContentView.swift +++ b/QuickDropiOS/ContentView.swift @@ -8,14 +8,54 @@ import SwiftUI struct ContentView: View { + var body: some View { + DeviceListView() + } +} + +struct DeviceListView: View { + @StateObject var model = ShareViewModel() + var body: some View { VStack { - Image(systemName: "globe") - .imageScale(.large) - .foregroundStyle(.tint) - Text("Hello, world!") + Text("Available Devices") + .font(.title) + .padding() + + if model.foundDevices.isEmpty { + Text("Searching for devices...") + .italic() + .padding() + } else { + List(model.foundDevices) { device in + HStack { + Image(systemName: "smartphone") + .resizable() + .frame(width: 30, height: 30) + Text(device.name.isEmpty ? "Android" : device.name) + Spacer() + + if model.selectedDevice == device { + ProgressView() + .progressViewStyle(CircularProgressViewStyle()) + } + } + .contentShape(Rectangle()) // Make entire row tappable + .onTapGesture { + model.selectDevice(device: device) + } + .background(model.selectedDevice == device ? Color.blue.opacity(0.2) : Color.clear) + } + .listStyle(PlainListStyle()) + } } - .padding() + .frame(minWidth: 300, minHeight: 400) + } +} + +struct DeviceListView_Previews: PreviewProvider { + static var previews: some View { + DeviceListView() } } diff --git a/QuickDropiOS/Model.swift b/QuickDropiOS/Model.swift new file mode 100644 index 0000000..7081d46 --- /dev/null +++ b/QuickDropiOS/Model.swift @@ -0,0 +1,75 @@ +// +// Model.swift +// QuickDrop +// +// Created by Leon Böttger on 28.07.25. +// + +import SwiftUI + +class ShareViewModel: ObservableObject, ShareExtensionDelegate { + + @Published var foundDevices: [RemoteDeviceInfo] = [] + @Published var selectedDevice: RemoteDeviceInfo? + @Published var lastError: Error? + + @Published var progressState: String? = nil + @Published var progressValue: Double? = nil + + private var urls: [URL] = [] + private var textToSend: String? = nil + //private var errorAlertHandler = ErrorAlertHandler.shared + + private var connectionEstablished = false + private var timeoutDispatchWorkItem: DispatchWorkItem? = nil + + init() { + NearbyConnectionManager.shared.startDeviceDiscovery() + } + + func addDevice(device: RemoteDeviceInfo) { + withAnimation { + foundDevices.append(device) + } + } + + func removeDevice(id: String) { + withAnimation { + foundDevices.removeAll { $0.id == id } + } + } + + func connectionWasEstablished(pinCode: String) { + connectionEstablished = true + progressState = String(format: "PinCode".localized(), arguments: [pinCode]) + progressValue = 0 + } + + func connectionFailed(with error: any Error) { + progressValue = nil + lastError = error + + //ErrorAlertHandler.shared.showErrorAlert(for: chosenDevice?.name ?? "", error: error) + } + + func transferAccepted() { + progressState = "Sending".localized() + } + + func transferProgress(progress: Double) { + progressValue = progress + } + + func transferFinished() { + progressState = "TransferFinished".localized() + } + + func selectDevice(device: RemoteDeviceInfo) { + NearbyConnectionManager.shared.stopDeviceDiscovery() + + progressValue = 0 + progressState = "Connecting".localized() + selectedDevice = device + NearbyConnectionManager.shared.startOutgoingTransfer(deviceID: device.id!, delegate: self, urls: urls, textToSend: textToSend) + } +} From ddd9a320235441f5c69bcdae814ebae110eb8e68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20B=C3=B6ttger?= Date: Mon, 28 Jul 2025 14:10:16 +0200 Subject: [PATCH 05/20] Better error message for failed signature check, throw error on canceled file transfer --- .../Connection/InboundNearbyConnection.swift | 2 + NearbyShare/Connection/NearbyConnection.swift | 13 +- .../Connection/OutboundNearbyConnection.swift | 6 +- QuickDrop/Localizable.xcstrings | 213 +++++++++++++++++- 4 files changed, 227 insertions(+), 7 deletions(-) diff --git a/NearbyShare/Connection/InboundNearbyConnection.swift b/NearbyShare/Connection/InboundNearbyConnection.swift index bb32b03..c083a04 100644 --- a/NearbyShare/Connection/InboundNearbyConnection.swift +++ b/NearbyShare/Connection/InboundNearbyConnection.swift @@ -101,6 +101,8 @@ class InboundNearbyConnection: NearbyConnection { override func processTransferSetupFrame(_ frame: Sharing_Nearby_Frame) throws { if frame.hasV1 && frame.v1.hasType, case .cancel = frame.v1.type { + self.lastError = NearbyError.canceled(reason: .userCanceled) + self.cancelled = true log("[InboundNearbyConnection] Transfer canceled") try sendDisconnectionAndDisconnect() return diff --git a/NearbyShare/Connection/NearbyConnection.swift b/NearbyShare/Connection/NearbyConnection.swift index 3eeb9d6..50bb45d 100644 --- a/NearbyShare/Connection/NearbyConnection.swift +++ b/NearbyShare/Connection/NearbyConnection.swift @@ -26,6 +26,7 @@ class NearbyConnection { var encryptionDone: Bool = false var lastError: Error? var bytesTransferred: Int64 = 0 + var cancelled: Bool = false private var payloadBuffers: [Int64: NSMutableData] = [:] private var connectionClosed: Bool = false @@ -299,7 +300,7 @@ class NearbyConnection { guard smsg.hasSignature, smsg.hasHeaderAndBody else { throw NearbyError.requiredFieldMissing("secureMessage.signature|headerAndBody") } let hmac = Data(HMAC.authenticationCode(for: smsg.headerAndBody, using: recvHmacKey!)) - guard hmac == smsg.signature else { throw NearbyError.protocolError("hmac!=signature: Expected \(hmac), got \(smsg.signature)") } + guard hmac == smsg.signature else { throw NearbyError.protocolError("Error.Signature".localized()) } let headerAndBody = try Securemessage_HeaderAndBody(serializedBytes: smsg.headerAndBody) var decryptedData = Data(count: headerAndBody.body.count) @@ -338,8 +339,14 @@ class NearbyConnection { guard header.hasType, header.hasID else { throw NearbyError.requiredFieldMissing("payloadHeader.type|id") } guard payloadTransfer.hasPayloadChunk, chunk.hasOffset, chunk.hasFlags else { - log("[NearbyConnection] Payload transfer chunk is missing offset or flags, likely canceled.") - throw NearbyError.canceled(reason: .userCanceled) + + if payloadTransfer.controlMessage.event == .payloadCanceled { + log("[NearbyConnection] Cancel control frame received.") + throw NearbyError.canceled(reason: .userCanceled) + } + + log("[NearbyConnection] Payload transfer chunk is missing offset or flags. Frame is \(payloadTransfer.debugDescription)") + throw NearbyError.requiredFieldMissing("payloadChunk.offset|flags") } if case .bytes = header.type { diff --git a/NearbyShare/Connection/OutboundNearbyConnection.swift b/NearbyShare/Connection/OutboundNearbyConnection.swift index 649b940..3886be8 100644 --- a/NearbyShare/Connection/OutboundNearbyConnection.swift +++ b/NearbyShare/Connection/OutboundNearbyConnection.swift @@ -24,7 +24,6 @@ class OutboundNearbyConnection: NearbyConnection { private var currentTransfer: OutgoingFileTransfer? private var totalBytesToSend: Int64 = 0 private var totalBytesSent: Int64 = 0 - private var cancelled: Bool = false private var textPayloadID: Int64 = 0 public var delegate: OutboundNearbyConnectionDelegate? @@ -136,10 +135,11 @@ class OutboundNearbyConnection: NearbyConnection { override func processTransferSetupFrame(_ frame: Sharing_Nearby_Frame) throws { if frame.hasV1 && frame.v1.hasType, case .cancel = frame.v1.type { - + self.cancelled = true + self.lastError = NearbyError.canceled(reason: .userCanceled) log("[OutboundNearbyConnection] Transfer canceled") try sendDisconnectionAndDisconnect() - delegate?.outboundConnection(connection: self, failedWithError: NearbyError.canceled(reason: .userCanceled)) + delegate?.outboundConnection(connection: self, failedWithError: self.lastError!) return } diff --git a/QuickDrop/Localizable.xcstrings b/QuickDrop/Localizable.xcstrings index b5b3146..1c7e1a4 100644 --- a/QuickDrop/Localizable.xcstrings +++ b/QuickDrop/Localizable.xcstrings @@ -3313,6 +3313,113 @@ } } }, + "Error.Signature" : { + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "توقيع غير صالح. عادةً ما يعني هذا أن الرسالة تعرضت للتلف أثناء النقل. يرجى المحاولة مرة أخرى." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ungültige Signatur. Dies bedeutet normalerweise, dass eine Nachricht während der Übertragung beschädigt wurde. Bitte versuche es erneut." + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Invalid signature. This usually means that a message was damaged during transit. Please try again." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Firma no válida. Esto usualmente significa que un mensaje fue dañado durante el tránsito. Por favor, inténtelo de nuevo." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Signature invalide. Cela signifie généralement qu'un message a été endommagé pendant le transit. Veuillez réessayer." + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Érvénytelen aláírás. Ez általában azt jelenti, hogy egy üzenet sérült meg az átvitel során. Kérjük, próbálja újra." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Firma non valida. Questo di solito significa che un messaggio è stato danneggiato durante la trasmissione. Per favore riprova." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "署名が無効です。これは通常、メッセージが転送中に破損したことを意味します。もう一度試してください。" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "잘못된 서명입니다. 보통은 전송 중 메시지가 손상된 것을 의미합니다. 다시 시도해 주세요." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ongeldige handtekening. Dit betekent meestal dat een bericht beschadigd is tijdens de overdracht. Probeer het opnieuw." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Assinatura inválida. Isso geralmente significa que uma mensagem foi danificada durante a transmissão. Por favor, tente novamente." + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Assinatura inválida. Isso geralmente significa que uma mensagem foi danificada durante o trânsito. Por favor, tente novamente." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Semnătură nevalidă. De obicei, asta înseamnă că un mesaj a fost deteriorat în timpul transmiterii. Vă rugăm să încercați din nou." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Недействительная подпись. Обычно это означает, что сообщение было повреждено во время передачи. Пожалуйста, попробуйте еще раз." + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "ลายเซ็นไม่ถูกต้อง ซึ่งมักหมายถึงข้อความได้รับความเสียหายระหว่างการส่ง กรุณาลองใหม่อีกครั้ง" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Недійсний підпис. Зазвичай це означає, що повідомлення було пошкоджене під час передачі. Будь ласка, спробуйте ще раз." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "签名无效。这通常意味着消息在传输过程中损坏。请再试一次。" + } + } + } + }, "FirewallDescription" : { "extractionState" : "manual", "localizations" : { @@ -6942,7 +7049,111 @@ } }, "Reset UD" : { - + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "de" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "es" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "fr" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "hu" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "it" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "ja" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "ko" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "nl" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "ro" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "ru" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "th" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "uk" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "new", + "value" : "" + } + } + }, + "shouldTranslate" : false }, "SaveFilesLocation" : { "extractionState" : "manual", From 6985d4b1ba4e2192152dab506b7e4f384f0aa900 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20B=C3=B6ttger?= Date: Mon, 28 Jul 2025 14:33:18 +0200 Subject: [PATCH 06/20] Improve error handling --- QuickDrop/Localizable.xcstrings | 282 ++++++++++++------------- QuickDrop/Util/ErrorAlertHandler.swift | 11 +- 2 files changed, 147 insertions(+), 146 deletions(-) diff --git a/QuickDrop/Localizable.xcstrings b/QuickDrop/Localizable.xcstrings index 1c7e1a4..cc1af05 100644 --- a/QuickDrop/Localizable.xcstrings +++ b/QuickDrop/Localizable.xcstrings @@ -2891,103 +2891,103 @@ "ar" : { "stringUnit" : { "state" : "translated", - "value" : "تم إنهاء الاتصال بواسطة هاتفك. إذا حدث ذلك بشكل متكرر، يرجى إعادة تشغيل هاتفك." + "value" : "تم إيقاف الاتصال بواسطة هاتفك." } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Die Verbindung wurde von deinem Telefon abgebrochen. Wenn dieses Problem wiederholt auftritt, starte bitte dein Telefon neu." + "value" : "Die Verbindung wurde von deinem Telefon abgebrochen." } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "The connection was aborted by your phone. If this happens repeatedly, please restart your phone." + "value" : "The connection was aborted by your phone." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "La conexión fue interrumpida por tu teléfono. Si esto ocurre repetidamente, por favor reinicia tu teléfono." + "value" : "La conexión fue abortada por tu teléfono." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "La connexion a été interrompue par votre téléphone. Si cela se produit plusieurs fois, veuillez redémarrer votre téléphone." + "value" : "La connexion a été interrompue par votre téléphone." } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "A kapcsolatot a telefonja szakította meg. Ha ez ismételten előfordul, kérjük, indítsa újra a telefonját." + "value" : "A kapcsolatot megszakította a telefonod." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "La connessione è stata interrotta dal tuo telefono. Se questo accade ripetutamente, ti preghiamo di riavviare il telefono." + "value" : "La connessione è stata interrotta dal tuo telefono." } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "接続が電話によって中断されました。これが繰り返し起こる場合は、電話を再起動してください。" + "value" : "接続はあなたの電話によって中断されました。" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "연결이 휴대전화에 의해 중단되었습니다. 이 문제가 반복되면, 휴대전화를 재시작하세요." + "value" : "연결이 휴대폰에 의해 중단되었습니다." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "De verbinding is afgebroken door je telefoon. Als dit herhaaldelijk gebeurt, start dan je telefoon opnieuw op." + "value" : "De verbinding is door uw telefoon afgebroken." } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "A conexão foi abortada pelo seu telefone. Se isso acontecer repetidamente, por favor, reinicie seu telefone." + "value" : "A conexão foi interrompida pelo seu telefone." } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "A ligação foi interrompida pelo seu telemóvel. Se isto acontecer repetidamente, por favor reinicie o seu telemóvel." + "value" : "A ligação foi interrompida pelo seu telemóvel." } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Conexiunea a fost întreruptă de telefonul dumneavoastră. Dacă acest lucru se întâmplă în mod repetat, vă rugăm să reporniți telefonul." + "value" : "Conexiunea a fost întreruptă de telefonul dumneavoastră." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Соединение было прервано вашим телефоном. Если это происходит повторно, пожалуйста, перезагрузите телефон." + "value" : "Соединение было прервано вашим телефоном." } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "การเชื่อมต่อถูกยกเลิกโดยโทรศัพท์ของคุณ หากเกิดขึ้นบ่อย ๆ กรุณารีสตาร์ทโทรศัพท์ของคุณ" + "value" : "การเชื่อมต่อล้มเลิกโดยโทรศัพท์ของคุณ" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "З'єднання було перервано вашим телефоном. Якщо це повторюється, перезапустіть телефон." + "value" : "З'єднання було перервано вашим телефоном." } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "连接被您的手机中断。如果此问题频繁出现,请重启您的手机。" + "value" : "连接被您的手机中断。" } } } @@ -3099,6 +3099,113 @@ } } }, + "Error.FixInstructions" : { + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "إذا حدث ذلك مرارًا، يرجى تعطيل وتمكين الواي فاي على هاتفك، أو إعادة تشغيل هاتفك." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wenn dies wiederholt passiert, deaktiviere bitte das WLAN auf deinem Telefon und aktiviere es erneut oder starte dein Telefon neu." + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "If this happens repeatedly, please disable and re-enable Wi-Fi on your phone, or restart your phone." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Si esto ocurre repetidamente, desactiva y vuelve a activar el Wi-Fi en tu teléfono, o reinicia tu teléfono." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Si cela se produit plusieurs fois, veuillez désactiver et réactiver le Wi-Fi sur votre téléphone ou redémarrez votre téléphone." + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ha ez ismétlődik, kapcsold ki majd be újra a Wi-Fi-t a telefonodon, vagy indítsd újra a telefonod." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Se ciò accade ripetutamente, disattiva e riattiva il Wi-Fi sul tuo telefono o riavvia il tuo telefono." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "これが繰り返し起こる場合は、お使いの電話でWi-Fiを無効にして再度有効にするか、電話を再起動してください。" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "이 문제가 반복되면 휴대폰에서 Wi-Fi를 비활성화하고 다시 활성화하거나 휴대폰을 재시작하세요." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Als dit herhaaldelijk gebeurt, schakel dan Wi-Fi op uw telefoon uit en weer in, of start uw telefoon opnieuw op." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Se isso acontecer repetidamente, desative e reative o Wi-Fi no seu telefone, ou reinicie o seu telefone." + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Se isso acontecer repetidamente, por favor, desative e ative novamente o Wi-Fi do seu telemóvel ou reinicie o seu telemóvel." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dacă se întâmplă în mod repetat, vă rugăm să dezactivați și să reactivați Wi-Fi pe telefonul dvs. sau să reporniți telefonul." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Если это повторяется, пожалуйста, отключите и снова включите Wi-Fi на вашем телефоне или перезагрузите его." + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "ถ้าเกิดเหตุการณ์นี้บ่อย ๆ กรุณาปิดและเปิด Wi-Fi ในโทรศัพท์ของคุณใหม่ หรือรีสตาร์ทโทรศัพท์ของคุณ" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Якщо це повторюється, будь ласка, вимкніть і знову увімкніть Wi-Fi на вашому телефоні або перезавантажте телефон." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "如果这种情况反复发生,请禁用并重新启用手机上的Wi-Fi,或重启手机。" + } + } + } + }, "Error.NetworkDown" : { "extractionState" : "manual", "localizations" : { @@ -3206,113 +3313,6 @@ } } }, - "Error.Protocol" : { - "extractionState" : "manual", - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "خطأ في الاتصال" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Fehler bei der Datenübertragung" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Communication error" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Error de comunicación" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Erreur de communication" - } - }, - "hu" : { - "stringUnit" : { - "state" : "translated", - "value" : "Kommunikációs hiba" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Errore di comunicazione" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "通信エラー" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "통신 오류" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Communicatiefout" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Erro de comunicação" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Erro de comunicação" - } - }, - "ro" : { - "stringUnit" : { - "state" : "translated", - "value" : "Eroare de comunicare" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ошибка протокола" - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "การสื่อสารผิดพลาด" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Помилка протоколу" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "通信出错" - } - } - } - }, "Error.Signature" : { "extractionState" : "manual", "localizations" : { @@ -8870,103 +8870,103 @@ "ar" : { "stringUnit" : { "state" : "translated", - "value" : "الجهاز الآخر لم يستجب في الوقت المحدد. إذا حدثت هذه المشكلة بشكل متكرر، يرجى إعادة تشغيل جهاز الأندرويد الخاص بك." + "value" : "لم يستجب الجهاز الآخر في الوقت المناسب." } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Das andere Gerät hat nicht rechtzeitig geantwortet. Wenn dieses Problem wiederholt auftritt, starte bitte dein Android-Gerät neu." + "value" : "Das andere Gerät hat nicht rechtzeitig geantwortet." } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "The other device did not respond in time. If this issue occurs repeatedly, please restart your Android device." + "value" : "The other device did not respond in time." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "El otro dispositivo no respondió a tiempo. Si este problema ocurre repetidamente, reinicie su dispositivo Android." + "value" : "El otro dispositivo no respondió a tiempo." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "L'autre appareil n'a pas répondu à temps. Si ce problème se reproduit, redémarrez votre appareil Android." + "value" : "L'autre appareil n'a pas réagi à temps." } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "A másik eszköz nem válaszolt időben. Ha ez a probléma ismétlődik, kérjük, indítsa újra Android eszközét." + "value" : "A másik eszköz nem válaszolt időben." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "L'altro dispositivo non ha risposto in tempo. Se questo problema si ripete, riavvia il tuo dispositivo Android." + "value" : "L'altro dispositivo non ha risposto in tempo." } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "他のデバイスが時間内に応答しませんでした。この問題が繰り返し発生する場合は、Androidデバイスを再起動してください。" + "value" : "他のデバイスが時間内に応答しませんでした。" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "다른 기기가 시간 내에 응답하지 않았습니다. 이 문제가 반복되면 Android 기기를 다시 시작해 주세요." + "value" : "다른 기기가 시간 내에 응답하지 않았습니다." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Het andere apparaat heeft niet op tijd gereageerd. Als dit probleem zich herhaaldelijk voordoet, start dan uw Android-apparaat opnieuw op." + "value" : "Het andere apparaat reageerde niet op tijd." } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "O outro dispositivo não respondeu a tempo. Se esse problema ocorrer repetidamente, reinicie seu dispositivo Android." + "value" : "O outro dispositivo não respondeu a tempo." } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "O outro dispositivo não respondeu a tempo. Se este problema ocorrer repetidamente, reinicie o seu dispositivo Android." + "value" : "O outro dispositivo não respondeu a tempo." } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Cealaltă unitate nu a răspuns la timp. Dacă problema apare frecvent, reporniți dispozitivul Android." + "value" : "Celălalt dispozitiv nu a răspuns la timp." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Другое устройство не ответило вовремя. Если это повторяется, перезагрузите устройство Android." + "value" : "Другое устройство не ответило вовремя." } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "อุปกรณ์อื่นไม่ตอบสนองในเวลาที่กำหนด ถ้าเกิดปัญหานี้ซ้ำๆ กรุณารีสตาร์ทอุปกรณ์ Android ของคุณ" + "value" : "อุปกรณ์อีกเครื่องไม่ตอบสนองทันเวลา" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Інший пристрій не встиг відповісти. Якщо проблема повторюється, перезапустіть ваш Android пристрій." + "value" : "Інший пристрій не відповів вчасно." } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "其他设备未能及时回应。如果此问题重复出现,请重启您的安卓设备。" + "value" : "另一台设备未及时响应。" } } } diff --git a/QuickDrop/Util/ErrorAlertHandler.swift b/QuickDrop/Util/ErrorAlertHandler.swift index 4bedf22..4e8f531 100644 --- a/QuickDrop/Util/ErrorAlertHandler.swift +++ b/QuickDrop/Util/ErrorAlertHandler.swift @@ -27,22 +27,23 @@ class ErrorAlertHandler { NSApp.activate(ignoringOtherApps: true) var description = "" + let fixInstructions = " " + "Error.FixInstructions".localized() if let ne = (error as? NearbyError) { switch ne { case .inputOutput: - description = "I/O Error" + description = "I/O Error." + fixInstructions case .protocolError(errorMessage: let errorMessage): - description = "Error.Protocol".localized() + ": \(errorMessage)" + description = errorMessage + fixInstructions case .packetFilterError: openAlert(type: .NetworkFilter) return case .requiredFieldMissing(errorMessage: let errorMessage): - description = "Error.Protocol".localized() + ": \(errorMessage)" + description = errorMessage + fixInstructions case .ukey2: - description = "Error.Crypto".localized() + ": \(ne.localizedDescription)" + description = "Error.Crypto".localized() + ": \(ne.localizedDescription)" + fixInstructions case .canceled(reason: let reason): - description = "Error.Protocol".localized() + ": \(reason.localizedDescription())" + description = reason.localizedDescription() } } else { description = error.localizedDescription From de7cd2bf9845df39a145ceeb734b40255b363247 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20B=C3=B6ttger?= Date: Mon, 28 Jul 2025 14:58:34 +0200 Subject: [PATCH 07/20] Add data transfer timer timeout --- .../Connection/InboundNearbyConnection.swift | 1 + NearbyShare/Connection/NearbyConnection.swift | 31 +++++++++++++++++-- .../Connection/OutboundNearbyConnection.swift | 3 +- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/NearbyShare/Connection/InboundNearbyConnection.swift b/NearbyShare/Connection/InboundNearbyConnection.swift index c083a04..6c001c0 100644 --- a/NearbyShare/Connection/InboundNearbyConnection.swift +++ b/NearbyShare/Connection/InboundNearbyConnection.swift @@ -512,6 +512,7 @@ class InboundNearbyConnection: NearbyConnection { frame.v1.type = .response frame.v1.connectionResponse.status = .accept currentState = .receivingFiles + isTransferring = true try sendTransferSetupFrame(frame) } catch { lastError = error diff --git a/NearbyShare/Connection/NearbyConnection.swift b/NearbyShare/Connection/NearbyConnection.swift index 50bb45d..1f1c917 100644 --- a/NearbyShare/Connection/NearbyConnection.swift +++ b/NearbyShare/Connection/NearbyConnection.swift @@ -27,6 +27,12 @@ class NearbyConnection { var lastError: Error? var bytesTransferred: Int64 = 0 var cancelled: Bool = false + var isTransferring: Bool = false { + didSet { + log("[NearbyConnection] Now transferring. Setting up data transfer inactivity timer.") + startDataTransferTimer(previousBytesTransferred: bytesTransferred) + } + } private var payloadBuffers: [Int64: NSMutableData] = [:] private var connectionClosed: Bool = false @@ -171,7 +177,7 @@ class NearbyConnection { return } - self.startInactivityTimer() + self.startAndResetHeartbeatTimer() self.receiveFrameAsync(length: frameLength) } } @@ -193,7 +199,7 @@ class NearbyConnection { return } - self.startInactivityTimer() + self.startAndResetHeartbeatTimer() self.processReceivedFrame(frameData: content) self.receiveFrameAsync() } @@ -556,7 +562,7 @@ class NearbyConnection { } - func startInactivityTimer() { + func startAndResetHeartbeatTimer() { // Cancel previous timer if any inactivityTimer?.cancel() @@ -578,6 +584,25 @@ class NearbyConnection { } + func startDataTransferTimer(previousBytesTransferred: Int64) { + NearbyConnection.dispatchQueue.asyncAfter(deadline: .now() + timeoutInterval) { + + if previousBytesTransferred < self.bytesTransferred { + // everything good, transferred more than last check, schedule next check + self.startDataTransferTimer(previousBytesTransferred: self.bytesTransferred) + } + else { + // connection stale, need to abort + if !self.connectionClosed { + log("[NearbyConnection] Connection timeout: No more data received in \(self.timeoutInterval) seconds") + self.lastError = NearbyError.canceled(reason: .timedOut) + self.disconnect() + } + } + } + } + + // -- MARK: - Internal Data Model struct InternalFileInfo { diff --git a/NearbyShare/Connection/OutboundNearbyConnection.swift b/NearbyShare/Connection/OutboundNearbyConnection.swift index 3886be8..db4ee08 100644 --- a/NearbyShare/Connection/OutboundNearbyConnection.swift +++ b/NearbyShare/Connection/OutboundNearbyConnection.swift @@ -362,6 +362,7 @@ class OutboundNearbyConnection: NearbyConnection { switch frame.v1.connectionResponse.status { case .accept: currentState = .sendingFiles + isTransferring = true delegate?.outboundConnectionTransferAccepted(connection: self) if let textToSend = textToSend { @@ -457,7 +458,7 @@ class OutboundNearbyConnection: NearbyConnection { // only for logging self.bytesTransferred += Int64(fileBuffer.count) - startInactivityTimer() + startAndResetHeartbeatTimer() delegate?.outboundConnection(connection: self, transferProgress: Double(totalBytesSent) / Double(totalBytesToSend)) if currentTransfer!.currentOffset == currentTransfer!.totalBytes { From 86bde438e20b86314395db810706c40669030b97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20B=C3=B6ttger?= Date: Mon, 28 Jul 2025 14:59:08 +0200 Subject: [PATCH 08/20] Decrease timeout interval --- NearbyShare/Connection/NearbyConnection.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NearbyShare/Connection/NearbyConnection.swift b/NearbyShare/Connection/NearbyConnection.swift index 1f1c917..70f899d 100644 --- a/NearbyShare/Connection/NearbyConnection.swift +++ b/NearbyShare/Connection/NearbyConnection.swift @@ -38,7 +38,7 @@ class NearbyConnection { private var connectionClosed: Bool = false private var inactivityTimer: DispatchSourceTimer? - private let timeoutInterval: TimeInterval = 50 + private let timeoutInterval: TimeInterval = 30 // UKEY2-related state var publicKey: ECPublicKey? From eac2df3d44f92c7febdc8b5fa741062c7961d231 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20B=C3=B6ttger?= Date: Mon, 28 Jul 2025 21:12:22 +0200 Subject: [PATCH 09/20] Add error instructions for timeout --- QuickDrop.xcodeproj/project.pbxproj | 8 ++++---- QuickDrop/Util/ErrorAlertHandler.swift | 7 ++++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/QuickDrop.xcodeproj/project.pbxproj b/QuickDrop.xcodeproj/project.pbxproj index 5a034a4..43246d3 100644 --- a/QuickDrop.xcodeproj/project.pbxproj +++ b/QuickDrop.xcodeproj/project.pbxproj @@ -888,8 +888,8 @@ MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; - SHARED_BUILD = 57; - SHARED_VERSION = 1.11; + SHARED_BUILD = 58; + SHARED_VERSION = 1.11.1; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -950,8 +950,8 @@ MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = macosx; - SHARED_BUILD = 57; - SHARED_VERSION = 1.11; + SHARED_BUILD = 58; + SHARED_VERSION = 1.11.1; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OPTIMIZATION_LEVEL = "-O"; diff --git a/QuickDrop/Util/ErrorAlertHandler.swift b/QuickDrop/Util/ErrorAlertHandler.swift index 4e8f531..bc1bc34 100644 --- a/QuickDrop/Util/ErrorAlertHandler.swift +++ b/QuickDrop/Util/ErrorAlertHandler.swift @@ -43,7 +43,12 @@ class ErrorAlertHandler { case .ukey2: description = "Error.Crypto".localized() + ": \(ne.localizedDescription)" + fixInstructions case .canceled(reason: let reason): - description = reason.localizedDescription() + if reason == .timedOut { + description = reason.localizedDescription() + fixInstructions + } + else { + description = reason.localizedDescription() + } } } else { description = error.localizedDescription From 5bf8469732c33a386d5ea2f5b617fcde68ef9370 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20B=C3=B6ttger?= Date: Tue, 29 Jul 2025 11:24:31 +0200 Subject: [PATCH 10/20] Add ability to open app at login --- QuickDrop.xcodeproj/project.pbxproj | 20 + QuickDrop/AppDelegate.swift | 4 +- QuickDrop/Localizable.xcstrings | 3268 +++++++++-------- QuickDrop/Util/AudioManager.swift | 23 +- QuickDrop/Util/ErrorAlertHandler.swift | 1 + QuickDrop/Util/LaunchAtLogin.swift | 156 + .../IssueViews/ApIsolationIssueView.swift | 1 + QuickDrop/View/SettingsView.swift | 15 + QuickDrop/View/TutorialView.swift | 2 +- Resources/License.txt | 1 + Resources/justMaybe.mp3 | Bin 0 -> 33017 bytes ShareExtension/ShareViewController.swift | 2 + 12 files changed, 1960 insertions(+), 1533 deletions(-) create mode 100644 QuickDrop/Util/LaunchAtLogin.swift create mode 100644 Resources/justMaybe.mp3 diff --git a/QuickDrop.xcodeproj/project.pbxproj b/QuickDrop.xcodeproj/project.pbxproj index 43246d3..205deee 100644 --- a/QuickDrop.xcodeproj/project.pbxproj +++ b/QuickDrop.xcodeproj/project.pbxproj @@ -74,6 +74,14 @@ EB5C37FF2E377CE70052EFD7 /* wire_format.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2929E18CB500A442DA /* wire_format.pb.swift */; }; EB5C38002E377CE70052EFD7 /* DataModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C36E82E3546100052EFD7 /* DataModel.swift */; }; EB5C38012E377DE60052EFD7 /* Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB96ED8E2D7F8103004B3FF1 /* Localized.swift */; }; + EB5C38392E38C1F60052EFD7 /* justMaybe.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = EB5C38382E38C1F60052EFD7 /* justMaybe.mp3 */; }; + EB5C383A2E38C1F60052EFD7 /* justMaybe.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = EB5C38382E38C1F60052EFD7 /* justMaybe.mp3 */; }; + EB5C383B2E38C1F60052EFD7 /* justMaybe.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = EB5C38382E38C1F60052EFD7 /* justMaybe.mp3 */; }; + EB5C383C2E38C2D90052EFD7 /* AudioManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB36435F2D73A29F009CDFF8 /* AudioManager.swift */; }; + EB5C383D2E38C2D90052EFD7 /* AudioManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB36435F2D73A29F009CDFF8 /* AudioManager.swift */; }; + EB5C383F2E38C49D0052EFD7 /* LaunchAtLogin.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C383E2E38C49D0052EFD7 /* LaunchAtLogin.swift */; }; + EB5C38402E38C49D0052EFD7 /* LaunchAtLogin.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C383E2E38C49D0052EFD7 /* LaunchAtLogin.swift */; }; + EB5C38412E38C49D0052EFD7 /* LaunchAtLogin.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C383E2E38C49D0052EFD7 /* LaunchAtLogin.swift */; }; EB6F62D72D91F4AF00534EC2 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB6F62D62D91F4AC00534EC2 /* SettingsView.swift */; }; EB7CD1452D6FB0C300CB0CB5 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */; }; EB7CD1462D6FB0E800CB0CB5 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */; }; @@ -201,6 +209,8 @@ EB5C36E62E3544FB0052EFD7 /* SaveFilesManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveFilesManager.swift; sourceTree = ""; }; EB5C36E82E3546100052EFD7 /* DataModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataModel.swift; sourceTree = ""; }; EB5C37E22E377C2E0052EFD7 /* QuickDropiOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = QuickDropiOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; + EB5C38382E38C1F60052EFD7 /* justMaybe.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = justMaybe.mp3; sourceTree = ""; }; + EB5C383E2E38C49D0052EFD7 /* LaunchAtLogin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchAtLogin.swift; sourceTree = ""; }; EB6E743D2DD5C76F00331466 /* ConnectionFailureTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionFailureTracker.swift; sourceTree = ""; }; EB6F62D62D91F4AC00534EC2 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; @@ -424,6 +434,7 @@ EBC05C6B2E33B57100EECC18 /* Util */ = { isa = PBXGroup; children = ( + EB5C383E2E38C49D0052EFD7 /* LaunchAtLogin.swift */, EB36435F2D73A29F009CDFF8 /* AudioManager.swift */, EBC05C5A2E33936C00EECC18 /* ErrorAlertHandler.swift */, ); @@ -465,6 +476,7 @@ isa = PBXGroup; children = ( EB5C36E32E34EC0E0052EFD7 /* License.txt */, + EB5C38382E38C1F60052EFD7 /* justMaybe.mp3 */, EBC05C8A2E33B59E00EECC18 /* timeIsNow.mp3 */, ); path = Resources; @@ -614,6 +626,7 @@ 691F53BC2ABB70840089FD92 /* DeviceListCell.xib in Resources */, 699DEBA92AB0573200115D22 /* ShareViewController.xib in Resources */, EB96ED8D2D7F7EA3004B3FF1 /* Assets.xcassets in Resources */, + EB5C383A2E38C1F60052EFD7 /* justMaybe.mp3 in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -626,6 +639,7 @@ EB5C36932E34EBBD0052EFD7 /* Localizable.xcstrings in Resources */, EBC05C8C2E33B59E00EECC18 /* timeIsNow.mp3 in Resources */, 69D2C32F29E7898C00EC7E30 /* MainMenu.xib in Resources */, + EB5C38392E38C1F60052EFD7 /* justMaybe.mp3 in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -633,6 +647,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + EB5C383B2E38C1F60052EFD7 /* justMaybe.mp3 in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -657,12 +672,14 @@ EB5C37C72E377A780052EFD7 /* wire_format.pb.swift in Sources */, EB5C37C82E377A780052EFD7 /* ukey.pb.swift in Sources */, EB5C37C92E377A780052EFD7 /* device_to_device_messages.pb.swift in Sources */, + EB5C383C2E38C2D90052EFD7 /* AudioManager.swift in Sources */, EB5C37CA2E377A780052EFD7 /* offline_wire_formats.pb.swift in Sources */, EBC05C5C2E3394EF00EECC18 /* ErrorAlertHandler.swift in Sources */, EBC05C7B2E33B58100EECC18 /* ActiveNetworkInterface.swift in Sources */, EBC05C7C2E33B58100EECC18 /* ApIsolationTester.swift in Sources */, 691F53BB2ABB70840089FD92 /* DeviceListCell.swift in Sources */, EB5C37BB2E377A700052EFD7 /* ConnectionFailureTracker.swift in Sources */, + EB5C383F2E38C49D0052EFD7 /* LaunchAtLogin.swift in Sources */, EB5C37BC2E377A700052EFD7 /* SleepManager.swift in Sources */, EB5C37D02E377A7F0052EFD7 /* OutboundNearbyConnection.swift in Sources */, EB5C37D12E377A7F0052EFD7 /* DataModel.swift in Sources */, @@ -681,6 +698,7 @@ EBD2A2172D2860C100BCB8E2 /* WelcomeScreen.swift in Sources */, EB3643602D73A2A2009CDFF8 /* AudioManager.swift in Sources */, EB5C37B72E377A6F0052EFD7 /* ConnectionFailureTracker.swift in Sources */, + EB5C38412E38C49D0052EFD7 /* LaunchAtLogin.swift in Sources */, EB5C37B82E377A6F0052EFD7 /* SleepManager.swift in Sources */, EB5C37B92E377A6F0052EFD7 /* SaveFilesManager.swift in Sources */, EB5C37BA2E377A6F0052EFD7 /* Helpers.swift in Sources */, @@ -735,6 +753,8 @@ EB5C37FC2E377CE70052EFD7 /* OutboundNearbyConnection.swift in Sources */, EB5C37FD2E377CE70052EFD7 /* Logger.swift in Sources */, EB5C37FE2E377CE70052EFD7 /* offline_wire_formats.pb.swift in Sources */, + EB5C383D2E38C2D90052EFD7 /* AudioManager.swift in Sources */, + EB5C38402E38C49D0052EFD7 /* LaunchAtLogin.swift in Sources */, EB5C37FF2E377CE70052EFD7 /* wire_format.pb.swift in Sources */, EB5C38002E377CE70052EFD7 /* DataModel.swift in Sources */, ); diff --git a/QuickDrop/AppDelegate.swift b/QuickDrop/AppDelegate.swift index 7f47ed1..1ad51a1 100644 --- a/QuickDrop/AppDelegate.swift +++ b/QuickDrop/AppDelegate.swift @@ -304,6 +304,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele DispatchQueue.main.async { + AudioManager.playErrorSound() + NSApp.activate(ignoringOtherApps: true) let alert = NSAlert() @@ -337,7 +339,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, UNUserNotificationCenterDele activeIncomingTransfers[transfer.id] = TransferInfo(device: device, transfer: transfer) - AudioManager.playSound() + AudioManager.playIncomingFileSound() let acceptAutomatically = UserDefaults.standard.bool(forKey: UserDefaultsKeys.automaticallyAcceptFiles.rawValue) diff --git a/QuickDrop/Localizable.xcstrings b/QuickDrop/Localizable.xcstrings index cc1af05..54ffcfa 100644 --- a/QuickDrop/Localizable.xcstrings +++ b/QuickDrop/Localizable.xcstrings @@ -1282,110 +1282,108 @@ } } }, - "Connecting" : { - "comment" : "Share Extension", - "extractionState" : "manual", + "ConnectWithQuickDropApp" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "جارٍ الاتصال..." + "value" : "الاتصال بتطبيق QuickDrop" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Verbinden..." + "value" : "Mit QuickDrop-App verbinden" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Connecting..." + "value" : "Connect with QuickDrop App" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Conectando..." + "value" : "Conectar con la aplicación QuickDrop" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Connexion en cours..." + "value" : "Se connecter avec l'app QuickDrop" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Csatlakozás..." + "value" : "Csatlakozás a QuickDrop alkalmazással" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Connessione in corso..." + "value" : "Connetti con l'app QuickDrop" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "接続中..." + "value" : "QuickDropアプリで接続" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "연결 중..." + "value" : "QuickDrop 앱으로 연결" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Verbinden..." + "value" : "Verbind met de QuickDrop-app" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Conectando..." + "value" : "Conectar com o aplicativo QuickDrop" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "A ligar..." + "value" : "Ligar com a app QuickDrop" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Se conectează..." + "value" : "Conectează-te cu aplicația QuickDrop" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Подключение..." + "value" : "Подключиться с помощью приложения QuickDrop" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "กำลังเชื่อมต่อ..." + "value" : "เชื่อมต่อด้วยแอป QuickDrop" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Підключення..." + "value" : "Підключитися за допомогою додатка QuickDrop" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "正在连接..." + "value" : "使用 QuickDrop 应用连接" } } } @@ -1496,108 +1494,110 @@ } } }, - "ConnectWithQuickDropApp" : { + "Connecting" : { + "comment" : "Share Extension", + "extractionState" : "manual", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "الاتصال بتطبيق QuickDrop" + "value" : "جارٍ الاتصال..." } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Mit QuickDrop-App verbinden" + "value" : "Verbinden..." } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Connect with QuickDrop App" + "value" : "Connecting..." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Conectar con la aplicación QuickDrop" + "value" : "Conectando..." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Se connecter avec l'app QuickDrop" + "value" : "Connexion en cours..." } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Csatlakozás a QuickDrop alkalmazással" + "value" : "Csatlakozás..." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Connetti con l'app QuickDrop" + "value" : "Connessione in corso..." } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "QuickDropアプリで接続" + "value" : "接続中..." } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "QuickDrop 앱으로 연결" + "value" : "연결 중..." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Verbind met de QuickDrop-app" + "value" : "Verbinden..." } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Conectar com o aplicativo QuickDrop" + "value" : "Conectando..." } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Ligar com a app QuickDrop" + "value" : "A ligar..." } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Conectează-te cu aplicația QuickDrop" + "value" : "Se conectează..." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Подключиться с помощью приложения QuickDrop" + "value" : "Подключение..." } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "เชื่อมต่อด้วยแอป QuickDrop" + "value" : "กำลังเชื่อมต่อ..." } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Підключитися за допомогою додатка QuickDrop" + "value" : "Підключення..." } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "使用 QuickDrop 应用连接" + "value" : "正在连接..." } } } @@ -3955,6 +3955,220 @@ } } }, + "LaunchAtLogin" : { + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "فتح QuickDrop عند بدء تشغيل Mac" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "QuickDrop beim Starten des Macs öffnen" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Open QuickDrop When Starting Mac" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abrir QuickDrop al iniciar Mac" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ouvrir QuickDrop au démarrage du Mac" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nyissa meg a QuickDropot a Mac indításakor" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Apri QuickDrop all'avvio del Mac" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "Macの起動時にQuickDropを開く" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mac 시작 시 QuickDrop 열기" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Open QuickDrop bij het opstarten van de Mac" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abrir o QuickDrop ao iniciar o Mac" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Abrir QuickDrop ao Iniciar o Mac" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Deschide QuickDrop la Pornirea Mac-ului" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Открыть QuickDrop при запуске Mac" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "เปิด QuickDrop เมื่อเริ่มต้น Mac" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Відкрити QuickDrop при запуску Mac" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "启动 Mac 时打开 QuickDrop" + } + } + } + }, + "NFiles" : { + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d ملفات" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d Dateien" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d files" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d archivos" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d fichiers" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d fájl" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d files" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%dつのファイル" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d개의 파일" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d bestanden" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d arquivos" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d ficheiros" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d fișiere" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d файлов" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d ไฟล์" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d files" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d files" + } + } + } + }, "NetworkFilterDescription" : { "extractionState" : "manual", "localizations" : { @@ -4169,113 +4383,6 @@ } } }, - "NFiles" : { - "extractionState" : "manual", - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d ملفات" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d Dateien" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d files" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d archivos" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d fichiers" - } - }, - "hu" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d fájl" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d files" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "%dつのファイル" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d개의 파일" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d bestanden" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d arquivos" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d ficheiros" - } - }, - "ro" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d fișiere" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d файлов" - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d ไฟล์" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d files" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d files" - } - } - } - }, "NoNetworkConnection" : { "extractionState" : "manual", "localizations" : { @@ -4596,6 +4703,113 @@ } } }, + "OpenFinderAfterReceivingFooter" : { + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "إذا تم تمكين هذا الإعداد، ستفتح نافذة Finder جديدة تلقائيًا بعد النقل، مع تحديد الملفات التي تم تنزيلها للتو من هاتفك إلى جهاز Mac الخاص بك." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wenn diese Einstellung aktiviert ist, wird nach der Übertragung automatisch ein neues Finder-Fenster geöffnet, das die gerade von deinem Telefon auf deinen Mac heruntergeladenen Dateien hervorhebt." + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "If this setting is enabled, a new Finder window will automatically open after the transfer, highlighting the files just downloaded from your phone to your Mac." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Si esta configuración está habilitada, se abrirá automáticamente una nueva ventana del Finder después de la transferencia, resaltando los archivos recién descargados de tu teléfono a tu Mac." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Si ce paramètre est activé, une nouvelle fenêtre du Finder s'ouvrira automatiquement après le transfert, mettant en évidence les fichiers juste téléchargés de votre téléphone vers votre Mac." + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ha ez a beállítás engedélyezve van, a fájlok telefonjáról a Mac-re történő letöltése után automatikusan megnyílik egy új Finder ablak, amely kiemeli az imént letöltött fájlokat." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Se questa impostazione è attivata, una nuova finestra del Finder si aprirà automaticamente dopo il trasferimento, evidenziando i file appena scaricati dal tuo telefono al tuo Mac." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "この設定を有効にすると、転送後に新しいFinderウィンドウが自動的に開き、電話からMacにダウンロードされたファイルが強調表示されます。" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "이 설정이 활성화되면 전송 후 Finder 새 창이 자동으로 열리고, 휴대폰에서 Mac으로 방금 다운로드된 파일이 강조 표시됩니다." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Als deze instelling is ingeschakeld, wordt er automatisch een nieuw Finder-venster geopend na de overdracht, waarin de zojuist van je telefoon naar je Mac gedownloade bestanden worden gemarkeerd." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Se esta configuração estiver ativada, uma nova janela do Finder será aberta automaticamente após a transferência, destacando os arquivos recém-baixados do seu telefone para o seu Mac." + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Se esta definição estiver ativada, uma nova janela do Finder abrir-se-á automaticamente após a transferência, destacando os ficheiros acabados de descarregar do seu telemóvel para o seu Mac." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dacă această setare este activată, o fereastră nouă Finder se va deschide automat după transfer, evidențiind fișierele tocmai descărcate de pe telefonul tău pe Mac-ul tău." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Если эта настройка включена, новое окно Finder автоматически откроется после передачи, выделяя файлы, только что загруженные с вашего телефона на ваш Mac." + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "หากเปิดใช้งานการตั้งค่านี้ หน้าต่าง Finder ใหม่จะเปิดอัตโนมัติหลังจากการถ่ายโอน โดยจะเน้นไฟล์ที่เพิ่งดาวน์โหลดจากโทรศัพท์ของคุณไปยัง Mac" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Якщо увімкнути цей параметр, нове вікно Finder автоматично відкриється після передачі, виділяючи файли, щойно завантажені з вашого телефону на ваш Mac." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "如果启用此设置,传输完成后,将自动打开一个新的访达窗口,高亮显示从手机下载到Mac的文件。" + } + } + } + }, "PinCode" : { "extractionState" : "manual", "localizations" : { @@ -4703,1388 +4917,6 @@ } } }, - "plusview_description" : { - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "قم بفتح QuickDrop+ لمواصلة تبادل الملفات بلا حدود. QuickDrop+ هو شراء لمرة واحدة – لا حاجة للاشتراك. تشمل جميع الميزات الحالية والمستقبلية." - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Schalte QuickDrop+ frei, um weiterhin Dateien unbegrenzt auszutauschen. QuickDrop+ ist ein Einmalkauf - ein Abonnement ist nicht erforderlich. Alle aktuellen und zukünftigen Funktionen sind enthalten." - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Unlock QuickDrop+ to continue exchanging files without limits. QuickDrop+ is a one-time purchase – no subscription required. All current and future features are included." - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Desbloquea QuickDrop+ para seguir intercambiando archivos sin límites. QuickDrop+ es una compra única – no se requiere suscripción. Todas las funciones actuales y futuras están incluidas." - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Débloquez QuickDrop+ pour continuer à échanger des fichiers sans limites. QuickDrop+ est un achat unique – aucun abonnement requis. Toutes les fonctionnalités actuelles et futures sont incluses." - } - }, - "hu" : { - "stringUnit" : { - "state" : "translated", - "value" : "Oldd fel a QuickDrop+-t, hogy korlátlanul folytathasd a fájlcserét. A QuickDrop+ egy egyszeri vásárlás – előfizetés nem szükséges. Az összes jelenlegi és jövőbeli funkciót tartalmazza." - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sblocca QuickDrop+ per continuare a scambiare file senza limiti. QuickDrop+ è un acquisto unico – nessun abbonamento richiesto. Tutte le funzionalità attuali e future sono incluse." - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "QuickDrop+ をアンロックして、無制限にファイルを交換できます。QuickDrop+ は買い切り型のアプリです。サブスクリプション不要で、現在および将来のすべての機能が含まれます。" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "QuickDrop+를 잠금 해제하여 파일을 제한 없이 교환하세요. QuickDrop+는 일회성 구매이며, 구독이 필요하지 않습니다. 모든 현재 및 미래 기능이 포함됩니다." - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ontgrendel QuickDrop+ om bestanden zonder limieten te blijven uitwisselen. QuickDrop+ is een eenmalige aankoop – geen abonnement vereist. Alle huidige en toekomstige functies zijn inbegrepen." - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Desbloqueie o QuickDrop+ para continuar trocando arquivos sem limites. QuickDrop+ é uma compra única – sem necessidade de assinatura. Todos os recursos atuais e futuros estão incluídos." - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Desbloqueia o QuickDrop+ para continuar a trocar ficheiros sem limites. QuickDrop+ é uma compra única – não é necessária subscrição. Todas as funcionalidades atuais e futuras estão incluídas." - } - }, - "ro" : { - "stringUnit" : { - "state" : "translated", - "value" : "Deblochează QuickDrop+ pentru a continua schimbul de fișiere fără limite. QuickDrop+ este o achiziție unică – nu este necesar abonament. Toate funcțiile curente și viitoare sunt incluse." - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Разблокируйте QuickDrop+, чтобы продолжить обмен файлами без ограничений. QuickDrop+ – это разовая покупка, подписка не требуется. Все текущие и будущие функции включены." - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "ปลดล็อก QuickDrop+ เพื่อแลกเปลี่ยนไฟล์ได้อย่างไม่จำกัด QuickDrop+ เป็นการซื้อครั้งเดียว – ไม่ต้องสมัครสมาชิก ฟีเจอร์ทั้งหมดทั้งปัจจุบันและอนาคตรวมอยู่ในนี้แล้ว" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Розблокуйте QuickDrop+, щоб продовжити обмін файлами без обмежень. QuickDrop+ – це одноразова покупка, підписка не потрібна. Всі поточні та майбутні функції включені." - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "解锁 QuickDrop+ 以无限制地交换文件。QuickDrop+ 是一次性购买 – 无需订阅。所有当前和未来的功能均包含在内。" - } - } - } - }, - "plusview_nothingtorestore" : { - "extractionState" : "manual", - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "لا يوجد شيء لاستعادته!" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Es gibt nichts wiederherzustellen!" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "There is nothing to restore!" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "¡No hay nada que restaurar!" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Aucun achat à restaurer !" - } - }, - "hu" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nincs visszaállítható vásárlás!" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Non c'è nulla da ripristinare!" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "復元するものがありません!" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "복원할 항목이 없습니다!" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Er is niets om te herstellen!" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nada para restaurar!" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Não há nada para restaurar!" - } - }, - "ro" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nu există nimic de restaurat!" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Нет покупок для восстановления!" - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "ไม่มีรายการที่ต้องกู้คืน!" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Немає покупок для відновлення!" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "没有可恢复的购买!" - } - } - } - }, - "plusview_productRequestFailed" : { - "extractionState" : "manual", - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "تعذر جلب عمليات الشراء داخل التطبيق في الوقت الحالي." - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "In-App-Käufe können momentan nicht abgerufen werden." - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Unable to fetch In-App Purchases at the moment." - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "No se pueden recuperar las compras dentro de la aplicación en este momento." - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Impossible de récupérer les achats intégrés pour le moment." - } - }, - "hu" : { - "stringUnit" : { - "state" : "translated", - "value" : "Jelenleg nem lehet lekérni az alkalmazáson belüli vásárlásokat." - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Impossibile recuperare gli acquisti in-app al momento." - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "現在、アプリ内購入情報を取得できません。" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "현재 인앱 구매 정보를 가져올 수 없습니다." - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "In-app aankopen kunnen momenteel niet worden opgehaald." - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Não foi possível recuperar as compras dentro do aplicativo no momento." - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Não foi possível recuperar as compras dentro do app no momento." - } - }, - "ro" : { - "stringUnit" : { - "state" : "translated", - "value" : "Momentan, nu se pot obține informații despre achizițiile în aplicație." - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Не удалось получить данные о встроенных покупках." - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "ไม่สามารถดึงข้อมูลการซื้อภายในแอปได้ในขณะนี้" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Наразі неможливо отримати інформацію про покупки в додатку." - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "暂时无法获取应用内购买信息。" - } - } - } - }, - "plusview_purchasefailed" : { - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "فشل الشراء" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Kauf fehlgeschlagen" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Purchase failed" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "La compra ha fallado" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Échec de l'achat" - } - }, - "hu" : { - "stringUnit" : { - "state" : "translated", - "value" : "A vásárlás sikertelen" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Acquisto non riuscito" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "購入に失敗しました" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "구매 실패" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Aankoop mislukt" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Falha na compra" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Falha na compra" - } - }, - "ro" : { - "stringUnit" : { - "state" : "translated", - "value" : "Achiziția a eșuat" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ошибка покупки" - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "การซื้อล้มเหลว" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Помилка покупки" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "购买失败" - } - } - } - }, - "plusview_purchasefaileddescription" : { - "extractionState" : "manual", - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "إذا ظهر هذا الرسالة بشكل غير متوقع، يرجى التأكد من اتصالك بالإنترنت وأن عمليات الشراء داخل التطبيق مفعّلة على جهازك، ثم حاول مرة أخرى." - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Falls diese Nachricht unerwartet erscheint, stelle sicher, dass du mit dem Internet verbunden bist und In-App-Käufe auf deinem Gerät aktiviert sind, und versuche es erneut." - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "If this message appeared unexpectedly, please make sure that you are connected to the Internet and In-App-Purchases are enabled for your device and then try again." - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Si este mensaje apareció inesperadamente, asegúrate de estar conectado a internet y que las compras dentro de la aplicación estén habilitadas en tu dispositivo, luego intenta nuevamente." - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Si ce message est apparu de manière inattendue, assurez-vous d'être connecté à Internet et que les achats intégrés sont activés sur votre appareil, puis réessayez." - } - }, - "hu" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ha ez az üzenet váratlanul jelent meg, ellenőrizd az internetkapcsolatot, és győződj meg róla, hogy az alkalmazáson belüli vásárlások engedélyezve vannak az eszközödön, majd próbáld újra." - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Se questo messaggio è apparso inaspettatamente, assicurati di essere connesso a Internet e che gli acquisti in-app siano abilitati sul tuo dispositivo, quindi riprova." - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "このメッセージが予期せず表示された場合、インターネット接続を確認し、アプリ内購入が有効になっていることを確認した上で再試行してください。" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "이 메시지가 예상치 않게 나타난 경우, 인터넷에 연결되어 있는지 확인하고 기기에서 인앱 결제가 활성화되어 있는지 확인한 후 다시 시도해 주세요." - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Als dit bericht onverwachts verschijnt, controleer dan of je verbonden bent met internet en of in-app aankopen zijn ingeschakeld op je apparaat, en probeer het opnieuw." - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Se esta mensagem apareceu inesperadamente, verifique se você está conectado à internet e se as compras dentro do app estão ativadas no seu dispositivo e tente novamente." - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Se esta mensagem apareceu inesperadamente, certifique-se de estar conectado à Internet e que as compras dentro do app estejam ativadas no seu dispositivo, depois tente novamente." - } - }, - "ro" : { - "stringUnit" : { - "state" : "translated", - "value" : "Dacă acest mesaj a apărut neașteptat, asigură-te că ești conectat la internet și că achizițiile în aplicație sunt activate pe dispozitivul tău, apoi încearcă din nou." - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Если это сообщение появилось неожиданно, убедитесь, что у вас есть доступ к интернету и включены встроенные покупки на вашем устройстве, затем попробуйте снова." - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "หากข้อความนี้ปรากฏขึ้นโดยไม่คาดคิด โปรดตรวจสอบว่าคุณเชื่อมต่ออินเทอร์เน็ตและเปิดใช้งานการซื้อภายในแอปในอุปกรณ์ของคุณแล้วลองอีกครั้ง" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Якщо це повідомлення з’явилося несподівано, переконайтеся, що у вас є підключення до Інтернету, а покупки в додатках увімкнені на вашому пристрої, і спробуйте ще раз." - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "如果此消息意外出现,请确保您已连接到互联网,并且设备已启用应用内购买功能,然后重试。" - } - } - } - }, - "plusview_restorefailed" : { - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "حدثت مشكلة أثناء استعادة عملية الشراء" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Es gab ein Problem beim Wiederherstellen deines Kaufs" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "There was a problem restoring your purchase" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Hubo un problema al restaurar tu compra" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Un problème est survenu lors de la restauration de votre achat" - } - }, - "hu" : { - "stringUnit" : { - "state" : "translated", - "value" : "Hiba történt a vásárlás visszaállítása során" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Si è verificato un problema durante il ripristino dell'acquisto" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "購入の復元中に問題が発生しました" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "구매 복원 중 문제가 발생했습니다." - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Er was een probleem bij het herstellen van je aankoop" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Houve um problema ao restaurar sua compra" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Houve um problema ao restaurar sua compra" - } - }, - "ro" : { - "stringUnit" : { - "state" : "translated", - "value" : "A apărut o problemă la restaurarea achiziției" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Произошла ошибка при восстановлении покупки" - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "เกิดปัญหาในการกู้คืนการซื้อของคุณ" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Сталася помилка під час відновлення покупки" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "恢复购买时出现问题" - } - } - } - }, - "plusview_restorefailed_proceed" : { - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "موافق" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Okay" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Okay" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Aceptar" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "OK" - } - }, - "hu" : { - "stringUnit" : { - "state" : "translated", - "value" : "Rendben" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ok" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "OK" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "확인" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Oké" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "OK" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ok" - } - }, - "ro" : { - "stringUnit" : { - "state" : "translated", - "value" : "OK" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Окей" - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "ตกลง" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Гаразд" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "确定" - } - } - } - }, - "plusview_restorepurchase" : { - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "هل اشتريته بالفعل؟" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bereits gekauft?" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Already bought?" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "¿Ya lo compraste?" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Déjà acheté ?" - } - }, - "hu" : { - "stringUnit" : { - "state" : "translated", - "value" : "Már megvásároltad?" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Già acquistato?" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "すでに購入済みですか?" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "이미 구매하셨나요?" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Al gekocht?" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Já comprou?" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Já comprou?" - } - }, - "ro" : { - "stringUnit" : { - "state" : "translated", - "value" : "Deja cumpărat?" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Уже куплено?" - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "เคยซื้อแล้ว?" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Вже куплено?" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "已购买?" - } - } - } - }, - "plusview_success_button" : { - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "حسنًا!" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Alles klar!" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Got it!" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "¡Entendido!" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "D'accord !" - } - }, - "hu" : { - "stringUnit" : { - "state" : "translated", - "value" : "Értettem!" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Capito!" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "了解!" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "알겠습니다!" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Begrepen!" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Entendi!" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Entendido!" - } - }, - "ro" : { - "stringUnit" : { - "state" : "translated", - "value" : "Înțeles!" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Понял!" - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "รับทราบ!" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Зрозуміло!" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "知道了!" - } - } - } - }, - "plusview_success_description" : { - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "يمكنك الآن متابعة استلام الملفات بدون قيود. أي ملفات حاولت إرسالها سابقًا تم حفظها بالفعل على جهازك." - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Du kannst nun weiterhin Dateien ohne Einschränkungen empfangen. Alle Dateien, welche gerade gesendet wurden, wurden bereits gespeichert." - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "You can now continue receiving files without restrictions. Any files you tried to send were already saved on your device." - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ahora puedes seguir recibiendo archivos sin restricciones. Todos los archivos que intentaste enviar antes ya se han guardado en tu dispositivo." - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Vous pouvez maintenant continuer à recevoir des fichiers sans restrictions. Tous les fichiers que vous avez essayé d'envoyer précédemment ont déjà été enregistrés sur votre appareil." - } - }, - "hu" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mostantól korlátozások nélkül fogadhat fájlokat. Az összes fájl, amelyet korábban próbált elküldeni, már el lett mentve az eszközére." - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ora puoi continuare a ricevere file senza restrizioni. Tutti i file che hai provato a inviare in precedenza sono già stati salvati sul tuo dispositivo." - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "これで、制限なくファイルを受信し続けることができます。以前送信しようとしたファイルはすでにデバイスに保存されています。" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "이제 파일을 제한 없이 계속 받을 수 있습니다. 이전에 전송을 시도한 모든 파일이 이미 기기에 저장되었습니다." - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Je kunt nu bestanden blijven ontvangen zonder beperkingen. Alle bestanden die je eerder probeerde te verzenden, zijn al opgeslagen op je apparaat." - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Agora você pode continuar recebendo arquivos sem restrições. Todos os arquivos que você tentou enviar antes já foram salvos no seu dispositivo." - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Agora pode continuar a receber ficheiros sem restrições. Todos os ficheiros que tentou enviar antes já foram guardados no seu dispositivo." - } - }, - "ro" : { - "stringUnit" : { - "state" : "translated", - "value" : "Acum puteți continua să primiți fișiere fără restricții. Toate fișierele pe care ați încercat să le trimiteți anterior au fost deja salvate pe dispozitivul dvs." - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Теперь вы можете продолжать получать файлы без ограничений. Все файлы, которые вы пытались отправить ранее, уже сохранены на вашем устройстве." - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "คุณสามารถรับไฟล์ต่อไปได้โดยไม่มีข้อจำกัด ไฟล์ทั้งหมดที่คุณพยายามส่งก่อนหน้านี้ถูกบันทึกไว้ในอุปกรณ์ของคุณแล้ว" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Тепер ви можете продовжувати отримувати файли без обмежень. Усі файли, які ви намагалися надіслати раніше, вже збережено на вашому пристрої." - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "您现在可以继续无限制地接收文件。您之前尝试发送的所有文件已保存到您的设备上。" - } - } - } - }, - "plusview_success_title" : { - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "شكرًا لشرائك!" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Danke für deinen Kauf!" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Thanks for your purchase!" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "¡Gracias por tu compra!" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Merci pour votre achat !" - } - }, - "hu" : { - "stringUnit" : { - "state" : "translated", - "value" : "Köszönjük a vásárlást!" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Grazie per il tuo acquisto!" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "ご購入ありがとうございます!" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "구매해 주셔서 감사합니다!" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bedankt voor je aankoop!" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Obrigado pela sua compra!" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Obrigado pela sua compra!" - } - }, - "ro" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mulțumim pentru achiziție!" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Спасибо за покупку!" - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "ขอบคุณสำหรับการซื้อ!" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Дякуємо за покупку!" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "感谢您的购买!" - } - } - } - }, - "plusview_title" : { - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "شكرًا لاستخدامك QuickDrop!" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Danke, dass du QuickDrop benutzt!" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Thanks for using QuickDrop!" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "¡Gracias por usar QuickDrop!" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Merci d'utiliser QuickDrop !" - } - }, - "hu" : { - "stringUnit" : { - "state" : "translated", - "value" : "Köszönjük, hogy használod a QuickDropot!" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Grazie per aver usato QuickDrop!" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "QuickDropをご利用いただきありがとうございます!" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "QuickDrop을 사용해 주셔서 감사합니다!" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bedankt voor het gebruiken van QuickDrop!" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Obrigado por usar o QuickDrop!" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Obrigado por usar o QuickDrop!" - } - }, - "ro" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mulțumim că folosești QuickDrop!" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Спасибо за использование QuickDrop!" - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "ขอบคุณที่ใช้ QuickDrop!" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Дякуємо за використання QuickDrop!" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "感谢使用 QuickDrop!" - } - } - } - }, - "plusview_unlock_label" : { - "extractionState" : "manual", - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "ادعم QuickDrop" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Unterstütze QuickDrop" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Support QuickDrop" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Apoyar QuickDrop" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Soutenir QuickDrop" - } - }, - "hu" : { - "stringUnit" : { - "state" : "translated", - "value" : "Támogasd a QuickDropot" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Supporta QuickDrop" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "QuickDropをサポート" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "QuickDrop 지원하기" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ondersteun QuickDrop" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Apoie o QuickDrop" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Apoiar QuickDrop" - } - }, - "ro" : { - "stringUnit" : { - "state" : "translated", - "value" : "Susține QuickDrop" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Поддержать QuickDrop" - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "สนับสนุน QuickDrop" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Підтримати QuickDrop" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "支持 QuickDrop" - } - } - } - }, "PrivacyPolicy" : { "extractionState" : "manual", "localizations" : { @@ -10254,6 +9086,1388 @@ } } } + }, + "plusview_description" : { + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "قم بفتح QuickDrop+ لمواصلة تبادل الملفات بلا حدود. QuickDrop+ هو شراء لمرة واحدة – لا حاجة للاشتراك. تشمل جميع الميزات الحالية والمستقبلية." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Schalte QuickDrop+ frei, um weiterhin Dateien unbegrenzt auszutauschen. QuickDrop+ ist ein Einmalkauf - ein Abonnement ist nicht erforderlich. Alle aktuellen und zukünftigen Funktionen sind enthalten." + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unlock QuickDrop+ to continue exchanging files without limits. QuickDrop+ is a one-time purchase – no subscription required. All current and future features are included." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Desbloquea QuickDrop+ para seguir intercambiando archivos sin límites. QuickDrop+ es una compra única – no se requiere suscripción. Todas las funciones actuales y futuras están incluidas." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Débloquez QuickDrop+ pour continuer à échanger des fichiers sans limites. QuickDrop+ est un achat unique – aucun abonnement requis. Toutes les fonctionnalités actuelles et futures sont incluses." + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Oldd fel a QuickDrop+-t, hogy korlátlanul folytathasd a fájlcserét. A QuickDrop+ egy egyszeri vásárlás – előfizetés nem szükséges. Az összes jelenlegi és jövőbeli funkciót tartalmazza." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sblocca QuickDrop+ per continuare a scambiare file senza limiti. QuickDrop+ è un acquisto unico – nessun abbonamento richiesto. Tutte le funzionalità attuali e future sono incluse." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "QuickDrop+ をアンロックして、無制限にファイルを交換できます。QuickDrop+ は買い切り型のアプリです。サブスクリプション不要で、現在および将来のすべての機能が含まれます。" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "QuickDrop+를 잠금 해제하여 파일을 제한 없이 교환하세요. QuickDrop+는 일회성 구매이며, 구독이 필요하지 않습니다. 모든 현재 및 미래 기능이 포함됩니다." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ontgrendel QuickDrop+ om bestanden zonder limieten te blijven uitwisselen. QuickDrop+ is een eenmalige aankoop – geen abonnement vereist. Alle huidige en toekomstige functies zijn inbegrepen." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Desbloqueie o QuickDrop+ para continuar trocando arquivos sem limites. QuickDrop+ é uma compra única – sem necessidade de assinatura. Todos os recursos atuais e futuros estão incluídos." + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Desbloqueia o QuickDrop+ para continuar a trocar ficheiros sem limites. QuickDrop+ é uma compra única – não é necessária subscrição. Todas as funcionalidades atuais e futuras estão incluídas." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Deblochează QuickDrop+ pentru a continua schimbul de fișiere fără limite. QuickDrop+ este o achiziție unică – nu este necesar abonament. Toate funcțiile curente și viitoare sunt incluse." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Разблокируйте QuickDrop+, чтобы продолжить обмен файлами без ограничений. QuickDrop+ – это разовая покупка, подписка не требуется. Все текущие и будущие функции включены." + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "ปลดล็อก QuickDrop+ เพื่อแลกเปลี่ยนไฟล์ได้อย่างไม่จำกัด QuickDrop+ เป็นการซื้อครั้งเดียว – ไม่ต้องสมัครสมาชิก ฟีเจอร์ทั้งหมดทั้งปัจจุบันและอนาคตรวมอยู่ในนี้แล้ว" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Розблокуйте QuickDrop+, щоб продовжити обмін файлами без обмежень. QuickDrop+ – це одноразова покупка, підписка не потрібна. Всі поточні та майбутні функції включені." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "解锁 QuickDrop+ 以无限制地交换文件。QuickDrop+ 是一次性购买 – 无需订阅。所有当前和未来的功能均包含在内。" + } + } + } + }, + "plusview_nothingtorestore" : { + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "لا يوجد شيء لاستعادته!" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Es gibt nichts wiederherzustellen!" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "There is nothing to restore!" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "¡No hay nada que restaurar!" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucun achat à restaurer !" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nincs visszaállítható vásárlás!" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Non c'è nulla da ripristinare!" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "復元するものがありません!" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "복원할 항목이 없습니다!" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Er is niets om te herstellen!" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nada para restaurar!" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Não há nada para restaurar!" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nu există nimic de restaurat!" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Нет покупок для восстановления!" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "ไม่มีรายการที่ต้องกู้คืน!" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Немає покупок для відновлення!" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "没有可恢复的购买!" + } + } + } + }, + "plusview_productRequestFailed" : { + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "تعذر جلب عمليات الشراء داخل التطبيق في الوقت الحالي." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "In-App-Käufe können momentan nicht abgerufen werden." + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unable to fetch In-App Purchases at the moment." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "No se pueden recuperar las compras dentro de la aplicación en este momento." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Impossible de récupérer les achats intégrés pour le moment." + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Jelenleg nem lehet lekérni az alkalmazáson belüli vásárlásokat." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Impossibile recuperare gli acquisti in-app al momento." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "現在、アプリ内購入情報を取得できません。" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "현재 인앱 구매 정보를 가져올 수 없습니다." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "In-app aankopen kunnen momenteel niet worden opgehaald." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Não foi possível recuperar as compras dentro do aplicativo no momento." + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Não foi possível recuperar as compras dentro do app no momento." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Momentan, nu se pot obține informații despre achizițiile în aplicație." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Не удалось получить данные о встроенных покупках." + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "ไม่สามารถดึงข้อมูลการซื้อภายในแอปได้ในขณะนี้" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Наразі неможливо отримати інформацію про покупки в додатку." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "暂时无法获取应用内购买信息。" + } + } + } + }, + "plusview_purchasefailed" : { + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "فشل الشراء" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kauf fehlgeschlagen" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Purchase failed" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "La compra ha fallado" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Échec de l'achat" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "A vásárlás sikertelen" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Acquisto non riuscito" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "購入に失敗しました" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "구매 실패" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aankoop mislukt" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Falha na compra" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Falha na compra" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Achiziția a eșuat" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ошибка покупки" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "การซื้อล้มเหลว" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Помилка покупки" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "购买失败" + } + } + } + }, + "plusview_purchasefaileddescription" : { + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "إذا ظهر هذا الرسالة بشكل غير متوقع، يرجى التأكد من اتصالك بالإنترنت وأن عمليات الشراء داخل التطبيق مفعّلة على جهازك، ثم حاول مرة أخرى." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Falls diese Nachricht unerwartet erscheint, stelle sicher, dass du mit dem Internet verbunden bist und In-App-Käufe auf deinem Gerät aktiviert sind, und versuche es erneut." + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "If this message appeared unexpectedly, please make sure that you are connected to the Internet and In-App-Purchases are enabled for your device and then try again." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Si este mensaje apareció inesperadamente, asegúrate de estar conectado a internet y que las compras dentro de la aplicación estén habilitadas en tu dispositivo, luego intenta nuevamente." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Si ce message est apparu de manière inattendue, assurez-vous d'être connecté à Internet et que les achats intégrés sont activés sur votre appareil, puis réessayez." + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ha ez az üzenet váratlanul jelent meg, ellenőrizd az internetkapcsolatot, és győződj meg róla, hogy az alkalmazáson belüli vásárlások engedélyezve vannak az eszközödön, majd próbáld újra." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Se questo messaggio è apparso inaspettatamente, assicurati di essere connesso a Internet e che gli acquisti in-app siano abilitati sul tuo dispositivo, quindi riprova." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "このメッセージが予期せず表示された場合、インターネット接続を確認し、アプリ内購入が有効になっていることを確認した上で再試行してください。" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "이 메시지가 예상치 않게 나타난 경우, 인터넷에 연결되어 있는지 확인하고 기기에서 인앱 결제가 활성화되어 있는지 확인한 후 다시 시도해 주세요." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Als dit bericht onverwachts verschijnt, controleer dan of je verbonden bent met internet en of in-app aankopen zijn ingeschakeld op je apparaat, en probeer het opnieuw." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Se esta mensagem apareceu inesperadamente, verifique se você está conectado à internet e se as compras dentro do app estão ativadas no seu dispositivo e tente novamente." + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Se esta mensagem apareceu inesperadamente, certifique-se de estar conectado à Internet e que as compras dentro do app estejam ativadas no seu dispositivo, depois tente novamente." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dacă acest mesaj a apărut neașteptat, asigură-te că ești conectat la internet și că achizițiile în aplicație sunt activate pe dispozitivul tău, apoi încearcă din nou." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Если это сообщение появилось неожиданно, убедитесь, что у вас есть доступ к интернету и включены встроенные покупки на вашем устройстве, затем попробуйте снова." + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "หากข้อความนี้ปรากฏขึ้นโดยไม่คาดคิด โปรดตรวจสอบว่าคุณเชื่อมต่ออินเทอร์เน็ตและเปิดใช้งานการซื้อภายในแอปในอุปกรณ์ของคุณแล้วลองอีกครั้ง" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Якщо це повідомлення з’явилося несподівано, переконайтеся, що у вас є підключення до Інтернету, а покупки в додатках увімкнені на вашому пристрої, і спробуйте ще раз." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "如果此消息意外出现,请确保您已连接到互联网,并且设备已启用应用内购买功能,然后重试。" + } + } + } + }, + "plusview_restorefailed" : { + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "حدثت مشكلة أثناء استعادة عملية الشراء" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Es gab ein Problem beim Wiederherstellen deines Kaufs" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "There was a problem restoring your purchase" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hubo un problema al restaurar tu compra" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Un problème est survenu lors de la restauration de votre achat" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hiba történt a vásárlás visszaállítása során" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Si è verificato un problema durante il ripristino dell'acquisto" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "購入の復元中に問題が発生しました" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "구매 복원 중 문제가 발생했습니다." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Er was een probleem bij het herstellen van je aankoop" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Houve um problema ao restaurar sua compra" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Houve um problema ao restaurar sua compra" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "A apărut o problemă la restaurarea achiziției" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Произошла ошибка при восстановлении покупки" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "เกิดปัญหาในการกู้คืนการซื้อของคุณ" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сталася помилка під час відновлення покупки" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "恢复购买时出现问题" + } + } + } + }, + "plusview_restorefailed_proceed" : { + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "موافق" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Okay" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Okay" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aceptar" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "OK" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rendben" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ok" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "OK" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "확인" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Oké" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "OK" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ok" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "OK" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Окей" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "ตกลง" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Гаразд" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "确定" + } + } + } + }, + "plusview_restorepurchase" : { + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "هل اشتريته بالفعل؟" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bereits gekauft?" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Already bought?" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "¿Ya lo compraste?" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Déjà acheté ?" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Már megvásároltad?" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Già acquistato?" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "すでに購入済みですか?" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "이미 구매하셨나요?" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Al gekocht?" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Já comprou?" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Já comprou?" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Deja cumpărat?" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Уже куплено?" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "เคยซื้อแล้ว?" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Вже куплено?" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已购买?" + } + } + } + }, + "plusview_success_button" : { + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "حسنًا!" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alles klar!" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Got it!" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "¡Entendido!" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "D'accord !" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Értettem!" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Capito!" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "了解!" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "알겠습니다!" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Begrepen!" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Entendi!" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Entendido!" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Înțeles!" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Понял!" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "รับทราบ!" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Зрозуміло!" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "知道了!" + } + } + } + }, + "plusview_success_description" : { + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "يمكنك الآن متابعة استلام الملفات بدون قيود. أي ملفات حاولت إرسالها سابقًا تم حفظها بالفعل على جهازك." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Du kannst nun weiterhin Dateien ohne Einschränkungen empfangen. Alle Dateien, welche gerade gesendet wurden, wurden bereits gespeichert." + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "You can now continue receiving files without restrictions. Any files you tried to send were already saved on your device." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ahora puedes seguir recibiendo archivos sin restricciones. Todos los archivos que intentaste enviar antes ya se han guardado en tu dispositivo." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vous pouvez maintenant continuer à recevoir des fichiers sans restrictions. Tous les fichiers que vous avez essayé d'envoyer précédemment ont déjà été enregistrés sur votre appareil." + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mostantól korlátozások nélkül fogadhat fájlokat. Az összes fájl, amelyet korábban próbált elküldeni, már el lett mentve az eszközére." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ora puoi continuare a ricevere file senza restrizioni. Tutti i file che hai provato a inviare in precedenza sono già stati salvati sul tuo dispositivo." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "これで、制限なくファイルを受信し続けることができます。以前送信しようとしたファイルはすでにデバイスに保存されています。" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "이제 파일을 제한 없이 계속 받을 수 있습니다. 이전에 전송을 시도한 모든 파일이 이미 기기에 저장되었습니다." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Je kunt nu bestanden blijven ontvangen zonder beperkingen. Alle bestanden die je eerder probeerde te verzenden, zijn al opgeslagen op je apparaat." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Agora você pode continuar recebendo arquivos sem restrições. Todos os arquivos que você tentou enviar antes já foram salvos no seu dispositivo." + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Agora pode continuar a receber ficheiros sem restrições. Todos os ficheiros que tentou enviar antes já foram guardados no seu dispositivo." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Acum puteți continua să primiți fișiere fără restricții. Toate fișierele pe care ați încercat să le trimiteți anterior au fost deja salvate pe dispozitivul dvs." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Теперь вы можете продолжать получать файлы без ограничений. Все файлы, которые вы пытались отправить ранее, уже сохранены на вашем устройстве." + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "คุณสามารถรับไฟล์ต่อไปได้โดยไม่มีข้อจำกัด ไฟล์ทั้งหมดที่คุณพยายามส่งก่อนหน้านี้ถูกบันทึกไว้ในอุปกรณ์ของคุณแล้ว" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Тепер ви можете продовжувати отримувати файли без обмежень. Усі файли, які ви намагалися надіслати раніше, вже збережено на вашому пристрої." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "您现在可以继续无限制地接收文件。您之前尝试发送的所有文件已保存到您的设备上。" + } + } + } + }, + "plusview_success_title" : { + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "شكرًا لشرائك!" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Danke für deinen Kauf!" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Thanks for your purchase!" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "¡Gracias por tu compra!" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Merci pour votre achat !" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Köszönjük a vásárlást!" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Grazie per il tuo acquisto!" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ご購入ありがとうございます!" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "구매해 주셔서 감사합니다!" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bedankt voor je aankoop!" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obrigado pela sua compra!" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obrigado pela sua compra!" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mulțumim pentru achiziție!" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Спасибо за покупку!" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "ขอบคุณสำหรับการซื้อ!" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Дякуємо за покупку!" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "感谢您的购买!" + } + } + } + }, + "plusview_title" : { + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "شكرًا لاستخدامك QuickDrop!" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Danke, dass du QuickDrop benutzt!" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Thanks for using QuickDrop!" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "¡Gracias por usar QuickDrop!" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Merci d'utiliser QuickDrop !" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Köszönjük, hogy használod a QuickDropot!" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Grazie per aver usato QuickDrop!" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "QuickDropをご利用いただきありがとうございます!" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "QuickDrop을 사용해 주셔서 감사합니다!" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bedankt voor het gebruiken van QuickDrop!" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obrigado por usar o QuickDrop!" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obrigado por usar o QuickDrop!" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mulțumim că folosești QuickDrop!" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Спасибо за использование QuickDrop!" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "ขอบคุณที่ใช้ QuickDrop!" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Дякуємо за використання QuickDrop!" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "感谢使用 QuickDrop!" + } + } + } + }, + "plusview_unlock_label" : { + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "ادعم QuickDrop" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unterstütze QuickDrop" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Support QuickDrop" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Apoyar QuickDrop" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Soutenir QuickDrop" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Támogasd a QuickDropot" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Supporta QuickDrop" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "QuickDropをサポート" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "QuickDrop 지원하기" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ondersteun QuickDrop" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Apoie o QuickDrop" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Apoiar QuickDrop" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Susține QuickDrop" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Поддержать QuickDrop" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "สนับสนุน QuickDrop" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Підтримати QuickDrop" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "支持 QuickDrop" + } + } + } } }, "version" : "1.0" diff --git a/QuickDrop/Util/AudioManager.swift b/QuickDrop/Util/AudioManager.swift index 0e513b6..61bd87a 100644 --- a/QuickDrop/Util/AudioManager.swift +++ b/QuickDrop/Util/AudioManager.swift @@ -8,17 +8,32 @@ import AVFoundation class AudioManager { - static var audioPlayer: AVAudioPlayer? + static private var incomingFileSoundPlayer: AVAudioPlayer? + static private var errorSoundPlayer: AVAudioPlayer? - static func playSound() { + static func playIncomingFileSound() { guard let soundURL = Bundle.main.url(forResource: "timeIsNow", withExtension: "mp3") else { log("Sound file not found") return } do { - audioPlayer = try AVAudioPlayer(contentsOf: soundURL) - audioPlayer?.play() + incomingFileSoundPlayer = try AVAudioPlayer(contentsOf: soundURL) + incomingFileSoundPlayer?.play() + } catch { + log("Error playing sound: \(error.localizedDescription)") + } + } + + static func playErrorSound() { + guard let soundURL = Bundle.main.url(forResource: "justMaybe", withExtension: "mp3") else { + log("Sound file not found") + return + } + + do { + errorSoundPlayer = try AVAudioPlayer(contentsOf: soundURL) + errorSoundPlayer?.play() } catch { log("Error playing sound: \(error.localizedDescription)") } diff --git a/QuickDrop/Util/ErrorAlertHandler.swift b/QuickDrop/Util/ErrorAlertHandler.swift index bc1bc34..83723e0 100644 --- a/QuickDrop/Util/ErrorAlertHandler.swift +++ b/QuickDrop/Util/ErrorAlertHandler.swift @@ -67,6 +67,7 @@ class ErrorAlertHandler { return } else { + AudioManager.playErrorSound() self.isAlertShown = true log("Showing alert with message: \"\(alert.messageText)\" and description: \"\(alert.informativeText)\"") log("Unsuccessful transmission. Already successful transmissions: \(transmissionCount())") diff --git a/QuickDrop/Util/LaunchAtLogin.swift b/QuickDrop/Util/LaunchAtLogin.swift new file mode 100644 index 0000000..165eca2 --- /dev/null +++ b/QuickDrop/Util/LaunchAtLogin.swift @@ -0,0 +1,156 @@ +/* + + LaunchAtLogin-Modern + https://github.com/sindresorhus/LaunchAtLogin-Modern + + MIT License + + Copyright (c) Sindre Sorhus (https://sindresorhus.com) + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + */ + +#if os(macOS) || targetEnvironment(macCatalyst) +import SwiftUI +import ServiceManagement +import os.log + +@available(macOS 13.0, *) +public enum LaunchAtLogin { + fileprivate static let observable = Observable() + + /** + Toggle “launch at login” for your app or check whether it's enabled. + */ + public static var isEnabled: Bool { + get { SMAppService.mainApp.status == .enabled } + set { + observable.objectWillChange.send() + + do { + if newValue { + if SMAppService.mainApp.status == .enabled { + try? SMAppService.mainApp.unregister() + } + + try SMAppService.mainApp.register() + } else { + try SMAppService.mainApp.unregister() + } + } catch { + log("Failed to \(newValue ? "enable" : "disable") launch at login: \(error.localizedDescription)") + } + } + } + + /** + Whether the app was launched at login. + + - Important: This property must only be checked in `NSApplicationDelegate#applicationDidFinishLaunching`. + */ + public static var wasLaunchedAtLogin: Bool { + let event = NSAppleEventManager.shared().currentAppleEvent + return event?.eventID == kAEOpenApplication + && event?.paramDescriptor(forKeyword: keyAEPropData)?.enumCodeValue == keyAELaunchedAsLogInItem + } +} + + +@available(macOS 13.0, *) +extension LaunchAtLogin { + final class Observable: ObservableObject { + var isEnabled: Bool { + get { LaunchAtLogin.isEnabled } + set { + LaunchAtLogin.isEnabled = newValue + } + } + } +} + + +@available(macOS 13.0, *) +extension LaunchAtLogin { + /** + This package comes with a `LaunchAtLogin.Toggle` view which is like the built-in `Toggle` but with a predefined binding and label. Clicking the view toggles “launch at login” for your app. + + ``` + struct ContentView: View { + var body: some View { + LaunchAtLogin.Toggle() + } + } + ``` + + The default label is `"Launch at login"`, but it can be overridden for localization and other needs: + + ``` + struct ContentView: View { + var body: some View { + LaunchAtLogin.Toggle { + Text("Launch at login") + } + } + } + ``` + */ + public struct Toggle: View { + @ObservedObject private var launchAtLogin = LaunchAtLogin.observable + private let label: Label + + /** + Creates a toggle that displays a custom label. + + - Parameters: + - label: A view that describes the purpose of the toggle. + */ + public init(@ViewBuilder label: () -> Label) { + self.label = label() + } + + public var body: some View { + SwiftUI.Toggle(isOn: $launchAtLogin.isEnabled) { label } + } + } +} + + +@available(macOS 13.0, *) +extension LaunchAtLogin.Toggle { + /** + Creates a toggle that generates its label from a localized string key. + + This initializer creates a ``Text`` view on your behalf with the provided `titleKey`. + + - Parameters: + - titleKey: The key for the toggle's localized title, that describes the purpose of the toggle. + */ + public init(_ titleKey: LocalizedStringKey) { + label = Text(titleKey) + } + + /** + Creates a toggle that generates its label from a string. + + This initializer creates a `Text` view on your behalf with the provided `title`. + + - Parameters: + - title: A string that describes the purpose of the toggle. + */ + public init(_ title: some StringProtocol) { + label = Text(title) + } + + /** + Creates a toggle with the default title of `Launch at login`. + */ + public init() { + self.init("Launch at login") + } +} +#endif diff --git a/QuickDrop/View/IssueViews/ApIsolationIssueView.swift b/QuickDrop/View/IssueViews/ApIsolationIssueView.swift index b123621..619e15b 100644 --- a/QuickDrop/View/IssueViews/ApIsolationIssueView.swift +++ b/QuickDrop/View/IssueViews/ApIsolationIssueView.swift @@ -62,6 +62,7 @@ func openRouterSettingsPage() async { guard let routerIP else { DispatchQueue.main.async { + AudioManager.playErrorSound() let alert = NSAlert() alert.alertStyle = .critical diff --git a/QuickDrop/View/SettingsView.swift b/QuickDrop/View/SettingsView.swift index f301601..86471a6 100644 --- a/QuickDrop/View/SettingsView.swift +++ b/QuickDrop/View/SettingsView.swift @@ -45,6 +45,21 @@ struct SettingsView: View { Toggle("OpenFinderAfterReceiving", isOn: $openFinderAfterReceiving) .padding(.top, 5) + Text("OpenFinderAfterReceivingFooter") + .font(.footnote) + .padding(.top, 5) + + + if #available(macOS 13.0, *) { + + Divider() + .padding(.vertical, 10) + + LaunchAtLogin.Toggle { + Text("LaunchAtLogin") + } + .padding(.top, 5) + } } } } diff --git a/QuickDrop/View/TutorialView.swift b/QuickDrop/View/TutorialView.swift index 34970fd..5c728d0 100644 --- a/QuickDrop/View/TutorialView.swift +++ b/QuickDrop/View/TutorialView.swift @@ -86,7 +86,7 @@ struct LargeAppIconView: View { Image("AppIconHighRes") .resizable() .frame(width: 150, height: 150) - .padding(.top, 50) + .padding(.top, 40) Text(title.localized()) .font(.largeTitle) diff --git a/Resources/License.txt b/Resources/License.txt index ee719f7..97d7008 100644 --- a/Resources/License.txt +++ b/Resources/License.txt @@ -327,5 +327,6 @@ Terms of use: https://notificationsounds.com/terms-of-use Sound files: https://notificationsounds.com/notification-sounds/time-is-now-585 +https://notificationsounds.com/notification-sounds/just-maybe-577 Modifications to sound files: Volume and length, no previous modifications. diff --git a/Resources/justMaybe.mp3 b/Resources/justMaybe.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..198753f56dee9cd5c640079bd6da6c4929dfa079 GIT binary patch literal 33017 zcmd?w^;eY7A1Lq*V(EsZq*LOdq+#ih25F>IL?lE)SsDb9?(US9mL;S=bW2MkpdcW* z7w`AC`!C!%XV2Mq&hE^4&3m4idC#J6;`%`UZ&7n|c=hle(ZfRv1VUbcpjbEr1jJ;N zG&J;#tgIZ|yaEEkqLPv_a*C>|Pqp;)o*A22TH4qDR(Eos-WcQ%YQ1T^QuWn-u-8AQVFFE{J zD*{QGygM)P53=ua!oEY8Fa(Z=N>2imA7wYfHBgKtK86#-p>mLy3}5@iC3ma^y)#6GbH85O!1vSS)e^f(h{vq)o7*F8S(e_sB@jCf+WMI>3QK@lZ_U|I z%1f_6KoBZw$HZFqmdKc2;cp@%7RI(Ni8vH-5;bEOod^JTYU#V9rayg>jdE67TSR>5 zLGrnD;drql+rT#Xf#W{mY1y$RywvYXXTx{aGl9eY@G?Usbj+f6Q!yF1nxhQ#) zacmh+|7=Z$FW2D;lJePOVAH&i@Xp(1fpSJX+X%f(3CBo~V6Hj+qMF~F;EPn))Bxq^ zF%1=JlSeSVJoAhqK0N+25j4p@)cK3iV^ZA{eUs3~5khH-+rh%RhWn{I6)BuHh`6w~ z9k$tXt)D*qqV}{}z>F=RsJpme=su}v5|^e)kob04H@fO#*3htLt1f6euCvL)ok(A} zF-_``zPVPI@|b`ssp1wJhmcw!c^vUwX{fj}*Th7%cEBQ(D~q@)F?p{-CqmRPHeF}9 zY}Um_mM5K@fdjOPgXl;FiOD!dpx9SZ`=tFc@p2J3GKC-p=2zK z>dt=hEMJA#Sx~F<35@Z%1mmE=<7~FcOB|7%MP33PJUB*h2}$o=S_Y?inA;b0N1Um~ z&J+ff0x?zkb`$gA6`!?)Cjb~V!A8@YXW!>Cjg~V2g$!F{}%r+>FDn1WFidLd;j~$&J+^EQ}-|?_Tbebq5m( zw$2b+DtYZ#ovCJ1r6s*`SqxrLYfM*DDp|~gEiMdkXnh=N`AJ!>Mrs?lVt6}Di5EKX zo3Qfkv+%UQURa?9$ou3Xyba$3NRO%Q-f!4J+8DzAVv)HUb_yg=q6GMR=An{FvhD)u z5X;~8snLpmsZ9!-f|3jP#o+v4J227w_(;$V9@rxKGhVjeQ9iJRQ076B9BJqh39LR1r@Hz-N42Ar~ zK0%{VC}q$B2uTkG*?|B+6u|)nBS6FeE5ZT_`H4Zy14hBI^T?3yFh&fp4;qbLx1s}y zVL>M_K%8J>I0ga->J3fC`i2Bx*29?H(L9S_b&iM29?HGBu)EA!_;qrg4ktM-CL{>) z@B{`W;UeNq6L96}0ZoIt{|ONwsl63sTZfUj^Sq2iU5ubyeI}OCvLABA4YE;%aPwV3C5tFi`m9;5-l}XpCB1O(pQ*Tb)p87>bbI5Kx5c z%-|xcp~XF2?Q>M$Mtq<9qcoRzYTPhx-dQwRng6B^rPcVSAWuS)Rmnl-Y*XP%^qH`o z3W5;?3XhC=!Ys+JvBK1<5h2e#%8#7ECj_)&D2WjB!BP1sm>9vCNm2D>xN4^cS!pq4b*v7)@Fcw_LW*rX zQ@`u+%L(}rt9^%_3!kOZqBbM5hg1kw-b6{r1}_LoAXtDW@}=So6NjNf>M=2C z43CszgAq}nw@C!3e?nx?2@(Ma(W!jMa5+P87ZPOq)T5jQz<$asRX4GaoY&ZwY(OSR zMXJg=tZQbz!S%UNNb!{r^Pj|#Pr9#|eaY>f#?}W}PEvow|EfisC9138dBFbrcB&*x z$VFUjm#myF!%tsdM8un_rKG|1@uBBAmL3#96gZzr?qDOyhj%2K> zv>O|>4LlT$K(W4lV=T8o&mp^{H32neX8^^=bu~K!{dcTJL z^NdlLS%7CCp?@;+OQ9*0jf))%i|pfTxXxf2Nf8C@@M2qZWC)Zpb)&l}!>MH*VQ$T5 zNWR7?82q867g^@0`A>)eNk3_C)Y=K6@h&KQ+^GYKW7)9Uk{8gF&nNzl9=9o+`}y|! zQzu;~){f@iyJccJlVxx>-Js}5Me6x**Zm+mv!C{Q2OX(hvAw@c8bwQnGcuCwsL=Q4 zGy+!o)i+tgz(<3%w#kxbsc7_-&xqT98#li5AJ>SXBp6Vv$lk`bWY#xQ^Jw%n&M1uh zCdK&r*Yb65nV2#67fy0z`r_NvDda%&Cs{Q5d{cf|Ev;?Ohb`{z>n?iS*m3qQ<%%&6 z1p2O76pJgR+Xs0{{r#$$ zRp9v`xe(z{jo?E1oy;_tUywN%2Pl^RC6r~+1yFX=0QksAAQ012JG~3KRACGEXzYSqBXR=tJ>-#>=s=Q z=!1~H>GsN&6)YJ30b-%y-L=N&E7vnhN=_OV~#qTr<` z`CHc?IE{{;YCkqvaJ=}=u;XBbxGhS(_+HR6sz@?@ek+B#;6#ePA9(CS@)1ym5FuEs zP(AiE2UH)1UA)vkd3AmT z9lPN*1%UlnCyytcF(LEi*^zm^sOR$}jj zvpoxdc1tm-JoRjWpFLT?Fq9Dp@!bHbz_EZVq8<1k&j+w0rvy|{a3S@^yWlg*VEl9j z>0f_(kWdI2{y!mR=n{qUHPIPv$kTEL@V&F@RJ0@4rP)U_no#5MogYJ|>h=~ZXN^P8 zUbL@&*}Uwc6A|qHnlNeA?b*%~Xyka#_E5*a)uv`^mTh@K;_n8HS=HbCzN{F(G~&v1 z)*wo~6V5d`#D$XxE^U3i?Bl#YIr!YQ7S5CX>AxeCDa(Vf;N=CLyRXB-VOK#gOmGmK zXb9PearL1QB$q+IOH5ww8xm5o$C-~DRA{d4PIaF8`$KzXQBX>#Sl6SejQLf=s&s5) zheumI1|nHY^E%GW8q>3%c{Io%olG7*B0+Pz(thga$Dh!1NgVuQn`l6;kFELMWo`Qp z#j+T|&f3j9$K67ISblLiBC_Va2+K|95x$nB^wU5aou|4AVKn7(vP-Fa;n z8m%vjPL^9Zsrkndm0N81f|SEe3RlLJ5iB9DMTAE!k`q*m&FA3!L`60p-!Gy z8Y5<1Rix!VATo-&ZzJd-4WWKlN3BM*82z7MwN1HIeW_&_Sv!Q+QrQC{^+Fpy?sj<9)?TH?SD_a z2>+}|2ZR>*VQoD zoN|sHXc@Kc5V(o|9Ee`)LYK}dgr7WCs(EPPK2Av96YKfa){Pt;Jd*0Siu(~A1e0OI zRQn^)4yK4}T<0i|Bb*sF849<`aEH|$QJ2x+aa)OKP&*^_c=(2R^uDC^^?#_-+vh~m zuU(+C<|u>7Mto+xB87|n(RhCOF^f&s(;~f&KLb-;PgQ%bPOlZyi@R2>&js2`nV%PX zQQmNcy^?vyI+9PZyRFGM``xgFMrThWM|wqu$Y*73V%{ygHy3=Fyw@ekUAES>bbqle zef$nR#y^g}kwE`_f6=*>hM5-eJm4k3&8!FYA5+~dn{rFpNhfCR%wY!Y!kXwXSXmH$Wledb$_+ftAm(LOAi ztSnE_2oZs##`opli)P-{Pz4LBIqR~K+Qhx+{-OxHG%6Bbn#p)2=Fc`SMD-&prP#Y{ zKk-axHm{gU#YB4emD!w=vHjlcX&t-ks<*63?$OBSuJe_|Jnzr5Y3c24$Vd=$l$1CZ zU!kNjhEt~=UmIY-hfPr7V^2_M$|m{5z$XYal9&M`5>RFXVYY>Tb~5p57Fc-ucU+*+Uh&Xel-P!(&&oQ z+JChU^4+*`aHmY8V-423&0=j$f@0oSxg!vi*K=-7wg^P%m-OrCApGZC^Q74Y2!8w# z-Oscf@T~h1w~)yD(j9yyLkr{fub)2NXzsE^bN zqxKU9Z&C8=%_2`#FoUb&YH1W>+c-={aTqNqdG+UXW{^rmBxeh#y(}Tm^J0_W5}Q%# zX_v_>YN6+;bCE4J9aR_SoJXdvm99#RkLMb`@9^$-NtmHES>v>#LL{`!a%BRW3k` zc8GoymB2vqJ9sc!Wza8Db_^JRl@nRh4~rzk;-oZZp=1(rL99D&5x5j8)!vay{Ut0(y1X;fp%ktf5yn`5GrzhIh*`;#j7vC|>je=|PSb6?^h;zu zZ3cM%357vOXZ=AZN2QQAHJ1O~^{rp%K4JTzhFxNM7C|h#d}isvW1-nzbwB&~VkheH zo$?T41m3AwI8n)i6L3W>q& zV5em9E0{8ijw)1uK1Zl_kjBy_!7+INd6kU&5rXLe+$}*41;l$wipqG^LQY8wR6z#s zQmXs(X0&N=gmZ#-vD6dje;~kq5)QLk@TiS=&)g?vvt%LbYAOZEYu^-Pw4*KkWqRaXg`b-24@5<28mhH!Gp?! z1}S+`;cbiz@W#WD%hxfvP4Vv!7-M@-xp(WrA}rU2%e>j&eW)V*->JZeR(EZe)YO*S385NocvSy zQhZ(OS*W?`(lZbUM2FgdHBM@23HO~~@t}^uNDRz6Bm(Cu9fmK5WQD?o^fDNlnLTIhD15R~^*fe4wUIm!LpAU| z;%Vz;yw8;gY;NzfTz$zG9VwzKQ5@Oj@xWi^5thFw4wkF`PIXU4cAoUMjE=v0Bl9y6 zjsDe%{yiw!Wmm%u+(cAA$-8P048*7eKFSpVPm$%o)9!NN(hwEE1ko&7q`)XFaCY|S z2Yz>cq(M%y9Gtks03)+%D_%1BJA1oF%(zqNRGqAkxQWS;t=L|0tmcC3Wh`kuvf}wC zo;d1%LMtJO*?zaL4mf)Lay=a#&Q`~2pdiajWrE2z#0dFdhsNU)mjQn?ATxu zH|iiLPqTz@3?6}*crwl=mB(OWny~n{a&!BEfb-W;`7`CPAFP8L|Ct0MIBccC(rSm| zd;{z6R21ONZXKi=rt0Fka?-ha+^hAj_?y_C9>GROQIC7+vyCBgk*Z!5wtT7gU#shp z4i=_q4)?0=nRN=fkAl1LxNQ8XX3Mud#(w<13>&Y>ZGP3DBAV>$x1pHgtkO}v@^S2B z5B=vKrw1qK7l|CQA(C^SXo{1b&9q~*Dw9$PfJ6V2 zFgs&m?zT)3S>9;z32W>Xw7nCzp?xdSJhHyU%~KZ;mfF(u2QS33L!oUPZxZ}_+A zJ{dVyY6#4Rzj#yQ?-S-@d=bvPP+E%)u9@$&Jsivv>B3%}_fKYx=5b%a@FzlFWy#*H zHRt9_#&ZL+Dlx*btwjP`eV+k`pkjaGCjntp3_t+F)V2jNg3hp|h-y(d z=`ITWX3j4-h4}`Q8ea9=dP9iq7ZuFNVlp&5-*sjUz4vu{{j4WQJ#VwEf2^qDYw za{7)_Ea83Ue4dT~H(Bq2d@BaeVSpDS)@EkCY+mAGwrM=xu!J$D5RzlZ6W2l9<3eB8 zXj3OqR3-!8$?{CNzHyu1MP5?$Vr(mE8|Y=Z3C$Pn*P&?_q%;Px`(>}>4us3vDzh=w&vX9Z zP?S!em~^KZDs9bu=Op{jOyhd=nHe@~5}WkGn%`*d#5)Jfu2X9-ztmwqRYHty(#79P z;u;A#7TG@R=AmiOkOtcRdn))@PZXR#?=+1H*#xK0ugPwCNeY*+icbqrfmgA>r`WR^ zHz1**H-HY*d0(gE;OC%D-T1SPOp~QdcC7*XnshW{XivS+Sn-uA5WW1)5^S@(!63wQ zJKOe|EQBQ|4>!D!epjYV*Rj=3(_6yxCd1d8F1*-q1;Ynl=GBegNnnx)C3Slf-zB9# zWA7fXMtNH^Hk80@8g=36Wf^7|E$i6DnRte?5@@Q&YFKx5Q7!e@zwX?|9F1;%f#zDU zu++VJu}J~+M@kB|1R4o=V=4eodtd-@&?`U!N-Q;&><=Ub5sGr-4{dw$(-NH=0(%4i zJ`JS@g(kHz=N~yxQgcz|v?s2LY+0tmL>VzB)%}Y`%n)ytVhKT{?nVAh=z+}9@;{+t z2*tOA8|{~+(<0^c;QOuh>~|3a(8$!<9HZ63%IS04SECL@yLJ)@YM)KrioA^N{SH;pOl zO4&F1j_CX9Pj@W_Z{Maq{Y4%1M3`hk?oEDX!Xvq`H2m_Q3)t(RV~X zVu!7S;=KEx$8iAFS@8T&x;xb2nigF;pOLyEe^HLx|MFx7vbOxEC4o)M-|iNf^HcVC zc(w!-UjqDxJ;7h7 zIxq(6F<1)43K2tT!vY{5!U;7Wy{HNj)FQ&hQtGhRSiO(8lvbadIKWDv?>%}`{=fUs zqKN7q(HRF+wJfXqetq5b)$<}t)xoPvjU`wjB;>jzSaRCX(Nx-QEdK zzw3FC&B&LhT~ykWeJ+wE%1RLSPI_VPbyQv*sli08Ql|3hc&dquUe3(tw)gKuS{w!b zvwokoc2h~4J9dldfJTd=vo$GB2|h<+KeTMd%I8Cg+Z*FIwyO~~R^<>^Du`FNH(2bt z8!+z`2W5?{w{+*L>SPLyv9d8%u;bqfL1q7ogfF}6l5}ud9B|9l^r;(=uBAzoGX{v7ej56&2ae?>0W2r0Xc?7-ZDkewlR@xSM-X(WYzr`&eISX_dhv zGZ%fykG{FW>D(Xw{m_Q{CLp}O0th_20IC($0imFHKtK))Z~`aFyrj4VCXrEs7zBW@ zE+Ca7>PHX6>lLc*9uN*II-?|*gs+~(-CGr*X+uHThVr=3-IbrismFJzIA;HZPM{0f z4?^3Ay`=wQXm9XX>)jAxiHY`@84oc5{rNavLzh-x%|V9V(DyGiR-*{9HOdB9;8UU`Mj2~A6YVafkBYks zK^YW4SPF!A`aO0(-myZ0yS(lGXj8{UF=8^kI=e;)Q!jrOBl$KgNXB7{*A*w1HWQo) zc6;T5 zvsnAcl5wEAF`OS0m(W&;s0&x?Y4^a-H@zL6gD@01;p)MzI|lz_!TywgLcc;5${&O- z*%cmf(RJT%4HBv+9jP3fu0S|W{n-Ojxnw`hkMBJvxSigdV8jvaPP)rXwG~^|OB3&y zrRp^FUOQs$ndtp{?~czZs`J^RkW9(P&~y?dqN zcyoysOASOP*P*9-C47!Ec3(cUZoz6DN7eme!pZJGtVnA}P^?ufSi)*3GA`irQJZyG z2*}zT%K^lQw3;C)$z~Py7{JC6`awH4HtT>-L-jUl&Wpsd2wyzVqez;x1wovaDpO;Z zhsq5UUcrAOPl-8KyY-H^uGL1%6_EmJz8>Ju(==lUCEf3x`1G^Awa%PJIRoI*Zx+wK zy0iZyM8KW=$+6QQ_1*GUX)d*mykwwz%D?%|J;kxQ-5ok+(x{HVm&g9hXObOq`O6rWckfww3#z&?3F^4$-Buf_zz4 z5ZY<**Hx>HQbMpY@1Or+uHPy#B+bf{cn4-hW6g-cUccEw+7js;!UVJ0U+5vQxl#joSnGJU@r|Ta z4Bj$@(3o3Z-D`5+MfQ4VnGmq=2`#_=)zq2*VxDlYlzB&W@!2d!=J9C}$NECakFmb_ z#sqm>+E;#+xagbuW|3c>zW2YnmsUFJ?FGn*KHe7!t#w=}@Vdfn*ODd;$2`wX%#yY* z?@!KR7grnZR3-1wHMZ!rD@mtsPwX!qu#SLA2)`(^mW7}mXq=G99JTk5z$_$jonc=e zLDU!2ln3@a6m8SBMFTF5cu% z>V;K4=P6|dk{wl38tZ8;KXxy5=hV(TXHG@E(}?$Bn?*t`6`-EU2G2u=Ay|A}^{YSn z$|4q`89gxdNBzTWb6?N7-$<4%omjkRz1A44Fb_Bx@~+A7WjI8)JE94AUvZU2w?A~N zVn9fVTPR>(5GW4`1sJ=102*)(fKdD-R2VP=1k3dT9LNrU7HJN!p=<>#kO*iT4PhVK z(U8(&{H$`ZnswC@)vrc|P)%Nf9uj0V5z*Q|p>60~x9ZM=kOCeTUC`}UP9b)-oR%-$ zic=Pyb9A>PmqLAiEsr_*|kbM5k{pBa_kFfsp`CpA{?T$vgx4xy$Lvf*? zCjyPqt^Fz35OscfvdBZxbjsyr>nM-wpvhQ6Y2#4H#UF?{V+^ZAPDR9gi5q{Q`p zs4Sf24#l7D_;JK+SERW{1rfNs(fxeod&kB*Kg3XU&lzV{(4FSn)lXMy)5rA7bk3GJwUS31ij0gSuHqr6c&_-nNA#A1q!q8pe?9xsFJOu@S7W5ZjMcM+P z-QzNhze5Fx5c2{Mm@TGNB%nket`PFn2i7sL4_?pR z-d{y~HH7g5IENpXHu>Q1;Qe^W;KlWwhb+8Q-6YySRAA?#1EcK*Gp%gBSv~*D9+pjy zJk!(A59i>xJ|$4J{?NoLP^%wy#ij3o2bxRA(vHQb+RWIF+F_u!)r* zVa|Muy5qojwWe&dCgJ&bpk4^UyOVb66ExZreMQcgdun29^w6|pApD!KAWa#m0DZ3% zpdLgIAP{aqFi1}(yR=CFAL$}Y_fmi^3~-u~E&*e-#fFQG7YmQbU?#~o1g@yx8wUM- z=f9KTQAQS6lc7jgqrp?l$;CJ0aeKe%Xud*X;BJ+&h4$o|MGDQXDzB+g@eQATOZnj- ztPVT9n)LqNiC(I2(I~I9?zFbGblkB>T6<);jdmq}H~&7^0&LtoKHf3xa~`~FGnu*O zbK3mrQ2JJU4SjEkzE%~xY&tWY^qEAXPkL)|Q#~Z76b~UzxGM2z{PK`-elWL2HKv z3Hi%Us+3};tC8-=9&!!R`xipz(1mX0b)p+~1$@rshXw1PBo;k;3I{1ZP5w$yt0Mu1(<;H$jhev(oj6D)MgL7VjWb%_$GYszzF20;lUFSrh? z*vI!?A0M9pz{}6!`4>+a&RyO_1ftQWB_s<}>_6g-9h4*KONdY)EZj&6Ws*3>-TR)- z5+-6I_V*F&l%E_hGn8kC*^QSVkt7mEWmY<6%z2y0ixN`@iq((S-U@{IR6rF6-;3wC zEDrbTQW|nq3;hv%!)90(OHrvq5F(%Zd)3*?a(jr*cFtFdHmuWawbvk$g3rhA|EP6! zv~oNBda;t?AKBwx>F?+0-YxgS=Ml{2)47zjiU^m6=3%#Epk)t5hZm@e# zPhqHkSy<}zIyC0TA(P}J5)0TQBt*k_HgTBA9 z$x6oKC~{9G>}k9X9nm1IyJcU*BCCeFrT=ce#T9215c+`2f%!+5Z;HroR7F_hJZzeDt` z8Xd8Fx~81LVcvzl?t1qOXizi|`H#_DkVf7Ca6;w+bf`-}T+v0=d724mcvvnYo&v6n z0u%2hZ(X^(ZqC zFa6{)bg%wRe6c`BjwtP)&@FVqL~-@sWLvPDHt0b}=@IR`ZG{P1{SW&$6QY5f=LU}6 z<@qHxsc~Bu>nY=1J9@L2ek{Ic;w{ggy&hLrqSyOk6sS*nQth~B@fUnHkjs-epD_NW z(Yc}HYuZ!36(7 z^+TOd6694k9iC!6G+4L25IAF~pq-8+u8}0@TNK4-{hvd&MY>lWUEiPnPQDtlPNGOD zDqnUOV%`TBdU@wC!kRrQD@at6p3+~_X7-0X{=+2(M<5OKeO&KUupc4Ry%x+cLb996 zO%J-X4)L*+O`pCs32Of2WP`uaD#C{zrj;=-ZDt6LuT`THQ280_dDENN&e!N+OH7G)6VzV zhOqk!md+*(AQP7W{s~=&ESV@T{S(sX{QhrC!9<%UL{8vG{w=rwTi(=e$UY%hTB0!j z3Or*2#i#yG8UiGxvtc=9vK1$^r~YRcV4j}~X<&)V;x_e`7qxjUlJ}f7DXOuA=htMA zibNu%vvX@=ly;z+;ZH|+3k6A3Q=?p zd@MUXrXC%Y5p)$lIigRyr*BnUA!i!eUD;lGYk7cn(OX?b?>V9G(!tM{#)9C_n7pH~ z%0J~Fzq*m|sm`^i0L3Wuy#392jem!z?ODKkIQrs_>U*x$DOuLb^JtWu6Nm%)to4f- z`+`0#{a*gemL7%t3^s;l=dO}y-Oc^9p5T6}8`!ZIQ&1G~t1PjnSjqG?8?BZ?=qn~iauz@VXeFh*&xpGJwZA$)AkQ;nbeSoEDl+UZQa#&fkI8?xGt z8Dw#P&Nm@MWd0NS4b1{8&hcL=%ZHRLs~rnp(=Hyc5kzAk(nV9q3_h?2bt_+uo3l=?pq?DbwI74&rvXD zP6=OicllQTclQ11c@cH_>+4Sj?HJ;n!eB`qk*mHc>>)hs#wdpKjyv$U3J>eWx#?@ZYz{-Z<39Z zJV?e@c#fNrhktIHr5atn{&=@?T%-(&wp}ajj5TZ+UP_lbzLf3gUqu_EdzaDZx=-kD zOkxikq+N8L!xt9Y8va$@cf%-&PRzL9-Vbxrz$Orc5d_2h0VBZ|s)*=?>!bn7kT|3= zAP-6ur7)r)i9<>9x1ccLd_fq2x>AJ?dB-n<7q*KWmAZLM|+W~FUe zqJy6s_>WP}{1du_q}dm&pD2kZn%0=%JS6a3-OWg}r)347$bD1~Hu$y^sMz-Uk>?>m z(=T7|IbYTy%QULZxnF`jB5Pb4EiCkJy)y33sCLo0;A$`9+pH`2!>?t`jt9eh+kxeL z8tlI{H3+8!euHK<$vHk5Op1&D7zn5#!y*F^4t+zPOXPpiln^=*D8u4l4*14`%Aw5I z;Ut6LonR8U*juX*=b@|;9dULA{l4eiAP}ajRJKEk;Zc??n$vA_D7|x2n1I!3VxP`m-2TuM>Y3hF>(qWH2H5M9>3PPV@U4N8vPU*Yy>oY=V{iIB zJ;H<|awUClnpjRoUJU6Ia4Yw$ds3~O%@UsFg|C*5@V>he@fU9IK8-XK zeHk#S($#r8dMSFr#37Nf-}foa|D4`K(uiZ+B_dn+;a?F5?lg(;sD7c%beYiokUTs% zO^`q^V3$Q-$Q5cu)42VG3jf6+?o;5f;G9;0ry|;+i6fGf=pvp-!#VKs)ZmqorI>ZD z<>4mf%Mh#3oPR>Uf~j~5_O(S)r%gGRoquNEe*c$g(xCc-0nsDD#R9?fyBQCN1wc?J5T9zi*Xg0nN-`Wo0QdIDX1iinmT9m{FJZ8qWkhM5K=r zs67l&BO`j%=hZEpd{0_;T>g>ZrWxT==VhbQuCT6x2dUviAQI%;uw1e%>Yx_Jbay#d zaF2sNMn1U{R>uuOkA(s=RrjLE@+T*UceDpk+kP;Q4p_?UAM5^U)pGl4tJduci`7zZ z#K0fx`4>a~gifGo+Yd41yrZ|!X#R1h{&dDl`4^2hcPvGff2y%%vl05ZXjCfjMZleE zKKsi!pLrp4S9HyI*P+On=$-l7@tX6Tkd^EM@XZsS*u{5i7!=r0hPxk{W0}I~R zKZ_Z^o=3o#sV+3fbH>Dq{c?-7COj&S{<||>_Wd(*;~_-gsXNG^XY`%cy}qpInMzf_ z@~7MtyD4XyuE3YE(Fc=@-_7ZGA_a=H8v*o${}} z-x~M%dmYD8`#|f%DdDL9>1Sn2VfS{!a>@1e;yvRYYA%A5)>#ToCOz763jQK$hU(Qq zI#U9q&Qq(!f(d&$hbB^aQcpi|d^s<9eV0`DGQ5C#_dLU~f;G8`yp>bM@(+JP#IYc8 zoUVwpqP30UQ_iJT*Y)Mi*q7Wxt)D^UC>dBs|CTaFH#X)i3ITzkw84!J+rDvIGAdTx z*Wg-I5a@ysu|XImSZ@8#IrpD%~-U)b(4~y)Klh?|)ZG z<-QuQYVvhdt8+x1u1TR{_%ySz61q=zL-< z+Z0ryXJ6zK!^>K5AMcyF+0PA^+xiSNU)wgFT{p58;$wDB4y#a~1uToJ1JF(x_Y!lm z=)eL0%hW31^Tpqe`W_{z|Ex{~_TRo|bF}N=Qt4`Kz882ns_<|_@&p$V_2TFte_&$Z zD1e+G`XD%Ho%m6ITKan?Fadmmn3&Tm>3TOkS?~0X5r(lKh+=a_yrtwdzhA^JFH!{` z-H8@n)qg@qkQC{H4Q&z8Y0rj?kGGvI)FG}u(X$MF`HKRPAf=0JHqWW3RtM!WSM!ou zf$=8glyXw%-ska0kBK8%KFG}Tta^=R`CdAh3Tvve3^sipWH;;BXKy}q6Q=d)RMCju z7HWMF#Ir@w#ZUWD}g;r3I>C6070k_k01{47o?lVEk_cR$DHk> zI2noreFFR0e?aPn>2S1)q_6C=Vrm$Ie$g(TGnzBK9u8YP+2d_sMB?SsefdLi?36_L zEwF-{_z{Nf)NtVXLRQno=RuZt3xjJx5XdC zGd)PVmZgeBzl?k3<-8X5RM+51O^jYPRp_z#0j;B#yK!FU%OzCBE;2OC$xQstR{_#s z>k~d~lh&B-?dV2P$KZlz`iim$OKGAYPFMeY%HYU8 zTcL-^PLpRi<6Wf#xwg-vHw1Eu7IiBdwz^UcwaejIm98B4r!z~>Y?I1m!SclRUA^4W z6a3}^CI5teLMXd)*0hgG)%zOF_qPT8{GR=pVet~WO^fmHP+e!VGei&Ai95DYM_AO3 z)bBQ{J*qHjXLWsw+su^tewj(u(?7q|<%`GMllE75#%{x3Dm4t(c5SI+?Q>|_@n7VQ z&3CQ+*AquK7r{VxH4uims9M;Q;M(?wN#SvpKY0WR<%{|)0~`#}eWa=!yrVD($;bl( z1)x~*@JXOqN1g>S>|kE*Og)m6I#DvsBdjBTez|A^lCA+xndBZ6x+~T}ONDn;-(EO?mK1RS$9G>zdu0!E#^PaH6s2jz4>Bf>q|r zKMoWy=PS4R>UwXedM(kgWD16njZ`e{=_1*&U_{}PjAs3C7z_&|j({EFL=%p|7_gJW zUeJ%7$5R3V;md*e-~{Ug@Dz}4xUW1D%*p>d%QcMoKAyg(NT?OXi09vS{HczZAaP+w zYC-H-6-$DSe%8j~dh5#;`F%;04|0#OjQ44PXigwOL@WENSw^WjNkb|~7IXaWH0fMd z)0jbb^Y5svW{t^rhCCbO6`AQ{?K%Ls`?Y zP79t-L}l*yZ)IxpMRDK^`dJ?4z_(1p5#o&uTB>rcrbu}ZM3|UZ-5Lagzx~i1^b5>Y zyADeTV~V;xjsxov)OuMFQrpWaDJU&gYrLa6AiD3rs-Xg5{u8={QnTeOzZy#X?!vi*c_;0D8d_E0VQ$X;rK{e{Q=;sa{S$R*YDp+$_xR?S ziu}?wJ(=M4L};byc}i~cI-g6s)n& z==o`{6PjJW!+qb+Rq9LP|LDlGw94gN84b*g<(QXLACK6}`TXkfpzkvvke2QLw0D+Y zQHEc?pBaW8q+#eD8itf+=)c%&fUT*S=<7dw;+CHb5ahy=FDg7L zeCzV}&YUst_uBrSm#@0~tDm$NvP62{nS<@*YVZysYVgg%I@JEPg zI5o-`PKXkK6C-5dZGcidK=bo{;TxmlW&lXIQKBJ?N6tjw;PFmyL)6&h>h$F_u1hi61rV^$+D2FQ8o|rQ}A`Ry4&K>D75<0juo#Iw^*7 z%#Uw_?yCRm;)*GTp@pa|A|>e1KtKtqiUKW&tMJVw73~GGPf|kS@T@3S0p|Y2f`TRd%Dfkt?fCHw_W>>(Ao2RN%arJ&y*8?iR6uKVrqV1yb;r{ zG9OoHR1)t0wUU((d-dWv>0a*fC*Gy-^x1->*e%~o~d`dZ_c6TM>;B%Gk z_qlP~C9gcp9RXFI?B4zukH)#f^A|D!Yf>HcDwPY>=LG4RV$m5&O!`Lvtm|mF6Lc#_ z@`q!TLR=7jXygxUAZip$it58&jekPSQp%O^6|rk0abPVSJB*cua|jOJ!<2@4Gw19< zj09lZ>oe}rs^n4`W_GSE{k6JQX%Sgshw=Nmqzt+v(hOGt{Ec#Xu&beZ51J1%M*0iM zf^CB78=rF2icU)UKCh+u^M$hgalHz>4mK#wgjf|)^<7>N45*ofWzFv?^ajHMnaVrw zCbD^s&5v7N?S-eBUfgE(@9i|E|MKc*vn>G^t_e-O;8~}wW$C?BnMDf5lbj)XiJ0t>?#nDkc zE))Kw(V39S^mr~Jbsh8Qx=Bq)r1DkO$Mk=M4nY*N_qm*}3wavXYUJ^^^Z|eIXEn|W z6yzk`Tu)JvY9XoWW;@SrIp}^TU7UIH;VmL-BmB|SD#zcYbFJH8>O0v%lI0BL;;+7J zy_Oj)_VNE@wO=z4$byV znmG&;(Z5~u@LeS|EB6&(?ZQq?R#sEfZ>4e&qe8Tg+?qYy$G#Y7HmhV<5)>=NX10DS zm791(ftRfjsOgbnBg>He>1LAG_*e9Tj**K{#A?&rmq+Gh3Z_Og9fHM%`E5%85Fh{} zjU~}LPsEK(tAQGk26(TEj6xb-)wM_h-i-NNT;yI z=TRLR5Ydc9uub}`m6qoM3sl#|xaO-_6WzC$6|=FJj|3Zq_!`F>tQroS{Tg2Z|GoX9t1ep{USQTTXPUEK5*0GbIp(1_MMX06#EXSdsYp<06h>__+)KQnkH@ zhGwtHFUN|h%Bzp|p@h=31q=*22s<-n52DjgF%f#oLm||}dycg|mTW+K0lzfpS+lKsifusZzi+zk}_vaAWQ zVC4MGic3++U)HB1hnjuCOMu)Y+u%NplR@EY%ofWFdy*Qmyo6xQ1APeK+W1D_zEHsX zfQHPxN@!znGH&Elz`U4M#2bUNz)w*t?#s%M5porlwjcsr;(Fn! zO7^*q;Nno-dZArbtvnNS(tybKjdq^Gc3aepA7yB`1Xl>4lt4LA8F5WQR;g^LZ*6K^ z&7%@Z!`%|NU_#NMHg!I$& zWMI5@j`S73JJRY>Kml`QN~YIS7gV!wSnaehNe63HqP%{C^x#%*b{#BS*SP7vxiE)$ zz2bM_i1h7}&G`!9mUEMDeC<86@y?e1xzn%5St9Mb&Tm{YM>mG~>|g9~k;ZFe3}Wrd z%<*`Nf}hr21kd!b9y>c~YFs9G_t=fZ`VLZAmX^9ZSLfcmh^X_{3Dk9VBhkzI;^jU1 zpGzxH32;Jmj#D0N1oXk9;DiF1*`N@>P~M04n@~oK1iBnzl?jPUC7B|_>mGNhU&^>zae(t*z*d-xYtF@-(Y^JFCV3Ux5hFJboJ$Gm0v6yBqo`KZU)mqJ8h+M0F~nd>Y{Fnqs-C2d@A^=y_cmy1rI#8XON<5>*SOC zdCVjG`C6Tt3^L-7|Ait2_*#R#V8LbAO;LJve^1_u1lT<9`O}E+Yc_Zdsb(~Vuv%_dUutz zlurU@d1!-}_(TjO{6}a%iZV2HS?`P!%iv*d%+(7!)0-a~9aRZ0276n5m_U?s;Rg}f zlvE3k-|dM=WXMI`{Z+Oq)O=a6nfJi3ugu}}CRP5obKf_m2$S|~$tdKQPu`UMnN~q{ zsak5i8sD0Ymxp|b}E@KOcodyx_vPKddHKX|S$o@`GJ z2Sw7tNf3^3A_NUKFwH(Rib5MSSC8P&RsI38!$Hx&XO2ESz2PGSU_X+K^Tcoi6f|3= zLtWq-YZK73{ORVY{bzw2A0DTS#$#Ppvuw1iFZG_>K#U%lwmWjsetQK*b#@+FbnzTB(5& z6TCY)%^IR0iiJI5_($jzL}gLBaD3`A7~h?%a+Qx(YM4~us%lN#?))C@^0yQ3LqC-E znP=k{LX+O#Nq2(@%2^UB!B5LFy*@Fwq`ea$W@ideW(D*0{sA;hVIOsjOw+>_Zw+$a5#I;GNq;g8mb>N9oNdENZ zw6$`wWVbKtBXeE)!AxC&z3L-riz%DAu28KF<3abFXRht;5qv5vD9V)Pw%&*D*`JhJ zcUN|7hD~`b64}J$e{cG`Hr38%dEEC&V)h}^*PjMen@`p{HtG)w27CF#*Sdxo>TeCj zVB6!HyLUgDles@pmnfFg)m_jr!j2-9xLv=!e(-t1<69wI3_&Jb3yOfy{=y{R`^K^Q zBUy=ZW3}ItMtNgvq$r#|k0gz;4;>niu!GO9;ZJX| zQxRSfMhB)Nr}XcPSXdWAlaH~x9*KD2SKofM{KX{00iR?k2)yk&|8gD9)`11ZL7|}^ zox`At7&|B)Gf_GKVy6TG+qc0S?6Lqeb|ffWJ3N$t9X`O=ZVp9n^U&}1JB7LNL~^V2zzW8wM~Q{&uvA;Swed24-Ix3QN>kCx5x?(&U1CzP)^v~E_xi*or{yVe z=Yp2TwX=V!Go^XTb#?)~&ep@F504I?J^C2d*)>PL2GGVsglQ9!Z)tvtc z_mc#{7eO`+-n?Jt^AnXuOhWF;M=U)_bz7SAq&D{9n_uQv_Z~J{uK$PvP4W(5R#~+m zD8sv0gbmHjZlFy>|(B~|%9NE`8tKbJznT+Fw4~_dw@vsVSl@}Smvj>~FvJsKY z`Kdh{+cvpgs(ZQOM+GZeT6=6o95jb#@rq*ZxVm-QaXMV8?U2+1EDTk26`D|sihB`y zOs$&x5bS-{&S!ex{DwGyi8-IM?z_~1ku=OCcH6i}%B?M5!8IIiW;{D5AkO`WNnsoq z2Y|k2cG0nwYxorP4anEQ?_qY{(k9kB9~DWm6eU08^NhwtFCk$gM3`%^91%w zeLa3OlYG4;ZUy}pLMKt^!>mccUTV$$HcOh_Qi%+~s8z2XM|z3n^yRBYJ+I-6*;dQ; zX8i2v#9s04eBsCPG#X)6Ha=1QgaRX8EXbZ3ip8|6BPS_xi7_iz3q$p8t|*+^(cSG( zvM<#f!ek~%?V~?$bjwC}LaRUjrQThu39G*=efX>DFsWyN&p~&zOJ=G~QJV*F((i;b z$@bi7>!a7C7Jvdwu{A?U7-Ym%kFY_8>7)ETAyv{^~Q1czsPdB zfYr>04R?+Nh9rYxBIXvGYuN31I3kR)zScoqgMMvqr#@15hP_VWm%W$Gt-hZcdcf=M z{!7XBm0n@j`malRvEd%4Up(RcAEE-*R2#dtTX)ZpH?2k?hrZn#J=ei?L8tdpysJTx zBO}M-Q{l=$_jq73C-}#rfcK55T(;}Twm_Q zTR;9S-(>sShl*GDN;{s)rb-7coY0ui0eun0Z+7{rYfP^1bF5A_SYJfDO8$@1E9;-( z&WEMVSp3=YH_dNf9kn8t%EkTvPLnR&N?V?54O@fLWlzsxAHE{(x4g+X*^#t0Cti%- zsL2#fW?H`mG6Lv%~?ABw$h}Rt^z~!`BQX*ruM}Tt#$D8(ppwZpGMeb4;$Wv*u+^8 zy<6Hb3q!tetM54eN9YJEum8`eV9%%VEKy7RoxCs~+ADX#B&*vs^$jX!3pJ47#-Xg- zQ%ggRW9f?*_1`(w>bZZom|Xg?GbNve`aIb&NO;^FoipCYW~w#cvEKGBMg2ztDc+(p z0dCFHc&Fel;~A%m`@W$JslrtThgGJueU_7fw;aW3Pr!I0~2_ zb`4laEQF{Xj2P+Xp=xIehTT5{bOM!KH$ac0o#9iXaZ(VZbu;Z);=$FWbBLTs!QtNU zl|loFknnW^lVpiGi+slgXW+cLo`>sVRhgFogh+3F(Po0>lq#PoP#|2P`W*Xic!sL6 zFV8Q0hq)=R@tvE!e6DA=2x-d!o!#FQqC&1@ZB?&Kf>Lz73HSvUFK;=diG^y&hwyup zc|N;(^L)Ik*FgpgN&Z@>3(s>Yj~^W)HUBu-J(1asWomI?7h#Jlqp6l!O`&IwGI2g~-t&0;Kxe~3}^jlQKG{?RiY21YDe|p z*ZivG?Dwg=hbdyq1_%e%MzbeW!v%$_Zdx+}Iie_Vdjo-~lh7ri(%-9i`R#)8bG#C9 zjYl055Gk!>TaEbK8J5Jos#dzkA7-5228A^Y##!nlCsyqZW&~u^Je*R=*66!!Sy>$* zNB3nipD;&O`tZb<_)qNcYG1G%>P*bIb_29kqYI90{%D_ooCbynl8NrqGasT4ES&%d z4=aK(&#{=^feV`-X`Gi8urSL8&?hq3ZOmTN{c?JRU%8J1#EtdO?;QO-EN$@fjZP@< z*>plMi_D?ey%0HL=qD&wr*wSUo_~Vkev;`HH=H~r+X=n$D(yE#Hp1uqpSUki z%}3i@IRAN~T;KMt@Ug$ChJS=Xf0@3ZwWU^_M%@LMh;xnc6X(bS(yM3_$WqVq2vry$#ZK0Aj$#Db&CiG zsLzDtE+rqvI>Bew2joy`Em-#Qy6n5Bd%Yfck1jKHzIa-{b~wE#dm*|i z@s^7VN0VWFU2=-avm5rp+%8gQ+Ci#4xQ*C(c_}Edxs5I>zt;j)W}J zqU77H)GRD;HJs-_j69r!#F2{09U#i88U_a_~d{Wu3M3&{`XaY`IgRHB_9J`T2` zF!J~p4iL})2Wt2zW2y2W*t3L#k?U&~8VJaO`_=j(ecAb%VFEnz^~j#;ccSkPqR&Nt zXUBzBr)9x{Z20~WItAs9y%}w+r%yZ)x6s%tm5dXf$cw)8t{S|EMl5{p3+AgLSei1c z{B-4#R6}ayHuA2nnxp1Om#jj52lsfOp(9|&D4jZb-|fgdYNkHlqNO9tire(TYrQjf z_w!y-7pe^+rp(-9@D4w~g`^}T3gxqEvbh3`B!fo=!2ji?$^L2@0bYT1?OZtIC zH3k!OYnRu$US4gzlalp~uZ{4;TN_oAkk<2m!u!oQ;MfZ3LLZ2F;V1G%SO1A25L&0`{(ne_P^N38|ElzJUE*yhv7yhv@iBB##=FV6&cjm9_f?l6 zI0yE)#)x{U*accI(&IrhEt_Wb-d~iscQSyTljC7JLe+Jz%^E{ve53v1oE&nnM&n-h zN>Aib2Us~-!+vtdv21VTYQoP9Vc|!M#(VdEE8%F!H*uG|Y|<(4AeM>M*v=Kd5^RTK zcV94R-~4UwLx_WACv5is2m_*+9a@OEm?&bu(=@!daqE^C!%^e%+^Zr=vy}V(Eq97J z;A_~FueOa&m;)ZFf#&;hm0+LZSX#(foxikL2DMsF9*vSWwuwtbc9d8Np0GJ0m4{;S zFz3j5WqBvmq;CiQRrd_}rRYMqXNTj|bhUg{2w!mN_B+%dY?UuQGU!v+P`*85sZ6Th zN(R+fa*-v8^7EUCw{fdK#1F>Ho}O<$Iy}66Zt+pU-3-73(3XlR|Dsx>-HOFNpiP)e zai7b9C8w`LnFk9u^16DrPa(p6&U@2xjZ` z5ogJN|HogyQ8%4hY~SbM2k%6#jFrN)E{+d3Ta1lX8=PbE`lo1~E#XBiD{@NWs6ooe zgl1Np3;Ub=@*}qyG@$yTj$>ORJNWAH{oF0x?O)phg5loOc18{&tZMx|Cb3);XAWdpu2lJ~=t@%ksca8B{M% z+xPaxY{4g9LFY0&fz+rtOV^==x5~GSYkL)eWTWt~dYAGk)voV0hWJb$o2^BIEbmO( z7muGVZw)Xo)c~+aKzfr{cFGWsi~tBDMgW4HMx>2}Ab{YU&`x3dktaVm!JR}SFwaqJ zdn$N?yggZG?kl|U(VRR#tv}J+jMwf6v2L>PG1R6jfZ{sne!McD@genXJe>(L;uy)| zus1PvZa=j9wfUb+;V=p+bTldm4^He6&D}YyyKXV^c(Ul4cQxN)aLNZ4Ov}@|UNXUg zwM2E$bsny$zZx?$@Vd4s<*j2-Q2e(W z4yrfxKGk&m(-K1+UvG84?ls-)`L8-1Nxwaq_`tA^ktsYj$E4x?>}OYR!$@KGpm3i9 zvarrUXt0taBE}!P350b&AAKIc2Rs4b;(&kA)=aY_mjq`tlT3&6MXxTq%)AiUwaP(@cO#qLcWq$=l%P~Zv>9t;LxNb8B%yDO!YvNC;aK| zw;dsIe!6tv8Yxk#aO~`iF(lnRWkD1QIqV>^d+WK?$JU6v7waq_w z>P#NZu`>`X8YhI^z7!N` zb#PD+yN4)!_oY}kA2|(5`=8wL3kks_h?$?#Qgg_^H2+050O0xJvGAW$&)mP?C|JIw zdzg2VBCoWGDdhjf&`lKW_0fdjA?JibKQ!{HR8sDZV^CqWdarqhTz&7=s!{lHr>^C$ ztCacSQt)Nr%d4Qv-9}3%ytr1K&c%+04|6p{r){o1bKFmV`rrI$lbxJm>53ECAXk-g z1TkOyR$Ky=CApVB0?w7%Q0`Y*&8gtTY_J3ki$=^>^)_m)sM7h{vDml7BXMSkca7Ao zU^H7`yJFP@6g?aYNEed<{e`Onn5Y~{W8yU+bMJ*5wI&F{n+HC$j$3nozs__V>2OP0 zI#oHJF2_DW2Z1$6#n!QlU>MxAPL7OoT~R%4nNF85j%X4?#hIOVFGE7!(0E`~D+2;Z zW$81;M=Y|kUpSWXd{H!cs2P*+UW?|ZhH!-f*7nyIn|MvkjJ|cVa;SA;AX#YEc06R8CkAL^`$$fPec{w*^WoCQW+sfb zqeK%h+tPA69HQx%Nr?fPkq7|xB(6Ri#TtWayZ2Lj43>;deZv7Bo9w=3hk)g7a8T@S zo1TaEIqtq8Gh+T9p~EP&^Zqb#Q*aVB2EBM&d`(_m<2Dvv^!`JB(6wmnIP<4P6*cLV z+J5)`foz!--O zm_}}7pk6(QwS^-BkO8p6(7ZQ)uXjbawk0|BOWzAv*`poH!K#^37txHl#vHB&Az5S}Ymey!nO;ok zO2iusnw;+my4hQE`;&1SKZmA7e6j&4V;NF^g<-CmM`%6m8kX_Ea6D>s`mIBaVeRm1 zYC`?-dAzx7mB9hle0*I4hLeuiK!O{WW6zc^qn$)1A{(^It@&!+dMf$|mZc ze^(H!oV_o_)pRUkUJjH92r}Fs$aYei(wGS5RNoF$;c}wql9!8d)qW^=I4=^0Dp8E340PAzzRV@97?fQ6yfKa1aj6nV; z^Ae$&_v$K!4)J83$w3oVMi727s$d<=maTSO+#ZXoj2@Z42D0a8Jl1<|8tn*+hPdV> zo9g$OxN(Z-0hi%6&_$;?L*1`-{R#R*i!X6%q%5A72dn*TL5x~5xaORODl2i^z`B%j zhLg#lV&u@{=C7RwdG*KjbxP#AS{|$y_bOnB-7gAUqQgJV;0K7}uW&}tIdGO;%6^U< z%g2xcurU1qm`U1X%mv_5+GK85u(0qpQatVac!))<(O~mus*dY1-S}tT*mi=|mljVj zrX6O&i@RP0F8^Za2y1!$?=Z0!?!*HGH1de~`i=IU>of7-xY>o__N&8Qhm~HZB6szO zr9ca?n-s?>=wn^I0ABB7d4CKSuVbIhppY|CFDpX&Kv#=_W01m0 zXESb6nm~>xU+miaWx!Y8$@)#h-OrrPVSzc{3F`j)R2AX9h-W&LE=q_eC#M_%{A8;l?f;CGsMZgu! z>V1WkjgFAr*Q%ua;$Dc>$|p1t{G}85gB3?6MXzk$+OITbc5n2B9)CaI2pfEIP*om% z;@s{WONH}V={?K^=%|q9Lt+>2Z6}csi*5HH9U^2u6lKa?B%7bh6Y`4LwBA?m-7y-S zk!5sy?|XI-nEgYav^0==8;U&I5$NlYuY2~?_A4cyf)mCmCvxly{!y9BlJ4cs&@V&w zuP{MmTahK92E@nflf~{Dw36NU6>@*OPE*{*`}`H&@mSrlStl-)J+ENT#>WirnTMdx_VJ^Hx%m4C+Bw9E%o~0aO2ld9bkHn)v69P`7 zTm|!BVv}9Vk2i^W+p6^fROB;=JCu)Hx*T=?h zJRQ~4Uo$b8bWsqrUH*kdryBJ%eK}i;?y!I^a>g0&)J(F2Q;o#Ny6q46WfXy)1=XX` zID{NCT+tA8uFK$7n?h(9Zb-0vUVtTW*mBnIg?M}6s2d^i=(PU+&yQL{8Ch+spT6#) zjq}tje3cK$V0?NfKJ~Hpz*H)yU=e5!n6ztSg>m@i*^g2nL3Y+Wph_H!vfH<_N-_+E zodAHNmJooz)7XsAUik0|*6PRmSgQS+sJ-2@`skhW^sI-~W8KsK2`#E=!(~mO*xl&4r!kx3GwP@Q1cFlN-ko`>EuUt6}mK*OBj540>%Rk?aU(k3h zPur0&c5meHGvO3ip9OVQ_o^=xq!7f@=nV)dzlpV2Fe9xNC+97kyxR+`xsp*WX||Gi zsruL5z*Zg+^WO)Xi3tEeQ^J@`4!~Q`Hi6ip%q3`M5g0ps9np*|bus%s>&qjP2-mbse$albrT%alY-ye}X1s(_c2y6&8DxZvYB`%T?Ti>1EaaB9 z6&?5h-;KZ?5v?atm9xmnyjXQ+3>k{&HC(`IbFHSjp6hyV4gk_C8hN#XNOIsdMr;%+ zjsPHuI-Z@rd+3lH4vnqr0SGPw?^x`DOX+S-jkf*Zd)j?aR zhC9%&t{=2{nd5JoL8ChM-QnCiSw69kT{ae(3J6oL&{IvnO@?KHbS(>F|6^l4jiULB z^Ip)6lOS5uVrPNI7@+{v5G6VMJm?x2Gb6;1&zGDJs~$8PXE(C&-{^|LxAOa%?gpb_ zwr(IVUs!6>xYJ3h8q&@ak4obfiYDT7#>#wrbM@tAvaHG9*R|Jwjn9u$Y@WQV+OG9y zt65XLZ!g@5-k|)MnB(9Upg|Cd1I~{(Xd|KwcL7G0Sfq#RV&{K{Z1+8F&_d=1yowpe zE3#hl2trzEamBe_h_XEX`GA65F*=mhkq0}IOYlbz+cz1SOng7h; z>m-eSAB~4Y*JuK)8#<+6%IYlU3~frunw~V`WH}f1BBL0uEvJq3b7t41GnnON&tDlJ zY|V?X=r7;yny={kDVQ>GIjwWzWful;*7^h@PhFzJ%{wg~>P4Umwy-AYA(+KS+j_*F zRUfhSf`n5_-&nIHuW`m&zY`KkR@4_!ym-sPh{82Vo%gUUHLQKV^OZ>l1IhuWf z5xqbbtaJr4l^^BTmV)VH&3N2k@(gCyL@eQ5Nq`X>8oVpKj3-v6@%=mBQZEeWl&cUt zuRD)eBtJ^Wo|xo9;VdIn`{dx`9%UW5)_UnFHaNS8Cwp}Y0+}jID+}Zqk_nnBkdy3+$Y#Z{Y^MAz<>;E^yiC&loJh6|v6&1$ zZI#W$1}$)f`Xy)Dy@swxHFvlAAv)cs?Mb;bjwdeDCWEkH3nX3o_75QG8g*iW#t2&^ zrhFmEv>;8Iz9r{R&KS&!@`)0v82bpvW^p#wnez}YkRW^RT2P~Jf)%gDV$^V(HJYnm zyb976)>cn&08uDA>6w%%8e|DBsNYO=OoToVPG8c&sEy|f8o*OVr&yW)5&8k3G$veG8Y;AMn*OLt8`b+{c@bF&LOFaL%u3^_( z%-4m*f=z$S!E$i<;j&`+_o}bQ#(dQTQv$h?pn6zjR3c=yWwIo4d-asx$F=ll9p395 zvU6A%YEGCtkoD6T3&(buT4KC$B&bzAj)d+JaIw8q1BfcE_8S`HDOb=nc zm~8w?pFlL%k61sEH%sd+zv|MnNh`ZsWo75s`SuJ>y0SIu^zOo|Cj@*}tRK_=P4tZk zk~!?wGL9M65W?e?tC+&)OQMKJp-42|zOJTzpvvKz%I7wCgEgdYV9YC7uEOlEZt#FT z#wlJ{jeZ=5Q`Q?+Fs9*T7imr_H{l3%J5JrI5a;J?>1Gd0uRm83eJc@j{*Ta26de+$ zr_q;%kXU{Nf4!Uqwol7%nrW^h3QbV`;s&&BabE45{8HBxNubUhsO{l0t16TU%@_Fg z@q-Vl5Z~FLkygq)60ezD-K$=%PKc041^rc1capQ2J0r<}py)m}6}se;sWFyi*2 z&_2*%MtrFd-QFl=ixx4Md#E!ME!XLfm#?XA>0XTI`P;%sI9h9FVkI)$Y$(+QvzJiflUkR%iEUl!1 zo$a8YHHvZ^9%~dyL#I=XgItoQ(Xm|@=5uR)pvm7G*5U$?YshzxyL%@?Tz%5$A{Z{IYP^jjWQ@MfOCj@y)`7mob_pl4Hg}op}g|l6d`u!X%C*yS;n&!-vgnZHMIgi z^Wk|gyDl)Ptnl6p{({YOJq6F?vOTM9QrbU zbF!Iy^``#W?p~p*m9-Um61_$IlEWbz~ zh4lQaOS8n*?fnml1LOQo-5<0@nq#t6BW9{mjf>+}yIHN9hqfO~9=mKjK8fpcdS*57 zh&Dq6iA9PXIIMvMH`5?bW!#5h@$lrbL|!%%o6ia<6DB@erEF(awg_<2C}T7hIJ0*O z6m@|{O4Q8F&4%QI4oT=Uz9PfaNvR%_af&7?zv0MIRx!vgq__XxpS{eiy58rIVXTyC zqKnNl{+*PEi~N0PC9SAtIZ1j(UHs?(79mSI5Y^Y=MPGo3fZ$^l z1|gb@TuH!5P);B%t{3P@5`}Ofx)wMFbp(YNSrGyH=z>wXt&@i*HCn=~8ELzBu4yt( zJjEd$eieB7Xn`;dMhkzkkehW)k{s^EU0I>l39^M<+J%G)JEB>-RSnQ(po$mFtdh!j zr=(3p2N~QFCG@7%FMA&UJqI!bK3CnqtlW$b8t)0?d(Ty@w(_n1jfbzoJ)nOLJSqXe z&{PU9Axa^qf24eLC(CXCpDzPU3Ui)OArB}wQTo)Xm#dm1Qu)$C2I<)}H95dJ6|=Fr zOwK}yK~+UtK1Z$wturG*MU#yE?fET6>V!pHnrAjh2!AxUa0G2g*P7=w4`C9IB)gk< zCfR`d^Yize(;1~%23(J&PEEL(?CaEvxad5Xzy`cVtTWNDhqd+bOWeLrv1ZrUQw2MI zFp35kjahY~R(v0j%0S)b;UCj)C2d)P>Dvv27>Cvpo znTca;DS4w^m8R@S_p14k`Sce^+76zEpm9FobFaF1Q_V6FXPJ&oAS9DkOuE7|s=bT- zQ$y}iN6~OW*i=!pKHGx3pp^Rs?X^rpDoA=;IYY5ib=taGi)gEN@&CA{n*0y1e}oP} zG$2SfzZa__y3K+nvi$ac;yba~KV|?j$y{oXz8o@M7G^c?7Hw-jBeYhZF-CGS? zD1;PaM^)IHQ#6c?lGQc*O;q}3c1o0L#b}jzD6wO>31Sq>6iJkY6BiXH{gjni)78rj zr&uYm70B6@M`M)VBjfNA+MU!}Ot|?HNU9Z#G)ojIu~@QOwtXPKIK-dIdsVl*fIR_3n;B_zF5K$ Date: Sat, 2 Aug 2025 11:22:56 +0200 Subject: [PATCH 11/20] Add logging on file sizes --- .../Connection/InboundNearbyConnection.swift | 2 + .../Connection/OutboundNearbyConnection.swift | 3 +- QuickDrop.xcodeproj/project.pbxproj | 8 +- QuickDrop/Localizable.xcstrings | 3054 ++++++++--------- 4 files changed, 1535 insertions(+), 1532 deletions(-) diff --git a/NearbyShare/Connection/InboundNearbyConnection.swift b/NearbyShare/Connection/InboundNearbyConnection.swift index 6c001c0..5b03344 100644 --- a/NearbyShare/Connection/InboundNearbyConnection.swift +++ b/NearbyShare/Connection/InboundNearbyConnection.swift @@ -505,6 +505,8 @@ class InboundNearbyConnection: NearbyConnection { #endif filesToBeReceived[id]!.progress = progress filesToBeReceived[id]!.created = true + + log("[InboundNearbyConnection] Accepted file with size \(file.meta.size)") } var frame = Sharing_Nearby_Frame() diff --git a/NearbyShare/Connection/OutboundNearbyConnection.swift b/NearbyShare/Connection/OutboundNearbyConnection.swift index db4ee08..5f6cf81 100644 --- a/NearbyShare/Connection/OutboundNearbyConnection.swift +++ b/NearbyShare/Connection/OutboundNearbyConnection.swift @@ -348,9 +348,10 @@ class OutboundNearbyConnection: NearbyConnection { try queue.append(OutgoingFileTransfer(url: url, payloadID: meta.payloadID, handle: FileHandle(forReadingFrom: url), totalBytes: meta.size, currentOffset: 0)) introduction.v1.introduction.fileMetadata.append(meta) totalBytesToSend += meta.size + + log("[OutboundNearbyConnection] Sending file with \(meta.size) bytes") } } - log("[OutboundNearbyConnection] Sent introduction: \(introduction)") try sendTransferSetupFrame(introduction) currentState = .sentIntroduction diff --git a/QuickDrop.xcodeproj/project.pbxproj b/QuickDrop.xcodeproj/project.pbxproj index 205deee..a506f7c 100644 --- a/QuickDrop.xcodeproj/project.pbxproj +++ b/QuickDrop.xcodeproj/project.pbxproj @@ -908,8 +908,8 @@ MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; - SHARED_BUILD = 58; - SHARED_VERSION = 1.11.1; + SHARED_BUILD = 59; + SHARED_VERSION = 1.12; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -970,8 +970,8 @@ MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = macosx; - SHARED_BUILD = 58; - SHARED_VERSION = 1.11.1; + SHARED_BUILD = 59; + SHARED_VERSION = 1.12; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OPTIMIZATION_LEVEL = "-O"; diff --git a/QuickDrop/Localizable.xcstrings b/QuickDrop/Localizable.xcstrings index 54ffcfa..f957cc9 100644 --- a/QuickDrop/Localizable.xcstrings +++ b/QuickDrop/Localizable.xcstrings @@ -1282,108 +1282,110 @@ } } }, - "ConnectWithQuickDropApp" : { + "Connecting" : { + "comment" : "Share Extension", + "extractionState" : "manual", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "الاتصال بتطبيق QuickDrop" + "value" : "جارٍ الاتصال..." } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Mit QuickDrop-App verbinden" + "value" : "Verbinden..." } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Connect with QuickDrop App" + "value" : "Connecting..." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Conectar con la aplicación QuickDrop" + "value" : "Conectando..." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Se connecter avec l'app QuickDrop" + "value" : "Connexion en cours..." } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Csatlakozás a QuickDrop alkalmazással" + "value" : "Csatlakozás..." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Connetti con l'app QuickDrop" + "value" : "Connessione in corso..." } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "QuickDropアプリで接続" + "value" : "接続中..." } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "QuickDrop 앱으로 연결" + "value" : "연결 중..." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Verbind met de QuickDrop-app" + "value" : "Verbinden..." } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Conectar com o aplicativo QuickDrop" + "value" : "Conectando..." } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Ligar com a app QuickDrop" + "value" : "A ligar..." } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Conectează-te cu aplicația QuickDrop" + "value" : "Se conectează..." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Подключиться с помощью приложения QuickDrop" + "value" : "Подключение..." } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "เชื่อมต่อด้วยแอป QuickDrop" + "value" : "กำลังเชื่อมต่อ..." } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Підключитися за допомогою додатка QuickDrop" + "value" : "Підключення..." } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "使用 QuickDrop 应用连接" + "value" : "正在连接..." } } } @@ -1494,110 +1496,108 @@ } } }, - "Connecting" : { - "comment" : "Share Extension", - "extractionState" : "manual", + "ConnectWithQuickDropApp" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "جارٍ الاتصال..." + "value" : "الاتصال بتطبيق QuickDrop" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Verbinden..." + "value" : "Mit QuickDrop-App verbinden" } }, "en" : { "stringUnit" : { "state" : "translated", - "value" : "Connecting..." + "value" : "Connect with QuickDrop App" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Conectando..." + "value" : "Conectar con la aplicación QuickDrop" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Connexion en cours..." + "value" : "Se connecter avec l'app QuickDrop" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Csatlakozás..." + "value" : "Csatlakozás a QuickDrop alkalmazással" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Connessione in corso..." + "value" : "Connetti con l'app QuickDrop" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "接続中..." + "value" : "QuickDropアプリで接続" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "연결 중..." + "value" : "QuickDrop 앱으로 연결" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Verbinden..." + "value" : "Verbind met de QuickDrop-app" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Conectando..." + "value" : "Conectar com o aplicativo QuickDrop" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "A ligar..." + "value" : "Ligar com a app QuickDrop" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Se conectează..." + "value" : "Conectează-te cu aplicația QuickDrop" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Подключение..." + "value" : "Подключиться с помощью приложения QuickDrop" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "กำลังเชื่อมต่อ..." + "value" : "เชื่อมต่อด้วยแอป QuickDrop" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Підключення..." + "value" : "Підключитися за допомогою додатка QuickDrop" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "正在连接..." + "value" : "使用 QuickDrop 应用连接" } } } @@ -4062,113 +4062,6 @@ } } }, - "NFiles" : { - "extractionState" : "manual", - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d ملفات" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d Dateien" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d files" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d archivos" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d fichiers" - } - }, - "hu" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d fájl" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d files" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "%dつのファイル" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d개의 파일" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d bestanden" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d arquivos" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d ficheiros" - } - }, - "ro" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d fișiere" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d файлов" - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d ไฟล์" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d files" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "%d files" - } - } - } - }, "NetworkFilterDescription" : { "extractionState" : "manual", "localizations" : { @@ -4383,6 +4276,113 @@ } } }, + "NFiles" : { + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d ملفات" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d Dateien" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d files" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d archivos" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d fichiers" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d fájl" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d files" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "%dつのファイル" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d개의 파일" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d bestanden" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d arquivos" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d ficheiros" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d fișiere" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d файлов" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d ไฟล์" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d files" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "%d files" + } + } + } + }, "NoNetworkConnection" : { "extractionState" : "manual", "localizations" : { @@ -4917,6 +4917,1388 @@ } } }, + "plusview_description" : { + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "قم بفتح QuickDrop+ لمواصلة تبادل الملفات بلا حدود. QuickDrop+ هو شراء لمرة واحدة – لا حاجة للاشتراك. تشمل جميع الميزات الحالية والمستقبلية." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Schalte QuickDrop+ frei, um weiterhin Dateien unbegrenzt auszutauschen. QuickDrop+ ist ein Einmalkauf - ein Abonnement ist nicht erforderlich. Alle aktuellen und zukünftigen Funktionen sind enthalten." + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unlock QuickDrop+ to continue exchanging files without limits. QuickDrop+ is a one-time purchase – no subscription required. All current and future features are included." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Desbloquea QuickDrop+ para seguir intercambiando archivos sin límites. QuickDrop+ es una compra única – no se requiere suscripción. Todas las funciones actuales y futuras están incluidas." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Débloquez QuickDrop+ pour continuer à échanger des fichiers sans limites. QuickDrop+ est un achat unique – aucun abonnement requis. Toutes les fonctionnalités actuelles et futures sont incluses." + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Oldd fel a QuickDrop+-t, hogy korlátlanul folytathasd a fájlcserét. A QuickDrop+ egy egyszeri vásárlás – előfizetés nem szükséges. Az összes jelenlegi és jövőbeli funkciót tartalmazza." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sblocca QuickDrop+ per continuare a scambiare file senza limiti. QuickDrop+ è un acquisto unico – nessun abbonamento richiesto. Tutte le funzionalità attuali e future sono incluse." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "QuickDrop+ をアンロックして、無制限にファイルを交換できます。QuickDrop+ は買い切り型のアプリです。サブスクリプション不要で、現在および将来のすべての機能が含まれます。" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "QuickDrop+를 잠금 해제하여 파일을 제한 없이 교환하세요. QuickDrop+는 일회성 구매이며, 구독이 필요하지 않습니다. 모든 현재 및 미래 기능이 포함됩니다." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ontgrendel QuickDrop+ om bestanden zonder limieten te blijven uitwisselen. QuickDrop+ is een eenmalige aankoop – geen abonnement vereist. Alle huidige en toekomstige functies zijn inbegrepen." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Desbloqueie o QuickDrop+ para continuar trocando arquivos sem limites. QuickDrop+ é uma compra única – sem necessidade de assinatura. Todos os recursos atuais e futuros estão incluídos." + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Desbloqueia o QuickDrop+ para continuar a trocar ficheiros sem limites. QuickDrop+ é uma compra única – não é necessária subscrição. Todas as funcionalidades atuais e futuras estão incluídas." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Deblochează QuickDrop+ pentru a continua schimbul de fișiere fără limite. QuickDrop+ este o achiziție unică – nu este necesar abonament. Toate funcțiile curente și viitoare sunt incluse." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Разблокируйте QuickDrop+, чтобы продолжить обмен файлами без ограничений. QuickDrop+ – это разовая покупка, подписка не требуется. Все текущие и будущие функции включены." + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "ปลดล็อก QuickDrop+ เพื่อแลกเปลี่ยนไฟล์ได้อย่างไม่จำกัด QuickDrop+ เป็นการซื้อครั้งเดียว – ไม่ต้องสมัครสมาชิก ฟีเจอร์ทั้งหมดทั้งปัจจุบันและอนาคตรวมอยู่ในนี้แล้ว" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Розблокуйте QuickDrop+, щоб продовжити обмін файлами без обмежень. QuickDrop+ – це одноразова покупка, підписка не потрібна. Всі поточні та майбутні функції включені." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "解锁 QuickDrop+ 以无限制地交换文件。QuickDrop+ 是一次性购买 – 无需订阅。所有当前和未来的功能均包含在内。" + } + } + } + }, + "plusview_nothingtorestore" : { + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "لا يوجد شيء لاستعادته!" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Es gibt nichts wiederherzustellen!" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "There is nothing to restore!" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "¡No hay nada que restaurar!" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aucun achat à restaurer !" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nincs visszaállítható vásárlás!" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Non c'è nulla da ripristinare!" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "復元するものがありません!" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "복원할 항목이 없습니다!" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Er is niets om te herstellen!" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nada para restaurar!" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Não há nada para restaurar!" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Nu există nimic de restaurat!" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Нет покупок для восстановления!" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "ไม่มีรายการที่ต้องกู้คืน!" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Немає покупок для відновлення!" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "没有可恢复的购买!" + } + } + } + }, + "plusview_productRequestFailed" : { + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "تعذر جلب عمليات الشراء داخل التطبيق في الوقت الحالي." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "In-App-Käufe können momentan nicht abgerufen werden." + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unable to fetch In-App Purchases at the moment." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "No se pueden recuperar las compras dentro de la aplicación en este momento." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Impossible de récupérer les achats intégrés pour le moment." + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Jelenleg nem lehet lekérni az alkalmazáson belüli vásárlásokat." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Impossibile recuperare gli acquisti in-app al momento." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "現在、アプリ内購入情報を取得できません。" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "현재 인앱 구매 정보를 가져올 수 없습니다." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "In-app aankopen kunnen momenteel niet worden opgehaald." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Não foi possível recuperar as compras dentro do aplicativo no momento." + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Não foi possível recuperar as compras dentro do app no momento." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Momentan, nu se pot obține informații despre achizițiile în aplicație." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Не удалось получить данные о встроенных покупках." + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "ไม่สามารถดึงข้อมูลการซื้อภายในแอปได้ในขณะนี้" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Наразі неможливо отримати інформацію про покупки в додатку." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "暂时无法获取应用内购买信息。" + } + } + } + }, + "plusview_purchasefailed" : { + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "فشل الشراء" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Kauf fehlgeschlagen" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Purchase failed" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "La compra ha fallado" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Échec de l'achat" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "A vásárlás sikertelen" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Acquisto non riuscito" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "購入に失敗しました" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "구매 실패" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aankoop mislukt" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Falha na compra" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Falha na compra" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Achiziția a eșuat" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ошибка покупки" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "การซื้อล้มเหลว" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Помилка покупки" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "购买失败" + } + } + } + }, + "plusview_purchasefaileddescription" : { + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "إذا ظهر هذا الرسالة بشكل غير متوقع، يرجى التأكد من اتصالك بالإنترنت وأن عمليات الشراء داخل التطبيق مفعّلة على جهازك، ثم حاول مرة أخرى." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Falls diese Nachricht unerwartet erscheint, stelle sicher, dass du mit dem Internet verbunden bist und In-App-Käufe auf deinem Gerät aktiviert sind, und versuche es erneut." + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "If this message appeared unexpectedly, please make sure that you are connected to the Internet and In-App-Purchases are enabled for your device and then try again." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Si este mensaje apareció inesperadamente, asegúrate de estar conectado a internet y que las compras dentro de la aplicación estén habilitadas en tu dispositivo, luego intenta nuevamente." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Si ce message est apparu de manière inattendue, assurez-vous d'être connecté à Internet et que les achats intégrés sont activés sur votre appareil, puis réessayez." + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ha ez az üzenet váratlanul jelent meg, ellenőrizd az internetkapcsolatot, és győződj meg róla, hogy az alkalmazáson belüli vásárlások engedélyezve vannak az eszközödön, majd próbáld újra." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Se questo messaggio è apparso inaspettatamente, assicurati di essere connesso a Internet e che gli acquisti in-app siano abilitati sul tuo dispositivo, quindi riprova." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "このメッセージが予期せず表示された場合、インターネット接続を確認し、アプリ内購入が有効になっていることを確認した上で再試行してください。" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "이 메시지가 예상치 않게 나타난 경우, 인터넷에 연결되어 있는지 확인하고 기기에서 인앱 결제가 활성화되어 있는지 확인한 후 다시 시도해 주세요." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Als dit bericht onverwachts verschijnt, controleer dan of je verbonden bent met internet en of in-app aankopen zijn ingeschakeld op je apparaat, en probeer het opnieuw." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Se esta mensagem apareceu inesperadamente, verifique se você está conectado à internet e se as compras dentro do app estão ativadas no seu dispositivo e tente novamente." + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Se esta mensagem apareceu inesperadamente, certifique-se de estar conectado à Internet e que as compras dentro do app estejam ativadas no seu dispositivo, depois tente novamente." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dacă acest mesaj a apărut neașteptat, asigură-te că ești conectat la internet și că achizițiile în aplicație sunt activate pe dispozitivul tău, apoi încearcă din nou." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Если это сообщение появилось неожиданно, убедитесь, что у вас есть доступ к интернету и включены встроенные покупки на вашем устройстве, затем попробуйте снова." + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "หากข้อความนี้ปรากฏขึ้นโดยไม่คาดคิด โปรดตรวจสอบว่าคุณเชื่อมต่ออินเทอร์เน็ตและเปิดใช้งานการซื้อภายในแอปในอุปกรณ์ของคุณแล้วลองอีกครั้ง" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Якщо це повідомлення з’явилося несподівано, переконайтеся, що у вас є підключення до Інтернету, а покупки в додатках увімкнені на вашому пристрої, і спробуйте ще раз." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "如果此消息意外出现,请确保您已连接到互联网,并且设备已启用应用内购买功能,然后重试。" + } + } + } + }, + "plusview_restorefailed" : { + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "حدثت مشكلة أثناء استعادة عملية الشراء" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Es gab ein Problem beim Wiederherstellen deines Kaufs" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "There was a problem restoring your purchase" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hubo un problema al restaurar tu compra" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Un problème est survenu lors de la restauration de votre achat" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hiba történt a vásárlás visszaállítása során" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Si è verificato un problema durante il ripristino dell'acquisto" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "購入の復元中に問題が発生しました" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "구매 복원 중 문제가 발생했습니다." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Er was een probleem bij het herstellen van je aankoop" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Houve um problema ao restaurar sua compra" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Houve um problema ao restaurar sua compra" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "A apărut o problemă la restaurarea achiziției" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Произошла ошибка при восстановлении покупки" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "เกิดปัญหาในการกู้คืนการซื้อของคุณ" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Сталася помилка під час відновлення покупки" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "恢复购买时出现问题" + } + } + } + }, + "plusview_restorefailed_proceed" : { + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "موافق" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Okay" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Okay" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Aceptar" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "OK" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Rendben" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ok" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "OK" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "확인" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Oké" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "OK" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ok" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "OK" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Окей" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "ตกลง" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Гаразд" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "确定" + } + } + } + }, + "plusview_restorepurchase" : { + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "هل اشتريته بالفعل؟" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bereits gekauft?" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Already bought?" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "¿Ya lo compraste?" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Déjà acheté ?" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Már megvásároltad?" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Già acquistato?" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "すでに購入済みですか?" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "이미 구매하셨나요?" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Al gekocht?" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Já comprou?" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Já comprou?" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Deja cumpărat?" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Уже куплено?" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "เคยซื้อแล้ว?" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Вже куплено?" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "已购买?" + } + } + } + }, + "plusview_success_button" : { + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "حسنًا!" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Alles klar!" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Got it!" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "¡Entendido!" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "D'accord !" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Értettem!" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Capito!" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "了解!" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "알겠습니다!" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Begrepen!" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Entendi!" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Entendido!" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Înțeles!" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Понял!" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "รับทราบ!" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Зрозуміло!" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "知道了!" + } + } + } + }, + "plusview_success_description" : { + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "يمكنك الآن متابعة استلام الملفات بدون قيود. أي ملفات حاولت إرسالها سابقًا تم حفظها بالفعل على جهازك." + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Du kannst nun weiterhin Dateien ohne Einschränkungen empfangen. Alle Dateien, welche gerade gesendet wurden, wurden bereits gespeichert." + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "You can now continue receiving files without restrictions. Any files you tried to send were already saved on your device." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ahora puedes seguir recibiendo archivos sin restricciones. Todos los archivos que intentaste enviar antes ya se han guardado en tu dispositivo." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vous pouvez maintenant continuer à recevoir des fichiers sans restrictions. Tous les fichiers que vous avez essayé d'envoyer précédemment ont déjà été enregistrés sur votre appareil." + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mostantól korlátozások nélkül fogadhat fájlokat. Az összes fájl, amelyet korábban próbált elküldeni, már el lett mentve az eszközére." + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ora puoi continuare a ricevere file senza restrizioni. Tutti i file che hai provato a inviare in precedenza sono già stati salvati sul tuo dispositivo." + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "これで、制限なくファイルを受信し続けることができます。以前送信しようとしたファイルはすでにデバイスに保存されています。" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "이제 파일을 제한 없이 계속 받을 수 있습니다. 이전에 전송을 시도한 모든 파일이 이미 기기에 저장되었습니다." + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Je kunt nu bestanden blijven ontvangen zonder beperkingen. Alle bestanden die je eerder probeerde te verzenden, zijn al opgeslagen op je apparaat." + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Agora você pode continuar recebendo arquivos sem restrições. Todos os arquivos que você tentou enviar antes já foram salvos no seu dispositivo." + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Agora pode continuar a receber ficheiros sem restrições. Todos os ficheiros que tentou enviar antes já foram guardados no seu dispositivo." + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Acum puteți continua să primiți fișiere fără restricții. Toate fișierele pe care ați încercat să le trimiteți anterior au fost deja salvate pe dispozitivul dvs." + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Теперь вы можете продолжать получать файлы без ограничений. Все файлы, которые вы пытались отправить ранее, уже сохранены на вашем устройстве." + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "คุณสามารถรับไฟล์ต่อไปได้โดยไม่มีข้อจำกัด ไฟล์ทั้งหมดที่คุณพยายามส่งก่อนหน้านี้ถูกบันทึกไว้ในอุปกรณ์ของคุณแล้ว" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Тепер ви можете продовжувати отримувати файли без обмежень. Усі файли, які ви намагалися надіслати раніше, вже збережено на вашому пристрої." + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "您现在可以继续无限制地接收文件。您之前尝试发送的所有文件已保存到您的设备上。" + } + } + } + }, + "plusview_success_title" : { + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "شكرًا لشرائك!" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Danke für deinen Kauf!" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Thanks for your purchase!" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "¡Gracias por tu compra!" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Merci pour votre achat !" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Köszönjük a vásárlást!" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Grazie per il tuo acquisto!" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "ご購入ありがとうございます!" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "구매해 주셔서 감사합니다!" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bedankt voor je aankoop!" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obrigado pela sua compra!" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obrigado pela sua compra!" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mulțumim pentru achiziție!" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Спасибо за покупку!" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "ขอบคุณสำหรับการซื้อ!" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Дякуємо за покупку!" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "感谢您的购买!" + } + } + } + }, + "plusview_title" : { + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "شكرًا لاستخدامك QuickDrop!" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Danke, dass du QuickDrop benutzt!" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Thanks for using QuickDrop!" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "¡Gracias por usar QuickDrop!" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Merci d'utiliser QuickDrop !" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Köszönjük, hogy használod a QuickDropot!" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Grazie per aver usato QuickDrop!" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "QuickDropをご利用いただきありがとうございます!" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "QuickDrop을 사용해 주셔서 감사합니다!" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Bedankt voor het gebruiken van QuickDrop!" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obrigado por usar o QuickDrop!" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Obrigado por usar o QuickDrop!" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mulțumim că folosești QuickDrop!" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Спасибо за использование QuickDrop!" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "ขอบคุณที่ใช้ QuickDrop!" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Дякуємо за використання QuickDrop!" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "感谢使用 QuickDrop!" + } + } + } + }, + "plusview_unlock_label" : { + "extractionState" : "manual", + "localizations" : { + "ar" : { + "stringUnit" : { + "state" : "translated", + "value" : "ادعم QuickDrop" + } + }, + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unterstütze QuickDrop" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Support QuickDrop" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Apoyar QuickDrop" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Soutenir QuickDrop" + } + }, + "hu" : { + "stringUnit" : { + "state" : "translated", + "value" : "Támogasd a QuickDropot" + } + }, + "it" : { + "stringUnit" : { + "state" : "translated", + "value" : "Supporta QuickDrop" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "QuickDropをサポート" + } + }, + "ko" : { + "stringUnit" : { + "state" : "translated", + "value" : "QuickDrop 지원하기" + } + }, + "nl" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ondersteun QuickDrop" + } + }, + "pt-BR" : { + "stringUnit" : { + "state" : "translated", + "value" : "Apoie o QuickDrop" + } + }, + "pt-PT" : { + "stringUnit" : { + "state" : "translated", + "value" : "Apoiar QuickDrop" + } + }, + "ro" : { + "stringUnit" : { + "state" : "translated", + "value" : "Susține QuickDrop" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Поддержать QuickDrop" + } + }, + "th" : { + "stringUnit" : { + "state" : "translated", + "value" : "สนับสนุน QuickDrop" + } + }, + "uk" : { + "stringUnit" : { + "state" : "translated", + "value" : "Підтримати QuickDrop" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "支持 QuickDrop" + } + } + } + }, "PrivacyPolicy" : { "extractionState" : "manual", "localizations" : { @@ -9086,1388 +10468,6 @@ } } } - }, - "plusview_description" : { - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "قم بفتح QuickDrop+ لمواصلة تبادل الملفات بلا حدود. QuickDrop+ هو شراء لمرة واحدة – لا حاجة للاشتراك. تشمل جميع الميزات الحالية والمستقبلية." - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Schalte QuickDrop+ frei, um weiterhin Dateien unbegrenzt auszutauschen. QuickDrop+ ist ein Einmalkauf - ein Abonnement ist nicht erforderlich. Alle aktuellen und zukünftigen Funktionen sind enthalten." - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Unlock QuickDrop+ to continue exchanging files without limits. QuickDrop+ is a one-time purchase – no subscription required. All current and future features are included." - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Desbloquea QuickDrop+ para seguir intercambiando archivos sin límites. QuickDrop+ es una compra única – no se requiere suscripción. Todas las funciones actuales y futuras están incluidas." - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Débloquez QuickDrop+ pour continuer à échanger des fichiers sans limites. QuickDrop+ est un achat unique – aucun abonnement requis. Toutes les fonctionnalités actuelles et futures sont incluses." - } - }, - "hu" : { - "stringUnit" : { - "state" : "translated", - "value" : "Oldd fel a QuickDrop+-t, hogy korlátlanul folytathasd a fájlcserét. A QuickDrop+ egy egyszeri vásárlás – előfizetés nem szükséges. Az összes jelenlegi és jövőbeli funkciót tartalmazza." - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sblocca QuickDrop+ per continuare a scambiare file senza limiti. QuickDrop+ è un acquisto unico – nessun abbonamento richiesto. Tutte le funzionalità attuali e future sono incluse." - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "QuickDrop+ をアンロックして、無制限にファイルを交換できます。QuickDrop+ は買い切り型のアプリです。サブスクリプション不要で、現在および将来のすべての機能が含まれます。" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "QuickDrop+를 잠금 해제하여 파일을 제한 없이 교환하세요. QuickDrop+는 일회성 구매이며, 구독이 필요하지 않습니다. 모든 현재 및 미래 기능이 포함됩니다." - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ontgrendel QuickDrop+ om bestanden zonder limieten te blijven uitwisselen. QuickDrop+ is een eenmalige aankoop – geen abonnement vereist. Alle huidige en toekomstige functies zijn inbegrepen." - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Desbloqueie o QuickDrop+ para continuar trocando arquivos sem limites. QuickDrop+ é uma compra única – sem necessidade de assinatura. Todos os recursos atuais e futuros estão incluídos." - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Desbloqueia o QuickDrop+ para continuar a trocar ficheiros sem limites. QuickDrop+ é uma compra única – não é necessária subscrição. Todas as funcionalidades atuais e futuras estão incluídas." - } - }, - "ro" : { - "stringUnit" : { - "state" : "translated", - "value" : "Deblochează QuickDrop+ pentru a continua schimbul de fișiere fără limite. QuickDrop+ este o achiziție unică – nu este necesar abonament. Toate funcțiile curente și viitoare sunt incluse." - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Разблокируйте QuickDrop+, чтобы продолжить обмен файлами без ограничений. QuickDrop+ – это разовая покупка, подписка не требуется. Все текущие и будущие функции включены." - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "ปลดล็อก QuickDrop+ เพื่อแลกเปลี่ยนไฟล์ได้อย่างไม่จำกัด QuickDrop+ เป็นการซื้อครั้งเดียว – ไม่ต้องสมัครสมาชิก ฟีเจอร์ทั้งหมดทั้งปัจจุบันและอนาคตรวมอยู่ในนี้แล้ว" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Розблокуйте QuickDrop+, щоб продовжити обмін файлами без обмежень. QuickDrop+ – це одноразова покупка, підписка не потрібна. Всі поточні та майбутні функції включені." - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "解锁 QuickDrop+ 以无限制地交换文件。QuickDrop+ 是一次性购买 – 无需订阅。所有当前和未来的功能均包含在内。" - } - } - } - }, - "plusview_nothingtorestore" : { - "extractionState" : "manual", - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "لا يوجد شيء لاستعادته!" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Es gibt nichts wiederherzustellen!" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "There is nothing to restore!" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "¡No hay nada que restaurar!" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Aucun achat à restaurer !" - } - }, - "hu" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nincs visszaállítható vásárlás!" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Non c'è nulla da ripristinare!" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "復元するものがありません!" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "복원할 항목이 없습니다!" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Er is niets om te herstellen!" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nada para restaurar!" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Não há nada para restaurar!" - } - }, - "ro" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nu există nimic de restaurat!" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Нет покупок для восстановления!" - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "ไม่มีรายการที่ต้องกู้คืน!" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Немає покупок для відновлення!" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "没有可恢复的购买!" - } - } - } - }, - "plusview_productRequestFailed" : { - "extractionState" : "manual", - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "تعذر جلب عمليات الشراء داخل التطبيق في الوقت الحالي." - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "In-App-Käufe können momentan nicht abgerufen werden." - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Unable to fetch In-App Purchases at the moment." - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "No se pueden recuperar las compras dentro de la aplicación en este momento." - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Impossible de récupérer les achats intégrés pour le moment." - } - }, - "hu" : { - "stringUnit" : { - "state" : "translated", - "value" : "Jelenleg nem lehet lekérni az alkalmazáson belüli vásárlásokat." - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Impossibile recuperare gli acquisti in-app al momento." - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "現在、アプリ内購入情報を取得できません。" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "현재 인앱 구매 정보를 가져올 수 없습니다." - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "In-app aankopen kunnen momenteel niet worden opgehaald." - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Não foi possível recuperar as compras dentro do aplicativo no momento." - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Não foi possível recuperar as compras dentro do app no momento." - } - }, - "ro" : { - "stringUnit" : { - "state" : "translated", - "value" : "Momentan, nu se pot obține informații despre achizițiile în aplicație." - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Не удалось получить данные о встроенных покупках." - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "ไม่สามารถดึงข้อมูลการซื้อภายในแอปได้ในขณะนี้" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Наразі неможливо отримати інформацію про покупки в додатку." - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "暂时无法获取应用内购买信息。" - } - } - } - }, - "plusview_purchasefailed" : { - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "فشل الشراء" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Kauf fehlgeschlagen" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Purchase failed" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "La compra ha fallado" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Échec de l'achat" - } - }, - "hu" : { - "stringUnit" : { - "state" : "translated", - "value" : "A vásárlás sikertelen" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Acquisto non riuscito" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "購入に失敗しました" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "구매 실패" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Aankoop mislukt" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Falha na compra" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Falha na compra" - } - }, - "ro" : { - "stringUnit" : { - "state" : "translated", - "value" : "Achiziția a eșuat" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ошибка покупки" - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "การซื้อล้มเหลว" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Помилка покупки" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "购买失败" - } - } - } - }, - "plusview_purchasefaileddescription" : { - "extractionState" : "manual", - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "إذا ظهر هذا الرسالة بشكل غير متوقع، يرجى التأكد من اتصالك بالإنترنت وأن عمليات الشراء داخل التطبيق مفعّلة على جهازك، ثم حاول مرة أخرى." - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Falls diese Nachricht unerwartet erscheint, stelle sicher, dass du mit dem Internet verbunden bist und In-App-Käufe auf deinem Gerät aktiviert sind, und versuche es erneut." - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "If this message appeared unexpectedly, please make sure that you are connected to the Internet and In-App-Purchases are enabled for your device and then try again." - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Si este mensaje apareció inesperadamente, asegúrate de estar conectado a internet y que las compras dentro de la aplicación estén habilitadas en tu dispositivo, luego intenta nuevamente." - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Si ce message est apparu de manière inattendue, assurez-vous d'être connecté à Internet et que les achats intégrés sont activés sur votre appareil, puis réessayez." - } - }, - "hu" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ha ez az üzenet váratlanul jelent meg, ellenőrizd az internetkapcsolatot, és győződj meg róla, hogy az alkalmazáson belüli vásárlások engedélyezve vannak az eszközödön, majd próbáld újra." - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Se questo messaggio è apparso inaspettatamente, assicurati di essere connesso a Internet e che gli acquisti in-app siano abilitati sul tuo dispositivo, quindi riprova." - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "このメッセージが予期せず表示された場合、インターネット接続を確認し、アプリ内購入が有効になっていることを確認した上で再試行してください。" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "이 메시지가 예상치 않게 나타난 경우, 인터넷에 연결되어 있는지 확인하고 기기에서 인앱 결제가 활성화되어 있는지 확인한 후 다시 시도해 주세요." - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Als dit bericht onverwachts verschijnt, controleer dan of je verbonden bent met internet en of in-app aankopen zijn ingeschakeld op je apparaat, en probeer het opnieuw." - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Se esta mensagem apareceu inesperadamente, verifique se você está conectado à internet e se as compras dentro do app estão ativadas no seu dispositivo e tente novamente." - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Se esta mensagem apareceu inesperadamente, certifique-se de estar conectado à Internet e que as compras dentro do app estejam ativadas no seu dispositivo, depois tente novamente." - } - }, - "ro" : { - "stringUnit" : { - "state" : "translated", - "value" : "Dacă acest mesaj a apărut neașteptat, asigură-te că ești conectat la internet și că achizițiile în aplicație sunt activate pe dispozitivul tău, apoi încearcă din nou." - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Если это сообщение появилось неожиданно, убедитесь, что у вас есть доступ к интернету и включены встроенные покупки на вашем устройстве, затем попробуйте снова." - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "หากข้อความนี้ปรากฏขึ้นโดยไม่คาดคิด โปรดตรวจสอบว่าคุณเชื่อมต่ออินเทอร์เน็ตและเปิดใช้งานการซื้อภายในแอปในอุปกรณ์ของคุณแล้วลองอีกครั้ง" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Якщо це повідомлення з’явилося несподівано, переконайтеся, що у вас є підключення до Інтернету, а покупки в додатках увімкнені на вашому пристрої, і спробуйте ще раз." - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "如果此消息意外出现,请确保您已连接到互联网,并且设备已启用应用内购买功能,然后重试。" - } - } - } - }, - "plusview_restorefailed" : { - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "حدثت مشكلة أثناء استعادة عملية الشراء" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Es gab ein Problem beim Wiederherstellen deines Kaufs" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "There was a problem restoring your purchase" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Hubo un problema al restaurar tu compra" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Un problème est survenu lors de la restauration de votre achat" - } - }, - "hu" : { - "stringUnit" : { - "state" : "translated", - "value" : "Hiba történt a vásárlás visszaállítása során" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Si è verificato un problema durante il ripristino dell'acquisto" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "購入の復元中に問題が発生しました" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "구매 복원 중 문제가 발생했습니다." - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Er was een probleem bij het herstellen van je aankoop" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Houve um problema ao restaurar sua compra" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Houve um problema ao restaurar sua compra" - } - }, - "ro" : { - "stringUnit" : { - "state" : "translated", - "value" : "A apărut o problemă la restaurarea achiziției" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Произошла ошибка при восстановлении покупки" - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "เกิดปัญหาในการกู้คืนการซื้อของคุณ" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Сталася помилка під час відновлення покупки" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "恢复购买时出现问题" - } - } - } - }, - "plusview_restorefailed_proceed" : { - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "موافق" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Okay" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Okay" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Aceptar" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "OK" - } - }, - "hu" : { - "stringUnit" : { - "state" : "translated", - "value" : "Rendben" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ok" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "OK" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "확인" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Oké" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "OK" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ok" - } - }, - "ro" : { - "stringUnit" : { - "state" : "translated", - "value" : "OK" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Окей" - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "ตกลง" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Гаразд" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "确定" - } - } - } - }, - "plusview_restorepurchase" : { - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "هل اشتريته بالفعل؟" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bereits gekauft?" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Already bought?" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "¿Ya lo compraste?" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Déjà acheté ?" - } - }, - "hu" : { - "stringUnit" : { - "state" : "translated", - "value" : "Már megvásároltad?" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Già acquistato?" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "すでに購入済みですか?" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "이미 구매하셨나요?" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Al gekocht?" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Já comprou?" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Já comprou?" - } - }, - "ro" : { - "stringUnit" : { - "state" : "translated", - "value" : "Deja cumpărat?" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Уже куплено?" - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "เคยซื้อแล้ว?" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Вже куплено?" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "已购买?" - } - } - } - }, - "plusview_success_button" : { - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "حسنًا!" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Alles klar!" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Got it!" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "¡Entendido!" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "D'accord !" - } - }, - "hu" : { - "stringUnit" : { - "state" : "translated", - "value" : "Értettem!" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Capito!" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "了解!" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "알겠습니다!" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Begrepen!" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Entendi!" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Entendido!" - } - }, - "ro" : { - "stringUnit" : { - "state" : "translated", - "value" : "Înțeles!" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Понял!" - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "รับทราบ!" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Зрозуміло!" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "知道了!" - } - } - } - }, - "plusview_success_description" : { - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "يمكنك الآن متابعة استلام الملفات بدون قيود. أي ملفات حاولت إرسالها سابقًا تم حفظها بالفعل على جهازك." - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Du kannst nun weiterhin Dateien ohne Einschränkungen empfangen. Alle Dateien, welche gerade gesendet wurden, wurden bereits gespeichert." - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "You can now continue receiving files without restrictions. Any files you tried to send were already saved on your device." - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ahora puedes seguir recibiendo archivos sin restricciones. Todos los archivos que intentaste enviar antes ya se han guardado en tu dispositivo." - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Vous pouvez maintenant continuer à recevoir des fichiers sans restrictions. Tous les fichiers que vous avez essayé d'envoyer précédemment ont déjà été enregistrés sur votre appareil." - } - }, - "hu" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mostantól korlátozások nélkül fogadhat fájlokat. Az összes fájl, amelyet korábban próbált elküldeni, már el lett mentve az eszközére." - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ora puoi continuare a ricevere file senza restrizioni. Tutti i file che hai provato a inviare in precedenza sono già stati salvati sul tuo dispositivo." - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "これで、制限なくファイルを受信し続けることができます。以前送信しようとしたファイルはすでにデバイスに保存されています。" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "이제 파일을 제한 없이 계속 받을 수 있습니다. 이전에 전송을 시도한 모든 파일이 이미 기기에 저장되었습니다." - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Je kunt nu bestanden blijven ontvangen zonder beperkingen. Alle bestanden die je eerder probeerde te verzenden, zijn al opgeslagen op je apparaat." - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Agora você pode continuar recebendo arquivos sem restrições. Todos os arquivos que você tentou enviar antes já foram salvos no seu dispositivo." - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Agora pode continuar a receber ficheiros sem restrições. Todos os ficheiros que tentou enviar antes já foram guardados no seu dispositivo." - } - }, - "ro" : { - "stringUnit" : { - "state" : "translated", - "value" : "Acum puteți continua să primiți fișiere fără restricții. Toate fișierele pe care ați încercat să le trimiteți anterior au fost deja salvate pe dispozitivul dvs." - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Теперь вы можете продолжать получать файлы без ограничений. Все файлы, которые вы пытались отправить ранее, уже сохранены на вашем устройстве." - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "คุณสามารถรับไฟล์ต่อไปได้โดยไม่มีข้อจำกัด ไฟล์ทั้งหมดที่คุณพยายามส่งก่อนหน้านี้ถูกบันทึกไว้ในอุปกรณ์ของคุณแล้ว" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Тепер ви можете продовжувати отримувати файли без обмежень. Усі файли, які ви намагалися надіслати раніше, вже збережено на вашому пристрої." - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "您现在可以继续无限制地接收文件。您之前尝试发送的所有文件已保存到您的设备上。" - } - } - } - }, - "plusview_success_title" : { - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "شكرًا لشرائك!" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Danke für deinen Kauf!" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Thanks for your purchase!" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "¡Gracias por tu compra!" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Merci pour votre achat !" - } - }, - "hu" : { - "stringUnit" : { - "state" : "translated", - "value" : "Köszönjük a vásárlást!" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Grazie per il tuo acquisto!" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "ご購入ありがとうございます!" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "구매해 주셔서 감사합니다!" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bedankt voor je aankoop!" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Obrigado pela sua compra!" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Obrigado pela sua compra!" - } - }, - "ro" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mulțumim pentru achiziție!" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Спасибо за покупку!" - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "ขอบคุณสำหรับการซื้อ!" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Дякуємо за покупку!" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "感谢您的购买!" - } - } - } - }, - "plusview_title" : { - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "شكرًا لاستخدامك QuickDrop!" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Danke, dass du QuickDrop benutzt!" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Thanks for using QuickDrop!" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "¡Gracias por usar QuickDrop!" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Merci d'utiliser QuickDrop !" - } - }, - "hu" : { - "stringUnit" : { - "state" : "translated", - "value" : "Köszönjük, hogy használod a QuickDropot!" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Grazie per aver usato QuickDrop!" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "QuickDropをご利用いただきありがとうございます!" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "QuickDrop을 사용해 주셔서 감사합니다!" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bedankt voor het gebruiken van QuickDrop!" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Obrigado por usar o QuickDrop!" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Obrigado por usar o QuickDrop!" - } - }, - "ro" : { - "stringUnit" : { - "state" : "translated", - "value" : "Mulțumim că folosești QuickDrop!" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Спасибо за использование QuickDrop!" - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "ขอบคุณที่ใช้ QuickDrop!" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Дякуємо за використання QuickDrop!" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "感谢使用 QuickDrop!" - } - } - } - }, - "plusview_unlock_label" : { - "extractionState" : "manual", - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "ادعم QuickDrop" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Unterstütze QuickDrop" - } - }, - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Support QuickDrop" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Apoyar QuickDrop" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Soutenir QuickDrop" - } - }, - "hu" : { - "stringUnit" : { - "state" : "translated", - "value" : "Támogasd a QuickDropot" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Supporta QuickDrop" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "QuickDropをサポート" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "QuickDrop 지원하기" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ondersteun QuickDrop" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Apoie o QuickDrop" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Apoiar QuickDrop" - } - }, - "ro" : { - "stringUnit" : { - "state" : "translated", - "value" : "Susține QuickDrop" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Поддержать QuickDrop" - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "สนับสนุน QuickDrop" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Підтримати QuickDrop" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "支持 QuickDrop" - } - } - } } }, "version" : "1.0" From a082d524facfc60e70e6b0337ab35f596df3a11c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20B=C3=B6ttger?= Date: Fri, 8 Aug 2025 08:25:07 +0200 Subject: [PATCH 12/20] Add multicast networking capability --- QuickDrop.xcodeproj/project.pbxproj | 10 ++++++---- QuickDrop/Localizable.xcstrings | 1 + QuickDropiOS/QuickDropiOS.entitlements | 8 ++++++++ 3 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 QuickDropiOS/QuickDropiOS.entitlements diff --git a/QuickDrop.xcodeproj/project.pbxproj b/QuickDrop.xcodeproj/project.pbxproj index a506f7c..fda2b9b 100644 --- a/QuickDrop.xcodeproj/project.pbxproj +++ b/QuickDrop.xcodeproj/project.pbxproj @@ -908,8 +908,8 @@ MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; - SHARED_BUILD = 59; - SHARED_VERSION = 1.12; + SHARED_BUILD = 60; + SHARED_VERSION = 1.12.1; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -970,8 +970,8 @@ MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = macosx; - SHARED_BUILD = 59; - SHARED_VERSION = 1.12; + SHARED_BUILD = 60; + SHARED_VERSION = 1.12.1; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OPTIMIZATION_LEVEL = "-O"; @@ -1062,6 +1062,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = QuickDropiOS/QuickDropiOS.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; ENABLE_PREVIEWS = YES; @@ -1097,6 +1098,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = QuickDropiOS/QuickDropiOS.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; ENABLE_PREVIEWS = YES; diff --git a/QuickDrop/Localizable.xcstrings b/QuickDrop/Localizable.xcstrings index f957cc9..ed5210e 100644 --- a/QuickDrop/Localizable.xcstrings +++ b/QuickDrop/Localizable.xcstrings @@ -7263,6 +7263,7 @@ } }, "Reset UD" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { diff --git a/QuickDropiOS/QuickDropiOS.entitlements b/QuickDropiOS/QuickDropiOS.entitlements new file mode 100644 index 0000000..ec706aa --- /dev/null +++ b/QuickDropiOS/QuickDropiOS.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.developer.networking.multicast + + + From a0c6770c51c942c42ae96374572b87d2b18a4b68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20B=C3=B6ttger?= Date: Fri, 8 Aug 2025 08:52:06 +0200 Subject: [PATCH 13/20] Update Protobuf --- .../Connection/OutboundNearbyConnection.swift | 2 +- .../Protobuf/offline_wire_formats.pb.swift | 2133 +++++++++- .../Model/Protobuf/sharing_enums.pb.swift | 3468 +++++++++++++++++ .../Model/Protobuf/wire_format.pb.swift | 1235 +++++- .../ProtobufSource/offline_wire_formats.proto | 207 +- .../Model/ProtobufSource/sharing_enums.proto | 962 +++++ .../Model/ProtobufSource/wire_format.proto | 212 +- QuickDrop.xcodeproj/project.pbxproj | 28 +- QuickDrop/Localizable.xcstrings | 213 +- Resources/License.txt | 181 + 10 files changed, 8173 insertions(+), 468 deletions(-) create mode 100644 NearbyShare/Model/Protobuf/sharing_enums.pb.swift create mode 100644 NearbyShare/Model/ProtobufSource/sharing_enums.proto diff --git a/NearbyShare/Connection/OutboundNearbyConnection.swift b/NearbyShare/Connection/OutboundNearbyConnection.swift index 5f6cf81..d6c1dd8 100644 --- a/NearbyShare/Connection/OutboundNearbyConnection.swift +++ b/NearbyShare/Connection/OutboundNearbyConnection.swift @@ -340,7 +340,7 @@ class OutboundNearbyConnection: NearbyConnection { } else if meta.mimeType.starts(with: "audio/") { meta.type = .audio } else if url.pathExtension.lowercased() == "apk" { - meta.type = .app + meta.type = .androidApp } else { meta.type = .unknown } diff --git a/NearbyShare/Model/Protobuf/offline_wire_formats.pb.swift b/NearbyShare/Model/Protobuf/offline_wire_formats.pb.swift index 7524b1c..2d5c0a0 100644 --- a/NearbyShare/Model/Protobuf/offline_wire_formats.pb.swift +++ b/NearbyShare/Model/Protobuf/offline_wire_formats.pb.swift @@ -34,6 +34,43 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP typealias Version = _2 } +enum Location_Nearby_Connections_EndpointType: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknownEndpoint // = 0 + case connectionsEndpoint // = 1 + case presenceEndpoint // = 2 + + init() { + self = .unknownEndpoint + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknownEndpoint + case 1: self = .connectionsEndpoint + case 2: self = .presenceEndpoint + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknownEndpoint: return 0 + case .connectionsEndpoint: return 1 + case .presenceEndpoint: return 2 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Connections_EndpointType: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + struct Location_Nearby_Connections_OfflineFrame { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -179,6 +216,51 @@ struct Location_Nearby_Connections_V1Frame { /// Clears the value of `pairedKeyEncryption`. Subsequent reads from it will return its default value. mutating func clearPairedKeyEncryption() {_uniqueStorage()._pairedKeyEncryption = nil} + var authenticationMessage: Location_Nearby_Connections_AuthenticationMessageFrame { + get {return _storage._authenticationMessage ?? Location_Nearby_Connections_AuthenticationMessageFrame()} + set {_uniqueStorage()._authenticationMessage = newValue} + } + /// Returns true if `authenticationMessage` has been explicitly set. + var hasAuthenticationMessage: Bool {return _storage._authenticationMessage != nil} + /// Clears the value of `authenticationMessage`. Subsequent reads from it will return its default value. + mutating func clearAuthenticationMessage() {_uniqueStorage()._authenticationMessage = nil} + + var authenticationResult: Location_Nearby_Connections_AuthenticationResultFrame { + get {return _storage._authenticationResult ?? Location_Nearby_Connections_AuthenticationResultFrame()} + set {_uniqueStorage()._authenticationResult = newValue} + } + /// Returns true if `authenticationResult` has been explicitly set. + var hasAuthenticationResult: Bool {return _storage._authenticationResult != nil} + /// Clears the value of `authenticationResult`. Subsequent reads from it will return its default value. + mutating func clearAuthenticationResult() {_uniqueStorage()._authenticationResult = nil} + + var autoResume: Location_Nearby_Connections_AutoResumeFrame { + get {return _storage._autoResume ?? Location_Nearby_Connections_AutoResumeFrame()} + set {_uniqueStorage()._autoResume = newValue} + } + /// Returns true if `autoResume` has been explicitly set. + var hasAutoResume: Bool {return _storage._autoResume != nil} + /// Clears the value of `autoResume`. Subsequent reads from it will return its default value. + mutating func clearAutoResume() {_uniqueStorage()._autoResume = nil} + + var autoReconnect: Location_Nearby_Connections_AutoReconnectFrame { + get {return _storage._autoReconnect ?? Location_Nearby_Connections_AutoReconnectFrame()} + set {_uniqueStorage()._autoReconnect = newValue} + } + /// Returns true if `autoReconnect` has been explicitly set. + var hasAutoReconnect: Bool {return _storage._autoReconnect != nil} + /// Clears the value of `autoReconnect`. Subsequent reads from it will return its default value. + mutating func clearAutoReconnect() {_uniqueStorage()._autoReconnect = nil} + + var bandwidthUpgradeRetry: Location_Nearby_Connections_BandwidthUpgradeRetryFrame { + get {return _storage._bandwidthUpgradeRetry ?? Location_Nearby_Connections_BandwidthUpgradeRetryFrame()} + set {_uniqueStorage()._bandwidthUpgradeRetry = newValue} + } + /// Returns true if `bandwidthUpgradeRetry` has been explicitly set. + var hasBandwidthUpgradeRetry: Bool {return _storage._bandwidthUpgradeRetry != nil} + /// Clears the value of `bandwidthUpgradeRetry`. Subsequent reads from it will return its default value. + mutating func clearBandwidthUpgradeRetry() {_uniqueStorage()._bandwidthUpgradeRetry = nil} + var unknownFields = SwiftProtobuf.UnknownStorage() enum FrameType: SwiftProtobuf.Enum { @@ -191,6 +273,11 @@ struct Location_Nearby_Connections_V1Frame { case keepAlive // = 5 case disconnection // = 6 case pairedKeyEncryption // = 7 + case authenticationMessage // = 8 + case authenticationResult // = 9 + case autoResume // = 10 + case autoReconnect // = 11 + case bandwidthUpgradeRetry // = 12 init() { self = .unknownFrameType @@ -206,6 +293,11 @@ struct Location_Nearby_Connections_V1Frame { case 5: self = .keepAlive case 6: self = .disconnection case 7: self = .pairedKeyEncryption + case 8: self = .authenticationMessage + case 9: self = .authenticationResult + case 10: self = .autoResume + case 11: self = .autoReconnect + case 12: self = .bandwidthUpgradeRetry default: return nil } } @@ -220,6 +312,11 @@ struct Location_Nearby_Connections_V1Frame { case .keepAlive: return 5 case .disconnection: return 6 case .pairedKeyEncryption: return 7 + case .authenticationMessage: return 8 + case .authenticationResult: return 9 + case .autoResume: return 10 + case .autoReconnect: return 11 + case .bandwidthUpgradeRetry: return 12 } } @@ -346,8 +443,73 @@ struct Location_Nearby_Connections_ConnectionRequestFrame { /// Clears the value of `deviceInfo`. Subsequent reads from it will return its default value. mutating func clearDeviceInfo() {_uniqueStorage()._deviceInfo = nil} + /// Represents the {@link Device} that invokes the request. + var device: OneOf_Device? { + get {return _storage._device} + set {_uniqueStorage()._device = newValue} + } + + var connectionsDevice: Location_Nearby_Connections_ConnectionsDevice { + get { + if case .connectionsDevice(let v)? = _storage._device {return v} + return Location_Nearby_Connections_ConnectionsDevice() + } + set {_uniqueStorage()._device = .connectionsDevice(newValue)} + } + + var presenceDevice: Location_Nearby_Connections_PresenceDevice { + get { + if case .presenceDevice(let v)? = _storage._device {return v} + return Location_Nearby_Connections_PresenceDevice() + } + set {_uniqueStorage()._device = .presenceDevice(newValue)} + } + + var connectionMode: Location_Nearby_Connections_ConnectionRequestFrame.ConnectionMode { + get {return _storage._connectionMode ?? .legacy} + set {_uniqueStorage()._connectionMode = newValue} + } + /// Returns true if `connectionMode` has been explicitly set. + var hasConnectionMode: Bool {return _storage._connectionMode != nil} + /// Clears the value of `connectionMode`. Subsequent reads from it will return its default value. + mutating func clearConnectionMode() {_uniqueStorage()._connectionMode = nil} + + var locationHint: Location_Nearby_Connections_LocationHint { + get {return _storage._locationHint ?? Location_Nearby_Connections_LocationHint()} + set {_uniqueStorage()._locationHint = newValue} + } + /// Returns true if `locationHint` has been explicitly set. + var hasLocationHint: Bool {return _storage._locationHint != nil} + /// Clears the value of `locationHint`. Subsequent reads from it will return its default value. + mutating func clearLocationHint() {_uniqueStorage()._locationHint = nil} + var unknownFields = SwiftProtobuf.UnknownStorage() + /// Represents the {@link Device} that invokes the request. + enum OneOf_Device: Equatable { + case connectionsDevice(Location_Nearby_Connections_ConnectionsDevice) + case presenceDevice(Location_Nearby_Connections_PresenceDevice) + + #if !swift(>=4.1) + static func ==(lhs: Location_Nearby_Connections_ConnectionRequestFrame.OneOf_Device, rhs: Location_Nearby_Connections_ConnectionRequestFrame.OneOf_Device) -> Bool { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch (lhs, rhs) { + case (.connectionsDevice, .connectionsDevice): return { + guard case .connectionsDevice(let l) = lhs, case .connectionsDevice(let r) = rhs else { preconditionFailure() } + return l == r + }() + case (.presenceDevice, .presenceDevice): return { + guard case .presenceDevice(let l) = lhs, case .presenceDevice(let r) = rhs else { preconditionFailure() } + return l == r + }() + default: return false + } + } + #endif + } + /// Should always match cs/symbol:location.nearby.proto.connections.Medium /// LINT.IfChange enum Medium: SwiftProtobuf.Enum { @@ -364,6 +526,8 @@ struct Location_Nearby_Connections_ConnectionRequestFrame { case webRtc // = 9 case bleL2Cap // = 10 case usb // = 11 + case webRtcNonCellular // = 12 + case awdl // = 13 init() { self = .unknownMedium @@ -383,6 +547,8 @@ struct Location_Nearby_Connections_ConnectionRequestFrame { case 9: self = .webRtc case 10: self = .bleL2Cap case 11: self = .usb + case 12: self = .webRtcNonCellular + case 13: self = .awdl default: return nil } } @@ -401,6 +567,35 @@ struct Location_Nearby_Connections_ConnectionRequestFrame { case .webRtc: return 9 case .bleL2Cap: return 10 case .usb: return 11 + case .webRtcNonCellular: return 12 + case .awdl: return 13 + } + } + + } + + /// LINT.IfChange + enum ConnectionMode: SwiftProtobuf.Enum { + typealias RawValue = Int + case legacy // = 0 + case instant // = 1 + + init() { + self = .legacy + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .legacy + case 1: self = .instant + default: return nil + } + } + + var rawValue: Int { + switch self { + case .legacy: return 0 + case .instant: return 1 } } @@ -417,6 +612,10 @@ extension Location_Nearby_Connections_ConnectionRequestFrame.Medium: CaseIterabl // Support synthesized by the compiler. } +extension Location_Nearby_Connections_ConnectionRequestFrame.ConnectionMode: CaseIterable { + // Support synthesized by the compiler. +} + #endif // swift(>=4.2) /// This doesn't need to send back endpoint_id and endpoint_name (like @@ -490,6 +689,33 @@ struct Location_Nearby_Connections_ConnectionResponseFrame { /// Clears the value of `nearbyConnectionsVersion`. Subsequent reads from it will return its default value. mutating func clearNearbyConnectionsVersion() {self._nearbyConnectionsVersion = nil} + var safeToDisconnectVersion: Int32 { + get {return _safeToDisconnectVersion ?? 0} + set {_safeToDisconnectVersion = newValue} + } + /// Returns true if `safeToDisconnectVersion` has been explicitly set. + var hasSafeToDisconnectVersion: Bool {return self._safeToDisconnectVersion != nil} + /// Clears the value of `safeToDisconnectVersion`. Subsequent reads from it will return its default value. + mutating func clearSafeToDisconnectVersion() {self._safeToDisconnectVersion = nil} + + var locationHint: Location_Nearby_Connections_LocationHint { + get {return _locationHint ?? Location_Nearby_Connections_LocationHint()} + set {_locationHint = newValue} + } + /// Returns true if `locationHint` has been explicitly set. + var hasLocationHint: Bool {return self._locationHint != nil} + /// Clears the value of `locationHint`. Subsequent reads from it will return its default value. + mutating func clearLocationHint() {self._locationHint = nil} + + var keepAliveTimeoutMillis: Int32 { + get {return _keepAliveTimeoutMillis ?? 0} + set {_keepAliveTimeoutMillis = newValue} + } + /// Returns true if `keepAliveTimeoutMillis` has been explicitly set. + var hasKeepAliveTimeoutMillis: Bool {return self._keepAliveTimeoutMillis != nil} + /// Clears the value of `keepAliveTimeoutMillis`. Subsequent reads from it will return its default value. + mutating func clearKeepAliveTimeoutMillis() {self._keepAliveTimeoutMillis = nil} + var unknownFields = SwiftProtobuf.UnknownStorage() /// Used to replace the status integer parameter with a meaningful enum item. @@ -533,6 +759,9 @@ struct Location_Nearby_Connections_ConnectionResponseFrame { fileprivate var _osInfo: Location_Nearby_Connections_OsInfo? = nil fileprivate var _multiplexSocketBitmask: Int32? = nil fileprivate var _nearbyConnectionsVersion: Int32? = nil + fileprivate var _safeToDisconnectVersion: Int32? = nil + fileprivate var _locationHint: Location_Nearby_Connections_LocationHint? = nil + fileprivate var _keepAliveTimeoutMillis: Int32? = nil } #if swift(>=4.2) @@ -592,6 +821,7 @@ struct Location_Nearby_Connections_PayloadTransferFrame { case unknownPacketType // = 0 case data // = 1 case control // = 2 + case payloadAck // = 3 init() { self = .unknownPacketType @@ -602,6 +832,7 @@ struct Location_Nearby_Connections_PayloadTransferFrame { case 0: self = .unknownPacketType case 1: self = .data case 2: self = .control + case 3: self = .payloadAck default: return nil } } @@ -611,6 +842,7 @@ struct Location_Nearby_Connections_PayloadTransferFrame { case .unknownPacketType: return 0 case .data: return 1 case .control: return 2 + case .payloadAck: return 3 } } @@ -675,6 +907,16 @@ struct Location_Nearby_Connections_PayloadTransferFrame { /// Clears the value of `parentFolder`. Subsequent reads from it will return its default value. mutating func clearParentFolder() {self._parentFolder = nil} + /// Time since the epoch in milliseconds. + var lastModifiedTimestampMillis: Int64 { + get {return _lastModifiedTimestampMillis ?? 0} + set {_lastModifiedTimestampMillis = newValue} + } + /// Returns true if `lastModifiedTimestampMillis` has been explicitly set. + var hasLastModifiedTimestampMillis: Bool {return self._lastModifiedTimestampMillis != nil} + /// Clears the value of `lastModifiedTimestampMillis`. Subsequent reads from it will return its default value. + mutating func clearLastModifiedTimestampMillis() {self._lastModifiedTimestampMillis = nil} + var unknownFields = SwiftProtobuf.UnknownStorage() enum PayloadType: SwiftProtobuf.Enum { @@ -717,6 +959,7 @@ struct Location_Nearby_Connections_PayloadTransferFrame { fileprivate var _isSensitive: Bool? = nil fileprivate var _fileName: String? = nil fileprivate var _parentFolder: String? = nil + fileprivate var _lastModifiedTimestampMillis: Int64? = nil } /// Accompanies DATA packets. @@ -752,6 +995,15 @@ struct Location_Nearby_Connections_PayloadTransferFrame { /// Clears the value of `body`. Subsequent reads from it will return its default value. mutating func clearBody() {self._body = nil} + var index: Int32 { + get {return _index ?? 0} + set {_index = newValue} + } + /// Returns true if `index` has been explicitly set. + var hasIndex: Bool {return self._index != nil} + /// Clears the value of `index`. Subsequent reads from it will return its default value. + mutating func clearIndex() {self._index = nil} + var unknownFields = SwiftProtobuf.UnknownStorage() enum Flags: SwiftProtobuf.Enum { @@ -782,6 +1034,7 @@ struct Location_Nearby_Connections_PayloadTransferFrame { fileprivate var _flags: Int32? = nil fileprivate var _offset: Int64? = nil fileprivate var _body: Data? = nil + fileprivate var _index: Int32? = nil } /// Accompanies CONTROL packets. @@ -815,6 +1068,8 @@ struct Location_Nearby_Connections_PayloadTransferFrame { case unknownEventType // = 0 case payloadError // = 1 case payloadCanceled // = 2 + + /// Use PacketType.PAYLOAD_ACK instead case payloadReceivedAck // = 3 init() { @@ -918,6 +1173,15 @@ struct Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame { /// Clears the value of `clientIntroductionAck`. Subsequent reads from it will return its default value. mutating func clearClientIntroductionAck() {self._clientIntroductionAck = nil} + var safeToClosePriorChannel: Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.SafeToClosePriorChannel { + get {return _safeToClosePriorChannel ?? Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.SafeToClosePriorChannel()} + set {_safeToClosePriorChannel = newValue} + } + /// Returns true if `safeToClosePriorChannel` has been explicitly set. + var hasSafeToClosePriorChannel: Bool {return self._safeToClosePriorChannel != nil} + /// Clears the value of `safeToClosePriorChannel`. Subsequent reads from it will return its default value. + mutating func clearSafeToClosePriorChannel() {self._safeToClosePriorChannel = nil} + var unknownFields = SwiftProtobuf.UnknownStorage() enum EventType: SwiftProtobuf.Enum { @@ -930,6 +1194,10 @@ struct Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame { case upgradeFailure // = 5 case clientIntroductionAck // = 6 + /// The event type that requires the remote device to send the available + /// upgrade path. + case upgradePathRequest // = 7 + init() { self = .unknownEventType } @@ -943,6 +1211,7 @@ struct Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame { case 4: self = .clientIntroduction case 5: self = .upgradeFailure case 6: self = .clientIntroductionAck + case 7: self = .upgradePathRequest default: return nil } } @@ -956,6 +1225,7 @@ struct Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame { case .clientIntroduction: return 4 case .upgradeFailure: return 5 case .clientIntroductionAck: return 6 + case .upgradePathRequest: return 7 } } @@ -1031,6 +1301,15 @@ struct Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame { /// Clears the value of `webRtcCredentials`. Subsequent reads from it will return its default value. mutating func clearWebRtcCredentials() {_uniqueStorage()._webRtcCredentials = nil} + var awdlCredentials: Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.UpgradePathInfo.AwdlCredentials { + get {return _storage._awdlCredentials ?? Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.UpgradePathInfo.AwdlCredentials()} + set {_uniqueStorage()._awdlCredentials = newValue} + } + /// Returns true if `awdlCredentials` has been explicitly set. + var hasAwdlCredentials: Bool {return _storage._awdlCredentials != nil} + /// Clears the value of `awdlCredentials`. Subsequent reads from it will return its default value. + mutating func clearAwdlCredentials() {_uniqueStorage()._awdlCredentials = nil} + /// Disable Encryption for this upgrade medium to improve throughput. var supportsDisablingEncryption: Bool { get {return _storage._supportsDisablingEncryption ?? false} @@ -1051,6 +1330,15 @@ struct Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame { /// Clears the value of `supportsClientIntroductionAck`. Subsequent reads from it will return its default value. mutating func clearSupportsClientIntroductionAck() {_uniqueStorage()._supportsClientIntroductionAck = nil} + var upgradePathRequest: Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.UpgradePathInfo.UpgradePathRequest { + get {return _storage._upgradePathRequest ?? Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.UpgradePathInfo.UpgradePathRequest()} + set {_uniqueStorage()._upgradePathRequest = newValue} + } + /// Returns true if `upgradePathRequest` has been explicitly set. + var hasUpgradePathRequest: Bool {return _storage._upgradePathRequest != nil} + /// Clears the value of `upgradePathRequest`. Subsequent reads from it will return its default value. + mutating func clearUpgradePathRequest() {_uniqueStorage()._upgradePathRequest = nil} + var unknownFields = SwiftProtobuf.UnknownStorage() /// Should always match cs/symbol:location.nearby.proto.connections.Medium @@ -1069,6 +1357,8 @@ struct Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame { /// 10 is reserved. case usb // = 11 + case webRtcNonCellular // = 12 + case awdl // = 13 init() { self = .unknownMedium @@ -1087,6 +1377,8 @@ struct Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame { case 8: self = .wifiDirect case 9: self = .webRtc case 11: self = .usb + case 12: self = .webRtcNonCellular + case 13: self = .awdl default: return nil } } @@ -1104,6 +1396,8 @@ struct Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame { case .wifiDirect: return 8 case .webRtc: return 9 case .usb: return 11 + case .webRtcNonCellular: return 12 + case .awdl: return 13 } } @@ -1329,6 +1623,18 @@ struct Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame { /// Clears the value of `gateway`. Subsequent reads from it will return its default value. mutating func clearGateway() {self._gateway = nil} + /// IPv6 link-local address, network order (128bits). + /// The GO should listen on both IPv4 and IPv6 addresses. + /// https://en.wikipedia.org/wiki/Link-local_address#IPv6 + var ipV6Address: Data { + get {return _ipV6Address ?? Data()} + set {_ipV6Address = newValue} + } + /// Returns true if `ipV6Address` has been explicitly set. + var hasIpV6Address: Bool {return self._ipV6Address != nil} + /// Clears the value of `ipV6Address`. Subsequent reads from it will return its default value. + mutating func clearIpV6Address() {self._ipV6Address = nil} + var unknownFields = SwiftProtobuf.UnknownStorage() init() {} @@ -1338,6 +1644,7 @@ struct Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame { fileprivate var _port: Int32? = nil fileprivate var _frequency: Int32? = nil fileprivate var _gateway: String? = nil + fileprivate var _ipV6Address: Data? = nil } /// Accompanies Medium.WEB_RTC @@ -1372,11 +1679,99 @@ struct Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame { fileprivate var _locationHint: Location_Nearby_Connections_LocationHint? = nil } + /// Accompanies Medium.AWDL. + struct AwdlCredentials { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var serviceName: String { + get {return _serviceName ?? String()} + set {_serviceName = newValue} + } + /// Returns true if `serviceName` has been explicitly set. + var hasServiceName: Bool {return self._serviceName != nil} + /// Clears the value of `serviceName`. Subsequent reads from it will return its default value. + mutating func clearServiceName() {self._serviceName = nil} + + var serviceType: String { + get {return _serviceType ?? String()} + set {_serviceType = newValue} + } + /// Returns true if `serviceType` has been explicitly set. + var hasServiceType: Bool {return self._serviceType != nil} + /// Clears the value of `serviceType`. Subsequent reads from it will return its default value. + mutating func clearServiceType() {self._serviceType = nil} + + var password: String { + get {return _password ?? String()} + set {_password = newValue} + } + /// Returns true if `password` has been explicitly set. + var hasPassword: Bool {return self._password != nil} + /// Clears the value of `password`. Subsequent reads from it will return its default value. + mutating func clearPassword() {self._password = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _serviceName: String? = nil + fileprivate var _serviceType: String? = nil + fileprivate var _password: String? = nil + } + + struct UpgradePathRequest { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// Supported mediums on the advertiser device. + var mediums: [Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.UpgradePathInfo.Medium] = [] + + var mediumMetaData: Location_Nearby_Connections_MediumMetadata { + get {return _mediumMetaData ?? Location_Nearby_Connections_MediumMetadata()} + set {_mediumMetaData = newValue} + } + /// Returns true if `mediumMetaData` has been explicitly set. + var hasMediumMetaData: Bool {return self._mediumMetaData != nil} + /// Clears the value of `mediumMetaData`. Subsequent reads from it will return its default value. + mutating func clearMediumMetaData() {self._mediumMetaData = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _mediumMetaData: Location_Nearby_Connections_MediumMetadata? = nil + } + init() {} fileprivate var _storage = _StorageClass.defaultInstance } + /// Accompanies SAFE_TO_CLOSE_PRIOR_CHANNEL events. + struct SafeToClosePriorChannel { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var staFrequency: Int32 { + get {return _staFrequency ?? 0} + set {_staFrequency = newValue} + } + /// Returns true if `staFrequency` has been explicitly set. + var hasStaFrequency: Bool {return self._staFrequency != nil} + /// Clears the value of `staFrequency`. Subsequent reads from it will return its default value. + mutating func clearStaFrequency() {self._staFrequency = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _staFrequency: Int32? = nil + } + /// Accompanies CLIENT_INTRODUCTION events. struct ClientIntroduction { // SwiftProtobuf.Message conformance is added in an extension below. See the @@ -1401,12 +1796,22 @@ struct Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame { /// Clears the value of `supportsDisablingEncryption`. Subsequent reads from it will return its default value. mutating func clearSupportsDisablingEncryption() {self._supportsDisablingEncryption = nil} + var lastEndpointID: String { + get {return _lastEndpointID ?? String()} + set {_lastEndpointID = newValue} + } + /// Returns true if `lastEndpointID` has been explicitly set. + var hasLastEndpointID: Bool {return self._lastEndpointID != nil} + /// Clears the value of `lastEndpointID`. Subsequent reads from it will return its default value. + mutating func clearLastEndpointID() {self._lastEndpointID = nil} + var unknownFields = SwiftProtobuf.UnknownStorage() init() {} fileprivate var _endpointID: String? = nil fileprivate var _supportsDisablingEncryption: Bool? = nil + fileprivate var _lastEndpointID: String? = nil } /// Accompanies CLIENT_INTRODUCTION_ACK events. @@ -1426,6 +1831,7 @@ struct Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame { fileprivate var _upgradePathInfo: Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.UpgradePathInfo? = nil fileprivate var _clientIntroduction: Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.ClientIntroduction? = nil fileprivate var _clientIntroductionAck: Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.ClientIntroductionAck? = nil + fileprivate var _safeToClosePriorChannel: Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.SafeToClosePriorChannel? = nil } #if swift(>=4.2) @@ -1440,6 +1846,104 @@ extension Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.UpgradePa #endif // swift(>=4.2) +struct Location_Nearby_Connections_BandwidthUpgradeRetryFrame { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// The mediums this device supports upgrading to. This list should be filtered + /// by both the strategy and this device's individual limitations. + var supportedMedium: [Location_Nearby_Connections_BandwidthUpgradeRetryFrame.Medium] = [] + + /// If true, expect the remote endpoint to send back the latest + /// supported_medium. + var isRequest: Bool { + get {return _isRequest ?? false} + set {_isRequest = newValue} + } + /// Returns true if `isRequest` has been explicitly set. + var hasIsRequest: Bool {return self._isRequest != nil} + /// Clears the value of `isRequest`. Subsequent reads from it will return its default value. + mutating func clearIsRequest() {self._isRequest = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + /// Should always match cs/symbol:location.nearby.proto.connections.Medium + /// LINT.IfChange + enum Medium: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknownMedium // = 0 + + /// 1 is reserved. + case bluetooth // = 2 + case wifiHotspot // = 3 + case ble // = 4 + case wifiLan // = 5 + case wifiAware // = 6 + case nfc // = 7 + case wifiDirect // = 8 + case webRtc // = 9 + case bleL2Cap // = 10 + case usb // = 11 + case webRtcNonCellular // = 12 + case awdl // = 13 + + init() { + self = .unknownMedium + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknownMedium + case 2: self = .bluetooth + case 3: self = .wifiHotspot + case 4: self = .ble + case 5: self = .wifiLan + case 6: self = .wifiAware + case 7: self = .nfc + case 8: self = .wifiDirect + case 9: self = .webRtc + case 10: self = .bleL2Cap + case 11: self = .usb + case 12: self = .webRtcNonCellular + case 13: self = .awdl + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknownMedium: return 0 + case .bluetooth: return 2 + case .wifiHotspot: return 3 + case .ble: return 4 + case .wifiLan: return 5 + case .wifiAware: return 6 + case .nfc: return 7 + case .wifiDirect: return 8 + case .webRtc: return 9 + case .bleL2Cap: return 10 + case .usb: return 11 + case .webRtcNonCellular: return 12 + case .awdl: return 13 + } + } + + } + + init() {} + + fileprivate var _isRequest: Bool? = nil +} + +#if swift(>=4.2) + +extension Location_Nearby_Connections_BandwidthUpgradeRetryFrame.Medium: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + struct Location_Nearby_Connections_KeepAliveFrame { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -1455,11 +1959,22 @@ struct Location_Nearby_Connections_KeepAliveFrame { /// Clears the value of `ack`. Subsequent reads from it will return its default value. mutating func clearAck() {self._ack = nil} + /// The sequence number + var seqNum: UInt32 { + get {return _seqNum ?? 0} + set {_seqNum = newValue} + } + /// Returns true if `seqNum` has been explicitly set. + var hasSeqNum: Bool {return self._seqNum != nil} + /// Clears the value of `seqNum`. Subsequent reads from it will return its default value. + mutating func clearSeqNum() {self._seqNum = nil} + var unknownFields = SwiftProtobuf.UnknownStorage() init() {} fileprivate var _ack: Bool? = nil + fileprivate var _seqNum: UInt32? = nil } /// Informs the remote side to immediately severe the socket connection. @@ -1524,6 +2039,221 @@ struct Location_Nearby_Connections_PairedKeyEncryptionFrame { fileprivate var _signedData: Data? = nil } +/// Nearby Connections authentication frame, contains the bytes format of a +/// DeviceProvider's authentication message. +struct Location_Nearby_Connections_AuthenticationMessageFrame { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// An auth message generated by DeviceProvider. + /// To be sent to the remote device for verification during connection setups. + var authMessage: Data { + get {return _authMessage ?? Data()} + set {_authMessage = newValue} + } + /// Returns true if `authMessage` has been explicitly set. + var hasAuthMessage: Bool {return self._authMessage != nil} + /// Clears the value of `authMessage`. Subsequent reads from it will return its default value. + mutating func clearAuthMessage() {self._authMessage = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _authMessage: Data? = nil +} + +/// Nearby Connections authentication result frame. +struct Location_Nearby_Connections_AuthenticationResultFrame { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// The authentication result. Non null if this frame is used to exchange + /// authentication result. + var result: Int32 { + get {return _result ?? 0} + set {_result = newValue} + } + /// Returns true if `result` has been explicitly set. + var hasResult: Bool {return self._result != nil} + /// Clears the value of `result`. Subsequent reads from it will return its default value. + mutating func clearResult() {self._result = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _result: Int32? = nil +} + +struct Location_Nearby_Connections_AutoResumeFrame { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var eventType: Location_Nearby_Connections_AutoResumeFrame.EventType { + get {return _eventType ?? .unknownAutoResumeEventType} + set {_eventType = newValue} + } + /// Returns true if `eventType` has been explicitly set. + var hasEventType: Bool {return self._eventType != nil} + /// Clears the value of `eventType`. Subsequent reads from it will return its default value. + mutating func clearEventType() {self._eventType = nil} + + var pendingPayloadID: Int64 { + get {return _pendingPayloadID ?? 0} + set {_pendingPayloadID = newValue} + } + /// Returns true if `pendingPayloadID` has been explicitly set. + var hasPendingPayloadID: Bool {return self._pendingPayloadID != nil} + /// Clears the value of `pendingPayloadID`. Subsequent reads from it will return its default value. + mutating func clearPendingPayloadID() {self._pendingPayloadID = nil} + + var nextPayloadChunkIndex: Int32 { + get {return _nextPayloadChunkIndex ?? 0} + set {_nextPayloadChunkIndex = newValue} + } + /// Returns true if `nextPayloadChunkIndex` has been explicitly set. + var hasNextPayloadChunkIndex: Bool {return self._nextPayloadChunkIndex != nil} + /// Clears the value of `nextPayloadChunkIndex`. Subsequent reads from it will return its default value. + mutating func clearNextPayloadChunkIndex() {self._nextPayloadChunkIndex = nil} + + var version: Int32 { + get {return _version ?? 0} + set {_version = newValue} + } + /// Returns true if `version` has been explicitly set. + var hasVersion: Bool {return self._version != nil} + /// Clears the value of `version`. Subsequent reads from it will return its default value. + mutating func clearVersion() {self._version = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + enum EventType: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknownAutoResumeEventType // = 0 + case payloadResumeTransferStart // = 1 + case payloadResumeTransferAck // = 2 + + init() { + self = .unknownAutoResumeEventType + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknownAutoResumeEventType + case 1: self = .payloadResumeTransferStart + case 2: self = .payloadResumeTransferAck + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknownAutoResumeEventType: return 0 + case .payloadResumeTransferStart: return 1 + case .payloadResumeTransferAck: return 2 + } + } + + } + + init() {} + + fileprivate var _eventType: Location_Nearby_Connections_AutoResumeFrame.EventType? = nil + fileprivate var _pendingPayloadID: Int64? = nil + fileprivate var _nextPayloadChunkIndex: Int32? = nil + fileprivate var _version: Int32? = nil +} + +#if swift(>=4.2) + +extension Location_Nearby_Connections_AutoResumeFrame.EventType: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +struct Location_Nearby_Connections_AutoReconnectFrame { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var endpointID: String { + get {return _endpointID ?? String()} + set {_endpointID = newValue} + } + /// Returns true if `endpointID` has been explicitly set. + var hasEndpointID: Bool {return self._endpointID != nil} + /// Clears the value of `endpointID`. Subsequent reads from it will return its default value. + mutating func clearEndpointID() {self._endpointID = nil} + + var eventType: Location_Nearby_Connections_AutoReconnectFrame.EventType { + get {return _eventType ?? .unknownEventType} + set {_eventType = newValue} + } + /// Returns true if `eventType` has been explicitly set. + var hasEventType: Bool {return self._eventType != nil} + /// Clears the value of `eventType`. Subsequent reads from it will return its default value. + mutating func clearEventType() {self._eventType = nil} + + var lastEndpointID: String { + get {return _lastEndpointID ?? String()} + set {_lastEndpointID = newValue} + } + /// Returns true if `lastEndpointID` has been explicitly set. + var hasLastEndpointID: Bool {return self._lastEndpointID != nil} + /// Clears the value of `lastEndpointID`. Subsequent reads from it will return its default value. + mutating func clearLastEndpointID() {self._lastEndpointID = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + enum EventType: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknownEventType // = 0 + case clientIntroduction // = 1 + case clientIntroductionAck // = 2 + + init() { + self = .unknownEventType + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknownEventType + case 1: self = .clientIntroduction + case 2: self = .clientIntroductionAck + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknownEventType: return 0 + case .clientIntroduction: return 1 + case .clientIntroductionAck: return 2 + } + } + + } + + init() {} + + fileprivate var _endpointID: String? = nil + fileprivate var _eventType: Location_Nearby_Connections_AutoReconnectFrame.EventType? = nil + fileprivate var _lastEndpointID: String? = nil +} + +#if swift(>=4.2) + +extension Location_Nearby_Connections_AutoReconnectFrame.EventType: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + struct Location_Nearby_Connections_MediumMetadata { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -1531,131 +2261,131 @@ struct Location_Nearby_Connections_MediumMetadata { /// True if local device supports 5GHz. var supports5Ghz: Bool { - get {return _supports5Ghz ?? false} - set {_supports5Ghz = newValue} + get {return _storage._supports5Ghz ?? false} + set {_uniqueStorage()._supports5Ghz = newValue} } /// Returns true if `supports5Ghz` has been explicitly set. - var hasSupports5Ghz: Bool {return self._supports5Ghz != nil} + var hasSupports5Ghz: Bool {return _storage._supports5Ghz != nil} /// Clears the value of `supports5Ghz`. Subsequent reads from it will return its default value. - mutating func clearSupports5Ghz() {self._supports5Ghz = nil} + mutating func clearSupports5Ghz() {_uniqueStorage()._supports5Ghz = nil} /// WiFi LAN BSSID, in the form of a six-byte MAC address: XX:XX:XX:XX:XX:XX var bssid: String { - get {return _bssid ?? String()} - set {_bssid = newValue} + get {return _storage._bssid ?? String()} + set {_uniqueStorage()._bssid = newValue} } /// Returns true if `bssid` has been explicitly set. - var hasBssid: Bool {return self._bssid != nil} + var hasBssid: Bool {return _storage._bssid != nil} /// Clears the value of `bssid`. Subsequent reads from it will return its default value. - mutating func clearBssid() {self._bssid = nil} + mutating func clearBssid() {_uniqueStorage()._bssid = nil} /// IP address, in network byte order: the highest order byte of the address is /// in byte[0]. var ipAddress: Data { - get {return _ipAddress ?? Data()} - set {_ipAddress = newValue} + get {return _storage._ipAddress ?? Data()} + set {_uniqueStorage()._ipAddress = newValue} } /// Returns true if `ipAddress` has been explicitly set. - var hasIpAddress: Bool {return self._ipAddress != nil} + var hasIpAddress: Bool {return _storage._ipAddress != nil} /// Clears the value of `ipAddress`. Subsequent reads from it will return its default value. - mutating func clearIpAddress() {self._ipAddress = nil} + mutating func clearIpAddress() {_uniqueStorage()._ipAddress = nil} /// True if local device supports 6GHz. var supports6Ghz: Bool { - get {return _supports6Ghz ?? false} - set {_supports6Ghz = newValue} + get {return _storage._supports6Ghz ?? false} + set {_uniqueStorage()._supports6Ghz = newValue} } /// Returns true if `supports6Ghz` has been explicitly set. - var hasSupports6Ghz: Bool {return self._supports6Ghz != nil} + var hasSupports6Ghz: Bool {return _storage._supports6Ghz != nil} /// Clears the value of `supports6Ghz`. Subsequent reads from it will return its default value. - mutating func clearSupports6Ghz() {self._supports6Ghz = nil} + mutating func clearSupports6Ghz() {_uniqueStorage()._supports6Ghz = nil} /// True if local device has mobile radio. var mobileRadio: Bool { - get {return _mobileRadio ?? false} - set {_mobileRadio = newValue} + get {return _storage._mobileRadio ?? false} + set {_uniqueStorage()._mobileRadio = newValue} } /// Returns true if `mobileRadio` has been explicitly set. - var hasMobileRadio: Bool {return self._mobileRadio != nil} + var hasMobileRadio: Bool {return _storage._mobileRadio != nil} /// Clears the value of `mobileRadio`. Subsequent reads from it will return its default value. - mutating func clearMobileRadio() {self._mobileRadio = nil} + mutating func clearMobileRadio() {_uniqueStorage()._mobileRadio = nil} /// The frequency of the WiFi LAN AP(in MHz). Or -1 is not associated with an /// AP over WiFi, -2 represents the active network uses an Ethernet transport. var apFrequency: Int32 { - get {return _apFrequency ?? -1} - set {_apFrequency = newValue} + get {return _storage._apFrequency ?? -1} + set {_uniqueStorage()._apFrequency = newValue} } /// Returns true if `apFrequency` has been explicitly set. - var hasApFrequency: Bool {return self._apFrequency != nil} + var hasApFrequency: Bool {return _storage._apFrequency != nil} /// Clears the value of `apFrequency`. Subsequent reads from it will return its default value. - mutating func clearApFrequency() {self._apFrequency = nil} + mutating func clearApFrequency() {_uniqueStorage()._apFrequency = nil} /// Available channels on the local device. var availableChannels: Location_Nearby_Connections_AvailableChannels { - get {return _availableChannels ?? Location_Nearby_Connections_AvailableChannels()} - set {_availableChannels = newValue} + get {return _storage._availableChannels ?? Location_Nearby_Connections_AvailableChannels()} + set {_uniqueStorage()._availableChannels = newValue} } /// Returns true if `availableChannels` has been explicitly set. - var hasAvailableChannels: Bool {return self._availableChannels != nil} + var hasAvailableChannels: Bool {return _storage._availableChannels != nil} /// Clears the value of `availableChannels`. Subsequent reads from it will return its default value. - mutating func clearAvailableChannels() {self._availableChannels = nil} + mutating func clearAvailableChannels() {_uniqueStorage()._availableChannels = nil} /// Usable WiFi Direct client channels on the local device. var wifiDirectCliUsableChannels: Location_Nearby_Connections_WifiDirectCliUsableChannels { - get {return _wifiDirectCliUsableChannels ?? Location_Nearby_Connections_WifiDirectCliUsableChannels()} - set {_wifiDirectCliUsableChannels = newValue} + get {return _storage._wifiDirectCliUsableChannels ?? Location_Nearby_Connections_WifiDirectCliUsableChannels()} + set {_uniqueStorage()._wifiDirectCliUsableChannels = newValue} } /// Returns true if `wifiDirectCliUsableChannels` has been explicitly set. - var hasWifiDirectCliUsableChannels: Bool {return self._wifiDirectCliUsableChannels != nil} + var hasWifiDirectCliUsableChannels: Bool {return _storage._wifiDirectCliUsableChannels != nil} /// Clears the value of `wifiDirectCliUsableChannels`. Subsequent reads from it will return its default value. - mutating func clearWifiDirectCliUsableChannels() {self._wifiDirectCliUsableChannels = nil} + mutating func clearWifiDirectCliUsableChannels() {_uniqueStorage()._wifiDirectCliUsableChannels = nil} /// Usable WiFi LAN channels on the local device. var wifiLanUsableChannels: Location_Nearby_Connections_WifiLanUsableChannels { - get {return _wifiLanUsableChannels ?? Location_Nearby_Connections_WifiLanUsableChannels()} - set {_wifiLanUsableChannels = newValue} + get {return _storage._wifiLanUsableChannels ?? Location_Nearby_Connections_WifiLanUsableChannels()} + set {_uniqueStorage()._wifiLanUsableChannels = newValue} } /// Returns true if `wifiLanUsableChannels` has been explicitly set. - var hasWifiLanUsableChannels: Bool {return self._wifiLanUsableChannels != nil} + var hasWifiLanUsableChannels: Bool {return _storage._wifiLanUsableChannels != nil} /// Clears the value of `wifiLanUsableChannels`. Subsequent reads from it will return its default value. - mutating func clearWifiLanUsableChannels() {self._wifiLanUsableChannels = nil} + mutating func clearWifiLanUsableChannels() {_uniqueStorage()._wifiLanUsableChannels = nil} /// Usable WiFi Aware channels on the local device. var wifiAwareUsableChannels: Location_Nearby_Connections_WifiAwareUsableChannels { - get {return _wifiAwareUsableChannels ?? Location_Nearby_Connections_WifiAwareUsableChannels()} - set {_wifiAwareUsableChannels = newValue} + get {return _storage._wifiAwareUsableChannels ?? Location_Nearby_Connections_WifiAwareUsableChannels()} + set {_uniqueStorage()._wifiAwareUsableChannels = newValue} } /// Returns true if `wifiAwareUsableChannels` has been explicitly set. - var hasWifiAwareUsableChannels: Bool {return self._wifiAwareUsableChannels != nil} + var hasWifiAwareUsableChannels: Bool {return _storage._wifiAwareUsableChannels != nil} /// Clears the value of `wifiAwareUsableChannels`. Subsequent reads from it will return its default value. - mutating func clearWifiAwareUsableChannels() {self._wifiAwareUsableChannels = nil} + mutating func clearWifiAwareUsableChannels() {_uniqueStorage()._wifiAwareUsableChannels = nil} /// Usable WiFi Hotspot STA channels on the local device. var wifiHotspotStaUsableChannels: Location_Nearby_Connections_WifiHotspotStaUsableChannels { - get {return _wifiHotspotStaUsableChannels ?? Location_Nearby_Connections_WifiHotspotStaUsableChannels()} - set {_wifiHotspotStaUsableChannels = newValue} + get {return _storage._wifiHotspotStaUsableChannels ?? Location_Nearby_Connections_WifiHotspotStaUsableChannels()} + set {_uniqueStorage()._wifiHotspotStaUsableChannels = newValue} } /// Returns true if `wifiHotspotStaUsableChannels` has been explicitly set. - var hasWifiHotspotStaUsableChannels: Bool {return self._wifiHotspotStaUsableChannels != nil} + var hasWifiHotspotStaUsableChannels: Bool {return _storage._wifiHotspotStaUsableChannels != nil} /// Clears the value of `wifiHotspotStaUsableChannels`. Subsequent reads from it will return its default value. - mutating func clearWifiHotspotStaUsableChannels() {self._wifiHotspotStaUsableChannels = nil} + mutating func clearWifiHotspotStaUsableChannels() {_uniqueStorage()._wifiHotspotStaUsableChannels = nil} + + /// The supported medium roles. + var mediumRole: Location_Nearby_Connections_MediumRole { + get {return _storage._mediumRole ?? Location_Nearby_Connections_MediumRole()} + set {_uniqueStorage()._mediumRole = newValue} + } + /// Returns true if `mediumRole` has been explicitly set. + var hasMediumRole: Bool {return _storage._mediumRole != nil} + /// Clears the value of `mediumRole`. Subsequent reads from it will return its default value. + mutating func clearMediumRole() {_uniqueStorage()._mediumRole = nil} var unknownFields = SwiftProtobuf.UnknownStorage() init() {} - fileprivate var _supports5Ghz: Bool? = nil - fileprivate var _bssid: String? = nil - fileprivate var _ipAddress: Data? = nil - fileprivate var _supports6Ghz: Bool? = nil - fileprivate var _mobileRadio: Bool? = nil - fileprivate var _apFrequency: Int32? = nil - fileprivate var _availableChannels: Location_Nearby_Connections_AvailableChannels? = nil - fileprivate var _wifiDirectCliUsableChannels: Location_Nearby_Connections_WifiDirectCliUsableChannels? = nil - fileprivate var _wifiLanUsableChannels: Location_Nearby_Connections_WifiLanUsableChannels? = nil - fileprivate var _wifiAwareUsableChannels: Location_Nearby_Connections_WifiAwareUsableChannels? = nil - fileprivate var _wifiHotspotStaUsableChannels: Location_Nearby_Connections_WifiHotspotStaUsableChannels? = nil + fileprivate var _storage = _StorageClass.defaultInstance } /// Available channels on the local device. @@ -1723,6 +2453,98 @@ struct Location_Nearby_Connections_WifiHotspotStaUsableChannels { init() {} } +/// The medium roles. +struct Location_Nearby_Connections_MediumRole { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var supportWifiDirectGroupOwner: Bool { + get {return _supportWifiDirectGroupOwner ?? false} + set {_supportWifiDirectGroupOwner = newValue} + } + /// Returns true if `supportWifiDirectGroupOwner` has been explicitly set. + var hasSupportWifiDirectGroupOwner: Bool {return self._supportWifiDirectGroupOwner != nil} + /// Clears the value of `supportWifiDirectGroupOwner`. Subsequent reads from it will return its default value. + mutating func clearSupportWifiDirectGroupOwner() {self._supportWifiDirectGroupOwner = nil} + + var supportWifiDirectGroupClient: Bool { + get {return _supportWifiDirectGroupClient ?? false} + set {_supportWifiDirectGroupClient = newValue} + } + /// Returns true if `supportWifiDirectGroupClient` has been explicitly set. + var hasSupportWifiDirectGroupClient: Bool {return self._supportWifiDirectGroupClient != nil} + /// Clears the value of `supportWifiDirectGroupClient`. Subsequent reads from it will return its default value. + mutating func clearSupportWifiDirectGroupClient() {self._supportWifiDirectGroupClient = nil} + + var supportWifiHotspotHost: Bool { + get {return _supportWifiHotspotHost ?? false} + set {_supportWifiHotspotHost = newValue} + } + /// Returns true if `supportWifiHotspotHost` has been explicitly set. + var hasSupportWifiHotspotHost: Bool {return self._supportWifiHotspotHost != nil} + /// Clears the value of `supportWifiHotspotHost`. Subsequent reads from it will return its default value. + mutating func clearSupportWifiHotspotHost() {self._supportWifiHotspotHost = nil} + + var supportWifiHotspotClient: Bool { + get {return _supportWifiHotspotClient ?? false} + set {_supportWifiHotspotClient = newValue} + } + /// Returns true if `supportWifiHotspotClient` has been explicitly set. + var hasSupportWifiHotspotClient: Bool {return self._supportWifiHotspotClient != nil} + /// Clears the value of `supportWifiHotspotClient`. Subsequent reads from it will return its default value. + mutating func clearSupportWifiHotspotClient() {self._supportWifiHotspotClient = nil} + + var supportWifiAwarePublisher: Bool { + get {return _supportWifiAwarePublisher ?? false} + set {_supportWifiAwarePublisher = newValue} + } + /// Returns true if `supportWifiAwarePublisher` has been explicitly set. + var hasSupportWifiAwarePublisher: Bool {return self._supportWifiAwarePublisher != nil} + /// Clears the value of `supportWifiAwarePublisher`. Subsequent reads from it will return its default value. + mutating func clearSupportWifiAwarePublisher() {self._supportWifiAwarePublisher = nil} + + var supportWifiAwareSubscriber: Bool { + get {return _supportWifiAwareSubscriber ?? false} + set {_supportWifiAwareSubscriber = newValue} + } + /// Returns true if `supportWifiAwareSubscriber` has been explicitly set. + var hasSupportWifiAwareSubscriber: Bool {return self._supportWifiAwareSubscriber != nil} + /// Clears the value of `supportWifiAwareSubscriber`. Subsequent reads from it will return its default value. + mutating func clearSupportWifiAwareSubscriber() {self._supportWifiAwareSubscriber = nil} + + var supportAwdlPublisher: Bool { + get {return _supportAwdlPublisher ?? false} + set {_supportAwdlPublisher = newValue} + } + /// Returns true if `supportAwdlPublisher` has been explicitly set. + var hasSupportAwdlPublisher: Bool {return self._supportAwdlPublisher != nil} + /// Clears the value of `supportAwdlPublisher`. Subsequent reads from it will return its default value. + mutating func clearSupportAwdlPublisher() {self._supportAwdlPublisher = nil} + + var supportAwdlSubscriber: Bool { + get {return _supportAwdlSubscriber ?? false} + set {_supportAwdlSubscriber = newValue} + } + /// Returns true if `supportAwdlSubscriber` has been explicitly set. + var hasSupportAwdlSubscriber: Bool {return self._supportAwdlSubscriber != nil} + /// Clears the value of `supportAwdlSubscriber`. Subsequent reads from it will return its default value. + mutating func clearSupportAwdlSubscriber() {self._supportAwdlSubscriber = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _supportWifiDirectGroupOwner: Bool? = nil + fileprivate var _supportWifiDirectGroupClient: Bool? = nil + fileprivate var _supportWifiHotspotHost: Bool? = nil + fileprivate var _supportWifiHotspotClient: Bool? = nil + fileprivate var _supportWifiAwarePublisher: Bool? = nil + fileprivate var _supportWifiAwareSubscriber: Bool? = nil + fileprivate var _supportAwdlPublisher: Bool? = nil + fileprivate var _supportAwdlSubscriber: Bool? = nil +} + /// LocationHint is used to specify a location as well as format. struct Location_Nearby_Connections_LocationHint { // SwiftProtobuf.Message conformance is added in an extension below. See the @@ -1881,13 +2703,205 @@ extension Location_Nearby_Connections_OsInfo.OsType: CaseIterable { #endif // swift(>=4.2) +struct Location_Nearby_Connections_ConnectionsDevice { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var endpointID: String { + get {return _endpointID ?? String()} + set {_endpointID = newValue} + } + /// Returns true if `endpointID` has been explicitly set. + var hasEndpointID: Bool {return self._endpointID != nil} + /// Clears the value of `endpointID`. Subsequent reads from it will return its default value. + mutating func clearEndpointID() {self._endpointID = nil} + + var endpointType: Location_Nearby_Connections_EndpointType { + get {return _endpointType ?? .unknownEndpoint} + set {_endpointType = newValue} + } + /// Returns true if `endpointType` has been explicitly set. + var hasEndpointType: Bool {return self._endpointType != nil} + /// Clears the value of `endpointType`. Subsequent reads from it will return its default value. + mutating func clearEndpointType() {self._endpointType = nil} + + /// Data Elements. + var connectivityInfoList: Data { + get {return _connectivityInfoList ?? Data()} + set {_connectivityInfoList = newValue} + } + /// Returns true if `connectivityInfoList` has been explicitly set. + var hasConnectivityInfoList: Bool {return self._connectivityInfoList != nil} + /// Clears the value of `connectivityInfoList`. Subsequent reads from it will return its default value. + mutating func clearConnectivityInfoList() {self._connectivityInfoList = nil} + + var endpointInfo: Data { + get {return _endpointInfo ?? Data()} + set {_endpointInfo = newValue} + } + /// Returns true if `endpointInfo` has been explicitly set. + var hasEndpointInfo: Bool {return self._endpointInfo != nil} + /// Clears the value of `endpointInfo`. Subsequent reads from it will return its default value. + mutating func clearEndpointInfo() {self._endpointInfo = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _endpointID: String? = nil + fileprivate var _endpointType: Location_Nearby_Connections_EndpointType? = nil + fileprivate var _connectivityInfoList: Data? = nil + fileprivate var _endpointInfo: Data? = nil +} + +struct Location_Nearby_Connections_PresenceDevice { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var endpointID: String { + get {return _endpointID ?? String()} + set {_endpointID = newValue} + } + /// Returns true if `endpointID` has been explicitly set. + var hasEndpointID: Bool {return self._endpointID != nil} + /// Clears the value of `endpointID`. Subsequent reads from it will return its default value. + mutating func clearEndpointID() {self._endpointID = nil} + + var endpointType: Location_Nearby_Connections_EndpointType { + get {return _endpointType ?? .unknownEndpoint} + set {_endpointType = newValue} + } + /// Returns true if `endpointType` has been explicitly set. + var hasEndpointType: Bool {return self._endpointType != nil} + /// Clears the value of `endpointType`. Subsequent reads from it will return its default value. + mutating func clearEndpointType() {self._endpointType = nil} + + /// Data Elements. + var connectivityInfoList: Data { + get {return _connectivityInfoList ?? Data()} + set {_connectivityInfoList = newValue} + } + /// Returns true if `connectivityInfoList` has been explicitly set. + var hasConnectivityInfoList: Bool {return self._connectivityInfoList != nil} + /// Clears the value of `connectivityInfoList`. Subsequent reads from it will return its default value. + mutating func clearConnectivityInfoList() {self._connectivityInfoList = nil} + + var deviceID: Int64 { + get {return _deviceID ?? 0} + set {_deviceID = newValue} + } + /// Returns true if `deviceID` has been explicitly set. + var hasDeviceID: Bool {return self._deviceID != nil} + /// Clears the value of `deviceID`. Subsequent reads from it will return its default value. + mutating func clearDeviceID() {self._deviceID = nil} + + var deviceName: String { + get {return _deviceName ?? String()} + set {_deviceName = newValue} + } + /// Returns true if `deviceName` has been explicitly set. + var hasDeviceName: Bool {return self._deviceName != nil} + /// Clears the value of `deviceName`. Subsequent reads from it will return its default value. + mutating func clearDeviceName() {self._deviceName = nil} + + var deviceType: Location_Nearby_Connections_PresenceDevice.DeviceType { + get {return _deviceType ?? .unknown} + set {_deviceType = newValue} + } + /// Returns true if `deviceType` has been explicitly set. + var hasDeviceType: Bool {return self._deviceType != nil} + /// Clears the value of `deviceType`. Subsequent reads from it will return its default value. + mutating func clearDeviceType() {self._deviceType = nil} + + var deviceImageURL: String { + get {return _deviceImageURL ?? String()} + set {_deviceImageURL = newValue} + } + /// Returns true if `deviceImageURL` has been explicitly set. + var hasDeviceImageURL: Bool {return self._deviceImageURL != nil} + /// Clears the value of `deviceImageURL`. Subsequent reads from it will return its default value. + mutating func clearDeviceImageURL() {self._deviceImageURL = nil} + + var discoveryMedium: [Location_Nearby_Connections_ConnectionRequestFrame.Medium] = [] + + var actions: [Int32] = [] + + var identityType: [Int64] = [] + + var unknownFields = SwiftProtobuf.UnknownStorage() + + enum DeviceType: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknown // = 0 + case phone // = 1 + case tablet // = 2 + case display // = 3 + case laptop // = 4 + case tv // = 5 + case watch // = 6 + + init() { + self = .unknown + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknown + case 1: self = .phone + case 2: self = .tablet + case 3: self = .display + case 4: self = .laptop + case 5: self = .tv + case 6: self = .watch + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknown: return 0 + case .phone: return 1 + case .tablet: return 2 + case .display: return 3 + case .laptop: return 4 + case .tv: return 5 + case .watch: return 6 + } + } + + } + + init() {} + + fileprivate var _endpointID: String? = nil + fileprivate var _endpointType: Location_Nearby_Connections_EndpointType? = nil + fileprivate var _connectivityInfoList: Data? = nil + fileprivate var _deviceID: Int64? = nil + fileprivate var _deviceName: String? = nil + fileprivate var _deviceType: Location_Nearby_Connections_PresenceDevice.DeviceType? = nil + fileprivate var _deviceImageURL: String? = nil +} + +#if swift(>=4.2) + +extension Location_Nearby_Connections_PresenceDevice.DeviceType: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + #if swift(>=5.5) && canImport(_Concurrency) +extension Location_Nearby_Connections_EndpointType: @unchecked Sendable {} extension Location_Nearby_Connections_OfflineFrame: @unchecked Sendable {} extension Location_Nearby_Connections_OfflineFrame.Version: @unchecked Sendable {} extension Location_Nearby_Connections_V1Frame: @unchecked Sendable {} extension Location_Nearby_Connections_V1Frame.FrameType: @unchecked Sendable {} extension Location_Nearby_Connections_ConnectionRequestFrame: @unchecked Sendable {} +extension Location_Nearby_Connections_ConnectionRequestFrame.OneOf_Device: @unchecked Sendable {} extension Location_Nearby_Connections_ConnectionRequestFrame.Medium: @unchecked Sendable {} +extension Location_Nearby_Connections_ConnectionRequestFrame.ConnectionMode: @unchecked Sendable {} extension Location_Nearby_Connections_ConnectionResponseFrame: @unchecked Sendable {} extension Location_Nearby_Connections_ConnectionResponseFrame.ResponseStatus: @unchecked Sendable {} extension Location_Nearby_Connections_PayloadTransferFrame: @unchecked Sendable {} @@ -1908,28 +2922,51 @@ extension Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.UpgradePa extension Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.UpgradePathInfo.WifiAwareCredentials: @unchecked Sendable {} extension Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.UpgradePathInfo.WifiDirectCredentials: @unchecked Sendable {} extension Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.UpgradePathInfo.WebRtcCredentials: @unchecked Sendable {} +extension Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.UpgradePathInfo.AwdlCredentials: @unchecked Sendable {} +extension Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.UpgradePathInfo.UpgradePathRequest: @unchecked Sendable {} +extension Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.SafeToClosePriorChannel: @unchecked Sendable {} extension Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.ClientIntroduction: @unchecked Sendable {} extension Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.ClientIntroductionAck: @unchecked Sendable {} +extension Location_Nearby_Connections_BandwidthUpgradeRetryFrame: @unchecked Sendable {} +extension Location_Nearby_Connections_BandwidthUpgradeRetryFrame.Medium: @unchecked Sendable {} extension Location_Nearby_Connections_KeepAliveFrame: @unchecked Sendable {} extension Location_Nearby_Connections_DisconnectionFrame: @unchecked Sendable {} extension Location_Nearby_Connections_PairedKeyEncryptionFrame: @unchecked Sendable {} +extension Location_Nearby_Connections_AuthenticationMessageFrame: @unchecked Sendable {} +extension Location_Nearby_Connections_AuthenticationResultFrame: @unchecked Sendable {} +extension Location_Nearby_Connections_AutoResumeFrame: @unchecked Sendable {} +extension Location_Nearby_Connections_AutoResumeFrame.EventType: @unchecked Sendable {} +extension Location_Nearby_Connections_AutoReconnectFrame: @unchecked Sendable {} +extension Location_Nearby_Connections_AutoReconnectFrame.EventType: @unchecked Sendable {} extension Location_Nearby_Connections_MediumMetadata: @unchecked Sendable {} extension Location_Nearby_Connections_AvailableChannels: @unchecked Sendable {} extension Location_Nearby_Connections_WifiDirectCliUsableChannels: @unchecked Sendable {} extension Location_Nearby_Connections_WifiLanUsableChannels: @unchecked Sendable {} extension Location_Nearby_Connections_WifiAwareUsableChannels: @unchecked Sendable {} extension Location_Nearby_Connections_WifiHotspotStaUsableChannels: @unchecked Sendable {} +extension Location_Nearby_Connections_MediumRole: @unchecked Sendable {} extension Location_Nearby_Connections_LocationHint: @unchecked Sendable {} extension Location_Nearby_Connections_LocationStandard: @unchecked Sendable {} extension Location_Nearby_Connections_LocationStandard.Format: @unchecked Sendable {} extension Location_Nearby_Connections_OsInfo: @unchecked Sendable {} extension Location_Nearby_Connections_OsInfo.OsType: @unchecked Sendable {} +extension Location_Nearby_Connections_ConnectionsDevice: @unchecked Sendable {} +extension Location_Nearby_Connections_PresenceDevice: @unchecked Sendable {} +extension Location_Nearby_Connections_PresenceDevice.DeviceType: @unchecked Sendable {} #endif // swift(>=5.5) && canImport(_Concurrency) // MARK: - Code below here is support for the SwiftProtobuf runtime. fileprivate let _protobuf_package = "location.nearby.connections" +extension Location_Nearby_Connections_EndpointType: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "UNKNOWN_ENDPOINT"), + 1: .same(proto: "CONNECTIONS_ENDPOINT"), + 2: .same(proto: "PRESENCE_ENDPOINT"), + ] +} + extension Location_Nearby_Connections_OfflineFrame: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { static let protoMessageName: String = _protobuf_package + ".OfflineFrame" static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ @@ -1990,6 +3027,11 @@ extension Location_Nearby_Connections_V1Frame: SwiftProtobuf.Message, SwiftProto 6: .standard(proto: "keep_alive"), 7: .same(proto: "disconnection"), 8: .standard(proto: "paired_key_encryption"), + 9: .standard(proto: "authentication_message"), + 10: .standard(proto: "authentication_result"), + 11: .standard(proto: "auto_resume"), + 12: .standard(proto: "auto_reconnect"), + 13: .standard(proto: "bandwidth_upgrade_retry"), ] fileprivate class _StorageClass { @@ -2001,6 +3043,11 @@ extension Location_Nearby_Connections_V1Frame: SwiftProtobuf.Message, SwiftProto var _keepAlive: Location_Nearby_Connections_KeepAliveFrame? = nil var _disconnection: Location_Nearby_Connections_DisconnectionFrame? = nil var _pairedKeyEncryption: Location_Nearby_Connections_PairedKeyEncryptionFrame? = nil + var _authenticationMessage: Location_Nearby_Connections_AuthenticationMessageFrame? = nil + var _authenticationResult: Location_Nearby_Connections_AuthenticationResultFrame? = nil + var _autoResume: Location_Nearby_Connections_AutoResumeFrame? = nil + var _autoReconnect: Location_Nearby_Connections_AutoReconnectFrame? = nil + var _bandwidthUpgradeRetry: Location_Nearby_Connections_BandwidthUpgradeRetryFrame? = nil static let defaultInstance = _StorageClass() @@ -2015,6 +3062,11 @@ extension Location_Nearby_Connections_V1Frame: SwiftProtobuf.Message, SwiftProto _keepAlive = source._keepAlive _disconnection = source._disconnection _pairedKeyEncryption = source._pairedKeyEncryption + _authenticationMessage = source._authenticationMessage + _authenticationResult = source._authenticationResult + _autoResume = source._autoResume + _autoReconnect = source._autoReconnect + _bandwidthUpgradeRetry = source._bandwidthUpgradeRetry } } @@ -2041,6 +3093,11 @@ extension Location_Nearby_Connections_V1Frame: SwiftProtobuf.Message, SwiftProto case 6: try { try decoder.decodeSingularMessageField(value: &_storage._keepAlive) }() case 7: try { try decoder.decodeSingularMessageField(value: &_storage._disconnection) }() case 8: try { try decoder.decodeSingularMessageField(value: &_storage._pairedKeyEncryption) }() + case 9: try { try decoder.decodeSingularMessageField(value: &_storage._authenticationMessage) }() + case 10: try { try decoder.decodeSingularMessageField(value: &_storage._authenticationResult) }() + case 11: try { try decoder.decodeSingularMessageField(value: &_storage._autoResume) }() + case 12: try { try decoder.decodeSingularMessageField(value: &_storage._autoReconnect) }() + case 13: try { try decoder.decodeSingularMessageField(value: &_storage._bandwidthUpgradeRetry) }() default: break } } @@ -2077,6 +3134,21 @@ extension Location_Nearby_Connections_V1Frame: SwiftProtobuf.Message, SwiftProto try { if let v = _storage._pairedKeyEncryption { try visitor.visitSingularMessageField(value: v, fieldNumber: 8) } }() + try { if let v = _storage._authenticationMessage { + try visitor.visitSingularMessageField(value: v, fieldNumber: 9) + } }() + try { if let v = _storage._authenticationResult { + try visitor.visitSingularMessageField(value: v, fieldNumber: 10) + } }() + try { if let v = _storage._autoResume { + try visitor.visitSingularMessageField(value: v, fieldNumber: 11) + } }() + try { if let v = _storage._autoReconnect { + try visitor.visitSingularMessageField(value: v, fieldNumber: 12) + } }() + try { if let v = _storage._bandwidthUpgradeRetry { + try visitor.visitSingularMessageField(value: v, fieldNumber: 13) + } }() } try unknownFields.traverse(visitor: &visitor) } @@ -2094,6 +3166,11 @@ extension Location_Nearby_Connections_V1Frame: SwiftProtobuf.Message, SwiftProto if _storage._keepAlive != rhs_storage._keepAlive {return false} if _storage._disconnection != rhs_storage._disconnection {return false} if _storage._pairedKeyEncryption != rhs_storage._pairedKeyEncryption {return false} + if _storage._authenticationMessage != rhs_storage._authenticationMessage {return false} + if _storage._authenticationResult != rhs_storage._authenticationResult {return false} + if _storage._autoResume != rhs_storage._autoResume {return false} + if _storage._autoReconnect != rhs_storage._autoReconnect {return false} + if _storage._bandwidthUpgradeRetry != rhs_storage._bandwidthUpgradeRetry {return false} return true } if !storagesAreEqual {return false} @@ -2113,6 +3190,11 @@ extension Location_Nearby_Connections_V1Frame.FrameType: SwiftProtobuf._ProtoNam 5: .same(proto: "KEEP_ALIVE"), 6: .same(proto: "DISCONNECTION"), 7: .same(proto: "PAIRED_KEY_ENCRYPTION"), + 8: .same(proto: "AUTHENTICATION_MESSAGE"), + 9: .same(proto: "AUTHENTICATION_RESULT"), + 10: .same(proto: "AUTO_RESUME"), + 11: .same(proto: "AUTO_RECONNECT"), + 12: .same(proto: "BANDWIDTH_UPGRADE_RETRY"), ] } @@ -2130,6 +3212,10 @@ extension Location_Nearby_Connections_ConnectionRequestFrame: SwiftProtobuf.Mess 9: .standard(proto: "keep_alive_timeout_millis"), 10: .standard(proto: "device_type"), 11: .standard(proto: "device_info"), + 12: .standard(proto: "connections_device"), + 13: .standard(proto: "presence_device"), + 14: .standard(proto: "connection_mode"), + 15: .standard(proto: "location_hint"), ] fileprivate class _StorageClass { @@ -2144,6 +3230,9 @@ extension Location_Nearby_Connections_ConnectionRequestFrame: SwiftProtobuf.Mess var _keepAliveTimeoutMillis: Int32? = nil var _deviceType: Int32? = nil var _deviceInfo: Data? = nil + var _device: Location_Nearby_Connections_ConnectionRequestFrame.OneOf_Device? + var _connectionMode: Location_Nearby_Connections_ConnectionRequestFrame.ConnectionMode? = nil + var _locationHint: Location_Nearby_Connections_LocationHint? = nil static let defaultInstance = _StorageClass() @@ -2161,6 +3250,9 @@ extension Location_Nearby_Connections_ConnectionRequestFrame: SwiftProtobuf.Mess _keepAliveTimeoutMillis = source._keepAliveTimeoutMillis _deviceType = source._deviceType _deviceInfo = source._deviceInfo + _device = source._device + _connectionMode = source._connectionMode + _locationHint = source._locationHint } } @@ -2190,6 +3282,34 @@ extension Location_Nearby_Connections_ConnectionRequestFrame: SwiftProtobuf.Mess case 9: try { try decoder.decodeSingularInt32Field(value: &_storage._keepAliveTimeoutMillis) }() case 10: try { try decoder.decodeSingularInt32Field(value: &_storage._deviceType) }() case 11: try { try decoder.decodeSingularBytesField(value: &_storage._deviceInfo) }() + case 12: try { + var v: Location_Nearby_Connections_ConnectionsDevice? + var hadOneofValue = false + if let current = _storage._device { + hadOneofValue = true + if case .connectionsDevice(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + _storage._device = .connectionsDevice(v) + } + }() + case 13: try { + var v: Location_Nearby_Connections_PresenceDevice? + var hadOneofValue = false + if let current = _storage._device { + hadOneofValue = true + if case .presenceDevice(let m) = current {v = m} + } + try decoder.decodeSingularMessageField(value: &v) + if let v = v { + if hadOneofValue {try decoder.handleConflictingOneOf()} + _storage._device = .presenceDevice(v) + } + }() + case 14: try { try decoder.decodeSingularEnumField(value: &_storage._connectionMode) }() + case 15: try { try decoder.decodeSingularMessageField(value: &_storage._locationHint) }() default: break } } @@ -2235,6 +3355,23 @@ extension Location_Nearby_Connections_ConnectionRequestFrame: SwiftProtobuf.Mess try { if let v = _storage._deviceInfo { try visitor.visitSingularBytesField(value: v, fieldNumber: 11) } }() + switch _storage._device { + case .connectionsDevice?: try { + guard case .connectionsDevice(let v)? = _storage._device else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 12) + }() + case .presenceDevice?: try { + guard case .presenceDevice(let v)? = _storage._device else { preconditionFailure() } + try visitor.visitSingularMessageField(value: v, fieldNumber: 13) + }() + case nil: break + } + try { if let v = _storage._connectionMode { + try visitor.visitSingularEnumField(value: v, fieldNumber: 14) + } }() + try { if let v = _storage._locationHint { + try visitor.visitSingularMessageField(value: v, fieldNumber: 15) + } }() } try unknownFields.traverse(visitor: &visitor) } @@ -2255,6 +3392,9 @@ extension Location_Nearby_Connections_ConnectionRequestFrame: SwiftProtobuf.Mess if _storage._keepAliveTimeoutMillis != rhs_storage._keepAliveTimeoutMillis {return false} if _storage._deviceType != rhs_storage._deviceType {return false} if _storage._deviceInfo != rhs_storage._deviceInfo {return false} + if _storage._device != rhs_storage._device {return false} + if _storage._connectionMode != rhs_storage._connectionMode {return false} + if _storage._locationHint != rhs_storage._locationHint {return false} return true } if !storagesAreEqual {return false} @@ -2278,6 +3418,15 @@ extension Location_Nearby_Connections_ConnectionRequestFrame.Medium: SwiftProtob 9: .same(proto: "WEB_RTC"), 10: .same(proto: "BLE_L2CAP"), 11: .same(proto: "USB"), + 12: .same(proto: "WEB_RTC_NON_CELLULAR"), + 13: .same(proto: "AWDL"), + ] +} + +extension Location_Nearby_Connections_ConnectionRequestFrame.ConnectionMode: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "LEGACY"), + 1: .same(proto: "INSTANT"), ] } @@ -2290,6 +3439,9 @@ extension Location_Nearby_Connections_ConnectionResponseFrame: SwiftProtobuf.Mes 4: .standard(proto: "os_info"), 5: .standard(proto: "multiplex_socket_bitmask"), 6: .standard(proto: "nearby_connections_version"), + 7: .standard(proto: "safe_to_disconnect_version"), + 8: .standard(proto: "location_hint"), + 9: .standard(proto: "keep_alive_timeout_millis"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -2304,6 +3456,9 @@ extension Location_Nearby_Connections_ConnectionResponseFrame: SwiftProtobuf.Mes case 4: try { try decoder.decodeSingularMessageField(value: &self._osInfo) }() case 5: try { try decoder.decodeSingularInt32Field(value: &self._multiplexSocketBitmask) }() case 6: try { try decoder.decodeSingularInt32Field(value: &self._nearbyConnectionsVersion) }() + case 7: try { try decoder.decodeSingularInt32Field(value: &self._safeToDisconnectVersion) }() + case 8: try { try decoder.decodeSingularMessageField(value: &self._locationHint) }() + case 9: try { try decoder.decodeSingularInt32Field(value: &self._keepAliveTimeoutMillis) }() default: break } } @@ -2332,6 +3487,15 @@ extension Location_Nearby_Connections_ConnectionResponseFrame: SwiftProtobuf.Mes try { if let v = self._nearbyConnectionsVersion { try visitor.visitSingularInt32Field(value: v, fieldNumber: 6) } }() + try { if let v = self._safeToDisconnectVersion { + try visitor.visitSingularInt32Field(value: v, fieldNumber: 7) + } }() + try { if let v = self._locationHint { + try visitor.visitSingularMessageField(value: v, fieldNumber: 8) + } }() + try { if let v = self._keepAliveTimeoutMillis { + try visitor.visitSingularInt32Field(value: v, fieldNumber: 9) + } }() try unknownFields.traverse(visitor: &visitor) } @@ -2342,6 +3506,9 @@ extension Location_Nearby_Connections_ConnectionResponseFrame: SwiftProtobuf.Mes if lhs._osInfo != rhs._osInfo {return false} if lhs._multiplexSocketBitmask != rhs._multiplexSocketBitmask {return false} if lhs._nearbyConnectionsVersion != rhs._nearbyConnectionsVersion {return false} + if lhs._safeToDisconnectVersion != rhs._safeToDisconnectVersion {return false} + if lhs._locationHint != rhs._locationHint {return false} + if lhs._keepAliveTimeoutMillis != rhs._keepAliveTimeoutMillis {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -2414,6 +3581,7 @@ extension Location_Nearby_Connections_PayloadTransferFrame.PacketType: SwiftProt 0: .same(proto: "UNKNOWN_PACKET_TYPE"), 1: .same(proto: "DATA"), 2: .same(proto: "CONTROL"), + 3: .same(proto: "PAYLOAD_ACK"), ] } @@ -2426,6 +3594,7 @@ extension Location_Nearby_Connections_PayloadTransferFrame.PayloadHeader: SwiftP 4: .standard(proto: "is_sensitive"), 5: .standard(proto: "file_name"), 6: .standard(proto: "parent_folder"), + 7: .standard(proto: "last_modified_timestamp_millis"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -2440,6 +3609,7 @@ extension Location_Nearby_Connections_PayloadTransferFrame.PayloadHeader: SwiftP case 4: try { try decoder.decodeSingularBoolField(value: &self._isSensitive) }() case 5: try { try decoder.decodeSingularStringField(value: &self._fileName) }() case 6: try { try decoder.decodeSingularStringField(value: &self._parentFolder) }() + case 7: try { try decoder.decodeSingularInt64Field(value: &self._lastModifiedTimestampMillis) }() default: break } } @@ -2468,6 +3638,9 @@ extension Location_Nearby_Connections_PayloadTransferFrame.PayloadHeader: SwiftP try { if let v = self._parentFolder { try visitor.visitSingularStringField(value: v, fieldNumber: 6) } }() + try { if let v = self._lastModifiedTimestampMillis { + try visitor.visitSingularInt64Field(value: v, fieldNumber: 7) + } }() try unknownFields.traverse(visitor: &visitor) } @@ -2478,6 +3651,7 @@ extension Location_Nearby_Connections_PayloadTransferFrame.PayloadHeader: SwiftP if lhs._isSensitive != rhs._isSensitive {return false} if lhs._fileName != rhs._fileName {return false} if lhs._parentFolder != rhs._parentFolder {return false} + if lhs._lastModifiedTimestampMillis != rhs._lastModifiedTimestampMillis {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -2498,6 +3672,7 @@ extension Location_Nearby_Connections_PayloadTransferFrame.PayloadChunk: SwiftPr 1: .same(proto: "flags"), 2: .same(proto: "offset"), 3: .same(proto: "body"), + 4: .same(proto: "index"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -2509,6 +3684,7 @@ extension Location_Nearby_Connections_PayloadTransferFrame.PayloadChunk: SwiftPr case 1: try { try decoder.decodeSingularInt32Field(value: &self._flags) }() case 2: try { try decoder.decodeSingularInt64Field(value: &self._offset) }() case 3: try { try decoder.decodeSingularBytesField(value: &self._body) }() + case 4: try { try decoder.decodeSingularInt32Field(value: &self._index) }() default: break } } @@ -2528,6 +3704,9 @@ extension Location_Nearby_Connections_PayloadTransferFrame.PayloadChunk: SwiftPr try { if let v = self._body { try visitor.visitSingularBytesField(value: v, fieldNumber: 3) } }() + try { if let v = self._index { + try visitor.visitSingularInt32Field(value: v, fieldNumber: 4) + } }() try unknownFields.traverse(visitor: &visitor) } @@ -2535,6 +3714,7 @@ extension Location_Nearby_Connections_PayloadTransferFrame.PayloadChunk: SwiftPr if lhs._flags != rhs._flags {return false} if lhs._offset != rhs._offset {return false} if lhs._body != rhs._body {return false} + if lhs._index != rhs._index {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -2604,6 +3784,7 @@ extension Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame: SwiftPro 2: .standard(proto: "upgrade_path_info"), 3: .standard(proto: "client_introduction"), 4: .standard(proto: "client_introduction_ack"), + 5: .standard(proto: "safe_to_close_prior_channel"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -2616,6 +3797,7 @@ extension Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame: SwiftPro case 2: try { try decoder.decodeSingularMessageField(value: &self._upgradePathInfo) }() case 3: try { try decoder.decodeSingularMessageField(value: &self._clientIntroduction) }() case 4: try { try decoder.decodeSingularMessageField(value: &self._clientIntroductionAck) }() + case 5: try { try decoder.decodeSingularMessageField(value: &self._safeToClosePriorChannel) }() default: break } } @@ -2638,6 +3820,9 @@ extension Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame: SwiftPro try { if let v = self._clientIntroductionAck { try visitor.visitSingularMessageField(value: v, fieldNumber: 4) } }() + try { if let v = self._safeToClosePriorChannel { + try visitor.visitSingularMessageField(value: v, fieldNumber: 5) + } }() try unknownFields.traverse(visitor: &visitor) } @@ -2646,6 +3831,7 @@ extension Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame: SwiftPro if lhs._upgradePathInfo != rhs._upgradePathInfo {return false} if lhs._clientIntroduction != rhs._clientIntroduction {return false} if lhs._clientIntroductionAck != rhs._clientIntroductionAck {return false} + if lhs._safeToClosePriorChannel != rhs._safeToClosePriorChannel {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -2660,6 +3846,7 @@ extension Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.EventType 4: .same(proto: "CLIENT_INTRODUCTION"), 5: .same(proto: "UPGRADE_FAILURE"), 6: .same(proto: "CLIENT_INTRODUCTION_ACK"), + 7: .same(proto: "UPGRADE_PATH_REQUEST"), ] } @@ -2673,8 +3860,10 @@ extension Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.UpgradePa 5: .standard(proto: "wifi_aware_credentials"), 6: .standard(proto: "wifi_direct_credentials"), 8: .standard(proto: "web_rtc_credentials"), + 11: .standard(proto: "awdl_credentials"), 7: .standard(proto: "supports_disabling_encryption"), 9: .standard(proto: "supports_client_introduction_ack"), + 10: .standard(proto: "upgrade_path_request"), ] fileprivate class _StorageClass { @@ -2685,8 +3874,10 @@ extension Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.UpgradePa var _wifiAwareCredentials: Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.UpgradePathInfo.WifiAwareCredentials? = nil var _wifiDirectCredentials: Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.UpgradePathInfo.WifiDirectCredentials? = nil var _webRtcCredentials: Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.UpgradePathInfo.WebRtcCredentials? = nil + var _awdlCredentials: Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.UpgradePathInfo.AwdlCredentials? = nil var _supportsDisablingEncryption: Bool? = nil var _supportsClientIntroductionAck: Bool? = nil + var _upgradePathRequest: Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.UpgradePathInfo.UpgradePathRequest? = nil static let defaultInstance = _StorageClass() @@ -2700,8 +3891,10 @@ extension Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.UpgradePa _wifiAwareCredentials = source._wifiAwareCredentials _wifiDirectCredentials = source._wifiDirectCredentials _webRtcCredentials = source._webRtcCredentials + _awdlCredentials = source._awdlCredentials _supportsDisablingEncryption = source._supportsDisablingEncryption _supportsClientIntroductionAck = source._supportsClientIntroductionAck + _upgradePathRequest = source._upgradePathRequest } } @@ -2729,6 +3922,8 @@ extension Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.UpgradePa case 7: try { try decoder.decodeSingularBoolField(value: &_storage._supportsDisablingEncryption) }() case 8: try { try decoder.decodeSingularMessageField(value: &_storage._webRtcCredentials) }() case 9: try { try decoder.decodeSingularBoolField(value: &_storage._supportsClientIntroductionAck) }() + case 10: try { try decoder.decodeSingularMessageField(value: &_storage._upgradePathRequest) }() + case 11: try { try decoder.decodeSingularMessageField(value: &_storage._awdlCredentials) }() default: break } } @@ -2768,6 +3963,12 @@ extension Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.UpgradePa try { if let v = _storage._supportsClientIntroductionAck { try visitor.visitSingularBoolField(value: v, fieldNumber: 9) } }() + try { if let v = _storage._upgradePathRequest { + try visitor.visitSingularMessageField(value: v, fieldNumber: 10) + } }() + try { if let v = _storage._awdlCredentials { + try visitor.visitSingularMessageField(value: v, fieldNumber: 11) + } }() } try unknownFields.traverse(visitor: &visitor) } @@ -2784,8 +3985,10 @@ extension Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.UpgradePa if _storage._wifiAwareCredentials != rhs_storage._wifiAwareCredentials {return false} if _storage._wifiDirectCredentials != rhs_storage._wifiDirectCredentials {return false} if _storage._webRtcCredentials != rhs_storage._webRtcCredentials {return false} + if _storage._awdlCredentials != rhs_storage._awdlCredentials {return false} if _storage._supportsDisablingEncryption != rhs_storage._supportsDisablingEncryption {return false} if _storage._supportsClientIntroductionAck != rhs_storage._supportsClientIntroductionAck {return false} + if _storage._upgradePathRequest != rhs_storage._upgradePathRequest {return false} return true } if !storagesAreEqual {return false} @@ -2808,6 +4011,8 @@ extension Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.UpgradePa 8: .same(proto: "WIFI_DIRECT"), 9: .same(proto: "WEB_RTC"), 11: .same(proto: "USB"), + 12: .same(proto: "WEB_RTC_NON_CELLULAR"), + 13: .same(proto: "AWDL"), ] } @@ -3011,6 +4216,7 @@ extension Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.UpgradePa 3: .same(proto: "port"), 4: .same(proto: "frequency"), 5: .same(proto: "gateway"), + 6: .standard(proto: "ip_v6_address"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -3024,6 +4230,7 @@ extension Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.UpgradePa case 3: try { try decoder.decodeSingularInt32Field(value: &self._port) }() case 4: try { try decoder.decodeSingularInt32Field(value: &self._frequency) }() case 5: try { try decoder.decodeSingularStringField(value: &self._gateway) }() + case 6: try { try decoder.decodeSingularBytesField(value: &self._ipV6Address) }() default: break } } @@ -3049,6 +4256,9 @@ extension Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.UpgradePa try { if let v = self._gateway { try visitor.visitSingularStringField(value: v, fieldNumber: 5) } }() + try { if let v = self._ipV6Address { + try visitor.visitSingularBytesField(value: v, fieldNumber: 6) + } }() try unknownFields.traverse(visitor: &visitor) } @@ -3058,6 +4268,7 @@ extension Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.UpgradePa if lhs._port != rhs._port {return false} if lhs._frequency != rhs._frequency {return false} if lhs._gateway != rhs._gateway {return false} + if lhs._ipV6Address != rhs._ipV6Address {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -3105,11 +4316,138 @@ extension Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.UpgradePa } } +extension Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.UpgradePathInfo.AwdlCredentials: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.UpgradePathInfo.protoMessageName + ".AwdlCredentials" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "service_name"), + 2: .standard(proto: "service_type"), + 3: .same(proto: "password"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularStringField(value: &self._serviceName) }() + case 2: try { try decoder.decodeSingularStringField(value: &self._serviceType) }() + case 3: try { try decoder.decodeSingularStringField(value: &self._password) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._serviceName { + try visitor.visitSingularStringField(value: v, fieldNumber: 1) + } }() + try { if let v = self._serviceType { + try visitor.visitSingularStringField(value: v, fieldNumber: 2) + } }() + try { if let v = self._password { + try visitor.visitSingularStringField(value: v, fieldNumber: 3) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.UpgradePathInfo.AwdlCredentials, rhs: Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.UpgradePathInfo.AwdlCredentials) -> Bool { + if lhs._serviceName != rhs._serviceName {return false} + if lhs._serviceType != rhs._serviceType {return false} + if lhs._password != rhs._password {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.UpgradePathInfo.UpgradePathRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.UpgradePathInfo.protoMessageName + ".UpgradePathRequest" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "mediums"), + 2: .standard(proto: "medium_meta_data"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeRepeatedEnumField(value: &self.mediums) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._mediumMetaData) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if !self.mediums.isEmpty { + try visitor.visitPackedEnumField(value: self.mediums, fieldNumber: 1) + } + try { if let v = self._mediumMetaData { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.UpgradePathInfo.UpgradePathRequest, rhs: Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.UpgradePathInfo.UpgradePathRequest) -> Bool { + if lhs.mediums != rhs.mediums {return false} + if lhs._mediumMetaData != rhs._mediumMetaData {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.SafeToClosePriorChannel: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.protoMessageName + ".SafeToClosePriorChannel" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "sta_frequency"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularInt32Field(value: &self._staFrequency) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._staFrequency { + try visitor.visitSingularInt32Field(value: v, fieldNumber: 1) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.SafeToClosePriorChannel, rhs: Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.SafeToClosePriorChannel) -> Bool { + if lhs._staFrequency != rhs._staFrequency {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + extension Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.ClientIntroduction: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { static let protoMessageName: String = Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.protoMessageName + ".ClientIntroduction" static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .standard(proto: "endpoint_id"), 2: .standard(proto: "supports_disabling_encryption"), + 3: .standard(proto: "last_endpoint_id"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -3120,6 +4458,7 @@ extension Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.ClientInt switch fieldNumber { case 1: try { try decoder.decodeSingularStringField(value: &self._endpointID) }() case 2: try { try decoder.decodeSingularBoolField(value: &self._supportsDisablingEncryption) }() + case 3: try { try decoder.decodeSingularStringField(value: &self._lastEndpointID) }() default: break } } @@ -3136,12 +4475,16 @@ extension Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.ClientInt try { if let v = self._supportsDisablingEncryption { try visitor.visitSingularBoolField(value: v, fieldNumber: 2) } }() + try { if let v = self._lastEndpointID { + try visitor.visitSingularStringField(value: v, fieldNumber: 3) + } }() try unknownFields.traverse(visitor: &visitor) } static func ==(lhs: Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.ClientIntroduction, rhs: Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.ClientIntroduction) -> Bool { if lhs._endpointID != rhs._endpointID {return false} if lhs._supportsDisablingEncryption != rhs._supportsDisablingEncryption {return false} + if lhs._lastEndpointID != rhs._lastEndpointID {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -3166,10 +4509,71 @@ extension Location_Nearby_Connections_BandwidthUpgradeNegotiationFrame.ClientInt } } +extension Location_Nearby_Connections_BandwidthUpgradeRetryFrame: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".BandwidthUpgradeRetryFrame" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "supported_medium"), + 2: .standard(proto: "is_request"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeRepeatedEnumField(value: &self.supportedMedium) }() + case 2: try { try decoder.decodeSingularBoolField(value: &self._isRequest) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + if !self.supportedMedium.isEmpty { + try visitor.visitRepeatedEnumField(value: self.supportedMedium, fieldNumber: 1) + } + try { if let v = self._isRequest { + try visitor.visitSingularBoolField(value: v, fieldNumber: 2) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Location_Nearby_Connections_BandwidthUpgradeRetryFrame, rhs: Location_Nearby_Connections_BandwidthUpgradeRetryFrame) -> Bool { + if lhs.supportedMedium != rhs.supportedMedium {return false} + if lhs._isRequest != rhs._isRequest {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Location_Nearby_Connections_BandwidthUpgradeRetryFrame.Medium: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "UNKNOWN_MEDIUM"), + 2: .same(proto: "BLUETOOTH"), + 3: .same(proto: "WIFI_HOTSPOT"), + 4: .same(proto: "BLE"), + 5: .same(proto: "WIFI_LAN"), + 6: .same(proto: "WIFI_AWARE"), + 7: .same(proto: "NFC"), + 8: .same(proto: "WIFI_DIRECT"), + 9: .same(proto: "WEB_RTC"), + 10: .same(proto: "BLE_L2CAP"), + 11: .same(proto: "USB"), + 12: .same(proto: "WEB_RTC_NON_CELLULAR"), + 13: .same(proto: "AWDL"), + ] +} + extension Location_Nearby_Connections_KeepAliveFrame: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { static let protoMessageName: String = _protobuf_package + ".KeepAliveFrame" static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "ack"), + 2: .standard(proto: "seq_num"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -3179,6 +4583,7 @@ extension Location_Nearby_Connections_KeepAliveFrame: SwiftProtobuf.Message, Swi // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { case 1: try { try decoder.decodeSingularBoolField(value: &self._ack) }() + case 2: try { try decoder.decodeSingularUInt32Field(value: &self._seqNum) }() default: break } } @@ -3192,11 +4597,15 @@ extension Location_Nearby_Connections_KeepAliveFrame: SwiftProtobuf.Message, Swi try { if let v = self._ack { try visitor.visitSingularBoolField(value: v, fieldNumber: 1) } }() + try { if let v = self._seqNum { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 2) + } }() try unknownFields.traverse(visitor: &visitor) } static func ==(lhs: Location_Nearby_Connections_KeepAliveFrame, rhs: Location_Nearby_Connections_KeepAliveFrame) -> Bool { if lhs._ack != rhs._ack {return false} + if lhs._seqNum != rhs._seqNum {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -3280,6 +4689,196 @@ extension Location_Nearby_Connections_PairedKeyEncryptionFrame: SwiftProtobuf.Me } } +extension Location_Nearby_Connections_AuthenticationMessageFrame: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".AuthenticationMessageFrame" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "auth_message"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularBytesField(value: &self._authMessage) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._authMessage { + try visitor.visitSingularBytesField(value: v, fieldNumber: 1) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Location_Nearby_Connections_AuthenticationMessageFrame, rhs: Location_Nearby_Connections_AuthenticationMessageFrame) -> Bool { + if lhs._authMessage != rhs._authMessage {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Location_Nearby_Connections_AuthenticationResultFrame: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".AuthenticationResultFrame" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "result"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularInt32Field(value: &self._result) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._result { + try visitor.visitSingularInt32Field(value: v, fieldNumber: 1) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Location_Nearby_Connections_AuthenticationResultFrame, rhs: Location_Nearby_Connections_AuthenticationResultFrame) -> Bool { + if lhs._result != rhs._result {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Location_Nearby_Connections_AutoResumeFrame: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".AutoResumeFrame" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "event_type"), + 2: .standard(proto: "pending_payload_id"), + 3: .standard(proto: "next_payload_chunk_index"), + 4: .same(proto: "version"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularEnumField(value: &self._eventType) }() + case 2: try { try decoder.decodeSingularInt64Field(value: &self._pendingPayloadID) }() + case 3: try { try decoder.decodeSingularInt32Field(value: &self._nextPayloadChunkIndex) }() + case 4: try { try decoder.decodeSingularInt32Field(value: &self._version) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._eventType { + try visitor.visitSingularEnumField(value: v, fieldNumber: 1) + } }() + try { if let v = self._pendingPayloadID { + try visitor.visitSingularInt64Field(value: v, fieldNumber: 2) + } }() + try { if let v = self._nextPayloadChunkIndex { + try visitor.visitSingularInt32Field(value: v, fieldNumber: 3) + } }() + try { if let v = self._version { + try visitor.visitSingularInt32Field(value: v, fieldNumber: 4) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Location_Nearby_Connections_AutoResumeFrame, rhs: Location_Nearby_Connections_AutoResumeFrame) -> Bool { + if lhs._eventType != rhs._eventType {return false} + if lhs._pendingPayloadID != rhs._pendingPayloadID {return false} + if lhs._nextPayloadChunkIndex != rhs._nextPayloadChunkIndex {return false} + if lhs._version != rhs._version {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Location_Nearby_Connections_AutoResumeFrame.EventType: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "UNKNOWN_AUTO_RESUME_EVENT_TYPE"), + 1: .same(proto: "PAYLOAD_RESUME_TRANSFER_START"), + 2: .same(proto: "PAYLOAD_RESUME_TRANSFER_ACK"), + ] +} + +extension Location_Nearby_Connections_AutoReconnectFrame: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".AutoReconnectFrame" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "endpoint_id"), + 2: .standard(proto: "event_type"), + 3: .standard(proto: "last_endpoint_id"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularStringField(value: &self._endpointID) }() + case 2: try { try decoder.decodeSingularEnumField(value: &self._eventType) }() + case 3: try { try decoder.decodeSingularStringField(value: &self._lastEndpointID) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._endpointID { + try visitor.visitSingularStringField(value: v, fieldNumber: 1) + } }() + try { if let v = self._eventType { + try visitor.visitSingularEnumField(value: v, fieldNumber: 2) + } }() + try { if let v = self._lastEndpointID { + try visitor.visitSingularStringField(value: v, fieldNumber: 3) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Location_Nearby_Connections_AutoReconnectFrame, rhs: Location_Nearby_Connections_AutoReconnectFrame) -> Bool { + if lhs._endpointID != rhs._endpointID {return false} + if lhs._eventType != rhs._eventType {return false} + if lhs._lastEndpointID != rhs._lastEndpointID {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Location_Nearby_Connections_AutoReconnectFrame.EventType: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "UNKNOWN_EVENT_TYPE"), + 1: .same(proto: "CLIENT_INTRODUCTION"), + 2: .same(proto: "CLIENT_INTRODUCTION_ACK"), + ] +} + extension Location_Nearby_Connections_MediumMetadata: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { static let protoMessageName: String = _protobuf_package + ".MediumMetadata" static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ @@ -3294,83 +4893,143 @@ extension Location_Nearby_Connections_MediumMetadata: SwiftProtobuf.Message, Swi 9: .standard(proto: "wifi_lan_usable_channels"), 10: .standard(proto: "wifi_aware_usable_channels"), 11: .standard(proto: "wifi_hotspot_sta_usable_channels"), + 12: .standard(proto: "medium_role"), ] + fileprivate class _StorageClass { + var _supports5Ghz: Bool? = nil + var _bssid: String? = nil + var _ipAddress: Data? = nil + var _supports6Ghz: Bool? = nil + var _mobileRadio: Bool? = nil + var _apFrequency: Int32? = nil + var _availableChannels: Location_Nearby_Connections_AvailableChannels? = nil + var _wifiDirectCliUsableChannels: Location_Nearby_Connections_WifiDirectCliUsableChannels? = nil + var _wifiLanUsableChannels: Location_Nearby_Connections_WifiLanUsableChannels? = nil + var _wifiAwareUsableChannels: Location_Nearby_Connections_WifiAwareUsableChannels? = nil + var _wifiHotspotStaUsableChannels: Location_Nearby_Connections_WifiHotspotStaUsableChannels? = nil + var _mediumRole: Location_Nearby_Connections_MediumRole? = nil + + static let defaultInstance = _StorageClass() + + private init() {} + + init(copying source: _StorageClass) { + _supports5Ghz = source._supports5Ghz + _bssid = source._bssid + _ipAddress = source._ipAddress + _supports6Ghz = source._supports6Ghz + _mobileRadio = source._mobileRadio + _apFrequency = source._apFrequency + _availableChannels = source._availableChannels + _wifiDirectCliUsableChannels = source._wifiDirectCliUsableChannels + _wifiLanUsableChannels = source._wifiLanUsableChannels + _wifiAwareUsableChannels = source._wifiAwareUsableChannels + _wifiHotspotStaUsableChannels = source._wifiHotspotStaUsableChannels + _mediumRole = source._mediumRole + } + } + + fileprivate mutating func _uniqueStorage() -> _StorageClass { + if !isKnownUniquelyReferenced(&_storage) { + _storage = _StorageClass(copying: _storage) + } + return _storage + } + mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularBoolField(value: &self._supports5Ghz) }() - case 2: try { try decoder.decodeSingularStringField(value: &self._bssid) }() - case 3: try { try decoder.decodeSingularBytesField(value: &self._ipAddress) }() - case 4: try { try decoder.decodeSingularBoolField(value: &self._supports6Ghz) }() - case 5: try { try decoder.decodeSingularBoolField(value: &self._mobileRadio) }() - case 6: try { try decoder.decodeSingularInt32Field(value: &self._apFrequency) }() - case 7: try { try decoder.decodeSingularMessageField(value: &self._availableChannels) }() - case 8: try { try decoder.decodeSingularMessageField(value: &self._wifiDirectCliUsableChannels) }() - case 9: try { try decoder.decodeSingularMessageField(value: &self._wifiLanUsableChannels) }() - case 10: try { try decoder.decodeSingularMessageField(value: &self._wifiAwareUsableChannels) }() - case 11: try { try decoder.decodeSingularMessageField(value: &self._wifiHotspotStaUsableChannels) }() - default: break + _ = _uniqueStorage() + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularBoolField(value: &_storage._supports5Ghz) }() + case 2: try { try decoder.decodeSingularStringField(value: &_storage._bssid) }() + case 3: try { try decoder.decodeSingularBytesField(value: &_storage._ipAddress) }() + case 4: try { try decoder.decodeSingularBoolField(value: &_storage._supports6Ghz) }() + case 5: try { try decoder.decodeSingularBoolField(value: &_storage._mobileRadio) }() + case 6: try { try decoder.decodeSingularInt32Field(value: &_storage._apFrequency) }() + case 7: try { try decoder.decodeSingularMessageField(value: &_storage._availableChannels) }() + case 8: try { try decoder.decodeSingularMessageField(value: &_storage._wifiDirectCliUsableChannels) }() + case 9: try { try decoder.decodeSingularMessageField(value: &_storage._wifiLanUsableChannels) }() + case 10: try { try decoder.decodeSingularMessageField(value: &_storage._wifiAwareUsableChannels) }() + case 11: try { try decoder.decodeSingularMessageField(value: &_storage._wifiHotspotStaUsableChannels) }() + case 12: try { try decoder.decodeSingularMessageField(value: &_storage._mediumRole) }() + default: break + } } } } func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - try { if let v = self._supports5Ghz { - try visitor.visitSingularBoolField(value: v, fieldNumber: 1) - } }() - try { if let v = self._bssid { - try visitor.visitSingularStringField(value: v, fieldNumber: 2) - } }() - try { if let v = self._ipAddress { - try visitor.visitSingularBytesField(value: v, fieldNumber: 3) - } }() - try { if let v = self._supports6Ghz { - try visitor.visitSingularBoolField(value: v, fieldNumber: 4) - } }() - try { if let v = self._mobileRadio { - try visitor.visitSingularBoolField(value: v, fieldNumber: 5) - } }() - try { if let v = self._apFrequency { - try visitor.visitSingularInt32Field(value: v, fieldNumber: 6) - } }() - try { if let v = self._availableChannels { - try visitor.visitSingularMessageField(value: v, fieldNumber: 7) - } }() - try { if let v = self._wifiDirectCliUsableChannels { - try visitor.visitSingularMessageField(value: v, fieldNumber: 8) - } }() - try { if let v = self._wifiLanUsableChannels { - try visitor.visitSingularMessageField(value: v, fieldNumber: 9) - } }() - try { if let v = self._wifiAwareUsableChannels { - try visitor.visitSingularMessageField(value: v, fieldNumber: 10) - } }() - try { if let v = self._wifiHotspotStaUsableChannels { - try visitor.visitSingularMessageField(value: v, fieldNumber: 11) - } }() + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = _storage._supports5Ghz { + try visitor.visitSingularBoolField(value: v, fieldNumber: 1) + } }() + try { if let v = _storage._bssid { + try visitor.visitSingularStringField(value: v, fieldNumber: 2) + } }() + try { if let v = _storage._ipAddress { + try visitor.visitSingularBytesField(value: v, fieldNumber: 3) + } }() + try { if let v = _storage._supports6Ghz { + try visitor.visitSingularBoolField(value: v, fieldNumber: 4) + } }() + try { if let v = _storage._mobileRadio { + try visitor.visitSingularBoolField(value: v, fieldNumber: 5) + } }() + try { if let v = _storage._apFrequency { + try visitor.visitSingularInt32Field(value: v, fieldNumber: 6) + } }() + try { if let v = _storage._availableChannels { + try visitor.visitSingularMessageField(value: v, fieldNumber: 7) + } }() + try { if let v = _storage._wifiDirectCliUsableChannels { + try visitor.visitSingularMessageField(value: v, fieldNumber: 8) + } }() + try { if let v = _storage._wifiLanUsableChannels { + try visitor.visitSingularMessageField(value: v, fieldNumber: 9) + } }() + try { if let v = _storage._wifiAwareUsableChannels { + try visitor.visitSingularMessageField(value: v, fieldNumber: 10) + } }() + try { if let v = _storage._wifiHotspotStaUsableChannels { + try visitor.visitSingularMessageField(value: v, fieldNumber: 11) + } }() + try { if let v = _storage._mediumRole { + try visitor.visitSingularMessageField(value: v, fieldNumber: 12) + } }() + } try unknownFields.traverse(visitor: &visitor) } static func ==(lhs: Location_Nearby_Connections_MediumMetadata, rhs: Location_Nearby_Connections_MediumMetadata) -> Bool { - if lhs._supports5Ghz != rhs._supports5Ghz {return false} - if lhs._bssid != rhs._bssid {return false} - if lhs._ipAddress != rhs._ipAddress {return false} - if lhs._supports6Ghz != rhs._supports6Ghz {return false} - if lhs._mobileRadio != rhs._mobileRadio {return false} - if lhs._apFrequency != rhs._apFrequency {return false} - if lhs._availableChannels != rhs._availableChannels {return false} - if lhs._wifiDirectCliUsableChannels != rhs._wifiDirectCliUsableChannels {return false} - if lhs._wifiLanUsableChannels != rhs._wifiLanUsableChannels {return false} - if lhs._wifiAwareUsableChannels != rhs._wifiAwareUsableChannels {return false} - if lhs._wifiHotspotStaUsableChannels != rhs._wifiHotspotStaUsableChannels {return false} + if lhs._storage !== rhs._storage { + let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in + let _storage = _args.0 + let rhs_storage = _args.1 + if _storage._supports5Ghz != rhs_storage._supports5Ghz {return false} + if _storage._bssid != rhs_storage._bssid {return false} + if _storage._ipAddress != rhs_storage._ipAddress {return false} + if _storage._supports6Ghz != rhs_storage._supports6Ghz {return false} + if _storage._mobileRadio != rhs_storage._mobileRadio {return false} + if _storage._apFrequency != rhs_storage._apFrequency {return false} + if _storage._availableChannels != rhs_storage._availableChannels {return false} + if _storage._wifiDirectCliUsableChannels != rhs_storage._wifiDirectCliUsableChannels {return false} + if _storage._wifiLanUsableChannels != rhs_storage._wifiLanUsableChannels {return false} + if _storage._wifiAwareUsableChannels != rhs_storage._wifiAwareUsableChannels {return false} + if _storage._wifiHotspotStaUsableChannels != rhs_storage._wifiHotspotStaUsableChannels {return false} + if _storage._mediumRole != rhs_storage._mediumRole {return false} + return true + } + if !storagesAreEqual {return false} + } if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -3536,6 +5195,84 @@ extension Location_Nearby_Connections_WifiHotspotStaUsableChannels: SwiftProtobu } } +extension Location_Nearby_Connections_MediumRole: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".MediumRole" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "support_wifi_direct_group_owner"), + 2: .standard(proto: "support_wifi_direct_group_client"), + 3: .standard(proto: "support_wifi_hotspot_host"), + 4: .standard(proto: "support_wifi_hotspot_client"), + 5: .standard(proto: "support_wifi_aware_publisher"), + 6: .standard(proto: "support_wifi_aware_subscriber"), + 7: .standard(proto: "support_awdl_publisher"), + 8: .standard(proto: "support_awdl_subscriber"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularBoolField(value: &self._supportWifiDirectGroupOwner) }() + case 2: try { try decoder.decodeSingularBoolField(value: &self._supportWifiDirectGroupClient) }() + case 3: try { try decoder.decodeSingularBoolField(value: &self._supportWifiHotspotHost) }() + case 4: try { try decoder.decodeSingularBoolField(value: &self._supportWifiHotspotClient) }() + case 5: try { try decoder.decodeSingularBoolField(value: &self._supportWifiAwarePublisher) }() + case 6: try { try decoder.decodeSingularBoolField(value: &self._supportWifiAwareSubscriber) }() + case 7: try { try decoder.decodeSingularBoolField(value: &self._supportAwdlPublisher) }() + case 8: try { try decoder.decodeSingularBoolField(value: &self._supportAwdlSubscriber) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._supportWifiDirectGroupOwner { + try visitor.visitSingularBoolField(value: v, fieldNumber: 1) + } }() + try { if let v = self._supportWifiDirectGroupClient { + try visitor.visitSingularBoolField(value: v, fieldNumber: 2) + } }() + try { if let v = self._supportWifiHotspotHost { + try visitor.visitSingularBoolField(value: v, fieldNumber: 3) + } }() + try { if let v = self._supportWifiHotspotClient { + try visitor.visitSingularBoolField(value: v, fieldNumber: 4) + } }() + try { if let v = self._supportWifiAwarePublisher { + try visitor.visitSingularBoolField(value: v, fieldNumber: 5) + } }() + try { if let v = self._supportWifiAwareSubscriber { + try visitor.visitSingularBoolField(value: v, fieldNumber: 6) + } }() + try { if let v = self._supportAwdlPublisher { + try visitor.visitSingularBoolField(value: v, fieldNumber: 7) + } }() + try { if let v = self._supportAwdlSubscriber { + try visitor.visitSingularBoolField(value: v, fieldNumber: 8) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Location_Nearby_Connections_MediumRole, rhs: Location_Nearby_Connections_MediumRole) -> Bool { + if lhs._supportWifiDirectGroupOwner != rhs._supportWifiDirectGroupOwner {return false} + if lhs._supportWifiDirectGroupClient != rhs._supportWifiDirectGroupClient {return false} + if lhs._supportWifiHotspotHost != rhs._supportWifiHotspotHost {return false} + if lhs._supportWifiHotspotClient != rhs._supportWifiHotspotClient {return false} + if lhs._supportWifiAwarePublisher != rhs._supportWifiAwarePublisher {return false} + if lhs._supportWifiAwareSubscriber != rhs._supportWifiAwareSubscriber {return false} + if lhs._supportAwdlPublisher != rhs._supportAwdlPublisher {return false} + if lhs._supportAwdlSubscriber != rhs._supportAwdlSubscriber {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + extension Location_Nearby_Connections_LocationHint: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { static let protoMessageName: String = _protobuf_package + ".LocationHint" static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ @@ -3651,3 +5388,159 @@ extension Location_Nearby_Connections_OsInfo.OsType: SwiftProtobuf._ProtoNamePro 100: .same(proto: "LINUX"), ] } + +extension Location_Nearby_Connections_ConnectionsDevice: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".ConnectionsDevice" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "endpoint_id"), + 2: .standard(proto: "endpoint_type"), + 3: .standard(proto: "connectivity_info_list"), + 4: .standard(proto: "endpoint_info"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularStringField(value: &self._endpointID) }() + case 2: try { try decoder.decodeSingularEnumField(value: &self._endpointType) }() + case 3: try { try decoder.decodeSingularBytesField(value: &self._connectivityInfoList) }() + case 4: try { try decoder.decodeSingularBytesField(value: &self._endpointInfo) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._endpointID { + try visitor.visitSingularStringField(value: v, fieldNumber: 1) + } }() + try { if let v = self._endpointType { + try visitor.visitSingularEnumField(value: v, fieldNumber: 2) + } }() + try { if let v = self._connectivityInfoList { + try visitor.visitSingularBytesField(value: v, fieldNumber: 3) + } }() + try { if let v = self._endpointInfo { + try visitor.visitSingularBytesField(value: v, fieldNumber: 4) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Location_Nearby_Connections_ConnectionsDevice, rhs: Location_Nearby_Connections_ConnectionsDevice) -> Bool { + if lhs._endpointID != rhs._endpointID {return false} + if lhs._endpointType != rhs._endpointType {return false} + if lhs._connectivityInfoList != rhs._connectivityInfoList {return false} + if lhs._endpointInfo != rhs._endpointInfo {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Location_Nearby_Connections_PresenceDevice: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".PresenceDevice" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "endpoint_id"), + 2: .standard(proto: "endpoint_type"), + 3: .standard(proto: "connectivity_info_list"), + 4: .standard(proto: "device_id"), + 5: .standard(proto: "device_name"), + 6: .standard(proto: "device_type"), + 7: .standard(proto: "device_image_url"), + 8: .standard(proto: "discovery_medium"), + 9: .same(proto: "actions"), + 10: .standard(proto: "identity_type"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularStringField(value: &self._endpointID) }() + case 2: try { try decoder.decodeSingularEnumField(value: &self._endpointType) }() + case 3: try { try decoder.decodeSingularBytesField(value: &self._connectivityInfoList) }() + case 4: try { try decoder.decodeSingularInt64Field(value: &self._deviceID) }() + case 5: try { try decoder.decodeSingularStringField(value: &self._deviceName) }() + case 6: try { try decoder.decodeSingularEnumField(value: &self._deviceType) }() + case 7: try { try decoder.decodeSingularStringField(value: &self._deviceImageURL) }() + case 8: try { try decoder.decodeRepeatedEnumField(value: &self.discoveryMedium) }() + case 9: try { try decoder.decodeRepeatedInt32Field(value: &self.actions) }() + case 10: try { try decoder.decodeRepeatedInt64Field(value: &self.identityType) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._endpointID { + try visitor.visitSingularStringField(value: v, fieldNumber: 1) + } }() + try { if let v = self._endpointType { + try visitor.visitSingularEnumField(value: v, fieldNumber: 2) + } }() + try { if let v = self._connectivityInfoList { + try visitor.visitSingularBytesField(value: v, fieldNumber: 3) + } }() + try { if let v = self._deviceID { + try visitor.visitSingularInt64Field(value: v, fieldNumber: 4) + } }() + try { if let v = self._deviceName { + try visitor.visitSingularStringField(value: v, fieldNumber: 5) + } }() + try { if let v = self._deviceType { + try visitor.visitSingularEnumField(value: v, fieldNumber: 6) + } }() + try { if let v = self._deviceImageURL { + try visitor.visitSingularStringField(value: v, fieldNumber: 7) + } }() + if !self.discoveryMedium.isEmpty { + try visitor.visitPackedEnumField(value: self.discoveryMedium, fieldNumber: 8) + } + if !self.actions.isEmpty { + try visitor.visitPackedInt32Field(value: self.actions, fieldNumber: 9) + } + if !self.identityType.isEmpty { + try visitor.visitPackedInt64Field(value: self.identityType, fieldNumber: 10) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Location_Nearby_Connections_PresenceDevice, rhs: Location_Nearby_Connections_PresenceDevice) -> Bool { + if lhs._endpointID != rhs._endpointID {return false} + if lhs._endpointType != rhs._endpointType {return false} + if lhs._connectivityInfoList != rhs._connectivityInfoList {return false} + if lhs._deviceID != rhs._deviceID {return false} + if lhs._deviceName != rhs._deviceName {return false} + if lhs._deviceType != rhs._deviceType {return false} + if lhs._deviceImageURL != rhs._deviceImageURL {return false} + if lhs.discoveryMedium != rhs.discoveryMedium {return false} + if lhs.actions != rhs.actions {return false} + if lhs.identityType != rhs.identityType {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Location_Nearby_Connections_PresenceDevice.DeviceType: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "UNKNOWN"), + 1: .same(proto: "PHONE"), + 2: .same(proto: "TABLET"), + 3: .same(proto: "DISPLAY"), + 4: .same(proto: "LAPTOP"), + 5: .same(proto: "TV"), + 6: .same(proto: "WATCH"), + ] +} diff --git a/NearbyShare/Model/Protobuf/sharing_enums.pb.swift b/NearbyShare/Model/Protobuf/sharing_enums.pb.swift new file mode 100644 index 0000000..3c891db --- /dev/null +++ b/NearbyShare/Model/Protobuf/sharing_enums.pb.swift @@ -0,0 +1,3468 @@ +// DO NOT EDIT. +// swift-format-ignore-file +// +// Generated by the Swift generator plugin for the protocol buffer compiler. +// Source: sharing_enums.proto +// +// For information on using the generated types, please see the documentation: +// https://github.com/apple/swift-protobuf/ + +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import Foundation +import SwiftProtobuf + +// If the compiler emits an error on this type, it is because this file +// was generated by a version of the `protoc` Swift plug-in that is +// incompatible with the version of SwiftProtobuf to which you are linking. +// Please ensure that you are building against the same version of the API +// that was used to generate this file. +fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { + struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} + typealias Version = _2 +} + +/// We use event based logging (an event object can be constructed and logged +/// immediately when they occur). To obtain session based information (e.g. +/// durations, counting incoming introductions), we use flowId (sender/receiver) +/// in NearbyClearcutLogger (for android, or clearcut_event_logger as the +/// equivalence for Windows) for all events (may exclude settings), and +/// session_id for a pair of events (start and end of a session). +/// Next id: 74 +enum Location_Nearby_Proto_Sharing_EventType: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknownEventType // = 0 + + /// When new users accept agreements (like grant permission to contacts for + /// CONTACT_ONLY visibility) and are enrolled into Nearby Sharing. This event + /// is used to count number of new users. + case acceptAgreements // = 1 + + /// User enables/disables nearby sharing from setting or tile service. + case enableNearbySharing // = 2 + + /// User sets visibility preference from setting. + case setVisibility // = 3 + + /// Describe attachments immediately when Nearby Sharing is opened by another + /// app which is used to generate/attach attachments to be shared with other + /// devices. + case describeAttachments // = 4 + + /// Start of a scanning phase at sender. + case scanForShareTargetsStart // = 5 + + /// End of the scanning phase at sender. + case scanForShareTargetsEnd // = 6 + + /// Receiver advertises itself for presence (a pseudo session). + case advertiseDevicePresenceStart // = 7 + + /// End of the advertising phase at receiver. + case advertiseDevicePresenceEnd // = 8 + + /// Sender sends a fast initialization to receiver. + case sendFastInitialization // = 9 + + /// Receiver receives the fast initialization. + case receiveFastInitialization // = 10 + + /// Sender discovers a share target. + case discoverShareTarget // = 11 + + /// Sender sends introduction (before attachments being sent). + case sendIntroduction // = 12 + + /// Receiver receives introduction. + case receiveIntroduction // = 13 + + /// Receiver responds to introduction (before attachments being sent). + /// Actions: Accept, Reject, or (for some reason) Fail. + case respondToIntroduction // = 14 + + /// Start of the sending attachments phase at sender. + case sendAttachmentsStart // = 15 + + /// End of sending attachments phase at sender. + case sendAttachmentsEnd // = 16 + + /// Start of the receiving attachments phase at receiver. + case receiveAttachmentsStart // = 17 + + /// End of receiving attachments phase at receiver. + case receiveAttachmentsEnd // = 18 + + /// Sender cancels sending attachments. + case cancelSendingAttachments // = 19 + + /// Receiver cancels receiving attachments. + case cancelReceivingAttachments // = 20 + + /// Receiver opens received attachments. + case openReceivedAttachments // = 21 + + /// User opens the setup activity. + case launchSetupActivity // = 22 + + /// User adds a contact. + case addContact // = 23 + + /// User removes a contact. + case removeContact // = 24 + + /// Local devices all Fast Share server. + case fastShareServerResponse // = 25 + + /// The start of a sending session. + case sendStart // = 26 + + /// Receiver accepts a fast initialization. + case acceptFastInitialization // = 27 + + /// Set data usage preference. + case setDataUsage // = 28 + + /// Receiver dismisses a fast initialization + case dismissFastInitialization // = 29 + + /// Cancel connection. + case cancelConnection // = 30 + + /// User starts a chimera activity (e.g. ConsentsChimeraActivity, + /// DeviceVisibilityChimeraActivity...) + case launchActivity // = 31 + + /// Receiver dismisses a privacy notification. + case dismissPrivacyNotification // = 32 + + /// Receiver taps a privacy notification. + case tapPrivacyNotification // = 33 + + /// Receiver taps a help page. + case tapHelp // = 34 + + /// Receiver taps a feedback. + case tapFeedback // = 35 + + /// Receiver adds quick settings tile. + case addQuickSettingsTile // = 36 + + /// Receiver removes quick settings tile. + case removeQuickSettingsTile // = 37 + + /// Receiver phone consent clicked. + case launchPhoneConsent // = 38 + + /// Devices show a phone consent banner in Nearby Share Settings + case displayPhoneConsent // = 54 + + /// Receiver taps quick settings tile. + case tapQuickSettingsTile // = 39 + + /// Receiver Installation of APKs status. + case installApk // = 40 + + /// Receiver verification of APKs status. + case verifyApk // = 41 + + /// User starts a consent. + case launchConsent // = 42 + + /// After receiving payloads, Nearby Share still needs to transfer the payloads + /// to correct attachment formats and move files attachments from temporary + /// directory to final destination. + case processReceivedAttachmentsEnd // = 43 + + /// Toggle Show Notification setting item in Nearby Share setting. + case toggleShowNotification // = 44 + + /// Set device name + case setDeviceName // = 45 + + /// users dropped off opt-in page. + case declineAgreements // = 46 + + /// Request setting permissions (Wifi/BT/location/airplane mode). + case requestSettingPermissions // = 47 + + /// Set up a connection with the remote device. + case establishConnection // = 48 + + /// Track device states in Nearby Share setting. + case deviceSettings // = 49 + + /// Receiver auto dismisses a fast initialization notification. + case autoDismissFastInitialization // = 50 + + /// App Crash event. + /// Used only for Windows App now. + case appCrash // = 51 + + /// Sender taps the Send button in quick settings + case tapQuickSettingsFileShare // = 52 + + /// Devices show a privacy notification + case displayPrivacyNotification // = 53 + + /// Preference usage event (e.g. load/save preferences, etc). + /// Used only for Windows App now. + case preferencesUsage // = 55 + + /// Default opt in + case defaultOptIn // = 56 + + /// The result of the setup wizard flow + case setupWizard // = 57 + + /// Sender taps a QR code + case tapQrCode // = 58 + + /// QR code link shown + case qrCodeLinkShown // = 59 + + /// Sender failed to parse endpoint id. + case parsingFailedEndpointID // = 60 + + /// The device is discovered by fast initialization + case fastInitDiscoverDevice // = 61 + + /// Send desktop notification. + case sendDesktopNotification // = 62 + + /// User sets account preference + case setAccount // = 63 + + /// Decrypt certificate failure + case decryptCertificateFailure // = 64 + + /// Show allow permission auto access UI + case showAllowPermissionAutoAccess // = 65 + + /// UI events for transferring files with desktop applications. It includes + /// event types such as DESKTOP_TRANSFER_EVENT_SEND_TYPE_SELECT_A_DEVICE. + case sendDesktopTransferEvent // = 66 + + /// Show accept button on Quick Share receive UI + case waitingForAccept // = 67 + + /// High quality event setup + case highQualityMediumSetup // = 68 + + /// RPC call status + case rpcCallStatus // = 69 + + /// A QR code sharing session has started + case startQrCodeSession // = 70 + + /// A QR code URL has been opened in a web client/browser instead of in a + /// native Quick Share app. + case qrCodeOpenedInWebClient // = 71 + + /// A HaTS survey session id has been joined with Quick Share flow id. + case hatsJointEvent // = 72 + + /// Previews received. + case receivePreviews // = 73 + + init() { + self = .unknownEventType + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknownEventType + case 1: self = .acceptAgreements + case 2: self = .enableNearbySharing + case 3: self = .setVisibility + case 4: self = .describeAttachments + case 5: self = .scanForShareTargetsStart + case 6: self = .scanForShareTargetsEnd + case 7: self = .advertiseDevicePresenceStart + case 8: self = .advertiseDevicePresenceEnd + case 9: self = .sendFastInitialization + case 10: self = .receiveFastInitialization + case 11: self = .discoverShareTarget + case 12: self = .sendIntroduction + case 13: self = .receiveIntroduction + case 14: self = .respondToIntroduction + case 15: self = .sendAttachmentsStart + case 16: self = .sendAttachmentsEnd + case 17: self = .receiveAttachmentsStart + case 18: self = .receiveAttachmentsEnd + case 19: self = .cancelSendingAttachments + case 20: self = .cancelReceivingAttachments + case 21: self = .openReceivedAttachments + case 22: self = .launchSetupActivity + case 23: self = .addContact + case 24: self = .removeContact + case 25: self = .fastShareServerResponse + case 26: self = .sendStart + case 27: self = .acceptFastInitialization + case 28: self = .setDataUsage + case 29: self = .dismissFastInitialization + case 30: self = .cancelConnection + case 31: self = .launchActivity + case 32: self = .dismissPrivacyNotification + case 33: self = .tapPrivacyNotification + case 34: self = .tapHelp + case 35: self = .tapFeedback + case 36: self = .addQuickSettingsTile + case 37: self = .removeQuickSettingsTile + case 38: self = .launchPhoneConsent + case 39: self = .tapQuickSettingsTile + case 40: self = .installApk + case 41: self = .verifyApk + case 42: self = .launchConsent + case 43: self = .processReceivedAttachmentsEnd + case 44: self = .toggleShowNotification + case 45: self = .setDeviceName + case 46: self = .declineAgreements + case 47: self = .requestSettingPermissions + case 48: self = .establishConnection + case 49: self = .deviceSettings + case 50: self = .autoDismissFastInitialization + case 51: self = .appCrash + case 52: self = .tapQuickSettingsFileShare + case 53: self = .displayPrivacyNotification + case 54: self = .displayPhoneConsent + case 55: self = .preferencesUsage + case 56: self = .defaultOptIn + case 57: self = .setupWizard + case 58: self = .tapQrCode + case 59: self = .qrCodeLinkShown + case 60: self = .parsingFailedEndpointID + case 61: self = .fastInitDiscoverDevice + case 62: self = .sendDesktopNotification + case 63: self = .setAccount + case 64: self = .decryptCertificateFailure + case 65: self = .showAllowPermissionAutoAccess + case 66: self = .sendDesktopTransferEvent + case 67: self = .waitingForAccept + case 68: self = .highQualityMediumSetup + case 69: self = .rpcCallStatus + case 70: self = .startQrCodeSession + case 71: self = .qrCodeOpenedInWebClient + case 72: self = .hatsJointEvent + case 73: self = .receivePreviews + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknownEventType: return 0 + case .acceptAgreements: return 1 + case .enableNearbySharing: return 2 + case .setVisibility: return 3 + case .describeAttachments: return 4 + case .scanForShareTargetsStart: return 5 + case .scanForShareTargetsEnd: return 6 + case .advertiseDevicePresenceStart: return 7 + case .advertiseDevicePresenceEnd: return 8 + case .sendFastInitialization: return 9 + case .receiveFastInitialization: return 10 + case .discoverShareTarget: return 11 + case .sendIntroduction: return 12 + case .receiveIntroduction: return 13 + case .respondToIntroduction: return 14 + case .sendAttachmentsStart: return 15 + case .sendAttachmentsEnd: return 16 + case .receiveAttachmentsStart: return 17 + case .receiveAttachmentsEnd: return 18 + case .cancelSendingAttachments: return 19 + case .cancelReceivingAttachments: return 20 + case .openReceivedAttachments: return 21 + case .launchSetupActivity: return 22 + case .addContact: return 23 + case .removeContact: return 24 + case .fastShareServerResponse: return 25 + case .sendStart: return 26 + case .acceptFastInitialization: return 27 + case .setDataUsage: return 28 + case .dismissFastInitialization: return 29 + case .cancelConnection: return 30 + case .launchActivity: return 31 + case .dismissPrivacyNotification: return 32 + case .tapPrivacyNotification: return 33 + case .tapHelp: return 34 + case .tapFeedback: return 35 + case .addQuickSettingsTile: return 36 + case .removeQuickSettingsTile: return 37 + case .launchPhoneConsent: return 38 + case .tapQuickSettingsTile: return 39 + case .installApk: return 40 + case .verifyApk: return 41 + case .launchConsent: return 42 + case .processReceivedAttachmentsEnd: return 43 + case .toggleShowNotification: return 44 + case .setDeviceName: return 45 + case .declineAgreements: return 46 + case .requestSettingPermissions: return 47 + case .establishConnection: return 48 + case .deviceSettings: return 49 + case .autoDismissFastInitialization: return 50 + case .appCrash: return 51 + case .tapQuickSettingsFileShare: return 52 + case .displayPrivacyNotification: return 53 + case .displayPhoneConsent: return 54 + case .preferencesUsage: return 55 + case .defaultOptIn: return 56 + case .setupWizard: return 57 + case .tapQrCode: return 58 + case .qrCodeLinkShown: return 59 + case .parsingFailedEndpointID: return 60 + case .fastInitDiscoverDevice: return 61 + case .sendDesktopNotification: return 62 + case .setAccount: return 63 + case .decryptCertificateFailure: return 64 + case .showAllowPermissionAutoAccess: return 65 + case .sendDesktopTransferEvent: return 66 + case .waitingForAccept: return 67 + case .highQualityMediumSetup: return 68 + case .rpcCallStatus: return 69 + case .startQrCodeSession: return 70 + case .qrCodeOpenedInWebClient: return 71 + case .hatsJointEvent: return 72 + case .receivePreviews: return 73 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_EventType: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +/// Event category to differentiate whether this comes from sender or receiver, +/// whether this is for communication flow, or for settings. +enum Location_Nearby_Proto_Sharing_EventCategory: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknownEventCategory // = 0 + case sendingEvent // = 1 + case receivingEvent // = 2 + case settingsEvent // = 3 + case rpcEvent // = 4 + + init() { + self = .unknownEventCategory + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknownEventCategory + case 1: self = .sendingEvent + case 2: self = .receivingEvent + case 3: self = .settingsEvent + case 4: self = .rpcEvent + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknownEventCategory: return 0 + case .sendingEvent: return 1 + case .receivingEvent: return 2 + case .settingsEvent: return 3 + case .rpcEvent: return 4 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_EventCategory: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +/// Status of nearby sharing. +enum Location_Nearby_Proto_Sharing_NearbySharingStatus: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknownNearbySharingStatus // = 0 + case on // = 1 + case off // = 2 + + init() { + self = .unknownNearbySharingStatus + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknownNearbySharingStatus + case 1: self = .on + case 2: self = .off + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknownNearbySharingStatus: return 0 + case .on: return 1 + case .off: return 2 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_NearbySharingStatus: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +enum Location_Nearby_Proto_Sharing_Visibility: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknownVisibility // = 0 + case contactsOnly // = 1 + case everyone // = 2 + case selectedContactsOnly // = 3 + case hidden // = 4 + case selfShare // = 5 + + init() { + self = .unknownVisibility + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknownVisibility + case 1: self = .contactsOnly + case 2: self = .everyone + case 3: self = .selectedContactsOnly + case 4: self = .hidden + case 5: self = .selfShare + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknownVisibility: return 0 + case .contactsOnly: return 1 + case .everyone: return 2 + case .selectedContactsOnly: return 3 + case .hidden: return 4 + case .selfShare: return 5 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_Visibility: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +enum Location_Nearby_Proto_Sharing_DataUsage: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknownDataUsage // = 0 + case online // = 1 + case wifiOnly // = 2 + case offline // = 3 + + init() { + self = .unknownDataUsage + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknownDataUsage + case 1: self = .online + case 2: self = .wifiOnly + case 3: self = .offline + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknownDataUsage: return 0 + case .online: return 1 + case .wifiOnly: return 2 + case .offline: return 3 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_DataUsage: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +/// The status of establishing a connection. Used by ESTABLISH_CONNECTION. +enum Location_Nearby_Proto_Sharing_EstablishConnectionStatus: SwiftProtobuf.Enum { + typealias RawValue = Int + case connectionStatusUnknown // = 0 + case connectionStatusSuccess // = 1 + case connectionStatusFailure // = 2 + case connectionStatusCancellation // = 3 + case connectionStatusMediaUnavailableAttachment // = 4 + case connectionStatusFailedPairedKeyhandshake // = 5 + case connectionStatusFailedWriteIntroduction // = 6 + case connectionStatusFailedNullConnection // = 7 + case connectionStatusFailedNoTransferUpdateCallback // = 8 + case connectionStatusLostConnectivity // = 9 + + /// TODO: b/341782941 - : Annote this status when it's confirmed by Nearby + /// Connections team. + case connectionStatusInvalidAdvertisement // = 10 + + init() { + self = .connectionStatusUnknown + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .connectionStatusUnknown + case 1: self = .connectionStatusSuccess + case 2: self = .connectionStatusFailure + case 3: self = .connectionStatusCancellation + case 4: self = .connectionStatusMediaUnavailableAttachment + case 5: self = .connectionStatusFailedPairedKeyhandshake + case 6: self = .connectionStatusFailedWriteIntroduction + case 7: self = .connectionStatusFailedNullConnection + case 8: self = .connectionStatusFailedNoTransferUpdateCallback + case 9: self = .connectionStatusLostConnectivity + case 10: self = .connectionStatusInvalidAdvertisement + default: return nil + } + } + + var rawValue: Int { + switch self { + case .connectionStatusUnknown: return 0 + case .connectionStatusSuccess: return 1 + case .connectionStatusFailure: return 2 + case .connectionStatusCancellation: return 3 + case .connectionStatusMediaUnavailableAttachment: return 4 + case .connectionStatusFailedPairedKeyhandshake: return 5 + case .connectionStatusFailedWriteIntroduction: return 6 + case .connectionStatusFailedNullConnection: return 7 + case .connectionStatusFailedNoTransferUpdateCallback: return 8 + case .connectionStatusLostConnectivity: return 9 + case .connectionStatusInvalidAdvertisement: return 10 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_EstablishConnectionStatus: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +/// The status of sending and receiving attachments. Used by SEND_ATTACHMENTS. +enum Location_Nearby_Proto_Sharing_AttachmentTransmissionStatus: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknownAttachmentTransmissionStatus // = 0 + case completeAttachmentTransmissionStatus // = 1 + case canceledAttachmentTransmissionStatus // = 2 + case failedAttachmentTransmissionStatus // = 3 + case rejectedAttachment // = 4 + case timedOutAttachment // = 5 + case awaitingRemoteAcceptanceFailedAttachment // = 6 + case notEnoughSpaceAttachment // = 7 + case failedNoTransferUpdateCallback // = 8 + case mediaUnavailableAttachment // = 9 + case unsupportedAttachmentTypeAttachment // = 10 + case noAttachmentFound // = 11 + case failedNoShareTargetEndpoint // = 12 + case failedPairedKeyhandshake // = 13 + case failedNullConnection // = 14 + case failedNoPayload // = 15 + case failedWriteIntroduction // = 16 + + /// The remote response is either missing or has an unknown type. + case failedUnknownRemoteResponse // = 17 + + /// Breakdowns of FAILED_NULL_CONNECTION (Desktop side) + case failedNullConnectionInitOutgoing // = 18 + case failedNullConnectionDisconnected // = 19 + + /// Breakdowns of FAILED_NULL_CONNECTION (android side) + /// Connection failed due to Wifi is disconnected or Bluetooth setting is off + /// or user turn on airplane mode. + case failedNullConnectionLostConnectivity // = 20 + + /// Unexpected connection failure. + case failedNullConnectionFailure // = 21 + case rejectedAttachmentTransmissionStatus // = 22 + case timedOutAttachmentTransmissionStatus // = 23 + case notEnoughSpaceAttachmentTransmissionStatus // = 24 + case unsupportedAttachmentTypeAttachmentTransmissionStatus // = 25 + case failedUnknownRemoteResponseTransmissionStatus // = 26 + + /// Connection failed due to Wifi is disconnected or Bluetooth setting is off + /// or user turn on airplane mode. + case noResponseFrameConnectionClosedLostConnectivityTransmissionStatus // = 27 + + /// Unexpected connection failure due to no response frame. + case noResponseFrameConnectionClosedTransmissionStatus // = 28 + + /// Connection failed due to Wifi is disconnected or Bluetooth setting is off + /// or user turn on airplane mode. + case lostConnectivityTransmissionStatus // = 29 + + /// Connection failed due to the medium is not allowed. + case failedDisallowedMedium // = 30 + + init() { + self = .unknownAttachmentTransmissionStatus + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknownAttachmentTransmissionStatus + case 1: self = .completeAttachmentTransmissionStatus + case 2: self = .canceledAttachmentTransmissionStatus + case 3: self = .failedAttachmentTransmissionStatus + case 4: self = .rejectedAttachment + case 5: self = .timedOutAttachment + case 6: self = .awaitingRemoteAcceptanceFailedAttachment + case 7: self = .notEnoughSpaceAttachment + case 8: self = .failedNoTransferUpdateCallback + case 9: self = .mediaUnavailableAttachment + case 10: self = .unsupportedAttachmentTypeAttachment + case 11: self = .noAttachmentFound + case 12: self = .failedNoShareTargetEndpoint + case 13: self = .failedPairedKeyhandshake + case 14: self = .failedNullConnection + case 15: self = .failedNoPayload + case 16: self = .failedWriteIntroduction + case 17: self = .failedUnknownRemoteResponse + case 18: self = .failedNullConnectionInitOutgoing + case 19: self = .failedNullConnectionDisconnected + case 20: self = .failedNullConnectionLostConnectivity + case 21: self = .failedNullConnectionFailure + case 22: self = .rejectedAttachmentTransmissionStatus + case 23: self = .timedOutAttachmentTransmissionStatus + case 24: self = .notEnoughSpaceAttachmentTransmissionStatus + case 25: self = .unsupportedAttachmentTypeAttachmentTransmissionStatus + case 26: self = .failedUnknownRemoteResponseTransmissionStatus + case 27: self = .noResponseFrameConnectionClosedLostConnectivityTransmissionStatus + case 28: self = .noResponseFrameConnectionClosedTransmissionStatus + case 29: self = .lostConnectivityTransmissionStatus + case 30: self = .failedDisallowedMedium + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknownAttachmentTransmissionStatus: return 0 + case .completeAttachmentTransmissionStatus: return 1 + case .canceledAttachmentTransmissionStatus: return 2 + case .failedAttachmentTransmissionStatus: return 3 + case .rejectedAttachment: return 4 + case .timedOutAttachment: return 5 + case .awaitingRemoteAcceptanceFailedAttachment: return 6 + case .notEnoughSpaceAttachment: return 7 + case .failedNoTransferUpdateCallback: return 8 + case .mediaUnavailableAttachment: return 9 + case .unsupportedAttachmentTypeAttachment: return 10 + case .noAttachmentFound: return 11 + case .failedNoShareTargetEndpoint: return 12 + case .failedPairedKeyhandshake: return 13 + case .failedNullConnection: return 14 + case .failedNoPayload: return 15 + case .failedWriteIntroduction: return 16 + case .failedUnknownRemoteResponse: return 17 + case .failedNullConnectionInitOutgoing: return 18 + case .failedNullConnectionDisconnected: return 19 + case .failedNullConnectionLostConnectivity: return 20 + case .failedNullConnectionFailure: return 21 + case .rejectedAttachmentTransmissionStatus: return 22 + case .timedOutAttachmentTransmissionStatus: return 23 + case .notEnoughSpaceAttachmentTransmissionStatus: return 24 + case .unsupportedAttachmentTypeAttachmentTransmissionStatus: return 25 + case .failedUnknownRemoteResponseTransmissionStatus: return 26 + case .noResponseFrameConnectionClosedLostConnectivityTransmissionStatus: return 27 + case .noResponseFrameConnectionClosedTransmissionStatus: return 28 + case .lostConnectivityTransmissionStatus: return 29 + case .failedDisallowedMedium: return 30 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_AttachmentTransmissionStatus: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +/// Generic result status of NearbyConnections API calls. +enum Location_Nearby_Proto_Sharing_ConnectionLayerStatus: SwiftProtobuf.Enum { + typealias RawValue = Int + + /// No status is available + case unknown // = 0 + + /// The operation was successful. + case success // = 1 + + /// The operation failed, without any more information. + case error // = 2 + + /// The app called an API method out of order (i.e. another method is expected + /// to be called first). + case outOfOrderApiCall // = 3 + + /// The app already has active operations (advertising, discovering, or + /// connected to other devices) with another Strategy. Stop these operations on + /// the current Strategy before trying to advertise or discover with a new + /// Strategy. + case alreadyHaveActiveStrategy // = 4 + + /// The app is already advertising; call StopAdvertising() before trying to + /// advertise again. + case alreadyAdvertising // = 5 + + /// The app is already discovering; call StopDiscovery() before trying to + /// discover again. + case alreadyDiscovering // = 6 + + /// NC is already listening for incoming connections from remote endpoints. + case alreadyListening // = 7 + + /// An attempt to read from/write to a connected remote endpoint failed. If + /// this occurs repeatedly, consider invoking DisconnectFromEndpoint(). + case endPointIoError // = 8 + + /// An attempt to interact with a remote endpoint failed because it's unknown + /// to us -- it's either an endpoint that was never discovered, or an endpoint + /// that never connected to us (both of which are indicative of bad input from + /// the client app). + case endPointUnknown // = 9 + + /// The remote endpoint rejected the connection request. + case connectionRejected // = 10 + + /// The app is already connected to the specified endpoint. Multiple + /// connections to a remote endpoint cannot be maintained simultaneously. + case alreadyConnectedToEndPoint // = 11 + + /// The remote endpoint is not connected; messages cannot be sent to it. + case notConnectedToEndPoint // = 12 + + /// There was an error trying to use the device's Bluetooth capabilities. + case bluetoothError // = 13 + + /// There was an error trying to use the device's Bluetooth Low Energy + /// capabilities. + case bleError // = 14 + + /// There was an error trying to use the device's Wi-Fi capabilities. + case wifiLanError // = 15 + + /// An attempt to interact with an in-flight Payload failed because it's + /// unknown to us. + case payloadUnknown // = 16 + + /// The connection was reset + case reset // = 17 + + /// The connection timed out + case timeout // = 18 + + init() { + self = .unknown + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknown + case 1: self = .success + case 2: self = .error + case 3: self = .outOfOrderApiCall + case 4: self = .alreadyHaveActiveStrategy + case 5: self = .alreadyAdvertising + case 6: self = .alreadyDiscovering + case 7: self = .alreadyListening + case 8: self = .endPointIoError + case 9: self = .endPointUnknown + case 10: self = .connectionRejected + case 11: self = .alreadyConnectedToEndPoint + case 12: self = .notConnectedToEndPoint + case 13: self = .bluetoothError + case 14: self = .bleError + case 15: self = .wifiLanError + case 16: self = .payloadUnknown + case 17: self = .reset + case 18: self = .timeout + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknown: return 0 + case .success: return 1 + case .error: return 2 + case .outOfOrderApiCall: return 3 + case .alreadyHaveActiveStrategy: return 4 + case .alreadyAdvertising: return 5 + case .alreadyDiscovering: return 6 + case .alreadyListening: return 7 + case .endPointIoError: return 8 + case .endPointUnknown: return 9 + case .connectionRejected: return 10 + case .alreadyConnectedToEndPoint: return 11 + case .notConnectedToEndPoint: return 12 + case .bluetoothError: return 13 + case .bleError: return 14 + case .wifiLanError: return 15 + case .payloadUnknown: return 16 + case .reset: return 17 + case .timeout: return 18 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_ConnectionLayerStatus: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +/// The status of processing attachments after receiver received payloads +/// successfully. +enum Location_Nearby_Proto_Sharing_ProcessReceivedAttachmentsStatus: SwiftProtobuf.Enum { + typealias RawValue = Int + case processingStatusUnknown // = 0 + case processingStatusCompleteProcessingAttachments // = 1 + case processingStatusFailedMovingFiles // = 2 + case processingStatusFailedReceivingApk // = 3 + case processingStatusFailedReceivingText // = 4 + case processingStatusFailedReceivingWifiCredentials // = 5 + + init() { + self = .processingStatusUnknown + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .processingStatusUnknown + case 1: self = .processingStatusCompleteProcessingAttachments + case 2: self = .processingStatusFailedMovingFiles + case 3: self = .processingStatusFailedReceivingApk + case 4: self = .processingStatusFailedReceivingText + case 5: self = .processingStatusFailedReceivingWifiCredentials + default: return nil + } + } + + var rawValue: Int { + switch self { + case .processingStatusUnknown: return 0 + case .processingStatusCompleteProcessingAttachments: return 1 + case .processingStatusFailedMovingFiles: return 2 + case .processingStatusFailedReceivingApk: return 3 + case .processingStatusFailedReceivingText: return 4 + case .processingStatusFailedReceivingWifiCredentials: return 5 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_ProcessReceivedAttachmentsStatus: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +/// The status of advertising and discovering sessions. Used by +/// SCAN_FOR_SHARE_TARGETS and ADVERTISE_DEVICE_PRESENCE. +enum Location_Nearby_Proto_Sharing_SessionStatus: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknownSessionStatus // = 0 + case succeededSessionStatus // = 1 + + /// TODO: b/341782941 - FAILED_SESSION_STATUS occurs when the status of + /// advertising or discovering sessions is not successful. It can be + /// due to STATUS_INTERNAL_ERROR, STATUS_INTERRUPTION, STATUS_CANCELLED. + /// More session statuses should be logged to determine the status. + case failedSessionStatus // = 2 + + init() { + self = .unknownSessionStatus + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknownSessionStatus + case 1: self = .succeededSessionStatus + case 2: self = .failedSessionStatus + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknownSessionStatus: return 0 + case .succeededSessionStatus: return 1 + case .failedSessionStatus: return 2 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_SessionStatus: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +/// User's response to introductions. +enum Location_Nearby_Proto_Sharing_ResponseToIntroduction: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknownResponseToIntroduction // = 0 + case acceptIntroduction // = 1 + case rejectIntroduction // = 2 + case failIntroduction // = 3 + + init() { + self = .unknownResponseToIntroduction + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknownResponseToIntroduction + case 1: self = .acceptIntroduction + case 2: self = .rejectIntroduction + case 3: self = .failIntroduction + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknownResponseToIntroduction: return 0 + case .acceptIntroduction: return 1 + case .rejectIntroduction: return 2 + case .failIntroduction: return 3 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_ResponseToIntroduction: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +/// TODO(fdi): may eventually include desktop, etc. +/// The type of a remote device. +enum Location_Nearby_Proto_Sharing_DeviceType: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknownDeviceType // = 0 + case phone // = 1 + case tablet // = 2 + case laptop // = 3 + case car // = 4 + case foldable // = 5 + case xr // = 6 + + init() { + self = .unknownDeviceType + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknownDeviceType + case 1: self = .phone + case 2: self = .tablet + case 3: self = .laptop + case 4: self = .car + case 5: self = .foldable + case 6: self = .xr + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknownDeviceType: return 0 + case .phone: return 1 + case .tablet: return 2 + case .laptop: return 3 + case .car: return 4 + case .foldable: return 5 + case .xr: return 6 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_DeviceType: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +/// TODO(fdi): may eventually include windows, iOS, etc. +/// The OS type of a remote device. +enum Location_Nearby_Proto_Sharing_OSType: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknownOsType // = 0 + case android // = 1 + case chromeOs // = 2 + case ios // = 3 + case windows // = 4 + case macos // = 5 + + init() { + self = .unknownOsType + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknownOsType + case 1: self = .android + case 2: self = .chromeOs + case 3: self = .ios + case 4: self = .windows + case 5: self = .macos + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknownOsType: return 0 + case .android: return 1 + case .chromeOs: return 2 + case .ios: return 3 + case .windows: return 4 + case .macos: return 5 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_OSType: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +/// Relationship of remote device to sender device. +enum Location_Nearby_Proto_Sharing_DeviceRelationship: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknownDeviceRelationship // = 0 + + /// The remote device belongs to the same owner as sender device. + case isSelf // = 1 + + /// The remote device is a contact of sender. + case isContact // = 2 + + /// The remote device is a stranger. + case isStranger // = 3 + + init() { + self = .unknownDeviceRelationship + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknownDeviceRelationship + case 1: self = .isSelf + case 2: self = .isContact + case 3: self = .isStranger + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknownDeviceRelationship: return 0 + case .isSelf: return 1 + case .isContact: return 2 + case .isStranger: return 3 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_DeviceRelationship: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +/// The device sources of the clearcut log. +enum Location_Nearby_Proto_Sharing_LogSource: SwiftProtobuf.Enum { + typealias RawValue = Int + case unspecifiedSource // = 0 + + /// Represents the devices in Nearby labs. + case labDevices // = 1 + + /// Represents the devices tested by Nearby engs, in the long term can include + /// any devices with newest feature flags. + case internalDevices // = 2 + + /// Represents the devices testing our in-development features before they're + /// released to the greater public. + case betaTesterDevices // = 3 + + /// Represents the OEM partners (like Samsung) that we're working with to + /// verify functionality on their devices. + case oemDevices // = 4 + + /// Represents the device for debugging. + case debugDevices // = 5 + + /// Represents the device for Nearby Module Food. + case nearbyModuleFoodDevices // = 6 + + /// Represents the device for BeTo Team Food. + case betoDogfoodDevices // = 7 + + /// Represents the device for Nearby dog Food. + case nearbyDogfoodDevices // = 8 + + /// Represents the device for Nearby Team Food. + case nearbyTeamfoodDevices // = 9 + + init() { + self = .unspecifiedSource + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unspecifiedSource + case 1: self = .labDevices + case 2: self = .internalDevices + case 3: self = .betaTesterDevices + case 4: self = .oemDevices + case 5: self = .debugDevices + case 6: self = .nearbyModuleFoodDevices + case 7: self = .betoDogfoodDevices + case 8: self = .nearbyDogfoodDevices + case 9: self = .nearbyTeamfoodDevices + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unspecifiedSource: return 0 + case .labDevices: return 1 + case .internalDevices: return 2 + case .betaTesterDevices: return 3 + case .oemDevices: return 4 + case .debugDevices: return 5 + case .nearbyModuleFoodDevices: return 6 + case .betoDogfoodDevices: return 7 + case .nearbyDogfoodDevices: return 8 + case .nearbyTeamfoodDevices: return 9 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_LogSource: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +/// The Fast Share server action name. +enum Location_Nearby_Proto_Sharing_ServerActionName: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknownServerAction // = 0 + case uploadCertificates // = 1 + case downloadCertificates // = 2 + case checkReachability // = 3 + case uploadContacts // = 4 + case updateDeviceName // = 5 + case uploadSenderCertificates // = 6 + case downloadSenderCertificates // = 7 + case uploadContactsAndCertificates // = 8 + case listReachablePhoneNumbers // = 9 + case listMyDevices // = 10 + case listContactPeople // = 11 + + /// used for analytics logger to record action name. + case downloadCertificatesInfo // = 12 + + init() { + self = .unknownServerAction + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknownServerAction + case 1: self = .uploadCertificates + case 2: self = .downloadCertificates + case 3: self = .checkReachability + case 4: self = .uploadContacts + case 5: self = .updateDeviceName + case 6: self = .uploadSenderCertificates + case 7: self = .downloadSenderCertificates + case 8: self = .uploadContactsAndCertificates + case 9: self = .listReachablePhoneNumbers + case 10: self = .listMyDevices + case 11: self = .listContactPeople + case 12: self = .downloadCertificatesInfo + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknownServerAction: return 0 + case .uploadCertificates: return 1 + case .downloadCertificates: return 2 + case .checkReachability: return 3 + case .uploadContacts: return 4 + case .updateDeviceName: return 5 + case .uploadSenderCertificates: return 6 + case .downloadSenderCertificates: return 7 + case .uploadContactsAndCertificates: return 8 + case .listReachablePhoneNumbers: return 9 + case .listMyDevices: return 10 + case .listContactPeople: return 11 + case .downloadCertificatesInfo: return 12 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_ServerActionName: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +/// The Fast Share server response state. +enum Location_Nearby_Proto_Sharing_ServerResponseState: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknownServerResponseState // = 0 + case serverResponseSuccess // = 1 + case serverResponseUnknownFailure // = 2 + + /// For StatusException. + case serverResponseStatusOtherFailure // = 3 + case serverResponseStatusDeadlineExceeded // = 4 + case serverResponseStatusPermissionDenied // = 5 + case serverResponseStatusUnavailable // = 6 + case serverResponseStatusUnauthenticated // = 7 + case serverResponseStatusInvalidArgument // = 9 + + /// For GoogleAuthException. + case serverResponseGoogleAuthFailure // = 8 + + /// For Internet connect status. + case serverResponseNotConnectedToInternet // = 10 + + init() { + self = .unknownServerResponseState + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknownServerResponseState + case 1: self = .serverResponseSuccess + case 2: self = .serverResponseUnknownFailure + case 3: self = .serverResponseStatusOtherFailure + case 4: self = .serverResponseStatusDeadlineExceeded + case 5: self = .serverResponseStatusPermissionDenied + case 6: self = .serverResponseStatusUnavailable + case 7: self = .serverResponseStatusUnauthenticated + case 8: self = .serverResponseGoogleAuthFailure + case 9: self = .serverResponseStatusInvalidArgument + case 10: self = .serverResponseNotConnectedToInternet + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknownServerResponseState: return 0 + case .serverResponseSuccess: return 1 + case .serverResponseUnknownFailure: return 2 + case .serverResponseStatusOtherFailure: return 3 + case .serverResponseStatusDeadlineExceeded: return 4 + case .serverResponseStatusPermissionDenied: return 5 + case .serverResponseStatusUnavailable: return 6 + case .serverResponseStatusUnauthenticated: return 7 + case .serverResponseGoogleAuthFailure: return 8 + case .serverResponseStatusInvalidArgument: return 9 + case .serverResponseNotConnectedToInternet: return 10 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_ServerResponseState: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +/// The purpose of requesting the server request. +enum Location_Nearby_Proto_Sharing_SyncPurpose: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknown // = 0 + + /// When NearbySharingChimeraService#sync() is called. + case onDemandSync // = 1 + + /// Requested by chime notification. + case chimeNotification // = 2 + + /// For reqular daily sync. + case dailySync // = 3 + + /// Wen a device opts into Nearby Share. + case optInFirstSync // = 4 + + /// Requested when Nearby Share automatically enables a device that shares + /// a single account that has already opted in on another device. + case checkDefaultOptIn // = 5 + + /// When a device enables Nearby Share. + case nearbyShareEnabled // = 6 + + /// When a device is in fast init advertising. + case syncAtFastInit // = 7 + + /// When device start discovery. + case syncAtDiscovery // = 8 + + /// When device tries to load valid private certificate. + case syncAtLoadPrivateCertificate // = 9 + + /// When device start advertiseement. + case syncAtAdvertisement // = 10 + + /// When device contacts list changes. + case contactListChange // = 11 + + /// When showing the C11 banner in Neary Share setting. + case showC11NView // = 12 + + /// For regular check contact reachability. + case regularCheckContactReachability // = 13 + + /// When selected contacts list changes in visibility setting. + case visibilitySelectedContactChange // = 14 + + /// When switching account. + case accountChange // = 15 + + /// When regenerate certificates + case regenerateCertificates // = 16 + + /// When Device Contacts consent changes + case deviceContactsConsentChange // = 17 + + init() { + self = .unknown + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknown + case 1: self = .onDemandSync + case 2: self = .chimeNotification + case 3: self = .dailySync + case 4: self = .optInFirstSync + case 5: self = .checkDefaultOptIn + case 6: self = .nearbyShareEnabled + case 7: self = .syncAtFastInit + case 8: self = .syncAtDiscovery + case 9: self = .syncAtLoadPrivateCertificate + case 10: self = .syncAtAdvertisement + case 11: self = .contactListChange + case 12: self = .showC11NView + case 13: self = .regularCheckContactReachability + case 14: self = .visibilitySelectedContactChange + case 15: self = .accountChange + case 16: self = .regenerateCertificates + case 17: self = .deviceContactsConsentChange + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknown: return 0 + case .onDemandSync: return 1 + case .chimeNotification: return 2 + case .dailySync: return 3 + case .optInFirstSync: return 4 + case .checkDefaultOptIn: return 5 + case .nearbyShareEnabled: return 6 + case .syncAtFastInit: return 7 + case .syncAtDiscovery: return 8 + case .syncAtLoadPrivateCertificate: return 9 + case .syncAtAdvertisement: return 10 + case .contactListChange: return 11 + case .showC11NView: return 12 + case .regularCheckContactReachability: return 13 + case .visibilitySelectedContactChange: return 14 + case .accountChange: return 15 + case .regenerateCertificates: return 16 + case .deviceContactsConsentChange: return 17 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_SyncPurpose: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +/// The device role to trigger the server request. +enum Location_Nearby_Proto_Sharing_ClientRole: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknown // = 0 + case sender // = 1 + case receiver // = 2 + + init() { + self = .unknown + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknown + case 1: self = .sender + case 2: self = .receiver + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknown: return 0 + case .sender: return 1 + case .receiver: return 2 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_ClientRole: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +/// The type of Nearby Sharing scanning. +enum Location_Nearby_Proto_Sharing_ScanType: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknownScanType // = 0 + case foregroundScan // = 1 + case foregroundRetryScan // = 2 + case directShareScan // = 3 + case backgroundScan // = 4 + + init() { + self = .unknownScanType + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknownScanType + case 1: self = .foregroundScan + case 2: self = .foregroundRetryScan + case 3: self = .directShareScan + case 4: self = .backgroundScan + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknownScanType: return 0 + case .foregroundScan: return 1 + case .foregroundRetryScan: return 2 + case .directShareScan: return 3 + case .backgroundScan: return 4 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_ScanType: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +/// The type of parsing endpoint id failed type. +enum Location_Nearby_Proto_Sharing_ParsingFailedType: SwiftProtobuf.Enum { + typealias RawValue = Int + case failedUnknownType // = 0 + + /// NULL advertisement is returned due to sender failing to parse advertisement + /// from endpointInfo byte stream from receiver advertisement. + case failedParseAdvertisement // = 1 + + /// NULL shareTarget is returned due to sender failing to create shareTarget + /// from a valid parsed advertisement stemming from issues in certificates, QR + /// code tokens or device names. + case failedConvertShareTarget // = 2 + + init() { + self = .failedUnknownType + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .failedUnknownType + case 1: self = .failedParseAdvertisement + case 2: self = .failedConvertShareTarget + default: return nil + } + } + + var rawValue: Int { + switch self { + case .failedUnknownType: return 0 + case .failedParseAdvertisement: return 1 + case .failedConvertShareTarget: return 2 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_ParsingFailedType: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +/// The Nearby Sharing advertising mode. +enum Location_Nearby_Proto_Sharing_AdvertisingMode: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknownAdvertisingMode // = 0 + case screenOffAdvertisingMode // = 1 + case backgroundAdvertisingMode // = 2 + case midgroundAdvertisingMode // = 3 + case foregroundAdvertisingMode // = 4 + case suspendedAdvertisingMode // = 5 + + init() { + self = .unknownAdvertisingMode + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknownAdvertisingMode + case 1: self = .screenOffAdvertisingMode + case 2: self = .backgroundAdvertisingMode + case 3: self = .midgroundAdvertisingMode + case 4: self = .foregroundAdvertisingMode + case 5: self = .suspendedAdvertisingMode + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknownAdvertisingMode: return 0 + case .screenOffAdvertisingMode: return 1 + case .backgroundAdvertisingMode: return 2 + case .midgroundAdvertisingMode: return 3 + case .foregroundAdvertisingMode: return 4 + case .suspendedAdvertisingMode: return 5 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_AdvertisingMode: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +/// The Nearby Sharing discovery mode. +enum Location_Nearby_Proto_Sharing_DiscoveryMode: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknownDiscoveryMode // = 0 + case screenOffDiscoveryMode // = 1 + case backgroundDiscoveryMode // = 2 + case midgroundDiscoveryMode // = 3 + case foregroundDiscoveryMode // = 4 + case suspendedDiscoveryMode // = 5 + + init() { + self = .unknownDiscoveryMode + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknownDiscoveryMode + case 1: self = .screenOffDiscoveryMode + case 2: self = .backgroundDiscoveryMode + case 3: self = .midgroundDiscoveryMode + case 4: self = .foregroundDiscoveryMode + case 5: self = .suspendedDiscoveryMode + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknownDiscoveryMode: return 0 + case .screenOffDiscoveryMode: return 1 + case .backgroundDiscoveryMode: return 2 + case .midgroundDiscoveryMode: return 3 + case .foregroundDiscoveryMode: return 4 + case .suspendedDiscoveryMode: return 5 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_DiscoveryMode: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +/// The class name of chimera activity. +enum Location_Nearby_Proto_Sharing_ActivityName: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknownActivity // = 0 + case shareSheetActivity // = 1 + case settingsActivity // = 2 + case receiveSurfaceActivity // = 3 + case setupActivity // = 4 + case deviceVisibilityActivity // = 5 + case consentsActivity // = 6 + case setDeviceNameDialog // = 7 + case setDataUsageDialog // = 8 + case quickSettingsActivity // = 9 + case remoteCopyShareSheetActivity // = 10 + case setupWizardActivity // = 11 + case settingsReviewActivity // = 12 + + init() { + self = .unknownActivity + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknownActivity + case 1: self = .shareSheetActivity + case 2: self = .settingsActivity + case 3: self = .receiveSurfaceActivity + case 4: self = .setupActivity + case 5: self = .deviceVisibilityActivity + case 6: self = .consentsActivity + case 7: self = .setDeviceNameDialog + case 8: self = .setDataUsageDialog + case 9: self = .quickSettingsActivity + case 10: self = .remoteCopyShareSheetActivity + case 11: self = .setupWizardActivity + case 12: self = .settingsReviewActivity + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknownActivity: return 0 + case .shareSheetActivity: return 1 + case .settingsActivity: return 2 + case .receiveSurfaceActivity: return 3 + case .setupActivity: return 4 + case .deviceVisibilityActivity: return 5 + case .consentsActivity: return 6 + case .setDeviceNameDialog: return 7 + case .setDataUsageDialog: return 8 + case .quickSettingsActivity: return 9 + case .remoteCopyShareSheetActivity: return 10 + case .setupWizardActivity: return 11 + case .settingsReviewActivity: return 12 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_ActivityName: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +/// The type of consent page user launches. +enum Location_Nearby_Proto_Sharing_ConsentType: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknown // = 0 + + /// launch Constellation consent page. + case c11N // = 1 + + /// launch device contact consent page. + case deviceContact // = 2 + + init() { + self = .unknown + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknown + case 1: self = .c11N + case 2: self = .deviceContact + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknown: return 0 + case .c11N: return 1 + case .deviceContact: return 2 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_ConsentType: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +enum Location_Nearby_Proto_Sharing_ConsentAcceptanceStatus: SwiftProtobuf.Enum { + typealias RawValue = Int + case consentUnknownAcceptStatus // = 0 + case consentAccepted // = 1 + case consentDeclined // = 2 + + /// unable to enable consent. + case consentUnableToEnable // = 3 + + init() { + self = .consentUnknownAcceptStatus + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .consentUnknownAcceptStatus + case 1: self = .consentAccepted + case 2: self = .consentDeclined + case 3: self = .consentUnableToEnable + default: return nil + } + } + + var rawValue: Int { + switch self { + case .consentUnknownAcceptStatus: return 0 + case .consentAccepted: return 1 + case .consentDeclined: return 2 + case .consentUnableToEnable: return 3 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_ConsentAcceptanceStatus: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +enum Location_Nearby_Proto_Sharing_ApkSource: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknownApkSource // = 0 + case apkFromSdCard // = 1 + case installedApp // = 2 + + init() { + self = .unknownApkSource + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknownApkSource + case 1: self = .apkFromSdCard + case 2: self = .installedApp + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknownApkSource: return 0 + case .apkFromSdCard: return 1 + case .installedApp: return 2 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_ApkSource: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +/// The Installation status of APK. +enum Location_Nearby_Proto_Sharing_InstallAPKStatus: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknownInstallApkStatus // = 0 + case failInstallation // = 1 + case successInstallation // = 2 + + init() { + self = .unknownInstallApkStatus + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknownInstallApkStatus + case 1: self = .failInstallation + case 2: self = .successInstallation + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknownInstallApkStatus: return 0 + case .failInstallation: return 1 + case .successInstallation: return 2 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_InstallAPKStatus: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +/// The verification status of APK. +enum Location_Nearby_Proto_Sharing_VerifyAPKStatus: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknownVerifyApkStatus // = 0 + case notInstallable // = 1 + case installable // = 2 + case alreadyInstalled // = 3 + + init() { + self = .unknownVerifyApkStatus + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknownVerifyApkStatus + case 1: self = .notInstallable + case 2: self = .installable + case 3: self = .alreadyInstalled + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknownVerifyApkStatus: return 0 + case .notInstallable: return 1 + case .installable: return 2 + case .alreadyInstalled: return 3 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_VerifyAPKStatus: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +enum Location_Nearby_Proto_Sharing_ShowNotificationStatus: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknownShowNotificationStatus // = 0 + case show // = 1 + case notShow // = 2 + + init() { + self = .unknownShowNotificationStatus + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknownShowNotificationStatus + case 1: self = .show + case 2: self = .notShow + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknownShowNotificationStatus: return 0 + case .show: return 1 + case .notShow: return 2 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_ShowNotificationStatus: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +enum Location_Nearby_Proto_Sharing_PermissionRequestResult: SwiftProtobuf.Enum { + typealias RawValue = Int + case permissionUnknownRequestResult // = 0 + case permissionGranted // = 1 + case permissionRejected // = 2 + case permissionUnableToGrant // = 3 + + init() { + self = .permissionUnknownRequestResult + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .permissionUnknownRequestResult + case 1: self = .permissionGranted + case 2: self = .permissionRejected + case 3: self = .permissionUnableToGrant + default: return nil + } + } + + var rawValue: Int { + switch self { + case .permissionUnknownRequestResult: return 0 + case .permissionGranted: return 1 + case .permissionRejected: return 2 + case .permissionUnableToGrant: return 3 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_PermissionRequestResult: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +enum Location_Nearby_Proto_Sharing_PermissionRequestType: SwiftProtobuf.Enum { + typealias RawValue = Int + case permissionUnknownType // = 0 + case permissionAirplaneModeOff // = 1 + case permissionWifi // = 2 + case permissionBluetooth // = 3 + case permissionLocation // = 4 + case permissionWifiHotspot // = 5 + + init() { + self = .permissionUnknownType + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .permissionUnknownType + case 1: self = .permissionAirplaneModeOff + case 2: self = .permissionWifi + case 3: self = .permissionBluetooth + case 4: self = .permissionLocation + case 5: self = .permissionWifiHotspot + default: return nil + } + } + + var rawValue: Int { + switch self { + case .permissionUnknownType: return 0 + case .permissionAirplaneModeOff: return 1 + case .permissionWifi: return 2 + case .permissionBluetooth: return 3 + case .permissionLocation: return 4 + case .permissionWifiHotspot: return 5 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_PermissionRequestType: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +enum Location_Nearby_Proto_Sharing_SharingUseCase: SwiftProtobuf.Enum { + typealias RawValue = Int + case useCaseUnknown // = 0 + case useCaseNearbyShare // = 1 + case useCaseRemoteCopyPaste // = 2 + case useCaseWifiCredential // = 3 + case useCaseAppShare // = 4 + case useCaseQuickSettingFileShare // = 5 + case useCaseSetupWizard // = 6 + + /// Deprecated. QR code is an addition to existing use cases rather than being + /// a separate use case. + case useCaseNearbyShareWithQrCode // = 7 + + /// The user was redirected from Bluetooth sharing UI to Nearby Share + case useCaseRedirectedFromBluetoothShare // = 8 + + init() { + self = .useCaseUnknown + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .useCaseUnknown + case 1: self = .useCaseNearbyShare + case 2: self = .useCaseRemoteCopyPaste + case 3: self = .useCaseWifiCredential + case 4: self = .useCaseAppShare + case 5: self = .useCaseQuickSettingFileShare + case 6: self = .useCaseSetupWizard + case 7: self = .useCaseNearbyShareWithQrCode + case 8: self = .useCaseRedirectedFromBluetoothShare + default: return nil + } + } + + var rawValue: Int { + switch self { + case .useCaseUnknown: return 0 + case .useCaseNearbyShare: return 1 + case .useCaseRemoteCopyPaste: return 2 + case .useCaseWifiCredential: return 3 + case .useCaseAppShare: return 4 + case .useCaseQuickSettingFileShare: return 5 + case .useCaseSetupWizard: return 6 + case .useCaseNearbyShareWithQrCode: return 7 + case .useCaseRedirectedFromBluetoothShare: return 8 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_SharingUseCase: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +/// Used only for Windows App now. +enum Location_Nearby_Proto_Sharing_AppCrashReason: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknown // = 0 + + init() { + self = .unknown + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknown + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknown: return 0 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_AppCrashReason: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +/// Thes source where the attachemnt comes from. It can be an action, app name, +/// etc. The first 6 source types are being used as FileSenderType in Nearby +/// Share Windows app. +enum Location_Nearby_Proto_Sharing_AttachmentSourceType: SwiftProtobuf.Enum { + typealias RawValue = Int + case attachmentSourceUnknown // = 0 + case attachmentSourceContextMenu // = 1 + case attachmentSourceDragAndDrop // = 2 + case attachmentSourceSelectFilesButton // = 3 + case attachmentSourcePaste // = 4 + case attachmentSourceSelectFoldersButton // = 5 + case attachmentSourceShareActivation // = 6 + + init() { + self = .attachmentSourceUnknown + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .attachmentSourceUnknown + case 1: self = .attachmentSourceContextMenu + case 2: self = .attachmentSourceDragAndDrop + case 3: self = .attachmentSourceSelectFilesButton + case 4: self = .attachmentSourcePaste + case 5: self = .attachmentSourceSelectFoldersButton + case 6: self = .attachmentSourceShareActivation + default: return nil + } + } + + var rawValue: Int { + switch self { + case .attachmentSourceUnknown: return 0 + case .attachmentSourceContextMenu: return 1 + case .attachmentSourceDragAndDrop: return 2 + case .attachmentSourceSelectFilesButton: return 3 + case .attachmentSourcePaste: return 4 + case .attachmentSourceSelectFoldersButton: return 5 + case .attachmentSourceShareActivation: return 6 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_AttachmentSourceType: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +/// The action to interact with preferences. +/// Used only for Windows App now. +enum Location_Nearby_Proto_Sharing_PreferencesAction: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknown // = 0 + case noAction // = 1 + + /// Primary actions/functions towards preferences + case loadPreferences // = 2 + case savePreferencess // = 3 + case attemptLoad // = 4 + case restoreFromBackup // = 5 + + /// Other actions within the 4 actions above + case createPreferencesPath // = 6 + case makePreferencesBackupFile // = 7 + case checkIfPreferencesPathExists // = 8 + case checkIfPreferencesInputStreamStatus // = 9 + case checkIfPreferencesFileIsCorrupted // = 10 + case checkIfPreferencesBackupFileExists // = 11 + + init() { + self = .unknown + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknown + case 1: self = .noAction + case 2: self = .loadPreferences + case 3: self = .savePreferencess + case 4: self = .attemptLoad + case 5: self = .restoreFromBackup + case 6: self = .createPreferencesPath + case 7: self = .makePreferencesBackupFile + case 8: self = .checkIfPreferencesPathExists + case 9: self = .checkIfPreferencesInputStreamStatus + case 10: self = .checkIfPreferencesFileIsCorrupted + case 11: self = .checkIfPreferencesBackupFileExists + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknown: return 0 + case .noAction: return 1 + case .loadPreferences: return 2 + case .savePreferencess: return 3 + case .attemptLoad: return 4 + case .restoreFromBackup: return 5 + case .createPreferencesPath: return 6 + case .makePreferencesBackupFile: return 7 + case .checkIfPreferencesPathExists: return 8 + case .checkIfPreferencesInputStreamStatus: return 9 + case .checkIfPreferencesFileIsCorrupted: return 10 + case .checkIfPreferencesBackupFileExists: return 11 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_PreferencesAction: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +/// The status of the action to interact with preferences. +/// Used only for Windows App now. +enum Location_Nearby_Proto_Sharing_PreferencesActionStatus: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknown // = 0 + case success // = 1 + case fail // = 2 + + init() { + self = .unknown + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknown + case 1: self = .success + case 2: self = .fail + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknown: return 0 + case .success: return 1 + case .fail: return 2 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_PreferencesActionStatus: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +///* The distance of the found nearby fast init advertisement. +enum Location_Nearby_Proto_Sharing_FastInitState: SwiftProtobuf.Enum { + typealias RawValue = Int + case fastInitUnknownState // = 0 + + /// A device was found in close proximity. + /// distance < fast_init_distance_close_centimeters(50 cm) + case fastInitCloseState // = 1 + + /// A device was found in far proximity. + /// distance < fast_init_distance_close_centimeters(10 m) + case fastInitFarState // = 2 + + /// No devices have been found nearby. The default state. + case fastInitLostState // = 3 + + init() { + self = .fastInitUnknownState + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .fastInitUnknownState + case 1: self = .fastInitCloseState + case 2: self = .fastInitFarState + case 3: self = .fastInitLostState + default: return nil + } + } + + var rawValue: Int { + switch self { + case .fastInitUnknownState: return 0 + case .fastInitCloseState: return 1 + case .fastInitFarState: return 2 + case .fastInitLostState: return 3 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_FastInitState: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +///* The type of FastInit advertisement. +enum Location_Nearby_Proto_Sharing_FastInitType: SwiftProtobuf.Enum { + typealias RawValue = Int + case fastInitUnknownType // = 0 + + /// Show HUN to notify the user. + case fastInitNotifyType // = 1 + + /// Not notify the user. + case fastInitSilentType // = 2 + + init() { + self = .fastInitUnknownType + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .fastInitUnknownType + case 1: self = .fastInitNotifyType + case 2: self = .fastInitSilentType + default: return nil + } + } + + var rawValue: Int { + switch self { + case .fastInitUnknownType: return 0 + case .fastInitNotifyType: return 1 + case .fastInitSilentType: return 2 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_FastInitType: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +///* The type of desktop notification event. +enum Location_Nearby_Proto_Sharing_DesktopNotification: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknown // = 0 + case connecting // = 1 + case progress // = 2 + case accept // = 3 + case received // = 4 + case error // = 5 + + init() { + self = .unknown + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknown + case 1: self = .connecting + case 2: self = .progress + case 3: self = .accept + case 4: self = .received + case 5: self = .error + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknown: return 0 + case .connecting: return 1 + case .progress: return 2 + case .accept: return 3 + case .received: return 4 + case .error: return 5 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_DesktopNotification: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +enum Location_Nearby_Proto_Sharing_DesktopTransferEventType: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknown // = 0 + + /// Receive attachments. + case desktopTransferEventReceiveTypeAccept // = 1 + case desktopTransferEventReceiveTypeProgress // = 2 + case desktopTransferEventReceiveTypeReceived // = 3 + case desktopTransferEventReceiveTypeError // = 4 + + /// Send attachments. + case desktopTransferEventSendTypeStart // = 5 + case desktopTransferEventSendTypeSelectADevice // = 6 + case desktopTransferEventSendTypeProgress // = 7 + case desktopTransferEventSendTypeSent // = 8 + case desktopTransferEventSendTypeError // = 9 + + init() { + self = .unknown + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknown + case 1: self = .desktopTransferEventReceiveTypeAccept + case 2: self = .desktopTransferEventReceiveTypeProgress + case 3: self = .desktopTransferEventReceiveTypeReceived + case 4: self = .desktopTransferEventReceiveTypeError + case 5: self = .desktopTransferEventSendTypeStart + case 6: self = .desktopTransferEventSendTypeSelectADevice + case 7: self = .desktopTransferEventSendTypeProgress + case 8: self = .desktopTransferEventSendTypeSent + case 9: self = .desktopTransferEventSendTypeError + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknown: return 0 + case .desktopTransferEventReceiveTypeAccept: return 1 + case .desktopTransferEventReceiveTypeProgress: return 2 + case .desktopTransferEventReceiveTypeReceived: return 3 + case .desktopTransferEventReceiveTypeError: return 4 + case .desktopTransferEventSendTypeStart: return 5 + case .desktopTransferEventSendTypeSelectADevice: return 6 + case .desktopTransferEventSendTypeProgress: return 7 + case .desktopTransferEventSendTypeSent: return 8 + case .desktopTransferEventSendTypeError: return 9 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_DesktopTransferEventType: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +enum Location_Nearby_Proto_Sharing_DecryptCertificateFailureStatus: SwiftProtobuf.Enum { + typealias RawValue = Int + case decryptCertUnknownFailure // = 0 + case decryptCertNoSuchAlgorithmFailure // = 1 + case decryptCertNoSuchPaddingFailure // = 2 + case decryptCertInvalidKeyFailure // = 3 + case decryptCertInvalidAlgorithmParameterFailure // = 4 + case decryptCertIllegalBlockSizeFailure // = 5 + case decryptCertBadPaddingFailure // = 6 + + init() { + self = .decryptCertUnknownFailure + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .decryptCertUnknownFailure + case 1: self = .decryptCertNoSuchAlgorithmFailure + case 2: self = .decryptCertNoSuchPaddingFailure + case 3: self = .decryptCertInvalidKeyFailure + case 4: self = .decryptCertInvalidAlgorithmParameterFailure + case 5: self = .decryptCertIllegalBlockSizeFailure + case 6: self = .decryptCertBadPaddingFailure + default: return nil + } + } + + var rawValue: Int { + switch self { + case .decryptCertUnknownFailure: return 0 + case .decryptCertNoSuchAlgorithmFailure: return 1 + case .decryptCertNoSuchPaddingFailure: return 2 + case .decryptCertInvalidKeyFailure: return 3 + case .decryptCertInvalidAlgorithmParameterFailure: return 4 + case .decryptCertIllegalBlockSizeFailure: return 5 + case .decryptCertBadPaddingFailure: return 6 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_DecryptCertificateFailureStatus: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +/// Refer to go/qs-contacts-consent-2024 for the detail. +enum Location_Nearby_Proto_Sharing_ContactAccess: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknown // = 0 + case noContactUploaded // = 1 + case onlyUploadGoogleContact // = 2 + case uploadContactForDeviceContactConsent // = 3 + case uploadContactForQuickShareConsent // = 4 + + init() { + self = .unknown + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknown + case 1: self = .noContactUploaded + case 2: self = .onlyUploadGoogleContact + case 3: self = .uploadContactForDeviceContactConsent + case 4: self = .uploadContactForQuickShareConsent + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknown: return 0 + case .noContactUploaded: return 1 + case .onlyUploadGoogleContact: return 2 + case .uploadContactForDeviceContactConsent: return 3 + case .uploadContactForQuickShareConsent: return 4 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_ContactAccess: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +/// Refer to go/qs-contacts-consent-2024 for the detail. +enum Location_Nearby_Proto_Sharing_IdentityVerification: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknown // = 0 + case noPhoneNumberVerified // = 1 + case phoneNumberVerifiedNotLinkedToGaia // = 2 + case phoneNumberVerifiedLinkedToQsGaia // = 3 + + init() { + self = .unknown + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknown + case 1: self = .noPhoneNumberVerified + case 2: self = .phoneNumberVerifiedNotLinkedToGaia + case 3: self = .phoneNumberVerifiedLinkedToQsGaia + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknown: return 0 + case .noPhoneNumberVerified: return 1 + case .phoneNumberVerifiedNotLinkedToGaia: return 2 + case .phoneNumberVerifiedLinkedToQsGaia: return 3 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_IdentityVerification: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +enum Location_Nearby_Proto_Sharing_ButtonStatus: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknown // = 0 + case clickAccept // = 1 + case clickReject // = 2 + case ignore // = 3 + + init() { + self = .unknown + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknown + case 1: self = .clickAccept + case 2: self = .clickReject + case 3: self = .ignore + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknown: return 0 + case .clickAccept: return 1 + case .clickReject: return 2 + case .ignore: return 3 + } + } + +} + +#if swift(>=4.2) + +extension Location_Nearby_Proto_Sharing_ButtonStatus: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +#if swift(>=5.5) && canImport(_Concurrency) +extension Location_Nearby_Proto_Sharing_EventType: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_EventCategory: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_NearbySharingStatus: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_Visibility: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_DataUsage: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_EstablishConnectionStatus: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_AttachmentTransmissionStatus: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_ConnectionLayerStatus: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_ProcessReceivedAttachmentsStatus: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_SessionStatus: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_ResponseToIntroduction: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_DeviceType: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_OSType: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_DeviceRelationship: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_LogSource: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_ServerActionName: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_ServerResponseState: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_SyncPurpose: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_ClientRole: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_ScanType: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_ParsingFailedType: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_AdvertisingMode: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_DiscoveryMode: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_ActivityName: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_ConsentType: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_ConsentAcceptanceStatus: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_ApkSource: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_InstallAPKStatus: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_VerifyAPKStatus: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_ShowNotificationStatus: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_PermissionRequestResult: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_PermissionRequestType: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_SharingUseCase: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_AppCrashReason: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_AttachmentSourceType: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_PreferencesAction: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_PreferencesActionStatus: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_FastInitState: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_FastInitType: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_DesktopNotification: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_DesktopTransferEventType: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_DecryptCertificateFailureStatus: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_ContactAccess: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_IdentityVerification: @unchecked Sendable {} +extension Location_Nearby_Proto_Sharing_ButtonStatus: @unchecked Sendable {} +#endif // swift(>=5.5) && canImport(_Concurrency) + +// MARK: - Code below here is support for the SwiftProtobuf runtime. + +extension Location_Nearby_Proto_Sharing_EventType: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "UNKNOWN_EVENT_TYPE"), + 1: .same(proto: "ACCEPT_AGREEMENTS"), + 2: .same(proto: "ENABLE_NEARBY_SHARING"), + 3: .same(proto: "SET_VISIBILITY"), + 4: .same(proto: "DESCRIBE_ATTACHMENTS"), + 5: .same(proto: "SCAN_FOR_SHARE_TARGETS_START"), + 6: .same(proto: "SCAN_FOR_SHARE_TARGETS_END"), + 7: .same(proto: "ADVERTISE_DEVICE_PRESENCE_START"), + 8: .same(proto: "ADVERTISE_DEVICE_PRESENCE_END"), + 9: .same(proto: "SEND_FAST_INITIALIZATION"), + 10: .same(proto: "RECEIVE_FAST_INITIALIZATION"), + 11: .same(proto: "DISCOVER_SHARE_TARGET"), + 12: .same(proto: "SEND_INTRODUCTION"), + 13: .same(proto: "RECEIVE_INTRODUCTION"), + 14: .same(proto: "RESPOND_TO_INTRODUCTION"), + 15: .same(proto: "SEND_ATTACHMENTS_START"), + 16: .same(proto: "SEND_ATTACHMENTS_END"), + 17: .same(proto: "RECEIVE_ATTACHMENTS_START"), + 18: .same(proto: "RECEIVE_ATTACHMENTS_END"), + 19: .same(proto: "CANCEL_SENDING_ATTACHMENTS"), + 20: .same(proto: "CANCEL_RECEIVING_ATTACHMENTS"), + 21: .same(proto: "OPEN_RECEIVED_ATTACHMENTS"), + 22: .same(proto: "LAUNCH_SETUP_ACTIVITY"), + 23: .same(proto: "ADD_CONTACT"), + 24: .same(proto: "REMOVE_CONTACT"), + 25: .same(proto: "FAST_SHARE_SERVER_RESPONSE"), + 26: .same(proto: "SEND_START"), + 27: .same(proto: "ACCEPT_FAST_INITIALIZATION"), + 28: .same(proto: "SET_DATA_USAGE"), + 29: .same(proto: "DISMISS_FAST_INITIALIZATION"), + 30: .same(proto: "CANCEL_CONNECTION"), + 31: .same(proto: "LAUNCH_ACTIVITY"), + 32: .same(proto: "DISMISS_PRIVACY_NOTIFICATION"), + 33: .same(proto: "TAP_PRIVACY_NOTIFICATION"), + 34: .same(proto: "TAP_HELP"), + 35: .same(proto: "TAP_FEEDBACK"), + 36: .same(proto: "ADD_QUICK_SETTINGS_TILE"), + 37: .same(proto: "REMOVE_QUICK_SETTINGS_TILE"), + 38: .same(proto: "LAUNCH_PHONE_CONSENT"), + 39: .same(proto: "TAP_QUICK_SETTINGS_TILE"), + 40: .same(proto: "INSTALL_APK"), + 41: .same(proto: "VERIFY_APK"), + 42: .same(proto: "LAUNCH_CONSENT"), + 43: .same(proto: "PROCESS_RECEIVED_ATTACHMENTS_END"), + 44: .same(proto: "TOGGLE_SHOW_NOTIFICATION"), + 45: .same(proto: "SET_DEVICE_NAME"), + 46: .same(proto: "DECLINE_AGREEMENTS"), + 47: .same(proto: "REQUEST_SETTING_PERMISSIONS"), + 48: .same(proto: "ESTABLISH_CONNECTION"), + 49: .same(proto: "DEVICE_SETTINGS"), + 50: .same(proto: "AUTO_DISMISS_FAST_INITIALIZATION"), + 51: .same(proto: "APP_CRASH"), + 52: .same(proto: "TAP_QUICK_SETTINGS_FILE_SHARE"), + 53: .same(proto: "DISPLAY_PRIVACY_NOTIFICATION"), + 54: .same(proto: "DISPLAY_PHONE_CONSENT"), + 55: .same(proto: "PREFERENCES_USAGE"), + 56: .same(proto: "DEFAULT_OPT_IN"), + 57: .same(proto: "SETUP_WIZARD"), + 58: .same(proto: "TAP_QR_CODE"), + 59: .same(proto: "QR_CODE_LINK_SHOWN"), + 60: .same(proto: "PARSING_FAILED_ENDPOINT_ID"), + 61: .same(proto: "FAST_INIT_DISCOVER_DEVICE"), + 62: .same(proto: "SEND_DESKTOP_NOTIFICATION"), + 63: .same(proto: "SET_ACCOUNT"), + 64: .same(proto: "DECRYPT_CERTIFICATE_FAILURE"), + 65: .same(proto: "SHOW_ALLOW_PERMISSION_AUTO_ACCESS"), + 66: .same(proto: "SEND_DESKTOP_TRANSFER_EVENT"), + 67: .same(proto: "WAITING_FOR_ACCEPT"), + 68: .same(proto: "HIGH_QUALITY_MEDIUM_SETUP"), + 69: .same(proto: "RPC_CALL_STATUS"), + 70: .same(proto: "START_QR_CODE_SESSION"), + 71: .same(proto: "QR_CODE_OPENED_IN_WEB_CLIENT"), + 72: .same(proto: "HATS_JOINT_EVENT"), + 73: .same(proto: "RECEIVE_PREVIEWS"), + ] +} + +extension Location_Nearby_Proto_Sharing_EventCategory: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "UNKNOWN_EVENT_CATEGORY"), + 1: .same(proto: "SENDING_EVENT"), + 2: .same(proto: "RECEIVING_EVENT"), + 3: .same(proto: "SETTINGS_EVENT"), + 4: .same(proto: "RPC_EVENT"), + ] +} + +extension Location_Nearby_Proto_Sharing_NearbySharingStatus: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "UNKNOWN_NEARBY_SHARING_STATUS"), + 1: .same(proto: "ON"), + 2: .same(proto: "OFF"), + ] +} + +extension Location_Nearby_Proto_Sharing_Visibility: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "UNKNOWN_VISIBILITY"), + 1: .same(proto: "CONTACTS_ONLY"), + 2: .same(proto: "EVERYONE"), + 3: .same(proto: "SELECTED_CONTACTS_ONLY"), + 4: .same(proto: "HIDDEN"), + 5: .same(proto: "SELF_SHARE"), + ] +} + +extension Location_Nearby_Proto_Sharing_DataUsage: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "UNKNOWN_DATA_USAGE"), + 1: .same(proto: "ONLINE"), + 2: .same(proto: "WIFI_ONLY"), + 3: .same(proto: "OFFLINE"), + ] +} + +extension Location_Nearby_Proto_Sharing_EstablishConnectionStatus: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "CONNECTION_STATUS_UNKNOWN"), + 1: .same(proto: "CONNECTION_STATUS_SUCCESS"), + 2: .same(proto: "CONNECTION_STATUS_FAILURE"), + 3: .same(proto: "CONNECTION_STATUS_CANCELLATION"), + 4: .same(proto: "CONNECTION_STATUS_MEDIA_UNAVAILABLE_ATTACHMENT"), + 5: .same(proto: "CONNECTION_STATUS_FAILED_PAIRED_KEYHANDSHAKE"), + 6: .same(proto: "CONNECTION_STATUS_FAILED_WRITE_INTRODUCTION"), + 7: .same(proto: "CONNECTION_STATUS_FAILED_NULL_CONNECTION"), + 8: .same(proto: "CONNECTION_STATUS_FAILED_NO_TRANSFER_UPDATE_CALLBACK"), + 9: .same(proto: "CONNECTION_STATUS_LOST_CONNECTIVITY"), + 10: .same(proto: "CONNECTION_STATUS_INVALID_ADVERTISEMENT"), + ] +} + +extension Location_Nearby_Proto_Sharing_AttachmentTransmissionStatus: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "UNKNOWN_ATTACHMENT_TRANSMISSION_STATUS"), + 1: .same(proto: "COMPLETE_ATTACHMENT_TRANSMISSION_STATUS"), + 2: .same(proto: "CANCELED_ATTACHMENT_TRANSMISSION_STATUS"), + 3: .same(proto: "FAILED_ATTACHMENT_TRANSMISSION_STATUS"), + 4: .same(proto: "REJECTED_ATTACHMENT"), + 5: .same(proto: "TIMED_OUT_ATTACHMENT"), + 6: .same(proto: "AWAITING_REMOTE_ACCEPTANCE_FAILED_ATTACHMENT"), + 7: .same(proto: "NOT_ENOUGH_SPACE_ATTACHMENT"), + 8: .same(proto: "FAILED_NO_TRANSFER_UPDATE_CALLBACK"), + 9: .same(proto: "MEDIA_UNAVAILABLE_ATTACHMENT"), + 10: .same(proto: "UNSUPPORTED_ATTACHMENT_TYPE_ATTACHMENT"), + 11: .same(proto: "NO_ATTACHMENT_FOUND"), + 12: .same(proto: "FAILED_NO_SHARE_TARGET_ENDPOINT"), + 13: .same(proto: "FAILED_PAIRED_KEYHANDSHAKE"), + 14: .same(proto: "FAILED_NULL_CONNECTION"), + 15: .same(proto: "FAILED_NO_PAYLOAD"), + 16: .same(proto: "FAILED_WRITE_INTRODUCTION"), + 17: .same(proto: "FAILED_UNKNOWN_REMOTE_RESPONSE"), + 18: .same(proto: "FAILED_NULL_CONNECTION_INIT_OUTGOING"), + 19: .same(proto: "FAILED_NULL_CONNECTION_DISCONNECTED"), + 20: .same(proto: "FAILED_NULL_CONNECTION_LOST_CONNECTIVITY"), + 21: .same(proto: "FAILED_NULL_CONNECTION_FAILURE"), + 22: .same(proto: "REJECTED_ATTACHMENT_TRANSMISSION_STATUS"), + 23: .same(proto: "TIMED_OUT_ATTACHMENT_TRANSMISSION_STATUS"), + 24: .same(proto: "NOT_ENOUGH_SPACE_ATTACHMENT_TRANSMISSION_STATUS"), + 25: .same(proto: "UNSUPPORTED_ATTACHMENT_TYPE_ATTACHMENT_TRANSMISSION_STATUS"), + 26: .same(proto: "FAILED_UNKNOWN_REMOTE_RESPONSE_TRANSMISSION_STATUS"), + 27: .same(proto: "NO_RESPONSE_FRAME_CONNECTION_CLOSED_LOST_CONNECTIVITY_TRANSMISSION_STATUS"), + 28: .same(proto: "NO_RESPONSE_FRAME_CONNECTION_CLOSED_TRANSMISSION_STATUS"), + 29: .same(proto: "LOST_CONNECTIVITY_TRANSMISSION_STATUS"), + 30: .same(proto: "FAILED_DISALLOWED_MEDIUM"), + ] +} + +extension Location_Nearby_Proto_Sharing_ConnectionLayerStatus: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "CONNECTION_LAYER_STATUS_UNKNOWN"), + 1: .same(proto: "CONNECTION_LAYER_STATUS_SUCCESS"), + 2: .same(proto: "CONNECTION_LAYER_STATUS_ERROR"), + 3: .same(proto: "CONNECTION_LAYER_STATUS_OUT_OF_ORDER_API_CALL"), + 4: .same(proto: "CONNECTION_LAYER_STATUS_ALREADY_HAVE_ACTIVE_STRATEGY"), + 5: .same(proto: "CONNECTION_LAYER_STATUS_ALREADY_ADVERTISING"), + 6: .same(proto: "CONNECTION_LAYER_STATUS_ALREADY_DISCOVERING"), + 7: .same(proto: "CONNECTION_LAYER_STATUS_ALREADY_LISTENING"), + 8: .same(proto: "CONNECTION_LAYER_STATUS_END_POINT_IO_ERROR"), + 9: .same(proto: "CONNECTION_LAYER_STATUS_END_POINT_UNKNOWN"), + 10: .same(proto: "CONNECTION_LAYER_STATUS_CONNECTION_REJECTED"), + 11: .same(proto: "CONNECTION_LAYER_STATUS_ALREADY_CONNECTED_TO_END_POINT"), + 12: .same(proto: "CONNECTION_LAYER_STATUS_NOT_CONNECTED_TO_END_POINT"), + 13: .same(proto: "CONNECTION_LAYER_STATUS_BLUETOOTH_ERROR"), + 14: .same(proto: "CONNECTION_LAYER_STATUS_BLE_ERROR"), + 15: .same(proto: "CONNECTION_LAYER_STATUS_WIFI_LAN_ERROR"), + 16: .same(proto: "CONNECTION_LAYER_STATUS_PAYLOAD_UNKNOWN"), + 17: .same(proto: "CONNECTION_LAYER_STATUS_RESET"), + 18: .same(proto: "CONNECTION_LAYER_STATUS_TIMEOUT"), + ] +} + +extension Location_Nearby_Proto_Sharing_ProcessReceivedAttachmentsStatus: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "PROCESSING_STATUS_UNKNOWN"), + 1: .same(proto: "PROCESSING_STATUS_COMPLETE_PROCESSING_ATTACHMENTS"), + 2: .same(proto: "PROCESSING_STATUS_FAILED_MOVING_FILES"), + 3: .same(proto: "PROCESSING_STATUS_FAILED_RECEIVING_APK"), + 4: .same(proto: "PROCESSING_STATUS_FAILED_RECEIVING_TEXT"), + 5: .same(proto: "PROCESSING_STATUS_FAILED_RECEIVING_WIFI_CREDENTIALS"), + ] +} + +extension Location_Nearby_Proto_Sharing_SessionStatus: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "UNKNOWN_SESSION_STATUS"), + 1: .same(proto: "SUCCEEDED_SESSION_STATUS"), + 2: .same(proto: "FAILED_SESSION_STATUS"), + ] +} + +extension Location_Nearby_Proto_Sharing_ResponseToIntroduction: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "UNKNOWN_RESPONSE_TO_INTRODUCTION"), + 1: .same(proto: "ACCEPT_INTRODUCTION"), + 2: .same(proto: "REJECT_INTRODUCTION"), + 3: .same(proto: "FAIL_INTRODUCTION"), + ] +} + +extension Location_Nearby_Proto_Sharing_DeviceType: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "UNKNOWN_DEVICE_TYPE"), + 1: .same(proto: "PHONE"), + 2: .same(proto: "TABLET"), + 3: .same(proto: "LAPTOP"), + 4: .same(proto: "CAR"), + 5: .same(proto: "FOLDABLE"), + 6: .same(proto: "XR"), + ] +} + +extension Location_Nearby_Proto_Sharing_OSType: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "UNKNOWN_OS_TYPE"), + 1: .same(proto: "ANDROID"), + 2: .same(proto: "CHROME_OS"), + 3: .same(proto: "IOS"), + 4: .same(proto: "WINDOWS"), + 5: .same(proto: "MACOS"), + ] +} + +extension Location_Nearby_Proto_Sharing_DeviceRelationship: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "UNKNOWN_DEVICE_RELATIONSHIP"), + 1: .same(proto: "IS_SELF"), + 2: .same(proto: "IS_CONTACT"), + 3: .same(proto: "IS_STRANGER"), + ] +} + +extension Location_Nearby_Proto_Sharing_LogSource: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "UNSPECIFIED_SOURCE"), + 1: .same(proto: "LAB_DEVICES"), + 2: .same(proto: "INTERNAL_DEVICES"), + 3: .same(proto: "BETA_TESTER_DEVICES"), + 4: .same(proto: "OEM_DEVICES"), + 5: .same(proto: "DEBUG_DEVICES"), + 6: .same(proto: "NEARBY_MODULE_FOOD_DEVICES"), + 7: .same(proto: "BETO_DOGFOOD_DEVICES"), + 8: .same(proto: "NEARBY_DOGFOOD_DEVICES"), + 9: .same(proto: "NEARBY_TEAMFOOD_DEVICES"), + ] +} + +extension Location_Nearby_Proto_Sharing_ServerActionName: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "UNKNOWN_SERVER_ACTION"), + 1: .same(proto: "UPLOAD_CERTIFICATES"), + 2: .same(proto: "DOWNLOAD_CERTIFICATES"), + 3: .same(proto: "CHECK_REACHABILITY"), + 4: .same(proto: "UPLOAD_CONTACTS"), + 5: .same(proto: "UPDATE_DEVICE_NAME"), + 6: .same(proto: "UPLOAD_SENDER_CERTIFICATES"), + 7: .same(proto: "DOWNLOAD_SENDER_CERTIFICATES"), + 8: .same(proto: "UPLOAD_CONTACTS_AND_CERTIFICATES"), + 9: .same(proto: "LIST_REACHABLE_PHONE_NUMBERS"), + 10: .same(proto: "LIST_MY_DEVICES"), + 11: .same(proto: "LIST_CONTACT_PEOPLE"), + 12: .same(proto: "DOWNLOAD_CERTIFICATES_INFO"), + ] +} + +extension Location_Nearby_Proto_Sharing_ServerResponseState: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "UNKNOWN_SERVER_RESPONSE_STATE"), + 1: .same(proto: "SERVER_RESPONSE_SUCCESS"), + 2: .same(proto: "SERVER_RESPONSE_UNKNOWN_FAILURE"), + 3: .same(proto: "SERVER_RESPONSE_STATUS_OTHER_FAILURE"), + 4: .same(proto: "SERVER_RESPONSE_STATUS_DEADLINE_EXCEEDED"), + 5: .same(proto: "SERVER_RESPONSE_STATUS_PERMISSION_DENIED"), + 6: .same(proto: "SERVER_RESPONSE_STATUS_UNAVAILABLE"), + 7: .same(proto: "SERVER_RESPONSE_STATUS_UNAUTHENTICATED"), + 8: .same(proto: "SERVER_RESPONSE_GOOGLE_AUTH_FAILURE"), + 9: .same(proto: "SERVER_RESPONSE_STATUS_INVALID_ARGUMENT"), + 10: .same(proto: "SERVER_RESPONSE_NOT_CONNECTED_TO_INTERNET"), + ] +} + +extension Location_Nearby_Proto_Sharing_SyncPurpose: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "SYNC_PURPOSE_UNKNOWN"), + 1: .same(proto: "SYNC_PURPOSE_ON_DEMAND_SYNC"), + 2: .same(proto: "SYNC_PURPOSE_CHIME_NOTIFICATION"), + 3: .same(proto: "SYNC_PURPOSE_DAILY_SYNC"), + 4: .same(proto: "SYNC_PURPOSE_OPT_IN_FIRST_SYNC"), + 5: .same(proto: "SYNC_PURPOSE_CHECK_DEFAULT_OPT_IN"), + 6: .same(proto: "SYNC_PURPOSE_NEARBY_SHARE_ENABLED"), + 7: .same(proto: "SYNC_PURPOSE_SYNC_AT_FAST_INIT"), + 8: .same(proto: "SYNC_PURPOSE_SYNC_AT_DISCOVERY"), + 9: .same(proto: "SYNC_PURPOSE_SYNC_AT_LOAD_PRIVATE_CERTIFICATE"), + 10: .same(proto: "SYNC_PURPOSE_SYNC_AT_ADVERTISEMENT"), + 11: .same(proto: "SYNC_PURPOSE_CONTACT_LIST_CHANGE"), + 12: .same(proto: "SYNC_PURPOSE_SHOW_C11N_VIEW"), + 13: .same(proto: "SYNC_PURPOSE_REGULAR_CHECK_CONTACT_REACHABILITY"), + 14: .same(proto: "SYNC_PURPOSE_VISIBILITY_SELECTED_CONTACT_CHANGE"), + 15: .same(proto: "SYNC_PURPOSE_ACCOUNT_CHANGE"), + 16: .same(proto: "SYNC_PURPOSE_REGENERATE_CERTIFICATES"), + 17: .same(proto: "SYNC_PURPOSE_DEVICE_CONTACTS_CONSENT_CHANGE"), + ] +} + +extension Location_Nearby_Proto_Sharing_ClientRole: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "CLIENT_ROLE_UNKNOWN"), + 1: .same(proto: "CLIENT_ROLE_SENDER"), + 2: .same(proto: "CLIENT_ROLE_RECEIVER"), + ] +} + +extension Location_Nearby_Proto_Sharing_ScanType: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "UNKNOWN_SCAN_TYPE"), + 1: .same(proto: "FOREGROUND_SCAN"), + 2: .same(proto: "FOREGROUND_RETRY_SCAN"), + 3: .same(proto: "DIRECT_SHARE_SCAN"), + 4: .same(proto: "BACKGROUND_SCAN"), + ] +} + +extension Location_Nearby_Proto_Sharing_ParsingFailedType: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "FAILED_UNKNOWN_TYPE"), + 1: .same(proto: "FAILED_PARSE_ADVERTISEMENT"), + 2: .same(proto: "FAILED_CONVERT_SHARE_TARGET"), + ] +} + +extension Location_Nearby_Proto_Sharing_AdvertisingMode: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "UNKNOWN_ADVERTISING_MODE"), + 1: .same(proto: "SCREEN_OFF_ADVERTISING_MODE"), + 2: .same(proto: "BACKGROUND_ADVERTISING_MODE"), + 3: .same(proto: "MIDGROUND_ADVERTISING_MODE"), + 4: .same(proto: "FOREGROUND_ADVERTISING_MODE"), + 5: .same(proto: "SUSPENDED_ADVERTISING_MODE"), + ] +} + +extension Location_Nearby_Proto_Sharing_DiscoveryMode: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "UNKNOWN_DISCOVERY_MODE"), + 1: .same(proto: "SCREEN_OFF_DISCOVERY_MODE"), + 2: .same(proto: "BACKGROUND_DISCOVERY_MODE"), + 3: .same(proto: "MIDGROUND_DISCOVERY_MODE"), + 4: .same(proto: "FOREGROUND_DISCOVERY_MODE"), + 5: .same(proto: "SUSPENDED_DISCOVERY_MODE"), + ] +} + +extension Location_Nearby_Proto_Sharing_ActivityName: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "UNKNOWN_ACTIVITY"), + 1: .same(proto: "SHARE_SHEET_ACTIVITY"), + 2: .same(proto: "SETTINGS_ACTIVITY"), + 3: .same(proto: "RECEIVE_SURFACE_ACTIVITY"), + 4: .same(proto: "SETUP_ACTIVITY"), + 5: .same(proto: "DEVICE_VISIBILITY_ACTIVITY"), + 6: .same(proto: "CONSENTS_ACTIVITY"), + 7: .same(proto: "SET_DEVICE_NAME_DIALOG"), + 8: .same(proto: "SET_DATA_USAGE_DIALOG"), + 9: .same(proto: "QUICK_SETTINGS_ACTIVITY"), + 10: .same(proto: "REMOTE_COPY_SHARE_SHEET_ACTIVITY"), + 11: .same(proto: "SETUP_WIZARD_ACTIVITY"), + 12: .same(proto: "SETTINGS_REVIEW_ACTIVITY"), + ] +} + +extension Location_Nearby_Proto_Sharing_ConsentType: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "CONSENT_TYPE_UNKNOWN"), + 1: .same(proto: "CONSENT_TYPE_C11N"), + 2: .same(proto: "CONSENT_TYPE_DEVICE_CONTACT"), + ] +} + +extension Location_Nearby_Proto_Sharing_ConsentAcceptanceStatus: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "CONSENT_UNKNOWN_ACCEPT_STATUS"), + 1: .same(proto: "CONSENT_ACCEPTED"), + 2: .same(proto: "CONSENT_DECLINED"), + 3: .same(proto: "CONSENT_UNABLE_TO_ENABLE"), + ] +} + +extension Location_Nearby_Proto_Sharing_ApkSource: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "UNKNOWN_APK_SOURCE"), + 1: .same(proto: "APK_FROM_SD_CARD"), + 2: .same(proto: "INSTALLED_APP"), + ] +} + +extension Location_Nearby_Proto_Sharing_InstallAPKStatus: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "UNKNOWN_INSTALL_APK_STATUS"), + 1: .same(proto: "FAIL_INSTALLATION"), + 2: .same(proto: "SUCCESS_INSTALLATION"), + ] +} + +extension Location_Nearby_Proto_Sharing_VerifyAPKStatus: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "UNKNOWN_VERIFY_APK_STATUS"), + 1: .same(proto: "NOT_INSTALLABLE"), + 2: .same(proto: "INSTALLABLE"), + 3: .same(proto: "ALREADY_INSTALLED"), + ] +} + +extension Location_Nearby_Proto_Sharing_ShowNotificationStatus: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "UNKNOWN_SHOW_NOTIFICATION_STATUS"), + 1: .same(proto: "SHOW"), + 2: .same(proto: "NOT_SHOW"), + ] +} + +extension Location_Nearby_Proto_Sharing_PermissionRequestResult: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "PERMISSION_UNKNOWN_REQUEST_RESULT"), + 1: .same(proto: "PERMISSION_GRANTED"), + 2: .same(proto: "PERMISSION_REJECTED"), + 3: .same(proto: "PERMISSION_UNABLE_TO_GRANT"), + ] +} + +extension Location_Nearby_Proto_Sharing_PermissionRequestType: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "PERMISSION_UNKNOWN_TYPE"), + 1: .same(proto: "PERMISSION_AIRPLANE_MODE_OFF"), + 2: .same(proto: "PERMISSION_WIFI"), + 3: .same(proto: "PERMISSION_BLUETOOTH"), + 4: .same(proto: "PERMISSION_LOCATION"), + 5: .same(proto: "PERMISSION_WIFI_HOTSPOT"), + ] +} + +extension Location_Nearby_Proto_Sharing_SharingUseCase: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "USE_CASE_UNKNOWN"), + 1: .same(proto: "USE_CASE_NEARBY_SHARE"), + 2: .same(proto: "USE_CASE_REMOTE_COPY_PASTE"), + 3: .same(proto: "USE_CASE_WIFI_CREDENTIAL"), + 4: .same(proto: "USE_CASE_APP_SHARE"), + 5: .same(proto: "USE_CASE_QUICK_SETTING_FILE_SHARE"), + 6: .same(proto: "USE_CASE_SETUP_WIZARD"), + 7: .same(proto: "USE_CASE_NEARBY_SHARE_WITH_QR_CODE"), + 8: .same(proto: "USE_CASE_REDIRECTED_FROM_BLUETOOTH_SHARE"), + ] +} + +extension Location_Nearby_Proto_Sharing_AppCrashReason: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "APP_CRASH_REASON_UNKNOWN"), + ] +} + +extension Location_Nearby_Proto_Sharing_AttachmentSourceType: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "ATTACHMENT_SOURCE_UNKNOWN"), + 1: .same(proto: "ATTACHMENT_SOURCE_CONTEXT_MENU"), + 2: .same(proto: "ATTACHMENT_SOURCE_DRAG_AND_DROP"), + 3: .same(proto: "ATTACHMENT_SOURCE_SELECT_FILES_BUTTON"), + 4: .same(proto: "ATTACHMENT_SOURCE_PASTE"), + 5: .same(proto: "ATTACHMENT_SOURCE_SELECT_FOLDERS_BUTTON"), + 6: .same(proto: "ATTACHMENT_SOURCE_SHARE_ACTIVATION"), + ] +} + +extension Location_Nearby_Proto_Sharing_PreferencesAction: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "PREFERENCES_ACTION_UNKNOWN"), + 1: .same(proto: "PREFERENCES_ACTION_NO_ACTION"), + 2: .same(proto: "PREFERENCES_ACTION_LOAD_PREFERENCES"), + 3: .same(proto: "PREFERENCES_ACTION_SAVE_PREFERENCESS"), + 4: .same(proto: "PREFERENCES_ACTION_ATTEMPT_LOAD"), + 5: .same(proto: "PREFERENCES_ACTION_RESTORE_FROM_BACKUP"), + 6: .same(proto: "PREFERENCES_ACTION_CREATE_PREFERENCES_PATH"), + 7: .same(proto: "PREFERENCES_ACTION_MAKE_PREFERENCES_BACKUP_FILE"), + 8: .same(proto: "PREFERENCES_ACTION_CHECK_IF_PREFERENCES_PATH_EXISTS"), + 9: .same(proto: "PREFERENCES_ACTION_CHECK_IF_PREFERENCES_INPUT_STREAM_STATUS"), + 10: .same(proto: "PREFERENCES_ACTION_CHECK_IF_PREFERENCES_FILE_IS_CORRUPTED"), + 11: .same(proto: "PREFERENCES_ACTION_CHECK_IF_PREFERENCES_BACKUP_FILE_EXISTS"), + ] +} + +extension Location_Nearby_Proto_Sharing_PreferencesActionStatus: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "PREFERENCES_ACTION_STATUS_UNKNOWN"), + 1: .same(proto: "PREFERENCES_ACTION_STATUS_SUCCESS"), + 2: .same(proto: "PREFERENCES_ACTION_STATUS_FAIL"), + ] +} + +extension Location_Nearby_Proto_Sharing_FastInitState: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "FAST_INIT_UNKNOWN_STATE"), + 1: .same(proto: "FAST_INIT_CLOSE_STATE"), + 2: .same(proto: "FAST_INIT_FAR_STATE"), + 3: .same(proto: "FAST_INIT_LOST_STATE"), + ] +} + +extension Location_Nearby_Proto_Sharing_FastInitType: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "FAST_INIT_UNKNOWN_TYPE"), + 1: .same(proto: "FAST_INIT_NOTIFY_TYPE"), + 2: .same(proto: "FAST_INIT_SILENT_TYPE"), + ] +} + +extension Location_Nearby_Proto_Sharing_DesktopNotification: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "DESKTOP_NOTIFICATION_UNKNOWN"), + 1: .same(proto: "DESKTOP_NOTIFICATION_CONNECTING"), + 2: .same(proto: "DESKTOP_NOTIFICATION_PROGRESS"), + 3: .same(proto: "DESKTOP_NOTIFICATION_ACCEPT"), + 4: .same(proto: "DESKTOP_NOTIFICATION_RECEIVED"), + 5: .same(proto: "DESKTOP_NOTIFICATION_ERROR"), + ] +} + +extension Location_Nearby_Proto_Sharing_DesktopTransferEventType: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "DESKTOP_TRANSFER_EVENT_TYPE_UNKNOWN"), + 1: .same(proto: "DESKTOP_TRANSFER_EVENT_RECEIVE_TYPE_ACCEPT"), + 2: .same(proto: "DESKTOP_TRANSFER_EVENT_RECEIVE_TYPE_PROGRESS"), + 3: .same(proto: "DESKTOP_TRANSFER_EVENT_RECEIVE_TYPE_RECEIVED"), + 4: .same(proto: "DESKTOP_TRANSFER_EVENT_RECEIVE_TYPE_ERROR"), + 5: .same(proto: "DESKTOP_TRANSFER_EVENT_SEND_TYPE_START"), + 6: .same(proto: "DESKTOP_TRANSFER_EVENT_SEND_TYPE_SELECT_A_DEVICE"), + 7: .same(proto: "DESKTOP_TRANSFER_EVENT_SEND_TYPE_PROGRESS"), + 8: .same(proto: "DESKTOP_TRANSFER_EVENT_SEND_TYPE_SENT"), + 9: .same(proto: "DESKTOP_TRANSFER_EVENT_SEND_TYPE_ERROR"), + ] +} + +extension Location_Nearby_Proto_Sharing_DecryptCertificateFailureStatus: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "DECRYPT_CERT_UNKNOWN_FAILURE"), + 1: .same(proto: "DECRYPT_CERT_NO_SUCH_ALGORITHM_FAILURE"), + 2: .same(proto: "DECRYPT_CERT_NO_SUCH_PADDING_FAILURE"), + 3: .same(proto: "DECRYPT_CERT_INVALID_KEY_FAILURE"), + 4: .same(proto: "DECRYPT_CERT_INVALID_ALGORITHM_PARAMETER_FAILURE"), + 5: .same(proto: "DECRYPT_CERT_ILLEGAL_BLOCK_SIZE_FAILURE"), + 6: .same(proto: "DECRYPT_CERT_BAD_PADDING_FAILURE"), + ] +} + +extension Location_Nearby_Proto_Sharing_ContactAccess: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "CONTACT_ACCESS_UNKNOWN"), + 1: .same(proto: "CONTACT_ACCESS_NO_CONTACT_UPLOADED"), + 2: .same(proto: "CONTACT_ACCESS_ONLY_UPLOAD_GOOGLE_CONTACT"), + 3: .same(proto: "CONTACT_ACCESS_UPLOAD_CONTACT_FOR_DEVICE_CONTACT_CONSENT"), + 4: .same(proto: "CONTACT_ACCESS_UPLOAD_CONTACT_FOR_QUICK_SHARE_CONSENT"), + ] +} + +extension Location_Nearby_Proto_Sharing_IdentityVerification: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "IDENTITY_VERIFICATION_UNKNOWN"), + 1: .same(proto: "IDENTITY_VERIFICATION_NO_PHONE_NUMBER_VERIFIED"), + 2: .same(proto: "IDENTITY_VERIFICATION_PHONE_NUMBER_VERIFIED_NOT_LINKED_TO_GAIA"), + 3: .same(proto: "IDENTITY_VERIFICATION_PHONE_NUMBER_VERIFIED_LINKED_TO_QS_GAIA"), + ] +} + +extension Location_Nearby_Proto_Sharing_ButtonStatus: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "BUTTON_STATUS_UNKNOWN"), + 1: .same(proto: "BUTTON_STATUS_CLICK_ACCEPT"), + 2: .same(proto: "BUTTON_STATUS_CLICK_REJECT"), + 3: .same(proto: "BUTTON_STATUS_IGNORE"), + ] +} diff --git a/NearbyShare/Model/Protobuf/wire_format.pb.swift b/NearbyShare/Model/Protobuf/wire_format.pb.swift index d8c5bd0..a99c16f 100644 --- a/NearbyShare/Model/Protobuf/wire_format.pb.swift +++ b/NearbyShare/Model/Protobuf/wire_format.pb.swift @@ -7,12 +7,19 @@ // For information on using the generated types, please see the documentation: // https://github.com/apple/swift-protobuf/ -// Copyright 2020 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// Brought from: //depot/google3/location/nearby/sharing/proto/wire_format.proto -// At CL 317565061 +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. import Foundation import SwiftProtobuf @@ -28,7 +35,7 @@ fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAP } /// File metadata. Does not include the actual bytes of the file. -/// NEXT_ID=6 +/// NEXT_ID=10 struct Sharing_Nearby_FileMetadata { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -97,6 +104,37 @@ struct Sharing_Nearby_FileMetadata { /// Clears the value of `id`. Subsequent reads from it will return its default value. mutating func clearID() {self._id = nil} + /// The parent folder. + var parentFolder: String { + get {return _parentFolder ?? String()} + set {_parentFolder = newValue} + } + /// Returns true if `parentFolder` has been explicitly set. + var hasParentFolder: Bool {return self._parentFolder != nil} + /// Clears the value of `parentFolder`. Subsequent reads from it will return its default value. + mutating func clearParentFolder() {self._parentFolder = nil} + + /// A stable identifier for the attachment. Used for receiver to identify same + /// attachment from different transfers. + var attachmentHash: Int64 { + get {return _attachmentHash ?? 0} + set {_attachmentHash = newValue} + } + /// Returns true if `attachmentHash` has been explicitly set. + var hasAttachmentHash: Bool {return self._attachmentHash != nil} + /// Clears the value of `attachmentHash`. Subsequent reads from it will return its default value. + mutating func clearAttachmentHash() {self._attachmentHash = nil} + + /// True, if image in file attachment is sensitive + var isSensitiveContent: Bool { + get {return _isSensitiveContent ?? false} + set {_isSensitiveContent = newValue} + } + /// Returns true if `isSensitiveContent` has been explicitly set. + var hasIsSensitiveContent: Bool {return self._isSensitiveContent != nil} + /// Clears the value of `isSensitiveContent`. Subsequent reads from it will return its default value. + mutating func clearIsSensitiveContent() {self._isSensitiveContent = nil} + var unknownFields = SwiftProtobuf.UnknownStorage() enum TypeEnum: SwiftProtobuf.Enum { @@ -104,8 +142,10 @@ struct Sharing_Nearby_FileMetadata { case unknown // = 0 case image // = 1 case video // = 2 - case app // = 3 + case androidApp // = 3 case audio // = 4 + case document // = 5 + case contactCard // = 6 init() { self = .unknown @@ -116,8 +156,10 @@ struct Sharing_Nearby_FileMetadata { case 0: self = .unknown case 1: self = .image case 2: self = .video - case 3: self = .app + case 3: self = .androidApp case 4: self = .audio + case 5: self = .document + case 6: self = .contactCard default: return nil } } @@ -127,8 +169,10 @@ struct Sharing_Nearby_FileMetadata { case .unknown: return 0 case .image: return 1 case .video: return 2 - case .app: return 3 + case .androidApp: return 3 case .audio: return 4 + case .document: return 5 + case .contactCard: return 6 } } @@ -142,6 +186,9 @@ struct Sharing_Nearby_FileMetadata { fileprivate var _size: Int64? = nil fileprivate var _mimeType: String? = nil fileprivate var _id: Int64? = nil + fileprivate var _parentFolder: String? = nil + fileprivate var _attachmentHash: Int64? = nil + fileprivate var _isSensitiveContent: Bool? = nil } #if swift(>=4.2) @@ -152,7 +199,7 @@ extension Sharing_Nearby_FileMetadata.TypeEnum: CaseIterable { #endif // swift(>=4.2) -/// NEXT_ID=5 +/// NEXT_ID=8 struct Sharing_Nearby_TextMetadata { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -209,6 +256,16 @@ struct Sharing_Nearby_TextMetadata { /// Clears the value of `id`. Subsequent reads from it will return its default value. mutating func clearID() {self._id = nil} + /// True if text is sensitive, e.g. password + var isSensitiveText: Bool { + get {return _isSensitiveText ?? false} + set {_isSensitiveText = newValue} + } + /// Returns true if `isSensitiveText` has been explicitly set. + var hasIsSensitiveText: Bool {return self._isSensitiveText != nil} + /// Clears the value of `isSensitiveText`. Subsequent reads from it will return its default value. + mutating func clearIsSensitiveText() {self._isSensitiveText = nil} + var unknownFields = SwiftProtobuf.UnknownStorage() enum TypeEnum: SwiftProtobuf.Enum { @@ -259,6 +316,7 @@ struct Sharing_Nearby_TextMetadata { fileprivate var _payloadID: Int64? = nil fileprivate var _size: Int64? = nil fileprivate var _id: Int64? = nil + fileprivate var _isSensitiveText: Bool? = nil } #if swift(>=4.2) @@ -269,7 +327,7 @@ extension Sharing_Nearby_TextMetadata.TypeEnum: CaseIterable { #endif // swift(>=4.2) -/// NEXT_ID=5 +/// NEXT_ID=6 struct Sharing_Nearby_WifiCredentialsMetadata { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -324,6 +382,7 @@ struct Sharing_Nearby_WifiCredentialsMetadata { case `open` // = 1 case wpaPsk // = 2 case wep // = 3 + case sae // = 4 init() { self = .unknownSecurityType @@ -335,6 +394,7 @@ struct Sharing_Nearby_WifiCredentialsMetadata { case 1: self = .open case 2: self = .wpaPsk case 3: self = .wep + case 4: self = .sae default: return nil } } @@ -345,6 +405,7 @@ struct Sharing_Nearby_WifiCredentialsMetadata { case .open: return 1 case .wpaPsk: return 2 case .wep: return 3 + case .sae: return 4 } } @@ -366,6 +427,131 @@ extension Sharing_Nearby_WifiCredentialsMetadata.SecurityType: CaseIterable { #endif // swift(>=4.2) +/// NEXT_ID=8 +struct Sharing_Nearby_AppMetadata { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// The app name. This will be sent in introduction. + var appName: String { + get {return _appName ?? String()} + set {_appName = newValue} + } + /// Returns true if `appName` has been explicitly set. + var hasAppName: Bool {return self._appName != nil} + /// Clears the value of `appName`. Subsequent reads from it will return its default value. + mutating func clearAppName() {self._appName = nil} + + /// The size of the all split of apks. + var size: Int64 { + get {return _size ?? 0} + set {_size = newValue} + } + /// Returns true if `size` has been explicitly set. + var hasSize: Bool {return self._size != nil} + /// Clears the value of `size`. Subsequent reads from it will return its default value. + mutating func clearSize() {self._size = nil} + + /// The File payload id that will be sent as a follow up containing the + /// apk paths. + var payloadID: [Int64] = [] + + /// A uuid for the attachment. Should be unique across all attachments. + var id: Int64 { + get {return _id ?? 0} + set {_id = newValue} + } + /// Returns true if `id` has been explicitly set. + var hasID: Bool {return self._id != nil} + /// Clears the value of `id`. Subsequent reads from it will return its default value. + mutating func clearID() {self._id = nil} + + /// The name of apk file. This will be sent in introduction. + var fileName: [String] = [] + + /// The size of apk file. This will be sent in introduction. + var fileSize: [Int64] = [] + + /// The package name. This will be sent in introduction. + var packageName: String { + get {return _packageName ?? String()} + set {_packageName = newValue} + } + /// Returns true if `packageName` has been explicitly set. + var hasPackageName: Bool {return self._packageName != nil} + /// Clears the value of `packageName`. Subsequent reads from it will return its default value. + mutating func clearPackageName() {self._packageName = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _appName: String? = nil + fileprivate var _size: Int64? = nil + fileprivate var _id: Int64? = nil + fileprivate var _packageName: String? = nil +} + +/// NEXT_ID=5 +struct Sharing_Nearby_StreamMetadata { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// A human readable description for the stream. + var description_p: String { + get {return _description_p ?? String()} + set {_description_p = newValue} + } + /// Returns true if `description_p` has been explicitly set. + var hasDescription_p: Bool {return self._description_p != nil} + /// Clears the value of `description_p`. Subsequent reads from it will return its default value. + mutating func clearDescription_p() {self._description_p = nil} + + /// The package name of the sending application. + var packageName: String { + get {return _packageName ?? String()} + set {_packageName = newValue} + } + /// Returns true if `packageName` has been explicitly set. + var hasPackageName: Bool {return self._packageName != nil} + /// Clears the value of `packageName`. Subsequent reads from it will return its default value. + mutating func clearPackageName() {self._packageName = nil} + + /// The payload id that will be send as a followup containing the + /// ParcelFileDescriptor. + var payloadID: Int64 { + get {return _payloadID ?? 0} + set {_payloadID = newValue} + } + /// Returns true if `payloadID` has been explicitly set. + var hasPayloadID: Bool {return self._payloadID != nil} + /// Clears the value of `payloadID`. Subsequent reads from it will return its default value. + mutating func clearPayloadID() {self._payloadID = nil} + + /// The human-readable name of the package that should be displayed as + /// attribution if no other information is available (i.e. the package is not + /// installed locally yet). + var attributedAppName: String { + get {return _attributedAppName ?? String()} + set {_attributedAppName = newValue} + } + /// Returns true if `attributedAppName` has been explicitly set. + var hasAttributedAppName: Bool {return self._attributedAppName != nil} + /// Clears the value of `attributedAppName`. Subsequent reads from it will return its default value. + mutating func clearAttributedAppName() {self._attributedAppName = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _description_p: String? = nil + fileprivate var _packageName: String? = nil + fileprivate var _payloadID: Int64? = nil + fileprivate var _attributedAppName: String? = nil +} + /// A frame used when sending messages over the wire. /// NEXT_ID=3 struct Sharing_Nearby_Frame { @@ -435,66 +621,75 @@ extension Sharing_Nearby_Frame.Version: CaseIterable { #endif // swift(>=4.2) -/// NEXT_ID=7 +/// NEXT_ID=8 struct Sharing_Nearby_V1Frame { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. var type: Sharing_Nearby_V1Frame.FrameType { - get {return _type ?? .unknownFrameType} - set {_type = newValue} + get {return _storage._type ?? .unknownFrameType} + set {_uniqueStorage()._type = newValue} } /// Returns true if `type` has been explicitly set. - var hasType: Bool {return self._type != nil} + var hasType: Bool {return _storage._type != nil} /// Clears the value of `type`. Subsequent reads from it will return its default value. - mutating func clearType() {self._type = nil} + mutating func clearType() {_uniqueStorage()._type = nil} - /// Exactly one of the following fields will be set. + /// At most one of the following fields will be set. var introduction: Sharing_Nearby_IntroductionFrame { - get {return _introduction ?? Sharing_Nearby_IntroductionFrame()} - set {_introduction = newValue} + get {return _storage._introduction ?? Sharing_Nearby_IntroductionFrame()} + set {_uniqueStorage()._introduction = newValue} } /// Returns true if `introduction` has been explicitly set. - var hasIntroduction: Bool {return self._introduction != nil} + var hasIntroduction: Bool {return _storage._introduction != nil} /// Clears the value of `introduction`. Subsequent reads from it will return its default value. - mutating func clearIntroduction() {self._introduction = nil} + mutating func clearIntroduction() {_uniqueStorage()._introduction = nil} var connectionResponse: Sharing_Nearby_ConnectionResponseFrame { - get {return _connectionResponse ?? Sharing_Nearby_ConnectionResponseFrame()} - set {_connectionResponse = newValue} + get {return _storage._connectionResponse ?? Sharing_Nearby_ConnectionResponseFrame()} + set {_uniqueStorage()._connectionResponse = newValue} } /// Returns true if `connectionResponse` has been explicitly set. - var hasConnectionResponse: Bool {return self._connectionResponse != nil} + var hasConnectionResponse: Bool {return _storage._connectionResponse != nil} /// Clears the value of `connectionResponse`. Subsequent reads from it will return its default value. - mutating func clearConnectionResponse() {self._connectionResponse = nil} + mutating func clearConnectionResponse() {_uniqueStorage()._connectionResponse = nil} var pairedKeyEncryption: Sharing_Nearby_PairedKeyEncryptionFrame { - get {return _pairedKeyEncryption ?? Sharing_Nearby_PairedKeyEncryptionFrame()} - set {_pairedKeyEncryption = newValue} + get {return _storage._pairedKeyEncryption ?? Sharing_Nearby_PairedKeyEncryptionFrame()} + set {_uniqueStorage()._pairedKeyEncryption = newValue} } /// Returns true if `pairedKeyEncryption` has been explicitly set. - var hasPairedKeyEncryption: Bool {return self._pairedKeyEncryption != nil} + var hasPairedKeyEncryption: Bool {return _storage._pairedKeyEncryption != nil} /// Clears the value of `pairedKeyEncryption`. Subsequent reads from it will return its default value. - mutating func clearPairedKeyEncryption() {self._pairedKeyEncryption = nil} + mutating func clearPairedKeyEncryption() {_uniqueStorage()._pairedKeyEncryption = nil} var pairedKeyResult: Sharing_Nearby_PairedKeyResultFrame { - get {return _pairedKeyResult ?? Sharing_Nearby_PairedKeyResultFrame()} - set {_pairedKeyResult = newValue} + get {return _storage._pairedKeyResult ?? Sharing_Nearby_PairedKeyResultFrame()} + set {_uniqueStorage()._pairedKeyResult = newValue} } /// Returns true if `pairedKeyResult` has been explicitly set. - var hasPairedKeyResult: Bool {return self._pairedKeyResult != nil} + var hasPairedKeyResult: Bool {return _storage._pairedKeyResult != nil} /// Clears the value of `pairedKeyResult`. Subsequent reads from it will return its default value. - mutating func clearPairedKeyResult() {self._pairedKeyResult = nil} + mutating func clearPairedKeyResult() {_uniqueStorage()._pairedKeyResult = nil} var certificateInfo: Sharing_Nearby_CertificateInfoFrame { - get {return _certificateInfo ?? Sharing_Nearby_CertificateInfoFrame()} - set {_certificateInfo = newValue} + get {return _storage._certificateInfo ?? Sharing_Nearby_CertificateInfoFrame()} + set {_uniqueStorage()._certificateInfo = newValue} } /// Returns true if `certificateInfo` has been explicitly set. - var hasCertificateInfo: Bool {return self._certificateInfo != nil} + var hasCertificateInfo: Bool {return _storage._certificateInfo != nil} /// Clears the value of `certificateInfo`. Subsequent reads from it will return its default value. - mutating func clearCertificateInfo() {self._certificateInfo = nil} + mutating func clearCertificateInfo() {_uniqueStorage()._certificateInfo = nil} + + var progressUpdate: Sharing_Nearby_ProgressUpdateFrame { + get {return _storage._progressUpdate ?? Sharing_Nearby_ProgressUpdateFrame()} + set {_uniqueStorage()._progressUpdate = newValue} + } + /// Returns true if `progressUpdate` has been explicitly set. + var hasProgressUpdate: Bool {return _storage._progressUpdate != nil} + /// Clears the value of `progressUpdate`. Subsequent reads from it will return its default value. + mutating func clearProgressUpdate() {_uniqueStorage()._progressUpdate = nil} var unknownFields = SwiftProtobuf.UnknownStorage() @@ -505,9 +700,14 @@ struct Sharing_Nearby_V1Frame { case response // = 2 case pairedKeyEncryption // = 3 case pairedKeyResult // = 4 + + /// No longer used. case certificateInfo // = 5 case cancel // = 6 + /// No longer used. + case progressUpdate // = 7 + init() { self = .unknownFrameType } @@ -521,6 +721,7 @@ struct Sharing_Nearby_V1Frame { case 4: self = .pairedKeyResult case 5: self = .certificateInfo case 6: self = .cancel + case 7: self = .progressUpdate default: return nil } } @@ -534,6 +735,7 @@ struct Sharing_Nearby_V1Frame { case .pairedKeyResult: return 4 case .certificateInfo: return 5 case .cancel: return 6 + case .progressUpdate: return 7 } } @@ -541,12 +743,7 @@ struct Sharing_Nearby_V1Frame { init() {} - fileprivate var _type: Sharing_Nearby_V1Frame.FrameType? = nil - fileprivate var _introduction: Sharing_Nearby_IntroductionFrame? = nil - fileprivate var _connectionResponse: Sharing_Nearby_ConnectionResponseFrame? = nil - fileprivate var _pairedKeyEncryption: Sharing_Nearby_PairedKeyEncryptionFrame? = nil - fileprivate var _pairedKeyResult: Sharing_Nearby_PairedKeyResultFrame? = nil - fileprivate var _certificateInfo: Sharing_Nearby_CertificateInfoFrame? = nil + fileprivate var _storage = _StorageClass.defaultInstance } #if swift(>=4.2) @@ -559,7 +756,7 @@ extension Sharing_Nearby_V1Frame.FrameType: CaseIterable { /// An introduction packet sent by the sending side. Contains a list of files /// they'd like to share. -/// NEXT_ID=4 +/// NEXT_ID=10 struct Sharing_Nearby_IntroductionFrame { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -581,16 +778,114 @@ struct Sharing_Nearby_IntroductionFrame { var wifiCredentialsMetadata: [Sharing_Nearby_WifiCredentialsMetadata] = [] + var appMetadata: [Sharing_Nearby_AppMetadata] = [] + + var startTransfer: Bool { + get {return _startTransfer ?? false} + set {_startTransfer = newValue} + } + /// Returns true if `startTransfer` has been explicitly set. + var hasStartTransfer: Bool {return self._startTransfer != nil} + /// Clears the value of `startTransfer`. Subsequent reads from it will return its default value. + mutating func clearStartTransfer() {self._startTransfer = nil} + + var streamMetadata: [Sharing_Nearby_StreamMetadata] = [] + + var useCase: Sharing_Nearby_IntroductionFrame.SharingUseCase { + get {return _useCase ?? .unknown} + set {_useCase = newValue} + } + /// Returns true if `useCase` has been explicitly set. + var hasUseCase: Bool {return self._useCase != nil} + /// Clears the value of `useCase`. Subsequent reads from it will return its default value. + mutating func clearUseCase() {self._useCase = nil} + + var previewPayloadIds: [Int64] = [] + var unknownFields = SwiftProtobuf.UnknownStorage() + enum SharingUseCase: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknown // = 0 + case nearbyShare // = 1 + case remoteCopy // = 2 + + init() { + self = .unknown + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknown + case 1: self = .nearbyShare + case 2: self = .remoteCopy + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknown: return 0 + case .nearbyShare: return 1 + case .remoteCopy: return 2 + } + } + + } + init() {} fileprivate var _requiredPackage: String? = nil + fileprivate var _startTransfer: Bool? = nil + fileprivate var _useCase: Sharing_Nearby_IntroductionFrame.SharingUseCase? = nil +} + +#if swift(>=4.2) + +extension Sharing_Nearby_IntroductionFrame.SharingUseCase: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +/// A progress update packet sent by the sending side. Contains transfer progress +/// value. NEXT_ID=3 +struct Sharing_Nearby_ProgressUpdateFrame { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var progress: Float { + get {return _progress ?? 0} + set {_progress = newValue} + } + /// Returns true if `progress` has been explicitly set. + var hasProgress: Bool {return self._progress != nil} + /// Clears the value of `progress`. Subsequent reads from it will return its default value. + mutating func clearProgress() {self._progress = nil} + + /// True, if the receiver should start bandwidth upgrade and receiving the + /// payloads. + var startTransfer: Bool { + get {return _startTransfer ?? false} + set {_startTransfer = newValue} + } + /// Returns true if `startTransfer` has been explicitly set. + var hasStartTransfer: Bool {return self._startTransfer != nil} + /// Clears the value of `startTransfer`. Subsequent reads from it will return its default value. + mutating func clearStartTransfer() {self._startTransfer = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _progress: Float? = nil + fileprivate var _startTransfer: Bool? = nil } /// A response packet sent by the receiving side. Accepts or rejects the list of /// files. -/// NEXT_ID=2 +/// NEXT_ID=4 struct Sharing_Nearby_ConnectionResponseFrame { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -606,6 +901,13 @@ struct Sharing_Nearby_ConnectionResponseFrame { /// Clears the value of `status`. Subsequent reads from it will return its default value. mutating func clearStatus() {self._status = nil} + /// Key is attachment hash, value is the details of attachment. + var attachmentDetails: Dictionary = [:] + + /// In the case of a stream attachments, the other side of the pipe. + /// Both sender and receiver should validate matching counts. + var streamMetadata: [Sharing_Nearby_StreamMetadata] = [] + var unknownFields = SwiftProtobuf.UnknownStorage() enum Status: SwiftProtobuf.Enum { @@ -659,8 +961,185 @@ extension Sharing_Nearby_ConnectionResponseFrame.Status: CaseIterable { #endif // swift(>=4.2) -/// A paired key encryption packet sent between devices, contains signed data. +/// Attachment details that sent in ConnectionResponseFrame. /// NEXT_ID=3 +struct Sharing_Nearby_AttachmentDetails { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// The attachment family type. + var type: Sharing_Nearby_AttachmentDetails.TypeEnum { + get {return _type ?? .unknown} + set {_type = newValue} + } + /// Returns true if `type` has been explicitly set. + var hasType: Bool {return self._type != nil} + /// Clears the value of `type`. Subsequent reads from it will return its default value. + mutating func clearType() {self._type = nil} + + /// This field is only for FILE type. + var fileAttachmentDetails: Sharing_Nearby_FileAttachmentDetails { + get {return _fileAttachmentDetails ?? Sharing_Nearby_FileAttachmentDetails()} + set {_fileAttachmentDetails = newValue} + } + /// Returns true if `fileAttachmentDetails` has been explicitly set. + var hasFileAttachmentDetails: Bool {return self._fileAttachmentDetails != nil} + /// Clears the value of `fileAttachmentDetails`. Subsequent reads from it will return its default value. + mutating func clearFileAttachmentDetails() {self._fileAttachmentDetails = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + /// LINT.IfChange + enum TypeEnum: SwiftProtobuf.Enum { + typealias RawValue = Int + case unknown // = 0 + + /// Represents FileAttachment. + case file // = 1 + + /// Represents TextAttachment. + case text // = 2 + + /// Represents WifiCredentialsAttachment. + case wifiCredentials // = 3 + + /// Represents AppAttachment. + case app // = 4 + + /// Represents StreamAttachment. + case stream // = 5 + + init() { + self = .unknown + } + + init?(rawValue: Int) { + switch rawValue { + case 0: self = .unknown + case 1: self = .file + case 2: self = .text + case 3: self = .wifiCredentials + case 4: self = .app + case 5: self = .stream + default: return nil + } + } + + var rawValue: Int { + switch self { + case .unknown: return 0 + case .file: return 1 + case .text: return 2 + case .wifiCredentials: return 3 + case .app: return 4 + case .stream: return 5 + } + } + + } + + init() {} + + fileprivate var _type: Sharing_Nearby_AttachmentDetails.TypeEnum? = nil + fileprivate var _fileAttachmentDetails: Sharing_Nearby_FileAttachmentDetails? = nil +} + +#if swift(>=4.2) + +extension Sharing_Nearby_AttachmentDetails.TypeEnum: CaseIterable { + // Support synthesized by the compiler. +} + +#endif // swift(>=4.2) + +/// File attachment details included in ConnectionResponseFrame. +/// NEXT_ID=3 +struct Sharing_Nearby_FileAttachmentDetails { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// Existing local file size on receiver side. + var receiverExistingFileSize: Int64 { + get {return _receiverExistingFileSize ?? 0} + set {_receiverExistingFileSize = newValue} + } + /// Returns true if `receiverExistingFileSize` has been explicitly set. + var hasReceiverExistingFileSize: Bool {return self._receiverExistingFileSize != nil} + /// Clears the value of `receiverExistingFileSize`. Subsequent reads from it will return its default value. + mutating func clearReceiverExistingFileSize() {self._receiverExistingFileSize = nil} + + /// The key is attachment hash, a stable identifier for the attachment. + /// Value is list of payload details transferred for the attachment. + var attachmentHashPayloads: Dictionary = [:] + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _receiverExistingFileSize: Int64? = nil +} + +/// NEXT_ID=2 +struct Sharing_Nearby_PayloadsDetails { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// The list should be sorted by creation timestamp. + var payloadDetails: [Sharing_Nearby_PayloadDetails] = [] + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} +} + +/// Metadata of a payload file created by Nearby Connections. +/// NEXT_ID=4 +struct Sharing_Nearby_PayloadDetails { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + var id: Int64 { + get {return _id ?? 0} + set {_id = newValue} + } + /// Returns true if `id` has been explicitly set. + var hasID: Bool {return self._id != nil} + /// Clears the value of `id`. Subsequent reads from it will return its default value. + mutating func clearID() {self._id = nil} + + var creationTimestampMillis: Int64 { + get {return _creationTimestampMillis ?? 0} + set {_creationTimestampMillis = newValue} + } + /// Returns true if `creationTimestampMillis` has been explicitly set. + var hasCreationTimestampMillis: Bool {return self._creationTimestampMillis != nil} + /// Clears the value of `creationTimestampMillis`. Subsequent reads from it will return its default value. + mutating func clearCreationTimestampMillis() {self._creationTimestampMillis = nil} + + var size: Int64 { + get {return _size ?? 0} + set {_size = newValue} + } + /// Returns true if `size` has been explicitly set. + var hasSize: Bool {return self._size != nil} + /// Clears the value of `size`. Subsequent reads from it will return its default value. + mutating func clearSize() {self._size = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _id: Int64? = nil + fileprivate var _creationTimestampMillis: Int64? = nil + fileprivate var _size: Int64? = nil +} + +/// A paired key encryption packet sent between devices, contains signed data. +/// NEXT_ID=5 struct Sharing_Nearby_PairedKeyEncryptionFrame { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -696,6 +1175,20 @@ struct Sharing_Nearby_PairedKeyEncryptionFrame { /// Clears the value of `optionalSignedData`. Subsequent reads from it will return its default value. mutating func clearOptionalSignedData() {self._optionalSignedData = nil} + /// An optional QR code handshake data in a byte array format. + /// For incoming connection contains a signature of the UKEY2 + /// token, created with the sender's private key. + /// For outgoing connection contains an HKDF of the connection token and of the + /// UKEY2 token + var qrCodeHandshakeData: Data { + get {return _qrCodeHandshakeData ?? Data()} + set {_qrCodeHandshakeData = newValue} + } + /// Returns true if `qrCodeHandshakeData` has been explicitly set. + var hasQrCodeHandshakeData: Bool {return self._qrCodeHandshakeData != nil} + /// Clears the value of `qrCodeHandshakeData`. Subsequent reads from it will return its default value. + mutating func clearQrCodeHandshakeData() {self._qrCodeHandshakeData = nil} + var unknownFields = SwiftProtobuf.UnknownStorage() init() {} @@ -703,10 +1196,11 @@ struct Sharing_Nearby_PairedKeyEncryptionFrame { fileprivate var _signedData: Data? = nil fileprivate var _secretIDHash: Data? = nil fileprivate var _optionalSignedData: Data? = nil + fileprivate var _qrCodeHandshakeData: Data? = nil } /// A paired key verification result packet sent between devices. -/// NEXT_ID=2 +/// NEXT_ID=3 struct Sharing_Nearby_PairedKeyResultFrame { // SwiftProtobuf.Message conformance is added in an extension below. See the // `Message` and `Message+*Additions` files in the SwiftProtobuf library for @@ -722,6 +1216,16 @@ struct Sharing_Nearby_PairedKeyResultFrame { /// Clears the value of `status`. Subsequent reads from it will return its default value. mutating func clearStatus() {self._status = nil} + /// OS type. + var osType: Location_Nearby_Proto_Sharing_OSType { + get {return _osType ?? .unknownOsType} + set {_osType = newValue} + } + /// Returns true if `osType` has been explicitly set. + var hasOsType: Bool {return self._osType != nil} + /// Clears the value of `osType`. Subsequent reads from it will return its default value. + mutating func clearOsType() {self._osType = nil} + var unknownFields = SwiftProtobuf.UnknownStorage() enum Status: SwiftProtobuf.Enum { @@ -759,6 +1263,7 @@ struct Sharing_Nearby_PairedKeyResultFrame { init() {} fileprivate var _status: Sharing_Nearby_PairedKeyResultFrame.Status? = nil + fileprivate var _osType: Location_Nearby_Proto_Sharing_OSType? = nil } #if swift(>=4.2) @@ -914,6 +1419,29 @@ struct Sharing_Nearby_WifiCredentials { fileprivate var _hiddenSsid: Bool? = nil } +/// NEXT_ID=2 +struct Sharing_Nearby_StreamDetails { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// Serialized ParcelFileDescriptor for input stream (for the receiver). + var inputStreamParcelFileDescriptorBytes: Data { + get {return _inputStreamParcelFileDescriptorBytes ?? Data()} + set {_inputStreamParcelFileDescriptorBytes = newValue} + } + /// Returns true if `inputStreamParcelFileDescriptorBytes` has been explicitly set. + var hasInputStreamParcelFileDescriptorBytes: Bool {return self._inputStreamParcelFileDescriptorBytes != nil} + /// Clears the value of `inputStreamParcelFileDescriptorBytes`. Subsequent reads from it will return its default value. + mutating func clearInputStreamParcelFileDescriptorBytes() {self._inputStreamParcelFileDescriptorBytes = nil} + + var unknownFields = SwiftProtobuf.UnknownStorage() + + init() {} + + fileprivate var _inputStreamParcelFileDescriptorBytes: Data? = nil +} + #if swift(>=5.5) && canImport(_Concurrency) extension Sharing_Nearby_FileMetadata: @unchecked Sendable {} extension Sharing_Nearby_FileMetadata.TypeEnum: @unchecked Sendable {} @@ -921,19 +1449,29 @@ extension Sharing_Nearby_TextMetadata: @unchecked Sendable {} extension Sharing_Nearby_TextMetadata.TypeEnum: @unchecked Sendable {} extension Sharing_Nearby_WifiCredentialsMetadata: @unchecked Sendable {} extension Sharing_Nearby_WifiCredentialsMetadata.SecurityType: @unchecked Sendable {} +extension Sharing_Nearby_AppMetadata: @unchecked Sendable {} +extension Sharing_Nearby_StreamMetadata: @unchecked Sendable {} extension Sharing_Nearby_Frame: @unchecked Sendable {} extension Sharing_Nearby_Frame.Version: @unchecked Sendable {} extension Sharing_Nearby_V1Frame: @unchecked Sendable {} extension Sharing_Nearby_V1Frame.FrameType: @unchecked Sendable {} extension Sharing_Nearby_IntroductionFrame: @unchecked Sendable {} +extension Sharing_Nearby_IntroductionFrame.SharingUseCase: @unchecked Sendable {} +extension Sharing_Nearby_ProgressUpdateFrame: @unchecked Sendable {} extension Sharing_Nearby_ConnectionResponseFrame: @unchecked Sendable {} extension Sharing_Nearby_ConnectionResponseFrame.Status: @unchecked Sendable {} +extension Sharing_Nearby_AttachmentDetails: @unchecked Sendable {} +extension Sharing_Nearby_AttachmentDetails.TypeEnum: @unchecked Sendable {} +extension Sharing_Nearby_FileAttachmentDetails: @unchecked Sendable {} +extension Sharing_Nearby_PayloadsDetails: @unchecked Sendable {} +extension Sharing_Nearby_PayloadDetails: @unchecked Sendable {} extension Sharing_Nearby_PairedKeyEncryptionFrame: @unchecked Sendable {} extension Sharing_Nearby_PairedKeyResultFrame: @unchecked Sendable {} extension Sharing_Nearby_PairedKeyResultFrame.Status: @unchecked Sendable {} extension Sharing_Nearby_CertificateInfoFrame: @unchecked Sendable {} extension Sharing_Nearby_PublicCertificate: @unchecked Sendable {} extension Sharing_Nearby_WifiCredentials: @unchecked Sendable {} +extension Sharing_Nearby_StreamDetails: @unchecked Sendable {} #endif // swift(>=5.5) && canImport(_Concurrency) // MARK: - Code below here is support for the SwiftProtobuf runtime. @@ -949,6 +1487,9 @@ extension Sharing_Nearby_FileMetadata: SwiftProtobuf.Message, SwiftProtobuf._Mes 4: .same(proto: "size"), 5: .standard(proto: "mime_type"), 6: .same(proto: "id"), + 7: .standard(proto: "parent_folder"), + 8: .standard(proto: "attachment_hash"), + 9: .standard(proto: "is_sensitive_content"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -963,6 +1504,9 @@ extension Sharing_Nearby_FileMetadata: SwiftProtobuf.Message, SwiftProtobuf._Mes case 4: try { try decoder.decodeSingularInt64Field(value: &self._size) }() case 5: try { try decoder.decodeSingularStringField(value: &self._mimeType) }() case 6: try { try decoder.decodeSingularInt64Field(value: &self._id) }() + case 7: try { try decoder.decodeSingularStringField(value: &self._parentFolder) }() + case 8: try { try decoder.decodeSingularInt64Field(value: &self._attachmentHash) }() + case 9: try { try decoder.decodeSingularBoolField(value: &self._isSensitiveContent) }() default: break } } @@ -991,6 +1535,15 @@ extension Sharing_Nearby_FileMetadata: SwiftProtobuf.Message, SwiftProtobuf._Mes try { if let v = self._id { try visitor.visitSingularInt64Field(value: v, fieldNumber: 6) } }() + try { if let v = self._parentFolder { + try visitor.visitSingularStringField(value: v, fieldNumber: 7) + } }() + try { if let v = self._attachmentHash { + try visitor.visitSingularInt64Field(value: v, fieldNumber: 8) + } }() + try { if let v = self._isSensitiveContent { + try visitor.visitSingularBoolField(value: v, fieldNumber: 9) + } }() try unknownFields.traverse(visitor: &visitor) } @@ -1001,6 +1554,9 @@ extension Sharing_Nearby_FileMetadata: SwiftProtobuf.Message, SwiftProtobuf._Mes if lhs._size != rhs._size {return false} if lhs._mimeType != rhs._mimeType {return false} if lhs._id != rhs._id {return false} + if lhs._parentFolder != rhs._parentFolder {return false} + if lhs._attachmentHash != rhs._attachmentHash {return false} + if lhs._isSensitiveContent != rhs._isSensitiveContent {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -1011,8 +1567,10 @@ extension Sharing_Nearby_FileMetadata.TypeEnum: SwiftProtobuf._ProtoNameProvidin 0: .same(proto: "UNKNOWN"), 1: .same(proto: "IMAGE"), 2: .same(proto: "VIDEO"), - 3: .same(proto: "APP"), + 3: .same(proto: "ANDROID_APP"), 4: .same(proto: "AUDIO"), + 5: .same(proto: "DOCUMENT"), + 6: .same(proto: "CONTACT_CARD"), ] } @@ -1024,6 +1582,7 @@ extension Sharing_Nearby_TextMetadata: SwiftProtobuf.Message, SwiftProtobuf._Mes 4: .standard(proto: "payload_id"), 5: .same(proto: "size"), 6: .same(proto: "id"), + 7: .standard(proto: "is_sensitive_text"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -1037,6 +1596,7 @@ extension Sharing_Nearby_TextMetadata: SwiftProtobuf.Message, SwiftProtobuf._Mes case 4: try { try decoder.decodeSingularInt64Field(value: &self._payloadID) }() case 5: try { try decoder.decodeSingularInt64Field(value: &self._size) }() case 6: try { try decoder.decodeSingularInt64Field(value: &self._id) }() + case 7: try { try decoder.decodeSingularBoolField(value: &self._isSensitiveText) }() default: break } } @@ -1062,6 +1622,9 @@ extension Sharing_Nearby_TextMetadata: SwiftProtobuf.Message, SwiftProtobuf._Mes try { if let v = self._id { try visitor.visitSingularInt64Field(value: v, fieldNumber: 6) } }() + try { if let v = self._isSensitiveText { + try visitor.visitSingularBoolField(value: v, fieldNumber: 7) + } }() try unknownFields.traverse(visitor: &visitor) } @@ -1071,6 +1634,7 @@ extension Sharing_Nearby_TextMetadata: SwiftProtobuf.Message, SwiftProtobuf._Mes if lhs._payloadID != rhs._payloadID {return false} if lhs._size != rhs._size {return false} if lhs._id != rhs._id {return false} + if lhs._isSensitiveText != rhs._isSensitiveText {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -1146,9 +1710,136 @@ extension Sharing_Nearby_WifiCredentialsMetadata.SecurityType: SwiftProtobuf._Pr 1: .same(proto: "OPEN"), 2: .same(proto: "WPA_PSK"), 3: .same(proto: "WEP"), + 4: .same(proto: "SAE"), ] } +extension Sharing_Nearby_AppMetadata: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".AppMetadata" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "app_name"), + 2: .same(proto: "size"), + 3: .standard(proto: "payload_id"), + 4: .same(proto: "id"), + 5: .standard(proto: "file_name"), + 6: .standard(proto: "file_size"), + 7: .standard(proto: "package_name"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularStringField(value: &self._appName) }() + case 2: try { try decoder.decodeSingularInt64Field(value: &self._size) }() + case 3: try { try decoder.decodeRepeatedInt64Field(value: &self.payloadID) }() + case 4: try { try decoder.decodeSingularInt64Field(value: &self._id) }() + case 5: try { try decoder.decodeRepeatedStringField(value: &self.fileName) }() + case 6: try { try decoder.decodeRepeatedInt64Field(value: &self.fileSize) }() + case 7: try { try decoder.decodeSingularStringField(value: &self._packageName) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._appName { + try visitor.visitSingularStringField(value: v, fieldNumber: 1) + } }() + try { if let v = self._size { + try visitor.visitSingularInt64Field(value: v, fieldNumber: 2) + } }() + if !self.payloadID.isEmpty { + try visitor.visitPackedInt64Field(value: self.payloadID, fieldNumber: 3) + } + try { if let v = self._id { + try visitor.visitSingularInt64Field(value: v, fieldNumber: 4) + } }() + if !self.fileName.isEmpty { + try visitor.visitRepeatedStringField(value: self.fileName, fieldNumber: 5) + } + if !self.fileSize.isEmpty { + try visitor.visitPackedInt64Field(value: self.fileSize, fieldNumber: 6) + } + try { if let v = self._packageName { + try visitor.visitSingularStringField(value: v, fieldNumber: 7) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Sharing_Nearby_AppMetadata, rhs: Sharing_Nearby_AppMetadata) -> Bool { + if lhs._appName != rhs._appName {return false} + if lhs._size != rhs._size {return false} + if lhs.payloadID != rhs.payloadID {return false} + if lhs._id != rhs._id {return false} + if lhs.fileName != rhs.fileName {return false} + if lhs.fileSize != rhs.fileSize {return false} + if lhs._packageName != rhs._packageName {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Sharing_Nearby_StreamMetadata: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".StreamMetadata" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "description"), + 2: .standard(proto: "package_name"), + 3: .standard(proto: "payload_id"), + 4: .standard(proto: "attributed_app_name"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularStringField(value: &self._description_p) }() + case 2: try { try decoder.decodeSingularStringField(value: &self._packageName) }() + case 3: try { try decoder.decodeSingularInt64Field(value: &self._payloadID) }() + case 4: try { try decoder.decodeSingularStringField(value: &self._attributedAppName) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._description_p { + try visitor.visitSingularStringField(value: v, fieldNumber: 1) + } }() + try { if let v = self._packageName { + try visitor.visitSingularStringField(value: v, fieldNumber: 2) + } }() + try { if let v = self._payloadID { + try visitor.visitSingularInt64Field(value: v, fieldNumber: 3) + } }() + try { if let v = self._attributedAppName { + try visitor.visitSingularStringField(value: v, fieldNumber: 4) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Sharing_Nearby_StreamMetadata, rhs: Sharing_Nearby_StreamMetadata) -> Bool { + if lhs._description_p != rhs._description_p {return false} + if lhs._packageName != rhs._packageName {return false} + if lhs._payloadID != rhs._payloadID {return false} + if lhs._attributedAppName != rhs._attributedAppName {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + extension Sharing_Nearby_Frame: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { static let protoMessageName: String = _protobuf_package + ".Frame" static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ @@ -1207,58 +1898,108 @@ extension Sharing_Nearby_V1Frame: SwiftProtobuf.Message, SwiftProtobuf._MessageI 4: .standard(proto: "paired_key_encryption"), 5: .standard(proto: "paired_key_result"), 6: .standard(proto: "certificate_info"), + 7: .standard(proto: "progress_update"), ] + fileprivate class _StorageClass { + var _type: Sharing_Nearby_V1Frame.FrameType? = nil + var _introduction: Sharing_Nearby_IntroductionFrame? = nil + var _connectionResponse: Sharing_Nearby_ConnectionResponseFrame? = nil + var _pairedKeyEncryption: Sharing_Nearby_PairedKeyEncryptionFrame? = nil + var _pairedKeyResult: Sharing_Nearby_PairedKeyResultFrame? = nil + var _certificateInfo: Sharing_Nearby_CertificateInfoFrame? = nil + var _progressUpdate: Sharing_Nearby_ProgressUpdateFrame? = nil + + static let defaultInstance = _StorageClass() + + private init() {} + + init(copying source: _StorageClass) { + _type = source._type + _introduction = source._introduction + _connectionResponse = source._connectionResponse + _pairedKeyEncryption = source._pairedKeyEncryption + _pairedKeyResult = source._pairedKeyResult + _certificateInfo = source._certificateInfo + _progressUpdate = source._progressUpdate + } + } + + fileprivate mutating func _uniqueStorage() -> _StorageClass { + if !isKnownUniquelyReferenced(&_storage) { + _storage = _StorageClass(copying: _storage) + } + return _storage + } + mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularEnumField(value: &self._type) }() - case 2: try { try decoder.decodeSingularMessageField(value: &self._introduction) }() - case 3: try { try decoder.decodeSingularMessageField(value: &self._connectionResponse) }() - case 4: try { try decoder.decodeSingularMessageField(value: &self._pairedKeyEncryption) }() - case 5: try { try decoder.decodeSingularMessageField(value: &self._pairedKeyResult) }() - case 6: try { try decoder.decodeSingularMessageField(value: &self._certificateInfo) }() - default: break + _ = _uniqueStorage() + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularEnumField(value: &_storage._type) }() + case 2: try { try decoder.decodeSingularMessageField(value: &_storage._introduction) }() + case 3: try { try decoder.decodeSingularMessageField(value: &_storage._connectionResponse) }() + case 4: try { try decoder.decodeSingularMessageField(value: &_storage._pairedKeyEncryption) }() + case 5: try { try decoder.decodeSingularMessageField(value: &_storage._pairedKeyResult) }() + case 6: try { try decoder.decodeSingularMessageField(value: &_storage._certificateInfo) }() + case 7: try { try decoder.decodeSingularMessageField(value: &_storage._progressUpdate) }() + default: break + } } } } func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - try { if let v = self._type { - try visitor.visitSingularEnumField(value: v, fieldNumber: 1) - } }() - try { if let v = self._introduction { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } }() - try { if let v = self._connectionResponse { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } }() - try { if let v = self._pairedKeyEncryption { - try visitor.visitSingularMessageField(value: v, fieldNumber: 4) - } }() - try { if let v = self._pairedKeyResult { - try visitor.visitSingularMessageField(value: v, fieldNumber: 5) - } }() - try { if let v = self._certificateInfo { - try visitor.visitSingularMessageField(value: v, fieldNumber: 6) - } }() + try withExtendedLifetime(_storage) { (_storage: _StorageClass) in + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = _storage._type { + try visitor.visitSingularEnumField(value: v, fieldNumber: 1) + } }() + try { if let v = _storage._introduction { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try { if let v = _storage._connectionResponse { + try visitor.visitSingularMessageField(value: v, fieldNumber: 3) + } }() + try { if let v = _storage._pairedKeyEncryption { + try visitor.visitSingularMessageField(value: v, fieldNumber: 4) + } }() + try { if let v = _storage._pairedKeyResult { + try visitor.visitSingularMessageField(value: v, fieldNumber: 5) + } }() + try { if let v = _storage._certificateInfo { + try visitor.visitSingularMessageField(value: v, fieldNumber: 6) + } }() + try { if let v = _storage._progressUpdate { + try visitor.visitSingularMessageField(value: v, fieldNumber: 7) + } }() + } try unknownFields.traverse(visitor: &visitor) } static func ==(lhs: Sharing_Nearby_V1Frame, rhs: Sharing_Nearby_V1Frame) -> Bool { - if lhs._type != rhs._type {return false} - if lhs._introduction != rhs._introduction {return false} - if lhs._connectionResponse != rhs._connectionResponse {return false} - if lhs._pairedKeyEncryption != rhs._pairedKeyEncryption {return false} - if lhs._pairedKeyResult != rhs._pairedKeyResult {return false} - if lhs._certificateInfo != rhs._certificateInfo {return false} + if lhs._storage !== rhs._storage { + let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in + let _storage = _args.0 + let rhs_storage = _args.1 + if _storage._type != rhs_storage._type {return false} + if _storage._introduction != rhs_storage._introduction {return false} + if _storage._connectionResponse != rhs_storage._connectionResponse {return false} + if _storage._pairedKeyEncryption != rhs_storage._pairedKeyEncryption {return false} + if _storage._pairedKeyResult != rhs_storage._pairedKeyResult {return false} + if _storage._certificateInfo != rhs_storage._certificateInfo {return false} + if _storage._progressUpdate != rhs_storage._progressUpdate {return false} + return true + } + if !storagesAreEqual {return false} + } if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -1273,6 +2014,7 @@ extension Sharing_Nearby_V1Frame.FrameType: SwiftProtobuf._ProtoNameProviding { 4: .same(proto: "PAIRED_KEY_RESULT"), 5: .same(proto: "CERTIFICATE_INFO"), 6: .same(proto: "CANCEL"), + 7: .same(proto: "PROGRESS_UPDATE"), ] } @@ -1283,6 +2025,11 @@ extension Sharing_Nearby_IntroductionFrame: SwiftProtobuf.Message, SwiftProtobuf 2: .standard(proto: "text_metadata"), 3: .standard(proto: "required_package"), 4: .standard(proto: "wifi_credentials_metadata"), + 5: .standard(proto: "app_metadata"), + 6: .standard(proto: "start_transfer"), + 7: .standard(proto: "stream_metadata"), + 8: .standard(proto: "use_case"), + 9: .standard(proto: "preview_payload_ids"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -1295,6 +2042,11 @@ extension Sharing_Nearby_IntroductionFrame: SwiftProtobuf.Message, SwiftProtobuf case 2: try { try decoder.decodeRepeatedMessageField(value: &self.textMetadata) }() case 3: try { try decoder.decodeSingularStringField(value: &self._requiredPackage) }() case 4: try { try decoder.decodeRepeatedMessageField(value: &self.wifiCredentialsMetadata) }() + case 5: try { try decoder.decodeRepeatedMessageField(value: &self.appMetadata) }() + case 6: try { try decoder.decodeSingularBoolField(value: &self._startTransfer) }() + case 7: try { try decoder.decodeRepeatedMessageField(value: &self.streamMetadata) }() + case 8: try { try decoder.decodeSingularEnumField(value: &self._useCase) }() + case 9: try { try decoder.decodeRepeatedInt64Field(value: &self.previewPayloadIds) }() default: break } } @@ -1317,6 +2069,21 @@ extension Sharing_Nearby_IntroductionFrame: SwiftProtobuf.Message, SwiftProtobuf if !self.wifiCredentialsMetadata.isEmpty { try visitor.visitRepeatedMessageField(value: self.wifiCredentialsMetadata, fieldNumber: 4) } + if !self.appMetadata.isEmpty { + try visitor.visitRepeatedMessageField(value: self.appMetadata, fieldNumber: 5) + } + try { if let v = self._startTransfer { + try visitor.visitSingularBoolField(value: v, fieldNumber: 6) + } }() + if !self.streamMetadata.isEmpty { + try visitor.visitRepeatedMessageField(value: self.streamMetadata, fieldNumber: 7) + } + try { if let v = self._useCase { + try visitor.visitSingularEnumField(value: v, fieldNumber: 8) + } }() + if !self.previewPayloadIds.isEmpty { + try visitor.visitRepeatedInt64Field(value: self.previewPayloadIds, fieldNumber: 9) + } try unknownFields.traverse(visitor: &visitor) } @@ -1325,6 +2092,61 @@ extension Sharing_Nearby_IntroductionFrame: SwiftProtobuf.Message, SwiftProtobuf if lhs.textMetadata != rhs.textMetadata {return false} if lhs._requiredPackage != rhs._requiredPackage {return false} if lhs.wifiCredentialsMetadata != rhs.wifiCredentialsMetadata {return false} + if lhs.appMetadata != rhs.appMetadata {return false} + if lhs._startTransfer != rhs._startTransfer {return false} + if lhs.streamMetadata != rhs.streamMetadata {return false} + if lhs._useCase != rhs._useCase {return false} + if lhs.previewPayloadIds != rhs.previewPayloadIds {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Sharing_Nearby_IntroductionFrame.SharingUseCase: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "UNKNOWN"), + 1: .same(proto: "NEARBY_SHARE"), + 2: .same(proto: "REMOTE_COPY"), + ] +} + +extension Sharing_Nearby_ProgressUpdateFrame: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".ProgressUpdateFrame" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "progress"), + 2: .standard(proto: "start_transfer"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularFloatField(value: &self._progress) }() + case 2: try { try decoder.decodeSingularBoolField(value: &self._startTransfer) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._progress { + try visitor.visitSingularFloatField(value: v, fieldNumber: 1) + } }() + try { if let v = self._startTransfer { + try visitor.visitSingularBoolField(value: v, fieldNumber: 2) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Sharing_Nearby_ProgressUpdateFrame, rhs: Sharing_Nearby_ProgressUpdateFrame) -> Bool { + if lhs._progress != rhs._progress {return false} + if lhs._startTransfer != rhs._startTransfer {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -1334,6 +2156,8 @@ extension Sharing_Nearby_ConnectionResponseFrame: SwiftProtobuf.Message, SwiftPr static let protoMessageName: String = _protobuf_package + ".ConnectionResponseFrame" static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "status"), + 2: .standard(proto: "attachment_details"), + 3: .standard(proto: "stream_metadata"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -1343,6 +2167,8 @@ extension Sharing_Nearby_ConnectionResponseFrame: SwiftProtobuf.Message, SwiftPr // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { case 1: try { try decoder.decodeSingularEnumField(value: &self._status) }() + case 2: try { try decoder.decodeMapField(fieldType: SwiftProtobuf._ProtobufMessageMap.self, value: &self.attachmentDetails) }() + case 3: try { try decoder.decodeRepeatedMessageField(value: &self.streamMetadata) }() default: break } } @@ -1356,11 +2182,19 @@ extension Sharing_Nearby_ConnectionResponseFrame: SwiftProtobuf.Message, SwiftPr try { if let v = self._status { try visitor.visitSingularEnumField(value: v, fieldNumber: 1) } }() + if !self.attachmentDetails.isEmpty { + try visitor.visitMapField(fieldType: SwiftProtobuf._ProtobufMessageMap.self, value: self.attachmentDetails, fieldNumber: 2) + } + if !self.streamMetadata.isEmpty { + try visitor.visitRepeatedMessageField(value: self.streamMetadata, fieldNumber: 3) + } try unknownFields.traverse(visitor: &visitor) } static func ==(lhs: Sharing_Nearby_ConnectionResponseFrame, rhs: Sharing_Nearby_ConnectionResponseFrame) -> Bool { if lhs._status != rhs._status {return false} + if lhs.attachmentDetails != rhs.attachmentDetails {return false} + if lhs.streamMetadata != rhs.streamMetadata {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -1377,12 +2211,188 @@ extension Sharing_Nearby_ConnectionResponseFrame.Status: SwiftProtobuf._ProtoNam ] } +extension Sharing_Nearby_AttachmentDetails: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".AttachmentDetails" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "type"), + 2: .standard(proto: "file_attachment_details"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularEnumField(value: &self._type) }() + case 2: try { try decoder.decodeSingularMessageField(value: &self._fileAttachmentDetails) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._type { + try visitor.visitSingularEnumField(value: v, fieldNumber: 1) + } }() + try { if let v = self._fileAttachmentDetails { + try visitor.visitSingularMessageField(value: v, fieldNumber: 2) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Sharing_Nearby_AttachmentDetails, rhs: Sharing_Nearby_AttachmentDetails) -> Bool { + if lhs._type != rhs._type {return false} + if lhs._fileAttachmentDetails != rhs._fileAttachmentDetails {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Sharing_Nearby_AttachmentDetails.TypeEnum: SwiftProtobuf._ProtoNameProviding { + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 0: .same(proto: "UNKNOWN"), + 1: .same(proto: "FILE"), + 2: .same(proto: "TEXT"), + 3: .same(proto: "WIFI_CREDENTIALS"), + 4: .same(proto: "APP"), + 5: .same(proto: "STREAM"), + ] +} + +extension Sharing_Nearby_FileAttachmentDetails: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".FileAttachmentDetails" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "receiver_existing_file_size"), + 2: .standard(proto: "attachment_hash_payloads"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularInt64Field(value: &self._receiverExistingFileSize) }() + case 2: try { try decoder.decodeMapField(fieldType: SwiftProtobuf._ProtobufMessageMap.self, value: &self.attachmentHashPayloads) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._receiverExistingFileSize { + try visitor.visitSingularInt64Field(value: v, fieldNumber: 1) + } }() + if !self.attachmentHashPayloads.isEmpty { + try visitor.visitMapField(fieldType: SwiftProtobuf._ProtobufMessageMap.self, value: self.attachmentHashPayloads, fieldNumber: 2) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Sharing_Nearby_FileAttachmentDetails, rhs: Sharing_Nearby_FileAttachmentDetails) -> Bool { + if lhs._receiverExistingFileSize != rhs._receiverExistingFileSize {return false} + if lhs.attachmentHashPayloads != rhs.attachmentHashPayloads {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Sharing_Nearby_PayloadsDetails: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".PayloadsDetails" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "payload_details"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeRepeatedMessageField(value: &self.payloadDetails) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + if !self.payloadDetails.isEmpty { + try visitor.visitRepeatedMessageField(value: self.payloadDetails, fieldNumber: 1) + } + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Sharing_Nearby_PayloadsDetails, rhs: Sharing_Nearby_PayloadsDetails) -> Bool { + if lhs.payloadDetails != rhs.payloadDetails {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + +extension Sharing_Nearby_PayloadDetails: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".PayloadDetails" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .same(proto: "id"), + 2: .standard(proto: "creation_timestamp_millis"), + 3: .same(proto: "size"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularInt64Field(value: &self._id) }() + case 2: try { try decoder.decodeSingularInt64Field(value: &self._creationTimestampMillis) }() + case 3: try { try decoder.decodeSingularInt64Field(value: &self._size) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._id { + try visitor.visitSingularInt64Field(value: v, fieldNumber: 1) + } }() + try { if let v = self._creationTimestampMillis { + try visitor.visitSingularInt64Field(value: v, fieldNumber: 2) + } }() + try { if let v = self._size { + try visitor.visitSingularInt64Field(value: v, fieldNumber: 3) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Sharing_Nearby_PayloadDetails, rhs: Sharing_Nearby_PayloadDetails) -> Bool { + if lhs._id != rhs._id {return false} + if lhs._creationTimestampMillis != rhs._creationTimestampMillis {return false} + if lhs._size != rhs._size {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} + extension Sharing_Nearby_PairedKeyEncryptionFrame: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { static let protoMessageName: String = _protobuf_package + ".PairedKeyEncryptionFrame" static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .standard(proto: "signed_data"), 2: .standard(proto: "secret_id_hash"), 3: .standard(proto: "optional_signed_data"), + 4: .standard(proto: "qr_code_handshake_data"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -1394,6 +2404,7 @@ extension Sharing_Nearby_PairedKeyEncryptionFrame: SwiftProtobuf.Message, SwiftP case 1: try { try decoder.decodeSingularBytesField(value: &self._signedData) }() case 2: try { try decoder.decodeSingularBytesField(value: &self._secretIDHash) }() case 3: try { try decoder.decodeSingularBytesField(value: &self._optionalSignedData) }() + case 4: try { try decoder.decodeSingularBytesField(value: &self._qrCodeHandshakeData) }() default: break } } @@ -1413,6 +2424,9 @@ extension Sharing_Nearby_PairedKeyEncryptionFrame: SwiftProtobuf.Message, SwiftP try { if let v = self._optionalSignedData { try visitor.visitSingularBytesField(value: v, fieldNumber: 3) } }() + try { if let v = self._qrCodeHandshakeData { + try visitor.visitSingularBytesField(value: v, fieldNumber: 4) + } }() try unknownFields.traverse(visitor: &visitor) } @@ -1420,6 +2434,7 @@ extension Sharing_Nearby_PairedKeyEncryptionFrame: SwiftProtobuf.Message, SwiftP if lhs._signedData != rhs._signedData {return false} if lhs._secretIDHash != rhs._secretIDHash {return false} if lhs._optionalSignedData != rhs._optionalSignedData {return false} + if lhs._qrCodeHandshakeData != rhs._qrCodeHandshakeData {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -1429,6 +2444,7 @@ extension Sharing_Nearby_PairedKeyResultFrame: SwiftProtobuf.Message, SwiftProto static let protoMessageName: String = _protobuf_package + ".PairedKeyResultFrame" static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ 1: .same(proto: "status"), + 2: .standard(proto: "os_type"), ] mutating func decodeMessage(decoder: inout D) throws { @@ -1438,6 +2454,7 @@ extension Sharing_Nearby_PairedKeyResultFrame: SwiftProtobuf.Message, SwiftProto // enabled. https://github.com/apple/swift-protobuf/issues/1034 switch fieldNumber { case 1: try { try decoder.decodeSingularEnumField(value: &self._status) }() + case 2: try { try decoder.decodeSingularEnumField(value: &self._osType) }() default: break } } @@ -1451,11 +2468,15 @@ extension Sharing_Nearby_PairedKeyResultFrame: SwiftProtobuf.Message, SwiftProto try { if let v = self._status { try visitor.visitSingularEnumField(value: v, fieldNumber: 1) } }() + try { if let v = self._osType { + try visitor.visitSingularEnumField(value: v, fieldNumber: 2) + } }() try unknownFields.traverse(visitor: &visitor) } static func ==(lhs: Sharing_Nearby_PairedKeyResultFrame, rhs: Sharing_Nearby_PairedKeyResultFrame) -> Bool { if lhs._status != rhs._status {return false} + if lhs._osType != rhs._osType {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } @@ -1615,3 +2636,39 @@ extension Sharing_Nearby_WifiCredentials: SwiftProtobuf.Message, SwiftProtobuf._ return true } } + +extension Sharing_Nearby_StreamDetails: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + static let protoMessageName: String = _protobuf_package + ".StreamDetails" + static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ + 1: .standard(proto: "input_stream_parcel_file_descriptor_bytes"), + ] + + mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularBytesField(value: &self._inputStreamParcelFileDescriptorBytes) }() + default: break + } + } + } + + func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._inputStreamParcelFileDescriptorBytes { + try visitor.visitSingularBytesField(value: v, fieldNumber: 1) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + static func ==(lhs: Sharing_Nearby_StreamDetails, rhs: Sharing_Nearby_StreamDetails) -> Bool { + if lhs._inputStreamParcelFileDescriptorBytes != rhs._inputStreamParcelFileDescriptorBytes {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} diff --git a/NearbyShare/Model/ProtobufSource/offline_wire_formats.proto b/NearbyShare/Model/ProtobufSource/offline_wire_formats.proto index c112004..afd0ed7 100644 --- a/NearbyShare/Model/ProtobufSource/offline_wire_formats.proto +++ b/NearbyShare/Model/ProtobufSource/offline_wire_formats.proto @@ -16,6 +16,8 @@ syntax = "proto2"; package location.nearby.connections; +// import "storage/datapol/annotations/proto/semantic_annotations.proto"; + option optimize_for = LITE_RUNTIME; option java_outer_classname = "OfflineWireFormatsProto"; option java_package = "com.google.location.nearby.connections.proto"; @@ -43,6 +45,11 @@ message V1Frame { KEEP_ALIVE = 5; DISCONNECTION = 6; PAIRED_KEY_ENCRYPTION = 7; + AUTHENTICATION_MESSAGE = 8; + AUTHENTICATION_RESULT = 9; + AUTO_RESUME = 10; + AUTO_RECONNECT = 11; + BANDWIDTH_UPGRADE_RETRY = 12; } optional FrameType type = 1; @@ -54,6 +61,11 @@ message V1Frame { optional KeepAliveFrame keep_alive = 6; optional DisconnectionFrame disconnection = 7; optional PairedKeyEncryptionFrame paired_key_encryption = 8; + optional AuthenticationMessageFrame authentication_message = 9; + optional AuthenticationResultFrame authentication_result = 10; + optional AutoResumeFrame auto_resume = 11; + optional AutoReconnectFrame auto_reconnect = 12; + optional BandwidthUpgradeRetryFrame bandwidth_upgrade_retry = 13; } message ConnectionRequestFrame { @@ -72,6 +84,15 @@ message ConnectionRequestFrame { WEB_RTC = 9; BLE_L2CAP = 10; USB = 11; + WEB_RTC_NON_CELLULAR = 12; + AWDL = 13; + } + // LINT.ThenChange(//depot/google3/third_party/nearby/proto/connections_enums.proto) + + // LINT.IfChange + enum ConnectionMode { + LEGACY = 0; + INSTANT = 1; } // LINT.ThenChange(//depot/google3/third_party/nearby/proto/connections_enums.proto) @@ -91,9 +112,16 @@ message ConnectionRequestFrame { optional int32 keep_alive_interval_millis = 8; optional int32 keep_alive_timeout_millis = 9; // The type of {@link Device} object. - optional int32 device_type = 10 [default = 0]; + optional int32 device_type = 10 [default = 0, deprecated = true]; // The bytes of serialized {@link Device} object. - optional bytes device_info = 11; + optional bytes device_info = 11 [deprecated = true]; + // Represents the {@link Device} that invokes the request. + oneof Device { + ConnectionsDevice connections_device = 12; + PresenceDevice presence_device = 13; + } + optional ConnectionMode connection_mode = 14; + optional LocationHint location_hint = 15; } message ConnectionResponseFrame { @@ -125,7 +153,10 @@ message ConnectionResponseFrame { // means bt supports multiplex while 0x00 means not. Refer to ClientProxy.java // for the bit usages. optional int32 multiplex_socket_bitmask = 5; - optional int32 nearby_connections_version = 6; + optional int32 nearby_connections_version = 6 [deprecated = true]; + optional int32 safe_to_disconnect_version = 7; + optional LocationHint location_hint = 8; + optional int32 keep_alive_timeout_millis = 9; } message PayloadTransferFrame { @@ -133,6 +164,7 @@ message PayloadTransferFrame { UNKNOWN_PACKET_TYPE = 0; DATA = 1; CONTROL = 2; + PAYLOAD_ACK = 3; } message PayloadHeader { @@ -148,6 +180,8 @@ message PayloadTransferFrame { optional bool is_sensitive = 4; optional string file_name = 5; optional string parent_folder = 6; + // Time since the epoch in milliseconds. + optional int64 last_modified_timestamp_millis = 7; } // Accompanies DATA packets. @@ -158,6 +192,7 @@ message PayloadTransferFrame { optional int32 flags = 1; optional int64 offset = 2; optional bytes body = 3; + optional int32 index = 4; } // Accompanies CONTROL packets. @@ -166,7 +201,8 @@ message PayloadTransferFrame { UNKNOWN_EVENT_TYPE = 0; PAYLOAD_ERROR = 1; PAYLOAD_CANCELED = 2; - PAYLOAD_RECEIVED_ACK = 3; + // Use PacketType.PAYLOAD_ACK instead + PAYLOAD_RECEIVED_ACK = 3 [deprecated = true]; } optional EventType event = 1; @@ -190,6 +226,9 @@ message BandwidthUpgradeNegotiationFrame { CLIENT_INTRODUCTION = 4; UPGRADE_FAILURE = 5; CLIENT_INTRODUCTION_ACK = 6; + // The event type that requires the remote device to send the available + // upgrade path. + UPGRADE_PATH_REQUEST = 7; } // Accompanies UPGRADE_PATH_AVAILABLE and UPGRADE_FAILURE events. @@ -208,12 +247,15 @@ message BandwidthUpgradeNegotiationFrame { WEB_RTC = 9; // 10 is reserved. USB = 11; + WEB_RTC_NON_CELLULAR = 12; + AWDL = 13; } // Accompanies Medium.WIFI_HOTSPOT. message WifiHotspotCredentials { optional string ssid = 1; - optional string password = 2; + optional string password = 2 + /* type = ST_ACCOUNT_CREDENTIAL */; optional int32 port = 3; optional string gateway = 4 [default = "0.0.0.0"]; // This field can be a band or frequency @@ -236,16 +278,22 @@ message BandwidthUpgradeNegotiationFrame { message WifiAwareCredentials { optional string service_id = 1; optional bytes service_info = 2; - optional string password = 3; + optional string password = 3 + /* type = ST_ACCOUNT_CREDENTIAL */; } // Accompanies Medium.WIFI_DIRECT. message WifiDirectCredentials { optional string ssid = 1; - optional string password = 2; + optional string password = 2 + /* type = ST_ACCOUNT_CREDENTIAL */; optional int32 port = 3; optional int32 frequency = 4; optional string gateway = 5 [default = "0.0.0.0"]; + // IPv6 link-local address, network order (128bits). + // The GO should listen on both IPv4 and IPv6 addresses. + // https://en.wikipedia.org/wiki/Link-local_address#IPv6 + optional bytes ip_v6_address = 6; } // Accompanies Medium.WEB_RTC @@ -254,6 +302,20 @@ message BandwidthUpgradeNegotiationFrame { optional LocationHint location_hint = 2; } + // Accompanies Medium.AWDL. + message AwdlCredentials { + optional string service_name = 1; + optional string service_type = 2; + optional string password = 3 + /* type = ST_ACCOUNT_CREDENTIAL */; + } + + message UpgradePathRequest { + // Supported mediums on the advertiser device. + repeated Medium mediums = 1 [packed = true]; + optional MediumMetadata medium_meta_data = 2; + } + optional Medium medium = 1; // Exactly one of the following fields will be set. @@ -263,18 +325,27 @@ message BandwidthUpgradeNegotiationFrame { optional WifiAwareCredentials wifi_aware_credentials = 5; optional WifiDirectCredentials wifi_direct_credentials = 6; optional WebRtcCredentials web_rtc_credentials = 8; + optional AwdlCredentials awdl_credentials = 11; // Disable Encryption for this upgrade medium to improve throughput. optional bool supports_disabling_encryption = 7; // An ack will be sent after the CLIENT_INTRODUCTION frame. optional bool supports_client_introduction_ack = 9; + + optional UpgradePathRequest upgrade_path_request = 10; + } + + // Accompanies SAFE_TO_CLOSE_PRIOR_CHANNEL events. + message SafeToClosePriorChannel { + optional int32 sta_frequency = 1; } // Accompanies CLIENT_INTRODUCTION events. message ClientIntroduction { optional string endpoint_id = 1; optional bool supports_disabling_encryption = 2; + optional string last_endpoint_id = 3; } // Accompanies CLIENT_INTRODUCTION_ACK events. @@ -286,11 +357,44 @@ message BandwidthUpgradeNegotiationFrame { optional UpgradePathInfo upgrade_path_info = 2; optional ClientIntroduction client_introduction = 3; optional ClientIntroductionAck client_introduction_ack = 4; + optional SafeToClosePriorChannel safe_to_close_prior_channel = 5; +} + +message BandwidthUpgradeRetryFrame { + // Should always match cs/symbol:location.nearby.proto.connections.Medium + // LINT.IfChange + enum Medium { + UNKNOWN_MEDIUM = 0; + // 1 is reserved. + BLUETOOTH = 2; + WIFI_HOTSPOT = 3; + BLE = 4; + WIFI_LAN = 5; + WIFI_AWARE = 6; + NFC = 7; + WIFI_DIRECT = 8; + WEB_RTC = 9; + BLE_L2CAP = 10; + USB = 11; + WEB_RTC_NON_CELLULAR = 12; + AWDL = 13; + } + // LINT.ThenChange(//depot/google3/third_party/nearby/proto/connections_enums.proto) + + // The mediums this device supports upgrading to. This list should be filtered + // by both the strategy and this device's individual limitations. + repeated Medium supported_medium = 1; + + // If true, expect the remote endpoint to send back the latest + // supported_medium. + optional bool is_request = 2; } message KeepAliveFrame { // And ack will be sent after receiving KEEP_ALIVE frame. optional bool ack = 1; + // The sequence number + optional uint32 seq_num = 2; } // Informs the remote side to immediately severe the socket connection. @@ -313,6 +417,45 @@ message PairedKeyEncryptionFrame { optional bytes signed_data = 1; } +// Nearby Connections authentication frame, contains the bytes format of a +// DeviceProvider's authentication message. +message AuthenticationMessageFrame { + // An auth message generated by DeviceProvider. + // To be sent to the remote device for verification during connection setups. + optional bytes auth_message = 1; +} + +// Nearby Connections authentication result frame. +message AuthenticationResultFrame { + // The authentication result. Non null if this frame is used to exchange + // authentication result. + optional int32 result = 1; +} + +message AutoResumeFrame { + enum EventType { + UNKNOWN_AUTO_RESUME_EVENT_TYPE = 0; + PAYLOAD_RESUME_TRANSFER_START = 1; + PAYLOAD_RESUME_TRANSFER_ACK = 2; + } + + optional EventType event_type = 1; + optional int64 pending_payload_id = 2; + optional int32 next_payload_chunk_index = 3; + optional int32 version = 4; +} + +message AutoReconnectFrame { + enum EventType { + UNKNOWN_EVENT_TYPE = 0; + CLIENT_INTRODUCTION = 1; + CLIENT_INTRODUCTION_ACK = 2; + } + optional string endpoint_id = 1; + optional EventType event_type = 2; + optional string last_endpoint_id = 3; +} + message MediumMetadata { // True if local device supports 5GHz. optional bool supports_5_ghz = 1; @@ -338,6 +481,8 @@ message MediumMetadata { optional WifiAwareUsableChannels wifi_aware_usable_channels = 10; // Usable WiFi Hotspot STA channels on the local device. optional WifiHotspotStaUsableChannels wifi_hotspot_sta_usable_channels = 11; + // The supported medium roles. + optional MediumRole medium_role = 12; } // Available channels on the local device. @@ -365,6 +510,18 @@ message WifiHotspotStaUsableChannels { repeated int32 channels = 1 [packed = true]; } +// The medium roles. +message MediumRole { + optional bool support_wifi_direct_group_owner = 1; + optional bool support_wifi_direct_group_client = 2; + optional bool support_wifi_hotspot_host = 3; + optional bool support_wifi_hotspot_client = 4; + optional bool support_wifi_aware_publisher = 5; + optional bool support_wifi_aware_subscriber = 6; + optional bool support_awdl_publisher = 7; + optional bool support_awdl_subscriber = 8; +} + // LocationHint is used to specify a location as well as format. message LocationHint { // Location is the location, provided in the format specified by format. @@ -401,3 +558,39 @@ message OsInfo { optional OsType type = 1; } + +enum EndpointType { + UNKNOWN_ENDPOINT = 0; + CONNECTIONS_ENDPOINT = 1; + PRESENCE_ENDPOINT = 2; +} + +message ConnectionsDevice { + optional string endpoint_id = 1; + optional EndpointType endpoint_type = 2; + optional bytes connectivity_info_list = 3; // Data Elements. + optional bytes endpoint_info = 4; +} + +message PresenceDevice { + enum DeviceType { + UNKNOWN = 0; + PHONE = 1; + TABLET = 2; + DISPLAY = 3; + LAPTOP = 4; + TV = 5; + WATCH = 6; + } + + optional string endpoint_id = 1; + optional EndpointType endpoint_type = 2; + optional bytes connectivity_info_list = 3; // Data Elements. + optional int64 device_id = 4; + optional string device_name = 5; + optional DeviceType device_type = 6; + optional string device_image_url = 7; + repeated ConnectionRequestFrame.Medium discovery_medium = 8 [packed = true]; + repeated int32 actions = 9 [packed = true]; + repeated int64 identity_type = 10 [packed = true]; +} diff --git a/NearbyShare/Model/ProtobufSource/sharing_enums.proto b/NearbyShare/Model/ProtobufSource/sharing_enums.proto new file mode 100644 index 0000000..e0e227c --- /dev/null +++ b/NearbyShare/Model/ProtobufSource/sharing_enums.proto @@ -0,0 +1,962 @@ +// Copyright 2020 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +syntax = "proto2"; + +package location.nearby.proto.sharing; + +// import "logs/proto/logs_annotations/logs_annotations.proto"; +// import "logs/proto/wireless/beto/beto_enums.proto"; +// import "logs/proto/wireless/beto/log_dimension_annotations.proto"; + +// option (logs_proto.file_not_used_for_logging_except_enums) = true; +option optimize_for = LITE_RUNTIME; +option java_package = "com.google.location.nearby.proto"; +option java_outer_classname = "SharingEnums"; +option objc_class_prefix = "GNSHP"; + +// We use event based logging (an event object can be constructed and logged +// immediately when they occur). To obtain session based information (e.g. +// durations, counting incoming introductions), we use flowId (sender/receiver) +// in NearbyClearcutLogger (for android, or clearcut_event_logger as the +// equivalence for Windows) for all events (may exclude settings), and +// session_id for a pair of events (start and end of a session). +// Next id: 74 +enum EventType { + UNKNOWN_EVENT_TYPE = 0; + + // LINT.IfChange(EventType) + + // When new users accept agreements (like grant permission to contacts for + // CONTACT_ONLY visibility) and are enrolled into Nearby Sharing. This event + // is used to count number of new users. + ACCEPT_AGREEMENTS = 1; + + // User enables/disables nearby sharing from setting or tile service. + ENABLE_NEARBY_SHARING = 2; + + // User sets visibility preference from setting. + SET_VISIBILITY = 3; + + // Describe attachments immediately when Nearby Sharing is opened by another + // app which is used to generate/attach attachments to be shared with other + // devices. + DESCRIBE_ATTACHMENTS = 4 + /*[ device_role = DEVICE_ROLE_INITIATOR ]*/; + + // Start of a scanning phase at sender. + SCAN_FOR_SHARE_TARGETS_START = 5 /*[[*/ + /*( device_role = DEVICE_ROLE_INITIATOR, )*/ + /*( stage = STAGE_QS_DISCOVER_START )*/ + /*]]*/; + + // End of the scanning phase at sender. + SCAN_FOR_SHARE_TARGETS_END = 6 /*[[*/ + /*( device_role = DEVICE_ROLE_INITIATOR, )*/ + /*( stage = STAGE_QS_DISCOVER_END )*/ + /*]]*/; + + // Receiver advertises itself for presence (a pseudo session). + ADVERTISE_DEVICE_PRESENCE_START = 7 /*[[*/ + /*( device_role = DEVICE_ROLE_REMOTE, )*/ + /*( stage = STAGE_QS_ADVERTISE_START )*/ + /*]]*/; + + // End of the advertising phase at receiver. + ADVERTISE_DEVICE_PRESENCE_END = 8 /*[[*/ + /*( device_role = DEVICE_ROLE_REMOTE, )*/ + /*( stage = STAGE_QS_ADVERTISE_END )*/ + /*]]*/; + + // Sender sends a fast initialization to receiver. + SEND_FAST_INITIALIZATION = 9 + /*[ device_role = DEVICE_ROLE_INITIATOR ]*/; + + // Receiver receives the fast initialization. + RECEIVE_FAST_INITIALIZATION = 10 + /*[ device_role = DEVICE_ROLE_REMOTE ]*/; + + // Sender discovers a share target. + DISCOVER_SHARE_TARGET = 11 /*[[*/ + /*( device_role = DEVICE_ROLE_INITIATOR, )*/ + /*( stage = STAGE_QS_DISCOVERED )*/ + /*]]*/; + + // Sender sends introduction (before attachments being sent). + SEND_INTRODUCTION = 12 + /*[ device_role = DEVICE_ROLE_INITIATOR ]*/; + + // Receiver receives introduction. + RECEIVE_INTRODUCTION = 13 + /*[ device_role = DEVICE_ROLE_REMOTE ]*/; + + // Receiver responds to introduction (before attachments being sent). + // Actions: Accept, Reject, or (for some reason) Fail. + RESPOND_TO_INTRODUCTION = 14 + /*[ device_role = DEVICE_ROLE_REMOTE ]*/; + + // Start of the sending attachments phase at sender. + SEND_ATTACHMENTS_START = 15 /*[[*/ + /*( device_role = DEVICE_ROLE_INITIATOR, )*/ + /*( stage = STAGE_QS_TRANSFER_START )*/ + /*]]*/; + + // End of sending attachments phase at sender. + SEND_ATTACHMENTS_END = 16 /*[[*/ + /*( device_role = DEVICE_ROLE_INITIATOR, )*/ + /*( stage = STAGE_QS_TRANSFER_END )*/ + /*]]*/; + + // Start of the receiving attachments phase at receiver. + RECEIVE_ATTACHMENTS_START = 17 + /*[ device_role = DEVICE_ROLE_REMOTE ]*/; + + // End of receiving attachments phase at receiver. + RECEIVE_ATTACHMENTS_END = 18 /*[[*/ + /*( device_role = DEVICE_ROLE_REMOTE, )*/ + /*( stage = STAGE_QS_TRANSFER_END )*/ + /*]]*/; + + // Sender cancels sending attachments. + CANCEL_SENDING_ATTACHMENTS = 19 + /*[ device_role = DEVICE_ROLE_INITIATOR ]*/; + + // Receiver cancels receiving attachments. + CANCEL_RECEIVING_ATTACHMENTS = 20 + /*[ device_role = DEVICE_ROLE_REMOTE ]*/; + + // Receiver opens received attachments. + OPEN_RECEIVED_ATTACHMENTS = 21 + /*[ device_role = DEVICE_ROLE_REMOTE ]*/; + + // User opens the setup activity. + LAUNCH_SETUP_ACTIVITY = 22 [deprecated = true]; + + // User adds a contact. + ADD_CONTACT = 23; + + // User removes a contact. + REMOVE_CONTACT = 24; + + // Local devices all Fast Share server. + FAST_SHARE_SERVER_RESPONSE = 25; + + // The start of a sending session. + SEND_START = 26 /*[[*/ + /*( device_role = DEVICE_ROLE_INITIATOR, )*/ + /*( stage = STAGE_QS_CONNECT_START )*/ + /*]]*/; + + // Receiver accepts a fast initialization. + ACCEPT_FAST_INITIALIZATION = 27 + /*[ device_role = DEVICE_ROLE_REMOTE ]*/; + + // Set data usage preference. + SET_DATA_USAGE = 28; + + // Receiver dismisses a fast initialization + DISMISS_FAST_INITIALIZATION = 29 + /*[ device_role = DEVICE_ROLE_REMOTE ]*/; + + // Cancel connection. + CANCEL_CONNECTION = 30 + /*[ device_role = DEVICE_ROLE_INITIATOR ]*/; + + // User starts a chimera activity (e.g. ConsentsChimeraActivity, + // DeviceVisibilityChimeraActivity...) + LAUNCH_ACTIVITY = 31; + + // Receiver dismisses a privacy notification. + DISMISS_PRIVACY_NOTIFICATION = 32; + + // Receiver taps a privacy notification. + TAP_PRIVACY_NOTIFICATION = 33; + + // Receiver taps a help page. + TAP_HELP = 34; + + // Receiver taps a feedback. + TAP_FEEDBACK = 35; + + // Receiver adds quick settings tile. + ADD_QUICK_SETTINGS_TILE = 36; + + // Receiver removes quick settings tile. + REMOVE_QUICK_SETTINGS_TILE = 37; + + // Receiver phone consent clicked. + LAUNCH_PHONE_CONSENT = 38; + + // Devices show a phone consent banner in Nearby Share Settings + DISPLAY_PHONE_CONSENT = 54; + + // Receiver taps quick settings tile. + TAP_QUICK_SETTINGS_TILE = 39; + + // Receiver Installation of APKs status. + INSTALL_APK = 40 /*[ device_role = DEVICE_ROLE_REMOTE ]*/; + + // Receiver verification of APKs status. + VERIFY_APK = 41 /*[ device_role = DEVICE_ROLE_REMOTE ]*/; + + // User starts a consent. + LAUNCH_CONSENT = 42; + + // After receiving payloads, Nearby Share still needs to transfer the payloads + // to correct attachment formats and move files attachments from temporary + // directory to final destination. + PROCESS_RECEIVED_ATTACHMENTS_END = 43 + /*[ device_role = DEVICE_ROLE_REMOTE ]*/; + + // Toggle Show Notification setting item in Nearby Share setting. + TOGGLE_SHOW_NOTIFICATION = 44; + + // Set device name + SET_DEVICE_NAME = 45; + + // users dropped off opt-in page. + DECLINE_AGREEMENTS = 46; + + // Request setting permissions (Wifi/BT/location/airplane mode). + REQUEST_SETTING_PERMISSIONS = 47; + + // Set up a connection with the remote device. + ESTABLISH_CONNECTION = 48 /*[ stage = STAGE_QS_CONNECT_END ]*/; + + // Track device states in Nearby Share setting. + DEVICE_SETTINGS = 49; + + // Receiver auto dismisses a fast initialization notification. + AUTO_DISMISS_FAST_INITIALIZATION = 50 + /*[ device_role = DEVICE_ROLE_REMOTE ]*/; + + // App Crash event. + // Used only for Windows App now. + APP_CRASH = 51; + + // Sender taps the Send button in quick settings + TAP_QUICK_SETTINGS_FILE_SHARE = 52 + /*[ device_role = DEVICE_ROLE_INITIATOR ]*/; + + // Devices show a privacy notification + DISPLAY_PRIVACY_NOTIFICATION = 53; + + // Preference usage event (e.g. load/save preferences, etc). + // Used only for Windows App now. + PREFERENCES_USAGE = 55; + + // Default opt in + DEFAULT_OPT_IN = 56; + + // The result of the setup wizard flow + SETUP_WIZARD = 57; + + // Sender taps a QR code + TAP_QR_CODE = 58 /*[ device_role = DEVICE_ROLE_INITIATOR ]*/; + + // QR code link shown + QR_CODE_LINK_SHOWN = 59 + /*[ device_role = DEVICE_ROLE_INITIATOR ]*/; + + // Sender failed to parse endpoint id. + PARSING_FAILED_ENDPOINT_ID = 60 + /*[ device_role = DEVICE_ROLE_INITIATOR ]*/; + + // The device is discovered by fast initialization + FAST_INIT_DISCOVER_DEVICE = 61 + /*[ device_role = DEVICE_ROLE_REMOTE ]*/; + + // Send desktop notification. + SEND_DESKTOP_NOTIFICATION = 62; + + // User sets account preference + SET_ACCOUNT = 63; + + // Decrypt certificate failure + DECRYPT_CERTIFICATE_FAILURE = 64; + + // Show allow permission auto access UI + SHOW_ALLOW_PERMISSION_AUTO_ACCESS = 65 + /*[ device_role = DEVICE_ROLE_INITIATOR ]*/; + + // UI events for transferring files with desktop applications. It includes + // event types such as DESKTOP_TRANSFER_EVENT_SEND_TYPE_SELECT_A_DEVICE. + SEND_DESKTOP_TRANSFER_EVENT = 66; + + // Show accept button on Quick Share receive UI + WAITING_FOR_ACCEPT = 67; + + // High quality event setup + HIGH_QUALITY_MEDIUM_SETUP = 68; + + // RPC call status + RPC_CALL_STATUS = 69; + + // A QR code sharing session has started + START_QR_CODE_SESSION = 70; + + // A QR code URL has been opened in a web client/browser instead of in a + // native Quick Share app. + QR_CODE_OPENED_IN_WEB_CLIENT = 71; + + // A HaTS survey session id has been joined with Quick Share flow id. + HATS_JOINT_EVENT = 72; + + // Previews received. + RECEIVE_PREVIEWS = 73; + + // LINT.ThenChange(//depot/google3/location/nearby/proto/nearby_event_codes.proto:SharingEventCode) +} + +// Event category to differentiate whether this comes from sender or receiver, +// whether this is for communication flow, or for settings. +enum EventCategory { + UNKNOWN_EVENT_CATEGORY = 0; + SENDING_EVENT = 1; + RECEIVING_EVENT = 2; + SETTINGS_EVENT = 3; + RPC_EVENT = 4; +} + +// Status of nearby sharing. +enum NearbySharingStatus { + UNKNOWN_NEARBY_SHARING_STATUS = 0; + + ON = 1; + OFF = 2; +} + +enum Visibility { + UNKNOWN_VISIBILITY = 0; + + CONTACTS_ONLY = 1; + EVERYONE = 2; + SELECTED_CONTACTS_ONLY = 3 [deprecated = true]; + HIDDEN = 4; + SELF_SHARE = 5; +} + +enum DataUsage { + UNKNOWN_DATA_USAGE = 0; + + ONLINE = 1; + WIFI_ONLY = 2; + OFFLINE = 3; +} + +// The status of establishing a connection. Used by ESTABLISH_CONNECTION. +enum EstablishConnectionStatus { + CONNECTION_STATUS_UNKNOWN = 0; + + CONNECTION_STATUS_SUCCESS = 1 + /*[ status_bucket = STATUS_SUCCESSFUL ]*/; + CONNECTION_STATUS_FAILURE = 2 + /*[ status_bucket = STATUS_INTERNAL_ERROR ]*/; + CONNECTION_STATUS_CANCELLATION = 3 + /*[ status_bucket = STATUS_CANCELLED ]*/; + CONNECTION_STATUS_MEDIA_UNAVAILABLE_ATTACHMENT = 4 + /*[ status_bucket = STATUS_INTERNAL_ERROR ]*/; + CONNECTION_STATUS_FAILED_PAIRED_KEYHANDSHAKE = 5 + /*[ status_bucket = STATUS_INTERNAL_ERROR ]*/; + CONNECTION_STATUS_FAILED_WRITE_INTRODUCTION = 6 + /*[ status_bucket = STATUS_INTERNAL_ERROR ]*/; + CONNECTION_STATUS_FAILED_NULL_CONNECTION = 7 + /*[ status_bucket = STATUS_INTERNAL_ERROR ]*/; + CONNECTION_STATUS_FAILED_NO_TRANSFER_UPDATE_CALLBACK = 8 + /*[ status_bucket = STATUS_INTERNAL_ERROR ]*/; + CONNECTION_STATUS_LOST_CONNECTIVITY = 9 + /*[ status_bucket = STATUS_INTERRUPTION ]*/; + // TODO: b/341782941 - : Annote this status when it's confirmed by Nearby + // Connections team. + CONNECTION_STATUS_INVALID_ADVERTISEMENT = 10; +} + +// The status of sending and receiving attachments. Used by SEND_ATTACHMENTS. +enum AttachmentTransmissionStatus { + UNKNOWN_ATTACHMENT_TRANSMISSION_STATUS = 0; + COMPLETE_ATTACHMENT_TRANSMISSION_STATUS = 1 + /*[ status_bucket = STATUS_SUCCESSFUL ]*/; + CANCELED_ATTACHMENT_TRANSMISSION_STATUS = 2 + /*[ status_bucket = STATUS_CANCELLED ]*/; + FAILED_ATTACHMENT_TRANSMISSION_STATUS = 3 + /*[ status_bucket = STATUS_INTERNAL_ERROR ]*/; + REJECTED_ATTACHMENT = 4 [deprecated = true]; + TIMED_OUT_ATTACHMENT = 5 [deprecated = true]; + AWAITING_REMOTE_ACCEPTANCE_FAILED_ATTACHMENT = 6 [deprecated = true]; + NOT_ENOUGH_SPACE_ATTACHMENT = 7 [deprecated = true]; + FAILED_NO_TRANSFER_UPDATE_CALLBACK = 8 [deprecated = true]; + MEDIA_UNAVAILABLE_ATTACHMENT = 9 [deprecated = true]; + UNSUPPORTED_ATTACHMENT_TYPE_ATTACHMENT = 10 [deprecated = true]; + NO_ATTACHMENT_FOUND = 11 [deprecated = true]; + FAILED_NO_SHARE_TARGET_ENDPOINT = 12 [deprecated = true]; + FAILED_PAIRED_KEYHANDSHAKE = 13 [deprecated = true]; + FAILED_NULL_CONNECTION = 14 [deprecated = true]; + FAILED_NO_PAYLOAD = 15 [deprecated = true]; + FAILED_WRITE_INTRODUCTION = 16 [deprecated = true]; + + // The remote response is either missing or has an unknown type. + FAILED_UNKNOWN_REMOTE_RESPONSE = 17 [deprecated = true]; + + // Breakdowns of FAILED_NULL_CONNECTION (Desktop side) + FAILED_NULL_CONNECTION_INIT_OUTGOING = 18; + FAILED_NULL_CONNECTION_DISCONNECTED = 19; + + // Breakdowns of FAILED_NULL_CONNECTION (android side) + // Connection failed due to Wifi is disconnected or Bluetooth setting is off + // or user turn on airplane mode. + FAILED_NULL_CONNECTION_LOST_CONNECTIVITY = 20 [deprecated = true]; + // Unexpected connection failure. + FAILED_NULL_CONNECTION_FAILURE = 21 [deprecated = true]; + + REJECTED_ATTACHMENT_TRANSMISSION_STATUS = 22; + TIMED_OUT_ATTACHMENT_TRANSMISSION_STATUS = 23; + NOT_ENOUGH_SPACE_ATTACHMENT_TRANSMISSION_STATUS = 24 + /*[ status_bucket = STATUS_INTERRUPTION ]*/; + UNSUPPORTED_ATTACHMENT_TYPE_ATTACHMENT_TRANSMISSION_STATUS = 25; + FAILED_UNKNOWN_REMOTE_RESPONSE_TRANSMISSION_STATUS = 26; + // Connection failed due to Wifi is disconnected or Bluetooth setting is off + // or user turn on airplane mode. + NO_RESPONSE_FRAME_CONNECTION_CLOSED_LOST_CONNECTIVITY_TRANSMISSION_STATUS = 27 + [deprecated = true]; + // Unexpected connection failure due to no response frame. + NO_RESPONSE_FRAME_CONNECTION_CLOSED_TRANSMISSION_STATUS = 28; + // Connection failed due to Wifi is disconnected or Bluetooth setting is off + // or user turn on airplane mode. + LOST_CONNECTIVITY_TRANSMISSION_STATUS = 29 + /*[ status_bucket = STATUS_INTERRUPTION ]*/; + // Connection failed due to the medium is not allowed. + FAILED_DISALLOWED_MEDIUM = 30; +} + +// Generic result status of NearbyConnections API calls. +enum ConnectionLayerStatus { + // No status is available + CONNECTION_LAYER_STATUS_UNKNOWN = 0; + // The operation was successful. + CONNECTION_LAYER_STATUS_SUCCESS = 1; + // The operation failed, without any more information. + CONNECTION_LAYER_STATUS_ERROR = 2; + // The app called an API method out of order (i.e. another method is expected + // to be called first). + CONNECTION_LAYER_STATUS_OUT_OF_ORDER_API_CALL = 3; + // The app already has active operations (advertising, discovering, or + // connected to other devices) with another Strategy. Stop these operations on + // the current Strategy before trying to advertise or discover with a new + // Strategy. + CONNECTION_LAYER_STATUS_ALREADY_HAVE_ACTIVE_STRATEGY = 4; + // The app is already advertising; call StopAdvertising() before trying to + // advertise again. + CONNECTION_LAYER_STATUS_ALREADY_ADVERTISING = 5; + // The app is already discovering; call StopDiscovery() before trying to + // discover again. + CONNECTION_LAYER_STATUS_ALREADY_DISCOVERING = 6; + // NC is already listening for incoming connections from remote endpoints. + CONNECTION_LAYER_STATUS_ALREADY_LISTENING = 7; + // An attempt to read from/write to a connected remote endpoint failed. If + // this occurs repeatedly, consider invoking DisconnectFromEndpoint(). + CONNECTION_LAYER_STATUS_END_POINT_IO_ERROR = 8; + // An attempt to interact with a remote endpoint failed because it's unknown + // to us -- it's either an endpoint that was never discovered, or an endpoint + // that never connected to us (both of which are indicative of bad input from + // the client app). + CONNECTION_LAYER_STATUS_END_POINT_UNKNOWN = 9; + // The remote endpoint rejected the connection request. + CONNECTION_LAYER_STATUS_CONNECTION_REJECTED = 10; + // The app is already connected to the specified endpoint. Multiple + // connections to a remote endpoint cannot be maintained simultaneously. + CONNECTION_LAYER_STATUS_ALREADY_CONNECTED_TO_END_POINT = 11; + // The remote endpoint is not connected; messages cannot be sent to it. + CONNECTION_LAYER_STATUS_NOT_CONNECTED_TO_END_POINT = 12; + // There was an error trying to use the device's Bluetooth capabilities. + CONNECTION_LAYER_STATUS_BLUETOOTH_ERROR = 13; + // There was an error trying to use the device's Bluetooth Low Energy + // capabilities. + CONNECTION_LAYER_STATUS_BLE_ERROR = 14; + // There was an error trying to use the device's Wi-Fi capabilities. + CONNECTION_LAYER_STATUS_WIFI_LAN_ERROR = 15; + // An attempt to interact with an in-flight Payload failed because it's + // unknown to us. + CONNECTION_LAYER_STATUS_PAYLOAD_UNKNOWN = 16; + // The connection was reset + CONNECTION_LAYER_STATUS_RESET = 17; + // The connection timed out + CONNECTION_LAYER_STATUS_TIMEOUT = 18; +} + +// The status of processing attachments after receiver received payloads +// successfully. +enum ProcessReceivedAttachmentsStatus { + PROCESSING_STATUS_UNKNOWN = 0; + + PROCESSING_STATUS_COMPLETE_PROCESSING_ATTACHMENTS = 1; + PROCESSING_STATUS_FAILED_MOVING_FILES = 2; + PROCESSING_STATUS_FAILED_RECEIVING_APK = 3; + PROCESSING_STATUS_FAILED_RECEIVING_TEXT = 4; + PROCESSING_STATUS_FAILED_RECEIVING_WIFI_CREDENTIALS = 5; +} + +// The status of advertising and discovering sessions. Used by +// SCAN_FOR_SHARE_TARGETS and ADVERTISE_DEVICE_PRESENCE. +enum SessionStatus { + UNKNOWN_SESSION_STATUS = 0; + SUCCEEDED_SESSION_STATUS = 1 + /*[ status_bucket = STATUS_SUCCESSFUL ]*/; + + // TODO: b/341782941 - FAILED_SESSION_STATUS occurs when the status of + // advertising or discovering sessions is not successful. It can be + // due to STATUS_INTERNAL_ERROR, STATUS_INTERRUPTION, STATUS_CANCELLED. + // More session statuses should be logged to determine the status. + FAILED_SESSION_STATUS = 2; +} + +// User's response to introductions. +enum ResponseToIntroduction { + UNKNOWN_RESPONSE_TO_INTRODUCTION = 0; + + ACCEPT_INTRODUCTION = 1; + REJECT_INTRODUCTION = 2; + FAIL_INTRODUCTION = 3; +} + +// TODO(fdi): may eventually include desktop, etc. +// The type of a remote device. +enum DeviceType { + UNKNOWN_DEVICE_TYPE = 0; + + PHONE = 1; + TABLET = 2; + LAPTOP = 3; + CAR = 4; + FOLDABLE = 5; + XR = 6; +} + +// TODO(fdi): may eventually include windows, iOS, etc. +// The OS type of a remote device. +enum OSType { + UNKNOWN_OS_TYPE = 0; + + ANDROID = 1; + CHROME_OS = 2; + IOS = 3; + WINDOWS = 4; + MACOS = 5; +} + +// Relationship of remote device to sender device. +enum DeviceRelationship { + UNKNOWN_DEVICE_RELATIONSHIP = 0; + + // The remote device belongs to the same owner as sender device. + IS_SELF = 1; + // The remote device is a contact of sender. + IS_CONTACT = 2; + // The remote device is a stranger. + IS_STRANGER = 3; +} + +// The device sources of the clearcut log. +enum LogSource { + UNSPECIFIED_SOURCE = 0; + + // Represents the devices in Nearby labs. + LAB_DEVICES = 1; + // Represents the devices tested by Nearby engs, in the long term can include + // any devices with newest feature flags. + INTERNAL_DEVICES = 2; + // Represents the devices testing our in-development features before they're + // released to the greater public. + BETA_TESTER_DEVICES = 3; + // Represents the OEM partners (like Samsung) that we're working with to + // verify functionality on their devices. + OEM_DEVICES = 4; + // Represents the device for debugging. + DEBUG_DEVICES = 5; + // Represents the device for Nearby Module Food. + NEARBY_MODULE_FOOD_DEVICES = 6; + // Represents the device for BeTo Team Food. + BETO_DOGFOOD_DEVICES = 7; + // Represents the device for Nearby dog Food. + NEARBY_DOGFOOD_DEVICES = 8; + // Represents the device for Nearby Team Food. + NEARBY_TEAMFOOD_DEVICES = 9; +} + +// The Fast Share server action name. +enum ServerActionName { + UNKNOWN_SERVER_ACTION = 0; + + UPLOAD_CERTIFICATES = 1; + DOWNLOAD_CERTIFICATES = 2; + CHECK_REACHABILITY = 3; + UPLOAD_CONTACTS = 4; + UPDATE_DEVICE_NAME = 5; + UPLOAD_SENDER_CERTIFICATES = 6; + DOWNLOAD_SENDER_CERTIFICATES = 7; + UPLOAD_CONTACTS_AND_CERTIFICATES = 8; + LIST_REACHABLE_PHONE_NUMBERS = 9; + LIST_MY_DEVICES = 10; + LIST_CONTACT_PEOPLE = 11; + + // used for analytics logger to record action name. + DOWNLOAD_CERTIFICATES_INFO = 12; +} + +// The Fast Share server response state. +enum ServerResponseState { + UNKNOWN_SERVER_RESPONSE_STATE = 0; + + SERVER_RESPONSE_SUCCESS = 1; + SERVER_RESPONSE_UNKNOWN_FAILURE = 2; + + // For StatusException. + SERVER_RESPONSE_STATUS_OTHER_FAILURE = 3; + SERVER_RESPONSE_STATUS_DEADLINE_EXCEEDED = 4; + SERVER_RESPONSE_STATUS_PERMISSION_DENIED = 5; + SERVER_RESPONSE_STATUS_UNAVAILABLE = 6; + SERVER_RESPONSE_STATUS_UNAUTHENTICATED = 7; + SERVER_RESPONSE_STATUS_INVALID_ARGUMENT = 9; + + // For GoogleAuthException. + SERVER_RESPONSE_GOOGLE_AUTH_FAILURE = 8; + + // For Internet connect status. + SERVER_RESPONSE_NOT_CONNECTED_TO_INTERNET = 10; +} + +// The purpose of requesting the server request. +enum SyncPurpose { + SYNC_PURPOSE_UNKNOWN = 0; + // When NearbySharingChimeraService#sync() is called. + SYNC_PURPOSE_ON_DEMAND_SYNC = 1; + // Requested by chime notification. + SYNC_PURPOSE_CHIME_NOTIFICATION = 2; + // For reqular daily sync. + SYNC_PURPOSE_DAILY_SYNC = 3; + // Wen a device opts into Nearby Share. + SYNC_PURPOSE_OPT_IN_FIRST_SYNC = 4; + // Requested when Nearby Share automatically enables a device that shares + // a single account that has already opted in on another device. + SYNC_PURPOSE_CHECK_DEFAULT_OPT_IN = 5; + // When a device enables Nearby Share. + SYNC_PURPOSE_NEARBY_SHARE_ENABLED = 6; + // When a device is in fast init advertising. + SYNC_PURPOSE_SYNC_AT_FAST_INIT = 7; + // When device start discovery. + SYNC_PURPOSE_SYNC_AT_DISCOVERY = 8; + // When device tries to load valid private certificate. + SYNC_PURPOSE_SYNC_AT_LOAD_PRIVATE_CERTIFICATE = 9; + // When device start advertiseement. + SYNC_PURPOSE_SYNC_AT_ADVERTISEMENT = 10; + // When device contacts list changes. + SYNC_PURPOSE_CONTACT_LIST_CHANGE = 11; + // When showing the C11 banner in Neary Share setting. + SYNC_PURPOSE_SHOW_C11N_VIEW = 12; + // For regular check contact reachability. + SYNC_PURPOSE_REGULAR_CHECK_CONTACT_REACHABILITY = 13; + // When selected contacts list changes in visibility setting. + SYNC_PURPOSE_VISIBILITY_SELECTED_CONTACT_CHANGE = 14; + // When switching account. + SYNC_PURPOSE_ACCOUNT_CHANGE = 15; + // When regenerate certificates + SYNC_PURPOSE_REGENERATE_CERTIFICATES = 16; + // When Device Contacts consent changes + SYNC_PURPOSE_DEVICE_CONTACTS_CONSENT_CHANGE = 17; +} + +// The device role to trigger the server request. +enum ClientRole { + CLIENT_ROLE_UNKNOWN = 0; + CLIENT_ROLE_SENDER = 1; + CLIENT_ROLE_RECEIVER = 2; +} + +// The type of Nearby Sharing scanning. +enum ScanType { + UNKNOWN_SCAN_TYPE = 0; + FOREGROUND_SCAN = 1; + FOREGROUND_RETRY_SCAN = 2; + DIRECT_SHARE_SCAN = 3; + BACKGROUND_SCAN = 4; +} + +// The type of parsing endpoint id failed type. +enum ParsingFailedType { + FAILED_UNKNOWN_TYPE = 0; + // NULL advertisement is returned due to sender failing to parse advertisement + // from endpointInfo byte stream from receiver advertisement. + FAILED_PARSE_ADVERTISEMENT = 1; + // NULL shareTarget is returned due to sender failing to create shareTarget + // from a valid parsed advertisement stemming from issues in certificates, QR + // code tokens or device names. + FAILED_CONVERT_SHARE_TARGET = 2; +} + +// The Nearby Sharing advertising mode. +enum AdvertisingMode { + UNKNOWN_ADVERTISING_MODE = 0; + SCREEN_OFF_ADVERTISING_MODE = 1; + BACKGROUND_ADVERTISING_MODE = 2; + MIDGROUND_ADVERTISING_MODE = 3; + FOREGROUND_ADVERTISING_MODE = 4; + SUSPENDED_ADVERTISING_MODE = 5; +} + +// The Nearby Sharing discovery mode. +enum DiscoveryMode { + UNKNOWN_DISCOVERY_MODE = 0; + SCREEN_OFF_DISCOVERY_MODE = 1; + BACKGROUND_DISCOVERY_MODE = 2; + MIDGROUND_DISCOVERY_MODE = 3; + FOREGROUND_DISCOVERY_MODE = 4; + SUSPENDED_DISCOVERY_MODE = 5; +} + +// The class name of chimera activity. +enum ActivityName { + UNKNOWN_ACTIVITY = 0; + + SHARE_SHEET_ACTIVITY = 1; + SETTINGS_ACTIVITY = 2; + RECEIVE_SURFACE_ACTIVITY = 3; + SETUP_ACTIVITY = 4; + DEVICE_VISIBILITY_ACTIVITY = 5; + CONSENTS_ACTIVITY = 6; + SET_DEVICE_NAME_DIALOG = 7; + SET_DATA_USAGE_DIALOG = 8; + QUICK_SETTINGS_ACTIVITY = 9; + REMOTE_COPY_SHARE_SHEET_ACTIVITY = 10; + SETUP_WIZARD_ACTIVITY = 11; + SETTINGS_REVIEW_ACTIVITY = 12; +} + +// The type of consent page user launches. +enum ConsentType { + CONSENT_TYPE_UNKNOWN = 0; + + // launch Constellation consent page. + CONSENT_TYPE_C11N = 1; + // launch device contact consent page. + CONSENT_TYPE_DEVICE_CONTACT = 2; +} + +enum ConsentAcceptanceStatus { + CONSENT_UNKNOWN_ACCEPT_STATUS = 0; + + CONSENT_ACCEPTED = 1; + CONSENT_DECLINED = 2; + // unable to enable consent. + CONSENT_UNABLE_TO_ENABLE = 3; +} + +enum ApkSource { + UNKNOWN_APK_SOURCE = 0; + + APK_FROM_SD_CARD = 1; + INSTALLED_APP = 2; +} + +// The Installation status of APK. +enum InstallAPKStatus { + UNKNOWN_INSTALL_APK_STATUS = 0; + + FAIL_INSTALLATION = 1; + SUCCESS_INSTALLATION = 2; +} + +// The verification status of APK. +enum VerifyAPKStatus { + UNKNOWN_VERIFY_APK_STATUS = 0; + + NOT_INSTALLABLE = 1; + INSTALLABLE = 2; + ALREADY_INSTALLED = 3; +} + +enum ShowNotificationStatus { + UNKNOWN_SHOW_NOTIFICATION_STATUS = 0; + + SHOW = 1; + NOT_SHOW = 2; +} + +enum PermissionRequestResult { + PERMISSION_UNKNOWN_REQUEST_RESULT = 0; + + PERMISSION_GRANTED = 1; + PERMISSION_REJECTED = 2; + PERMISSION_UNABLE_TO_GRANT = 3; +} + +enum PermissionRequestType { + PERMISSION_UNKNOWN_TYPE = 0; + + PERMISSION_AIRPLANE_MODE_OFF = 1; + PERMISSION_WIFI = 2; + PERMISSION_BLUETOOTH = 3; + PERMISSION_LOCATION = 4; + PERMISSION_WIFI_HOTSPOT = 5; +} + +enum SharingUseCase { + USE_CASE_UNKNOWN = 0; + + USE_CASE_NEARBY_SHARE = 1; + USE_CASE_REMOTE_COPY_PASTE = 2; + USE_CASE_WIFI_CREDENTIAL = 3; + USE_CASE_APP_SHARE = 4; + USE_CASE_QUICK_SETTING_FILE_SHARE = 5; + USE_CASE_SETUP_WIZARD = 6; + // Deprecated. QR code is an addition to existing use cases rather than being + // a separate use case. + USE_CASE_NEARBY_SHARE_WITH_QR_CODE = 7 [deprecated = true]; + // The user was redirected from Bluetooth sharing UI to Nearby Share + USE_CASE_REDIRECTED_FROM_BLUETOOTH_SHARE = 8; +} + +// Used only for Windows App now. +enum AppCrashReason { + APP_CRASH_REASON_UNKNOWN = 0; +} + +// Thes source where the attachemnt comes from. It can be an action, app name, +// etc. The first 6 source types are being used as FileSenderType in Nearby +// Share Windows app. +enum AttachmentSourceType { + ATTACHMENT_SOURCE_UNKNOWN = 0; + ATTACHMENT_SOURCE_CONTEXT_MENU = 1; + ATTACHMENT_SOURCE_DRAG_AND_DROP = 2; + ATTACHMENT_SOURCE_SELECT_FILES_BUTTON = 3; + ATTACHMENT_SOURCE_PASTE = 4; + ATTACHMENT_SOURCE_SELECT_FOLDERS_BUTTON = 5; + ATTACHMENT_SOURCE_SHARE_ACTIVATION = 6; +} + +// The action to interact with preferences. +// Used only for Windows App now. +enum PreferencesAction { + PREFERENCES_ACTION_UNKNOWN = 0; + PREFERENCES_ACTION_NO_ACTION = 1; + + // Primary actions/functions towards preferences + PREFERENCES_ACTION_LOAD_PREFERENCES = 2; + PREFERENCES_ACTION_SAVE_PREFERENCESS = 3; + PREFERENCES_ACTION_ATTEMPT_LOAD = 4; + PREFERENCES_ACTION_RESTORE_FROM_BACKUP = 5; + + // Other actions within the 4 actions above + PREFERENCES_ACTION_CREATE_PREFERENCES_PATH = 6; + PREFERENCES_ACTION_MAKE_PREFERENCES_BACKUP_FILE = 7; + PREFERENCES_ACTION_CHECK_IF_PREFERENCES_PATH_EXISTS = 8; + PREFERENCES_ACTION_CHECK_IF_PREFERENCES_INPUT_STREAM_STATUS = 9; + PREFERENCES_ACTION_CHECK_IF_PREFERENCES_FILE_IS_CORRUPTED = 10; + PREFERENCES_ACTION_CHECK_IF_PREFERENCES_BACKUP_FILE_EXISTS = 11; +} + +// The status of the action to interact with preferences. +// Used only for Windows App now. +enum PreferencesActionStatus { + PREFERENCES_ACTION_STATUS_UNKNOWN = 0; + PREFERENCES_ACTION_STATUS_SUCCESS = 1; + PREFERENCES_ACTION_STATUS_FAIL = 2; +} + +/** The distance of the found nearby fast init advertisement. */ +enum FastInitState { + FAST_INIT_UNKNOWN_STATE = 0; + // A device was found in close proximity. + // distance < fast_init_distance_close_centimeters(50 cm) + FAST_INIT_CLOSE_STATE = 1; + // A device was found in far proximity. + // distance < fast_init_distance_close_centimeters(10 m) + FAST_INIT_FAR_STATE = 2; + // No devices have been found nearby. The default state. + FAST_INIT_LOST_STATE = 3; +} + +/** The type of FastInit advertisement. */ +enum FastInitType { + FAST_INIT_UNKNOWN_TYPE = 0; + // Show HUN to notify the user. + FAST_INIT_NOTIFY_TYPE = 1; + // Not notify the user. + FAST_INIT_SILENT_TYPE = 2; +} + +// LINT.IfChange +/** The type of desktop notification event. */ +enum DesktopNotification { + DESKTOP_NOTIFICATION_UNKNOWN = 0; + DESKTOP_NOTIFICATION_CONNECTING = 1; + DESKTOP_NOTIFICATION_PROGRESS = 2; + DESKTOP_NOTIFICATION_ACCEPT = 3; + DESKTOP_NOTIFICATION_RECEIVED = 4; + DESKTOP_NOTIFICATION_ERROR = 5; +} + +enum DesktopTransferEventType { + DESKTOP_TRANSFER_EVENT_TYPE_UNKNOWN = 0; + + // Receive attachments. + DESKTOP_TRANSFER_EVENT_RECEIVE_TYPE_ACCEPT = 1; + DESKTOP_TRANSFER_EVENT_RECEIVE_TYPE_PROGRESS = 2; + DESKTOP_TRANSFER_EVENT_RECEIVE_TYPE_RECEIVED = 3; + DESKTOP_TRANSFER_EVENT_RECEIVE_TYPE_ERROR = 4; + + // Send attachments. + DESKTOP_TRANSFER_EVENT_SEND_TYPE_START = 5; + DESKTOP_TRANSFER_EVENT_SEND_TYPE_SELECT_A_DEVICE = 6; + DESKTOP_TRANSFER_EVENT_SEND_TYPE_PROGRESS = 7; + DESKTOP_TRANSFER_EVENT_SEND_TYPE_SENT = 8; + DESKTOP_TRANSFER_EVENT_SEND_TYPE_ERROR = 9; +} + +enum DecryptCertificateFailureStatus { + DECRYPT_CERT_UNKNOWN_FAILURE = 0; + DECRYPT_CERT_NO_SUCH_ALGORITHM_FAILURE = 1; + DECRYPT_CERT_NO_SUCH_PADDING_FAILURE = 2; + DECRYPT_CERT_INVALID_KEY_FAILURE = 3; + DECRYPT_CERT_INVALID_ALGORITHM_PARAMETER_FAILURE = 4; + DECRYPT_CERT_ILLEGAL_BLOCK_SIZE_FAILURE = 5; + DECRYPT_CERT_BAD_PADDING_FAILURE = 6; +} + +// Refer to go/qs-contacts-consent-2024 for the detail. +enum ContactAccess { + CONTACT_ACCESS_UNKNOWN = 0; + + CONTACT_ACCESS_NO_CONTACT_UPLOADED = 1; + CONTACT_ACCESS_ONLY_UPLOAD_GOOGLE_CONTACT = 2; + CONTACT_ACCESS_UPLOAD_CONTACT_FOR_DEVICE_CONTACT_CONSENT = 3; + CONTACT_ACCESS_UPLOAD_CONTACT_FOR_QUICK_SHARE_CONSENT = 4; +} + +// Refer to go/qs-contacts-consent-2024 for the detail. +enum IdentityVerification { + IDENTITY_VERIFICATION_UNKNOWN = 0; + + IDENTITY_VERIFICATION_NO_PHONE_NUMBER_VERIFIED = 1; + IDENTITY_VERIFICATION_PHONE_NUMBER_VERIFIED_NOT_LINKED_TO_GAIA = 2; + IDENTITY_VERIFICATION_PHONE_NUMBER_VERIFIED_LINKED_TO_QS_GAIA = 3; +} + +enum ButtonStatus { + BUTTON_STATUS_UNKNOWN = 0; + BUTTON_STATUS_CLICK_ACCEPT = 1; + BUTTON_STATUS_CLICK_REJECT = 2; + BUTTON_STATUS_IGNORE = 3; +} +// LINT.ThenChange( +// //depot/google3/location/nearby/cpp/sharing/clients/dart/platform/lib/types/models.dart +// ) diff --git a/NearbyShare/Model/ProtobufSource/wire_format.proto b/NearbyShare/Model/ProtobufSource/wire_format.proto index 6853e22..82510d3 100644 --- a/NearbyShare/Model/ProtobufSource/wire_format.proto +++ b/NearbyShare/Model/ProtobufSource/wire_format.proto @@ -1,26 +1,41 @@ -// Copyright 2020 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// Brought from: //depot/google3/location/nearby/sharing/proto/wire_format.proto -// At CL 317565061 +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. syntax = "proto2"; +//package nearby.sharing.service.proto; package sharing.nearby; -// Required in Chrome. +// import "storage/datapol/annotations/proto/semantic_annotations.proto"; +import "sharing_enums.proto"; + +option java_package = "com.google.android.gms.nearby.sharing"; +option java_outer_classname = "Protocol"; +option objc_class_prefix = "GNSHP"; option optimize_for = LITE_RUNTIME; // File metadata. Does not include the actual bytes of the file. -// NEXT_ID=6 +// NEXT_ID=10 message FileMetadata { enum Type { UNKNOWN = 0; IMAGE = 1; VIDEO = 2; - APP = 3; + ANDROID_APP = 3; AUDIO = 4; + DOCUMENT = 5; + CONTACT_CARD = 6; } // The human readable name of this file (eg. 'Cookbook.pdf'). @@ -43,9 +58,19 @@ message FileMetadata { // A uuid for the attachment. Should be unique across all attachments. optional int64 id = 6; + + // The parent folder. + optional string parent_folder = 7; + + // A stable identifier for the attachment. Used for receiver to identify same + // attachment from different transfers. + optional int64 attachment_hash = 8; + + // True, if image in file attachment is sensitive + optional bool is_sensitive_content = 9; } -// NEXT_ID=5 +// NEXT_ID=8 message TextMetadata { enum Type { UNKNOWN = 0; @@ -73,15 +98,19 @@ message TextMetadata { // A uuid for the attachment. Should be unique across all attachments. optional int64 id = 6; + + // True if text is sensitive, e.g. password + optional bool is_sensitive_text = 7; } -// NEXT_ID=5 +// NEXT_ID=6 message WifiCredentialsMetadata { enum SecurityType { UNKNOWN_SECURITY_TYPE = 0; OPEN = 1; WPA_PSK = 2; WEP = 3; + SAE = 4; } // The Wifi network name. This will be sent in introduction. @@ -98,6 +127,49 @@ message WifiCredentialsMetadata { optional int64 id = 5; } +// NEXT_ID=8 +message AppMetadata { + // The app name. This will be sent in introduction. + optional string app_name = 1; + + // The size of the all split of apks. + optional int64 size = 2; + + // The File payload id that will be sent as a follow up containing the + // apk paths. + repeated int64 payload_id = 3 [packed = true]; + + // A uuid for the attachment. Should be unique across all attachments. + optional int64 id = 4; + + // The name of apk file. This will be sent in introduction. + repeated string file_name = 5; + + // The size of apk file. This will be sent in introduction. + repeated int64 file_size = 6 [packed = true]; + + // The package name. This will be sent in introduction. + optional string package_name = 7; +} + +// NEXT_ID=5 +message StreamMetadata { + // A human readable description for the stream. + optional string description = 1; + + // The package name of the sending application. + optional string package_name = 2; + + // The payload id that will be send as a followup containing the + // ParcelFileDescriptor. + optional int64 payload_id = 3; + + // The human-readable name of the package that should be displayed as + // attribution if no other information is available (i.e. the package is not + // installed locally yet). + optional string attributed_app_name = 4; +} + // A frame used when sending messages over the wire. // NEXT_ID=3 message Frame { @@ -112,7 +184,7 @@ message Frame { optional V1Frame v1 = 2; } -// NEXT_ID=7 +// NEXT_ID=8 message V1Frame { enum FrameType { UNKNOWN_FRAME_TYPE = 0; @@ -120,34 +192,59 @@ message V1Frame { RESPONSE = 2; PAIRED_KEY_ENCRYPTION = 3; PAIRED_KEY_RESULT = 4; + // No longer used. CERTIFICATE_INFO = 5; CANCEL = 6; + // No longer used. + PROGRESS_UPDATE = 7; } optional FrameType type = 1; - // Exactly one of the following fields will be set. + // At most one of the following fields will be set. optional IntroductionFrame introduction = 2; optional ConnectionResponseFrame connection_response = 3; optional PairedKeyEncryptionFrame paired_key_encryption = 4; optional PairedKeyResultFrame paired_key_result = 5; - optional CertificateInfoFrame certificate_info = 6; + optional CertificateInfoFrame certificate_info = 6 [deprecated = true]; + optional ProgressUpdateFrame progress_update = 7 [deprecated = true]; } // An introduction packet sent by the sending side. Contains a list of files // they'd like to share. -// NEXT_ID=4 +// NEXT_ID=10 message IntroductionFrame { + enum SharingUseCase { + UNKNOWN = 0; + NEARBY_SHARE = 1; + REMOTE_COPY = 2; + } + repeated FileMetadata file_metadata = 1; repeated TextMetadata text_metadata = 2; // The required app package to open the content. May be null. optional string required_package = 3; repeated WifiCredentialsMetadata wifi_credentials_metadata = 4; + repeated AppMetadata app_metadata = 5; + optional bool start_transfer = 6; + repeated StreamMetadata stream_metadata = 7; + optional SharingUseCase use_case = 8; + repeated int64 preview_payload_ids = 9; +} + +// A progress update packet sent by the sending side. Contains transfer progress +// value. NEXT_ID=3 +message ProgressUpdateFrame { + optional float progress = 1; + + // True, if the receiver should start bandwidth upgrade and receiving the + // payloads. + optional bool start_transfer = 2; } // A response packet sent by the receiving side. Accepts or rejects the list of // files. -// NEXT_ID=2 +// NEXT_ID=4 message ConnectionResponseFrame { enum Status { UNKNOWN = 0; @@ -160,10 +257,68 @@ message ConnectionResponseFrame { // The receiving side's response. optional Status status = 1; + + // Key is attachment hash, value is the details of attachment. + map attachment_details = 2; + + // In the case of a stream attachments, the other side of the pipe. + // Both sender and receiver should validate matching counts. + repeated StreamMetadata stream_metadata = 3; +} + +// Attachment details that sent in ConnectionResponseFrame. +// NEXT_ID=3 +message AttachmentDetails { + // LINT.IfChange + enum Type { + UNKNOWN = 0; + // Represents FileAttachment. + FILE = 1; + // Represents TextAttachment. + TEXT = 2; + // Represents WifiCredentialsAttachment. + WIFI_CREDENTIALS = 3; + // Represents AppAttachment. + APP = 4; + // Represents StreamAttachment. + STREAM = 5; + } + // LINT.ThenChange(//depot/google3/java/com/google/android/gmscore/integ/client/nearby/src/com/google/android/gms/nearby/sharing/Attachment.java) + + // The attachment family type. + optional Type type = 1; + + // This field is only for FILE type. + optional FileAttachmentDetails file_attachment_details = 2; +} + +// File attachment details included in ConnectionResponseFrame. +// NEXT_ID=3 +message FileAttachmentDetails { + // Existing local file size on receiver side. + optional int64 receiver_existing_file_size = 1; + + // The key is attachment hash, a stable identifier for the attachment. + // Value is list of payload details transferred for the attachment. + map attachment_hash_payloads = 2; +} + +// NEXT_ID=2 +message PayloadsDetails { + // The list should be sorted by creation timestamp. + repeated PayloadDetails payload_details = 1; +} + +// Metadata of a payload file created by Nearby Connections. +// NEXT_ID=4 +message PayloadDetails { + optional int64 id = 1; + optional int64 creation_timestamp_millis = 2; + optional int64 size = 3; } // A paired key encryption packet sent between devices, contains signed data. -// NEXT_ID=3 +// NEXT_ID=5 message PairedKeyEncryptionFrame { // The encrypted data in byte array format. optional bytes signed_data = 1; @@ -173,10 +328,17 @@ message PairedKeyEncryptionFrame { // An optional encrypted data in byte array format. optional bytes optional_signed_data = 3; + + // An optional QR code handshake data in a byte array format. + // For incoming connection contains a signature of the UKEY2 + // token, created with the sender's private key. + // For outgoing connection contains an HKDF of the connection token and of the + // UKEY2 token + optional bytes qr_code_handshake_data = 4; } // A paired key verification result packet sent between devices. -// NEXT_ID=2 +// NEXT_ID=3 message PairedKeyResultFrame { enum Status { UNKNOWN = 0; @@ -187,6 +349,9 @@ message PairedKeyResultFrame { // The verification result. optional Status status = 1; + + // OS type. + optional location.nearby.proto.sharing.OSType os_type = 2; } // A package containing certificate info to be shared to remote device offline. @@ -229,8 +394,15 @@ message PublicCertificate { // NEXT_ID=3 message WifiCredentials { // Wi-Fi password. - optional string password = 1; + optional string password = 1 + /* type = ST_ACCOUNT_CREDENTIAL */; // True if the network is a hidden network that is not broadcasting its SSID. // Default is false. optional bool hidden_ssid = 2 [default = false]; -} \ No newline at end of file +} + +// NEXT_ID=2 +message StreamDetails { + // Serialized ParcelFileDescriptor for input stream (for the receiver). + optional bytes input_stream_parcel_file_descriptor_bytes = 1; +} diff --git a/QuickDrop.xcodeproj/project.pbxproj b/QuickDrop.xcodeproj/project.pbxproj index fda2b9b..fabd30c 100644 --- a/QuickDrop.xcodeproj/project.pbxproj +++ b/QuickDrop.xcodeproj/project.pbxproj @@ -92,6 +92,9 @@ EB96ED8D2D7F7EA3004B3FF1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 69DA9A1329E0BF5200A442DA /* Assets.xcassets */; }; EB96ED8F2D7F8105004B3FF1 /* Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB96ED8E2D7F8103004B3FF1 /* Localized.swift */; }; EB96ED902D7F8114004B3FF1 /* Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB96ED8E2D7F8103004B3FF1 /* Localized.swift */; }; + EB9925802E45D53E00357055 /* sharing_enums.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB99257F2E45D53E00357055 /* sharing_enums.pb.swift */; }; + EB9925812E45D53E00357055 /* sharing_enums.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB99257F2E45D53E00357055 /* sharing_enums.pb.swift */; }; + EB9925822E45D53E00357055 /* sharing_enums.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB99257F2E45D53E00357055 /* sharing_enums.pb.swift */; }; EBC05C5B2E33937200EECC18 /* ErrorAlertHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C5A2E33936C00EECC18 /* ErrorAlertHandler.swift */; }; EBC05C5C2E3394EF00EECC18 /* ErrorAlertHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C5A2E33936C00EECC18 /* ErrorAlertHandler.swift */; }; EBC05C712E33B57D00EECC18 /* Converter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EBC05C6D2E33B57D00EECC18 /* Converter.swift */; }; @@ -197,12 +200,6 @@ EB2B41E32DC63AC80070FC39 /* SleepManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SleepManager.swift; sourceTree = ""; }; EB2F9BFA2DA04B67004010A2 /* Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = ""; }; EB36435F2D73A29F009CDFF8 /* AudioManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioManager.swift; sourceTree = ""; }; - EB3B73F32DC8FF3A00369951 /* device_to_device_messages.proto */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.protobuf; path = device_to_device_messages.proto; sourceTree = ""; }; - EB3B73F42DC8FF3A00369951 /* offline_wire_formats.proto */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.protobuf; path = offline_wire_formats.proto; sourceTree = ""; }; - EB3B73F52DC8FF3A00369951 /* securegcm.proto */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.protobuf; path = securegcm.proto; sourceTree = ""; }; - EB3B73F62DC8FF3A00369951 /* securemessage.proto */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.protobuf; path = securemessage.proto; sourceTree = ""; }; - EB3B73F72DC8FF3A00369951 /* ukey.proto */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.protobuf; path = ukey.proto; sourceTree = ""; }; - EB3B73F82DC8FF3A00369951 /* wire_format.proto */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.protobuf; path = wire_format.proto; sourceTree = ""; }; EB5C36922E34EBBD0052EFD7 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; EB5C36952E34EBBD0052EFD7 /* mul */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; name = mul; path = mul.lproj/ShareViewController.xcstrings; sourceTree = ""; }; EB5C36E32E34EC0E0052EFD7 /* License.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = License.txt; sourceTree = ""; }; @@ -218,6 +215,7 @@ EB7F54132D78E9E400989564 /* LicenseWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LicenseWindow.swift; sourceTree = ""; }; EB96ED8A2D7F7CF4004B3FF1 /* SmallSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmallSheetView.swift; sourceTree = ""; }; EB96ED8E2D7F8103004B3FF1 /* Localized.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Localized.swift; sourceTree = ""; }; + EB99257F2E45D53E00357055 /* sharing_enums.pb.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = sharing_enums.pb.swift; sourceTree = ""; }; EBC05C5A2E33936C00EECC18 /* ErrorAlertHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorAlertHandler.swift; sourceTree = ""; }; EBC05C6D2E33B57D00EECC18 /* Converter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Converter.swift; sourceTree = ""; }; EBC05C6E2E33B57D00EECC18 /* Helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helper.swift; sourceTree = ""; }; @@ -336,6 +334,7 @@ 69DA9A2929E18CB500A442DA /* wire_format.pb.swift */, 69DA9A2A29E18CB500A442DA /* device_to_device_messages.pb.swift */, 69DA9A2B29E18CB500A442DA /* offline_wire_formats.pb.swift */, + EB99257F2E45D53E00357055 /* sharing_enums.pb.swift */, 69DA9A2C29E18CB500A442DA /* securegcm.pb.swift */, 69DA9A2D29E18CB500A442DA /* securemessage.pb.swift */, ); @@ -352,19 +351,6 @@ path = NearbyShare; sourceTree = ""; }; - EB3B73F92DC8FF3A00369951 /* ProtobufSource */ = { - isa = PBXGroup; - children = ( - EB3B73F32DC8FF3A00369951 /* device_to_device_messages.proto */, - EB3B73F42DC8FF3A00369951 /* offline_wire_formats.proto */, - EB3B73F52DC8FF3A00369951 /* securegcm.proto */, - EB3B73F62DC8FF3A00369951 /* securemessage.proto */, - EB3B73F72DC8FF3A00369951 /* ukey.proto */, - EB3B73F82DC8FF3A00369951 /* wire_format.proto */, - ); - path = ProtobufSource; - sourceTree = ""; - }; EB5C371C2E3669010052EFD7 /* Util */ = { isa = PBXGroup; children = ( @@ -411,7 +397,6 @@ EB5C37DD2E377B430052EFD7 /* Model */ = { isa = PBXGroup; children = ( - EB3B73F92DC8FF3A00369951 /* ProtobufSource */, 69DA9A2729E18CB500A442DA /* Protobuf */, EB5C36E82E3546100052EFD7 /* DataModel.swift */, ); @@ -688,6 +673,7 @@ EB5C37D42E377A7F0052EFD7 /* NearbyConnectionManager.swift in Sources */, EB5C37BD2E377A700052EFD7 /* Helpers.swift in Sources */, EB5C37BE2E377A700052EFD7 /* SaveFilesManager.swift in Sources */, + EB9925822E45D53E00357055 /* sharing_enums.pb.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -725,6 +711,7 @@ 69DA9A1229E0BF5100A442DA /* AppDelegate.swift in Sources */, EB6F62D72D91F4AF00534EC2 /* SettingsView.swift in Sources */, EB7F54122D78E96000989564 /* TutorialView.swift in Sources */, + EB9925812E45D53E00357055 /* sharing_enums.pb.swift in Sources */, EB96ED8F2D7F8105004B3FF1 /* Localized.swift in Sources */, EB7CD1452D6FB0C300CB0CB5 /* Logger.swift in Sources */, EBC05C822E33B58300EECC18 /* ApIsolationIssueView.swift in Sources */, @@ -750,6 +737,7 @@ EB5C37F92E377CE70052EFD7 /* securegcm.pb.swift in Sources */, EB5C37FA2E377CE70052EFD7 /* Helpers.swift in Sources */, EB5C37FB2E377CE70052EFD7 /* InboundNearbyConnection.swift in Sources */, + EB9925802E45D53E00357055 /* sharing_enums.pb.swift in Sources */, EB5C37FC2E377CE70052EFD7 /* OutboundNearbyConnection.swift in Sources */, EB5C37FD2E377CE70052EFD7 /* Logger.swift in Sources */, EB5C37FE2E377CE70052EFD7 /* offline_wire_formats.pb.swift in Sources */, diff --git a/QuickDrop/Localizable.xcstrings b/QuickDrop/Localizable.xcstrings index ed5210e..3f49cce 100644 --- a/QuickDrop/Localizable.xcstrings +++ b/QuickDrop/Localizable.xcstrings @@ -6621,111 +6621,7 @@ } }, "QuickDrop" : { - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "new", - "value" : "" - } - }, - "de" : { - "stringUnit" : { - "state" : "new", - "value" : "" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "" - } - }, - "es" : { - "stringUnit" : { - "state" : "new", - "value" : "" - } - }, - "fr" : { - "stringUnit" : { - "state" : "new", - "value" : "" - } - }, - "hu" : { - "stringUnit" : { - "state" : "new", - "value" : "" - } - }, - "it" : { - "stringUnit" : { - "state" : "new", - "value" : "" - } - }, - "ja" : { - "stringUnit" : { - "state" : "new", - "value" : "" - } - }, - "ko" : { - "stringUnit" : { - "state" : "new", - "value" : "" - } - }, - "nl" : { - "stringUnit" : { - "state" : "new", - "value" : "" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "new", - "value" : "" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "new", - "value" : "" - } - }, - "ro" : { - "stringUnit" : { - "state" : "new", - "value" : "" - } - }, - "ru" : { - "stringUnit" : { - "state" : "new", - "value" : "" - } - }, - "th" : { - "stringUnit" : { - "state" : "new", - "value" : "" - } - }, - "uk" : { - "stringUnit" : { - "state" : "new", - "value" : "" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "new", - "value" : "" - } - } - }, - "shouldTranslate" : false + }, "QuickDropAndroidAppAdvertisement" : { "extractionState" : "manual", @@ -7263,112 +7159,7 @@ } }, "Reset UD" : { - "extractionState" : "stale", - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "new", - "value" : "" - } - }, - "de" : { - "stringUnit" : { - "state" : "new", - "value" : "" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "" - } - }, - "es" : { - "stringUnit" : { - "state" : "new", - "value" : "" - } - }, - "fr" : { - "stringUnit" : { - "state" : "new", - "value" : "" - } - }, - "hu" : { - "stringUnit" : { - "state" : "new", - "value" : "" - } - }, - "it" : { - "stringUnit" : { - "state" : "new", - "value" : "" - } - }, - "ja" : { - "stringUnit" : { - "state" : "new", - "value" : "" - } - }, - "ko" : { - "stringUnit" : { - "state" : "new", - "value" : "" - } - }, - "nl" : { - "stringUnit" : { - "state" : "new", - "value" : "" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "new", - "value" : "" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "new", - "value" : "" - } - }, - "ro" : { - "stringUnit" : { - "state" : "new", - "value" : "" - } - }, - "ru" : { - "stringUnit" : { - "state" : "new", - "value" : "" - } - }, - "th" : { - "stringUnit" : { - "state" : "new", - "value" : "" - } - }, - "uk" : { - "stringUnit" : { - "state" : "new", - "value" : "" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "new", - "value" : "" - } - } - }, - "shouldTranslate" : false + }, "SaveFilesLocation" : { "extractionState" : "manual", diff --git a/Resources/License.txt b/Resources/License.txt index 97d7008..f6f5a6b 100644 --- a/Resources/License.txt +++ b/Resources/License.txt @@ -255,6 +255,186 @@ OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE PRODUCT OR THE USE OR OTHER DEALINGS IN OR WITH THE PRODUCT. +Nearby +https://github.com/google/nearby + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + ukey2 https://github.com/google/ukey2 Copyright 2023 Google @@ -320,6 +500,7 @@ The disclaimer of warranties and limitation of liability provided above shall be [...] + All sounds are copyrighted material of https://notificationsounds.com Terms of use: https://notificationsounds.com/terms-of-use From aff2cc18d48556c037c41b22bf1203bbbf709633 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20B=C3=B6ttger?= Date: Sat, 9 Aug 2025 19:56:55 +0200 Subject: [PATCH 14/20] Better error handling --- NearbyShare/Connection/NearbyConnection.swift | 53 ++++++++++++++----- 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/NearbyShare/Connection/NearbyConnection.swift b/NearbyShare/Connection/NearbyConnection.swift index 70f899d..fc140df 100644 --- a/NearbyShare/Connection/NearbyConnection.swift +++ b/NearbyShare/Connection/NearbyConnection.swift @@ -10,7 +10,6 @@ import CryptoKit import Foundation import Network import System - import BigInt import SwiftECC @@ -76,12 +75,10 @@ class NearbyConnection { // If connection reset by peer, it could still be a valid file transfer that has not been processed yet. => Wait if err == .posix(.ECONNRESET) { - NearbyConnection.dispatchQueue.asyncAfter(deadline: .now() + 0.5) { - - // If already closed, ignore the error, as file transfer has been processed - if !self.connectionClosed { - recordErrorAndDisconnect(err: err) - } + + log("[NearbyConnection] Network connection reset error detected.") + self.connectionClosedByPeer { + recordErrorAndDisconnect(err: err) } } else { @@ -102,12 +99,14 @@ class NearbyConnection { // If the error is a connection reset, it could be due to firewall issues. if err == .posix(.ENOTCONN) { + log("[NearbyConnection] Network not connected error detected.") ConnectionFailureTracker.shared.recordFailure { NearbyConnectionManager.shared.mainAppDelegate?.showFirewallAlert() } } if err == .posix(.ENETDOWN) { + log("[NearbyConnection] Network down error detected.") self.lastError = NearbyError.protocolError("Error.NetworkDown".localized()) } @@ -116,6 +115,17 @@ class NearbyConnection { } + func connectionClosedByPeer(onError: @escaping () -> Void) { + NearbyConnection.dispatchQueue.asyncAfter(deadline: .now() + 0.5) { + + // If already closed, ignore the error, as file transfer has been processed + if !self.connectionClosed { + onError() + } + } + } + + func connectionReady() {} @@ -156,17 +166,21 @@ class NearbyConnection { return } if isComplete { - log("[NearbyConnection] Connection closed by peer during receiveFrameAsync") - self.lastError = NearbyError.protocolError("Error.ClosedByPeer".localized()) - self.disconnect() + log("[NearbyConnection] Connection closed by peer during receiveFrameAsync()") + self.connectionClosedByPeer { + self.lastError = NearbyError.protocolError("Error.ClosedByPeer".localized()) + self.disconnect() + } return } if !(error == nil) { + log("[NearbyConnection] Error during receiveFrameAsync(): \(String(describing: error))") self.lastError = error self.protocolError() return } guard let content = content else { + log("[NearbyConnection] Received nil content during receiveFrameAsync(). IsComplete: \(isComplete)") assertionFailure() return } @@ -189,12 +203,25 @@ class NearbyConnection { return } if isComplete { - log("[NearbyConnection] Connection closed by peer during receiveFrameAsync") - self.lastError = NearbyError.protocolError("Error.ClosedByPeer".localized()) - self.disconnect() + log("[NearbyConnection] Connection closed by peer during receiveFrameAsync(length:)") + + if let content = content, !content.isEmpty { + log("[NearbyConnection] Connection closed by peer during receiveFrameAsync(length:), but trying to process the frame anyway. Frame: \(content.hex)") + self.processReceivedFrame(frameData: content) + } + else { + log("[NearbyConnection] Connection closed by peer during receiveFrameAsync(length:), but no content received anymore (\(String(describing: content))).") + } + + self.connectionClosedByPeer { + self.lastError = NearbyError.protocolError("Error.ClosedByPeer".localized()) + self.disconnect() + } + return } guard let content = content else { + log("[NearbyConnection] Received nil content during receiveFrameAsync(length:). IsComplete: \(isComplete)") self.protocolError() return } From bed222206da0699548b2b6ca96df0779656b52c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20B=C3=B6ttger?= Date: Sun, 10 Aug 2025 09:10:59 +0200 Subject: [PATCH 15/20] Increase version number --- QuickDrop.xcodeproj/project.pbxproj | 8 ++++---- QuickDrop/Localizable.xcstrings | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/QuickDrop.xcodeproj/project.pbxproj b/QuickDrop.xcodeproj/project.pbxproj index fabd30c..6039004 100644 --- a/QuickDrop.xcodeproj/project.pbxproj +++ b/QuickDrop.xcodeproj/project.pbxproj @@ -896,8 +896,8 @@ MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; - SHARED_BUILD = 60; - SHARED_VERSION = 1.12.1; + SHARED_BUILD = 61; + SHARED_VERSION = 1.12.2; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -958,8 +958,8 @@ MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = macosx; - SHARED_BUILD = 60; - SHARED_VERSION = 1.12.1; + SHARED_BUILD = 61; + SHARED_VERSION = 1.12.2; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OPTIMIZATION_LEVEL = "-O"; diff --git a/QuickDrop/Localizable.xcstrings b/QuickDrop/Localizable.xcstrings index 3f49cce..1e5ce82 100644 --- a/QuickDrop/Localizable.xcstrings +++ b/QuickDrop/Localizable.xcstrings @@ -6621,7 +6621,7 @@ } }, "QuickDrop" : { - + "shouldTranslate" : false }, "QuickDropAndroidAppAdvertisement" : { "extractionState" : "manual", From aacd95570b837a6d4489aefc9dd744332e67e682 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20B=C3=B6ttger?= Date: Mon, 11 Aug 2025 09:02:21 +0200 Subject: [PATCH 16/20] Improve logging --- NearbyShare/Connection/NearbyConnection.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NearbyShare/Connection/NearbyConnection.swift b/NearbyShare/Connection/NearbyConnection.swift index fc140df..a849ab3 100644 --- a/NearbyShare/Connection/NearbyConnection.swift +++ b/NearbyShare/Connection/NearbyConnection.swift @@ -206,7 +206,7 @@ class NearbyConnection { log("[NearbyConnection] Connection closed by peer during receiveFrameAsync(length:)") if let content = content, !content.isEmpty { - log("[NearbyConnection] Connection closed by peer during receiveFrameAsync(length:), but trying to process the frame anyway. Frame: \(content.hex)") + log("[NearbyConnection] Connection closed by peer during receiveFrameAsync(length:), but trying to process the frame anyway. Frame length: \(content.count)") self.processReceivedFrame(frameData: content) } else { From dc32e92d9ae67e318aa58914abdb9049d2412afe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20B=C3=B6ttger?= Date: Mon, 11 Aug 2025 09:06:19 +0200 Subject: [PATCH 17/20] Better logging --- .../Connection/InboundNearbyConnection.swift | 46 ++++++++-------- NearbyShare/Connection/NearbyConnection.swift | 52 +++++++++---------- .../Connection/NearbyConnectionManager.swift | 2 +- .../Connection/OutboundNearbyConnection.swift | 22 ++++---- 4 files changed, 61 insertions(+), 61 deletions(-) diff --git a/NearbyShare/Connection/InboundNearbyConnection.swift b/NearbyShare/Connection/InboundNearbyConnection.swift index 5b03344..0c858bb 100644 --- a/NearbyShare/Connection/InboundNearbyConnection.swift +++ b/NearbyShare/Connection/InboundNearbyConnection.swift @@ -57,7 +57,7 @@ class InboundNearbyConnection: NearbyConnection { override func processReceivedFrame(frameData: Data) { if currentState != .receivingFiles { - log("[InboundNearbyConnection] Received frame in state \(currentState)...") + log("[InboundNearbyConnection \(self.id)] Received frame in state \(currentState)...") } do { @@ -82,7 +82,7 @@ class InboundNearbyConnection: NearbyConnection { do { smsg = try Securemessage_SecureMessage(serializedBytes: frameData) } catch { - log("[InboundNearbyConnection] Error deserializing secure message (probably due to packet filter)") + log("[InboundNearbyConnection \(self.id)] Error deserializing secure message (probably due to packet filter)") lastError = NearbyError.packetFilterError protocolError() } @@ -93,7 +93,7 @@ class InboundNearbyConnection: NearbyConnection { } } catch { lastError = error - log("[InboundNearbyConnection] Error receiving frame: \(error) in state \(currentState).") + log("[InboundNearbyConnection \(self.id)] Error receiving frame: \(error) in state \(currentState).") protocolError() } } @@ -103,7 +103,7 @@ class InboundNearbyConnection: NearbyConnection { if frame.hasV1 && frame.v1.hasType, case .cancel = frame.v1.type { self.lastError = NearbyError.canceled(reason: .userCanceled) self.cancelled = true - log("[InboundNearbyConnection] Transfer canceled") + log("[InboundNearbyConnection \(self.id)] Transfer canceled") try sendDisconnectionAndDisconnect() return } @@ -115,7 +115,7 @@ class InboundNearbyConnection: NearbyConnection { case .receivedPairedKeyResult: try processIntroductionFrame(frame) default: - log("[InboundNearbyConnection] Unexpected connection state in processTransferSetupFrame: \(currentState)") + log("[InboundNearbyConnection \(self.id)] Unexpected connection state in processTransferSetupFrame: \(currentState)") log(frame.debugDescription) } } @@ -147,7 +147,7 @@ class InboundNearbyConnection: NearbyConnection { // only for logging self.bytesTransferred += Int64(frame.payloadChunk.body.count) } catch { - log("[InboundNearbyConnection] Error occurred during writing file: \(error.localizedDescription)") + log("[InboundNearbyConnection \(self.id)] Error occurred during writing file: \(error.localizedDescription)") throw NearbyError.protocolError(error.localizedDescription) } @@ -163,7 +163,7 @@ class InboundNearbyConnection: NearbyConnection { filesToBeReceived.removeValue(forKey: id) if filesToBeReceived.isEmpty { - log("[InboundNearbyConnection] All files received, sending disconnection frame and disconnecting.") + log("[InboundNearbyConnection \(self.id)] All files received, sending disconnection frame and disconnecting.") try sendDisconnectionAndDisconnect() } } @@ -199,7 +199,7 @@ class InboundNearbyConnection: NearbyConnection { } } - log("[InboundNearbyConnection] Received text payload. Disconnecting...") + log("[InboundNearbyConnection \(self.id)] Received text payload. Disconnecting...") try sendDisconnectionAndDisconnect() return true } @@ -215,7 +215,7 @@ class InboundNearbyConnection: NearbyConnection { filesToBeReceived.removeValue(forKey: id) SaveFilesManager.shared.registerFileFinishedDownloading(fileInfo.destinationURL) - log("[InboundNearbyConnection] Received file payload. Disconnecting...") + log("[InboundNearbyConnection \(self.id)] Received file payload. Disconnecting...") try sendDisconnectionAndDisconnect() return true } @@ -250,7 +250,7 @@ class InboundNearbyConnection: NearbyConnection { guard msg.hasMessageType, msg.hasMessageData else { throw NearbyError.requiredFieldMissing("clientInit ukey2message.type|data") } guard case .clientInit = msg.messageType else { sendUkey2Alert(type: .badMessageType) - log("[InboundNearbyConnection] Unsupported message type: \(msg.messageType)") + log("[InboundNearbyConnection \(self.id)] Unsupported message type: \(msg.messageType)") throw NearbyError.ukey2 } let clientInit: Securegcm_Ukey2ClientInit @@ -258,17 +258,17 @@ class InboundNearbyConnection: NearbyConnection { clientInit = try Securegcm_Ukey2ClientInit(serializedBytes: msg.messageData) } catch { sendUkey2Alert(type: .badMessageData) - log("[InboundNearbyConnection] Failed to parse clientInit: \(error)") + log("[InboundNearbyConnection \(self.id)] Failed to parse clientInit: \(error)") throw NearbyError.ukey2 } guard clientInit.version == 1 else { sendUkey2Alert(type: .badVersion) - log("[InboundNearbyConnection] Unsupported clientInit version: \(clientInit.version)") + log("[InboundNearbyConnection \(self.id)] Unsupported clientInit version: \(clientInit.version)") throw NearbyError.ukey2 } guard clientInit.random.count == 32 else { sendUkey2Alert(type: .badRandom) - log("[InboundNearbyConnection] Unsupported clientInit random: \(clientInit.random.count)") + log("[InboundNearbyConnection \(self.id)] Unsupported clientInit random: \(clientInit.random.count)") throw NearbyError.ukey2 } var found = false @@ -281,12 +281,12 @@ class InboundNearbyConnection: NearbyConnection { } guard found else { sendUkey2Alert(type: .badHandshakeCipher) - log("[InboundNearbyConnection] Unsupported clientInit handshakeCipher: \(clientInit.cipherCommitments)") + log("[InboundNearbyConnection \(self.id)] Unsupported clientInit handshakeCipher: \(clientInit.cipherCommitments)") throw NearbyError.ukey2 } guard clientInit.nextProtocol == "AES_256_CBC-HMAC_SHA256" else { sendUkey2Alert(type: .badNextProtocol) - log("[InboundNearbyConnection] Unsupported clientInit nextProtocol: \(clientInit.nextProtocol)") + log("[InboundNearbyConnection \(self.id)] Unsupported clientInit nextProtocol: \(clientInit.nextProtocol)") throw NearbyError.ukey2 } @@ -320,14 +320,14 @@ class InboundNearbyConnection: NearbyConnection { private func processUkey2ClientFinish(_ msg: Securegcm_Ukey2Message, raw: Data) throws { guard msg.hasMessageType, msg.hasMessageData else { throw NearbyError.requiredFieldMissing("clientFinish ukey2message.type|data") } guard case .clientFinish = msg.messageType else { - log("[InboundNearbyConnection] Unexpected message type \(msg.messageType)") + log("[InboundNearbyConnection \(self.id)] Unexpected message type \(msg.messageType)") throw NearbyError.ukey2 } var sha = SHA512() sha.update(data: raw) guard cipherCommitment == Data(sha.finalize()) else { - log("[InboundNearbyConnection] Invalid cipherCommitment in clientFinish") + log("[InboundNearbyConnection \(self.id)] Invalid cipherCommitment in clientFinish") throw NearbyError.ukey2 } @@ -368,7 +368,7 @@ class InboundNearbyConnection: NearbyConnection { try sendTransferSetupFrame(pairedEncryption) currentState = .sentConnectionResponse } else { - log("[InboundNearbyConnection] Unhandled offline frame plaintext: \(frame)") + log("[InboundNearbyConnection \(self.id)] Unhandled offline frame plaintext: \(frame)") } } @@ -459,7 +459,7 @@ class InboundNearbyConnection: NearbyConnection { func rejectDueToUnsupportedFileType(_ frame: Sharing_Nearby_Frame) { - log("[InboundNearbyConnection] Rejecting transfer due to unsupported file type. Frame is \(frame.debugDescription)") + log("[InboundNearbyConnection \(self.id)] Rejecting transfer due to unsupported file type. Frame is \(frame.debugDescription)") NearbyConnectionManager.shared.mainAppDelegate?.showUnsupportedFileAlert(for: remoteDeviceInfo) rejectTransfer(with: .unsupportedAttachmentType) @@ -479,7 +479,7 @@ class InboundNearbyConnection: NearbyConnection { private func acceptTransfer(storeInTemp: Bool) { if currentState == .disconnected { - log("[InboundNearbyConnection] Detected timeout, not accepting transfer") + log("[InboundNearbyConnection \(self.id)] Detected timeout, not accepting transfer") return } @@ -506,7 +506,7 @@ class InboundNearbyConnection: NearbyConnection { filesToBeReceived[id]!.progress = progress filesToBeReceived[id]!.created = true - log("[InboundNearbyConnection] Accepted file with size \(file.meta.size)") + log("[InboundNearbyConnection \(self.id)] Accepted file with size \(file.meta.size)") } var frame = Sharing_Nearby_Frame() @@ -527,7 +527,7 @@ class InboundNearbyConnection: NearbyConnection { self.wasRejected = true - log("[InboundNearbyConnection] Rejecting transfer because of \( reason)") + log("[InboundNearbyConnection \(self.id)] Rejecting transfer because of \( reason)") var frame = Sharing_Nearby_Frame() frame.version = .v1 @@ -537,7 +537,7 @@ class InboundNearbyConnection: NearbyConnection { try sendTransferSetupFrame(frame) try sendDisconnectionAndDisconnect() } catch { - log("[InboundNearbyConnection] Error \(error)") + log("[InboundNearbyConnection \(self.id)] Error \(error)") protocolError() } } diff --git a/NearbyShare/Connection/NearbyConnection.swift b/NearbyShare/Connection/NearbyConnection.swift index a849ab3..29eb49d 100644 --- a/NearbyShare/Connection/NearbyConnection.swift +++ b/NearbyShare/Connection/NearbyConnection.swift @@ -28,7 +28,7 @@ class NearbyConnection { var cancelled: Bool = false var isTransferring: Bool = false { didSet { - log("[NearbyConnection] Now transferring. Setting up data transfer inactivity timer.") + log("[NearbyConnection \(self.id)] Now transferring. Setting up data transfer inactivity timer.") startDataTransferTimer(previousBytesTransferred: bytesTransferred) } } @@ -76,7 +76,7 @@ class NearbyConnection { // If connection reset by peer, it could still be a valid file transfer that has not been processed yet. => Wait if err == .posix(.ECONNRESET) { - log("[NearbyConnection] Network connection reset error detected.") + log("[NearbyConnection \(self.id)] Network connection reset error detected.") self.connectionClosedByPeer { recordErrorAndDisconnect(err: err) } @@ -87,7 +87,7 @@ class NearbyConnection { } } else { - log("[NearbyConnection] Connection already closed, ignoring state update: \(state)") + log("[NearbyConnection \(self.id)] Connection already closed, ignoring state update: \(state)") } } @@ -95,18 +95,18 @@ class NearbyConnection { func recordErrorAndDisconnect(err: NWError) { self.lastError = err - log("[NearbyConnection] Connection Error: \(err)") + log("[NearbyConnection \(self.id)] Connection Error: \(err)") // If the error is a connection reset, it could be due to firewall issues. if err == .posix(.ENOTCONN) { - log("[NearbyConnection] Network not connected error detected.") + log("[NearbyConnection \(self.id)] Network not connected error detected.") ConnectionFailureTracker.shared.recordFailure { NearbyConnectionManager.shared.mainAppDelegate?.showFirewallAlert() } } if err == .posix(.ENETDOWN) { - log("[NearbyConnection] Network down error detected.") + log("[NearbyConnection \(self.id)] Network down error detected.") self.lastError = NearbyError.protocolError("Error.NetworkDown".localized()) } @@ -130,7 +130,7 @@ class NearbyConnection { func protocolError() { - log("[NearbyConnection] Protocol error: \(String(describing: lastError))") + log("[NearbyConnection \(self.id)] Protocol error: \(String(describing: lastError))") disconnect() } @@ -166,7 +166,7 @@ class NearbyConnection { return } if isComplete { - log("[NearbyConnection] Connection closed by peer during receiveFrameAsync()") + log("[NearbyConnection \(self.id)] Connection closed by peer during receiveFrameAsync()") self.connectionClosedByPeer { self.lastError = NearbyError.protocolError("Error.ClosedByPeer".localized()) self.disconnect() @@ -174,13 +174,13 @@ class NearbyConnection { return } if !(error == nil) { - log("[NearbyConnection] Error during receiveFrameAsync(): \(String(describing: error))") + log("[NearbyConnection \(self.id)] Error during receiveFrameAsync(): \(String(describing: error))") self.lastError = error self.protocolError() return } guard let content = content else { - log("[NearbyConnection] Received nil content during receiveFrameAsync(). IsComplete: \(isComplete)") + log("[NearbyConnection \(self.id)] Received nil content during receiveFrameAsync(). IsComplete: \(isComplete)") assertionFailure() return } @@ -198,19 +198,19 @@ class NearbyConnection { private func receiveFrameAsync(length: UInt32) { - connection.receive(minimumIncompleteLength: Int(length), maximumLength: Int(length)) { content, _, isComplete, _ in + connection.receive(minimumIncompleteLength: Int(length), maximumLength: Int(length)) { [self] content, _, isComplete, _ in if self.connectionClosed { return } if isComplete { - log("[NearbyConnection] Connection closed by peer during receiveFrameAsync(length:)") + log("[NearbyConnection \(self.id)] Connection closed by peer during receiveFrameAsync(length:)") if let content = content, !content.isEmpty { - log("[NearbyConnection] Connection closed by peer during receiveFrameAsync(length:), but trying to process the frame anyway. Frame length: \(content.count)") + log("[NearbyConnection \(self.id)] Connection closed by peer during receiveFrameAsync(length:), but trying to process the frame anyway. Frame length: \(content.count)") self.processReceivedFrame(frameData: content) } else { - log("[NearbyConnection] Connection closed by peer during receiveFrameAsync(length:), but no content received anymore (\(String(describing: content))).") + log("[NearbyConnection \(self.id)] Connection closed by peer during receiveFrameAsync(length:), but no content received anymore (\(String(describing: content))).") } self.connectionClosedByPeer { @@ -221,7 +221,7 @@ class NearbyConnection { return } guard let content = content else { - log("[NearbyConnection] Received nil content during receiveFrameAsync(length:). IsComplete: \(isComplete)") + log("[NearbyConnection \(self.id)] Received nil content during receiveFrameAsync(length:). IsComplete: \(isComplete)") self.protocolError() return } @@ -297,7 +297,7 @@ class NearbyConnection { func sendTransferSetupFrame(_ frame: Sharing_Nearby_Frame) throws { - log("[NearbyConnection] Sending transfer setup frame.") + log("[NearbyConnection \(self.id)] Sending transfer setup frame.") try sendBytesPayload(data: frame.serializedData(), id: Int64.random(in: Int64.min ... Int64.max)) } @@ -374,11 +374,11 @@ class NearbyConnection { guard payloadTransfer.hasPayloadChunk, chunk.hasOffset, chunk.hasFlags else { if payloadTransfer.controlMessage.event == .payloadCanceled { - log("[NearbyConnection] Cancel control frame received.") + log("[NearbyConnection \(self.id)] Cancel control frame received.") throw NearbyError.canceled(reason: .userCanceled) } - log("[NearbyConnection] Payload transfer chunk is missing offset or flags. Frame is \(payloadTransfer.debugDescription)") + log("[NearbyConnection \(self.id)] Payload transfer chunk is missing offset or flags. Frame is \(payloadTransfer.debugDescription)") throw NearbyError.requiredFieldMissing("payloadChunk.offset|flags") } @@ -423,11 +423,11 @@ class NearbyConnection { } else if offlineFrame.hasV1, offlineFrame.v1.hasType, case .keepAlive = offlineFrame.v1.type { - log("[NearbyConnection] Sent keep-alive, \(self.bytesTransferred) bytes sent") + log("[NearbyConnection \(self.id)] Sent keep-alive, \(self.bytesTransferred) bytes sent") sendKeepAlive(ack: true) } else { - log("[NearbyConnection] Unhandled offline frame encrypted: \(offlineFrame)") + log("[NearbyConnection \(self.id)] Unhandled offline frame encrypted: \(offlineFrame)") } } @@ -531,7 +531,7 @@ class NearbyConnection { func disconnect() { - log("[NearbyConnection] Disconnecting from connection.") + log("[NearbyConnection \(self.id)] Disconnecting from connection.") connection.stateUpdateHandler = nil inactivityTimer?.cancel() @@ -553,7 +553,7 @@ class NearbyConnection { try sendFrameAsync(offlineFrame.serializedData()) } - log("[NearbyConnection] Sent disconnection frame during sendDisconnectionAndDisconnect") + log("[NearbyConnection \(self.id)] Sent disconnection frame during sendDisconnectionAndDisconnect") disconnect() } @@ -566,7 +566,7 @@ class NearbyConnection { msg.messageData = try! alert.serializedData() sendFrameAsync(try! msg.serializedData()) - log("[NearbyConnection] Sent UKEY2 alert: \(type)") + log("[NearbyConnection \(self.id)] Sent UKEY2 alert: \(type)") disconnect() } @@ -584,7 +584,7 @@ class NearbyConnection { try sendFrameAsync(offlineFrame.serializedData()) } } catch { - log("[NearbyConnection] Error sending KEEP_ALIVE: \(error)") + log("[NearbyConnection \(self.id)] Error sending KEEP_ALIVE: \(error)") } } @@ -601,7 +601,7 @@ class NearbyConnection { guard let self = self else { return } if !self.connectionClosed { - log("[NearbyConnection] Connection timeout: No message received in \(self.timeoutInterval) seconds") + log("[NearbyConnection \(self.id)] Connection timeout: No message received in \(self.timeoutInterval) seconds") self.lastError = NearbyError.canceled(reason: .timedOut) self.disconnect() } @@ -621,7 +621,7 @@ class NearbyConnection { else { // connection stale, need to abort if !self.connectionClosed { - log("[NearbyConnection] Connection timeout: No more data received in \(self.timeoutInterval) seconds") + log("[NearbyConnection \(self.id)] Connection timeout: No more data received in \(self.timeoutInterval) seconds") self.lastError = NearbyError.canceled(reason: .timedOut) self.disconnect() } diff --git a/NearbyShare/Connection/NearbyConnectionManager.swift b/NearbyShare/Connection/NearbyConnectionManager.swift index 2ebfe39..0011235 100644 --- a/NearbyShare/Connection/NearbyConnectionManager.swift +++ b/NearbyShare/Connection/NearbyConnectionManager.swift @@ -135,7 +135,7 @@ public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearb public func submitUserConsent(transferID: String, accept: Bool, storeInTemp: Bool = false) { guard let conn = incomingConnections[transferID] else { return } - log("[NearbyConnectionManager] Submitting user consent for transfer, accepted: \(accept), store in temp: \(storeInTemp)") + log("[NearbyConnectionManager] Submitting user consent for transfer \(transferID), accepted: \(accept), store in temp: \(storeInTemp)") conn.submitUserConsent(accepted: accept, storeInTemp: storeInTemp) } diff --git a/NearbyShare/Connection/OutboundNearbyConnection.swift b/NearbyShare/Connection/OutboundNearbyConnection.swift index d6c1dd8..da57535 100644 --- a/NearbyShare/Connection/OutboundNearbyConnection.swift +++ b/NearbyShare/Connection/OutboundNearbyConnection.swift @@ -101,7 +101,7 @@ class OutboundNearbyConnection: NearbyConnection { override func processReceivedFrame(frameData: Data) { if currentState != .sendingFiles { - log("[OutboundNearbyConnection] Received frame in state \(currentState)...") + log("[OutboundNearbyConnection \(self.id)] Received frame in state \(currentState)...") } do { @@ -118,7 +118,7 @@ class OutboundNearbyConnection: NearbyConnection { } } catch { - log("[OutboundNearbyConnection] Error occured while processing frame with data \(frameData.hex): \(error)") + log("[OutboundNearbyConnection \(self.id)] Error occured while processing frame with data \(frameData.hex): \(error)") if case NearbyError.ukey2 = error { // do nothing @@ -137,7 +137,7 @@ class OutboundNearbyConnection: NearbyConnection { if frame.hasV1 && frame.v1.hasType, case .cancel = frame.v1.type { self.cancelled = true self.lastError = NearbyError.canceled(reason: .userCanceled) - log("[OutboundNearbyConnection] Transfer canceled") + log("[OutboundNearbyConnection \(self.id)] Transfer canceled") try sendDisconnectionAndDisconnect() delegate?.outboundConnection(connection: self, failedWithError: self.lastError!) return @@ -153,7 +153,7 @@ class OutboundNearbyConnection: NearbyConnection { case .sendingFiles: break default: - assertionFailure("[OutboundNearbyConnection] Unexpected state \(currentState)") + assertionFailure("[OutboundNearbyConnection \(self.id)] Unexpected state \(currentState)") } } @@ -224,23 +224,23 @@ class OutboundNearbyConnection: NearbyConnection { ukeyServerInitMsgData = raw guard frame.messageType == .serverInit else { sendUkey2Alert(type: .badMessageType) - log("[OutboundNearbyConnection] Invalid message type: \(frame.messageType)") + log("[OutboundNearbyConnection \(self.id)] Invalid message type: \(frame.messageType)") throw NearbyError.ukey2 } let serverInit = try Securegcm_Ukey2ServerInit(serializedBytes: frame.messageData) guard serverInit.version == 1 else { sendUkey2Alert(type: .badVersion) - log("[OutboundNearbyConnection] Invalid version: \(serverInit.version)") + log("[OutboundNearbyConnection \(self.id)] Invalid version: \(serverInit.version)") throw NearbyError.ukey2 } guard serverInit.random.count == 32 else { sendUkey2Alert(type: .badRandom) - log("[OutboundNearbyConnection] Invalid random: \(serverInit.random.count)") + log("[OutboundNearbyConnection \(self.id)] Invalid random: \(serverInit.random.count)") throw NearbyError.ukey2 } guard serverInit.handshakeCipher == .p256Sha512 else { sendUkey2Alert(type: .badHandshakeCipher) - log("[OutboundNearbyConnection] Invalid handshake cipher: \(serverInit.handshakeCipher)") + log("[OutboundNearbyConnection \(self.id)] Invalid handshake cipher: \(serverInit.handshakeCipher)") throw NearbyError.ukey2 } @@ -349,7 +349,7 @@ class OutboundNearbyConnection: NearbyConnection { introduction.v1.introduction.fileMetadata.append(meta) totalBytesToSend += meta.size - log("[OutboundNearbyConnection] Sending file with \(meta.size) bytes") + log("[OutboundNearbyConnection \(self.id)] Sending file with \(meta.size) bytes") } } try sendTransferSetupFrame(introduction) @@ -416,7 +416,7 @@ class OutboundNearbyConnection: NearbyConnection { try currentTransfer?.handle?.close() } if queue.isEmpty { - log("[OutboundNearbyConnection] Disconnecting because all files have been transferred") + log("[OutboundNearbyConnection \(self.id)] Disconnecting because all files have been transferred") try sendDisconnectionAndDisconnect() delegate?.outboundConnectionTransferFinished(connection: self) return @@ -479,7 +479,7 @@ class OutboundNearbyConnection: NearbyConnection { wrapper.v1.type = .payloadTransfer wrapper.v1.payloadTransfer = transfer try encryptAndSendOfflineFrame(wrapper) - log("[OutboundNearbyConnection] Sent EOF, current transfer: \(String(describing: currentTransfer))") + log("[OutboundNearbyConnection \(self.id)] Sent EOF, current transfer: \(String(describing: currentTransfer))") } } From e6384792037d172e3fbcf3b671ed3203bb1c386e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20B=C3=B6ttger?= Date: Mon, 11 Aug 2025 15:40:45 +0200 Subject: [PATCH 18/20] Fix: Firewall alert would open two alerts --- NearbyShare/Connection/NearbyConnection.swift | 6 +-- NearbyShare/Model/DataModel.swift | 2 +- .../Util/ConnectionFailureTracker.swift | 41 ------------------- QuickDrop.xcodeproj/project.pbxproj | 8 ---- QuickDrop/Util/ErrorAlertHandler.swift | 4 ++ 5 files changed, 7 insertions(+), 54 deletions(-) delete mode 100644 NearbyShare/Util/ConnectionFailureTracker.swift diff --git a/NearbyShare/Connection/NearbyConnection.swift b/NearbyShare/Connection/NearbyConnection.swift index 29eb49d..e71bd0b 100644 --- a/NearbyShare/Connection/NearbyConnection.swift +++ b/NearbyShare/Connection/NearbyConnection.swift @@ -99,10 +99,8 @@ class NearbyConnection { // If the error is a connection reset, it could be due to firewall issues. if err == .posix(.ENOTCONN) { - log("[NearbyConnection \(self.id)] Network not connected error detected.") - ConnectionFailureTracker.shared.recordFailure { - NearbyConnectionManager.shared.mainAppDelegate?.showFirewallAlert() - } + log("[NearbyConnection \(self.id)] Network not connected error detected (firewall likely).") + self.lastError = NearbyError.firewallError } if err == .posix(.ENETDOWN) { diff --git a/NearbyShare/Model/DataModel.swift b/NearbyShare/Model/DataModel.swift index 4586d21..0f2c977 100644 --- a/NearbyShare/Model/DataModel.swift +++ b/NearbyShare/Model/DataModel.swift @@ -27,7 +27,6 @@ protocol OutboundNearbyConnectionDelegate { public protocol MainAppDelegate { func obtainUserConsent(for transfer: TransferMetadata, from device: RemoteDeviceInfo) func incomingTransfer(id: String, didFinishWith error: Error?) - func showFirewallAlert() func showCopiedToClipboardAlert() func showUnsupportedFileAlert(for: RemoteDeviceInfo?) } @@ -164,6 +163,7 @@ public enum NearbyError: Error { case requiredFieldMissing(_ message: String) case ukey2 case packetFilterError + case firewallError case inputOutput case canceled(reason: CancellationReason) diff --git a/NearbyShare/Util/ConnectionFailureTracker.swift b/NearbyShare/Util/ConnectionFailureTracker.swift deleted file mode 100644 index d728a21..0000000 --- a/NearbyShare/Util/ConnectionFailureTracker.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// ConnectionFailureTracker.swift -// QuickDrop -// -// Created by Leon Böttger on 15.05.25. -// - -import Foundation - -class ConnectionFailureTracker { - static let shared = ConnectionFailureTracker() - - private var failureTimestamps: [Date] = [] - private var lastAlertShownAt: Date? - - private let queue = DispatchQueue(label: "ConnectionFailureTrackerQueue") - - private init() {} - - func recordFailure(onTrigger: @escaping () -> Void) { - queue.async { - let now = Date() - - // Remove old timestamps older than 10 seconds - self.failureTimestamps = self.failureTimestamps.filter { now.timeIntervalSince($0) < 10 } - self.failureTimestamps.append(now) - - // Check if alert can be shown - let canShowAlert = self.failureTimestamps.count >= 2 && - (self.lastAlertShownAt == nil || now.timeIntervalSince(self.lastAlertShownAt!) >= 60) - - if canShowAlert { - self.lastAlertShownAt = now - self.failureTimestamps.removeAll() - DispatchQueue.main.async { - onTrigger() - } - } - } - } -} diff --git a/QuickDrop.xcodeproj/project.pbxproj b/QuickDrop.xcodeproj/project.pbxproj index 6039004..1fad6cc 100644 --- a/QuickDrop.xcodeproj/project.pbxproj +++ b/QuickDrop.xcodeproj/project.pbxproj @@ -21,11 +21,9 @@ EB5C36942E34EBBD0052EFD7 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = EB5C36922E34EBBD0052EFD7 /* Localizable.xcstrings */; }; EB5C36E42E34EC0E0052EFD7 /* License.txt in Resources */ = {isa = PBXBuildFile; fileRef = EB5C36E32E34EC0E0052EFD7 /* License.txt */; }; EB5C36E52E34EC0E0052EFD7 /* License.txt in Resources */ = {isa = PBXBuildFile; fileRef = EB5C36E32E34EC0E0052EFD7 /* License.txt */; }; - EB5C37B72E377A6F0052EFD7 /* ConnectionFailureTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB6E743D2DD5C76F00331466 /* ConnectionFailureTracker.swift */; }; EB5C37B82E377A6F0052EFD7 /* SleepManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB2B41E32DC63AC80070FC39 /* SleepManager.swift */; }; EB5C37B92E377A6F0052EFD7 /* SaveFilesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C36E62E3544FB0052EFD7 /* SaveFilesManager.swift */; }; EB5C37BA2E377A6F0052EFD7 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB2F9BFA2DA04B67004010A2 /* Helpers.swift */; }; - EB5C37BB2E377A700052EFD7 /* ConnectionFailureTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB6E743D2DD5C76F00331466 /* ConnectionFailureTracker.swift */; }; EB5C37BC2E377A700052EFD7 /* SleepManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB2B41E32DC63AC80070FC39 /* SleepManager.swift */; }; EB5C37BD2E377A700052EFD7 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB2F9BFA2DA04B67004010A2 /* Helpers.swift */; }; EB5C37BE2E377A700052EFD7 /* SaveFilesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB5C36E62E3544FB0052EFD7 /* SaveFilesManager.swift */; }; @@ -63,7 +61,6 @@ EB5C37F42E377CE70052EFD7 /* NearbyConnectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A1E29E0C0B300A442DA /* NearbyConnectionManager.swift */; }; EB5C37F52E377CE70052EFD7 /* securemessage.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2D29E18CB500A442DA /* securemessage.pb.swift */; }; EB5C37F62E377CE70052EFD7 /* device_to_device_messages.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2A29E18CB500A442DA /* device_to_device_messages.pb.swift */; }; - EB5C37F72E377CE70052EFD7 /* ConnectionFailureTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB6E743D2DD5C76F00331466 /* ConnectionFailureTracker.swift */; }; EB5C37F82E377CE70052EFD7 /* SleepManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB2B41E32DC63AC80070FC39 /* SleepManager.swift */; }; EB5C37F92E377CE70052EFD7 /* securegcm.pb.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A2C29E18CB500A442DA /* securegcm.pb.swift */; }; EB5C37FA2E377CE70052EFD7 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB2F9BFA2DA04B67004010A2 /* Helpers.swift */; }; @@ -208,7 +205,6 @@ EB5C37E22E377C2E0052EFD7 /* QuickDropiOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = QuickDropiOS.app; sourceTree = BUILT_PRODUCTS_DIR; }; EB5C38382E38C1F60052EFD7 /* justMaybe.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = justMaybe.mp3; sourceTree = ""; }; EB5C383E2E38C49D0052EFD7 /* LaunchAtLogin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchAtLogin.swift; sourceTree = ""; }; - EB6E743D2DD5C76F00331466 /* ConnectionFailureTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectionFailureTracker.swift; sourceTree = ""; }; EB6F62D62D91F4AC00534EC2 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; EB7F54112D78E95C00989564 /* TutorialView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TutorialView.swift; sourceTree = ""; }; @@ -359,7 +355,6 @@ EB7CD1442D6FB0C100CB0CB5 /* Logger.swift */, EB5C36E62E3544FB0052EFD7 /* SaveFilesManager.swift */, EB2F9BFA2DA04B67004010A2 /* Helpers.swift */, - EB6E743D2DD5C76F00331466 /* ConnectionFailureTracker.swift */, ); path = Util; sourceTree = ""; @@ -663,7 +658,6 @@ EBC05C7B2E33B58100EECC18 /* ActiveNetworkInterface.swift in Sources */, EBC05C7C2E33B58100EECC18 /* ApIsolationTester.swift in Sources */, 691F53BB2ABB70840089FD92 /* DeviceListCell.swift in Sources */, - EB5C37BB2E377A700052EFD7 /* ConnectionFailureTracker.swift in Sources */, EB5C383F2E38C49D0052EFD7 /* LaunchAtLogin.swift in Sources */, EB5C37BC2E377A700052EFD7 /* SleepManager.swift in Sources */, EB5C37D02E377A7F0052EFD7 /* OutboundNearbyConnection.swift in Sources */, @@ -683,7 +677,6 @@ files = ( EBD2A2172D2860C100BCB8E2 /* WelcomeScreen.swift in Sources */, EB3643602D73A2A2009CDFF8 /* AudioManager.swift in Sources */, - EB5C37B72E377A6F0052EFD7 /* ConnectionFailureTracker.swift in Sources */, EB5C38412E38C49D0052EFD7 /* LaunchAtLogin.swift in Sources */, EB5C37B82E377A6F0052EFD7 /* SleepManager.swift in Sources */, EB5C37B92E377A6F0052EFD7 /* SaveFilesManager.swift in Sources */, @@ -731,7 +724,6 @@ EB5C37F42E377CE70052EFD7 /* NearbyConnectionManager.swift in Sources */, EB5C37F52E377CE70052EFD7 /* securemessage.pb.swift in Sources */, EB5C37F62E377CE70052EFD7 /* device_to_device_messages.pb.swift in Sources */, - EB5C37F72E377CE70052EFD7 /* ConnectionFailureTracker.swift in Sources */, EB5C37F82E377CE70052EFD7 /* SleepManager.swift in Sources */, EB5C38012E377DE60052EFD7 /* Localized.swift in Sources */, EB5C37F92E377CE70052EFD7 /* securegcm.pb.swift in Sources */, diff --git a/QuickDrop/Util/ErrorAlertHandler.swift b/QuickDrop/Util/ErrorAlertHandler.swift index 83723e0..0bd0107 100644 --- a/QuickDrop/Util/ErrorAlertHandler.swift +++ b/QuickDrop/Util/ErrorAlertHandler.swift @@ -38,6 +38,9 @@ class ErrorAlertHandler { case .packetFilterError: openAlert(type: .NetworkFilter) return + case .firewallError: + openAlert(type: .Firewall) + return case .requiredFieldMissing(errorMessage: let errorMessage): description = errorMessage + fixInstructions case .ukey2: @@ -85,6 +88,7 @@ class ErrorAlertHandler { func openAlert(type: AlertType) { log("Opening Alert for \(type)") + AudioManager.playErrorSound() DispatchQueue.main.async { [self] in // Create an NSWindow to host the SwiftUI view From 89a0569ffe776605675c8880285ed3a33f49a816 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20B=C3=B6ttger?= Date: Mon, 11 Aug 2025 16:01:32 +0200 Subject: [PATCH 19/20] Remove unused code --- .../Connection/DataModel.swift | 188 ------ .../Connection/InboundNearbyConnection.swift | 534 ---------------- .../Connection/NearbyConnection.swift | 592 ------------------ .../Connection/NearbyConnectionManager.swift | 323 ---------- .../Connection/OutboundNearbyConnection.swift | 497 --------------- .../device_to_device_messages.proto | 81 --- .../ProtobufSource/offline_wire_formats.proto | 403 ------------ .../ProtobufSource/securegcm.proto | 308 --------- .../ProtobufSource/securemessage.proto | 126 ---- .../ProtobufSource/ukey.proto | 105 ---- .../ProtobufSource/wire_format.proto | 236 ------- .../Util/ConnectionFailureTracker.swift | 41 -- NearbyShareFramework/Util/Helpers.swift | 100 --- NearbyShareFramework/Util/Logger.swift | 133 ---- .../Util/SaveFilesManager.swift | 220 ------- NearbyShareFramework/Util/SleepManager.swift | 59 -- PROTOCOL.md | 210 ------- 17 files changed, 4156 deletions(-) delete mode 100644 NearbyShareFramework/Connection/DataModel.swift delete mode 100644 NearbyShareFramework/Connection/InboundNearbyConnection.swift delete mode 100644 NearbyShareFramework/Connection/NearbyConnection.swift delete mode 100644 NearbyShareFramework/Connection/NearbyConnectionManager.swift delete mode 100644 NearbyShareFramework/Connection/OutboundNearbyConnection.swift delete mode 100644 NearbyShareFramework/ProtobufSource/device_to_device_messages.proto delete mode 100644 NearbyShareFramework/ProtobufSource/offline_wire_formats.proto delete mode 100644 NearbyShareFramework/ProtobufSource/securegcm.proto delete mode 100644 NearbyShareFramework/ProtobufSource/securemessage.proto delete mode 100644 NearbyShareFramework/ProtobufSource/ukey.proto delete mode 100644 NearbyShareFramework/ProtobufSource/wire_format.proto delete mode 100644 NearbyShareFramework/Util/ConnectionFailureTracker.swift delete mode 100644 NearbyShareFramework/Util/Helpers.swift delete mode 100644 NearbyShareFramework/Util/Logger.swift delete mode 100644 NearbyShareFramework/Util/SaveFilesManager.swift delete mode 100644 NearbyShareFramework/Util/SleepManager.swift delete mode 100644 PROTOCOL.md diff --git a/NearbyShareFramework/Connection/DataModel.swift b/NearbyShareFramework/Connection/DataModel.swift deleted file mode 100644 index 1d0927f..0000000 --- a/NearbyShareFramework/Connection/DataModel.swift +++ /dev/null @@ -1,188 +0,0 @@ -// -// DataModel.swift -// QuickDrop -// -// Created by Leon Böttger on 26.07.25. -// - -import Foundation -import Network - - -protocol InboundNearbyConnectionDelegate { - func obtainUserConsent(for transfer: TransferMetadata, from device: RemoteDeviceInfo, connection: InboundNearbyConnection) - func connectionWasTerminated(connection: InboundNearbyConnection, error: Error?) -} - - -protocol OutboundNearbyConnectionDelegate { - func outboundConnectionWasEstablished(connection: OutboundNearbyConnection) - func outboundConnection(connection: OutboundNearbyConnection, transferProgress: Double) - func outboundConnectionTransferAccepted(connection: OutboundNearbyConnection) - func outboundConnection(connection: OutboundNearbyConnection, failedWithError: Error) - func outboundConnectionTransferFinished(connection: OutboundNearbyConnection) -} - - -public protocol MainAppDelegate { - func obtainUserConsent(for transfer: TransferMetadata, from device: RemoteDeviceInfo) - func incomingTransfer(id: String, didFinishWith error: Error?) - func showFirewallAlert() - func showCopiedToClipboardAlert() - func showUnsupportedFileAlert(for: RemoteDeviceInfo?) -} - - -public protocol ShareExtensionDelegate: AnyObject { - func addDevice(device: RemoteDeviceInfo) - func removeDevice(id: String) - func connectionWasEstablished(pinCode: String) - func connectionFailed(with error: Error) - func transferAccepted() - func transferProgress(progress: Double) - func transferFinished() -} - - -public struct RemoteDeviceInfo { - public let name: String - public let type: DeviceType - public var id: String? - - init(name: String, type: DeviceType, id: String? = nil) { - self.name = name - self.type = type - self.id = id - } - - init(info: EndpointInfo) { - name = info.name - type = info.deviceType - } - - - public enum DeviceType: Int32 { - case unknown = 0 - case phone - case tablet - case computer - - public static func fromRawValue(value: Int) -> DeviceType { - switch value { - case 0: - return .unknown - case 1: - return .phone - case 2: - return .tablet - case 3: - return .computer - default: - return .unknown - } - } - } -} - - -public struct TransferMetadata { - public let files: [FileMetadata] - public let id: String - public let pinCode: String? - public let textDescription: String? - public let type: TransferType - - init(files: [FileMetadata], id: String, pinCode: String?, textDescription: String? = nil, transferType: TransferType = .file) { - self.files = files - self.id = id - self.pinCode = pinCode - self.textDescription = textDescription - self.type = transferType - } - - public enum TransferType { - case file - case text - case url - } -} - - -public struct FileMetadata { - public let name: String - public let size: Int64 - public let mimeType: String -} - - -struct EndpointInfo { - let name: String - let deviceType: RemoteDeviceInfo.DeviceType - - - init(name: String, deviceType: RemoteDeviceInfo.DeviceType) { - self.name = name - self.deviceType = deviceType - } - - - init?(data: Data) { - guard data.count > 17 else { return nil } - let deviceNameLength = Int(data[17]) - guard data.count >= deviceNameLength + 18 else { return nil } - guard let deviceName = String(data: data[18 ..< (18 + deviceNameLength)], encoding: .utf8) else { return nil } - let rawDeviceType = Int(data[0] & 7) >> 1 - name = deviceName - deviceType = RemoteDeviceInfo.DeviceType.fromRawValue(value: rawDeviceType) - } - - - func serialize() -> Data { - // 1 byte: Version(3 bits)|Visibility(1 bit)|Device Type(3 bits)|Reserved(1 bits) - // Device types: unknown=0, phone=1, tablet=2, laptop=3 - var endpointInfo: [UInt8] = [UInt8(deviceType.rawValue << 1)] - // 16 bytes: unknown random bytes - for _ in 0 ... 15 { - endpointInfo.append(UInt8.random(in: 0 ... 255)) - } - // Device name in UTF-8 prefixed with 1-byte length - var nameChars = [UInt8](name.utf8) - if nameChars.count > 255 { - nameChars = [UInt8](nameChars[0 ..< 255]) - } - endpointInfo.append(UInt8(nameChars.count)) - for ch in nameChars { - endpointInfo.append(UInt8(ch)) - } - return Data(endpointInfo) - } -} - - -public enum NearbyError: Error { - case protocolError(_ message: String) - case requiredFieldMissing(_ message: String) - case ukey2 - case packetFilterError - case inputOutput - case canceled(reason: CancellationReason) - - public enum CancellationReason { - case userRejected, userCanceled, notEnoughSpace, unsupportedType, timedOut - - public func localizedDescription() -> String { - switch self { - case .userRejected: - "TransferDeclined".localized() - case .userCanceled: - "TransferCanceled".localized() - case .notEnoughSpace: - "NotEnoughSpace".localized() - case .unsupportedType: - "UnsupportedType".localized() - case .timedOut: - "TransferTimedOut".localized() - } - } - } -} diff --git a/NearbyShareFramework/Connection/InboundNearbyConnection.swift b/NearbyShareFramework/Connection/InboundNearbyConnection.swift deleted file mode 100644 index dd3c832..0000000 --- a/NearbyShareFramework/Connection/InboundNearbyConnection.swift +++ /dev/null @@ -1,534 +0,0 @@ -// -// InboundNearbyConnection.swift -// QuickDrop -// -// Created by Grishka on 08.04.2023. -// - -import AppKit -import CommonCrypto -import CryptoKit -import Foundation -import Network -import System -import BigInt -import SwiftECC - -class InboundNearbyConnection: NearbyConnection { - - private var filesToBeReceived: [Int64: InternalFileInfo] = [:] - private var currentState: State = .initial - private var cipherCommitment: Data? - private var textPayloadID: Int64 = 0 - private var isPlainTextTransfer = false - - public var wasRejected = false - public var delegate: InboundNearbyConnectionDelegate? - - enum State { - case initial, receivedConnectionRequest, sentUkeyServerInit, receivedUkeyClientFinish, sentConnectionResponse, sentPairedKeyResult, receivedPairedKeyResult, waitingForUserConsent, receivingFiles, disconnected - } - - - override init(connection: NWConnection, id: String) { - super.init(connection: connection, id: id) - } - - - override func disconnect() { - super.disconnect() - currentState = .disconnected - deletePartiallyReceivedFiles() - - DispatchQueue.main.async { - self.delegate?.connectionWasTerminated(connection: self, error: self.lastError) - - SaveFilesManager.shared.movePendingFilesToTarget() - SaveFilesManager.shared.stopAccessingSecurityScopedResource() - } - } - - - override func processReceivedFrame(frameData: Data) { - - if currentState != .receivingFiles { - log("[InboundNearbyConnection] Received frame in state \(currentState)...") - } - - do { - switch currentState { - case .initial: - let frame = try Location_Nearby_Connections_OfflineFrame(serializedBytes: frameData) - try processConnectionRequestFrame(frame) - case .receivedConnectionRequest: - let msg = try Securegcm_Ukey2Message(serializedBytes: frameData) - ukeyClientInitMsgData = frameData - try processUkey2ClientInit(msg) - case .sentUkeyServerInit: - let msg = try Securegcm_Ukey2Message(serializedBytes: frameData) - try processUkey2ClientFinish(msg, raw: frameData) - case .receivedUkeyClientFinish: - let frame = try Location_Nearby_Connections_OfflineFrame(serializedBytes: frameData) - try processConnectionResponseFrame(frame) - default: - - var smsg: Securemessage_SecureMessage? = nil - - do { - smsg = try Securemessage_SecureMessage(serializedBytes: frameData) - } catch { - log("[InboundNearbyConnection] Error deserializing secure message (probably due to packet filter)") - lastError = NearbyError.packetFilterError - protocolError() - } - - if let smsg = smsg { - try decryptAndProcessReceivedSecureMessage(smsg) - } - } - } catch { - lastError = error - log("[InboundNearbyConnection] Error receiving frame: \(error) in state \(currentState).") - protocolError() - } - } - - - override func processTransferSetupFrame(_ frame: Sharing_Nearby_Frame) throws { - if frame.hasV1 && frame.v1.hasType, case .cancel = frame.v1.type { - log("[InboundNearbyConnection] Transfer canceled") - try sendDisconnectionAndDisconnect() - return - } - switch currentState { - case .sentConnectionResponse: - try processPairedKeyEncryptionFrame(frame) - case .sentPairedKeyResult: - try processPairedKeyResultFrame(frame) - case .receivedPairedKeyResult: - try processIntroductionFrame(frame) - default: - log("[InboundNearbyConnection] Unexpected connection state in processTransferSetupFrame: \(currentState)") - log(frame.debugDescription) - } - } - - - override func isServer() -> Bool { - return true - } - - - override func processFileChunk(frame: Location_Nearby_Connections_PayloadTransferFrame) throws { - - let id = frame.payloadHeader.id - - guard let fileInfo = filesToBeReceived[id] else { throw NearbyError.protocolError("File payload ID \(id) is not known") } - - let currentOffset = fileInfo.bytesTransferred - - guard frame.payloadChunk.offset == currentOffset else { throw NearbyError.protocolError("Invalid offset into file \(frame.payloadChunk.offset), expected \(currentOffset)") } - - guard currentOffset + Int64(frame.payloadChunk.body.count) <= fileInfo.meta.size else { throw NearbyError.protocolError("Transferred file size exceeds previously specified value") } - - if frame.payloadChunk.body.count > 0 { - do { - try fileInfo.fileHandle?.write(contentsOf: frame.payloadChunk.body) - filesToBeReceived[id]!.bytesTransferred += Int64(frame.payloadChunk.body.count) - fileInfo.progress?.completedUnitCount = filesToBeReceived[id]!.bytesTransferred - - // only for logging - self.bytesTransferred += Int64(frame.payloadChunk.body.count) - } catch { - log("[InboundNearbyConnection] Error occurred during writing file: \(error.localizedDescription)") - - throw NearbyError.protocolError(error.localizedDescription) - } - } - else if (frame.payloadChunk.flags & 1) == 1 { - try fileInfo.fileHandle?.close() - filesToBeReceived[id]!.fileHandle = nil - fileInfo.progress?.unpublish() - SaveFilesManager.shared.registerFileFinishedDownloading(fileInfo.destinationURL) - - filesToBeReceived.removeValue(forKey: id) - - if filesToBeReceived.isEmpty { - log("[InboundNearbyConnection] All files received, sending disconnection frame and disconnecting.") - try sendDisconnectionAndDisconnect() - } - } - } - - - override func processBytesPayload(payload: Data, id: Int64) throws -> Bool { - if id == textPayloadID { - if let urlStr = String(data: payload, encoding: .utf8) { - - if isPlainTextTransfer { - // paste to clipboard - let pasteboard = NSPasteboard.general - pasteboard.clearContents() - pasteboard.setString(urlStr, forType: .string) - - NearbyConnectionManager.shared.mainAppDelegate?.showCopiedToClipboardAlert() - } - else if let url = URL(string: urlStr) { - NSWorkspace.shared.open(url) - } - } - - log("[InboundNearbyConnection] Received text payload. Disconnecting...") - try sendDisconnectionAndDisconnect() - return true - } - else if let fileInfo = filesToBeReceived[id] { - fileInfo.fileHandle?.write(payload) - filesToBeReceived[id]!.bytesTransferred += Int64(payload.count) - fileInfo.progress?.completedUnitCount = filesToBeReceived[id]!.bytesTransferred - try fileInfo.fileHandle?.close() - filesToBeReceived[id]!.fileHandle = nil - fileInfo.progress?.unpublish() - filesToBeReceived.removeValue(forKey: id) - SaveFilesManager.shared.registerFileFinishedDownloading(fileInfo.destinationURL) - - log("[InboundNearbyConnection] Received file payload. Disconnecting...") - try sendDisconnectionAndDisconnect() - return true - } - return false - } - - - private func processConnectionRequestFrame(_ frame: Location_Nearby_Connections_OfflineFrame) throws { - - guard frame.hasV1 && frame.v1.hasConnectionRequest && frame.v1.connectionRequest.hasEndpointInfo else { throw NearbyError.requiredFieldMissing("connectionRequest.endpointInfo") } - - guard case .connectionRequest = frame.v1.type else { throw NearbyError.protocolError("Unexpected frame type \(frame.v1.type)") } - - let endpointInfo = frame.v1.connectionRequest.endpointInfo - - guard endpointInfo.count > 17 else { throw NearbyError.protocolError("Endpoint info too short") } - - let deviceNameLength = Int(endpointInfo[17]) - - guard endpointInfo.count >= deviceNameLength + 18 else { throw NearbyError.protocolError("Endpoint info too short to contain the device name") } - - guard let deviceName = String(data: endpointInfo[18 ..< (18 + deviceNameLength)], encoding: .utf8) else { throw NearbyError.protocolError("Device name is not valid UTF-8") } - - let rawDeviceType = Int(endpointInfo[0] & 7) >> 1 - - remoteDeviceInfo = RemoteDeviceInfo(name: deviceName, type: RemoteDeviceInfo.DeviceType.fromRawValue(value: rawDeviceType)) - currentState = .receivedConnectionRequest - } - - - private func processUkey2ClientInit(_ msg: Securegcm_Ukey2Message) throws { - guard msg.hasMessageType, msg.hasMessageData else { throw NearbyError.requiredFieldMissing("clientInit ukey2message.type|data") } - guard case .clientInit = msg.messageType else { - sendUkey2Alert(type: .badMessageType) - log("[InboundNearbyConnection] Unsupported message type: \(msg.messageType)") - throw NearbyError.ukey2 - } - let clientInit: Securegcm_Ukey2ClientInit - do { - clientInit = try Securegcm_Ukey2ClientInit(serializedBytes: msg.messageData) - } catch { - sendUkey2Alert(type: .badMessageData) - log("[InboundNearbyConnection] Failed to parse clientInit: \(error)") - throw NearbyError.ukey2 - } - guard clientInit.version == 1 else { - sendUkey2Alert(type: .badVersion) - log("[InboundNearbyConnection] Unsupported clientInit version: \(clientInit.version)") - throw NearbyError.ukey2 - } - guard clientInit.random.count == 32 else { - sendUkey2Alert(type: .badRandom) - log("[InboundNearbyConnection] Unsupported clientInit random: \(clientInit.random.count)") - throw NearbyError.ukey2 - } - var found = false - for commitment in clientInit.cipherCommitments { - if case .p256Sha512 = commitment.handshakeCipher { - found = true - cipherCommitment = commitment.commitment - break - } - } - guard found else { - sendUkey2Alert(type: .badHandshakeCipher) - log("[InboundNearbyConnection] Unsupported clientInit handshakeCipher: \(clientInit.cipherCommitments)") - throw NearbyError.ukey2 - } - guard clientInit.nextProtocol == "AES_256_CBC-HMAC_SHA256" else { - sendUkey2Alert(type: .badNextProtocol) - log("[InboundNearbyConnection] Unsupported clientInit nextProtocol: \(clientInit.nextProtocol)") - throw NearbyError.ukey2 - } - - let domain = Domain.instance(curve: .EC256r1) - let (pubKey, privKey) = domain.makeKeyPair() - publicKey = pubKey - privateKey = privKey - - var serverInit = Securegcm_Ukey2ServerInit() - serverInit.version = 1 - serverInit.random = Data.randomData(length: 32) - serverInit.handshakeCipher = .p256Sha512 - - var pkey = Securemessage_GenericPublicKey() - pkey.type = .ecP256 - pkey.ecP256PublicKey = Securemessage_EcP256PublicKey() - pkey.ecP256PublicKey.x = Data(pubKey.w.x.asSignedBytes()) - pkey.ecP256PublicKey.y = Data(pubKey.w.y.asSignedBytes()) - serverInit.publicKey = try pkey.serializedData() - - var serverInitMsg = Securegcm_Ukey2Message() - serverInitMsg.messageType = .serverInit - serverInitMsg.messageData = try serverInit.serializedData() - let serverInitData = try serverInitMsg.serializedData() - ukeyServerInitMsgData = serverInitData - sendFrameAsync(serverInitData) - currentState = .sentUkeyServerInit - } - - - private func processUkey2ClientFinish(_ msg: Securegcm_Ukey2Message, raw: Data) throws { - guard msg.hasMessageType, msg.hasMessageData else { throw NearbyError.requiredFieldMissing("clientFinish ukey2message.type|data") } - guard case .clientFinish = msg.messageType else { - log("[InboundNearbyConnection] Unexpected message type \(msg.messageType)") - throw NearbyError.ukey2 - } - - var sha = SHA512() - sha.update(data: raw) - guard cipherCommitment == Data(sha.finalize()) else { - log("[InboundNearbyConnection] Invalid cipherCommitment in clientFinish") - throw NearbyError.ukey2 - } - - let clientFinish = try Securegcm_Ukey2ClientFinished(serializedBytes: msg.messageData) - guard clientFinish.hasPublicKey else { throw NearbyError.requiredFieldMissing("ukey2clientFinish.publicKey") } - let clientKey = try Securemessage_GenericPublicKey(serializedBytes: clientFinish.publicKey) - - try finalizeKeyExchange(peerKey: clientKey) - - currentState = .receivedUkeyClientFinish - } - - - private func processConnectionResponseFrame(_ frame: Location_Nearby_Connections_OfflineFrame) throws { - guard frame.hasV1, frame.v1.hasType else { throw NearbyError.requiredFieldMissing("offlineFrame.v1.type") } - if case .connectionResponse = frame.v1.type { - var resp = Location_Nearby_Connections_OfflineFrame() - resp.version = .v1 - resp.v1 = Location_Nearby_Connections_V1Frame() - resp.v1.type = .connectionResponse - resp.v1.connectionResponse = Location_Nearby_Connections_ConnectionResponseFrame() - resp.v1.connectionResponse.response = .accept - resp.v1.connectionResponse.status = 0 - resp.v1.connectionResponse.osInfo = Location_Nearby_Connections_OsInfo() - resp.v1.connectionResponse.osInfo.type = .apple - try sendFrameAsync(resp.serializedData()) - - encryptionDone = true - - var pairedEncryption = Sharing_Nearby_Frame() - pairedEncryption.version = .v1 - pairedEncryption.v1 = Sharing_Nearby_V1Frame() - pairedEncryption.v1.type = .pairedKeyEncryption - pairedEncryption.v1.pairedKeyEncryption = Sharing_Nearby_PairedKeyEncryptionFrame() - // Presumably used for all the phone number stuff that no one needs anyway - pairedEncryption.v1.pairedKeyEncryption.secretIDHash = Data.randomData(length: 6) - pairedEncryption.v1.pairedKeyEncryption.signedData = Data.randomData(length: 72) - try sendTransferSetupFrame(pairedEncryption) - currentState = .sentConnectionResponse - } else { - log("[InboundNearbyConnection] Unhandled offline frame plaintext: \(frame)") - } - } - - - private func processPairedKeyEncryptionFrame(_ frame: Sharing_Nearby_Frame) throws { - guard frame.hasV1, frame.v1.hasPairedKeyEncryption else { throw NearbyError.requiredFieldMissing("shareNearbyFrame.v1.pairedKeyEncryption") } - var pairedResult = Sharing_Nearby_Frame() - pairedResult.version = .v1 - pairedResult.v1 = Sharing_Nearby_V1Frame() - pairedResult.v1.type = .pairedKeyResult - pairedResult.v1.pairedKeyResult = Sharing_Nearby_PairedKeyResultFrame() - pairedResult.v1.pairedKeyResult.status = .unable - try sendTransferSetupFrame(pairedResult) - currentState = .sentPairedKeyResult - } - - - private func processPairedKeyResultFrame(_ frame: Sharing_Nearby_Frame) throws { - guard frame.hasV1, frame.v1.hasPairedKeyResult else { throw NearbyError.requiredFieldMissing("shareNearbyFrame.v1.pairedKeyResult") } - currentState = .receivedPairedKeyResult - } - - - private func makeFileDestinationURL(_ initialDest: URL) -> URL { - var dest = initialDest - if FileManager.default.fileExists(atPath: dest.path) { - var counter = 1 - var path: String - let ext = dest.pathExtension - let baseUrl = dest.deletingPathExtension() - repeat { - path = "\(baseUrl.path) (\(counter))" - if !ext.isEmpty { - path += ".\(ext)" - } - counter += 1 - } while FileManager.default.fileExists(atPath: path) - dest = URL(fileURLWithPath: path) - } - return dest - } - - - private func processIntroductionFrame(_ frame: Sharing_Nearby_Frame) throws { - guard frame.hasV1, frame.v1.hasIntroduction else { throw NearbyError.requiredFieldMissing("shareNearbyFrame.v1.introduction") } - currentState = .waitingForUserConsent - - if frame.v1.introduction.fileMetadata.count > 0 && frame.v1.introduction.textMetadata.isEmpty { - let saveDirectory = SaveFilesManager.shared.getSaveDirectory() - - for file in frame.v1.introduction.fileMetadata { - let dest = makeFileDestinationURL(saveDirectory.appendingPathComponent(file.name)) - let info = InternalFileInfo(meta: FileMetadata(name: file.name, size: file.size, mimeType: file.mimeType), - payloadID: file.payloadID, - destinationURL: dest) - filesToBeReceived[file.payloadID] = info - } - let metadata = TransferMetadata(files: filesToBeReceived.map { $0.value.meta }, id: id, pinCode: pinCode) - DispatchQueue.main.async { - self.delegate?.obtainUserConsent(for: metadata, from: self.remoteDeviceInfo!, connection: self) - } - } - else if let textMetadata = frame.v1.introduction.textMetadata.first { - - if textMetadata.type == .url || textMetadata.type == .text { - - let isClipboardText = textMetadata.type == .text - - let metadata = TransferMetadata(files: [], id: id, pinCode: pinCode, textDescription: textMetadata.textTitle, transferType: isClipboardText ? .text : .url) - textPayloadID = textMetadata.payloadID - - if isClipboardText{ - isPlainTextTransfer = true - } - - DispatchQueue.main.async { - self.delegate?.obtainUserConsent(for: metadata, from: self.remoteDeviceInfo!, connection: self) - } - } - else { - rejectDueToUnsupportedFileType(frame) - } - } else { - rejectDueToUnsupportedFileType(frame) - } - } - - - func rejectDueToUnsupportedFileType(_ frame: Sharing_Nearby_Frame) { - - log("[InboundNearbyConnection] Rejecting transfer due to unsupported file type. Frame is \(frame.debugDescription)") - - NearbyConnectionManager.shared.mainAppDelegate?.showUnsupportedFileAlert(for: remoteDeviceInfo) - rejectTransfer(with: .unsupportedAttachmentType) - } - - - func submitUserConsent(accepted: Bool, storeInTemp: Bool = false) { - DispatchQueue.global(qos: .utility).async { - if accepted { - self.acceptTransfer(storeInTemp: storeInTemp) - } else { - self.rejectTransfer() - } - } - } - - - private func acceptTransfer(storeInTemp: Bool) { - if currentState == .disconnected { - log("[InboundNearbyConnection] Detected timeout, not accepting transfer") - return - } - - do { - if storeInTemp { - try FileManager.default.createDirectory(at: SaveFilesManager.shared.tempDirectory, withIntermediateDirectories: true) - } - - for (id, file) in filesToBeReceived { - let targetURL = storeInTemp ? SaveFilesManager.shared.tempDirectory.appendingPathComponent(file.destinationURL.lastPathComponent) : file.destinationURL - - FileManager.default.createFile(atPath: targetURL.path, contents: nil) - let handle = try FileHandle(forWritingTo: targetURL) - filesToBeReceived[id]!.fileHandle = handle - - let progress = Progress() - progress.fileURL = targetURL - progress.totalUnitCount = file.meta.size - progress.kind = .file - progress.isPausable = false - progress.publish() - filesToBeReceived[id]!.progress = progress - filesToBeReceived[id]!.created = true - } - - var frame = Sharing_Nearby_Frame() - frame.version = .v1 - frame.v1.type = .response - frame.v1.connectionResponse.status = .accept - currentState = .receivingFiles - try sendTransferSetupFrame(frame) - } catch { - lastError = error - protocolError() - } - } - - - private func rejectTransfer(with reason: Sharing_Nearby_ConnectionResponseFrame.Status = .reject) { - - self.wasRejected = true - - log("[InboundNearbyConnection] Rejecting transfer because of \( reason)") - - var frame = Sharing_Nearby_Frame() - frame.version = .v1 - frame.v1.type = .response - frame.v1.connectionResponse.status = reason - do { - try sendTransferSetupFrame(frame) - try sendDisconnectionAndDisconnect() - } catch { - log("[InboundNearbyConnection] Error \(error)") - protocolError() - } - } - - - private func deletePartiallyReceivedFiles() { - for (_, file) in filesToBeReceived { - - if let progress = file.progress { - progress.unpublish() - } - - guard file.created else { continue } - do { - try FileManager.default.removeItem(at: file.destinationURL) - } - catch { - // if it fails, we don't care. Could be because file was not created yet - } - } - } -} diff --git a/NearbyShareFramework/Connection/NearbyConnection.swift b/NearbyShareFramework/Connection/NearbyConnection.swift deleted file mode 100644 index 4a60e38..0000000 --- a/NearbyShareFramework/Connection/NearbyConnection.swift +++ /dev/null @@ -1,592 +0,0 @@ -// -// NearbyConnection.swift -// QuickDrop -// -// Created by Grishka on 09.04.2023. -// - -import CommonCrypto -import CryptoKit -import Foundation -import Network -import System - -import BigInt -import SwiftECC - -class NearbyConnection { - - private let maxFrameLength = 5 * 1024 * 1024 - private static let dispatchQueue = DispatchQueue(label: "com.leonboettger.quickdrop.queue", qos: .utility) - - let id: String - let connection: NWConnection - - var remoteDeviceInfo: RemoteDeviceInfo? - var encryptionDone: Bool = false - var lastError: Error? - var bytesTransferred: Int64 = 0 - - private var payloadBuffers: [Int64: NSMutableData] = [:] - private var connectionClosed: Bool = false - - private var inactivityTimer: DispatchSourceTimer? - private let timeoutInterval: TimeInterval = 50 - - // UKEY2-related state - var publicKey: ECPublicKey? - var privateKey: ECPrivateKey? - var ukeyClientInitMsgData: Data? - var ukeyServerInitMsgData: Data? - - // SecureMessage encryption keys - var decryptKey: [UInt8]? - var encryptKey: [UInt8]? - var recvHmacKey: SymmetricKey? - var sendHmacKey: SymmetricKey? - - // SecureMessage sequence numbers - private var serverSeq: Int32 = 0 - private var clientSeq: Int32 = 0 - - private(set) var pinCode: String? - - - init(connection: NWConnection, id: String) { - self.connection = connection - self.id = id - } - - - func start() { - connection.stateUpdateHandler = { state in - - if !self.connectionClosed { - if case .ready = state { - self.connectionReady() - self.receiveFrameAsync() - } else if case let .failed(err) = state { - - // If connection reset by peer, it could still be a valid file transfer that has not been processed yet. => Wait - if err == .posix(.ECONNRESET) { - NearbyConnection.dispatchQueue.asyncAfter(deadline: .now() + 0.5) { - - // If already closed, ignore the error, as file transfer has been processed - if !self.connectionClosed { - recordErrorAndDisconnect(err: err) - } - } - } - else { - recordErrorAndDisconnect(err: err) - } - } - } - else { - log("[NearbyConnection] Connection already closed, ignoring state update: \(state)") - } - } - - connection.start(queue: NearbyConnection.dispatchQueue) - - func recordErrorAndDisconnect(err: NWError) { - self.lastError = err - log("[NearbyConnection] Connection Error: \(err)") - - // If the error is a connection reset, it could be due to firewall issues. - if err == .posix(.ENOTCONN) { - ConnectionFailureTracker.shared.recordFailure { - NearbyConnectionManager.shared.mainAppDelegate?.showFirewallAlert() - } - } - - if err == .posix(.ENETDOWN) { - self.lastError = NearbyError.protocolError("Error.NetworkDown".localized()) - } - - self.disconnect() - } - } - - - func connectionReady() {} - - - func protocolError() { - log("[NearbyConnection] Protocol error: \(String(describing: lastError))") - disconnect() - } - - - func processReceivedFrame(frameData _: Data) { - fatalError() - } - - - func processTransferSetupFrame(_: Sharing_Nearby_Frame) throws { - fatalError() - } - - - func isServer() -> Bool { - fatalError() - } - - - func processFileChunk(frame _: Location_Nearby_Connections_PayloadTransferFrame) throws { - protocolError() - } - - - func processBytesPayload(payload _: Data, id _: Int64) throws -> Bool { - return false - } - - - private func receiveFrameAsync() { - connection.receive(minimumIncompleteLength: 4, maximumLength: 4) { content, _, isComplete, error in - if self.connectionClosed { - return - } - if isComplete { - log("[NearbyConnection] Connection closed by peer during receiveFrameAsync") - self.lastError = NearbyError.protocolError("Error.ClosedByPeer".localized()) - self.disconnect() - return - } - if !(error == nil) { - self.lastError = error - self.protocolError() - return - } - guard let content = content else { - assertionFailure() - return - } - let frameLength = UInt32(content[0]) << 24 | UInt32(content[1]) << 16 | UInt32(content[2]) << 8 | UInt32(content[3]) - guard frameLength < self.maxFrameLength else { - self.lastError = NearbyError.protocolError("Unexpected packet length") - self.protocolError() - return - } - - self.startInactivityTimer() - self.receiveFrameAsync(length: frameLength) - } - } - - - private func receiveFrameAsync(length: UInt32) { - connection.receive(minimumIncompleteLength: Int(length), maximumLength: Int(length)) { content, _, isComplete, _ in - if self.connectionClosed { - return - } - if isComplete { - log("[NearbyConnection] Connection closed by peer during receiveFrameAsync") - self.lastError = NearbyError.protocolError("Error.ClosedByPeer".localized()) - self.disconnect() - return - } - guard let content = content else { - self.protocolError() - return - } - - self.startInactivityTimer() - self.processReceivedFrame(frameData: content) - self.receiveFrameAsync() - } - } - - - func sendFrameAsync(_ frame: Data, completion: (() -> Void)? = nil) { - if connectionClosed { - return - } - var lengthPrefixedData = Data(capacity: frame.count + 4) - let length: Int = frame.count - lengthPrefixedData.append(contentsOf: [ - UInt8(truncatingIfNeeded: length >> 24), - UInt8(truncatingIfNeeded: length >> 16), - UInt8(truncatingIfNeeded: length >> 8), - UInt8(truncatingIfNeeded: length), - ]) - lengthPrefixedData.append(frame) - connection.send(content: lengthPrefixedData, completion: .contentProcessed { _ in - if let completion = completion { - completion() - } - }) - } - - - func encryptAndSendOfflineFrame(_ frame: Location_Nearby_Connections_OfflineFrame, completion: (() -> Void)? = nil) throws { - var d2dMsg = Securegcm_DeviceToDeviceMessage() - serverSeq += 1 - d2dMsg.sequenceNumber = serverSeq - d2dMsg.message = try frame.serializedData() - - let serializedMsg = try [UInt8](d2dMsg.serializedData()) - let iv = Data.randomData(length: 16) - var encryptedData = Data(count: serializedMsg.count + 16) - var encryptedLength: size_t = 0 - encryptedData.withUnsafeMutableBytes { - let status = CCCrypt( - CCOperation(kCCEncrypt), - CCAlgorithm(kCCAlgorithmAES128), - CCOptions(kCCOptionPKCS7Padding), - encryptKey, kCCKeySizeAES256, - [UInt8](iv), - serializedMsg, serializedMsg.count, - $0.baseAddress, $0.count, - &encryptedLength - ) - guard status == kCCSuccess else { fatalError("CCCrypt error: \(status)") } - } - - var hb = Securemessage_HeaderAndBody() - hb.body = encryptedData.prefix(encryptedLength) - hb.header = Securemessage_Header() - hb.header.encryptionScheme = .aes256Cbc - hb.header.signatureScheme = .hmacSha256 - hb.header.iv = iv - var md = Securegcm_GcmMetadata() - md.type = .deviceToDeviceMessage - md.version = 1 - hb.header.publicMetadata = try md.serializedData() - - var smsg = Securemessage_SecureMessage() - smsg.headerAndBody = try hb.serializedData() - smsg.signature = Data(HMAC.authenticationCode(for: smsg.headerAndBody, using: sendHmacKey!)) - try sendFrameAsync(smsg.serializedData(), completion: completion) - } - - - func sendTransferSetupFrame(_ frame: Sharing_Nearby_Frame) throws { - log("[NearbyConnection] Sending transfer setup frame.") - try sendBytesPayload(data: frame.serializedData(), id: Int64.random(in: Int64.min ... Int64.max)) - } - - - func sendBytesPayload(data: Data, id: Int64) throws { - - var transfer = Location_Nearby_Connections_PayloadTransferFrame() - transfer.packetType = .data - transfer.payloadChunk.offset = 0 - transfer.payloadChunk.flags = 0 - transfer.payloadChunk.body = data - transfer.payloadHeader.id = id - transfer.payloadHeader.type = .bytes - transfer.payloadHeader.totalSize = Int64(transfer.payloadChunk.body.count) - transfer.payloadHeader.isSensitive = false - - var wrapper = Location_Nearby_Connections_OfflineFrame() - wrapper.version = .v1 - wrapper.v1 = Location_Nearby_Connections_V1Frame() - wrapper.v1.type = .payloadTransfer - wrapper.v1.payloadTransfer = transfer - try encryptAndSendOfflineFrame(wrapper) - - transfer.payloadChunk.flags = 1 // .lastChunk - transfer.payloadChunk.offset = Int64(transfer.payloadChunk.body.count) - transfer.payloadChunk.clearBody() - wrapper.v1.payloadTransfer = transfer - try encryptAndSendOfflineFrame(wrapper) - } - - - func decryptAndProcessReceivedSecureMessage(_ smsg: Securemessage_SecureMessage) throws { - guard smsg.hasSignature, smsg.hasHeaderAndBody else { throw NearbyError.requiredFieldMissing("secureMessage.signature|headerAndBody") } - - let hmac = Data(HMAC.authenticationCode(for: smsg.headerAndBody, using: recvHmacKey!)) - guard hmac == smsg.signature else { throw NearbyError.protocolError("hmac!=signature: Expected \(hmac), got \(smsg.signature)") } - - let headerAndBody = try Securemessage_HeaderAndBody(serializedBytes: smsg.headerAndBody) - var decryptedData = Data(count: headerAndBody.body.count) - - var decryptedLength = 0 - decryptedData.withUnsafeMutableBytes { - let status = CCCrypt( - CCOperation(kCCDecrypt), - CCAlgorithm(kCCAlgorithmAES128), - CCOptions(kCCOptionPKCS7Padding), - decryptKey, kCCKeySizeAES256, - [UInt8](headerAndBody.header.iv), - [UInt8](headerAndBody.body), headerAndBody.body.count, - $0.baseAddress, $0.count, - &decryptedLength - ) - guard status == kCCSuccess else { fatalError("CCCrypt error: \(status)") } - } - - decryptedData = decryptedData.prefix(decryptedLength) - let d2dMsg = try Securegcm_DeviceToDeviceMessage(serializedBytes: decryptedData) - - guard d2dMsg.hasMessage, d2dMsg.hasSequenceNumber else { throw NearbyError.requiredFieldMissing("d2dMessage.message|sequenceNumber") } - clientSeq += 1 - - guard d2dMsg.sequenceNumber == clientSeq else { throw NearbyError.protocolError("Wrong sequence number. Expected \(clientSeq), got \(d2dMsg.sequenceNumber)") } - let offlineFrame = try Location_Nearby_Connections_OfflineFrame(serializedBytes: d2dMsg.message) - - if offlineFrame.hasV1, offlineFrame.v1.hasType, case .payloadTransfer = offlineFrame.v1.type { - - guard offlineFrame.v1.hasPayloadTransfer else { throw NearbyError.requiredFieldMissing("offlineFrame.v1.payloadTransfer") } - - let payloadTransfer = offlineFrame.v1.payloadTransfer - let header = payloadTransfer.payloadHeader - let chunk = payloadTransfer.payloadChunk - - guard header.hasType, header.hasID else { throw NearbyError.requiredFieldMissing("payloadHeader.type|id") } - guard payloadTransfer.hasPayloadChunk, chunk.hasOffset, chunk.hasFlags else { - log("[NearbyConnection] Payload transfer chunk is missing offset or flags, likely canceled.") - throw NearbyError.canceled(reason: .userCanceled) - } - - if case .bytes = header.type { - - let payloadID = header.id - - if header.totalSize > maxFrameLength { - - payloadBuffers.removeValue(forKey: payloadID) - throw NearbyError.protocolError("Payload too large (\(header.totalSize) bytes)") - } - - if payloadBuffers[payloadID] == nil { - - payloadBuffers[payloadID] = NSMutableData(capacity: Int(header.totalSize)) - } - - let buffer = payloadBuffers[payloadID]! - - guard chunk.offset == buffer.count else { - payloadBuffers.removeValue(forKey: payloadID) - throw NearbyError.protocolError("Unexpected chunk offset \(chunk.offset), expected \(buffer.count)") - } - - if chunk.hasBody { - buffer.append(chunk.body) - } - - if (chunk.flags & 1) == 1 { - payloadBuffers.removeValue(forKey: payloadID) - if try !processBytesPayload(payload: Data(buffer), id: payloadID) { - let innerFrame = try Sharing_Nearby_Frame(serializedBytes: buffer as Data) - try processTransferSetupFrame(innerFrame) - } - } - - } else if case .file = header.type { - - try processFileChunk(frame: payloadTransfer) - } - } - else if offlineFrame.hasV1, offlineFrame.v1.hasType, case .keepAlive = offlineFrame.v1.type { - - log("[NearbyConnection] Sent keep-alive, \(self.bytesTransferred) bytes sent") - sendKeepAlive(ack: true) - } else { - - log("[NearbyConnection] Unhandled offline frame encrypted: \(offlineFrame)") - } - } - - - static func pinCodeFromAuthKey(_ key: SymmetricKey) -> String { - var hash = 0 - var multiplier = 1 - let keyBytes: [UInt8] = key.withUnsafeBytes { - [UInt8]($0) - } - - for _byte in keyBytes { - let byte = Int(Int8(bitPattern: _byte)) - hash = (hash + byte * multiplier) % 9973 - multiplier = (multiplier * 31) % 9973 - } - - return String(format: "%04d", abs(hash)) - } - - - static func hkdfExtract(salt: Data, ikm: Data) -> Data { - return HMAC.authenticationCode(for: ikm, using: SymmetricKey(data: salt)).withUnsafeBytes { Data(bytes: $0.baseAddress!, count: $0.count) } - } - - - static func hkdfExpand(prk: Data, info: Data, length: Int) -> Data { - var okm = Data() - var t = Data() - var i = 0 - while okm.count < length { - i = i + 1 - let toDigest = t + info + Data([UInt8(truncatingIfNeeded: i)]) - t = HMAC.authenticationCode(for: toDigest, using: SymmetricKey(data: prk)).withUnsafeBytes { Data(bytes: $0.baseAddress!, count: $0.count) } - okm = okm + t - } - return okm.subdata(in: 0 ..< length) - } - - - static func hkdf(inputKeyMaterial: SymmetricKey, salt: Data, info: Data, outputByteCount: Int) -> SymmetricKey { - return HKDF.deriveKey(inputKeyMaterial: inputKeyMaterial, salt: salt, info: info, outputByteCount: outputByteCount) - } - - - func finalizeKeyExchange(peerKey: Securemessage_GenericPublicKey) throws { - guard peerKey.hasEcP256PublicKey else { throw NearbyError.requiredFieldMissing("peerKey.ecP256PublicKey") } - - let domain = Domain.instance(curve: .EC256r1) - var clientX = peerKey.ecP256PublicKey.x - var clientY = peerKey.ecP256PublicKey.y - if clientX.count > 32 { - clientX = clientX.suffix(32) - } - if clientY.count > 32 { - clientY = clientY.suffix(32) - } - let key = try ECPublicKey(domain: domain, w: Point(BInt(magnitude: [UInt8](clientX)), BInt(magnitude: [UInt8](clientY)))) - - let dhs = try (privateKey?.domain.multiplyPoint(key.w, privateKey!.s).x.asMagnitudeBytes())! - var sha = SHA256() - sha.update(data: dhs) - let derivedSecretKey = Data(sha.finalize()) - - var ukeyInfo = Data() - ukeyInfo.append(ukeyClientInitMsgData!) - ukeyInfo.append(ukeyServerInitMsgData!) - let authString = NearbyConnection.hkdf(inputKeyMaterial: SymmetricKey(data: derivedSecretKey), salt: "UKEY2 v1 auth".data(using: .utf8)!, info: ukeyInfo, outputByteCount: 32) - let nextSecret = NearbyConnection.hkdf(inputKeyMaterial: SymmetricKey(data: derivedSecretKey), salt: "UKEY2 v1 next".data(using: .utf8)!, info: ukeyInfo, outputByteCount: 32) - - pinCode = NearbyConnection.pinCodeFromAuthKey(authString) - - let salt = Data([0x82, 0xAA, 0x55, 0xA0, 0xD3, 0x97, 0xF8, 0x83, 0x46, 0xCA, 0x1C, - 0xEE, 0x8D, 0x39, 0x09, 0xB9, 0x5F, 0x13, 0xFA, 0x7D, 0xEB, 0x1D, - 0x4A, 0xB3, 0x83, 0x76, 0xB8, 0x25, 0x6D, 0xA8, 0x55, 0x10]) - - let d2dClientKey = NearbyConnection.hkdf(inputKeyMaterial: nextSecret, salt: salt, info: "client".data(using: .utf8)!, outputByteCount: 32) - let d2dServerKey = NearbyConnection.hkdf(inputKeyMaterial: nextSecret, salt: salt, info: "server".data(using: .utf8)!, outputByteCount: 32) - - sha = SHA256() - sha.update(data: "SecureMessage".data(using: .utf8)!) - let smsgSalt = Data(sha.finalize()) - - let clientKey = NearbyConnection.hkdf(inputKeyMaterial: d2dClientKey, salt: smsgSalt, info: "ENC:2".data(using: .utf8)!, outputByteCount: 32).withUnsafeBytes { [UInt8]($0) } - let clientHmacKey = NearbyConnection.hkdf(inputKeyMaterial: d2dClientKey, salt: smsgSalt, info: "SIG:1".data(using: .utf8)!, outputByteCount: 32) - let serverKey = NearbyConnection.hkdf(inputKeyMaterial: d2dServerKey, salt: smsgSalt, info: "ENC:2".data(using: .utf8)!, outputByteCount: 32).withUnsafeBytes { [UInt8]($0) } - let serverHmacKey = NearbyConnection.hkdf(inputKeyMaterial: d2dServerKey, salt: smsgSalt, info: "SIG:1".data(using: .utf8)!, outputByteCount: 32) - - if isServer() { - decryptKey = clientKey - recvHmacKey = clientHmacKey - encryptKey = serverKey - sendHmacKey = serverHmacKey - } else { - decryptKey = serverKey - recvHmacKey = serverHmacKey - encryptKey = clientKey - sendHmacKey = clientHmacKey - } - } - - - func disconnect() { - log("[NearbyConnection] Disconnecting from connection.") - - connection.stateUpdateHandler = nil - inactivityTimer?.cancel() - inactivityTimer = nil - connectionClosed = true - connection.send(content: nil, isComplete: true, completion: .contentProcessed { _ in }) - } - - - func sendDisconnectionAndDisconnect() throws { - var offlineFrame = Location_Nearby_Connections_OfflineFrame() - offlineFrame.version = .v1 - offlineFrame.v1.type = .disconnection - offlineFrame.v1.disconnection = Location_Nearby_Connections_DisconnectionFrame() - - if encryptionDone { - try encryptAndSendOfflineFrame(offlineFrame) - } else { - try sendFrameAsync(offlineFrame.serializedData()) - } - - log("[NearbyConnection] Sent disconnection frame during sendDisconnectionAndDisconnect") - disconnect() - } - - - func sendUkey2Alert(type: Securegcm_Ukey2Alert.AlertType) { - var alert = Securegcm_Ukey2Alert() - alert.type = type - var msg = Securegcm_Ukey2Message() - msg.messageType = .alert - msg.messageData = try! alert.serializedData() - sendFrameAsync(try! msg.serializedData()) - - log("[NearbyConnection] Sent UKEY2 alert: \(type)") - disconnect() - } - - - func sendKeepAlive(ack: Bool) { - var offlineFrame = Location_Nearby_Connections_OfflineFrame() - offlineFrame.version = .v1 - offlineFrame.v1.type = .keepAlive - offlineFrame.v1.keepAlive.ack = ack - - do { - if encryptionDone { - try encryptAndSendOfflineFrame(offlineFrame) - } else { - try sendFrameAsync(offlineFrame.serializedData()) - } - } catch { - log("[NearbyConnection] Error sending KEEP_ALIVE: \(error)") - } - } - - - func startInactivityTimer() { - - // Cancel previous timer if any - inactivityTimer?.cancel() - - inactivityTimer = DispatchSource.makeTimerSource(queue: NearbyConnection.dispatchQueue) - inactivityTimer?.schedule(deadline: .now() + timeoutInterval) - inactivityTimer?.setEventHandler { [weak self] in - - guard let self = self else { return } - - if !self.connectionClosed { - log("[NearbyConnection] Connection timeout: No message received in \(self.timeoutInterval) seconds") - self.lastError = NearbyError.canceled(reason: .timedOut) - self.disconnect() - } - } - - inactivityTimer?.resume() - } - - - // -- MARK: - Internal Data Model - - struct InternalFileInfo { - let meta: FileMetadata - let payloadID: Int64 - let destinationURL: URL - var bytesTransferred: Int64 = 0 - var fileHandle: FileHandle? - var progress: Progress? - var created: Bool = false - } -} - -public extension String { - func localized() -> String { - let localizedString = NSLocalizedString(self, comment: "") - return localizedString - } -} diff --git a/NearbyShareFramework/Connection/NearbyConnectionManager.swift b/NearbyShareFramework/Connection/NearbyConnectionManager.swift deleted file mode 100644 index c1f29ee..0000000 --- a/NearbyShareFramework/Connection/NearbyConnectionManager.swift +++ /dev/null @@ -1,323 +0,0 @@ -// -// NearbyConnectionManager.swift -// QuickDrop -// -// Created by Grishka on 08.04.2023. -// - -import Foundation -import Network -import System - - -public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearbyConnectionDelegate, OutboundNearbyConnectionDelegate { - - private let sleepManager = SleepManager.shared - private var tcpListener: NWListener - private var mdnsServices: [NetService] = [] - private var incomingConnections: [String: InboundNearbyConnection] = [:] - private var foundServices: [String: FoundServiceInfo] = [:] - private var shareExtensionDelegates: [ShareExtensionDelegate] = [] - private var outgoingTransfers: [String: OutgoingTransferInfo] = [:] - private var startedDeviceDiscovery = false - private var browsers: [NWBrowser] = [] - private let serviceTypes = ["_FC9F5ED42C8A._tcp."] - - public let endpointID: [UInt8] = generateEndpointID() - public var mainAppDelegate: (any MainAppDelegate)? - public static let shared = NearbyConnectionManager() - - - override init() { - tcpListener = try! NWListener(using: NWParameters(tls: .none)) - super.init() - } - - - public func becomeVisible() { - startTCPListener() - } - - - private func startTCPListener() { - tcpListener.stateUpdateHandler = { (state: NWListener.State) in - if case .ready = state { - self.initMDNS() - } - } - tcpListener.newConnectionHandler = { (connection: NWConnection) in - let id = UUID().uuidString - let conn = InboundNearbyConnection(connection: connection, id: id) - self.incomingConnections[id] = conn - conn.delegate = self - conn.start() - } - tcpListener.start(queue: .global(qos: .utility)) - } - - - private static func generateEndpointID() -> [UInt8] { - let userDefaultsKey = UserDefaultsKeys.endpointID.rawValue - - // Try to retrieve from UserDefaults - if let savedString = UserDefaults.standard.string(forKey: userDefaultsKey), - let savedData = savedString.data(using: .utf8) - { - return [UInt8](savedData) - } - - // Generate a new random ID - var id: [UInt8] = [] - let alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".compactMap { UInt8($0.asciiValue!) } - for _ in 0 ..< 4 { - id.append(alphabet[Int.random(in: 0 ..< alphabet.count)]) - } - - // Save to UserDefaults as String - let idString = String(bytes: id, encoding: .utf8) ?? "" - UserDefaults.standard.set(idString, forKey: userDefaultsKey) - - return id - } - - - private func initMDNS() { - let nameBytes: [UInt8] = [ - 0x23, // PCP - endpointID[0], endpointID[1], endpointID[2], endpointID[3], - 0xFC, 0x9F, 0x5E, // Service ID hash - 0, 0, - ] - - let name = Data(nameBytes).urlSafeBase64EncodedString() - let endpointInfo = EndpointInfo(name: Host.current().localizedName!, deviceType: .computer) - - let port = Int32(tcpListener.port!.rawValue) - - mdnsServices = serviceTypes.map { serviceType in - let service = NetService(domain: "", type: serviceType, name: name, port: port) - service.delegate = self - service.setTXTRecord(NetService.data(fromTXTRecord: [ - "n": endpointInfo.serialize().urlSafeBase64EncodedString().data(using: .utf8)!, - ])) - service.publish() - return service - } - } - - - func obtainUserConsent(for transfer: TransferMetadata, from device: RemoteDeviceInfo, connection _: InboundNearbyConnection) { - mainAppDelegate?.obtainUserConsent(for: transfer, from: device) - } - - - func connectionWasTerminated(connection: InboundNearbyConnection, error: Error?) { - incomingConnections.removeValue(forKey: connection.id) - - if !connection.wasRejected { - mainAppDelegate?.incomingTransfer(id: connection.id, didFinishWith: error) - } - } - - - public func submitUserConsent(transferID: String, accept: Bool, storeInTemp: Bool = false) { - guard let conn = incomingConnections[transferID] else { return } - - log("[NearbyConnectionManager] Submitting user consent for transfer, accepted: \(accept), store in temp: \(storeInTemp)") - conn.submitUserConsent(accepted: accept, storeInTemp: storeInTemp) - } - - - public func startDeviceDiscovery() { - - if !startedDeviceDiscovery { - startedDeviceDiscovery = true - foundServices.removeAll() - - log("[NearbyConnectionManager] Starting device discovery") - - if browsers.isEmpty { - for type in serviceTypes { - log("[NearbyConnectionManager] Starting browser for type \(type)") - - let browser = NWBrowser(for: .bonjourWithTXTRecord(type: type, domain: nil), using: .tcp) - browser.browseResultsChangedHandler = { _, changes in - for change in changes { - switch change { - case let .added(res): - self.addFoundDevice(service: res) - case let .removed(res): - self.removeFoundDevice(service: res) - default: - log("[NearbyConnectionManager] Ignoring change \(change)") - } - } - } - browser.start(queue: .main) - browsers.append(browser) - } - } - } - } - - - public func stopDeviceDiscovery() { - if startedDeviceDiscovery { - - log("Stopping device discovery") - - for browser in browsers { - browser.cancel() - } - - browsers.removeAll() - startedDeviceDiscovery = false - } - } - - - public func addShareExtensionDelegate(_ delegate: ShareExtensionDelegate) { - shareExtensionDelegates.append(delegate) - for service in foundServices.values { - guard let device = service.device else { continue } - delegate.addDevice(device: device) - } - } - - - public func removeShareExtensionDelegate(_ delegate: ShareExtensionDelegate) { - shareExtensionDelegates.removeAll(where: { $0 === delegate }) - } - - - public func cancelOutgoingTransfer(id: String) { - guard let transfer = outgoingTransfers[id] else { return } - transfer.connection.cancel() - } - - - private func endpointID(for service: NWBrowser.Result) -> String? { - guard case let NWEndpoint.service(name: serviceName, type: _, domain: _, interface: _) = service.endpoint else { return nil } - guard let nameData = Data.dataFromUrlSafeBase64(serviceName) else { return nil } - guard nameData.count >= 10 else { return nil } - let pcp = nameData[0] - guard pcp == 0x23 else { return nil } - let endpointID = String(data: nameData.subdata(in: 1 ..< 5), encoding: .ascii)! - let serviceIDHash = nameData.subdata(in: 5 ..< 8) - guard serviceIDHash == Data([0xFC, 0x9F, 0x5E]) else { return nil } - return endpointID - } - - - private func addFoundDevice(service: NWBrowser.Result) { - log("[NearbyConnectionManager] Found Service \(service)") - for interface in service.interfaces { - if case .loopback = interface.type { - return - } - } - guard let endpointID = endpointID(for: service) else { return } - var foundService = FoundServiceInfo(service: service) - - guard case let NWBrowser.Result.Metadata.bonjour(txtRecord) = service.metadata else { return } - guard let endpointInfoEncoded = txtRecord.dictionary["n"] else { return } - guard let endpointInfo = Data.dataFromUrlSafeBase64(endpointInfoEncoded) else { return } - guard endpointInfo.count >= 19 else { return } - let deviceType = RemoteDeviceInfo.DeviceType.fromRawValue(value: (Int(endpointInfo[0]) >> 1) & 7) - let deviceNameLength = Int(endpointInfo[17]) - guard endpointInfo.count >= deviceNameLength + 17 else { return } - guard let deviceName = String(data: endpointInfo.subdata(in: 18 ..< (18 + deviceNameLength)), encoding: .utf8) else { return } - - let deviceInfo = RemoteDeviceInfo(name: deviceName, type: deviceType, id: endpointID) - foundService.device = deviceInfo - foundServices[endpointID] = foundService - for delegate in shareExtensionDelegates { - delegate.addDevice(device: deviceInfo) - } - } - - - private func removeFoundDevice(service: NWBrowser.Result) { - guard let endpointID = endpointID(for: service) else { return } - guard let _ = foundServices.removeValue(forKey: endpointID) else { return } - for delegate in shareExtensionDelegates { - delegate.removeDevice(id: endpointID) - } - } - - - public func startOutgoingTransfer(deviceID: String, delegate: ShareExtensionDelegate, urls: [URL], textToSend: String?) { - guard let info = foundServices[deviceID] else { return } - let tcp = NWProtocolTCP.Options() - tcp.noDelay = true - let nwconn = NWConnection(to: info.service.endpoint, using: NWParameters(tls: .none, tcp: tcp)) - let conn = OutboundNearbyConnection(connection: nwconn, id: deviceID, urlsToSend: urls, textToSend: textToSend) - conn.delegate = self - let transfer = OutgoingTransferInfo(service: info.service, device: info.device!, connection: conn, delegate: delegate) - outgoingTransfers[deviceID] = transfer - conn.start() - } - - - func outboundConnectionWasEstablished(connection: OutboundNearbyConnection) { - guard let transfer = outgoingTransfers[connection.id] else { return } - DispatchQueue.main.async { - transfer.delegate.connectionWasEstablished(pinCode: connection.pinCode!) - } - } - - - func outboundConnectionTransferAccepted(connection: OutboundNearbyConnection) { - guard let transfer = outgoingTransfers[connection.id] else { return } - DispatchQueue.main.async { - transfer.delegate.transferAccepted() - } - } - - - func outboundConnection(connection: OutboundNearbyConnection, transferProgress: Double) { - guard let transfer = outgoingTransfers[connection.id] else { return } - DispatchQueue.main.async { - transfer.delegate.transferProgress(progress: transferProgress) - } - } - - - func outboundConnection(connection: OutboundNearbyConnection, failedWithError: Error) { - guard let transfer = outgoingTransfers[connection.id] else { return } - DispatchQueue.main.async { - transfer.delegate.connectionFailed(with: failedWithError) - } - outgoingTransfers.removeValue(forKey: connection.id) - } - - - func outboundConnectionTransferFinished(connection: OutboundNearbyConnection) { - guard let transfer = outgoingTransfers[connection.id] else { return } - DispatchQueue.main.async { - transfer.delegate.transferFinished() - } - outgoingTransfers.removeValue(forKey: connection.id) - } - - - public func getActiveConnectionsCount() -> Int { - return incomingConnections.count + outgoingTransfers.count - } - - - // -- MARK: - Internal Data Model - - private struct FoundServiceInfo { - let service: NWBrowser.Result - var device: RemoteDeviceInfo? - } - - - private struct OutgoingTransferInfo { - let service: NWBrowser.Result - let device: RemoteDeviceInfo - let connection: OutboundNearbyConnection - let delegate: ShareExtensionDelegate - } -} diff --git a/NearbyShareFramework/Connection/OutboundNearbyConnection.swift b/NearbyShareFramework/Connection/OutboundNearbyConnection.swift deleted file mode 100644 index 7c83375..0000000 --- a/NearbyShareFramework/Connection/OutboundNearbyConnection.swift +++ /dev/null @@ -1,497 +0,0 @@ -// -// OutboundNearbyConnection.swift -// NearbyShare -// -// Created by Grishka on 23.09.2023. -// - -import CommonCrypto -import CryptoKit -import Foundation -import Network -import System -import UniformTypeIdentifiers -import BigInt -import SwiftECC - - -class OutboundNearbyConnection: NearbyConnection { - private var currentState: State = .initial - private let urlsToSend: [URL] - private let textToSend: String? - private var ukeyClientFinishMsgData: Data? - private var queue: [OutgoingFileTransfer] = [] - private var currentTransfer: OutgoingFileTransfer? - private var totalBytesToSend: Int64 = 0 - private var totalBytesSent: Int64 = 0 - private var cancelled: Bool = false - private var textPayloadID: Int64 = 0 - - public var delegate: OutboundNearbyConnectionDelegate? - - enum State { - case initial, sentUkeyClientInit, sentUkeyClientFinish, sentPairedKeyEncryption, sentPairedKeyResult, sentIntroduction, sendingFiles - } - - init(connection: NWConnection, id: String, urlsToSend: [URL], textToSend: String?) { - - self.urlsToSend = urlsToSend - self.textToSend = textToSend - - super.init(connection: connection, id: id) - - if hasURL() || textToSend != nil { - textPayloadID = Int64.random(in: Int64.min ... Int64.max) - } - } - - - deinit { - if let transfer = currentTransfer, let handle = transfer.handle { - try? handle.close() - } - for transfer in queue { - if let handle = transfer.handle { - try? handle.close() - } - } - } - - - override func disconnect() { - super.disconnect() - - if let error = lastError { - DispatchQueue.main.async { - self.delegate?.outboundConnection(connection: self, failedWithError: error) - } - } - } - - - public func cancel() { - cancelled = true - if encryptionDone { - var cancel = Sharing_Nearby_Frame() - cancel.version = .v1 - cancel.v1 = Sharing_Nearby_V1Frame() - cancel.v1.type = .cancel - try? sendTransferSetupFrame(cancel) - } - try? sendDisconnectionAndDisconnect() - } - - - override func connectionReady() { - super.connectionReady() - do { - try sendConnectionRequest() - try sendUkey2ClientInit() - } catch { - lastError = error - protocolError() - } - } - - - override func isServer() -> Bool { - return false - } - - - override func processReceivedFrame(frameData: Data) { - - if currentState != .sendingFiles { - log("[OutboundNearbyConnection] Received frame in state \(currentState)...") - } - - do { - switch currentState { - case .initial: - protocolError() - case .sentUkeyClientInit: - try processUkey2ServerInit(frame: Securegcm_Ukey2Message(serializedBytes: frameData), raw: frameData) - case .sentUkeyClientFinish: - try processConnectionResponse(frame: Location_Nearby_Connections_OfflineFrame(serializedBytes: frameData)) - default: - let smsg = try Securemessage_SecureMessage(serializedBytes: frameData) - try decryptAndProcessReceivedSecureMessage(smsg) - } - } catch { - - log("[OutboundNearbyConnection] Error occured while processing frame with data \(frameData.hex): \(error)") - - if case NearbyError.ukey2 = error { - // do nothing - } - else if currentState == .sentUkeyClientInit { - sendUkey2Alert(type: .badMessage) - } - lastError = error - protocolError() - } - } - - - override func processTransferSetupFrame(_ frame: Sharing_Nearby_Frame) throws { - - if frame.hasV1 && frame.v1.hasType, case .cancel = frame.v1.type { - - log("[OutboundNearbyConnection] Transfer canceled") - try sendDisconnectionAndDisconnect() - delegate?.outboundConnection(connection: self, failedWithError: NearbyError.canceled(reason: .userCanceled)) - return - } - - switch currentState { - case .sentPairedKeyEncryption: - try processPairedKeyEncryption(frame: frame) - case .sentPairedKeyResult: - try processPairedKeyResult(frame: frame) - case .sentIntroduction: - try processConsent(frame: frame) - case .sendingFiles: - break - default: - assertionFailure("[OutboundNearbyConnection] Unexpected state \(currentState)") - } - } - - - override func protocolError() { - super.protocolError() - delegate?.outboundConnection(connection: self, failedWithError: lastError!) - } - - - private func sendConnectionRequest() throws { - var frame = Location_Nearby_Connections_OfflineFrame() - frame.version = .v1 - frame.v1 = Location_Nearby_Connections_V1Frame() - frame.v1.type = .connectionRequest - frame.v1.connectionRequest = Location_Nearby_Connections_ConnectionRequestFrame() - frame.v1.connectionRequest.endpointID = String(bytes: NearbyConnectionManager.shared.endpointID, encoding: .ascii)! - frame.v1.connectionRequest.endpointName = Host.current().localizedName! - let endpointInfo = EndpointInfo(name: Host.current().localizedName!, deviceType: .computer) - frame.v1.connectionRequest.endpointInfo = endpointInfo.serialize() - frame.v1.connectionRequest.mediums = [.wifiLan] - try sendFrameAsync(frame.serializedData()) - } - - - private func sendUkey2ClientInit() throws { - let domain = Domain.instance(curve: .EC256r1) - let (pubKey, privKey) = domain.makeKeyPair() - publicKey = pubKey - privateKey = privKey - - var finishFrame = Securegcm_Ukey2Message() - finishFrame.messageType = .clientFinish - var finish = Securegcm_Ukey2ClientFinished() - var pkey = Securemessage_GenericPublicKey() - pkey.type = .ecP256 - pkey.ecP256PublicKey = Securemessage_EcP256PublicKey() - pkey.ecP256PublicKey.x = Data(pubKey.w.x.asSignedBytes()) - pkey.ecP256PublicKey.y = Data(pubKey.w.y.asSignedBytes()) - finish.publicKey = try pkey.serializedData() - finishFrame.messageData = try finish.serializedData() - ukeyClientFinishMsgData = try finishFrame.serializedData() - - var frame = Securegcm_Ukey2Message() - frame.messageType = .clientInit - - var clientInit = Securegcm_Ukey2ClientInit() - clientInit.version = 1 - clientInit.random = Data.randomData(length: 32) - clientInit.nextProtocol = "AES_256_CBC-HMAC_SHA256" - var sha = SHA512() - sha.update(data: ukeyClientFinishMsgData!) - var commitment = Securegcm_Ukey2ClientInit.CipherCommitment() - commitment.commitment = Data(sha.finalize()) - commitment.handshakeCipher = .p256Sha512 - clientInit.cipherCommitments.append(commitment) - frame.messageData = try clientInit.serializedData() - - ukeyClientInitMsgData = try frame.serializedData() - sendFrameAsync(ukeyClientInitMsgData!) - currentState = .sentUkeyClientInit - } - - - private func processUkey2ServerInit(frame: Securegcm_Ukey2Message, raw: Data) throws { - ukeyServerInitMsgData = raw - guard frame.messageType == .serverInit else { - sendUkey2Alert(type: .badMessageType) - log("[OutboundNearbyConnection] Invalid message type: \(frame.messageType)") - throw NearbyError.ukey2 - } - let serverInit = try Securegcm_Ukey2ServerInit(serializedBytes: frame.messageData) - guard serverInit.version == 1 else { - sendUkey2Alert(type: .badVersion) - log("[OutboundNearbyConnection] Invalid version: \(serverInit.version)") - throw NearbyError.ukey2 - } - guard serverInit.random.count == 32 else { - sendUkey2Alert(type: .badRandom) - log("[OutboundNearbyConnection] Invalid random: \(serverInit.random.count)") - throw NearbyError.ukey2 - } - guard serverInit.handshakeCipher == .p256Sha512 else { - sendUkey2Alert(type: .badHandshakeCipher) - log("[OutboundNearbyConnection] Invalid handshake cipher: \(serverInit.handshakeCipher)") - throw NearbyError.ukey2 - } - - let serverKey = try Securemessage_GenericPublicKey(serializedBytes: serverInit.publicKey) - try finalizeKeyExchange(peerKey: serverKey) - sendFrameAsync(ukeyClientFinishMsgData!) - currentState = .sentUkeyClientFinish - - var resp = Location_Nearby_Connections_OfflineFrame() - resp.version = .v1 - resp.v1 = Location_Nearby_Connections_V1Frame() - resp.v1.type = .connectionResponse - resp.v1.connectionResponse = Location_Nearby_Connections_ConnectionResponseFrame() - resp.v1.connectionResponse.response = .accept - resp.v1.connectionResponse.status = 0 - resp.v1.connectionResponse.osInfo = Location_Nearby_Connections_OsInfo() - resp.v1.connectionResponse.osInfo.type = .apple - try sendFrameAsync(resp.serializedData()) - - encryptionDone = true - delegate?.outboundConnectionWasEstablished(connection: self) - } - - - private func processConnectionResponse(frame: Location_Nearby_Connections_OfflineFrame) throws { - guard frame.version == .v1 else { throw NearbyError.protocolError("Unexpected offline frame version \(frame.version)") } - guard frame.v1.type == .connectionResponse else { throw NearbyError.protocolError("Unexpected frame type \(frame.v1.type)") } - guard frame.v1.connectionResponse.response == .accept else { throw NearbyError.protocolError("Connection was rejected by recipient") } - - var pairedEncryption = Sharing_Nearby_Frame() - pairedEncryption.version = .v1 - pairedEncryption.v1 = Sharing_Nearby_V1Frame() - pairedEncryption.v1.type = .pairedKeyEncryption - pairedEncryption.v1.pairedKeyEncryption = Sharing_Nearby_PairedKeyEncryptionFrame() - pairedEncryption.v1.pairedKeyEncryption.secretIDHash = Data.randomData(length: 6) - pairedEncryption.v1.pairedKeyEncryption.signedData = Data.randomData(length: 72) - try sendTransferSetupFrame(pairedEncryption) - - currentState = .sentPairedKeyEncryption - } - - - private func processPairedKeyEncryption(frame: Sharing_Nearby_Frame) throws { - guard frame.hasV1, frame.v1.hasPairedKeyEncryption else { throw NearbyError.requiredFieldMissing("sharingNearbyFrame.v1.pairedKeyEncryption") } - var pairedResult = Sharing_Nearby_Frame() - pairedResult.version = .v1 - pairedResult.v1 = Sharing_Nearby_V1Frame() - pairedResult.v1.type = .pairedKeyResult - pairedResult.v1.pairedKeyResult = Sharing_Nearby_PairedKeyResultFrame() - pairedResult.v1.pairedKeyResult.status = .unable - try sendTransferSetupFrame(pairedResult) - currentState = .sentPairedKeyResult - } - - - private func processPairedKeyResult(frame: Sharing_Nearby_Frame) throws { - guard frame.hasV1, frame.v1.hasPairedKeyResult else { throw NearbyError.requiredFieldMissing("sharingNearbyFrame.v1.pairedKeyResult") } - - var introduction = Sharing_Nearby_Frame() - introduction.version = .v1 - introduction.v1.type = .introduction - - if let textToSend = textToSend { - var meta = Sharing_Nearby_TextMetadata() - meta.type = .text - meta.textTitle = textToSend - meta.size = Int64(textToSend.utf8.count) - meta.payloadID = textPayloadID - introduction.v1.introduction.textMetadata.append(meta) - - } else if hasURL() { - var meta = Sharing_Nearby_TextMetadata() - meta.type = .url - meta.textTitle = urlsToSend[0].host ?? "URL" - meta.size = Int64(urlsToSend[0].absoluteString.utf8.count) - meta.payloadID = textPayloadID - introduction.v1.introduction.textMetadata.append(meta) - } else { - for url in urlsToSend { - guard url.isFileURL else { continue } - var meta = Sharing_Nearby_FileMetadata() - meta.name = OutboundNearbyConnection.sanitizeFileName(name: url.lastPathComponent) - let attrs = try FileManager.default.attributesOfItem(atPath: url.path) - meta.size = (attrs[FileAttributeKey.size] as! NSNumber).int64Value - let typeID = try? url.resourceValues(forKeys: [.typeIdentifierKey]).typeIdentifier - meta.mimeType = "application/octet-stream" - if let typeID = typeID { - let type = UTType(typeID) - if let type = type, let mimeType = type.preferredMIMEType { - meta.mimeType = mimeType - } - } - if meta.mimeType.starts(with: "image/") { - meta.type = .image - } else if meta.mimeType.starts(with: "video/") { - meta.type = .video - } else if meta.mimeType.starts(with: "audio/") { - meta.type = .audio - } else if url.pathExtension.lowercased() == "apk" { - meta.type = .app - } else { - meta.type = .unknown - } - meta.payloadID = Int64.random(in: Int64.min ... Int64.max) - try queue.append(OutgoingFileTransfer(url: url, payloadID: meta.payloadID, handle: FileHandle(forReadingFrom: url), totalBytes: meta.size, currentOffset: 0)) - introduction.v1.introduction.fileMetadata.append(meta) - totalBytesToSend += meta.size - } - } - log("[OutboundNearbyConnection] Sent introduction: \(introduction)") - try sendTransferSetupFrame(introduction) - - currentState = .sentIntroduction - } - - - private func processConsent(frame: Sharing_Nearby_Frame) throws { - guard frame.version == .v1, frame.v1.type == .response else { throw NearbyError.requiredFieldMissing("sharingNearbyFrame.v1.type==response") } - switch frame.v1.connectionResponse.status { - case .accept: - currentState = .sendingFiles - delegate?.outboundConnectionTransferAccepted(connection: self) - - if let textToSend = textToSend { - try sendText(text: textToSend) - } - if hasURL() { - try sendURL() - } else { - try sendNextFileChunk() - } - case .reject, .unknown: - delegate?.outboundConnection(connection: self, failedWithError: NearbyError.canceled(reason: .userRejected)) - try sendDisconnectionAndDisconnect() - case .notEnoughSpace: - delegate?.outboundConnection(connection: self, failedWithError: NearbyError.canceled(reason: .notEnoughSpace)) - try sendDisconnectionAndDisconnect() - case .timedOut: - delegate?.outboundConnection(connection: self, failedWithError: NearbyError.canceled(reason: .timedOut)) - try sendDisconnectionAndDisconnect() - case .unsupportedAttachmentType: - delegate?.outboundConnection(connection: self, failedWithError: NearbyError.canceled(reason: .unsupportedType)) - try sendDisconnectionAndDisconnect() - } - } - - - private func hasURL() -> Bool { - urlsToSend.count == 1 && !urlsToSend[0].isFileURL - } - - - private func sendURL() throws { - try sendText(text: urlsToSend[0].absoluteString) - } - - - private func sendText(text: String) throws { - try sendBytesPayload(data: Data(text.utf8), id: textPayloadID) - delegate?.outboundConnectionTransferFinished(connection: self) - try sendDisconnectionAndDisconnect() - } - - - private func sendNextFileChunk() throws { - if cancelled { - return - } - if currentTransfer == nil || currentTransfer?.currentOffset == currentTransfer?.totalBytes { - if currentTransfer != nil && currentTransfer?.handle != nil { - try currentTransfer?.handle?.close() - } - if queue.isEmpty { - log("[OutboundNearbyConnection] Disconnecting because all files have been transferred") - try sendDisconnectionAndDisconnect() - delegate?.outboundConnectionTransferFinished(connection: self) - return - } - currentTransfer = queue.removeFirst() - } - - let fileBuffer: Data - guard let _fileBuffer = try currentTransfer!.handle!.read(upToCount: 512 * 1024) else { - throw NearbyError.inputOutput - } - fileBuffer = _fileBuffer - - var transfer = Location_Nearby_Connections_PayloadTransferFrame() - transfer.packetType = .data - transfer.payloadChunk.offset = currentTransfer!.currentOffset - transfer.payloadChunk.flags = 0 - transfer.payloadChunk.body = fileBuffer - transfer.payloadHeader.id = currentTransfer!.payloadID - transfer.payloadHeader.type = .file - transfer.payloadHeader.totalSize = Int64(currentTransfer!.totalBytes) - transfer.payloadHeader.isSensitive = false - currentTransfer!.currentOffset += Int64(fileBuffer.count) - - var wrapper = Location_Nearby_Connections_OfflineFrame() - wrapper.version = .v1 - wrapper.v1 = Location_Nearby_Connections_V1Frame() - wrapper.v1.type = .payloadTransfer - wrapper.v1.payloadTransfer = transfer - try encryptAndSendOfflineFrame(wrapper, completion: { - do { - try self.sendNextFileChunk() - } catch { - self.lastError = error - self.protocolError() - } - }) - totalBytesSent += Int64(fileBuffer.count) - - // only for logging - self.bytesTransferred += Int64(fileBuffer.count) - - startInactivityTimer() - delegate?.outboundConnection(connection: self, transferProgress: Double(totalBytesSent) / Double(totalBytesToSend)) - - if currentTransfer!.currentOffset == currentTransfer!.totalBytes { - // Signal end of file (yes, all this for one bit) - var transfer = Location_Nearby_Connections_PayloadTransferFrame() - transfer.packetType = .data - transfer.payloadChunk.offset = currentTransfer!.currentOffset - transfer.payloadChunk.flags = 1 // <- this one here - transfer.payloadHeader.id = currentTransfer!.payloadID - transfer.payloadHeader.type = .file - transfer.payloadHeader.totalSize = Int64(currentTransfer!.totalBytes) - transfer.payloadHeader.isSensitive = false - - var wrapper = Location_Nearby_Connections_OfflineFrame() - wrapper.version = .v1 - wrapper.v1 = Location_Nearby_Connections_V1Frame() - wrapper.v1.type = .payloadTransfer - wrapper.v1.payloadTransfer = transfer - try encryptAndSendOfflineFrame(wrapper) - log("[OutboundNearbyConnection] Sent EOF, current transfer: \(String(describing: currentTransfer))") - } - } - - - private static func sanitizeFileName(name: String) -> String { - return name.replacingOccurrences(of: "[\\/\\\\?%\\*:\\|\"<>=]", with: "_", options: .regularExpression) - } - - - // -- MARK: - Internal Data Model - - private struct OutgoingFileTransfer { - let url: URL - let payloadID: Int64 - let handle: FileHandle? - let totalBytes: Int64 - var currentOffset: Int64 - } -} diff --git a/NearbyShareFramework/ProtobufSource/device_to_device_messages.proto b/NearbyShareFramework/ProtobufSource/device_to_device_messages.proto deleted file mode 100644 index 5600373..0000000 --- a/NearbyShareFramework/ProtobufSource/device_to_device_messages.proto +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2020 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -syntax = "proto2"; - -package securegcm; - -import "securemessage.proto"; - -option optimize_for = LITE_RUNTIME; -option java_package = "com.google.security.cryptauth.lib.securegcm"; -option java_outer_classname = "DeviceToDeviceMessagesProto"; -option objc_class_prefix = "SGCM"; - -// Used by protocols between devices -message DeviceToDeviceMessage { - // the payload of the message - optional bytes message = 1; - - // the sequence number of the message - must be increasing. - optional int32 sequence_number = 2; -} - -// sent as the first message from initiator to responder -// in an unauthenticated Diffie-Hellman Key Exchange -message InitiatorHello { - // The session public key to send to the responder - optional securemessage.GenericPublicKey public_dh_key = 1; - - // The protocol version - optional int32 protocol_version = 2 [default = 0]; -} - -// sent inside the header of the first message from the responder to the -// initiator in an unauthenticated Diffie-Hellman Key Exchange -message ResponderHello { - // The session public key to send to the initiator - optional securemessage.GenericPublicKey public_dh_key = 1; - - // The protocol version - optional int32 protocol_version = 2 [default = 0]; -} - -// Type of curve -enum Curve { ED_25519 = 1; } - -// A convenience proto for encoding curve points in affine representation -message EcPoint { - required Curve curve = 1; - - // x and y are encoded in big-endian two's complement - // client MUST verify (x,y) is a valid point on the specified curve - required bytes x = 2; - required bytes y = 3; -} - -message SpakeHandshakeMessage { - // Each flow in the protocol bumps this counter - optional int32 flow_number = 1; - - // Some (but not all) SPAKE flows send a point on an elliptic curve - optional EcPoint ec_point = 2; - - // Some (but not all) SPAKE flows send a hash value - optional bytes hash_value = 3; - - // The last flow of a SPAKE protocol can send an optional payload, - // since the key exchange is already complete on the sender's side. - optional bytes payload = 4; -} diff --git a/NearbyShareFramework/ProtobufSource/offline_wire_formats.proto b/NearbyShareFramework/ProtobufSource/offline_wire_formats.proto deleted file mode 100644 index c112004..0000000 --- a/NearbyShareFramework/ProtobufSource/offline_wire_formats.proto +++ /dev/null @@ -1,403 +0,0 @@ -// Copyright 2020 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -syntax = "proto2"; - -package location.nearby.connections; - -option optimize_for = LITE_RUNTIME; -option java_outer_classname = "OfflineWireFormatsProto"; -option java_package = "com.google.location.nearby.connections.proto"; -option objc_class_prefix = "GNCP"; - -message OfflineFrame { - enum Version { - UNKNOWN_VERSION = 0; - V1 = 1; - } - optional Version version = 1; - - // Right now there's only 1 version, but if there are more, exactly one of - // the following fields will be set. - optional V1Frame v1 = 2; -} - -message V1Frame { - enum FrameType { - UNKNOWN_FRAME_TYPE = 0; - CONNECTION_REQUEST = 1; - CONNECTION_RESPONSE = 2; - PAYLOAD_TRANSFER = 3; - BANDWIDTH_UPGRADE_NEGOTIATION = 4; - KEEP_ALIVE = 5; - DISCONNECTION = 6; - PAIRED_KEY_ENCRYPTION = 7; - } - optional FrameType type = 1; - - // Exactly one of the following fields will be set. - optional ConnectionRequestFrame connection_request = 2; - optional ConnectionResponseFrame connection_response = 3; - optional PayloadTransferFrame payload_transfer = 4; - optional BandwidthUpgradeNegotiationFrame bandwidth_upgrade_negotiation = 5; - optional KeepAliveFrame keep_alive = 6; - optional DisconnectionFrame disconnection = 7; - optional PairedKeyEncryptionFrame paired_key_encryption = 8; -} - -message ConnectionRequestFrame { - // Should always match cs/symbol:location.nearby.proto.connections.Medium - // LINT.IfChange - enum Medium { - UNKNOWN_MEDIUM = 0; - MDNS = 1 [deprecated = true]; - BLUETOOTH = 2; - WIFI_HOTSPOT = 3; - BLE = 4; - WIFI_LAN = 5; - WIFI_AWARE = 6; - NFC = 7; - WIFI_DIRECT = 8; - WEB_RTC = 9; - BLE_L2CAP = 10; - USB = 11; - } - // LINT.ThenChange(//depot/google3/third_party/nearby/proto/connections_enums.proto) - - optional string endpoint_id = 1; - optional string endpoint_name = 2; - optional bytes handshake_data = 3; - // A random number generated for each outgoing connection that is presently - // used to act as a tiebreaker when 2 devices connect to each other - // simultaneously; this can also be used for other initialization-scoped - // things in the future. - optional int32 nonce = 4; - // The mediums this device supports upgrading to. This list should be filtered - // by both the strategy and this device's individual limitations. - repeated Medium mediums = 5; - optional bytes endpoint_info = 6; - optional MediumMetadata medium_metadata = 7; - optional int32 keep_alive_interval_millis = 8; - optional int32 keep_alive_timeout_millis = 9; - // The type of {@link Device} object. - optional int32 device_type = 10 [default = 0]; - // The bytes of serialized {@link Device} object. - optional bytes device_info = 11; -} - -message ConnectionResponseFrame { - // This doesn't need to send back endpoint_id and endpoint_name (like - // the ConnectionRequestFrame does) because those have already been - // transmitted out-of-band, at the time this endpoint was discovered. - - // One of: - // - // - ConnectionsStatusCodes.STATUS_OK - // - ConnectionsStatusCodes.STATUS_CONNECTION_REJECTED. - optional int32 status = 1 [deprecated = true]; - optional bytes handshake_data = 2; - - // Used to replace the status integer parameter with a meaningful enum item. - // Map ConnectionsStatusCodes.STATUS_OK to ACCEPT and - // ConnectionsStatusCodes.STATUS_CONNECTION_REJECTED to REJECT. - // Flag: connection_replace_status_with_response_connectionResponseFrame - enum ResponseStatus { - UNKNOWN_RESPONSE_STATUS = 0; - ACCEPT = 1; - REJECT = 2; - } - optional ResponseStatus response = 3; - optional OsInfo os_info = 4; - // A bitmask value to indicate which medium supports Multiplex transmission - // feature. Each supporting medium could utilize one bit starting from the - // least significant bit in this field. eq. BT utilizes the LSB bit which 0x01 - // means bt supports multiplex while 0x00 means not. Refer to ClientProxy.java - // for the bit usages. - optional int32 multiplex_socket_bitmask = 5; - optional int32 nearby_connections_version = 6; -} - -message PayloadTransferFrame { - enum PacketType { - UNKNOWN_PACKET_TYPE = 0; - DATA = 1; - CONTROL = 2; - } - - message PayloadHeader { - enum PayloadType { - UNKNOWN_PAYLOAD_TYPE = 0; - BYTES = 1; - FILE = 2; - STREAM = 3; - } - optional int64 id = 1; - optional PayloadType type = 2; - optional int64 total_size = 3; - optional bool is_sensitive = 4; - optional string file_name = 5; - optional string parent_folder = 6; - } - - // Accompanies DATA packets. - message PayloadChunk { - enum Flags { - LAST_CHUNK = 0x1; - } - optional int32 flags = 1; - optional int64 offset = 2; - optional bytes body = 3; - } - - // Accompanies CONTROL packets. - message ControlMessage { - enum EventType { - UNKNOWN_EVENT_TYPE = 0; - PAYLOAD_ERROR = 1; - PAYLOAD_CANCELED = 2; - PAYLOAD_RECEIVED_ACK = 3; - } - - optional EventType event = 1; - optional int64 offset = 2; - } - - optional PacketType packet_type = 1; - optional PayloadHeader payload_header = 2; - - // Exactly one of the following fields will be set, depending on the type. - optional PayloadChunk payload_chunk = 3; - optional ControlMessage control_message = 4; -} - -message BandwidthUpgradeNegotiationFrame { - enum EventType { - UNKNOWN_EVENT_TYPE = 0; - UPGRADE_PATH_AVAILABLE = 1; - LAST_WRITE_TO_PRIOR_CHANNEL = 2; - SAFE_TO_CLOSE_PRIOR_CHANNEL = 3; - CLIENT_INTRODUCTION = 4; - UPGRADE_FAILURE = 5; - CLIENT_INTRODUCTION_ACK = 6; - } - - // Accompanies UPGRADE_PATH_AVAILABLE and UPGRADE_FAILURE events. - message UpgradePathInfo { - // Should always match cs/symbol:location.nearby.proto.connections.Medium - enum Medium { - UNKNOWN_MEDIUM = 0; - MDNS = 1 [deprecated = true]; - BLUETOOTH = 2; - WIFI_HOTSPOT = 3; - BLE = 4; - WIFI_LAN = 5; - WIFI_AWARE = 6; - NFC = 7; - WIFI_DIRECT = 8; - WEB_RTC = 9; - // 10 is reserved. - USB = 11; - } - - // Accompanies Medium.WIFI_HOTSPOT. - message WifiHotspotCredentials { - optional string ssid = 1; - optional string password = 2; - optional int32 port = 3; - optional string gateway = 4 [default = "0.0.0.0"]; - // This field can be a band or frequency - optional int32 frequency = 5 [default = -1]; - } - - // Accompanies Medium.WIFI_LAN. - message WifiLanSocket { - optional bytes ip_address = 1; - optional int32 wifi_port = 2; - } - - // Accompanies Medium.BLUETOOTH. - message BluetoothCredentials { - optional string service_name = 1; - optional string mac_address = 2; - } - - // Accompanies Medium.WIFI_AWARE. - message WifiAwareCredentials { - optional string service_id = 1; - optional bytes service_info = 2; - optional string password = 3; - } - - // Accompanies Medium.WIFI_DIRECT. - message WifiDirectCredentials { - optional string ssid = 1; - optional string password = 2; - optional int32 port = 3; - optional int32 frequency = 4; - optional string gateway = 5 [default = "0.0.0.0"]; - } - - // Accompanies Medium.WEB_RTC - message WebRtcCredentials { - optional string peer_id = 1; - optional LocationHint location_hint = 2; - } - - optional Medium medium = 1; - - // Exactly one of the following fields will be set. - optional WifiHotspotCredentials wifi_hotspot_credentials = 2; - optional WifiLanSocket wifi_lan_socket = 3; - optional BluetoothCredentials bluetooth_credentials = 4; - optional WifiAwareCredentials wifi_aware_credentials = 5; - optional WifiDirectCredentials wifi_direct_credentials = 6; - optional WebRtcCredentials web_rtc_credentials = 8; - - // Disable Encryption for this upgrade medium to improve throughput. - optional bool supports_disabling_encryption = 7; - - // An ack will be sent after the CLIENT_INTRODUCTION frame. - optional bool supports_client_introduction_ack = 9; - } - - // Accompanies CLIENT_INTRODUCTION events. - message ClientIntroduction { - optional string endpoint_id = 1; - optional bool supports_disabling_encryption = 2; - } - - // Accompanies CLIENT_INTRODUCTION_ACK events. - message ClientIntroductionAck {} - - optional EventType event_type = 1; - - // Exactly one of the following fields will be set. - optional UpgradePathInfo upgrade_path_info = 2; - optional ClientIntroduction client_introduction = 3; - optional ClientIntroductionAck client_introduction_ack = 4; -} - -message KeepAliveFrame { - // And ack will be sent after receiving KEEP_ALIVE frame. - optional bool ack = 1; -} - -// Informs the remote side to immediately severe the socket connection. -// Used in bandwidth upgrades to get around a race condition, but may be used -// in other situations to trigger a faster disconnection event than waiting for -// socket closed on the remote side. -message DisconnectionFrame { - // Apply safe-to-disconnect protocol if true. - optional bool request_safe_to_disconnect = 1; - - // Ack of receiving Disconnection frame will be sent to the sender - // frame. - optional bool ack_safe_to_disconnect = 2; -} - -// A paired key encryption packet sent between devices, contains signed data. -message PairedKeyEncryptionFrame { - // The encrypted data (raw authentication token for the established - // connection) in byte array format. - optional bytes signed_data = 1; -} - -message MediumMetadata { - // True if local device supports 5GHz. - optional bool supports_5_ghz = 1; - // WiFi LAN BSSID, in the form of a six-byte MAC address: XX:XX:XX:XX:XX:XX - optional string bssid = 2; - // IP address, in network byte order: the highest order byte of the address is - // in byte[0]. - optional bytes ip_address = 3; - // True if local device supports 6GHz. - optional bool supports_6_ghz = 4; - // True if local device has mobile radio. - optional bool mobile_radio = 5; - // The frequency of the WiFi LAN AP(in MHz). Or -1 is not associated with an - // AP over WiFi, -2 represents the active network uses an Ethernet transport. - optional int32 ap_frequency = 6 [default = -1]; - // Available channels on the local device. - optional AvailableChannels available_channels = 7; - // Usable WiFi Direct client channels on the local device. - optional WifiDirectCliUsableChannels wifi_direct_cli_usable_channels = 8; - // Usable WiFi LAN channels on the local device. - optional WifiLanUsableChannels wifi_lan_usable_channels = 9; - // Usable WiFi Aware channels on the local device. - optional WifiAwareUsableChannels wifi_aware_usable_channels = 10; - // Usable WiFi Hotspot STA channels on the local device. - optional WifiHotspotStaUsableChannels wifi_hotspot_sta_usable_channels = 11; -} - -// Available channels on the local device. -message AvailableChannels { - repeated int32 channels = 1 [packed = true]; -} - -// Usable WiFi Direct client channels on the local device. -message WifiDirectCliUsableChannels { - repeated int32 channels = 1 [packed = true]; -} - -// Usable WiFi LAN channels on the local device. -message WifiLanUsableChannels { - repeated int32 channels = 1 [packed = true]; -} - -// Usable WiFi Aware channels on the local device. -message WifiAwareUsableChannels { - repeated int32 channels = 1 [packed = true]; -} - -// Usable WiFi Hotspot STA channels on the local device. -message WifiHotspotStaUsableChannels { - repeated int32 channels = 1 [packed = true]; -} - -// LocationHint is used to specify a location as well as format. -message LocationHint { - // Location is the location, provided in the format specified by format. - optional string location = 1; - - // the format of location. - optional LocationStandard.Format format = 2; -} - -message LocationStandard { - enum Format { - UNKNOWN = 0; - // E164 country codes: - // https://en.wikipedia.org/wiki/List_of_country_calling_codes - // e.g. +1 for USA - E164_CALLING = 1; - - // ISO 3166-1 alpha-2 country codes: - // https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 - ISO_3166_1_ALPHA_2 = 2; - } -} - -// Device capability for OS information. -message OsInfo { - enum OsType { - UNKNOWN_OS_TYPE = 0; - ANDROID = 1; - CHROME_OS = 2; - WINDOWS = 3; - APPLE = 4; - LINUX = 100; // g3 test environment - } - - optional OsType type = 1; -} diff --git a/NearbyShareFramework/ProtobufSource/securegcm.proto b/NearbyShareFramework/ProtobufSource/securegcm.proto deleted file mode 100644 index 0325f06..0000000 --- a/NearbyShareFramework/ProtobufSource/securegcm.proto +++ /dev/null @@ -1,308 +0,0 @@ -// Copyright 2020 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -syntax = "proto2"; - -package securegcm; - -option optimize_for = LITE_RUNTIME; -option java_package = "com.google.security.cryptauth.lib.securegcm"; -option java_outer_classname = "SecureGcmProto"; -option objc_class_prefix = "SGCM"; - -// Message used only during enrollment -// Field numbers should be kept in sync with DeviceInfo in: -// java/com/google/security/cryptauth/backend/services/common/common.proto -message GcmDeviceInfo { - // This field's name does not match the one in DeviceInfo for legacy reasons. - // Consider using long_device_id and device_type instead when enrolling - // non-android devices. - optional fixed64 android_device_id = 1; - - // Used for device_address of DeviceInfo field 2, but for GCM capable devices. - optional bytes gcm_registration_id = 102; - - // Used for device_address of DeviceInfo field 2, but for iOS devices. - optional bytes apn_registration_id = 202; - - // Does the user have notifications enabled for the given device address. - optional bool notification_enabled = 203 [default = true]; - - // Used for device_address of DeviceInfo field 2, a Bluetooth Mac address for - // the device (e.g., to be used with EasyUnlock) - optional string bluetooth_mac_address = 302; - - // SHA-256 hash of the device master key (from the key exchange). - // Differs from DeviceInfo field 3, which contains the actual master key. - optional bytes device_master_key_hash = 103; - - // A SecureMessage.EcP256PublicKey - required bytes user_public_key = 4; - - // device's model name - // (e.g., an android.os.Build.MODEL or UIDevice.model) - optional string device_model = 7; - - // device's locale - optional string locale = 8; - - // The handle for user_public_key (and implicitly, a master key) - optional bytes key_handle = 9; - - // The initial counter value for the device, sent by the device - optional int64 counter = 12 [default = 0]; - - // The Operating System version on the device - // (e.g., an android.os.Build.DISPLAY or UIDevice.systemVersion) - optional string device_os_version = 13; - - // The Operating System version number on the device - // (e.g., an android.os.Build.VERSION.SDK_INT) - optional int64 device_os_version_code = 14; - - // The Operating System release on the device - // (e.g., an android.os.Build.VERSION.RELEASE) - optional string device_os_release = 15; - - // The Operating System codename on the device - // (e.g., an android.os.Build.VERSION.CODENAME or UIDevice.systemName) - optional string device_os_codename = 16; - - // The software version running on the device - // (e.g., Authenticator app version string) - optional string device_software_version = 17; - - // The software version number running on the device - // (e.g., Authenticator app version code) - optional int64 device_software_version_code = 18; - - // Software package information if applicable - // (e.g., com.google.android.apps.authenticator2) - optional string device_software_package = 19; - - // Size of the display in thousandths of an inch (e.g., 7000 mils = 7 in) - optional int32 device_display_diagonal_mils = 22; - - // For Authzen capable devices, their Authzen protocol version - optional int32 device_authzen_version = 24; - - // Not all devices have device identifiers that fit in 64 bits. - optional bytes long_device_id = 29; - - // The device manufacturer name - // (e.g., android.os.Build.MANUFACTURER) - optional string device_manufacturer = 31; - - // Used to indicate which type of device this is. - optional DeviceType device_type = 32 [default = ANDROID]; - - // Fields corresponding to screenlock type/features and hardware features - // should be numbered in the 400 range. - - // Is this device using a secure screenlock (e.g., pattern or pin unlock) - optional bool using_secure_screenlock = 400 [default = false]; - - // Is auto-unlocking the screenlock (e.g., when at "home") supported? - optional bool auto_unlock_screenlock_supported = 401 [default = false]; - - // Is auto-unlocking the screenlock (e.g., when at "home") enabled? - optional bool auto_unlock_screenlock_enabled = 402 [default = false]; - - // Does the device have a Bluetooth (classic) radio? - optional bool bluetooth_radio_supported = 403 [default = false]; - - // Is the Bluetooth (classic) radio on? - optional bool bluetooth_radio_enabled = 404 [default = false]; - - // Does the device hardware support a mobile data connection? - optional bool mobile_data_supported = 405 [default = false]; - - // Does the device support tethering? - optional bool tethering_supported = 406 [default = false]; - - // Does the device have a BLE radio? - optional bool ble_radio_supported = 407 [default = false]; - - // Is the device a "Pixel Experience" Android device? - optional bool pixel_experience = 408 [default = false]; - - // Is the device running in the ARC++ container on a chromebook? - optional bool arc_plus_plus = 409 [default = false]; - - // Is the value set in |using_secure_screenlock| reliable? On some Android - // devices, the platform API to get the screenlock state is not trustworthy. - // See b/32212161. - optional bool is_screenlock_state_flaky = 410 [default = false]; - - // A list of multi-device software features supported by the device. - repeated SoftwareFeature supported_software_features = 411; - - // A list of multi-device software features currently enabled (active) on the - // device. - repeated SoftwareFeature enabled_software_features = 412; - - // The enrollment session id this is sent with - optional bytes enrollment_session_id = 1000; - - // A copy of the user's OAuth token - optional string oauth_token = 1001; -} - -// This enum is used by iOS devices as values for device_display_diagonal_mils -// in GcmDeviceInfo. There is no good way to calculate it on those devices. -enum AppleDeviceDiagonalMils { - // This is the mils diagonal on an iPhone 5. - APPLE_PHONE = 4000; - // This is the mils diagonal on an iPad mini. - APPLE_PAD = 7900; -} - -// This should be kept in sync with DeviceType in: -// java/com/google/security/cryptauth/backend/services/common/common_enums.proto -enum DeviceType { - UNKNOWN = 0; - ANDROID = 1; - CHROME = 2; - IOS = 3; - BROWSER = 4; - OSX = 5; -} - -// MultiDevice features which may be supported and enabled on a device. See -enum SoftwareFeature { - UNKNOWN_FEATURE = 0; - BETTER_TOGETHER_HOST = 1; - BETTER_TOGETHER_CLIENT = 2; - EASY_UNLOCK_HOST = 3; - EASY_UNLOCK_CLIENT = 4; - MAGIC_TETHER_HOST = 5; - MAGIC_TETHER_CLIENT = 6; - SMS_CONNECT_HOST = 7; - SMS_CONNECT_CLIENT = 8; -} - -// A list of "reasons" that can be provided for calling server-side APIs. -// This is particularly important for calls that can be triggered by different -// kinds of events. Please try to keep reasons as generic as possible, so that -// codes can be re-used by various callers in a sensible fashion. -enum InvocationReason { - REASON_UNKNOWN = 0; - // First run of the software package invoking this call - REASON_INITIALIZATION = 1; - // Ordinary periodic actions (e.g. monthly master key rotation) - REASON_PERIODIC = 2; - // Slow-cycle periodic action (e.g. yearly keypair rotation???) - REASON_SLOW_PERIODIC = 3; - // Fast-cycle periodic action (e.g. daily sync for Smart Lock users) - REASON_FAST_PERIODIC = 4; - // Expired state (e.g. expired credentials, or cached entries) was detected - REASON_EXPIRATION = 5; - // An unexpected protocol failure occurred (so attempting to repair state) - REASON_FAILURE_RECOVERY = 6; - // A new account has been added to the device - REASON_NEW_ACCOUNT = 7; - // An existing account on the device has been changed - REASON_CHANGED_ACCOUNT = 8; - // The user toggled the state of a feature (e.g. Smart Lock enabled via BT) - REASON_FEATURE_TOGGLED = 9; - // A "push" from the server caused this action (e.g. a sync tickle) - REASON_SERVER_INITIATED = 10; - // A local address change triggered this (e.g. GCM registration id changed) - REASON_ADDRESS_CHANGE = 11; - // A software update has triggered this - REASON_SOFTWARE_UPDATE = 12; - // A manual action by the user triggered this (e.g. commands sent via adb) - REASON_MANUAL = 13; - // A custom key has been invalidated on the device (e.g. screen lock is - // disabled). - REASON_CUSTOM_KEY_INVALIDATION = 14; - // Periodic action triggered by auth_proximity - REASON_PROXIMITY_PERIODIC = 15; -} - -enum Type { - ENROLLMENT = 0; - TICKLE = 1; - TX_REQUEST = 2; - TX_REPLY = 3; - TX_SYNC_REQUEST = 4; - TX_SYNC_RESPONSE = 5; - TX_PING = 6; - DEVICE_INFO_UPDATE = 7; - TX_CANCEL_REQUEST = 8; - - // DEPRECATED (can be re-used after Aug 2015) - PROXIMITYAUTH_PAIRING = 10; - - // The kind of identity assertion generated by a "GCM V1" device (i.e., - // an Android phone that has registered with us a public and a symmetric - // key) - GCMV1_IDENTITY_ASSERTION = 11; - - // Device-to-device communications are protected by an unauthenticated - // Diffie-Hellman exchange. The InitiatorHello message is simply the - // initiator's public DH key, and is not encoded as a SecureMessage, so - // it doesn't have a tag. - // The ResponderHello message (which is sent by the responder - // to the initiator), on the other hand, carries a payload that is protected - // by the derived shared key. It also contains the responder's - // public DH key. ResponderHelloAndPayload messages have the - // DEVICE_TO_DEVICE_RESPONDER_HELLO tag. - DEVICE_TO_DEVICE_RESPONDER_HELLO_PAYLOAD = 12; - - // Device-to-device communications are protected by an unauthenticated - // Diffie-Hellman exchange. Once the initiator and responder - // agree on a shared key (through Diffie-Hellman), they will use messages - // tagged with DEVICE_TO_DEVICE_MESSAGE to exchange data. - DEVICE_TO_DEVICE_MESSAGE = 13; - - // Notification to let a device know it should contact a nearby device. - DEVICE_PROXIMITY_CALLBACK = 14; - - // Device-to-device communications are protected by an unauthenticated - // Diffie-Hellman exchange. During device-to-device authentication, the first - // message from initiator (the challenge) is signed and put into the payload - // of the message sent back to the initiator. - UNLOCK_KEY_SIGNED_CHALLENGE = 15; - - // Specialty (corp only) features - LOGIN_NOTIFICATION = 101; -} - -message GcmMetadata { - required Type type = 1; - optional int32 version = 2 [default = 0]; -} - -message Tickle { - // Time after which this tickle should expire - optional fixed64 expiry_time = 1; -} - -message LoginNotificationInfo { - // Time at which the server received the login notification request. - optional fixed64 creation_time = 2; - - // Must correspond to user_id in LoginNotificationRequest, if set. - optional string email = 3; - - // Host where the user's credentials were used to login, if meaningful. - optional string host = 4; - - // Location from where the user's credentials were used, if meaningful. - optional string source = 5; - - // Type of login, e.g. ssh, gnome-screensaver, or web. - optional string event_type = 6; -} diff --git a/NearbyShareFramework/ProtobufSource/securemessage.proto b/NearbyShareFramework/ProtobufSource/securemessage.proto deleted file mode 100644 index 5118d35..0000000 --- a/NearbyShareFramework/ProtobufSource/securemessage.proto +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright 2020 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Proto definitions for SecureMessage format - -syntax = "proto2"; - -package securemessage; - -option optimize_for = LITE_RUNTIME; -option java_package = "com.google.security.cryptauth.lib.securemessage"; -option java_outer_classname = "SecureMessageProto"; -option objc_class_prefix = "SMSG"; - -message SecureMessage { - // Must contain a HeaderAndBody message - required bytes header_and_body = 1; - // Signature of header_and_body - required bytes signature = 2; -} - -// Supported "signature" schemes (both symmetric key and public key based) -enum SigScheme { - HMAC_SHA256 = 1; - ECDSA_P256_SHA256 = 2; - // Not recommended -- use ECDSA_P256_SHA256 instead - RSA2048_SHA256 = 3; -} - -// Supported encryption schemes -enum EncScheme { - // No encryption - NONE = 1; - AES_256_CBC = 2; -} - -message Header { - required SigScheme signature_scheme = 1; - required EncScheme encryption_scheme = 2; - // Identifies the verification key - optional bytes verification_key_id = 3; - // Identifies the decryption key - optional bytes decryption_key_id = 4; - // Encryption may use an IV - optional bytes iv = 5; - // Arbitrary per-protocol public data, to be sent with the plain-text header - optional bytes public_metadata = 6; - // The length of some associated data this is not sent in this SecureMessage, - // but which will be bound to the signature. - optional uint32 associated_data_length = 7 [default = 0]; -} - -message HeaderAndBody { - // Public data about this message (to be bound in the signature) - required Header header = 1; - // Payload data - required bytes body = 2; -} - -// Must be kept wire-format compatible with HeaderAndBody. Provides the -// SecureMessage code with a consistent wire-format representation that -// remains stable irrespective of protobuf implementation choices. This -// low-level representation of a HeaderAndBody should not be used by -// any code outside of the SecureMessage library implementation/tests. -message HeaderAndBodyInternal { - // A raw (wire-format) byte encoding of a Header, suitable for hashing - required bytes header = 1; - // Payload data - required bytes body = 2; -} - -// ------- -// The remainder of the messages defined here are provided only for -// convenience. They are not needed for SecureMessage proper, but are -// commonly useful wherever SecureMessage might be applied. -// ------- - -// A list of supported public key types -enum PublicKeyType { - EC_P256 = 1; - RSA2048 = 2; - // 2048-bit MODP group 14, from RFC 3526 - DH2048_MODP = 3; -} - -// A convenience proto for encoding NIST P-256 elliptic curve public keys -message EcP256PublicKey { - // x and y are encoded in big-endian two's complement (slightly wasteful) - // Client MUST verify (x,y) is a valid point on NIST P256 - required bytes x = 1; - required bytes y = 2; -} - -// A convenience proto for encoding RSA public keys with small exponents -message SimpleRsaPublicKey { - // Encoded in big-endian two's complement - required bytes n = 1; - optional int32 e = 2 [default = 65537]; -} - -// A convenience proto for encoding Diffie-Hellman public keys, -// for use only when Elliptic Curve based key exchanges are not possible. -// (Note that the group parameters must be specified separately) -message DhPublicKey { - // Big-endian two's complement encoded group element - required bytes y = 1; -} - -message GenericPublicKey { - required PublicKeyType type = 1; - optional EcP256PublicKey ec_p256_public_key = 2; - optional SimpleRsaPublicKey rsa2048_public_key = 3; - // Use only as a last resort - optional DhPublicKey dh2048_public_key = 4; -} diff --git a/NearbyShareFramework/ProtobufSource/ukey.proto b/NearbyShareFramework/ProtobufSource/ukey.proto deleted file mode 100644 index 327d8d3..0000000 --- a/NearbyShareFramework/ProtobufSource/ukey.proto +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright 2020 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -syntax = "proto2"; - -package securegcm; - -option optimize_for = LITE_RUNTIME; -option java_package = "com.google.security.cryptauth.lib.securegcm"; -option java_outer_classname = "UkeyProto"; - -message Ukey2Message { - enum Type { - UNKNOWN_DO_NOT_USE = 0; - ALERT = 1; - CLIENT_INIT = 2; - SERVER_INIT = 3; - CLIENT_FINISH = 4; - } - - optional Type message_type = 1; // Identifies message type - optional bytes message_data = 2; // Actual message, to be parsed according to - // message_type -} - -message Ukey2Alert { - enum AlertType { - // Framing errors - BAD_MESSAGE = 1; // The message could not be deserialized - BAD_MESSAGE_TYPE = 2; // message_type has an undefined value - INCORRECT_MESSAGE = 3; // message_type received does not correspond to - // expected type at this stage of the protocol - BAD_MESSAGE_DATA = 4; // Could not deserialize message_data as per - // value inmessage_type - - // ClientInit and ServerInit errors - BAD_VERSION = 100; // version is invalid; server cannot find - // suitable version to speak with client. - BAD_RANDOM = 101; // Random data is missing or of incorrect - // length - BAD_HANDSHAKE_CIPHER = 102; // No suitable handshake ciphers were found - BAD_NEXT_PROTOCOL = 103; // The next protocol is missing, unknown, or - // unsupported - BAD_PUBLIC_KEY = 104; // The public key could not be parsed - - // Other errors - INTERNAL_ERROR = 200; // An internal error has occurred. error_message - // may contain additional details for logging - // and debugging. - } - - optional AlertType type = 1; - optional string error_message = 2; -} - -enum Ukey2HandshakeCipher { - RESERVED = 0; - P256_SHA512 = 100; // NIST P-256 used for ECDH, SHA512 used for - // commitment - CURVE25519_SHA512 = 200; // Curve 25519 used for ECDH, SHA512 used for - // commitment -} - -message Ukey2ClientInit { - optional int32 version = 1; // highest supported version for rollback - // protection - optional bytes random = 2; // random bytes for replay/reuse protection - - // One commitment (hash of ClientFinished containing public key) per supported - // cipher - message CipherCommitment { - optional Ukey2HandshakeCipher handshake_cipher = 1; - optional bytes commitment = 2; - } - repeated CipherCommitment cipher_commitments = 3; - - // Next protocol that the client wants to speak. - optional string next_protocol = 4; -} - -message Ukey2ServerInit { - optional int32 version = 1; // highest supported version for rollback - // protection - optional bytes random = 2; // random bytes for replay/reuse protection - - // Selected Cipher and corresponding public key - optional Ukey2HandshakeCipher handshake_cipher = 3; - optional bytes public_key = 4; -} - -message Ukey2ClientFinished { - optional bytes public_key = 1; // public key matching selected handshake - // cipher -} diff --git a/NearbyShareFramework/ProtobufSource/wire_format.proto b/NearbyShareFramework/ProtobufSource/wire_format.proto deleted file mode 100644 index 6853e22..0000000 --- a/NearbyShareFramework/ProtobufSource/wire_format.proto +++ /dev/null @@ -1,236 +0,0 @@ -// Copyright 2020 The Chromium Authors -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// Brought from: //depot/google3/location/nearby/sharing/proto/wire_format.proto -// At CL 317565061 - -syntax = "proto2"; - -package sharing.nearby; - -// Required in Chrome. -option optimize_for = LITE_RUNTIME; - -// File metadata. Does not include the actual bytes of the file. -// NEXT_ID=6 -message FileMetadata { - enum Type { - UNKNOWN = 0; - IMAGE = 1; - VIDEO = 2; - APP = 3; - AUDIO = 4; - } - - // The human readable name of this file (eg. 'Cookbook.pdf'). - optional string name = 1; - - // The type of file (eg. 'IMAGE' from 'dog.jpg'). Specifying a type helps - // provide a richer experience on the receiving side. - optional Type type = 2 [default = UNKNOWN]; - - // The FILE payload id that will be sent as a follow up containing the actual - // bytes of the file. - optional int64 payload_id = 3; - - // The total size of the file. - optional int64 size = 4; - - // The mimeType of file (eg. 'image/jpeg' from 'dog.jpg'). Specifying a - // mimeType helps provide a richer experience on receiving side. - optional string mime_type = 5 [default = "application/octet-stream"]; - - // A uuid for the attachment. Should be unique across all attachments. - optional int64 id = 6; -} - -// NEXT_ID=5 -message TextMetadata { - enum Type { - UNKNOWN = 0; - TEXT = 1; - // Open with browsers. - URL = 2; - // Open with map apps. - ADDRESS = 3; - // Dial. - PHONE_NUMBER = 4; - } - - // The title of the text content. - optional string text_title = 2; - - // The type of text (phone number, url, address, or plain text). - optional Type type = 3 [default = UNKNOWN]; - - // The BYTE payload id that will be sent as a follow up containing the actual - // bytes of the text. - optional int64 payload_id = 4; - - // The size of the text content. - optional int64 size = 5; - - // A uuid for the attachment. Should be unique across all attachments. - optional int64 id = 6; -} - -// NEXT_ID=5 -message WifiCredentialsMetadata { - enum SecurityType { - UNKNOWN_SECURITY_TYPE = 0; - OPEN = 1; - WPA_PSK = 2; - WEP = 3; - } - - // The Wifi network name. This will be sent in introduction. - optional string ssid = 2; - - // The security type of network (OPEN, WPA_PSK, WEP). - optional SecurityType security_type = 3 [default = UNKNOWN_SECURITY_TYPE]; - - // The BYTE payload id that will be sent as a follow up containing the - // password. - optional int64 payload_id = 4; - - // A uuid for the attachment. Should be unique across all attachments. - optional int64 id = 5; -} - -// A frame used when sending messages over the wire. -// NEXT_ID=3 -message Frame { - enum Version { - UNKNOWN_VERSION = 0; - V1 = 1; - } - optional Version version = 1; - - // Right now there's only 1 version, but if there are more, exactly one of - // the following fields will be set. - optional V1Frame v1 = 2; -} - -// NEXT_ID=7 -message V1Frame { - enum FrameType { - UNKNOWN_FRAME_TYPE = 0; - INTRODUCTION = 1; - RESPONSE = 2; - PAIRED_KEY_ENCRYPTION = 3; - PAIRED_KEY_RESULT = 4; - CERTIFICATE_INFO = 5; - CANCEL = 6; - } - - optional FrameType type = 1; - - // Exactly one of the following fields will be set. - optional IntroductionFrame introduction = 2; - optional ConnectionResponseFrame connection_response = 3; - optional PairedKeyEncryptionFrame paired_key_encryption = 4; - optional PairedKeyResultFrame paired_key_result = 5; - optional CertificateInfoFrame certificate_info = 6; -} - -// An introduction packet sent by the sending side. Contains a list of files -// they'd like to share. -// NEXT_ID=4 -message IntroductionFrame { - repeated FileMetadata file_metadata = 1; - repeated TextMetadata text_metadata = 2; - // The required app package to open the content. May be null. - optional string required_package = 3; - repeated WifiCredentialsMetadata wifi_credentials_metadata = 4; -} - -// A response packet sent by the receiving side. Accepts or rejects the list of -// files. -// NEXT_ID=2 -message ConnectionResponseFrame { - enum Status { - UNKNOWN = 0; - ACCEPT = 1; - REJECT = 2; - NOT_ENOUGH_SPACE = 3; - UNSUPPORTED_ATTACHMENT_TYPE = 4; - TIMED_OUT = 5; - } - - // The receiving side's response. - optional Status status = 1; -} - -// A paired key encryption packet sent between devices, contains signed data. -// NEXT_ID=3 -message PairedKeyEncryptionFrame { - // The encrypted data in byte array format. - optional bytes signed_data = 1; - - // The hash of a certificate id. - optional bytes secret_id_hash = 2; - - // An optional encrypted data in byte array format. - optional bytes optional_signed_data = 3; -} - -// A paired key verification result packet sent between devices. -// NEXT_ID=2 -message PairedKeyResultFrame { - enum Status { - UNKNOWN = 0; - SUCCESS = 1; - FAIL = 2; - UNABLE = 3; - } - - // The verification result. - optional Status status = 1; -} - -// A package containing certificate info to be shared to remote device offline. -// NEXT_ID=2 -message CertificateInfoFrame { - // The public certificates to be shared with remote devices. - repeated PublicCertificate public_certificate = 1; -} - -// A public certificate from the local device. -// NEXT_ID=8 -message PublicCertificate { - // The unique id of the public certificate. - optional bytes secret_id = 1; - - // A bytes representation of a Secret Key owned by contact, to decrypt the - // metadata_key stored within the advertisement. - optional bytes authenticity_key = 2; - - // A bytes representation a public key of X509Certificate, owned by contact, - // to decrypt encrypted UKEY2 (from Nearby Connections API) as a hand shake in - // contact verification phase. - optional bytes public_key = 3; - - // The time in millis from epoch when this certificate becomes effective. - optional int64 start_time = 4; - - // The time in millis from epoch when this certificate expires. - optional int64 end_time = 5; - - // The encrypted metadata in bytes, contains personal information of the - // device/user who created this certificate. Needs to be decrypted into bytes, - // and converted back to EncryptedMetadata object to access fields. - optional bytes encrypted_metadata_bytes = 6; - - // The tag for verifying metadata_encryption_key. - optional bytes metadata_encryption_key_tag = 7; -} - -// NEXT_ID=3 -message WifiCredentials { - // Wi-Fi password. - optional string password = 1; - // True if the network is a hidden network that is not broadcasting its SSID. - // Default is false. - optional bool hidden_ssid = 2 [default = false]; -} \ No newline at end of file diff --git a/NearbyShareFramework/Util/ConnectionFailureTracker.swift b/NearbyShareFramework/Util/ConnectionFailureTracker.swift deleted file mode 100644 index d728a21..0000000 --- a/NearbyShareFramework/Util/ConnectionFailureTracker.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// ConnectionFailureTracker.swift -// QuickDrop -// -// Created by Leon Böttger on 15.05.25. -// - -import Foundation - -class ConnectionFailureTracker { - static let shared = ConnectionFailureTracker() - - private var failureTimestamps: [Date] = [] - private var lastAlertShownAt: Date? - - private let queue = DispatchQueue(label: "ConnectionFailureTrackerQueue") - - private init() {} - - func recordFailure(onTrigger: @escaping () -> Void) { - queue.async { - let now = Date() - - // Remove old timestamps older than 10 seconds - self.failureTimestamps = self.failureTimestamps.filter { now.timeIntervalSince($0) < 10 } - self.failureTimestamps.append(now) - - // Check if alert can be shown - let canShowAlert = self.failureTimestamps.count >= 2 && - (self.lastAlertShownAt == nil || now.timeIntervalSince(self.lastAlertShownAt!) >= 60) - - if canShowAlert { - self.lastAlertShownAt = now - self.failureTimestamps.removeAll() - DispatchQueue.main.async { - onTrigger() - } - } - } - } -} diff --git a/NearbyShareFramework/Util/Helpers.swift b/NearbyShareFramework/Util/Helpers.swift deleted file mode 100644 index 4ac8d26..0000000 --- a/NearbyShareFramework/Util/Helpers.swift +++ /dev/null @@ -1,100 +0,0 @@ -// -// Helpers.swift -// QuickDrop -// -// Created by Leon Böttger on 04.04.25. -// - -import Foundation - -extension Data { - func urlSafeBase64EncodedString() -> String { - return String(base64EncodedString().replacingOccurrences(of: "=", with: "").map { - if $0 == "/" { - return "_" - } else if $0 == "+" { - return "-" - } else { - return $0 - } - }) - } - - - static func randomData(length: Int) -> Data { - var data = Data(count: length) - data.withUnsafeMutableBytes { - guard SecRandomCopyBytes(kSecRandomDefault, length, $0.baseAddress!) == 0 else { fatalError() } - } - return data - } - - - static func dataFromUrlSafeBase64(_ str: String) -> Data? { - var regularB64 = String(str.map { - if $0 == "_" { - return "/" - } else if $0 == "-" { - return "+" - } else { - return $0 - } - }) - while (regularB64.count % 4) != 0 { - regularB64 = regularB64 + "=" - } - return Data(base64Encoded: regularB64, options: .ignoreUnknownCharacters) - } - - - var hex: String { - return map { String(format: "%02hhx", $0) }.joined() - } -} - - -extension String { - var dataFromHex: Data { - var data = Data(capacity: count / 2) - var index = startIndex - while index < endIndex { - let nextIndex = self.index(index, offsetBy: 2) - if nextIndex <= endIndex, - let byte = UInt8(self[index ..< nextIndex], radix: 16) - { - data.append(byte) - } else { - break // or handle invalid hex gracefully - } - index = nextIndex - } - return data - } -} - - -public enum UserDefaultsKeys: String, CaseIterable { - case isEligibleForIap = "isEligibleForIap" - case appLaunchedBefore = "ShowedWelcomeScreen" - case plusVersion = "isPlusVersion" - case transmissionCount = "reviewRequestCountKey" - case automaticallyAcceptFiles = "automaticallyAcceptFiles" - case saveFolderBookmark = "saveFolderBookmark" - case openFinderAfterReceiving = "openFinderAfterReceiving" - case endpointID = "endpointID" -} - - -public func isPlusVersion() -> Bool { - return UserDefaults.standard.bool(forKey: UserDefaultsKeys.plusVersion.rawValue) -} - - -public func isFileTransferRestricted() -> Bool { - (!isPlusVersion() && transmissionCount() > 1) -} - - -public func transmissionCount() -> Int { - return UserDefaults.standard.integer(forKey: UserDefaultsKeys.transmissionCount.rawValue) -} diff --git a/NearbyShareFramework/Util/Logger.swift b/NearbyShareFramework/Util/Logger.swift deleted file mode 100644 index 6d35119..0000000 --- a/NearbyShareFramework/Util/Logger.swift +++ /dev/null @@ -1,133 +0,0 @@ -// -// Logger.swift -// QuickDrop -// -// Created by Leon Böttger on 26.02.25. -// - -import Foundation -import os - -fileprivate let loggerQueue = DispatchQueue(label: "loggerQueue", qos: .utility) - -/// Manages logging functionality -final class LogManager { - - /// The URL of the log file - let logFileURL: URL? - - /// Shared instance - public static let sharedInstance = LogManager() - - @Published var loggingEnabled = true - - - /// Private initializer - private init() { - // Use the App Group container - let directory = getAppGroupDirectory() - - // Create file name from date - let formatter = DateFormatter() - formatter.dateFormat = "dd-MM-yyyy" - let dateString = formatter.string(from: Date()) - let fileName = "\(dateString).log" - - // Full path in app group container - self.logFileURL = directory?.appendingPathComponent(fileName) - } - - - /// Writes a specified string to the log file. - func writeToFile(string: String) { - guard let logFileURL = logFileURL else { return } - - let formatter = DateFormatter() - formatter.dateFormat = "HH:mm:ss" - let timestamp = formatter.string(from: Date()) - guard let data = (timestamp + " - " + string + "\n").data(using: .utf8) else { return } - - loggerQueue.async { - let fileManager = FileManager.default - - if !fileManager.fileExists(atPath: logFileURL.path) { - // Clean up older logs - self.deleteLogs() - // Create file atomically - try? data.write(to: logFileURL, options: .atomicWrite) - return - } - - // Atomic append with file locking using FileHandle - do { - let fileHandle = try FileHandle(forWritingTo: logFileURL) - defer { try? fileHandle.close() } - - // Locking is handled by the serial dispatch queue - try fileHandle.seekToEnd() - try fileHandle.write(contentsOf: data) - } catch { - print("[Logger] Failed to write to log file: \(error.localizedDescription)") - } - } - } - - - /// Gets the contents of the log file. - func getLogString() -> String { - guard let logFileURL = logFileURL else { return "" } - do { - return try String(contentsOf: logFileURL, encoding: .utf8) - } catch { - log("[LUI] Error reading file: \(error.localizedDescription)") - return "" - } - } - - - /// Deletes all log files in the shared container. - func deleteLogs() { - log("[LUI] Deleting all logs...") - - guard let directory = getAppGroupDirectory() else { return } - let fileManager = FileManager.default - - guard let allFiles = try? fileManager.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil) else { - return - } - - let logFiles = allFiles.filter { $0.pathExtension == "log" } - for file in logFiles { - do { - log("[LUI] Removing \(file.lastPathComponent)") - try fileManager.removeItem(at: file) - } catch { - print("[Logger] Failed to delete file: \(error.localizedDescription)") - } - } - } -} - - -/// Logs a specified string -public func log(_ text: String) { - let logger = LogManager.sharedInstance - - if logger.loggingEnabled { - loggerQueue.async { - print(text) -#if !os(tvOS) - let isAppExtension = Bundle.main.bundlePath.hasSuffix(".appex") - let prefix = isAppExtension ? "[EXT]" : "[APP]" - logger.writeToFile(string: "\(prefix) \(text)") -#endif - } - } -} - - -/// Returns the shared App Group directory -fileprivate func getAppGroupDirectory() -> URL? { - let groupIdentifier = "group.com.leonboettger.neardrop" - return FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: groupIdentifier) -} diff --git a/NearbyShareFramework/Util/SaveFilesManager.swift b/NearbyShareFramework/Util/SaveFilesManager.swift deleted file mode 100644 index 73b3e71..0000000 --- a/NearbyShareFramework/Util/SaveFilesManager.swift +++ /dev/null @@ -1,220 +0,0 @@ -// -// SaveFilesManager.swift -// QuickDrop -// -// Created by Leon Böttger on 26.07.25. -// - -import Foundation -import AppKit -import ImageIO - -public class SaveFilesManager { - - private init() { - // remove old temp directory - - let tempPath = FileManager.default.temporaryDirectory - let fileManager = FileManager.default - - do { - let contents = try fileManager.contentsOfDirectory(at: tempPath, includingPropertiesForKeys: nil) - - var didSomething = false - - for item in contents { - didSomething = true - try fileManager.removeItem(at: item) - } - - if didSomething { - log("Temporary directory cleared.") - } - } catch { - log("Failed to list contents of temp directory: \(error)") - } - } - - public static let shared = SaveFilesManager() - - let tempDirectory: URL = FileManager.default.temporaryDirectory.appendingPathComponent("Pending") - private var securityScopeUrl: URL? - - private var filesFinishedDownloading = [URL]() - private var filesFinishedDownloadingSinceLastRun = [URL]() - - - public func registerFileFinishedDownloading(_ fileURL: URL) { - filesFinishedDownloading.append(fileURL) - filesFinishedDownloadingSinceLastRun.append(fileURL) - - // read out EXIF timestamp and manually set file creation date to it - applyEXIFTimestamps(at: fileURL) - } - - - public func movePendingFilesToTarget() { - - if isPlusVersion() { - do { - let fileManager = FileManager.default - let files = try fileManager.contentsOfDirectory(at: tempDirectory, includingPropertiesForKeys: nil) - - let target = getSaveDirectory() - var loggedExecution = false - - for file in files { - - if !loggedExecution { - log("Moving pending files to target directory") - loggedExecution = true - } - - let fileName = file.lastPathComponent - let destinationURL = target.appendingPathComponent(fileName) - - if !filesFinishedDownloading.contains(destinationURL) { - log("File \(file) not finished downloading, skipping") - continue - } - - log("Moving file: \(file.lastPathComponent) to \(destinationURL.lastPathComponent)") - - do { - try fileManager.copyItem(at: file, to: destinationURL) - - let progress = Progress() - progress.fileURL = destinationURL - progress.totalUnitCount = 10 - progress.kind = .file - progress.isPausable = false - progress.publish() - - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - progress.completedUnitCount = 10 - progress.unpublish() - } - - try fileManager.removeItem(at: file) - } - catch { - log("Error moving file: \(error)") - } - } - log("Moved all pending files to target directory") - } - catch { - // Pending directory doesn't exist or is empty - } - } - - if !filesFinishedDownloadingSinceLastRun.isEmpty && !isFileTransferRestricted() { - - if UserDefaults.standard.bool(forKey: UserDefaultsKeys.openFinderAfterReceiving.rawValue) { - log("Opening \(filesFinishedDownloadingSinceLastRun.count) file(s) in Finder.") - NSWorkspace.shared.activateFileViewerSelecting(filesFinishedDownloadingSinceLastRun) - } - - // Clear the list of finished files - filesFinishedDownloadingSinceLastRun.removeAll() - } - - stopAccessingSecurityScopedResource() - } - - - public func stopAccessingSecurityScopedResource() { - guard let url = securityScopeUrl else { - return - } - - log("Stopping access to security scoped resource: \(url)") - url.stopAccessingSecurityScopedResource() - securityScopeUrl = nil - } - - - public func getSaveDirectory() -> URL { - if let securityScopeUrl = securityScopeUrl { - log("Using existing security scope URL: \(securityScopeUrl)") - return securityScopeUrl - } - - if let bookmarkData = UserDefaults.standard.data(forKey: UserDefaultsKeys.saveFolderBookmark.rawValue) { - var isStale = false - - do { - let url = try URL(resolvingBookmarkData: bookmarkData, options: .withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale) - - if !isStale { - if url.startAccessingSecurityScopedResource() { - log("Successfully accessed security scoped resource: \(url)") - - securityScopeUrl = url - return url - } - } else { - log("Bookmark is stale, using default downloads folder.") - } - - } catch { - log("Failed to resolve bookmark: \(error), using default downloads folder.") - } - } - - do { - return try FileManager.default.url(for: .downloadsDirectory, in: .userDomainMask, appropriateFor: nil, create: true).resolvingSymlinksInPath() - } catch { - fatalError("Failed to get downloads directory: \(error)") - } - } - - - /// Returns the EXIF original date of an image file, adjusted to the current time zone. - private func exifOriginalDate(from url: URL) -> Date? { - guard let imageSource = CGImageSourceCreateWithURL(url as CFURL, nil), - let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? [CFString: Any], - let exif = properties[kCGImagePropertyExifDictionary] as? [CFString: Any], - let dateString = exif[kCGImagePropertyExifDateTimeOriginal] as? String - else { - return nil - } - - let formatter = DateFormatter() - formatter.dateFormat = "yyyy:MM:dd HH:mm:ss" - formatter.timeZone = TimeZone.current // Use the current device time zone - return formatter.date(from: dateString) - } - - - /// Applies EXIF timestamps to a given file or directory. - private func applyEXIFTimestamps(at url: URL) { - let fm = FileManager.default - var isDir: ObjCBool = false - guard fm.fileExists(atPath: url.path, isDirectory: &isDir) else { return } - - if isDir.boolValue { - // It's a directory — enumerate files - if let enumerator = fm.enumerator(at: url, includingPropertiesForKeys: [.isRegularFileKey]) { - for case let fileURL as URL in enumerator { - guard (try? fileURL.resourceValues(forKeys: [.isRegularFileKey]))?.isRegularFile == true else { continue } - applyEXIFTimestamp(to: fileURL) - } - } - } else { - // It's a single file - applyEXIFTimestamp(to: url) - } - } - - - /// Applies EXIF timestamp (if available) to a single file - private func applyEXIFTimestamp(to fileURL: URL) { - guard let exifDate = exifOriginalDate(from: fileURL) else { - return - } - - // Set the "creation date" - try? FileManager.default.setAttributes([.creationDate: exifDate], ofItemAtPath: fileURL.path) - } -} diff --git a/NearbyShareFramework/Util/SleepManager.swift b/NearbyShareFramework/Util/SleepManager.swift deleted file mode 100644 index 01da003..0000000 --- a/NearbyShareFramework/Util/SleepManager.swift +++ /dev/null @@ -1,59 +0,0 @@ -// -// SleepManager.swift -// QuickDrop -// -// Created by Leon Böttger on 03.05.25. -// - -import Foundation -import IOKit -import IOKit.pwr_mgt - -class SleepManager { - public static let shared = SleepManager() - private var assertionID: IOPMAssertionID = 0 - private var timer: Timer? - - private init() { - // Start periodic check - timer = Timer.scheduledTimer(withTimeInterval: 5.0, repeats: true) { [weak self] _ in - self?.updateSleepState() - } - - RunLoop.main.add(timer!, forMode: .common) - } - - private func disableSleep() { - if assertionID != 0 { - return - } - - log("[SleepManager] Enabling Wakelock") - - IOPMAssertionCreateWithName( - kIOPMAssertionTypePreventUserIdleDisplaySleep as CFString, - IOPMAssertionLevel(kIOPMAssertionLevelOn), - "QuickDrop Data Transfer" as CFString, - &assertionID - ) - } - - private func enableSleep() { - if assertionID == 0 { - return - } - - log("[SleepManager] Disabling Wakelock") - IOPMAssertionRelease(assertionID) - assertionID = 0 - } - - private func updateSleepState() { - let activeConnections = NearbyConnectionManager.shared.getActiveConnectionsCount() - if activeConnections > 0 { - disableSleep() - } else { - enableSleep() - } - } -} diff --git a/PROTOCOL.md b/PROTOCOL.md deleted file mode 100644 index 851bf3d..0000000 --- a/PROTOCOL.md +++ /dev/null @@ -1,210 +0,0 @@ -# Google's Nearby Share protocol - -This document describes the Nearby Share protocol as understood by me (@grishka) from Chromium sources (that are way too convoluted) and decompiled Google Services Framework apk (that, despite being a decompilation of obfuscated code, is still more helpful than Chromium sources). - -The protocol is peer-to-peer, end-to-end encrypted. Overall, it appears like it was mostly designed to run over an unreliable medium like UDP, but I've only observed it over TCP. - -Only the WiFi LAN protocol is described here because that's what I reverse engineered, but the data running over other mediums (Bluetooth, WiFi Direct, ...) is most probably largely the same. - -If you want to build your own Nearby Share *thing*, you will need: - -1. An implementation of multicast DNS (most modern OSes have it built-in) -2. A cryptography library that is capable of ECDSA key exchange, AES-CBC, HMAC, and SHA256. OpenSSL will do but is definitely an overkill. -3. A Protobuf library -4. [These Protobuf files](/NearDrop/ProtobufSource) I collected from the Chromium sources so you don't have to - -It is also very helpful to read logcat on your Android device if you're having any trouble. The logging of the Android implementation of Nearby Share is very verbose. - -### Device roles - -Since a file transfer is unidirectional, the peers are assigned roles: - -* The receiving side is the **server**. It listens on a TCP port and advertises an MDNS service. -* The sending side is the **client**. It discovers the MDNS service and connects to the server's TCP port. - -### How devices find each other - -To become visible in the sheet on Android devices, a *server* advertises an MDNS service. The **domain** is empty. The **type** is `_FC9F5ED42C8A._tcp.`. The **port** is an arbitrary TCP port on which the server accepts incoming connections. - -The **name** is the following 10 bytes encoded in URL-safe base64: - -* `0x23`, Google calls this "PCP" but I have no idea what it is -* 4-byte **endpoint ID** -* 3-byte **service ID**: `0xFC, 0x9F, 0x5E` -* 2 zero bytes that serve an unknown purpose - -The **endpoint ID** is 4 random alphanumeric characters. It identifies devices to each other. Android uses it in its logs quite extensively. - -The service also needs to have a TXT record with key `n` and the value of the following encoded in URL-safe base64 ("endpoint info"): - -* 1 byte: bit field - * 3 bits: version, set to 0 - * 1 bit: visibility, 0 = visible - * 3 bits: device type. Android uses this to pick an icon. 0 = unknown, 1 = phone, 2 = tablet, 3 = laptop - * 1 bit: reserved, set to 0 -* 16 bytes of unknown purpose. I set them to random. -* User-visible device name in UTF-8, prefixed with 1-byte length. - -Android does not advertise the MDNS service all the time regardless of the visibility setting. It waits for a BLE advertisement with the following parameters: - -* Service UUID = `fe 2c` -* Service data = `fc 12 8e 01 42 00 00 00 00 00 00 00 00 00 [10 random bytes]` - -This can't be sent from macOS because there's no API I could find that would allow setting the service data. As far as I can tell, the Android side is hardcoded to look for that prefix in the service data so there really is no way to make it work on macOS. Android sends these BLE advertisements periodically while searching for Nearby Share targets; these are also what makes the "device nearby is sharing" notification pop up. - -The service ID (FC9F...) comes from SHA256("NearbySharing") = `fc9f5ed42c8a5e9e94684076ef3bf938a809c60ad354992b0435aebbdc58b97b`. - -### High-level diagram - -```mermaid -sequenceDiagram - Client-->>Server: (Connects to advertised TCP port) - Client->>Server: Connection request - Client->>Server: UKEY2 ClientInit - Server->>Client: UKEY2 ServerInit - Client->>Server: UKEY2 ClientFinish - Server->>Client: Connection response - Client->>Server: Connection response - Note over Server, Client: All following packets are encrypted - Server->>Client: Paired key encryption - Client->>Server: Paired key encryption - Server->>Client: Paired key result - Client->>Server: Paired key result - Client->>Server: Introduction (transfer metadata) - Note over Server: Asks the user - Server->>Client: Response (accept/reject) - Client->>Server: File chunks - Client-->>Server: - Client-->>Server: - Client->>Server: Disconnection - Client-->Server: (Close TCP connection) -``` - -From the Google point of view, the "Nearby connections" part is a separate universal transport layer, over which the "Share" runs. This may explain some bizarre arrangements where you have protobuf inside protobuf inside encrypted protobuf inside protobuf. - -### Frame/packet/message types - -There are three types of packets that can appear directly ~~on the wire~~ in the air: - -1. **Offline frames.** These are the basic uint of the nearby protocol. They are used to control the connection. -2. **UKEY2 messages.** These are used for the encryption key exchange (UKEY2 is Google's bespoke algorithm for that). -3. **Secure messages.** These are used exclusively after the initial negotiation and carry other packets inside them in the encrypted form. - -Keep the protobuf files open to follow along. - -### TCP framing - -Each protobuf message sent over the TCP connection is prefixed with 4-byte big-endian (MSB first) length. - -### Initial handshake & key exchange - -(described from the server/recipient point of view) - -After the client connects to the server, it sends two packets: a "connection request" and a "UKEY2 client init". - -#### Connection request - -This is a subtype of "offline frame". -The client wants to connect to the server and tells it about itself. The only field of interest here is `endpointInfo`. It contains the device type and name. It has the same format as the `n` TXT record described above, just without the base64 encoding. - -#### UKEY2 client init - -This is a subtype of "UKEY2 message". Google's UKEY2 reference implementation [is open source](https://github.com/google/ukey2) and comes with documentation. Please refer to that repo for details on the key exchange. -This is the initial step of the key exchange for end-to-end encryption. Upon receiving this, the server generates a ECDSA key pair and sends its public key in a "server init". The server also needs to remember the raw serialized client init message for the final key derivation step. The outgoing server init message is also needed for the next step. The bytes include the entire protobuf message but do not include the int32 length prefix. - -After receiving the server init, the client completes the key derivation and sends a "client finish", containing its public key. - -#### UKEY2 client finish - -This is where the server completes the key derivation. This step is described in detail [in the Google readme](https://github.com/google/ukey2#deriving-the-authentication-string-and-the-next-protocol-secret). - -The result of the key exchange is two values: the authentication string and the next protocol secret. - -The **next protocol secret** is further processed to obtain the two 32-byte AES and two 32-byte HMAC keys used for encryption and authentication of further communications ([relevant Chromium code](https://source.chromium.org/chromium/chromium/src/+/main:third_party/securemessage/src/cpp/src/securemessage/crypto_ops.cc) and [this as well](https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:third_party/ukey2/src/src/main/cpp/src/securegcm/d2d_crypto_ops.cc)): - -Derive two 32-byte "device to device" keys using HKDF-SHA256: - -* **D2D client key**, using the **next protocol secret** for input key material, `82AA55A0D397F88346CA1CEE8D3909B95F13FA7DEB1D4AB38376B8256DA85510` for salt, and the string `client` for info -* **D2D server key**, using the same parameters, except **info** is `server` - -Next, derive the four keys you will use for the actual encryption. All four use the same value of salt, which is `SHA256("SecureMessage")`, or `BF9D2A53C63616D75DB0A7165B91C1EF73E537F2427405FA23610A4BE657642E`. These keys are from the server POV; if you're the client, they need to be swapped around (decrypt/receive shoud use the server key and vice versa). - -* **Decrypt key**: IKM = D2D client key, info = `ENC:2` -* **Receive HMAC key**: IKM = D2D client key, info = `SIG:1` -* **Encrypt key**: IKM = D2D server key, info = `ENC:2` -* **Send HMAC key**: IKM = D2D server key, info = `SIG:1` - -The key exchange is now complete. - -The **authentication string** is used for out-of-band key verification. Nearby Share doesn't use the algorithm specified by UKEY2. Instead, a 4-digit PIN code is generated using [this algorithm](https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/nearby_sharing/nearby_sharing_service_impl.cc;l=215-230;drc=9468ba955b5794fdfaa7301fce4fb70940830c88). - -#### Connection response - -After the key exchange is complete, the server and client send each other one last plaintext message: a connection response. It's a subtype of offline frame saying that the other party has accepted the connection. All the following communication is encrypted and wrapped in the payload layer. - -### The encryption layer - -The message type on the wire is always the "secure message". A secure message has two fields: header and body, and the signature. - -Header and body is a serialized `HeaderAndBody` message. Inside, there are two fields, that are (what a surprise!) header and body. The body contains the encrypted payload. The header contains the encryption scheme (must be set to `AES256_CBC`), the signature scheme (must be set to `HMAC_SHA256`), the IV (initialization vector) for AES-CBC consisting of 16 random bytes, and the public metadata. Public metadata is needed because the protocol is extensible af. It contains two fields with constant values: version that is always 1 and type that is always `DEVICE_TO_DEVICE_MESSAGE`. - -The signature is a HMAC-SHA256 of the header-and-body fields using one of the keys derived above. - -The body inside header-and-body is encrypted using AES-CBC with PKCS7 padding. After decryption it should be a valid device to device message (see securegcm.proto). If it isn't, you did something wrong. Go back and debug your key exchange code. Cryptography is messy, don't worry, no one gets it right on the first try ¯\\\_(ツ)\_/¯ - -The device to device message contains a sequence number and a message. The message is always a serialized offline frame. The sequence number is 1 for the first message, and is incremented with each message. Client and server have their own independent sequence numbers. - -### The payload layer - -![yo dawg, I heard you like protobuf...](/images/YoDawg.jpg) - -This layer allows the transfer of arbitrarily large payloads in chunks. Payloads come in two types: bytes and files. All negotiation uses bytes payloads with protobuf messages inside. The file payloads are used for actual files. - -Payload transfer frames are wrapped into offline frames of type `PAYLOAD_TRANSFER`. These are then encrypted as described above. Meaning of the important payload transfer frame fields is as follows: - -* **header**: the metadata - * **id**: the payload identifier within the connection. Allows transferring multiple payloads in parallel. You use it to keep track of multiple transfers, associate buffers and files to it, etc. - * **type**: either `BYTES` or `FILE` - * **totalSize**: self-explanatory -* **chunk**: the data itself - * **offset**: the offset at which this chunk needs to be written into the buffer or file - * **flags**: if `LAST_CHUNK` (bit 0) is set, the transfer is complete and this is the last chunk - * **body**: the data bytes themselves - -Android does this thing where it sends 2 payload transfer frames in succession for each negotiation message: the first contains the entire message, the second contains 0 bytes but has the `LAST_CHUNK` flag set. I replicated this behavior in NearDrop. - -### The "paired key encryption", whatever that is - -The client and the server send each other a "paired key encryption" frame, wrapped into the payload layer. *Presumably*, this is used for all the visibility-restriction phone number stuff. Also presumably getting the data contained within involves talking to Google servers. I set `secretIDHash` to 6 random bytes and `signedData` to 72 random bytes in the ones I send. It works fine. - -After that, the client and the server send each other a "paired key result" frame. Both have `status` set to `UNABLE`. Whatever. - -These and following protobuf messages are specific to Nearby Share and [are defined here](/NearDrop/ProtobufSource/wire_format.proto). - -### The transfer metadata aka introduction - -After the successful exchange of the meaningless paired key encryption frames, the client sends an "introduction" frame to the server. This contains the list of files that the client is about to send to the server. The fields should be self-explanatory. The `payload_id` will be used in the payload layer for transferring that particluar file. - -At this point, Android shows that the connection was successful and displays the PIN code. The server would prompt the user to accept the transfer. - -### The file transfer - -#### Accepting the transfer - -To accept the transfer, the server sends a "response" frame with `status` set to `ACCEPT`. The client will then start sending file chunks over the payload layer. You did it 🎉 - -#### Rejecting the transfer - -Do the same but set `status` to `REJECT`. There are also other status codes, like `NOT_ENOUGH_SPACE`, that result in Android showing a different error. - -### Keep-alive frames - -Android sends offline frames of type `KEEP_ALIVE` every 10 seconds and expects the server to do the same. If you don't, it will terminate the connection after a while thinking your app crashed or something. This especially comes into play when sending large files. No, TCP's built-in acknowledgements are not enough. There are so many abstraction layers that whoever came up with this forgot about them. - -### The "mediums" - -Nearby Connections, the underlying layer of Nearby Share, supports running over different "mediums" as Google calls them. Wi-Fi LAN is one of them. Bluetooth, BLE, Wi-Fi Direct, to name a few, are others. - -The server is in charge of choosing the medium. The client specifies its supported mediums in its "connection request" packet. The server then intersects that with its own set of supported mediums. After the transfer is accepted, the server may ask the client for a "bandwidth upgrade" by sending a corresponding packet with its chosen medium and authentication credentials, if any. - -It is still not clear how the actual medium switch occurs. From e33f16f048a433d17ed839b4b6bf665412d38ebd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leon=20B=C3=B6ttger?= Date: Mon, 11 Aug 2025 16:12:00 +0200 Subject: [PATCH 20/20] Improve logging --- NearbyShare/Connection/InboundNearbyConnection.swift | 8 ++++++++ NearbyShare/Connection/NearbyConnection.swift | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/NearbyShare/Connection/InboundNearbyConnection.swift b/NearbyShare/Connection/InboundNearbyConnection.swift index 0c858bb..9008348 100644 --- a/NearbyShare/Connection/InboundNearbyConnection.swift +++ b/NearbyShare/Connection/InboundNearbyConnection.swift @@ -115,6 +115,14 @@ class InboundNearbyConnection: NearbyConnection { case .receivedPairedKeyResult: try processIntroductionFrame(frame) default: + if frame.hasV1, frame.v1.hasType, frame.v1.type == .progressUpdate { + // ignore progress updates + return + } + if frame.hasV1, frame.v1.hasType, frame.v1.type == .response, case .accept = frame.v1.connectionResponse.status { + // ignore accept response frame, it is inferred since the other device set up the connection + return + } log("[InboundNearbyConnection \(self.id)] Unexpected connection state in processTransferSetupFrame: \(currentState)") log(frame.debugDescription) } diff --git a/NearbyShare/Connection/NearbyConnection.swift b/NearbyShare/Connection/NearbyConnection.swift index e71bd0b..c3c257b 100644 --- a/NearbyShare/Connection/NearbyConnection.swift +++ b/NearbyShare/Connection/NearbyConnection.swift @@ -65,6 +65,9 @@ class NearbyConnection { func start() { + + log("[NearbyConnection \(self.id)] Starting connection.") + connection.stateUpdateHandler = { state in if !self.connectionClosed { @@ -425,6 +428,11 @@ class NearbyConnection { sendKeepAlive(ack: true) } else { + if offlineFrame.hasV1, offlineFrame.v1.hasType, offlineFrame.v1.type == .bandwidthUpgradeRetry { + // only supporting WiFi for now, ignore upgrade request to other mediums + return + } + log("[NearbyConnection \(self.id)] Unhandled offline frame encrypted: \(offlineFrame)") } }