From 637bd14a76bbf33799d2b853d92c6edcd6f73263 Mon Sep 17 00:00:00 2001 From: philips77 Date: Wed, 28 Apr 2021 12:21:52 +0200 Subject: [PATCH 1/2] Force unwrapping fixed (#394) --- .../GenericDFU/DFUPeripheral.swift | 18 +- .../Characteristics/DFUControlPoint.swift | 107 ++++----- .../Characteristics/DFUVersion.swift | 39 ++-- .../Peripherals/LegacyDFUPeripheral.swift | 27 ++- .../Characteristics/ButtonlessDFU.swift | 112 +++++----- .../SecureDFUControlPoint.swift | 208 +++++++++--------- .../DFU/SecureDFUServiceInitiator.swift | 3 +- .../Peripheral/SecureDFUPeripheral.swift | 53 ++--- .../SecureDFU/Services/SecureDFUService.swift | 91 ++++---- 9 files changed, 347 insertions(+), 311 deletions(-) diff --git a/iOSDFULibrary/Classes/Implementation/GenericDFU/DFUPeripheral.swift b/iOSDFULibrary/Classes/Implementation/GenericDFU/DFUPeripheral.swift index 6b6cb25a..200f9f05 100644 --- a/iOSDFULibrary/Classes/Implementation/GenericDFU/DFUPeripheral.swift +++ b/iOSDFULibrary/Classes/Implementation/GenericDFU/DFUPeripheral.swift @@ -161,16 +161,15 @@ internal class BaseDFUPeripheral : NSObject, BaseDF let name = peripheral.name ?? "Unknown device" logger.i("Connected to \(name)") - let dfuService = findDfuService(in: peripheral.services) - if dfuService == nil { + if let dfuService = findDfuService(in: peripheral.services) { + // A DFU service was found, congratulations! + logger.i("Services discovered") + peripheralDidDiscoverDfuService(dfuService) + } else { // DFU service has not been found, but it doesn't matter it's not // there. Perhaps the user's application didn't discover it. // Let's discover DFU services. discoverServices() - } else { - // A DFU service was found, congratulations! - logger.i("Services discovered") - peripheralDidDiscoverDfuService(dfuService!) } } } @@ -181,13 +180,14 @@ internal class BaseDFUPeripheral : NSObject, BaseDF } func disconnect() { - if peripheral!.state == .connected { + guard let peripheral = peripheral else { return } + if peripheral.state == .connected { logger.v("Disconnecting...") } else { logger.v("Cancelling connection...") } logger.d("centralManager.cancelPeripheralConnection(peripheral)") - centralManager.cancelPeripheralConnection(peripheral!) + centralManager.cancelPeripheralConnection(peripheral) } func destroy() { @@ -703,7 +703,7 @@ internal class BaseCommonDFUPeripheral Void return } - if error != nil { + if let error = error { logger.e("Reading DFU Version characteristic failed") - logger.e(error!) + logger.e(error) report?(.readingVersionFailed, "Reading DFU Version characteristic failed") - } else { - let data = characteristic.value - logger.i("Read Response received from \(characteristic.uuid.uuidString), value\(data != nil && data!.count > 0 ? " (0x): " + data!.hexString : ": 0 bytes")") - - // Validate data length - if data == nil || data!.count != 2 { - logger.w("Invalid value: 2 bytes expected") - report?(.readingVersionFailed, "Unsupported DFU Version: \(data != nil && data!.count > 0 ? "0x" + data!.hexString : "no value")") - return - } - - // Read major and minor - let minor: UInt8 = data![0] - let major: UInt8 = data![1] - - logger.a("Version number read: \(major).\(minor)") - success?(major, minor) + return } + + let data = characteristic.value + logger.i("Read Response received from \(characteristic.uuid.uuidString), value\(data != nil && data!.count > 0 ? " (0x): " + data!.hexString : ": 0 bytes")") + + // Validate data length + if data == nil || data!.count != 2 { + logger.w("Invalid value: 2 bytes expected") + report?(.readingVersionFailed, "Unsupported DFU Version: \(data != nil && data!.count > 0 ? "0x" + data!.hexString : "no value")") + return + } + + // Read major and minor + let minor: UInt8 = data![0] + let major: UInt8 = data![1] + + logger.a("Version number read: \(major).\(minor)") + success?(major, minor) } } diff --git a/iOSDFULibrary/Classes/Implementation/LegacyDFU/Peripherals/LegacyDFUPeripheral.swift b/iOSDFULibrary/Classes/Implementation/LegacyDFU/Peripherals/LegacyDFUPeripheral.swift index 12a0bca9..91fadf08 100644 --- a/iOSDFULibrary/Classes/Implementation/LegacyDFU/Peripherals/LegacyDFUPeripheral.swift +++ b/iOSDFULibrary/Classes/Implementation/LegacyDFU/Peripherals/LegacyDFUPeripheral.swift @@ -44,7 +44,7 @@ internal class LegacyDFUPeripheral : BaseCommonDFUPeripheral Bool { - let applicationMode = dfuService!.isInApplicationMode() ?? !forceDfu + let applicationMode = dfuService?.isInApplicationMode() ?? !forceDfu if applicationMode { logger.w("Application with buttonless update found") @@ -82,9 +82,10 @@ internal class LegacyDFUPeripheral : BaseCommonDFUPeripheral DFUError. - let offset = characteristic.uuid.isEqual(uuidHelper.buttonlessExperimentalCharacteristic) ? 9000 : 90 - report?(DFUError(rawValue: Int(dfuResponse.status!.code) + offset)!, dfuResponse.status!.description) - } - } else { - logger.e("Unknown response received: 0x\(characteristic.value!.hexString)") - report?(.unsupportedResponse, "Unsupported response received: 0x\(characteristic.value!.hexString)") - } + logger.i("Notification received from \(characteristic.uuid.uuidString), value (0x):\(characteristicValue.hexString)") } + + // Parse response received. + let dfuResponse = ButtonlessDFUResponse(characteristicValue) + guard let dfuResponse = dfuResponse else { + logger.e("Unknown response received: 0x\(characteristicValue.hexString)") + report?(.unsupportedResponse, "Unsupported response received: 0x\(characteristicValue.hexString)") + return + } + + guard dfuResponse.status == .success else { + logger.e("Error \(dfuResponse.status.code): \(dfuResponse.status.description)") + // The returned errod code is incremented by 90 or 9000 to match Buttonless DFU or + // Experimental Buttonless DFU remote codes. + // See DFUServiceDelegate.swift -> DFUError. + let offset = characteristic.uuid.isEqual(uuidHelper.buttonlessExperimentalCharacteristic) ? 9000 : 90 + report?(DFUError(rawValue: Int(dfuResponse.status.code) + offset)!, dfuResponse.status.description) + return + } + + logger.a("\(dfuResponse.description) received") + success?() } } diff --git a/iOSDFULibrary/Classes/Implementation/SecureDFU/Characteristics/SecureDFUControlPoint.swift b/iOSDFULibrary/Classes/Implementation/SecureDFU/Characteristics/SecureDFUControlPoint.swift index e58e39b6..eb0271f1 100644 --- a/iOSDFULibrary/Classes/Implementation/SecureDFU/Characteristics/SecureDFUControlPoint.swift +++ b/iOSDFULibrary/Classes/Implementation/SecureDFU/Characteristics/SecureDFUControlPoint.swift @@ -176,32 +176,32 @@ internal enum SecureDFUResultCode : UInt8 { } } -internal typealias SecureDFUResponseCallback = (_ response : SecureDFUResponse?) -> Void +internal typealias SecureDFUResponseCallback = (_ response : SecureDFUResponse) -> Void internal struct SecureDFUResponse { - let opCode : SecureDFUOpCode? - let requestOpCode : SecureDFUOpCode? - let status : SecureDFUResultCode? + let opCode : SecureDFUOpCode + let requestOpCode : SecureDFUOpCode + let status : SecureDFUResultCode let maxSize : UInt32? let offset : UInt32? let crc : UInt32? let error : SecureDFUExtendedErrorCode? init?(_ data: Data) { - let opCode : UInt8 = data[0] - let requestOpCode : UInt8 = data[1] - let status : UInt8 = data[2] + // The response has at least 3 bytes. + guard data.count >= 3 else { return nil } + let opCode = SecureDFUOpCode(rawValue: data[0]) + let requestOpCode = SecureDFUOpCode(rawValue: data[1]) + let status = SecureDFUResultCode(rawValue: data[2]) - self.opCode = SecureDFUOpCode(rawValue: opCode) - self.requestOpCode = SecureDFUOpCode(rawValue: requestOpCode) - self.status = SecureDFUResultCode(rawValue: status) - - // Parse response data in case of a success - if self.status == .success { - switch self.requestOpCode { - case .some(.readObjectInfo): + switch status { + case .success: + // Parse response data in case of a success. + switch requestOpCode { + case .readObjectInfo: // The correct reponse for Read Object Info has additional 12 bytes: // Max Object Size, Offset and CRC. + guard data.count == 15 else { return nil } let maxSize : UInt32 = data.asValue(offset: 3) let offset : UInt32 = data.asValue(offset: 7) let crc : UInt32 = data.asValue(offset: 11) @@ -210,9 +210,10 @@ internal struct SecureDFUResponse { self.offset = offset self.crc = crc self.error = nil - case .some(.calculateChecksum): + case .calculateChecksum: // The correct reponse for Calculate Checksum has additional 8 bytes: // Offset and CRC. + guard data.count == 11 else { return nil } let offset : UInt32 = data.asValue(offset: 3) let crc : UInt32 = data.asValue(offset: 7) @@ -221,83 +222,87 @@ internal struct SecureDFUResponse { self.crc = crc self.error = nil default: - self.maxSize = 0 - self.offset = 0 - self.crc = 0 + self.maxSize = nil + self.offset = nil + self.crc = nil self.error = nil } - } else if self.status == .extendedError { + case .extendedError: // If extended error was received, parse the extended error code // The correct response for Read Error request has 4 bytes. // The 4th byte is the extended error code. - let error : UInt8 = data[3] + guard data.count == 4 else { return nil } + guard let error = SecureDFUExtendedErrorCode(rawValue: data[3]) else { return nil } - self.maxSize = 0 - self.offset = 0 - self.crc = 0 - self.error = SecureDFUExtendedErrorCode(rawValue: error) - } else { - self.maxSize = 0 - self.offset = 0 - self.crc = 0 + self.maxSize = nil + self.offset = nil + self.crc = nil + self.error = error + default: + self.maxSize = nil + self.offset = nil + self.crc = nil self.error = nil } - if self.opCode != .responseCode || self.requestOpCode == nil || self.status == nil { + if opCode != .responseCode || requestOpCode == nil || status == nil { return nil } + + self.opCode = opCode! + self.requestOpCode = requestOpCode! + self.status = status! } var description: String { - if status == .success { + switch status { + case .extendedError: + if let error = error { + return "Response (Op Code = \(requestOpCode.rawValue), Status = \(status.rawValue), Extended Error \(error.rawValue) = \(error.description))" + } else { + return "Response (Op Code = \(requestOpCode.rawValue), Status = \(status.rawValue), Unsupported Extended Error value)" + } + case .success: switch requestOpCode { - case .some(.readObjectInfo): + case .readObjectInfo: // Max size for a command object is usually around 256. Let's say 1024, // just to be sure. This is only for logging, so may be wrong. return String(format: "\(maxSize! > 1024 ? "Data" : "Command") object info (Max size = \(maxSize!), Offset = \(offset!), CRC = %08X)", crc!) - case .some(.calculateChecksum): + case .calculateChecksum: return String(format: "Checksum (Offset = \(offset!), CRC = %08X)", crc!) default: // Other responses are either not logged, or logged by service or executor, // so this 'default' should never be called. break } - } else if status == .extendedError { - if let error = error { - return "Response (Op Code = \(requestOpCode!.rawValue), Status = \(status!.rawValue), Extended Error \(error.rawValue) = \(error.description))" - } else { - return "Response (Op Code = \(requestOpCode!.rawValue), Status = \(status!.rawValue), Unsupported Extended Error value)" - } + fallthrough + default: + return "Response (Op Code = \(requestOpCode.rawValue), Status = \(status.rawValue))" } - return "Response (Op Code = \(requestOpCode!.rawValue), Status = \(status!.rawValue))" } } internal struct SecureDFUPacketReceiptNotification { - let opCode : SecureDFUOpCode? - let requestOpCode : SecureDFUOpCode? - let resultCode : SecureDFUResultCode? + let opCode : SecureDFUOpCode + let requestOpCode : SecureDFUOpCode + let resultCode : SecureDFUResultCode let offset : UInt32 let crc : UInt32 init?(_ data: Data) { - let opCode : UInt8 = data[0] - let requestOpCode : UInt8 = data[1] - let resultCode : UInt8 = data[2] - - self.opCode = SecureDFUOpCode(rawValue: opCode) - self.requestOpCode = SecureDFUOpCode(rawValue: requestOpCode) - self.resultCode = SecureDFUResultCode(rawValue: resultCode) + guard data.count == 11 else { return nil } + + let opCode = SecureDFUOpCode(rawValue: data[0]) + let requestOpCode = SecureDFUOpCode(rawValue: data[1]) + let resultCode = SecureDFUResultCode(rawValue: data[2]) - if self.opCode != .responseCode { - return nil - } - if self.requestOpCode != .calculateChecksum { - return nil - } - if self.resultCode != .success { - return nil - } + guard opCode == .responseCode else { return nil } + guard requestOpCode == .calculateChecksum else { return nil } + guard resultCode == .success else { return nil } + + self.opCode = opCode! + self.requestOpCode = requestOpCode! + self.resultCode = resultCode! let offset : UInt32 = data.asValue(offset: 3) let crc : UInt32 = data.asValue(offset: 7) @@ -497,53 +502,58 @@ internal class SecureDFUControlPoint : NSObject, CBPeripheralDelegate, DFUCharac return } - if error != nil { + if let error = error { // This characteristic is never read, the error may only pop up when notification // is received. logger.e("Receiving notification failed") - logger.e(error!) + logger.e(error) report?(.receivingNotificationFailed, "Receiving notification failed") - } else { - // During the upload we may get either a Packet Receipt Notification, or a Response - // with status code. - if proceed != nil { - if let prn = SecureDFUPacketReceiptNotification(characteristic.value!) { - proceed!(prn.offset) // The CRC is not verified after receiving a PRN, only the offset is. - return - } + return + } + + guard let characteristicValue = characteristic.value else { return } + + // During the upload we may get either a Packet Receipt Notification, or a Response + // with status code. + if proceed != nil { + if let prn = SecureDFUPacketReceiptNotification(characteristicValue) { + proceed!(prn.offset) // The CRC is not verified after receiving a PRN, only the offset is. + return } - // Otherwise... - logger.i("Notification received from \(characteristic.uuid.uuidString), value (0x): \(characteristic.value!.hexString)") + } + // Otherwise... + logger.i("Notification received from \(characteristic.uuid.uuidString), value (0x): \(characteristicValue.hexString)") - // Parse response received. - let dfuResponse = SecureDFUResponse(characteristic.value!) - if let dfuResponse = dfuResponse { - if dfuResponse.status == .success { - switch dfuResponse.requestOpCode! { - case .readObjectInfo, .calculateChecksum: - logger.a("\(dfuResponse.description) received") - response?(dfuResponse) - case .createObject, .setPRNValue, .execute: - // Don't log, executor or service will do it for us. - success?() - default: - logger.a("\(dfuResponse.description) received") - success?() - } - } else if dfuResponse.status == .extendedError { - // An extended error was received. - logger.e("Error \(dfuResponse.error!.code): \(dfuResponse.error!.description)") - // The returned errod code is incremented by 20 to match Secure DFU remote codes. - report?(DFUError(rawValue: Int(dfuResponse.error!.code) + 20)!, dfuResponse.error!.description) - } else { - logger.e("Error \(dfuResponse.status!.code): \(dfuResponse.status!.description)") - // The returned errod code is incremented by 10 to match Secure DFU remote codes. - report?(DFUError(rawValue: Int(dfuResponse.status!.code) + 10)!, dfuResponse.status!.description) - } - } else { - logger.e("Unknown response received: 0x\(characteristic.value!.hexString)") - report?(.unsupportedResponse, "Unsupported response received: 0x\(characteristic.value!.hexString)") + // Parse response received. + let dfuResponse = SecureDFUResponse(characteristicValue) + guard let dfuResponse = dfuResponse else { + logger.e("Unknown response received: 0x\(characteristicValue.hexString)") + report?(.unsupportedResponse, "Unsupported response received: 0x\(characteristicValue.hexString)") + return + } + + switch dfuResponse.status { + case .success: + switch dfuResponse.requestOpCode { + case .readObjectInfo, .calculateChecksum: + logger.a("\(dfuResponse.description) received") + response?(dfuResponse) + case .createObject, .setPRNValue, .execute: + // Don't log, executor or service will do it for us. + success?() + default: + logger.a("\(dfuResponse.description) received") + success?() } + case .extendedError: + // An extended error was received. + logger.e("Error \(dfuResponse.error!.code): \(dfuResponse.error!.description)") + // The returned errod code is incremented by 20 to match Secure DFU remote codes. + report?(DFUError(rawValue: Int(dfuResponse.error!.code) + 20)!, dfuResponse.error!.description) + default: + logger.e("Error \(dfuResponse.status.code): \(dfuResponse.status.description)") + // The returned errod code is incremented by 10 to match Secure DFU remote codes. + report?(DFUError(rawValue: Int(dfuResponse.status.code) + 10)!, dfuResponse.status.description) } } diff --git a/iOSDFULibrary/Classes/Implementation/SecureDFU/DFU/SecureDFUServiceInitiator.swift b/iOSDFULibrary/Classes/Implementation/SecureDFU/DFU/SecureDFUServiceInitiator.swift index 9ae0b1c5..d87c83cb 100644 --- a/iOSDFULibrary/Classes/Implementation/SecureDFU/DFU/SecureDFUServiceInitiator.swift +++ b/iOSDFULibrary/Classes/Implementation/SecureDFU/DFU/SecureDFUServiceInitiator.swift @@ -36,8 +36,7 @@ import CoreBluetooth // The firmware file must be specified before calling `start(...)`. guard let _ = file else { delegateQueue.async { - self.delegate?.dfuError(.fileNotSpecified, didOccurWithMessage: - "Firmware not specified") + self.delegate?.dfuError(.fileNotSpecified, didOccurWithMessage: "Firmware not specified") } return nil } diff --git a/iOSDFULibrary/Classes/Implementation/SecureDFU/Peripheral/SecureDFUPeripheral.swift b/iOSDFULibrary/Classes/Implementation/SecureDFU/Peripheral/SecureDFUPeripheral.swift index f6c4f985..997f6e9d 100644 --- a/iOSDFULibrary/Classes/Implementation/SecureDFU/Peripheral/SecureDFUPeripheral.swift +++ b/iOSDFULibrary/Classes/Implementation/SecureDFU/Peripheral/SecureDFUPeripheral.swift @@ -73,14 +73,14 @@ internal class SecureDFUPeripheral : BaseCommonDFUPeripheral Bool { - let applicationMode = dfuService!.isInApplicationMode() ?? !forceDfu + let applicationMode = dfuService?.isInApplicationMode() ?? !forceDfu if applicationMode { logger.w("Application with buttonless update found") @@ -97,15 +97,16 @@ internal class SecureDFUPeripheral : BaseCommonDFUPeripheral, of firmware: DFUFirmware, andReportProgressTo progress: DFUProgressDelegate?, on queue: DispatchQueue) { - dfuService!.sendNextObject(from: range, of: firmware, + dfuService?.sendNextObject(from: range, of: firmware, andReportProgressTo: progress, on: queue, onSuccess: { self.delegate?.peripheralDidReceiveObject() }, onError: defaultErrorCallback @@ -228,7 +229,7 @@ internal class SecureDFUPeripheral : BaseCommonDFUPeripheral ~15 packets without // PRNs may lead to buffer overflow. - dfuService!.sendInitPacket(withdata: packetData) + dfuService?.sendInitPacket(withdata: packetData) self.delegate?.peripheralDidReceiveInitPacket() } @@ -253,10 +254,10 @@ internal class SecureDFUPeripheral : BaseCommonDFUPeripheral Bool { - if !aborted && paused && firmware != nil { + guard let dfuPacketCharacteristic = dfuPacketCharacteristic, + let range = range, + let firmware = firmware, + let progressQueue = progressQueue, + let success = success, + !aborted && paused else { paused = false - dfuPacketCharacteristic!.sendNext(packetReceiptNotificationNumber ?? 0, - packetsFrom: range!, of: firmware!, - andReportProgressTo: progressDelegate, on: progressQueue!, - andCompletionTo: success!) return paused } paused = false + dfuPacketCharacteristic.sendNext(packetReceiptNotificationNumber ?? 0, + packetsFrom: range, of: firmware, + andReportProgressTo: progressDelegate, on: progressQueue, + andCompletionTo: success) return paused } @@ -111,8 +116,7 @@ import CoreBluetooth // When upload has been started and paused, we have to send the Reset command here // as the device will not get a Packet Receipt Notification. If it hasn't been paused, // the Reset command will be sent after receiving it, on line 292. - if paused && firmware != nil { - let _report = report! + if let _report = report, paused && firmware != nil { firmware = nil range = nil success = nil @@ -164,12 +168,12 @@ import CoreBluetooth onError report: @escaping ErrorCallback) { if !aborted { // Support for Buttonless DFU Service - if buttonlessDfuCharacteristic != nil { - buttonlessDfuCharacteristic!.enable(onSuccess: success, onError: report) + if let buttonlessDfuCharacteristic = buttonlessDfuCharacteristic { + buttonlessDfuCharacteristic.enable(onSuccess: success, onError: report) return } // End - dfuControlPointCharacteristic!.enableNotifications(onSuccess: success, + dfuControlPointCharacteristic?.enableNotifications(onSuccess: success, onError: report) } else { sendReset(onError: report) @@ -185,7 +189,7 @@ import CoreBluetooth func readCommandObjectInfo(onReponse response: @escaping SecureDFUResponseCallback, onError report: @escaping ErrorCallback) { if !aborted { - dfuControlPointCharacteristic!.send(.readCommandObjectInfo, + dfuControlPointCharacteristic?.send(.readCommandObjectInfo, onResponse: response, onError: report) } else { sendReset(onError: report) @@ -201,7 +205,7 @@ import CoreBluetooth func readDataObjectInfo(onReponse response: @escaping SecureDFUResponseCallback, onError report: @escaping ErrorCallback) { if !aborted { - dfuControlPointCharacteristic!.send(.readDataObjectInfo, + dfuControlPointCharacteristic?.send(.readDataObjectInfo, onResponse: response, onError: report) } else { sendReset(onError: report) @@ -219,7 +223,7 @@ import CoreBluetooth func createCommandObject(withLength length: UInt32, onSuccess success: @escaping Callback, onError report: @escaping ErrorCallback) { if !aborted { - dfuControlPointCharacteristic!.send(.createCommandObject(withSize: length), + dfuControlPointCharacteristic?.send(.createCommandObject(withSize: length), onSuccess: success, onError:report) } else { sendReset(onError: report) @@ -236,7 +240,7 @@ import CoreBluetooth func createDataObject(withLength length: UInt32, onSuccess success: @escaping Callback, onError report: @escaping ErrorCallback) { if !aborted { - dfuControlPointCharacteristic!.send(.createDataObject(withSize: length), + dfuControlPointCharacteristic?.send(.createDataObject(withSize: length), onSuccess: success, onError:report) } else { sendReset(onError: report) @@ -281,7 +285,7 @@ import CoreBluetooth func calculateChecksumCommand(onSuccess response: @escaping SecureDFUResponseCallback, onError report: @escaping ErrorCallback) { if !aborted { - dfuControlPointCharacteristic!.send(SecureDFURequest.calculateChecksumCommand, + dfuControlPointCharacteristic?.send(SecureDFURequest.calculateChecksumCommand, onResponse: response, onError: report) } else { sendReset(onError: report) @@ -312,7 +316,7 @@ import CoreBluetooth private func sendReset(onError report: @escaping ErrorCallback) { aborted = true // There is no command to reset a Secure DFU device. We can just disconnect. - targetPeripheral!.disconnect() + targetPeripheral?.disconnect() } //MARK: - Packet commands @@ -325,7 +329,7 @@ import CoreBluetooth - parameter packetData: Data to be sent as Init Packet. */ func sendInitPacket(withdata packetData: Data){ - dfuPacketCharacteristic!.sendInitPacket(packetData) + dfuPacketCharacteristic?.sendInitPacket(packetData) } /** @@ -352,8 +356,7 @@ import CoreBluetooth self.progressDelegate = progressDelegate self.progressQueue = queue - self.report = { - error, message in + let _report: ErrorCallback = { error, message in self.firmware = nil self.range = nil self.success = nil @@ -362,19 +365,24 @@ import CoreBluetooth self.progressQueue = nil report(error, message) } - self.success = { + let _success: Callback = { self.firmware = nil self.range = nil self.success = nil self.report = nil self.progressDelegate = nil self.progressQueue = nil - self.dfuControlPointCharacteristic!.peripheralDidReceiveObject() + self.dfuControlPointCharacteristic?.peripheralDidReceiveObject() success() - } as Callback + } + self.report = _report + self.success = _success - dfuControlPointCharacteristic!.waitUntilUploadComplete(onSuccess: self.success!, - onPacketReceiptNofitication: { bytesReceived in + dfuControlPointCharacteristic?.waitUntilUploadComplete( + onSuccess: _success, + onPacketReceiptNofitication: { [weak self] bytesReceived in + guard let self = self, + let dfuPacketCharacteristic = self.dfuPacketCharacteristic else { return } // This callback is called from SecureDFUControlPoint in 2 cases: when a PRN // is received (`bytesReceived` contains number of bytes reported), or when the // iOS reports the `peripheralIsReady(toSendWriteWithoutResponse:)` callback @@ -387,12 +395,12 @@ import CoreBluetooth } if !self.paused && !self.aborted { - let bytesSent = self.dfuPacketCharacteristic!.bytesSent + UInt32(range.lowerBound) + let bytesSent = dfuPacketCharacteristic.bytesSent + UInt32(range.lowerBound) if peripheralIsReadyToSendWriteWithoutRequest || bytesSent == bytesReceived! { - self.dfuPacketCharacteristic!.sendNext(self.packetReceiptNotificationNumber ?? 0, - packetsFrom: range, of: firmware, - andReportProgressTo: progressDelegate, on: queue, - andCompletionTo: self.success!) + dfuPacketCharacteristic.sendNext(self.packetReceiptNotificationNumber ?? 0, + packetsFrom: range, of: firmware, + andReportProgressTo: progressDelegate, on: queue, + andCompletionTo: _success) } else { // Target device deported invalid number of bytes received report(.bytesLost, "\(bytesSent) bytes were sent while \(bytesReceived!) bytes were reported as received") @@ -405,19 +413,21 @@ import CoreBluetooth self.progressDelegate = nil self.sendReset(onError: report) } - }, onError: self.report!) + }, + onError: _report + ) // A new object is started, reset counters before sending the next object. // It must be done even if the upload was paused, otherwise it would be // resumed from a wrong place. - dfuPacketCharacteristic!.resetCounters() + dfuPacketCharacteristic?.resetCounters() if !paused && !aborted { - // ...and start sending firmware if - dfuPacketCharacteristic!.sendNext(packetReceiptNotificationNumber ?? 0, + // ...and start sending firmware. + dfuPacketCharacteristic?.sendNext(packetReceiptNotificationNumber ?? 0, packetsFrom: range, of: firmware, andReportProgressTo: progressDelegate, on: queue, - andCompletionTo: self.success!) + andCompletionTo: _success) } else if aborted { self.firmware = nil self.range = nil @@ -439,9 +449,9 @@ import CoreBluetooth self.success = nil self.report = nil - guard error == nil else { + if let error = error { logger.e("Characteristics discovery failed") - logger.e(error!) + logger.e(error) _report?(.serviceDiscoveryFailed, "Characteristics discovery failed") return } @@ -560,20 +570,21 @@ import CoreBluetooth func jumpToBootloaderMode(withAlternativeAdvertisingName name: String?, onSuccess success: @escaping Callback, onError report: @escaping ErrorCallback) { + guard let buttonlessDfuCharacteristic = buttonlessDfuCharacteristic else { return } if !aborted { func enterBootloader() { // The method above may reset the device before it sents a response to // the request. We will call the success callback right here. success() - self.buttonlessDfuCharacteristic!.send(ButtonlessDFURequest.enterBootloader, - onSuccess: nil, onError: report) + buttonlessDfuCharacteristic.send(ButtonlessDFURequest.enterBootloader, + onSuccess: nil, onError: report) } // If the device may support setting alternative advertising name in the // bootloader mode, try it. - if let name = name, buttonlessDfuCharacteristic!.maySupportSettingName { + if let name = name, buttonlessDfuCharacteristic.maySupportSettingName { logger.v("Trying setting bootloader name to \(name)") - buttonlessDfuCharacteristic!.send(ButtonlessDFURequest.set(name: name), + buttonlessDfuCharacteristic.send(ButtonlessDFURequest.set(name: name), onSuccess: { // Success. The buttonless service is from SDK 14.0+. // The bootloader, after jumping to it, will advertise with this name. From eb0fd34b55c68006f0bbfcf730664f4be38877b1 Mon Sep 17 00:00:00 2001 From: philips77 Date: Wed, 28 Apr 2021 16:12:20 +0200 Subject: [PATCH 2/2] Force unwrapping fixed, part 2 (#394) --- .../GenericDFU/DFUExecutor.swift | 4 +- .../GenericDFU/DFUPeripheral.swift | 64 +++---- .../Characteristics/DFUControlPoint.swift | 94 +++++----- .../LegacyDFU/Characteristics/DFUPacket.swift | 27 +-- .../Characteristics/DFUVersion.swift | 2 +- .../Peripherals/LegacyDFUPeripheral.swift | 4 +- .../LegacyDFU/Services/LegacyDFUService.swift | 173 +++++++++++------- .../Characteristics/ButtonlessDFU.swift | 17 +- .../SecureDFUControlPoint.swift | 86 +++++---- .../Characteristics/SecureDFUPacket.swift | 14 +- .../Peripheral/SecureDFUPeripheral.swift | 4 +- .../SecureDFU/Services/SecureDFUService.swift | 36 ++-- 12 files changed, 280 insertions(+), 245 deletions(-) diff --git a/iOSDFULibrary/Classes/Implementation/GenericDFU/DFUExecutor.swift b/iOSDFULibrary/Classes/Implementation/GenericDFU/DFUExecutor.swift index 322b0d25..2e7b53d4 100644 --- a/iOSDFULibrary/Classes/Implementation/GenericDFU/DFUExecutor.swift +++ b/iOSDFULibrary/Classes/Implementation/GenericDFU/DFUExecutor.swift @@ -117,7 +117,9 @@ extension BaseDFUExecutor { func error(_ error: DFUError, didOccurWithMessage message: String) { if error == .bluetoothDisabled { - delegate{ $0.dfuError(.bluetoothDisabled, didOccurWithMessage: message) } + delegate { + $0.dfuError(.bluetoothDisabled, didOccurWithMessage: message) + } // Release the cyclic reference. peripheral.destroy() return diff --git a/iOSDFULibrary/Classes/Implementation/GenericDFU/DFUPeripheral.swift b/iOSDFULibrary/Classes/Implementation/GenericDFU/DFUPeripheral.swift index 200f9f05..bbc7e83a 100644 --- a/iOSDFULibrary/Classes/Implementation/GenericDFU/DFUPeripheral.swift +++ b/iOSDFULibrary/Classes/Implementation/GenericDFU/DFUPeripheral.swift @@ -101,8 +101,8 @@ internal class BaseDFUPeripheral : NSObject, BaseDF internal let experimentalButtonlessServiceInSecureDfuEnabled: Bool /// Default error callback. internal var defaultErrorCallback: ErrorCallback { - return { (error, message) in - self.delegate?.error(error, didOccurWithMessage: message) + return { [weak self] error, message in + self?.delegate?.error(error, didOccurWithMessage: message) } } @@ -155,9 +155,8 @@ internal class BaseDFUPeripheral : NSObject, BaseDF } self.peripheral = peripheral - if peripheral.state != .connected { - connect() - } else { + switch peripheral.state { + case .connected: let name = peripheral.name ?? "Unknown device" logger.i("Connected to \(name)") @@ -171,6 +170,8 @@ internal class BaseDFUPeripheral : NSObject, BaseDF // Let's discover DFU services. discoverServices() } + default: + connect() } } @@ -239,10 +240,11 @@ internal class BaseDFUPeripheral : NSObject, BaseDF stateAsString = "Unknown" } logger.d("[Callback] Central Manager did update state to: \(stateAsString)") - if central.state == .poweredOn { + switch central.state { + case .poweredOn: // We are now ready to rumble! start() - } else { + default: // The device has been already disconnected if it was connected. delegate?.error(.bluetoothDisabled, didOccurWithMessage: "Bluetooth adapter powered off") destroy() @@ -339,11 +341,10 @@ internal class BaseDFUPeripheral : NSObject, BaseDF // MARK: - Peripheral Delegate methods func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { - guard error == nil else { + if let error = error { logger.e("Services discovery failed") - logger.e(error!) - delegate?.error(.serviceDiscoveryFailed, didOccurWithMessage: - "Services discovery failed") + logger.e(error) + delegate?.error(.serviceDiscoveryFailed, didOccurWithMessage: "Services discovery failed") return } @@ -370,8 +371,7 @@ internal class BaseDFUPeripheral : NSObject, BaseDF logger.d("Did you connect to the correct target? It might be that the previous services were cached: toggle Bluetooth from iOS settings to clear cache. Also, ensure the device contains the Service Changed characteristic") // The device does not support DFU, nor buttonless jump - delegate?.error(.deviceNotSupported, didOccurWithMessage: - "DFU Service not found") + delegate?.error(.deviceNotSupported, didOccurWithMessage: "DFU Service not found") return } // A DFU service was found, congratulations! @@ -409,7 +409,7 @@ internal class BaseDFUPeripheral : NSObject, BaseDF This method should reset the device, preferably switching it to application mode. */ func resetDevice() { - if peripheral != nil && peripheral!.state != .disconnected { + if let peripheral = peripheral, peripheral.state != .disconnected { disconnect() } else { peripheralDidDisconnect() @@ -424,27 +424,17 @@ internal class BaseDFUPeripheral : NSObject, BaseDF - returns: A `DFUService` type if a DFU service has been found, or `nil` if services are `nil` or the list does not contain any supported DFU Service. */ - private func findDfuService(in services:[CBService]?) -> CBService? { - if let services = services { - for service in services { - // Skip the experimental Buttonless DFU Service if this feature wasn't enabled. - if experimentalButtonlessServiceInSecureDfuEnabled && service.matches(uuid: uuidHelper.buttonlessExperimentalService) { - // The experimental Buttonless DFU Service for Secure DFU has been found. - return service - } - - if service.matches(uuid: uuidHelper.secureDFUService) { - // Secure DFU Service has been found. - return service - } - - if service.matches(uuid: uuidHelper.legacyDFUService) { - // Legacy DFU Service has been found. - return service - } - } + private func findDfuService(in services: [CBService]?) -> CBService? { + return services?.first { service in + // The experimental Buttonless DFU Service for Secure DFU has been found. + // Skip the experimental Buttonless DFU Service if this feature wasn't enabled. + (experimentalButtonlessServiceInSecureDfuEnabled && + service.matches(uuid: uuidHelper.buttonlessExperimentalService)) || + // Secure DFU Service has been found. + service.matches(uuid: uuidHelper.secureDFUService) || + // Legacy DFU Service has been found. + service.matches(uuid: uuidHelper.legacyDFUService) } - return nil } /** @@ -651,7 +641,7 @@ internal class BaseCommonDFUPeripheral 0xFFFF bytes (LegacyDFUService:L446). - let bytesReceived: UInt32 = data.asValue(offset: 1) - self.bytesReceived = bytesReceived + self.bytesReceived = data.asValue(offset: 1) } } @@ -309,9 +306,9 @@ internal struct PacketReceiptNotification { func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) { - if error != nil { + if let error = error { logger.e("Enabling notifications failed. Check if Service Changed service is enabled.") - logger.e(error!) + logger.e(error) // Note: // Error 253: Unknown ATT error. // This most proably is a caching issue. Check if your device had @@ -319,11 +316,12 @@ internal struct PacketReceiptNotification { // app and bootloader modes. For bonded devices make sure it sends // the Service Changed indication after connecting. report?(.enablingControlPointFailed, "Enabling notifications failed") - } else { - logger.v("Notifications enabled for \(characteristic.uuid.uuidString)") - logger.a("DFU Control Point notifications enabled") - success?() + return } + + logger.v("Notifications enabled for \(characteristic.uuid.uuidString)") + logger.a("DFU Control Point notifications enabled") + success?() } func peripheral(_ peripheral: CBPeripheral, @@ -338,11 +336,14 @@ internal struct PacketReceiptNotification { guard self.characteristic.isEqual(characteristic) else { return } + guard let request = request else { + return + } - if error != nil { + if let error = error { if !resetSent { logger.e("Writing to characteristic failed. Check if Service Changed characteristic is enabled.") - logger.e(error!) + logger.e(error) // Note: // Error 3: Writing is not permitted // This most proably is caching issue. Check if your device had @@ -354,32 +355,33 @@ internal struct PacketReceiptNotification { // When a 'JumpToBootloader', 'Activate and Reset' or 'Reset' // command is sent the device may reset before sending the acknowledgement. // This is not a blocker, as the device did disconnect and reset successfully. - logger.a("\(request!.description) request sent") + logger.a("\(request.description) request sent") logger.w("Device disconnected before sending ACK") - logger.w(error!) + logger.w(error) success?() } - } else { - logger.i("Data written to \(characteristic.uuid.uuidString)") - - switch request! { - case .startDfu(_), .startDfu_v1, .validateFirmware: - logger.a("\(request!.description) request sent") - // do not call success until we get a notification - case .jumpToBootloader, .activateAndReset, .reset, .packetReceiptNotificationRequest(_): - logger.a("\(request!.description) request sent") - // there will be no notification send after these requests, call - // `success()` immediatelly (for `.receiveFirmwareImage` the notification - // will be sent after firmware upload is complete) + return + } + + logger.i("Data written to \(characteristic.uuid.uuidString)") + + switch request { + case .startDfu(_), .startDfu_v1, .validateFirmware: + logger.a("\(request.description) request sent") + // do not call success until we get a notification + case .jumpToBootloader, .activateAndReset, .reset, .packetReceiptNotificationRequest(_): + logger.a("\(request.description) request sent") + // there will be no notification send after these requests, call + // `success()` immediatelly (for `.receiveFirmwareImage` the notification + // will be sent after firmware upload is complete) + success?() + case .initDfuParameters(_), .initDfuParameters_v1: + // Log was created before sending the Op Code. + // Do not call success until we get a notification. + break + case .receiveFirmwareImage: + if proceed == nil { success?() - case .initDfuParameters(_), .initDfuParameters_v1: - // Log was created before sending the Op Code. - // Do not call success until we get a notification. - break - case .receiveFirmwareImage: - if proceed == nil { - success?() - } } } } diff --git a/iOSDFULibrary/Classes/Implementation/LegacyDFU/Characteristics/DFUPacket.swift b/iOSDFULibrary/Classes/Implementation/LegacyDFU/Characteristics/DFUPacket.swift index c5c73fa0..775edf81 100644 --- a/iOSDFULibrary/Classes/Implementation/LegacyDFU/Characteristics/DFUPacket.swift +++ b/iOSDFULibrary/Classes/Implementation/LegacyDFU/Characteristics/DFUPacket.swift @@ -69,10 +69,10 @@ internal class DFUPacket: DFUCharacteristic { // Get the peripheral object let peripheral = characteristic.service.peripheral - var data = Data(capacity: 12) - data += size.softdevice.littleEndian - data += size.bootloader.littleEndian - data += size.application.littleEndian + let data = Data() + + size.softdevice.littleEndian + + size.bootloader.littleEndian + + size.application.littleEndian let packetUUID = characteristic.uuid.uuidString @@ -91,9 +91,8 @@ internal class DFUPacket: DFUCharacteristic { // Get the peripheral object. let peripheral = characteristic.service.peripheral - var data = Data(capacity: 4) - data += size.application.littleEndian - + let data = Data() + size.application.littleEndian + let packetUUID = characteristic.uuid.uuidString logger.v("Writing image size (\(size.application)b) to characteristic \(packetUUID)...") @@ -167,14 +166,15 @@ internal class DFUPacket: DFUCharacteristic { lastTime = startTime // Notify progress delegate that upload has started (0%). - queue.async(execute: { + queue.async { progress?.dfuProgressDidChange( for: firmware.currentPart, outOf: firmware.parts, to: 0, currentSpeedBytesPerSecond: 0.0, - avgSpeedBytesPerSecond: 0.0) - }) + avgSpeedBytesPerSecond: 0.0 + ) + } } while packetsToSendNow > 0 { @@ -212,14 +212,15 @@ internal class DFUPacket: DFUCharacteristic { lastTime = now bytesSentSinceProgessNotification = bytesSent - queue.async(execute: { + queue.async { progress?.dfuProgressDidChange( for: firmware.currentPart, outOf: firmware.parts, to: Int(currentProgress), currentSpeedBytesPerSecond: currentSpeed, - avgSpeedBytesPerSecond: avgSpeed) - }) + avgSpeedBytesPerSecond: avgSpeed + ) + } progressReported = currentProgress } } diff --git a/iOSDFULibrary/Classes/Implementation/LegacyDFU/Characteristics/DFUVersion.swift b/iOSDFULibrary/Classes/Implementation/LegacyDFU/Characteristics/DFUVersion.swift index 33240eaa..38567c0e 100644 --- a/iOSDFULibrary/Classes/Implementation/LegacyDFU/Characteristics/DFUVersion.swift +++ b/iOSDFULibrary/Classes/Implementation/LegacyDFU/Characteristics/DFUVersion.swift @@ -97,7 +97,7 @@ internal typealias VersionCallback = (_ major: UInt8, _ minor: UInt8) -> Void logger.i("Read Response received from \(characteristic.uuid.uuidString), value\(data != nil && data!.count > 0 ? " (0x): " + data!.hexString : ": 0 bytes")") // Validate data length - if data == nil || data!.count != 2 { + if data?.count != 2 { logger.w("Invalid value: 2 bytes expected") report?(.readingVersionFailed, "Unsupported DFU Version: \(data != nil && data!.count > 0 ? "0x" + data!.hexString : "no value")") return diff --git a/iOSDFULibrary/Classes/Implementation/LegacyDFU/Peripherals/LegacyDFUPeripheral.swift b/iOSDFULibrary/Classes/Implementation/LegacyDFU/Peripherals/LegacyDFUPeripheral.swift index 91fadf08..ef23c6a5 100644 --- a/iOSDFULibrary/Classes/Implementation/LegacyDFU/Peripherals/LegacyDFUPeripheral.swift +++ b/iOSDFULibrary/Classes/Implementation/LegacyDFU/Peripherals/LegacyDFUPeripheral.swift @@ -88,7 +88,7 @@ internal class LegacyDFUPeripheral : BaseCommonDFUPeripheral Bool { - if !aborted && paused && firmware != nil { + guard let dfuPacketCharacteristic = dfuPacketCharacteristic, + let firmware = firmware, + let progressQueue = progressQueue, + !aborted && paused else { paused = false - // onSuccess and onError callbacks are still kept by dfuControlPointCharacteristic. - dfuPacketCharacteristic!.sendNext(packetReceiptNotificationNumber, - packetsOf: firmware!, - andReportProgressTo: progressDelegate, - on: progressQueue!) return paused } paused = false + // onSuccess and onError callbacks are still kept by dfuControlPointCharacteristic. + dfuPacketCharacteristic.sendNext(packetReceiptNotificationNumber, + packetsOf: firmware, + andReportProgressTo: progressDelegate, + on: progressQueue) return paused } @@ -123,8 +126,7 @@ import CoreBluetooth // When upload has been started and paused, we have to send the Reset command // here as the device will not get a Packet Receipt Notification. If it hasn't // been paused, the Reset command will be sent after receiving it, on line 380. - if paused && firmware != nil { - let _report = report! + if let _report = report, paused && firmware != nil { firmware = nil success = nil report = nil @@ -192,8 +194,8 @@ import CoreBluetooth // - we must be in the DFU mode already (otherwise the device would be useless...). // Note: On iOS the Generic Access and Generic Attribute services (nor HID Service) // are not returned during service discovery. - let services = service.peripheral.services! - if services.count == 1 { + let services = service.peripheral.services + if services?.count == 1 { return false } // If there are more services than just DFU Service, the state is uncertain. @@ -226,7 +228,7 @@ import CoreBluetooth func enableControlPoint(onSuccess success: @escaping Callback, onError report: @escaping ErrorCallback) { if !aborted { - dfuControlPointCharacteristic!.enableNotifications(onSuccess: success, onError: report) + dfuControlPointCharacteristic?.enableNotifications(onSuccess: success, onError: report) } else { sendReset(onError: report) } @@ -239,7 +241,7 @@ import CoreBluetooth */ func jumpToBootloaderMode(onError report: @escaping ErrorCallback) { if !aborted { - dfuControlPointCharacteristic!.send(Request.jumpToBootloader, + dfuControlPointCharacteristic?.send(Request.jumpToBootloader, onSuccess: nil, onError: report) } else { sendReset(onError: report) @@ -248,8 +250,8 @@ import CoreBluetooth /** This methods sends the Start DFU command with the firmware type to the DFU Control - Point characterristic, followed by the sizes of each firware component - (each as UInt32, Little Endian). + Point characterristic, followed by the sizes of each firware component: + softdevice, bootloader, application (each as UInt32, Little Endian). - parameter type: The type of the current firmware part. - parameter size: The sizes of firmware components in the current part. @@ -257,7 +259,8 @@ import CoreBluetooth - parameter report: A callback called when a response with an error status is received. */ func sendDfuStart(withFirmwareType type: UInt8, andSize size: DFUFirmwareSize, - onSuccess success: @escaping Callback, onError report: @escaping ErrorCallback) { + onSuccess success: @escaping Callback, + onError report: @escaping ErrorCallback) { guard !aborted else { sendReset(onError: report) return @@ -276,20 +279,25 @@ import CoreBluetooth // it would receive a response with status = 6 (Operation failed) after sending // some firmware packets. Delay 1 sec seems to work while 600 ms was too short. // The time seems to be required to prepare flash(?). - let sendStartDfu = { + let sendStartDfu = { [weak self] in + guard let self = self else { return } // 1. Sends the Start DFU command with the firmware type to DFU Control Point // characteristic. // 2. Sends firmware sizes to DFU Packet characteristic. // 3. Receives response notification and calls onSuccess or onError. - self.dfuControlPointCharacteristic!.send(Request.startDfu(type: type), onSuccess: success) { (error, aMessage) in - if error == .remoteLegacyDFUInvalidState { - self.targetPeripheral!.shouldReconnect = true - self.sendReset(onError: report) - return + self.dfuControlPointCharacteristic?.send(Request.startDfu(type: type), + onSuccess: success, + onError: { [weak self] error, message in + guard let self = self else { return } + if error == .remoteLegacyDFUInvalidState { + self.targetPeripheral?.shouldReconnect = true + self.sendReset(onError: report) + return + } + report(error, message) } - report(error, aMessage) - } - self.dfuPacketCharacteristic!.sendFirmwareSize(size) + ) + self.dfuPacketCharacteristic?.sendFirmwareSize(size) } if version != nil { // The legacy DFU bootloader from SDK 7.0+ does not require delay. @@ -304,15 +312,15 @@ import CoreBluetooth /** This methods sends the old Start DFU command (without the firmware type) to the - DFU Control Point characterristic, followed by the application size - (UInt32, Little Endian). + DFU Control Point characterristic, followed by the application size (UInt32, Little Endian). - parameter size: The sizes of firmware components in the current part. - parameter success: A callback called when a response with status Success is received. - parameter report: A callback called when a response with an error status is received. */ func sendStartDfu(withFirmwareSize size: DFUFirmwareSize, - onSuccess success: @escaping Callback, onError report: @escaping ErrorCallback) { + onSuccess success: @escaping Callback, + onError report: @escaping ErrorCallback) { guard !aborted else { sendReset(onError: report) return @@ -320,21 +328,24 @@ import CoreBluetooth // See comment in sendDfuStart(withFirmwareType:andSize:onSuccess:onError) above logger.d("wait(1000)") - queue.asyncAfter(deadline: .now() + .milliseconds(1000)) { + queue.asyncAfter(deadline: .now() + .milliseconds(1000)) { [weak self] in + guard let self = self else { return } // 1. Sends the Start DFU command with the firmware type to the DFU Control Point // characteristic. // 2. Sends firmware sizes to the DFU Packet characteristic. // 3. Receives response notification and calls onSuccess or onError. - self.dfuControlPointCharacteristic!.send(Request.startDfu_v1, onSuccess: success) { - (error, aMessage) in - if error == .remoteLegacyDFUInvalidState { - self.targetPeripheral!.shouldReconnect = true - self.sendReset(onError: report) - return - } - report(error, aMessage) - } - self.dfuPacketCharacteristic!.sendFirmwareSize_v1(size) + self.dfuControlPointCharacteristic?.send(Request.startDfu_v1, + onSuccess: success, + onError: { [weak self] error, message in + guard let self = self else { return } + if error == .remoteLegacyDFUInvalidState { + self.targetPeripheral!.shouldReconnect = true + self.sendReset(onError: report) + return + } + report(error, message) + }) + self.dfuPacketCharacteristic?.sendFirmwareSize_v1(size) } } @@ -352,7 +363,8 @@ import CoreBluetooth - parameter report: A callback called when a response with an error status is received. */ func sendInitPacket(_ data: Data, - onSuccess success: @escaping Callback, onError report: @escaping ErrorCallback) { + onSuccess success: @escaping Callback, + onError report: @escaping ErrorCallback) { guard !aborted else { sendReset(onError: report) return @@ -378,11 +390,16 @@ import CoreBluetooth // Therefore, there are 2 commands to the DFU Control Point required: one // before we start sending init packet, and another one the whole init packet // is sent. After sending the second packet a notification will be received. - dfuControlPointCharacteristic!.send(Request.initDfuParameters(req: InitDfuParametersRequest.receiveInitPacket), onSuccess: nil, onError: report) - dfuPacketCharacteristic!.sendInitPacket(data) - dfuControlPointCharacteristic!.send(Request.initDfuParameters(req: InitDfuParametersRequest.initPacketComplete), onSuccess: success, - onError: { - error, message in + dfuControlPointCharacteristic?.send( + Request.initDfuParameters(req: InitDfuParametersRequest.receiveInitPacket), + onSuccess: nil, + onError: report + ) + dfuPacketCharacteristic?.sendInitPacket(data) + dfuControlPointCharacteristic?.send( + Request.initDfuParameters(req: InitDfuParametersRequest.initPacketComplete), + onSuccess: success, + onError: { error, message in if error == .remoteLegacyDFUOperationFailed { // Init packet validation failed. The device type, revision, app // version or SoftDevice version does not match values specified @@ -400,9 +417,9 @@ import CoreBluetooth // After receiving this packet the DFU target was sending a notification with // status. if data.count == 2 { - dfuControlPointCharacteristic!.send(Request.initDfuParameters_v1, + dfuControlPointCharacteristic?.send(Request.initDfuParameters_v1, onSuccess: success, onError: report) - dfuPacketCharacteristic!.sendInitPacket(data) + dfuPacketCharacteristic?.sendInitPacket(data) } else { // After sending the Extended Init Packet, the DFU would fail on CRC // validation eventually. @@ -436,7 +453,7 @@ import CoreBluetooth onError report: @escaping ErrorCallback) { if !aborted { packetReceiptNotificationNumber = prnValue - dfuControlPointCharacteristic!.send(Request.packetReceiptNotificationRequest(number: prnValue), + dfuControlPointCharacteristic?.send(Request.packetReceiptNotificationRequest(number: prnValue), onSuccess: success, onError: report) } else { sendReset(onError: report) @@ -472,11 +489,13 @@ import CoreBluetooth // 2. Sends firmware to the DFU Packet characteristic. If number > 0 it will receive // Packet Receit Notifications every number packets. // 3. Receives response notification and calls onSuccess or onError. - dfuControlPointCharacteristic!.send(Request.receiveFirmwareImage, - onSuccess: { + dfuControlPointCharacteristic?.send(Request.receiveFirmwareImage, + onSuccess: { [weak self] in + guard let self = self else { return } // Register callbacks for Packet Receipt Notifications/Responses. - self.dfuControlPointCharacteristic!.waitUntilUploadComplete( - onSuccess: { + self.dfuControlPointCharacteristic?.waitUntilUploadComplete( + onSuccess: { [weak self] in + guard let self = self else { return } // Upload is completed, release the temporary parameters. self.firmware = nil self.report = nil @@ -484,8 +503,8 @@ import CoreBluetooth self.progressQueue = nil success() }, - onPacketReceiptNofitication: { - bytesReceived in + onPacketReceiptNofitication: { [weak self] bytesReceived in + guard let self = self else { return } // This callback is called from SecureDFUControlPoint in 2 cases: // when a PRN is received (bytesReceived contains number of bytes // reported), or when the iOS reports the @@ -499,14 +518,21 @@ import CoreBluetooth // Each time a PRN is received, send next bunch of packets if !self.paused && !self.aborted { - let bytesSent = self.dfuPacketCharacteristic!.bytesSent + guard let dfuPacketCharacteristic = self.dfuPacketCharacteristic else { + self.firmware = nil + self.report = nil + self.progressDelegate = nil + self.progressQueue = nil + return + } + let bytesSent = dfuPacketCharacteristic.bytesSent // Due to https://github.com/NordicSemiconductor/IOS-Pods-DFU-Library/issues/54 // only 16 least significant bits are verified. if peripheralIsReadyToSendWriteWithoutRequest || (bytesSent & 0xFFFF) == (bytesReceived! & 0xFFFF) { - self.dfuPacketCharacteristic!.sendNext(self.packetReceiptNotificationNumber, - packetsOf: firmware, - andReportProgressTo: progress, on: queue) + dfuPacketCharacteristic.sendNext(self.packetReceiptNotificationNumber, + packetsOf: firmware, + andReportProgressTo: progress, on: queue) } else { // Target device deported invalid number of bytes received report(.bytesLost, "\(bytesSent) bytes were sent while \(bytesReceived!) bytes were reported as received") @@ -521,8 +547,8 @@ import CoreBluetooth self.sendReset(onError: report) } }, - onError: { - error, message in + onError: { [weak self] error, message in + guard let self = self else { return } // Upload failed, release the temporary parameters. self.firmware = nil self.report = nil @@ -533,10 +559,11 @@ import CoreBluetooth ) // ...and start sending firmware. if !self.paused && !self.aborted { - let start = { + let start = { [weak self] in + guard let self = self else { return } self.logger.a("Uploading firmware...") self.logger.v("Sending firmware to DFU Packet characteristic...") - self.dfuPacketCharacteristic!.sendNext(self.packetReceiptNotificationNumber, + self.dfuPacketCharacteristic?.sendNext(self.packetReceiptNotificationNumber, packetsOf: firmware, andReportProgressTo: progress, on: queue) } @@ -558,7 +585,8 @@ import CoreBluetooth self.sendReset(onError: report) } }, - onError: report) + onError: report + ) } /** @@ -570,7 +598,7 @@ import CoreBluetooth func sendValidateFirmwareRequest(onSuccess success: @escaping Callback, onError report: @escaping ErrorCallback) { if !aborted { - dfuControlPointCharacteristic!.send(Request.validateFirmware, + dfuControlPointCharacteristic?.send(Request.validateFirmware, onSuccess: success, onError: report) } else { sendReset(onError: report) @@ -585,7 +613,7 @@ import CoreBluetooth */ func sendActivateAndResetRequest(onError report: @escaping ErrorCallback) { if !aborted { - dfuControlPointCharacteristic!.send(Request.activateAndReset, + dfuControlPointCharacteristic?.send(Request.activateAndReset, onSuccess: nil, onError: report) } else { sendReset(onError: report) @@ -600,7 +628,7 @@ import CoreBluetooth - parameter report: A callback called when writing characteristic failed. */ func sendReset(onError report: @escaping ErrorCallback) { - dfuControlPointCharacteristic!.send(Request.reset, onSuccess: nil, onError: report) + dfuControlPointCharacteristic?.send(Request.reset, onSuccess: nil, onError: report) } // MARK: - Private service API methods @@ -614,13 +642,13 @@ import CoreBluetooth */ private func readDfuVersion(onSuccess success: @escaping Callback, onError report: @escaping ErrorCallback) { - dfuVersionCharacteristic!.readVersion( - onSuccess: { - major, minor in + dfuVersionCharacteristic?.readVersion( + onSuccess: { [weak self] major, minor in + guard let self = self else { return } self.version = (major, minor) success() }, - onError:report + onError: report ) } @@ -693,7 +721,12 @@ import CoreBluetooth _report?(.readingVersionFailed, "DFU Version found, but does not have the Read property") return } - readDfuVersion(onSuccess: _success!, onError: _report!) + guard let _success = _success, + let _report = _report else { + // This should never happen. + return + } + readDfuVersion(onSuccess: _success, onError: _report) } else { // Else... proceed. version = nil diff --git a/iOSDFULibrary/Classes/Implementation/SecureDFU/Characteristics/ButtonlessDFU.swift b/iOSDFULibrary/Classes/Implementation/SecureDFU/Characteristics/ButtonlessDFU.swift index d10702c0..cbb1ac2c 100644 --- a/iOSDFULibrary/Classes/Implementation/SecureDFU/Characteristics/ButtonlessDFU.swift +++ b/iOSDFULibrary/Classes/Implementation/SecureDFU/Characteristics/ButtonlessDFU.swift @@ -100,18 +100,17 @@ internal struct ButtonlessDFUResponse { init?(_ data: Data) { // The correct response is always 3 bytes long: Response Op Code, // Request Op Code and Status. - guard data.count == 3 else { return nil } - let opCode = ButtonlessDFUOpCode(rawValue: data[0]) - let requestOpCode = ButtonlessDFUOpCode(rawValue: data[1]) - let status = ButtonlessDFUResultCode(rawValue: data[2]) - - if opCode != .responseCode || requestOpCode == nil || status == nil { + guard data.count == 3, + let opCode = ButtonlessDFUOpCode(rawValue: data[0]), + let requestOpCode = ButtonlessDFUOpCode(rawValue: data[1]), + let status = ButtonlessDFUResultCode(rawValue: data[2]), + opCode == .responseCode else { return nil } - self.opCode = opCode! - self.requestOpCode = requestOpCode! - self.status = status! + self.opCode = opCode + self.requestOpCode = requestOpCode + self.status = status } var description: String { diff --git a/iOSDFULibrary/Classes/Implementation/SecureDFU/Characteristics/SecureDFUControlPoint.swift b/iOSDFULibrary/Classes/Implementation/SecureDFU/Characteristics/SecureDFUControlPoint.swift index eb0271f1..f8a0380b 100644 --- a/iOSDFULibrary/Classes/Implementation/SecureDFU/Characteristics/SecureDFUControlPoint.swift +++ b/iOSDFULibrary/Classes/Implementation/SecureDFU/Characteristics/SecureDFUControlPoint.swift @@ -189,10 +189,13 @@ internal struct SecureDFUResponse { init?(_ data: Data) { // The response has at least 3 bytes. - guard data.count >= 3 else { return nil } - let opCode = SecureDFUOpCode(rawValue: data[0]) - let requestOpCode = SecureDFUOpCode(rawValue: data[1]) - let status = SecureDFUResultCode(rawValue: data[2]) + guard data.count >= 3, + let opCode = SecureDFUOpCode(rawValue: data[0]), + let requestOpCode = SecureDFUOpCode(rawValue: data[1]), + let status = SecureDFUResultCode(rawValue: data[2]), + opCode == .responseCode else { + return nil + } switch status { case .success: @@ -231,8 +234,10 @@ internal struct SecureDFUResponse { // If extended error was received, parse the extended error code // The correct response for Read Error request has 4 bytes. // The 4th byte is the extended error code. - guard data.count == 4 else { return nil } - guard let error = SecureDFUExtendedErrorCode(rawValue: data[3]) else { return nil } + guard data.count == 4, + let error = SecureDFUExtendedErrorCode(rawValue: data[3]) else { + return nil + } self.maxSize = nil self.offset = nil @@ -244,14 +249,10 @@ internal struct SecureDFUResponse { self.crc = nil self.error = nil } - - if opCode != .responseCode || requestOpCode == nil || status == nil { - return nil - } - self.opCode = opCode! - self.requestOpCode = requestOpCode! - self.status = status! + self.opCode = opCode + self.requestOpCode = requestOpCode + self.status = status } var description: String { @@ -259,9 +260,8 @@ internal struct SecureDFUResponse { case .extendedError: if let error = error { return "Response (Op Code = \(requestOpCode.rawValue), Status = \(status.rawValue), Extended Error \(error.rawValue) = \(error.description))" - } else { - return "Response (Op Code = \(requestOpCode.rawValue), Status = \(status.rawValue), Unsupported Extended Error value)" } + return "Response (Op Code = \(requestOpCode.rawValue), Status = \(status.rawValue), Unsupported Extended Error value)" case .success: switch requestOpCode { case .readObjectInfo: @@ -290,19 +290,19 @@ internal struct SecureDFUPacketReceiptNotification { let crc : UInt32 init?(_ data: Data) { - guard data.count == 11 else { return nil } - - let opCode = SecureDFUOpCode(rawValue: data[0]) - let requestOpCode = SecureDFUOpCode(rawValue: data[1]) - let resultCode = SecureDFUResultCode(rawValue: data[2]) - - guard opCode == .responseCode else { return nil } - guard requestOpCode == .calculateChecksum else { return nil } - guard resultCode == .success else { return nil } + guard data.count == 11, + let opCode = SecureDFUOpCode(rawValue: data[0]), + let requestOpCode = SecureDFUOpCode(rawValue: data[1]), + let resultCode = SecureDFUResultCode(rawValue: data[2]), + opCode == .responseCode, + requestOpCode == .calculateChecksum, + resultCode == .success else { + return nil + } - self.opCode = opCode! - self.requestOpCode = requestOpCode! - self.resultCode = resultCode! + self.opCode = opCode + self.requestOpCode = requestOpCode + self.resultCode = resultCode let offset : UInt32 = data.asValue(offset: 3) let crc : UInt32 = data.asValue(offset: 7) @@ -451,9 +451,9 @@ internal class SecureDFUControlPoint : NSObject, CBPeripheralDelegate, DFUCharac // MARK: - Peripheral Delegate callbacks func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) { - if error != nil { + if let error = error { logger.e("Enabling notifications failed. Check if Service Changed service is enabled.") - logger.e(error!) + logger.e(error) // Note: // Error 253: Unknown ATT error. // This most proably is caching issue. Check if your device had Service Changed @@ -461,11 +461,11 @@ internal class SecureDFUControlPoint : NSObject, CBPeripheralDelegate, DFUCharac // For bonded devices make sure it sends the Service Changed indication after // connecting. report?(.enablingControlPointFailed, "Enabling notifications failed") - } else { - logger.v("Notifications enabled for \(characteristic.uuid.uuidString)") - logger.a("Secure DFU Control Point notifications enabled") - success?() + return } + logger.v("Notifications enabled for \(characteristic.uuid.uuidString)") + logger.a("Secure DFU Control Point notifications enabled") + success?() } func peripheral(_ peripheral: CBPeripheral, @@ -479,9 +479,9 @@ internal class SecureDFUControlPoint : NSObject, CBPeripheralDelegate, DFUCharac return } - if error != nil { + if let error = error { logger.e("Writing to characteristic failed. Check if Service Changed service is enabled.") - logger.e(error!) + logger.e(error) // Note: // Error 3: Writing is not permitted. // This most proably is caching issue. Check if your device had Service Changed @@ -489,9 +489,9 @@ internal class SecureDFUControlPoint : NSObject, CBPeripheralDelegate, DFUCharac // is a specially a case in SDK 12.x, where it was disabled by default. // For bonded devices make sure it sends the Service Changed indication after connecting. report?(.writingCharacteristicFailed, "Writing to characteristic failed") - } else { - logger.i("Data written to \(characteristic.uuid.uuidString)") + return } + logger.i("Data written to \(characteristic.uuid.uuidString)") } func peripheral(_ peripheral: CBPeripheral, @@ -515,18 +515,16 @@ internal class SecureDFUControlPoint : NSObject, CBPeripheralDelegate, DFUCharac // During the upload we may get either a Packet Receipt Notification, or a Response // with status code. - if proceed != nil { - if let prn = SecureDFUPacketReceiptNotification(characteristicValue) { - proceed!(prn.offset) // The CRC is not verified after receiving a PRN, only the offset is. - return - } + if let proceed = proceed, + let prn = SecureDFUPacketReceiptNotification(characteristicValue) { + proceed(prn.offset) // The CRC is not verified after receiving a PRN, only the offset is. + return } // Otherwise... logger.i("Notification received from \(characteristic.uuid.uuidString), value (0x): \(characteristicValue.hexString)") // Parse response received. - let dfuResponse = SecureDFUResponse(characteristicValue) - guard let dfuResponse = dfuResponse else { + guard let dfuResponse = SecureDFUResponse(characteristicValue) else { logger.e("Unknown response received: 0x\(characteristicValue.hexString)") report?(.unsupportedResponse, "Unsupported response received: 0x\(characteristicValue.hexString)") return diff --git a/iOSDFULibrary/Classes/Implementation/SecureDFU/Characteristics/SecureDFUPacket.swift b/iOSDFULibrary/Classes/Implementation/SecureDFU/Characteristics/SecureDFUPacket.swift index 9c9c2f1f..b5f7dafa 100644 --- a/iOSDFULibrary/Classes/Implementation/SecureDFU/Characteristics/SecureDFUPacket.swift +++ b/iOSDFULibrary/Classes/Implementation/SecureDFU/Characteristics/SecureDFUPacket.swift @@ -143,14 +143,15 @@ internal class SecureDFUPacket: DFUCharacteristic { totalBytesSentSinceProgessNotification = totalBytesSentWhenDfuStarted // Notify progress delegate that upload has started (0%). - queue.async(execute: { + queue.async { progress?.dfuProgressDidChange( for: firmware.currentPart, outOf: firmware.parts, to: 0, currentSpeedBytesPerSecond: 0.0, - avgSpeedBytesPerSecond: 0.0) - }) + avgSpeedBytesPerSecond: 0.0 + ) + } } let originalPacketsToSendNow = packetsToSendNow @@ -189,14 +190,15 @@ internal class SecureDFUPacket: DFUCharacteristic { totalBytesSentSinceProgessNotification = totalBytesSent // Notify progress delegate of overall progress. - queue.async(execute: { + queue.async { progress?.dfuProgressDidChange( for: firmware.currentPart, outOf: firmware.parts, to: Int(currentProgress), currentSpeedBytesPerSecond: currentSpeed, - avgSpeedBytesPerSecond: avgSpeed) - }) + avgSpeedBytesPerSecond: avgSpeed + ) + } progressReported = currentProgress } diff --git a/iOSDFULibrary/Classes/Implementation/SecureDFU/Peripheral/SecureDFUPeripheral.swift b/iOSDFULibrary/Classes/Implementation/SecureDFU/Peripheral/SecureDFUPeripheral.swift index 997f6e9d..fd2389eb 100644 --- a/iOSDFULibrary/Classes/Implementation/SecureDFU/Peripheral/SecureDFUPeripheral.swift +++ b/iOSDFULibrary/Classes/Implementation/SecureDFU/Peripheral/SecureDFUPeripheral.swift @@ -120,7 +120,7 @@ internal class SecureDFUPeripheral : BaseCommonDFUPeripheral 0 { self.logger.a("Packet Receipt Notif enabled (Op Code = 2, Value = \(newValue))") } else { @@ -356,7 +358,8 @@ import CoreBluetooth self.progressDelegate = progressDelegate self.progressQueue = queue - let _report: ErrorCallback = { error, message in + let _report: ErrorCallback = { [weak self] error, message in + guard let self = self else { return } self.firmware = nil self.range = nil self.success = nil @@ -365,7 +368,8 @@ import CoreBluetooth self.progressQueue = nil report(error, message) } - let _success: Callback = { + let _success: Callback = { [weak self] in + guard let self = self else { return } self.firmware = nil self.range = nil self.success = nil @@ -570,9 +574,9 @@ import CoreBluetooth func jumpToBootloaderMode(withAlternativeAdvertisingName name: String?, onSuccess success: @escaping Callback, onError report: @escaping ErrorCallback) { - guard let buttonlessDfuCharacteristic = buttonlessDfuCharacteristic else { return } if !aborted { func enterBootloader() { + guard let buttonlessDfuCharacteristic = buttonlessDfuCharacteristic else { return } // The method above may reset the device before it sents a response to // the request. We will call the success callback right here. success() @@ -582,17 +586,21 @@ import CoreBluetooth // If the device may support setting alternative advertising name in the // bootloader mode, try it. - if let name = name, buttonlessDfuCharacteristic.maySupportSettingName { + if let name = name, + let buttonlessDfuCharacteristic = buttonlessDfuCharacteristic, + buttonlessDfuCharacteristic.maySupportSettingName { logger.v("Trying setting bootloader name to \(name)") buttonlessDfuCharacteristic.send(ButtonlessDFURequest.set(name: name), - onSuccess: { + onSuccess: { [weak self] in + guard let self = self else { return } // Success. The buttonless service is from SDK 14.0+. // The bootloader, after jumping to it, will advertise with this name. - self.targetPeripheral!.bootloaderName = name + self.targetPeripheral?.bootloaderName = name self.logger.a("Bootloader name changed successfully") enterBootloader() - }, onError: { - error, message in + }, + onError: { [weak self] error, message in + guard let self = self else { return } if error == .remoteButtonlessDFUOpCodeNotSupported { // Setting name is not supported. Looks like it's buttonless service // from SDK 13. We can't rely on bootloader's name.