60 Commits
Author SHA1 Message Date
Leon Böttger a082d524fa Add multicast networking capability 2025-08-08 08:25:07 +02:00
Leon Böttger 3926f08a36 Add logging on file sizes 2025-08-02 11:22:56 +02:00
Leon Böttger 5bf8469732 Add ability to open app at login 2025-07-29 11:24:31 +02:00
Leon Böttger eac2df3d44 Add error instructions for timeout 2025-07-28 21:12:22 +02:00
Leon Böttger 86bde438e2 Decrease timeout interval 2025-07-28 14:59:08 +02:00
Leon Böttger de7cd2bf98 Add data transfer timer timeout 2025-07-28 14:58:34 +02:00
Leon Böttger 6985d4b1ba Improve error handling 2025-07-28 14:33:18 +02:00
Leon Böttger ddd9a32023 Better error message for failed signature check, throw error on canceled file transfer 2025-07-28 14:10:16 +02:00
Leon Böttger f18649b959 WIP: Add SwiftUI browse view 2025-07-28 12:29:22 +02:00
Leon Böttger 273b7459d8 Fix compilation for iOS 2025-07-28 11:57:50 +02:00
Leon Böttger d2fc94df75 Refactoring 2025-07-28 11:36:04 +02:00
Leon Böttger 0842559f49 Increment build 2025-07-28 11:05:02 +02:00
Leon Böttger 5f40e1d759 Fix could not restart device discovery after stopping 2025-07-27 20:54:28 +02:00
Leon Böttger a311aa01b0 Refactoring 2025-07-27 16:04:58 +02:00
Leon Böttger af5ff5e4fe Improved logging 2025-07-27 16:01:35 +02:00
Leon Böttger 77657ece73 Improve logging 2025-07-27 15:13:29 +02:00
Leon Böttger e3562ab03a Refactoring 2025-07-27 15:08:13 +02:00
Leon Böttger dacb1f868a Add ability to drop files into main window to upload 2025-07-27 14:41:11 +02:00
Leon Böttger 9f2af19adb Refactoring 2025-07-27 12:21:40 +02:00
Leon Böttger f4a21d3f0a Add wakelock when transferring files 2025-07-27 12:14:08 +02:00
Leon Böttger 0f6fef89fe Improve strings 2025-07-27 11:39:47 +02:00
Leon Böttger fb7dda8824 Refactoring 2025-07-26 19:27:59 +02:00
Leon Böttger 13716f41e2 Refactoring 2025-07-26 19:19:34 +02:00
Leon Böttger 98d1596672 Refactoring 2025-07-26 19:01:31 +02:00
Leon Böttger 4262c387a2 Only call connectionWasTerminated if not rejected 2025-07-26 18:53:58 +02:00
Leon Böttger 2c4d661ef3 Also show error message for connections that were not approved yet 2025-07-26 18:48:14 +02:00
Leon Böttger 71ef68d4cb Fix icons for older macOS versions 2025-07-26 13:15:41 +02:00
Leon Böttger b6e3943374 Remove unused code and strings 2025-07-26 13:05:11 +02:00
Leon Böttger a3679c10c0 Migrate to string catalog 2025-07-26 13:01:05 +02:00
Leon Böttger 5d79379b6b Add support for sharing folders 2025-07-26 12:55:40 +02:00
Leon Böttger 381c81e7d6 Remove unused strings 2025-07-26 12:22:44 +02:00
Leon Böttger 4ab9f0d815 Add missing translations 2025-07-26 12:06:30 +02:00
Leon Böttger 277c76e636 Fix timeout interval too short 2025-07-26 11:48:22 +02:00
Leon Böttger 5ecc66d57b Fix too short timeout interval 2025-07-26 11:40:17 +02:00
Leon Böttger b68e4a7345 Show review prompt only once 2025-07-25 15:04:34 +02:00
Leon Böttger 5f9104d4da Refactoring 2025-07-25 14:53:01 +02:00
Leon Böttger c1defe9eee Better error message if transfer was cancelled 2025-07-25 14:39:46 +02:00
Leon Böttger 83a6ed7040 Remove progress if file was aborted 2025-07-25 14:35:37 +02:00
Leon Böttger ae2fcfe886 Fix: share extension was unable to export logs 2025-07-25 14:18:34 +02:00
Leon Böttger 7371208227 Formatting 2025-07-25 13:36:25 +02:00
Leon Böttger 8bac50360f Formatting 2025-07-25 13:26:10 +02:00
Leon Böttger de2686d55e Remove unused code 2025-07-25 13:10:09 +02:00
Leon Böttger 8a9b63954c Add timeout for incoming/outgoing connections, better error handling for outgoing connections 2025-07-25 13:09:01 +02:00
Leon Böttger f3005c3dcf Add logging info to keep-alive frames 2025-07-25 11:44:07 +02:00
Leon Böttger c52dde3526 Reduce logging 2025-07-25 11:33:50 +02:00
Leon Böttger 07b6d9d6fa Refactoring 2025-07-25 11:29:40 +02:00
Leon Böttger 24ce9dd677 More detailed error messages if connection closed by peer or network connection was interrupted 2025-07-25 11:07:45 +02:00
Leon Böttger 8837d0e0aa Refactoring 2025-07-24 19:17:32 +02:00
Leon Böttger 2939990607 Catch errors when storage is not sufficient 2025-07-24 15:34:48 +02:00
Leon Böttger 404e25d3bd Only allow one alert message at a time 2025-07-23 14:42:36 +02:00
Leon Böttger 23705c5e26 Unify main app + app extension logging 2025-07-23 14:21:58 +02:00
Leon Böttger 73e86ff65f Improve strings 2025-07-23 13:36:02 +02:00
Leon Böttger 7403e70893 Set creation date for images using EXIF 2025-06-29 18:58:58 +02:00
Leon Böttger 34d677fb92 Merge remote-tracking branch 'refs/remotes/fork/master' 2025-06-17 16:13:29 +02:00
Leon Böttger da10b8bbb8 Update strings 2025-06-17 16:12:33 +02:00
Leon Böttger ff08266e44 Merge pull request #1 from joaquincorradi/master
Fix Spanish translation: typos and improved consistency
2025-06-17 16:09:17 +02:00
joaquincorradi b6fdf61430 Fix typos in Spanish translation 2025-06-16 19:00:21 -03:00
Leon Böttger d743a5039a Fix welcome screen opened multiple times if app icon clicked multiple times 2025-06-03 20:56:01 +02:00
Leon Böttger f416f8f098 Add Version Number to Logging Export 2025-06-03 09:14:32 +02:00
Leon Böttger 90e3e23905 UI Improvements 2025-06-02 14:31:38 +02:00
242 changed files with 17704 additions and 5477 deletions

No files matched your search

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 527 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 488 KiB

After

Width:  |  Height:  |  Size: 488 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 492 KiB

After

Width:  |  Height:  |  Size: 492 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 527 KiB

After

Width:  |  Height:  |  Size: 527 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 3.2 MiB

After

Width:  |  Height:  |  Size: 3.2 MiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 73 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

File renamed without changes.
File renamed without changes.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 158 KiB

After

Width:  |  Height:  |  Size: 158 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 488 KiB

After

Width:  |  Height:  |  Size: 488 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 492 KiB

After

Width:  |  Height:  |  Size: 492 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 527 KiB

After

Width:  |  Height:  |  Size: 527 KiB

File renamed without changes.
File renamed without changes.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 752 KiB

After

Width:  |  Height:  |  Size: 752 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 527 KiB

After

Width:  |  Height:  |  Size: 527 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 3.2 MiB

After

Width:  |  Height:  |  Size: 3.2 MiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 73 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 752 KiB

After

Width:  |  Height:  |  Size: 752 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 527 KiB

After

Width:  |  Height:  |  Size: 527 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 3.2 MiB

After

Width:  |  Height:  |  Size: 3.2 MiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 73 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

-147
View File
@@ -1,147 +0,0 @@
//
// Logger.swift
// QuickDrop
//
// Created by Leon Böttger on 26.02.25.
//
import Foundation
import os
fileprivate let loggerQueue = DispatchQueue(label: "loggerQueue")
/// Manages logging functionality
final class LogManager {
/// The URL of the log file
let logFileURL: URL?
/// The private initializer
private init() {
/// Get documents directory
let directory = getDocumentsDirectory()
/// Create file name from date
let formatter = DateFormatter()
formatter.dateFormat = "dd-MM-yyyy"
let dateString = formatter.string(from: Date())
let fileName = "\(dateString).log"
/// Create file URL from file name
self.logFileURL = directory.appendingPathComponent(fileName)
}
/// The shared instance.
public static let sharedInstance = LogManager()
@Published var loggingEnabled = true
/// Writes a specified string to the log file.
func writeToFile(string: String) {
/// Make sure the URL is not nil
guard let logFileURL = logFileURL else {
return
}
/// Get current timestamp
let formatter = DateFormatter()
formatter.dateFormat = "HH:mm:ss"
let timestamp = formatter.string(from: Date())
/// Create data from string and timestamp
guard let data = (timestamp + " - " + string + "\n").data(using: .utf8) else { return }
/// a log file exists already
if FileManager.default.fileExists(atPath: logFileURL.path) {
/// we try to get a handle to write to the log file
if let file = try? FileHandle(forWritingTo: logFileURL) {
/// go to end of file
file.seekToEndOfFile()
/// write string to end
file.write(data)
///close file
file.closeFile()
}
} else {
/// Clean up older logs
deleteLogs()
/// create new log file
try? data.write(to: logFileURL, options: .atomicWrite)
}
}
func getLogString() -> String {
if let logFileURL = logFileURL {
let fileURL = URL(fileURLWithPath: logFileURL.path)
do {
let content = try String(contentsOf: fileURL, encoding: .utf8)
return String(content)
} catch {
log("[LUI] Error reading file: \(error.localizedDescription)")
}
}
return ""
}
/// Deletes all log files.
func deleteLogs() {
log("[LUI] Deleting all logs...")
let fileManager = FileManager.default
let documentsDirectory = getDocumentsDirectory()
guard let allFiles = try? fileManager.contentsOfDirectory(at: documentsDirectory, includingPropertiesForKeys: nil) else {
return
}
let logFiles = allFiles.filter { $0.pathExtension.elementsEqual("log") }
for file in logFiles {
do {
log("[LUI] Removing \(file.description)")
try fileManager.removeItem(at: file)
} catch {
print(error.localizedDescription)
}
}
}
}
/// Logs a specified string.
public func log(_ text: String) {
let logger = LogManager.sharedInstance
if logger.loggingEnabled {
loggerQueue.async {
print(text)
// Weird bug?
#if !os(tvOS)
logger.writeToFile(string: text)
#endif
}
}
}
/// Returns the URL of the documents directory.
func getDocumentsDirectory() -> URL {
return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
}
@@ -0,0 +1,564 @@
//
// InboundNearbyConnection.swift
// QuickDrop
//
// Created by Grishka on 08.04.2023.
//
import CommonCrypto
import CryptoKit
import Foundation
import Network
import System
import BigInt
import SwiftECC
#if os(macOS)
import AppKit
#else
import UIKit
#endif
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 {
self.lastError = NearbyError.canceled(reason: .userCanceled)
self.cancelled = true
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
#if os(macOS)
fileInfo.progress?.unpublish()
#endif
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 {
#if os(macOS)
// macOS clipboard
let pasteboard = NSPasteboard.general
pasteboard.clearContents()
pasteboard.setString(urlStr, forType: .string)
NearbyConnectionManager.shared.mainAppDelegate?.showCopiedToClipboardAlert()
#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
}
}
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
#if os(macOS)
fileInfo.progress?.unpublish()
#endif
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
#if os(macOS)
progress.publish()
#endif
filesToBeReceived[id]!.progress = progress
filesToBeReceived[id]!.created = true
log("[InboundNearbyConnection] Accepted file with size \(file.meta.size)")
}
var frame = Sharing_Nearby_Frame()
frame.version = .v1
frame.v1.type = .response
frame.v1.connectionResponse.status = .accept
currentState = .receivingFiles
isTransferring = true
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 os(macOS)
if let progress = file.progress {
progress.unpublish()
}
#endif
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
}
}
}
}
Loaded 100 of 242 files, more files were not shown because too many files have changed in this diff. Show more