diff --git a/NearbyShare/NearbyConnection.swift b/NearbyShare/NearbyConnection.swift index 9c6bbd2..9f7728e 100644 --- a/NearbyShare/NearbyConnection.swift +++ b/NearbyShare/NearbyConnection.swift @@ -15,10 +15,11 @@ import BigInt import SwiftECC class NearbyConnection { - static let SANE_FRAME_LENGTH = 5 * 1024 * 1024 - private static let dispatchQueue = DispatchQueue(label: "com.leonboettger.quickdrop.queue", qos: .utility) // FIFO (non-concurrent) queue to avoid those exciting concurrency bugs - public let id: String + 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? @@ -30,7 +31,7 @@ class NearbyConnection { private var connectionClosed: Bool = false private var inactivityTimer: DispatchSourceTimer? - private let timeoutInterval: TimeInterval = 100 + private let timeoutInterval: TimeInterval = 50 // UKEY2-related state var publicKey: ECPublicKey? @@ -50,11 +51,13 @@ class NearbyConnection { private(set) var pinCode: String? + init(connection: NWConnection, id: String) { self.connection = connection self.id = id } + func start() { connection.stateUpdateHandler = { state in @@ -83,7 +86,7 @@ class NearbyConnection { log("Connection already closed, ignoring state update: \(state)") } } - // connection.start(queue: .global(qos: .utility)) + connection.start(queue: NearbyConnection.dispatchQueue) func recordErrorAndDisconnect(err: NWError) { @@ -105,38 +108,47 @@ class NearbyConnection { } } + func connectionReady() {} + func handleConnectionClosure() { log("Connection closed") deletePartiallyReceivedFiles() } + func protocolError() { log("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 { @@ -158,7 +170,7 @@ class NearbyConnection { return } let frameLength = UInt32(content[0]) << 24 | UInt32(content[1]) << 16 | UInt32(content[2]) << 8 | UInt32(content[3]) - guard frameLength < NearbyConnection.SANE_FRAME_LENGTH else { + guard frameLength < self.maxFrameLength else { self.lastError = NearbyError.protocolError("Unexpected packet length") self.protocolError() return @@ -169,6 +181,7 @@ class NearbyConnection { } } + private func receiveFrameAsync(length: UInt32) { connection.receive(minimumIncompleteLength: Int(length), maximumLength: Int(length)) { content, _, isComplete, _ in if self.connectionClosed { @@ -191,6 +204,7 @@ class NearbyConnection { } } + func sendFrameAsync(_ frame: Data, completion: (() -> Void)? = nil) { if connectionClosed { return @@ -211,6 +225,7 @@ class NearbyConnection { }) } + func encryptAndSendOfflineFrame(_ frame: Location_Nearby_Connections_OfflineFrame, completion: (() -> Void)? = nil) throws { var d2dMsg = Securegcm_DeviceToDeviceMessage() serverSeq += 1 @@ -252,11 +267,13 @@ class NearbyConnection { try sendFrameAsync(smsg.serializedData(), completion: completion) } + func sendTransferSetupFrame(_ frame: Sharing_Nearby_Frame) throws { log("Sending transfer setup frame.") try sendBytesPayload(data: frame.serializedData(), id: Int64.random(in: Int64.min ... Int64.max)) } + func sendBytesPayload(data: Data, id: Int64) throws { log("Sending bytes payload with ID \(id) of length \(data.count)") @@ -285,6 +302,7 @@ class NearbyConnection { try encryptAndSendOfflineFrame(wrapper) } + func decryptAndProcessReceivedSecureMessage(_ smsg: Securemessage_SecureMessage) throws { guard smsg.hasSignature, smsg.hasHeaderAndBody else { throw NearbyError.requiredFieldMissing("secureMessage.signature|headerAndBody") } @@ -336,7 +354,7 @@ class NearbyConnection { let payloadID = header.id - if header.totalSize > InboundNearbyConnection.SANE_FRAME_LENGTH { + if header.totalSize > maxFrameLength { payloadBuffers.removeValue(forKey: payloadID) throw NearbyError.protocolError("Payload too large (\(header.totalSize) bytes)") @@ -381,6 +399,7 @@ class NearbyConnection { } } + static func pinCodeFromAuthKey(_ key: SymmetricKey) -> String { var hash = 0 var multiplier = 1 @@ -397,10 +416,12 @@ class NearbyConnection { 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() @@ -414,10 +435,12 @@ class NearbyConnection { 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") } @@ -474,6 +497,7 @@ class NearbyConnection { } } + func disconnect() { log("Disconnecting from connection.") @@ -488,6 +512,7 @@ class NearbyConnection { }) } + func sendDisconnectionAndDisconnect() throws { var offlineFrame = Location_Nearby_Connections_OfflineFrame() offlineFrame.version = .v1 @@ -504,6 +529,7 @@ class NearbyConnection { disconnect() } + func sendUkey2Alert(type: Securegcm_Ukey2Alert.AlertType) { var alert = Securegcm_Ukey2Alert() alert.type = type @@ -516,6 +542,7 @@ class NearbyConnection { disconnect() } + func sendKeepAlive(ack: Bool) { var offlineFrame = Location_Nearby_Connections_OfflineFrame() offlineFrame.version = .v1 @@ -533,21 +560,29 @@ class NearbyConnection { } } + func startInactivityTimer() { - inactivityTimer?.cancel() // Cancel previous timer if any + + // 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("Connection timeout: No message received in \(self.timeoutInterval) seconds") self.lastError = NearbyError.canceled(reason: .timedOut) self.disconnect() } } + inactivityTimer?.resume() } + private func deletePartiallyReceivedFiles() { for (_, file) in filesToBeTransferred {