mirror of
https://github.com/leonboe1/QuickDrop.git
synced 2026-11-11 02:51:11 +00:00
Reformatting
This commit is contained in:
1 parent
05fa6043fe
commit
6c6c79cdad
5 files changed
+1279
-1307
No files matched your search
@@ -1,47 +1,47 @@
|
||||
//
|
||||
// Data+URLSafeBase64.swift
|
||||
// Data+Extensions.swift
|
||||
// QuickDrop
|
||||
//
|
||||
// Created by Grishka on 08.04.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreFoundation
|
||||
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 0 == SecRandomCopyBytes(kSecRandomDefault, length, $0.baseAddress!) 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)
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -5,94 +5,89 @@
|
||||
// Created by Grishka on 08.04.2023.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
import CommonCrypto
|
||||
import CryptoKit
|
||||
import Foundation
|
||||
import Network
|
||||
import CryptoKit
|
||||
import CommonCrypto
|
||||
import System
|
||||
import AppKit
|
||||
|
||||
import SwiftECC
|
||||
import BigInt
|
||||
import SwiftECC
|
||||
|
||||
class InboundNearbyConnection: NearbyConnection{
|
||||
|
||||
private var currentState:State = .initial
|
||||
public var delegate:InboundNearbyConnectionDelegate?
|
||||
private var cipherCommitment:Data?
|
||||
|
||||
private var textPayloadID:Int64=0
|
||||
|
||||
enum State{
|
||||
class InboundNearbyConnection: NearbyConnection {
|
||||
private var currentState: State = .initial
|
||||
public var delegate: InboundNearbyConnectionDelegate?
|
||||
private var cipherCommitment: Data?
|
||||
|
||||
private var textPayloadID: Int64 = 0
|
||||
|
||||
enum State {
|
||||
case initial, receivedConnectionRequest, sentUkeyServerInit, receivedUkeyClientFinish, sentConnectionResponse, sentPairedKeyResult, receivedPairedKeyResult, waitingForUserConsent, receivingFiles, disconnected
|
||||
}
|
||||
|
||||
override init(connection: NWConnection, id:String) {
|
||||
|
||||
override init(connection: NWConnection, id: String) {
|
||||
super.init(connection: connection, id: id)
|
||||
}
|
||||
|
||||
|
||||
override func handleConnectionClosure() {
|
||||
|
||||
super.handleConnectionClosure()
|
||||
currentState = .disconnected
|
||||
do {
|
||||
try deletePartiallyReceivedFiles()
|
||||
} catch{
|
||||
} catch {
|
||||
log("Error deleting partially received files: \(error)")
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.connectionWasTerminated(connection: self, error: self.lastError)
|
||||
|
||||
|
||||
SaveFilesManager.shared.movePendingFilesToTarget()
|
||||
SaveFilesManager.shared.stopAccessingSecurityScopedResource()
|
||||
}
|
||||
}
|
||||
|
||||
override internal func processReceivedFrame(frameData:Data){
|
||||
|
||||
|
||||
override func processReceivedFrame(frameData: Data) {
|
||||
if currentState != .receivingFiles {
|
||||
log("Received frame in state \(currentState)...")
|
||||
}
|
||||
|
||||
|
||||
do {
|
||||
switch currentState {
|
||||
case .initial:
|
||||
let frame=try Location_Nearby_Connections_OfflineFrame(serializedBytes: frameData)
|
||||
let frame = try Location_Nearby_Connections_OfflineFrame(serializedBytes: frameData)
|
||||
try processConnectionRequestFrame(frame)
|
||||
case .receivedConnectionRequest:
|
||||
let msg=try Securegcm_Ukey2Message(serializedBytes: frameData)
|
||||
ukeyClientInitMsgData=frameData
|
||||
let msg = try Securegcm_Ukey2Message(serializedBytes: frameData)
|
||||
ukeyClientInitMsgData = frameData
|
||||
try processUkey2ClientInit(msg)
|
||||
case .sentUkeyServerInit:
|
||||
let msg=try Securegcm_Ukey2Message(serializedBytes: frameData)
|
||||
let msg = try Securegcm_Ukey2Message(serializedBytes: frameData)
|
||||
try processUkey2ClientFinish(msg, raw: frameData)
|
||||
case .receivedUkeyClientFinish:
|
||||
let frame=try Location_Nearby_Connections_OfflineFrame(serializedBytes: frameData)
|
||||
let frame = try Location_Nearby_Connections_OfflineFrame(serializedBytes: frameData)
|
||||
try processConnectionResponseFrame(frame)
|
||||
default:
|
||||
|
||||
var smsg: Securemessage_SecureMessage
|
||||
|
||||
|
||||
do {
|
||||
smsg = try Securemessage_SecureMessage(serializedBytes: frameData)
|
||||
}
|
||||
catch {
|
||||
} catch {
|
||||
log("Error deserializing secure message. Trying again....")
|
||||
|
||||
|
||||
// last 32 bytes = HMAC key
|
||||
// change 2 bytes before to 0x12 0x20 for Protobuf to succeed
|
||||
|
||||
|
||||
if frameData.count < 34 {
|
||||
throw NearbyError.protocolError("Frame too short")
|
||||
}
|
||||
|
||||
|
||||
var newData = frameData
|
||||
let count = newData.count
|
||||
newData[count - 34] = 0x12
|
||||
newData[count - 33] = 0x20
|
||||
|
||||
|
||||
smsg = try Securemessage_SecureMessage(serializedBytes: newData)
|
||||
|
||||
|
||||
log("Secure message deserialized successfully after fixing Protobuf message")
|
||||
}
|
||||
try decryptAndProcessReceivedSecureMessage(smsg)
|
||||
@@ -100,27 +95,27 @@ class InboundNearbyConnection: NearbyConnection{
|
||||
} catch {
|
||||
lastError = error
|
||||
log("Deserialization error: \(error) in state \(currentState). Payload: \(frameData.hex)")
|
||||
|
||||
//log("Public Key: \(publicKey?.pem ?? "nil")")
|
||||
//log("Private Key: \(privateKey?.pem ?? "nil")")
|
||||
//log("Ukey Client Init Msg: \(ukeyClientInitMsgData?.hex ?? "nil")")
|
||||
//log("Ukey Server Init Msg: \(ukeyServerInitMsgData?.hex ?? "nil")")
|
||||
|
||||
// log("Public Key: \(publicKey?.pem ?? "nil")")
|
||||
// log("Private Key: \(privateKey?.pem ?? "nil")")
|
||||
// log("Ukey Client Init Msg: \(ukeyClientInitMsgData?.hex ?? "nil")")
|
||||
// log("Ukey Server Init Msg: \(ukeyServerInitMsgData?.hex ?? "nil")")
|
||||
log("S1: \(decryptKey?.map { String(format: "%02x", $0) }.joined() ?? "nil")")
|
||||
//log("Encrypt Key: \(encryptKey?.map { String(format: "%02x", $0) }.joined() ?? "nil")")
|
||||
// log("Encrypt Key: \(encryptKey?.map { String(format: "%02x", $0) }.joined() ?? "nil")")
|
||||
log("Received HMAC/Signature Key: \(recvHmacKey?.withUnsafeBytes { Data(Array($0)) }.map { String(format: "%02x", $0) }.joined() ?? "nil")")
|
||||
log("Sent HMAC/Signature Key: \(sendHmacKey?.withUnsafeBytes { Data(Array($0)) }.map { String(format: "%02x", $0) }.joined() ?? "nil")")
|
||||
|
||||
self.protocolError()
|
||||
|
||||
protocolError()
|
||||
}
|
||||
}
|
||||
|
||||
override internal func processTransferSetupFrame(_ frame:Sharing_Nearby_Frame) throws{
|
||||
|
||||
override func processTransferSetupFrame(_ frame: Sharing_Nearby_Frame) throws {
|
||||
if frame.hasV1 && frame.v1.hasType, case .cancel = frame.v1.type {
|
||||
log("Transfer canceled")
|
||||
try sendDisconnectionAndDisconnect()
|
||||
return
|
||||
}
|
||||
switch currentState{
|
||||
switch currentState {
|
||||
case .sentConnectionResponse:
|
||||
try processPairedKeyEncryptionFrame(frame)
|
||||
case .sentPairedKeyResult:
|
||||
@@ -132,47 +127,47 @@ class InboundNearbyConnection: NearbyConnection{
|
||||
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=transferredFiles[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{
|
||||
|
||||
override func processFileChunk(frame: Location_Nearby_Connections_PayloadTransferFrame) throws {
|
||||
let id = frame.payloadHeader.id
|
||||
guard let fileInfo = transferredFiles[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 {
|
||||
fileInfo.fileHandle?.write(frame.payloadChunk.body)
|
||||
transferredFiles[id]!.bytesTransferred+=Int64(frame.payloadChunk.body.count)
|
||||
fileInfo.progress?.completedUnitCount=transferredFiles[id]!.bytesTransferred
|
||||
}else if (frame.payloadChunk.flags & 1)==1{
|
||||
transferredFiles[id]!.bytesTransferred += Int64(frame.payloadChunk.body.count)
|
||||
fileInfo.progress?.completedUnitCount = transferredFiles[id]!.bytesTransferred
|
||||
} else if (frame.payloadChunk.flags & 1) == 1 {
|
||||
try fileInfo.fileHandle?.close()
|
||||
transferredFiles[id]!.fileHandle=nil
|
||||
transferredFiles[id]!.fileHandle = nil
|
||||
fileInfo.progress?.unpublish()
|
||||
SaveFilesManager.shared.registerFileFinishedDownloading(fileInfo.destinationURL)
|
||||
|
||||
|
||||
transferredFiles.removeValue(forKey: id)
|
||||
if transferredFiles.isEmpty{
|
||||
if transferredFiles.isEmpty {
|
||||
try sendDisconnectionAndDisconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override func processBytesPayload(payload: Data, id: Int64) throws -> Bool {
|
||||
if id==textPayloadID{
|
||||
if let urlStr=String(data: payload, encoding: .utf8), let url=URL(string: urlStr){
|
||||
if id == textPayloadID {
|
||||
if let urlStr = String(data: payload, encoding: .utf8), let url = URL(string: urlStr) {
|
||||
NSWorkspace.shared.open(url)
|
||||
}
|
||||
try sendDisconnectionAndDisconnect()
|
||||
return true
|
||||
}else if let fileInfo=transferredFiles[id]{
|
||||
} else if let fileInfo = transferredFiles[id] {
|
||||
fileInfo.fileHandle?.write(payload)
|
||||
transferredFiles[id]!.bytesTransferred+=Int64(payload.count)
|
||||
fileInfo.progress?.completedUnitCount=transferredFiles[id]!.bytesTransferred
|
||||
transferredFiles[id]!.bytesTransferred += Int64(payload.count)
|
||||
fileInfo.progress?.completedUnitCount = transferredFiles[id]!.bytesTransferred
|
||||
try fileInfo.fileHandle?.close()
|
||||
transferredFiles[id]!.fileHandle=nil
|
||||
transferredFiles[id]!.fileHandle = nil
|
||||
fileInfo.progress?.unpublish()
|
||||
transferredFiles.removeValue(forKey: id)
|
||||
SaveFilesManager.shared.registerFileFinishedDownloading(fileInfo.destinationURL)
|
||||
@@ -181,247 +176,243 @@ class InboundNearbyConnection: NearbyConnection{
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private func processConnectionRequestFrame(_ frame:Location_Nearby_Connections_OfflineFrame) throws{
|
||||
|
||||
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=Int(endpointInfo[0] & 7) >> 1
|
||||
remoteDeviceInfo=RemoteDeviceInfo(name: deviceName, type: RemoteDeviceInfo.DeviceType.fromRawValue(value: rawDeviceType))
|
||||
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{
|
||||
|
||||
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{
|
||||
guard case .clientInit = msg.messageType else {
|
||||
sendUkey2Alert(type: .badMessageType)
|
||||
throw NearbyError.ukey2
|
||||
}
|
||||
let clientInit:Securegcm_Ukey2ClientInit
|
||||
do{
|
||||
clientInit=try Securegcm_Ukey2ClientInit(serializedBytes: msg.messageData)
|
||||
}catch{
|
||||
let clientInit: Securegcm_Ukey2ClientInit
|
||||
do {
|
||||
clientInit = try Securegcm_Ukey2ClientInit(serializedBytes: msg.messageData)
|
||||
} catch {
|
||||
sendUkey2Alert(type: .badMessageData)
|
||||
throw NearbyError.ukey2
|
||||
}
|
||||
guard clientInit.version==1 else{
|
||||
guard clientInit.version == 1 else {
|
||||
sendUkey2Alert(type: .badVersion)
|
||||
throw NearbyError.ukey2
|
||||
}
|
||||
guard clientInit.random.count==32 else{
|
||||
guard clientInit.random.count == 32 else {
|
||||
sendUkey2Alert(type: .badRandom)
|
||||
throw NearbyError.ukey2
|
||||
}
|
||||
var found=false
|
||||
for commitment in clientInit.cipherCommitments{
|
||||
if case .p256Sha512 = commitment.handshakeCipher{
|
||||
found=true
|
||||
cipherCommitment=commitment.commitment
|
||||
var found = false
|
||||
for commitment in clientInit.cipherCommitments {
|
||||
if case .p256Sha512 = commitment.handshakeCipher {
|
||||
found = true
|
||||
cipherCommitment = commitment.commitment
|
||||
break
|
||||
}
|
||||
}
|
||||
guard found else{
|
||||
guard found else {
|
||||
sendUkey2Alert(type: .badHandshakeCipher)
|
||||
throw NearbyError.ukey2
|
||||
}
|
||||
guard clientInit.nextProtocol=="AES_256_CBC-HMAC_SHA256" else{
|
||||
guard clientInit.nextProtocol == "AES_256_CBC-HMAC_SHA256" else {
|
||||
sendUkey2Alert(type: .badNextProtocol)
|
||||
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)
|
||||
|
||||
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()
|
||||
|
||||
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()
|
||||
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
|
||||
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{
|
||||
|
||||
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 { throw NearbyError.ukey2 }
|
||||
|
||||
var sha=SHA512()
|
||||
|
||||
var sha = SHA512()
|
||||
sha.update(data: raw)
|
||||
guard cipherCommitment==Data(sha.finalize()) else { 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)
|
||||
|
||||
guard cipherCommitment == Data(sha.finalize()) else { 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{
|
||||
|
||||
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()
|
||||
var resp = Location_Nearby_Connections_OfflineFrame()
|
||||
resp.version = .v1
|
||||
resp.v1=Location_Nearby_Connections_V1Frame()
|
||||
resp.v1 = Location_Nearby_Connections_V1Frame()
|
||||
resp.v1.type = .connectionResponse
|
||||
resp.v1.connectionResponse=Location_Nearby_Connections_ConnectionResponseFrame()
|
||||
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.status = 0
|
||||
resp.v1.connectionResponse.osInfo = Location_Nearby_Connections_OsInfo()
|
||||
resp.v1.connectionResponse.osInfo.type = .apple
|
||||
sendFrameAsync(try resp.serializedData())
|
||||
|
||||
encryptionDone=true
|
||||
|
||||
var pairedEncryption=Sharing_Nearby_Frame()
|
||||
try sendFrameAsync(resp.serializedData())
|
||||
|
||||
encryptionDone = true
|
||||
|
||||
var pairedEncryption = Sharing_Nearby_Frame()
|
||||
pairedEncryption.version = .v1
|
||||
pairedEncryption.v1=Sharing_Nearby_V1Frame()
|
||||
pairedEncryption.v1 = Sharing_Nearby_V1Frame()
|
||||
pairedEncryption.v1.type = .pairedKeyEncryption
|
||||
pairedEncryption.v1.pairedKeyEncryption=Sharing_Nearby_PairedKeyEncryptionFrame()
|
||||
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)
|
||||
pairedEncryption.v1.pairedKeyEncryption.secretIDHash = Data.randomData(length: 6)
|
||||
pairedEncryption.v1.pairedKeyEncryption.signedData = Data.randomData(length: 72)
|
||||
try sendTransferSetupFrame(pairedEncryption)
|
||||
currentState = .sentConnectionResponse
|
||||
} else {
|
||||
log("Unhandled offline frame plaintext: \(frame)")
|
||||
}
|
||||
}
|
||||
|
||||
private func processPairedKeyEncryptionFrame(_ frame:Sharing_Nearby_Frame) throws{
|
||||
|
||||
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()
|
||||
var pairedResult = Sharing_Nearby_Frame()
|
||||
pairedResult.version = .v1
|
||||
pairedResult.v1=Sharing_Nearby_V1Frame()
|
||||
pairedResult.v1 = Sharing_Nearby_V1Frame()
|
||||
pairedResult.v1.type = .pairedKeyResult
|
||||
pairedResult.v1.pairedKeyResult=Sharing_Nearby_PairedKeyResultFrame()
|
||||
pairedResult.v1.pairedKeyResult = Sharing_Nearby_PairedKeyResultFrame()
|
||||
pairedResult.v1.pairedKeyResult.status = .unable
|
||||
try sendTransferSetupFrame(pairedResult)
|
||||
currentState = .sentPairedKeyResult
|
||||
}
|
||||
|
||||
private func processPairedKeyResultFrame(_ frame:Sharing_Nearby_Frame) throws{
|
||||
|
||||
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)"
|
||||
|
||||
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)
|
||||
counter += 1
|
||||
} while FileManager.default.fileExists(atPath: path)
|
||||
dest = URL(fileURLWithPath: path)
|
||||
}
|
||||
return dest
|
||||
}
|
||||
|
||||
private func processIntroductionFrame(_ frame:Sharing_Nearby_Frame) throws{
|
||||
|
||||
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{
|
||||
|
||||
|
||||
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)
|
||||
transferredFiles[file.payloadID] = info
|
||||
}
|
||||
let metadata=TransferMetadata(files: transferredFiles.map({$0.value.meta}), id: id, pinCode: pinCode)
|
||||
let metadata = TransferMetadata(files: transferredFiles.map { $0.value.meta }, id: id, pinCode: pinCode)
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.obtainUserConsent(for: metadata, from: self.remoteDeviceInfo!, connection: self)
|
||||
}
|
||||
}else if frame.v1.introduction.textMetadata.count==1{
|
||||
let meta=frame.v1.introduction.textMetadata[0]
|
||||
if case .url=meta.type{
|
||||
let metadata=TransferMetadata(files: [], id: id, pinCode: pinCode, textDescription: meta.textTitle)
|
||||
textPayloadID=meta.payloadID
|
||||
} else if frame.v1.introduction.textMetadata.count == 1 {
|
||||
let meta = frame.v1.introduction.textMetadata[0]
|
||||
if case .url = meta.type {
|
||||
let metadata = TransferMetadata(files: [], id: id, pinCode: pinCode, textDescription: meta.textTitle)
|
||||
textPayloadID = meta.payloadID
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.obtainUserConsent(for: metadata, from: self.remoteDeviceInfo!, connection: self)
|
||||
}
|
||||
}else if case .text=meta.type{
|
||||
|
||||
} else if case .text = meta.type {
|
||||
let saveDirectory = SaveFilesManager.shared.getSaveDirectory()
|
||||
|
||||
let dateFormatter=DateFormatter()
|
||||
dateFormatter.dateFormat="yyyy-MM-dd HH.mm.ss"
|
||||
let dest=makeFileDestinationURL(saveDirectory.appendingPathComponent("\(dateFormatter.string(from: Date())).txt"))
|
||||
let info=InternalFileInfo(meta: FileMetadata(name: dest.lastPathComponent, size: meta.size, mimeType: "text/plain"),
|
||||
payloadID: meta.payloadID,
|
||||
destinationURL: dest)
|
||||
transferredFiles[meta.payloadID]=info
|
||||
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "yyyy-MM-dd HH.mm.ss"
|
||||
let dest = makeFileDestinationURL(saveDirectory.appendingPathComponent("\(dateFormatter.string(from: Date())).txt"))
|
||||
let info = InternalFileInfo(meta: FileMetadata(name: dest.lastPathComponent, size: meta.size, mimeType: "text/plain"),
|
||||
payloadID: meta.payloadID,
|
||||
destinationURL: dest)
|
||||
transferredFiles[meta.payloadID] = info
|
||||
DispatchQueue.main.async {
|
||||
self.delegate?.obtainUserConsent(for: TransferMetadata(files: [info.meta], id: self.id, pinCode: self.pinCode), from: self.remoteDeviceInfo!, connection: self)
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
rejectTransfer(with: .unsupportedAttachmentType)
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
rejectTransfer(with: .unsupportedAttachmentType)
|
||||
}
|
||||
}
|
||||
|
||||
func submitUserConsent(accepted:Bool, storeInTemp: Bool = false){
|
||||
|
||||
func submitUserConsent(accepted: Bool, storeInTemp: Bool = false) {
|
||||
DispatchQueue.global(qos: .utility).async {
|
||||
if accepted{
|
||||
if accepted {
|
||||
self.acceptTransfer(storeInTemp: storeInTemp)
|
||||
}else{
|
||||
} else {
|
||||
self.rejectTransfer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func acceptTransfer(storeInTemp: Bool) {
|
||||
|
||||
if currentState == .disconnected {
|
||||
log("Detected timeout, not accepting transfer")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
do {
|
||||
if storeInTemp {
|
||||
try FileManager.default.createDirectory(at: SaveFilesManager.shared.tempDirectory, withIntermediateDirectories: true)
|
||||
}
|
||||
|
||||
|
||||
for (id, file) in transferredFiles {
|
||||
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)
|
||||
transferredFiles[id]!.fileHandle = handle
|
||||
|
||||
|
||||
let progress = Progress()
|
||||
progress.fileURL = targetURL
|
||||
progress.totalUnitCount = file.meta.size
|
||||
@@ -431,7 +422,7 @@ class InboundNearbyConnection: NearbyConnection{
|
||||
transferredFiles[id]!.progress = progress
|
||||
transferredFiles[id]!.created = true
|
||||
}
|
||||
|
||||
|
||||
var frame = Sharing_Nearby_Frame()
|
||||
frame.version = .v1
|
||||
frame.v1.type = .response
|
||||
@@ -443,24 +434,22 @@ class InboundNearbyConnection: NearbyConnection{
|
||||
protocolError()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func rejectTransfer(with reason:Sharing_Nearby_ConnectionResponseFrame.Status = .reject){
|
||||
var frame=Sharing_Nearby_Frame()
|
||||
|
||||
private func rejectTransfer(with reason: Sharing_Nearby_ConnectionResponseFrame.Status = .reject) {
|
||||
var frame = Sharing_Nearby_Frame()
|
||||
frame.version = .v1
|
||||
frame.v1.type = .response
|
||||
frame.v1.connectionResponse.status = reason
|
||||
do{
|
||||
do {
|
||||
try sendTransferSetupFrame(frame)
|
||||
try sendDisconnectionAndDisconnect()
|
||||
}catch{
|
||||
} catch {
|
||||
log("Error \(error)")
|
||||
protocolError()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func deletePartiallyReceivedFiles() throws{
|
||||
|
||||
private func deletePartiallyReceivedFiles() throws {
|
||||
for (_, file) in transferredFiles {
|
||||
guard file.created else { continue }
|
||||
try FileManager.default.removeItem(at: file.destinationURL)
|
||||
@@ -468,48 +457,42 @@ class InboundNearbyConnection: NearbyConnection{
|
||||
}
|
||||
}
|
||||
|
||||
protocol InboundNearbyConnectionDelegate{
|
||||
func obtainUserConsent(for transfer:TransferMetadata, from device:RemoteDeviceInfo, connection:InboundNearbyConnection)
|
||||
func connectionWasTerminated(connection:InboundNearbyConnection, error:Error?)
|
||||
protocol InboundNearbyConnectionDelegate {
|
||||
func obtainUserConsent(for transfer: TransferMetadata, from device: RemoteDeviceInfo, connection: InboundNearbyConnection)
|
||||
func connectionWasTerminated(connection: InboundNearbyConnection, error: Error?)
|
||||
}
|
||||
|
||||
|
||||
public class SaveFilesManager {
|
||||
|
||||
private init() {}
|
||||
|
||||
|
||||
public static let shared = SaveFilesManager()
|
||||
|
||||
internal let tempDirectory: URL = FileManager.default.temporaryDirectory.appendingPathComponent("Pending")
|
||||
|
||||
let tempDirectory: URL = FileManager.default.temporaryDirectory.appendingPathComponent("Pending")
|
||||
private var securityScopeUrl: URL?
|
||||
|
||||
|
||||
private var filesFinishedDownloading = [URL]()
|
||||
|
||||
|
||||
|
||||
public func registerFileFinishedDownloading(_ fileURL: URL) {
|
||||
filesFinishedDownloading.append(fileURL)
|
||||
}
|
||||
|
||||
|
||||
|
||||
public func movePendingFilesToTarget() {
|
||||
|
||||
if !isPlusVersion() {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
log("Moving pending files to target directory")
|
||||
|
||||
|
||||
do {
|
||||
let fileManager = FileManager.default
|
||||
let files = try fileManager.contentsOfDirectory(at: tempDirectory, includingPropertiesForKeys: nil)
|
||||
|
||||
|
||||
let target = getSaveDirectory()
|
||||
|
||||
|
||||
for file in files {
|
||||
|
||||
let fileName = file.lastPathComponent
|
||||
let destinationURL = target.appendingPathComponent(fileName)
|
||||
|
||||
|
||||
if !filesFinishedDownloading.contains(destinationURL) {
|
||||
log("File \(file) not finished downloading, skipping")
|
||||
continue
|
||||
@@ -517,74 +500,69 @@ public class SaveFilesManager {
|
||||
|
||||
log("Moving file: \(file.lastPathComponent) to \(destinationURL.lastPathComponent)")
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
stopAccessingSecurityScopedResource()
|
||||
|
||||
|
||||
log("Moved all pending files to target directory")
|
||||
|
||||
|
||||
} catch {
|
||||
log("Error moving pending files: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public func stopAccessingSecurityScopedResource() {
|
||||
guard let url = securityScopeUrl else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
log("Stopping access to security scoped resource: \(url)")
|
||||
url.stopAccessingSecurityScopedResource()
|
||||
self.securityScopeUrl = nil
|
||||
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)")
|
||||
|
||||
self.securityScopeUrl = url
|
||||
|
||||
securityScopeUrl = url
|
||||
return url
|
||||
}
|
||||
} else {
|
||||
print("Bookmark is stale, using default downloads folder.")
|
||||
}
|
||||
|
||||
|
||||
} catch {
|
||||
print("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 {
|
||||
@@ -606,7 +584,8 @@ extension String {
|
||||
while index < endIndex {
|
||||
let nextIndex = self.index(index, offsetBy: 2)
|
||||
if nextIndex <= endIndex,
|
||||
let byte = UInt8(self[index..<nextIndex], radix: 16) {
|
||||
let byte = UInt8(self[index ..< nextIndex], radix: 16)
|
||||
{
|
||||
data.append(byte)
|
||||
} else {
|
||||
break // or handle invalid hex gracefully
|
||||
|
||||
+280
-280
@@ -5,134 +5,134 @@
|
||||
// Created by Grishka on 09.04.2023.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Network
|
||||
import CommonCrypto
|
||||
import CryptoKit
|
||||
import Foundation
|
||||
import Network
|
||||
import System
|
||||
|
||||
import SwiftECC
|
||||
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
|
||||
|
||||
let connection: NWConnection
|
||||
var remoteDeviceInfo: RemoteDeviceInfo?
|
||||
private var payloadBuffers: [Int64: NSMutableData] = [:]
|
||||
var encryptionDone: Bool = false
|
||||
var transferredFiles: [Int64: InternalFileInfo] = [:]
|
||||
public let id: String
|
||||
var lastError: Error?
|
||||
private var connectionClosed: Bool = false
|
||||
|
||||
class NearbyConnection{
|
||||
internal 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
|
||||
|
||||
internal let connection:NWConnection
|
||||
internal var remoteDeviceInfo:RemoteDeviceInfo?
|
||||
private var payloadBuffers:[Int64:NSMutableData]=[:]
|
||||
internal var encryptionDone:Bool=false
|
||||
internal var transferredFiles:[Int64:InternalFileInfo]=[:]
|
||||
public let id:String
|
||||
internal var lastError:Error?
|
||||
private var connectionClosed:Bool=false
|
||||
|
||||
// UKEY2-related state
|
||||
internal var publicKey:ECPublicKey?
|
||||
internal var privateKey:ECPrivateKey?
|
||||
internal var ukeyClientInitMsgData:Data?
|
||||
internal var ukeyServerInitMsgData:Data?
|
||||
|
||||
var publicKey: ECPublicKey?
|
||||
var privateKey: ECPrivateKey?
|
||||
var ukeyClientInitMsgData: Data?
|
||||
var ukeyServerInitMsgData: Data?
|
||||
|
||||
// SecureMessage encryption keys
|
||||
internal var decryptKey:[UInt8]?
|
||||
internal var encryptKey:[UInt8]?
|
||||
internal var recvHmacKey:SymmetricKey?
|
||||
internal var sendHmacKey:SymmetricKey?
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
func start() {
|
||||
connection.stateUpdateHandler = { state in
|
||||
if case .ready = state {
|
||||
self.connectionReady()
|
||||
self.receiveFrameAsync()
|
||||
} else if case .failed(let err) = state {
|
||||
self.lastError=err
|
||||
} else if case let .failed(err) = state {
|
||||
self.lastError = err
|
||||
log("Error opening socket: \(err)")
|
||||
self.handleConnectionClosure()
|
||||
}
|
||||
}
|
||||
//connection.start(queue: .global(qos: .utility))
|
||||
// connection.start(queue: .global(qos: .utility))
|
||||
connection.start(queue: NearbyConnection.dispatchQueue)
|
||||
}
|
||||
|
||||
func connectionReady(){}
|
||||
|
||||
internal func handleConnectionClosure(){
|
||||
|
||||
func connectionReady() {}
|
||||
|
||||
func handleConnectionClosure() {
|
||||
log("Connection closed")
|
||||
}
|
||||
|
||||
internal func protocolError(){
|
||||
|
||||
func protocolError() {
|
||||
disconnect()
|
||||
}
|
||||
|
||||
internal func processReceivedFrame(frameData:Data){
|
||||
|
||||
func processReceivedFrame(frameData _: Data) {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
internal func processTransferSetupFrame(_ frame:Sharing_Nearby_Frame) throws{
|
||||
|
||||
func processTransferSetupFrame(_: Sharing_Nearby_Frame) throws {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
internal func isServer() -> Bool{
|
||||
|
||||
func isServer() -> Bool {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
internal func processFileChunk(frame:Location_Nearby_Connections_PayloadTransferFrame) throws{
|
||||
|
||||
func processFileChunk(frame _: Location_Nearby_Connections_PayloadTransferFrame) throws {
|
||||
protocolError()
|
||||
}
|
||||
|
||||
internal func processBytesPayload(payload:Data, id:Int64)throws ->Bool{
|
||||
|
||||
func processBytesPayload(payload _: Data, id _: Int64) throws -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
private func receiveFrameAsync(){
|
||||
connection.receive(minimumIncompleteLength: 4, maximumLength: 4) { content, contentContext, isComplete, error in
|
||||
if self.connectionClosed{
|
||||
|
||||
private func receiveFrameAsync() {
|
||||
connection.receive(minimumIncompleteLength: 4, maximumLength: 4) { content, _, isComplete, error in
|
||||
if self.connectionClosed {
|
||||
return
|
||||
}
|
||||
if isComplete{
|
||||
if isComplete {
|
||||
self.handleConnectionClosure()
|
||||
return
|
||||
}
|
||||
if !(error==nil){
|
||||
self.lastError=error
|
||||
if !(error == nil) {
|
||||
self.lastError = error
|
||||
self.protocolError()
|
||||
return
|
||||
}
|
||||
guard let content=content else {
|
||||
guard let content = content else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
let frameLength:UInt32=UInt32(content[0]) << 24 | UInt32(content[1]) << 16 | UInt32(content[2]) << 8 | UInt32(content[3])
|
||||
guard frameLength<NearbyConnection.SANE_FRAME_LENGTH else {
|
||||
self.lastError=NearbyError.protocolError("Unexpected packet length")
|
||||
let frameLength = UInt32(content[0]) << 24 | UInt32(content[1]) << 16 | UInt32(content[2]) << 8 | UInt32(content[3])
|
||||
guard frameLength < NearbyConnection.SANE_FRAME_LENGTH else {
|
||||
self.lastError = NearbyError.protocolError("Unexpected packet length")
|
||||
self.protocolError()
|
||||
return
|
||||
}
|
||||
self.receiveFrameAsync(length: frameLength)
|
||||
}
|
||||
}
|
||||
|
||||
private func receiveFrameAsync(length:UInt32){
|
||||
connection.receive(minimumIncompleteLength: Int(length), maximumLength: Int(length)) { content, contentContext, isComplete, error in
|
||||
if self.connectionClosed{
|
||||
|
||||
private func receiveFrameAsync(length: UInt32) {
|
||||
connection.receive(minimumIncompleteLength: Int(length), maximumLength: Int(length)) { content, _, isComplete, _ in
|
||||
if self.connectionClosed {
|
||||
return
|
||||
}
|
||||
if isComplete{
|
||||
if isComplete {
|
||||
self.handleConnectionClosure()
|
||||
return
|
||||
}
|
||||
guard let content=content else {
|
||||
guard let content = content else {
|
||||
self.protocolError()
|
||||
return
|
||||
}
|
||||
@@ -140,39 +140,39 @@ class NearbyConnection{
|
||||
self.receiveFrameAsync()
|
||||
}
|
||||
}
|
||||
|
||||
internal func sendFrameAsync(_ frame:Data, completion:(()->Void)?=nil){
|
||||
if connectionClosed{
|
||||
|
||||
func sendFrameAsync(_ frame: Data, completion: (() -> Void)? = nil) {
|
||||
if connectionClosed {
|
||||
return
|
||||
}
|
||||
var lengthPrefixedData=Data(capacity: frame.count+4)
|
||||
let length:Int=frame.count
|
||||
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)
|
||||
UInt8(truncatingIfNeeded: length),
|
||||
])
|
||||
lengthPrefixedData.append(frame)
|
||||
connection.send(content: lengthPrefixedData, completion: .contentProcessed({ error in
|
||||
if let completion=completion{
|
||||
connection.send(content: lengthPrefixedData, completion: .contentProcessed { _ in
|
||||
if let completion = completion {
|
||||
completion()
|
||||
}
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
internal 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=[UInt8](try 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(
|
||||
|
||||
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),
|
||||
@@ -182,69 +182,69 @@ class NearbyConnection{
|
||||
$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()
|
||||
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()
|
||||
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<SHA256>.authenticationCode(for: smsg.headerAndBody, using: sendHmacKey!))
|
||||
sendFrameAsync(try smsg.serializedData(), completion: completion)
|
||||
md.version = 1
|
||||
hb.header.publicMetadata = try md.serializedData()
|
||||
|
||||
var smsg = Securemessage_SecureMessage()
|
||||
smsg.headerAndBody = try hb.serializedData()
|
||||
smsg.signature = Data(HMAC<SHA256>.authenticationCode(for: smsg.headerAndBody, using: sendHmacKey!))
|
||||
try sendFrameAsync(smsg.serializedData(), completion: completion)
|
||||
}
|
||||
|
||||
internal func sendTransferSetupFrame(_ frame:Sharing_Nearby_Frame) throws{
|
||||
try sendBytesPayload(data: try frame.serializedData(), id: Int64.random(in: Int64.min...Int64.max))
|
||||
|
||||
func sendTransferSetupFrame(_ frame: Sharing_Nearby_Frame) throws {
|
||||
try sendBytesPayload(data: frame.serializedData(), id: Int64.random(in: Int64.min ... Int64.max))
|
||||
}
|
||||
|
||||
internal func sendBytesPayload(data:Data, id:Int64) throws{
|
||||
var transfer=Location_Nearby_Connections_PayloadTransferFrame()
|
||||
|
||||
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.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()
|
||||
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 = Location_Nearby_Connections_V1Frame()
|
||||
wrapper.v1.type = .payloadTransfer
|
||||
wrapper.v1.payloadTransfer=transfer
|
||||
wrapper.v1.payloadTransfer = transfer
|
||||
try encryptAndSendOfflineFrame(wrapper)
|
||||
|
||||
transfer.payloadChunk.flags=1 // .lastChunk
|
||||
transfer.payloadChunk.offset=Int64(transfer.payloadChunk.body.count)
|
||||
|
||||
transfer.payloadChunk.flags = 1 // .lastChunk
|
||||
transfer.payloadChunk.offset = Int64(transfer.payloadChunk.body.count)
|
||||
transfer.payloadChunk.clearBody()
|
||||
wrapper.v1.payloadTransfer=transfer
|
||||
wrapper.v1.payloadTransfer = transfer
|
||||
try encryptAndSendOfflineFrame(wrapper)
|
||||
}
|
||||
|
||||
internal func decryptAndProcessReceivedSecureMessage(_ smsg:Securemessage_SecureMessage) throws{
|
||||
|
||||
func decryptAndProcessReceivedSecureMessage(_ smsg: Securemessage_SecureMessage) throws {
|
||||
guard smsg.hasSignature, smsg.hasHeaderAndBody else { throw NearbyError.requiredFieldMissing("secureMessage.signature|headerAndBody") }
|
||||
|
||||
|
||||
if NearbyConnectionManager.shared.checkSignature {
|
||||
let hmac = Data(HMAC<SHA256>.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:Int=0
|
||||
decryptedData.withUnsafeMutableBytes({
|
||||
let status=CCCrypt(
|
||||
|
||||
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),
|
||||
@@ -254,209 +254,209 @@ class NearbyConnection{
|
||||
$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 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 {
|
||||
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;
|
||||
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 { throw NearbyError.requiredFieldMissing("payloadTransfer.payloadChunk|offset|flags") }
|
||||
if case .bytes = header.type{
|
||||
let payloadID=header.id
|
||||
if header.totalSize>InboundNearbyConnection.SANE_FRAME_LENGTH{
|
||||
if case .bytes = header.type {
|
||||
let payloadID = header.id
|
||||
if header.totalSize > InboundNearbyConnection.SANE_FRAME_LENGTH {
|
||||
payloadBuffers.removeValue(forKey: payloadID)
|
||||
throw NearbyError.protocolError("Payload too large (\(header.totalSize) bytes)")
|
||||
}
|
||||
if payloadBuffers[payloadID]==nil {
|
||||
payloadBuffers[payloadID]=NSMutableData(capacity: Int(header.totalSize))
|
||||
if payloadBuffers[payloadID] == nil {
|
||||
payloadBuffers[payloadID] = NSMutableData(capacity: Int(header.totalSize))
|
||||
}
|
||||
let buffer=payloadBuffers[payloadID]!
|
||||
guard chunk.offset==buffer.count else {
|
||||
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 {
|
||||
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)
|
||||
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{
|
||||
} else if case .file = header.type {
|
||||
try processFileChunk(frame: payloadTransfer)
|
||||
}
|
||||
}else if offlineFrame.hasV1 && offlineFrame.v1.hasType, case .keepAlive = offlineFrame.v1.type{
|
||||
} else if offlineFrame.hasV1, offlineFrame.v1.hasType, case .keepAlive = offlineFrame.v1.type {
|
||||
log("Sent keep-alive")
|
||||
sendKeepAlive(ack: true)
|
||||
}else{
|
||||
} else {
|
||||
log("Unhandled offline frame encrypted: \(offlineFrame)")
|
||||
}
|
||||
}
|
||||
|
||||
internal static func pinCodeFromAuthKey(_ key:SymmetricKey) -> String{
|
||||
var hash:Int=0
|
||||
var multiplier:Int=1
|
||||
let keyBytes:[UInt8]=key.withUnsafeBytes({
|
||||
return [UInt8]($0)
|
||||
})
|
||||
|
||||
for _byte in keyBytes {
|
||||
let byte=Int(Int8(bitPattern: _byte))
|
||||
hash=(hash+byte*multiplier)%9973
|
||||
multiplier=(multiplier*31)%9973
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
internal static func hkdfExtract(salt:Data, ikm:Data) -> Data{
|
||||
return HMAC<SHA256>.authenticationCode(for: ikm, using: SymmetricKey(data: salt)).withUnsafeBytes({return Data(bytes: $0.baseAddress!, count: $0.count)})
|
||||
|
||||
static func hkdfExtract(salt: Data, ikm: Data) -> Data {
|
||||
return HMAC<SHA256>.authenticationCode(for: ikm, using: SymmetricKey(data: salt)).withUnsafeBytes { Data(bytes: $0.baseAddress!, count: $0.count) }
|
||||
}
|
||||
|
||||
internal 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<SHA256>.authenticationCode(for: toDigest, using: SymmetricKey(data: prk)).withUnsafeBytes({return Data(bytes: $0.baseAddress!, count: $0.count)})
|
||||
okm=okm+t
|
||||
|
||||
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<SHA256>.authenticationCode(for: toDigest, using: SymmetricKey(data: prk)).withUnsafeBytes { Data(bytes: $0.baseAddress!, count: $0.count) }
|
||||
okm = okm + t
|
||||
}
|
||||
return okm.subdata(in: 0..<length)
|
||||
return okm.subdata(in: 0 ..< length)
|
||||
}
|
||||
|
||||
internal static func hkdf(inputKeyMaterial:SymmetricKey, salt:Data, info:Data, outputByteCount:Int) -> SymmetricKey{
|
||||
if #available(macOS 11.0, *){
|
||||
|
||||
static func hkdf(inputKeyMaterial: SymmetricKey, salt: Data, info: Data, outputByteCount: Int) -> SymmetricKey {
|
||||
if #available(macOS 11.0, *) {
|
||||
return HKDF<SHA256>.deriveKey(inputKeyMaterial: inputKeyMaterial, salt: salt, info: info, outputByteCount: outputByteCount)
|
||||
}else{
|
||||
return SymmetricKey(data: hkdfExpand(prk: hkdfExtract(salt: salt, ikm: inputKeyMaterial.withUnsafeBytes({return Data(bytes: $0.baseAddress!, count: $0.count)})), info: info, length: outputByteCount))
|
||||
} else {
|
||||
return SymmetricKey(data: hkdfExpand(prk: hkdfExtract(salt: salt, ikm: inputKeyMaterial.withUnsafeBytes { Data(bytes: $0.baseAddress!, count: $0.count) }), info: info, length: outputByteCount))
|
||||
}
|
||||
}
|
||||
|
||||
internal func finalizeKeyExchange(peerKey:Securemessage_GenericPublicKey) throws{
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
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()
|
||||
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()
|
||||
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=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()
|
||||
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({return [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({return [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
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
internal func disconnect(){
|
||||
connection.send(content: nil, isComplete: true, completion: .contentProcessed({ error in
|
||||
|
||||
func disconnect() {
|
||||
connection.send(content: nil, isComplete: true, completion: .contentProcessed { _ in
|
||||
self.handleConnectionClosure()
|
||||
}))
|
||||
connectionClosed=true
|
||||
})
|
||||
connectionClosed = true
|
||||
}
|
||||
|
||||
internal func sendDisconnectionAndDisconnect() throws{
|
||||
var offlineFrame=Location_Nearby_Connections_OfflineFrame()
|
||||
|
||||
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{
|
||||
offlineFrame.v1.disconnection = Location_Nearby_Connections_DisconnectionFrame()
|
||||
|
||||
if encryptionDone {
|
||||
try encryptAndSendOfflineFrame(offlineFrame)
|
||||
}else{
|
||||
sendFrameAsync(try offlineFrame.serializedData())
|
||||
} else {
|
||||
try sendFrameAsync(offlineFrame.serializedData())
|
||||
}
|
||||
disconnect()
|
||||
}
|
||||
|
||||
internal func sendUkey2Alert(type:Securegcm_Ukey2Alert.AlertType){
|
||||
var alert=Securegcm_Ukey2Alert()
|
||||
alert.type=type
|
||||
var msg=Securegcm_Ukey2Message()
|
||||
|
||||
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())
|
||||
disconnect()
|
||||
}
|
||||
|
||||
internal func sendKeepAlive(ack:Bool){
|
||||
var offlineFrame=Location_Nearby_Connections_OfflineFrame()
|
||||
|
||||
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{
|
||||
offlineFrame.v1.keepAlive.ack = ack
|
||||
|
||||
do {
|
||||
if encryptionDone {
|
||||
try encryptAndSendOfflineFrame(offlineFrame)
|
||||
}else{
|
||||
sendFrameAsync(try offlineFrame.serializedData())
|
||||
} else {
|
||||
try sendFrameAsync(offlineFrame.serializedData())
|
||||
}
|
||||
}catch{
|
||||
} catch {
|
||||
log("Error sending KEEP_ALIVE: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
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
|
||||
}
|
||||
@@ -9,147 +9,145 @@ import Foundation
|
||||
import Network
|
||||
import System
|
||||
|
||||
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){
|
||||
self.name=info.name
|
||||
self.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 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 enum NearbyError: Error {
|
||||
case protocolError(_ message: String)
|
||||
case requiredFieldMissing(_ message: String)
|
||||
case ukey2
|
||||
case inputOutput
|
||||
case canceled(reason: CancellationReason)
|
||||
|
||||
public enum NearbyError:Error{
|
||||
case protocolError(_ message:String)
|
||||
case requiredFieldMissing(_ message:String)
|
||||
case ukey2
|
||||
case inputOutput
|
||||
case canceled(reason:CancellationReason)
|
||||
|
||||
public enum CancellationReason{
|
||||
case userRejected, userCanceled, notEnoughSpace, unsupportedType, timedOut
|
||||
}
|
||||
public enum CancellationReason {
|
||||
case userRejected, userCanceled, notEnoughSpace, unsupportedType, timedOut
|
||||
}
|
||||
}
|
||||
|
||||
public struct TransferMetadata{
|
||||
public let files:[FileMetadata]
|
||||
public let id:String
|
||||
public let pinCode:String?
|
||||
public let textDescription:String?
|
||||
|
||||
init(files: [FileMetadata], id: String, pinCode: String?, textDescription: String?=nil){
|
||||
self.files = files
|
||||
self.id = id
|
||||
self.pinCode = pinCode
|
||||
self.textDescription = textDescription
|
||||
}
|
||||
public struct TransferMetadata {
|
||||
public let files: [FileMetadata]
|
||||
public let id: String
|
||||
public let pinCode: String?
|
||||
public let textDescription: String?
|
||||
|
||||
init(files: [FileMetadata], id: String, pinCode: String?, textDescription: String? = nil) {
|
||||
self.files = files
|
||||
self.id = id
|
||||
self.pinCode = pinCode
|
||||
self.textDescription = textDescription
|
||||
}
|
||||
}
|
||||
|
||||
public struct FileMetadata{
|
||||
public let name:String
|
||||
public let size:Int64
|
||||
public let mimeType:String
|
||||
public struct FileMetadata {
|
||||
public let name: String
|
||||
public let size: Int64
|
||||
public let mimeType: String
|
||||
}
|
||||
|
||||
struct FoundServiceInfo{
|
||||
let service:NWBrowser.Result
|
||||
var device:RemoteDeviceInfo?
|
||||
struct FoundServiceInfo {
|
||||
let service: NWBrowser.Result
|
||||
var device: RemoteDeviceInfo?
|
||||
}
|
||||
|
||||
struct OutgoingTransferInfo{
|
||||
let service:NWBrowser.Result
|
||||
let device:RemoteDeviceInfo
|
||||
let connection:OutboundNearbyConnection
|
||||
let delegate:ShareExtensionDelegate
|
||||
struct OutgoingTransferInfo {
|
||||
let service: NWBrowser.Result
|
||||
let device: RemoteDeviceInfo
|
||||
let connection: OutboundNearbyConnection
|
||||
let delegate: ShareExtensionDelegate
|
||||
}
|
||||
|
||||
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=Int(data[0] & 7) >> 1
|
||||
self.name=deviceName
|
||||
self.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)
|
||||
}
|
||||
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 protocol MainAppDelegate{
|
||||
func obtainUserConsent(for transfer:TransferMetadata, from device:RemoteDeviceInfo)
|
||||
func incomingTransfer(id:String, didFinishWith error:Error?)
|
||||
public protocol MainAppDelegate {
|
||||
func obtainUserConsent(for transfer: TransferMetadata, from device: RemoteDeviceInfo)
|
||||
func incomingTransfer(id: String, didFinishWith error: Error?)
|
||||
}
|
||||
|
||||
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 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 class NearbyConnectionManager : NSObject, NetServiceDelegate, InboundNearbyConnectionDelegate, OutboundNearbyConnectionDelegate{
|
||||
|
||||
public class NearbyConnectionManager: NSObject, NetServiceDelegate, InboundNearbyConnectionDelegate, OutboundNearbyConnectionDelegate {
|
||||
public var checkSignature = true {
|
||||
didSet {
|
||||
if !checkSignature {
|
||||
@@ -157,127 +155,125 @@ public class NearbyConnectionManager : NSObject, NetServiceDelegate, InboundNear
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var tcpListener:NWListener;
|
||||
public let endpointID:[UInt8]=generateEndpointID()
|
||||
|
||||
private var tcpListener: NWListener
|
||||
public let endpointID: [UInt8] = generateEndpointID()
|
||||
private var mdnsServices: [NetService] = []
|
||||
private var activeConnections:[String:InboundNearbyConnection]=[:]
|
||||
private var foundServices:[String:FoundServiceInfo]=[:]
|
||||
private var shareExtensionDelegates:[ShareExtensionDelegate]=[]
|
||||
private var outgoingTransfers:[String:OutgoingTransferInfo]=[:]
|
||||
public var mainAppDelegate:(any MainAppDelegate)?
|
||||
private var discoveryRefCount=0
|
||||
|
||||
private var activeConnections: [String: InboundNearbyConnection] = [:]
|
||||
private var foundServices: [String: FoundServiceInfo] = [:]
|
||||
private var shareExtensionDelegates: [ShareExtensionDelegate] = []
|
||||
private var outgoingTransfers: [String: OutgoingTransferInfo] = [:]
|
||||
public var mainAppDelegate: (any MainAppDelegate)?
|
||||
private var discoveryRefCount = 0
|
||||
|
||||
let serviceTypes = ["_FC9F5ED42C8A._tcp."]
|
||||
|
||||
|
||||
private var browsers: [NWBrowser] = []
|
||||
|
||||
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.activeConnections[id]=conn
|
||||
conn.delegate=self
|
||||
conn.start()
|
||||
}
|
||||
tcpListener.start(queue: .global(qos: .utility))
|
||||
}
|
||||
|
||||
|
||||
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.activeConnections[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) {
|
||||
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 { // use 0..<4 instead of 0...3 for clarity
|
||||
id.append(alphabet[Int.random(in: 0..<alphabet.count)])
|
||||
for _ in 0 ..< 4 { // use 0..<4 instead of 0...3 for clarity
|
||||
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
|
||||
0, 0,
|
||||
]
|
||||
let name = Data(nameBytes).urlSafeBase64EncodedString()
|
||||
let endpointInfo = EndpointInfo(name: Host.current().localizedName!, deviceType: .computer)
|
||||
|
||||
let port: Int32 = Int32(tcpListener.port!.rawValue)
|
||||
|
||||
|
||||
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": endpointInfo.serialize().urlSafeBase64EncodedString().data(using: .utf8)!,
|
||||
]))
|
||||
service.publish()
|
||||
return service
|
||||
}
|
||||
}
|
||||
|
||||
func obtainUserConsent(for transfer: TransferMetadata, from device: RemoteDeviceInfo, connection: InboundNearbyConnection) {
|
||||
guard let delegate=mainAppDelegate else {return}
|
||||
delegate.obtainUserConsent(for: transfer, from: device)
|
||||
}
|
||||
|
||||
func connectionWasTerminated(connection:InboundNearbyConnection, error:Error?){
|
||||
guard let delegate=mainAppDelegate else {return}
|
||||
delegate.incomingTransfer(id: connection.id, didFinishWith: error)
|
||||
activeConnections.removeValue(forKey: connection.id)
|
||||
}
|
||||
|
||||
public func submitUserConsent(transferID:String, accept:Bool, storeInTemp: Bool = false){
|
||||
guard let conn=activeConnections[transferID] else {return}
|
||||
|
||||
func obtainUserConsent(for transfer: TransferMetadata, from device: RemoteDeviceInfo, connection _: InboundNearbyConnection) {
|
||||
guard let delegate = mainAppDelegate else { return }
|
||||
delegate.obtainUserConsent(for: transfer, from: device)
|
||||
}
|
||||
|
||||
func connectionWasTerminated(connection: InboundNearbyConnection, error: Error?) {
|
||||
guard let delegate = mainAppDelegate else { return }
|
||||
delegate.incomingTransfer(id: connection.id, didFinishWith: error)
|
||||
activeConnections.removeValue(forKey: connection.id)
|
||||
}
|
||||
|
||||
public func submitUserConsent(transferID: String, accept: Bool, storeInTemp: Bool = false) {
|
||||
guard let conn = activeConnections[transferID] else { return }
|
||||
conn.submitUserConsent(accepted: accept, storeInTemp: storeInTemp)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public func startDeviceDiscovery() {
|
||||
|
||||
log("Device discovery requested.")
|
||||
|
||||
|
||||
if discoveryRefCount == 0 {
|
||||
foundServices.removeAll()
|
||||
|
||||
log("Starting device discovery")
|
||||
|
||||
|
||||
if browsers.isEmpty {
|
||||
for type in serviceTypes {
|
||||
|
||||
log("Starting browser for type \(type)")
|
||||
|
||||
|
||||
let browser = NWBrowser(for: .bonjourWithTXTRecord(type: type, domain: nil), using: .tcp)
|
||||
browser.browseResultsChangedHandler = { newResults, changes in
|
||||
browser.browseResultsChangedHandler = { _, changes in
|
||||
for change in changes {
|
||||
switch change {
|
||||
case let .added(res):
|
||||
@@ -286,7 +282,6 @@ public class NearbyConnectionManager : NSObject, NetServiceDelegate, InboundNear
|
||||
self.removeFoundDevice(service: res)
|
||||
default:
|
||||
log("Ignoring change \(change)")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -298,138 +293,136 @@ public class NearbyConnectionManager : NSObject, NetServiceDelegate, InboundNear
|
||||
|
||||
discoveryRefCount += 1
|
||||
}
|
||||
|
||||
|
||||
public func stopDeviceDiscovery(){
|
||||
discoveryRefCount -= 1
|
||||
assert(discoveryRefCount >= 0)
|
||||
|
||||
|
||||
public func stopDeviceDiscovery() {
|
||||
discoveryRefCount -= 1
|
||||
assert(discoveryRefCount >= 0)
|
||||
|
||||
if discoveryRefCount == 0 {
|
||||
for browser in browsers {
|
||||
browser.cancel()
|
||||
}
|
||||
|
||||
|
||||
browsers.removeAll()
|
||||
}
|
||||
}
|
||||
|
||||
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){
|
||||
}
|
||||
}
|
||||
|
||||
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("found service \(service)")
|
||||
for interface in service.interfaces{
|
||||
if case .loopback=interface.type{
|
||||
for interface in service.interfaces {
|
||||
if case .loopback = interface.type {
|
||||
log("ignoring localhost service")
|
||||
return
|
||||
}
|
||||
}
|
||||
guard let endpointID=endpointID(for: service) else {return}
|
||||
return
|
||||
}
|
||||
}
|
||||
guard let endpointID = endpointID(for: service) else { return }
|
||||
log("service name is valid, endpoint ID \(endpointID)")
|
||||
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]){
|
||||
guard let info=foundServices[deviceID] else {return}
|
||||
let tcp=NWProtocolTCP.Options.init()
|
||||
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)
|
||||
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)
|
||||
}
|
||||
|
||||
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]) {
|
||||
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)
|
||||
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 activeConnections.count
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,433 +5,433 @@
|
||||
// Created by Grishka on 23.09.2023.
|
||||
//
|
||||
|
||||
import CommonCrypto
|
||||
import CryptoKit
|
||||
import Foundation
|
||||
import Network
|
||||
import CryptoKit
|
||||
import CommonCrypto
|
||||
import System
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
import SwiftECC
|
||||
import BigInt
|
||||
import SwiftECC
|
||||
|
||||
class OutboundNearbyConnection:NearbyConnection{
|
||||
private var currentState:State = .initial
|
||||
private let urlsToSend:[URL]
|
||||
private var ukeyClientFinishMsgData:Data?
|
||||
private var queue:[OutgoingFileTransfer]=[]
|
||||
private var currentTransfer:OutgoingFileTransfer?
|
||||
public var delegate:OutboundNearbyConnectionDelegate?
|
||||
private var totalBytesToSend:Int64=0
|
||||
private var totalBytesSent:Int64=0
|
||||
private var cancelled:Bool=false
|
||||
private var textPayloadID:Int64=0
|
||||
|
||||
enum State{
|
||||
case initial, sentUkeyClientInit, sentUkeyClientFinish, sentPairedKeyEncryption, sentPairedKeyResult, sentIntroduction, sendingFiles
|
||||
}
|
||||
|
||||
init(connection: NWConnection, id: String, urlsToSend:[URL]){
|
||||
self.urlsToSend=urlsToSend
|
||||
super.init(connection: connection, id: id)
|
||||
if urlsToSend.count==1 && !urlsToSend[0].isFileURL{
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
do{
|
||||
log("received \(frameData), state is \(currentState)")
|
||||
switch currentState {
|
||||
case .initial:
|
||||
protocolError()
|
||||
case .sentUkeyClientInit:
|
||||
try processUkey2ServerInit(frame: try Securegcm_Ukey2Message(serializedBytes: frameData), raw: frameData)
|
||||
case .sentUkeyClientFinish:
|
||||
try processConnectionResponse(frame: try Location_Nearby_Connections_OfflineFrame(serializedBytes: frameData))
|
||||
default:
|
||||
let smsg=try Securemessage_SecureMessage(serializedBytes: frameData)
|
||||
try decryptAndProcessReceivedSecureMessage(smsg)
|
||||
}
|
||||
}catch{
|
||||
if case NearbyError.ukey2=error{
|
||||
}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("Transfer canceled")
|
||||
try sendDisconnectionAndDisconnect()
|
||||
delegate?.outboundConnection(connection: self, failedWithError: NearbyError.canceled(reason: .userCanceled))
|
||||
return
|
||||
}
|
||||
class OutboundNearbyConnection: NearbyConnection {
|
||||
private var currentState: State = .initial
|
||||
private let urlsToSend: [URL]
|
||||
private var ukeyClientFinishMsgData: Data?
|
||||
private var queue: [OutgoingFileTransfer] = []
|
||||
private var currentTransfer: OutgoingFileTransfer?
|
||||
public var delegate: OutboundNearbyConnectionDelegate?
|
||||
private var totalBytesToSend: Int64 = 0
|
||||
private var totalBytesSent: Int64 = 0
|
||||
private var cancelled: Bool = false
|
||||
private var textPayloadID: Int64 = 0
|
||||
|
||||
enum State {
|
||||
case initial, sentUkeyClientInit, sentUkeyClientFinish, sentPairedKeyEncryption, sentPairedKeyResult, sentIntroduction, sendingFiles
|
||||
}
|
||||
|
||||
init(connection: NWConnection, id: String, urlsToSend: [URL]) {
|
||||
self.urlsToSend = urlsToSend
|
||||
super.init(connection: connection, id: id)
|
||||
if urlsToSend.count == 1 && !urlsToSend[0].isFileURL {
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
do {
|
||||
log("received \(frameData), state is \(currentState)")
|
||||
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 {
|
||||
if case NearbyError.ukey2 = error {
|
||||
} 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("Transfer canceled")
|
||||
try sendDisconnectionAndDisconnect()
|
||||
delegate?.outboundConnection(connection: self, failedWithError: NearbyError.canceled(reason: .userCanceled))
|
||||
return
|
||||
}
|
||||
log(frame.debugDescription)
|
||||
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("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]
|
||||
sendFrameAsync(try 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)
|
||||
throw NearbyError.ukey2
|
||||
}
|
||||
let serverInit=try Securegcm_Ukey2ServerInit(serializedBytes: frame.messageData)
|
||||
guard serverInit.version==1 else{
|
||||
sendUkey2Alert(type: .badVersion)
|
||||
throw NearbyError.ukey2
|
||||
}
|
||||
guard serverInit.random.count==32 else{
|
||||
sendUkey2Alert(type: .badRandom)
|
||||
throw NearbyError.ukey2
|
||||
}
|
||||
guard serverInit.handshakeCipher == .p256Sha512 else{
|
||||
sendUkey2Alert(type: .badHandshakeCipher)
|
||||
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
|
||||
sendFrameAsync(try resp.serializedData())
|
||||
|
||||
encryptionDone=true
|
||||
delegate?.outboundConnectionWasEstablished(connection: self)
|
||||
}
|
||||
|
||||
private func processConnectionResponse(frame:Location_Nearby_Connections_OfflineFrame) throws{
|
||||
log("connection response: \(frame)")
|
||||
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 urlsToSend.count==1 && !urlsToSend[0].isFileURL{
|
||||
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{
|
||||
if #available(macOS 11.0, *){
|
||||
let type=UTType(typeID)
|
||||
if let type=type, let mimeType=type.preferredMIMEType{
|
||||
meta.mimeType=mimeType
|
||||
}
|
||||
}else{
|
||||
if let mimeType=UTTypeCopyPreferredTagWithClass(typeID as CFString, kUTTagClassMIMEType){
|
||||
meta.mimeType=(mimeType.takeRetainedValue() as NSString) as String
|
||||
}
|
||||
}
|
||||
}
|
||||
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)
|
||||
queue.append(OutgoingFileTransfer(url: url, payloadID: meta.payloadID, handle: try FileHandle(forReadingFrom: url), totalBytes: meta.size, currentOffset: 0))
|
||||
introduction.v1.introduction.fileMetadata.append(meta)
|
||||
totalBytesToSend+=meta.size
|
||||
}
|
||||
}
|
||||
log("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 urlsToSend.count==1 && !urlsToSend[0].isFileURL{
|
||||
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 sendURL() throws{
|
||||
try sendBytesPayload(data: Data(urlsToSend[0].absoluteString.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("Disconnecting because all files have been transferred")
|
||||
try sendDisconnectionAndDisconnect()
|
||||
delegate?.outboundConnectionTransferFinished(connection: self)
|
||||
return
|
||||
}
|
||||
currentTransfer=queue.removeFirst()
|
||||
}
|
||||
|
||||
let fileBuffer:Data
|
||||
if #available(macOS 10.15.4, *) {
|
||||
guard let _fileBuffer=try currentTransfer!.handle!.read(upToCount: 512*1024) else{
|
||||
throw NearbyError.inputOutput
|
||||
}
|
||||
fileBuffer=_fileBuffer
|
||||
} else {
|
||||
fileBuffer=currentTransfer!.handle!.readData(ofLength: 512*1024)
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
})
|
||||
log("sent file chunk, current transfer: \(String(describing: currentTransfer))")
|
||||
totalBytesSent+=Int64(fileBuffer.count)
|
||||
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("sent EOF, current transfer: \(String(describing: currentTransfer))")
|
||||
}
|
||||
}
|
||||
|
||||
private static func sanitizeFileName(name:String)->String{
|
||||
return name.replacingOccurrences(of: "[\\/\\\\?%\\*:\\|\"<>=]", with: "_", options: .regularExpression)
|
||||
}
|
||||
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("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)
|
||||
throw NearbyError.ukey2
|
||||
}
|
||||
let serverInit = try Securegcm_Ukey2ServerInit(serializedBytes: frame.messageData)
|
||||
guard serverInit.version == 1 else {
|
||||
sendUkey2Alert(type: .badVersion)
|
||||
throw NearbyError.ukey2
|
||||
}
|
||||
guard serverInit.random.count == 32 else {
|
||||
sendUkey2Alert(type: .badRandom)
|
||||
throw NearbyError.ukey2
|
||||
}
|
||||
guard serverInit.handshakeCipher == .p256Sha512 else {
|
||||
sendUkey2Alert(type: .badHandshakeCipher)
|
||||
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 {
|
||||
log("connection response: \(frame)")
|
||||
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 urlsToSend.count == 1 && !urlsToSend[0].isFileURL {
|
||||
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 {
|
||||
if #available(macOS 11.0, *) {
|
||||
let type = UTType(typeID)
|
||||
if let type = type, let mimeType = type.preferredMIMEType {
|
||||
meta.mimeType = mimeType
|
||||
}
|
||||
} else {
|
||||
if let mimeType = UTTypeCopyPreferredTagWithClass(typeID as CFString, kUTTagClassMIMEType) {
|
||||
meta.mimeType = (mimeType.takeRetainedValue() as NSString) as String
|
||||
}
|
||||
}
|
||||
}
|
||||
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("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 urlsToSend.count == 1 && !urlsToSend[0].isFileURL {
|
||||
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 sendURL() throws {
|
||||
try sendBytesPayload(data: Data(urlsToSend[0].absoluteString.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("Disconnecting because all files have been transferred")
|
||||
try sendDisconnectionAndDisconnect()
|
||||
delegate?.outboundConnectionTransferFinished(connection: self)
|
||||
return
|
||||
}
|
||||
currentTransfer = queue.removeFirst()
|
||||
}
|
||||
|
||||
let fileBuffer: Data
|
||||
if #available(macOS 10.15.4, *) {
|
||||
guard let _fileBuffer = try currentTransfer!.handle!.read(upToCount: 512 * 1024) else {
|
||||
throw NearbyError.inputOutput
|
||||
}
|
||||
fileBuffer = _fileBuffer
|
||||
} else {
|
||||
fileBuffer = currentTransfer!.handle!.readData(ofLength: 512 * 1024)
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
})
|
||||
log("sent file chunk, current transfer: \(String(describing: currentTransfer))")
|
||||
totalBytesSent += Int64(fileBuffer.count)
|
||||
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("sent EOF, current transfer: \(String(describing: currentTransfer))")
|
||||
}
|
||||
}
|
||||
|
||||
private static func sanitizeFileName(name: String) -> String {
|
||||
return name.replacingOccurrences(of: "[\\/\\\\?%\\*:\\|\"<>=]", with: "_", options: .regularExpression)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate struct OutgoingFileTransfer{
|
||||
let url:URL
|
||||
let payloadID:Int64
|
||||
let handle:FileHandle?
|
||||
let totalBytes:Int64
|
||||
var currentOffset:Int64
|
||||
private struct OutgoingFileTransfer {
|
||||
let url: URL
|
||||
let payloadID: Int64
|
||||
let handle: FileHandle?
|
||||
let totalBytes: Int64
|
||||
var currentOffset: Int64
|
||||
}
|
||||
|
||||
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)
|
||||
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)
|
||||
}
|
||||
Reference in new issue
Block a user