diff --git a/Cartfile b/Cartfile new file mode 100644 index 00000000..7e96d16f --- /dev/null +++ b/Cartfile @@ -0,0 +1 @@ +github "weichsel/ZIPFoundation" ~> 0.9 \ No newline at end of file diff --git a/iOSDFULibrary/Classes/Implementation/DFUSelector/DFUServiceSelector.swift b/iOSDFULibrary/Classes/Implementation/DFUSelector/DFUServiceSelector.swift index ee102b60..8b314f5a 100644 --- a/iOSDFULibrary/Classes/Implementation/DFUSelector/DFUServiceSelector.swift +++ b/iOSDFULibrary/Classes/Implementation/DFUSelector/DFUServiceSelector.swift @@ -44,22 +44,24 @@ internal class DFUServiceSelector : BaseDFUExecutor, DFUStarterPeripheralDelegat typealias DFUPeripheralType = DFUStarterPeripheral internal let initiator: DFUServiceInitiator + internal let logger: LoggerHelper internal let controller: DFUServiceController internal let peripheral: DFUStarterPeripheral internal var error: (error: DFUError, message: String)? init(initiator: DFUServiceInitiator, controller: DFUServiceController) { self.initiator = initiator + self.logger = LoggerHelper(initiator.logger, initiator.loggerQueue) self.controller = controller - self.peripheral = DFUStarterPeripheral(initiator) + self.peripheral = DFUStarterPeripheral(initiator, logger) self.peripheral.delegate = self } func start() { - DispatchQueue.main.async(execute: { - self.delegate?.dfuStateDidChange(to: .connecting) - }) + delegate { + $0.dfuStateDidChange(to: .connecting) + } peripheral.start() } @@ -67,7 +69,7 @@ internal class DFUServiceSelector : BaseDFUExecutor, DFUStarterPeripheralDelegat // Release the cyclic reference peripheral.destroy() - let executor = ExecutorType.init(initiator) + let executor = ExecutorType.init(initiator, logger) controller.executor = executor executor.start() } diff --git a/iOSDFULibrary/Classes/Implementation/DFUServiceInitiator.swift b/iOSDFULibrary/Classes/Implementation/DFUServiceInitiator.swift index 0e809af3..42cccf3f 100644 --- a/iOSDFULibrary/Classes/Implementation/DFUServiceInitiator.swift +++ b/iOSDFULibrary/Classes/Implementation/DFUServiceInitiator.swift @@ -35,6 +35,11 @@ import CoreBluetooth internal var target : CBPeripheral! internal var file : DFUFirmware? + internal var queue : DispatchQueue + internal var delegateQueue : DispatchQueue + internal var progressDelegateQueue : DispatchQueue + internal var loggerQueue : DispatchQueue + //MARK: - Public variables /** @@ -239,6 +244,10 @@ import CoreBluetooth // Default UUID helper with standard set of UUIDs self.uuidHelper = DFUUuidHelper() + self.queue = DispatchQueue.main + self.delegateQueue = DispatchQueue.main + self.progressDelegateQueue = DispatchQueue.main + self.loggerQueue = DispatchQueue.main super.init() } @@ -246,14 +255,20 @@ import CoreBluetooth Creates the DFUServiceInitializer that will allow to send an update to peripherals. - parameter queue: The dispatch queue to run BLE operations on. + - parameter callbackQueue: The dispatch queue to invoke all delegate callbacks on. + - parameter progressQueue: The dispatch queue to invoke all progress delegate callbacks on. + - parameter loggerQueue: The dispatch queue to invoke all logger events on. - returns: The initiator instance. - - version: Added in version 4.2 of the iOS DFU Library. + - version: Added in version 4.2 of the iOS DFU Library. Extended in 4.3 to allow setting delegate queues. - seeAlso: peripheralSelector property - a selector used when scanning for a device in DFU Bootloader mode in case you want to update a Softdevice and Application from a single ZIP Distribution Packet. */ - @objc public init(queue: DispatchQueue? = nil) { + @objc public init(queue: DispatchQueue? = nil, + delegateQueue: DispatchQueue = DispatchQueue.main, + progressQueue: DispatchQueue = DispatchQueue.main, + loggerQueue: DispatchQueue = DispatchQueue.main) { // Create a new instance of CBCentralManager self.centralManager = CBCentralManager(delegate: nil, queue: queue) // Default peripheral selector will choose the service UUID as a filter @@ -261,6 +276,10 @@ import CoreBluetooth // Default UUID helper with standard set of UUIDs self.uuidHelper = DFUUuidHelper() + self.queue = queue ?? DispatchQueue.main + self.delegateQueue = delegateQueue + self.progressDelegateQueue = progressQueue + self.loggerQueue = loggerQueue super.init() } diff --git a/iOSDFULibrary/Classes/Implementation/GenericDFU/DFUExecutor.swift b/iOSDFULibrary/Classes/Implementation/GenericDFU/DFUExecutor.swift index 305ebdee..69e23fbe 100644 --- a/iOSDFULibrary/Classes/Implementation/GenericDFU/DFUExecutor.swift +++ b/iOSDFULibrary/Classes/Implementation/GenericDFU/DFUExecutor.swift @@ -22,6 +22,8 @@ import CoreBluetooth +typealias DelegateCallback = (DFUServiceDelegate) -> Void + internal protocol BaseExecutorAPI : class, DFUController { /** @@ -33,28 +35,18 @@ internal protocol BaseExecutorAPI : class, DFUController { internal protocol BaseDFUExecutor : BaseExecutorAPI, BasePeripheralDelegate { associatedtype DFUPeripheralType : BaseDFUPeripheralAPI - /// Target peripheral object + /// Target peripheral object. var peripheral: DFUPeripheralType { get } /// The DFU Service Initiator instance that was used to start the service. var initiator: DFUServiceInitiator { get } + /// The optional logger delegate. + var logger: LoggerHelper { get } /// If an error occurred it is set as this variable. It will be reported to the user when the device gets disconnected. var error: (error: DFUError, message: String)? { set get } } extension BaseDFUExecutor { - /// The service delegate will be informed about status changes and errors. - internal var delegate: DFUServiceDelegate? { - // The delegate may change during DFU operation (by setting a new one in the initiator). Let's always use the current one. - return initiator.delegate - } - - /// The progress delegate will be informed about current upload progress. - internal var progressDelegate: DFUProgressDelegate? { - // The delegate may change during DFU operation (by setting a new one in the initiator). Let's always use the current one. - return initiator.progressDelegate - } - // MARK: - DFU Controller API func pause() -> Bool { @@ -72,38 +64,38 @@ extension BaseDFUExecutor { // MARK: - BasePeripheralDelegate API func peripheralDidFailToConnect() { - DispatchQueue.main.async(execute: { - self.delegate?.dfuError(.failedToConnect, didOccurWithMessage: "Device failed to connect") - }) + delegate { + $0.dfuError(.failedToConnect, didOccurWithMessage: "Device failed to connect") + } // Release the cyclic reference peripheral.destroy() } func peripheralDidDisconnect() { // The device is now disconnected. Check if there was an error that needs to be reported now - DispatchQueue.main.async(execute: { + delegate { if let error = self.error { - self.delegate?.dfuError(error.error, didOccurWithMessage: error.message) + $0.dfuError(error.error, didOccurWithMessage: error.message) } else { - self.delegate?.dfuError(.deviceDisconnected, didOccurWithMessage: "Device disconnected unexpectedly") + $0.dfuError(.deviceDisconnected, didOccurWithMessage: "Device disconnected unexpectedly") } - }) + } // Release the cyclic reference peripheral.destroy() } func peripheralDidDisconnect(withError error: Error) { - DispatchQueue.main.async(execute: { - self.delegate?.dfuError(.deviceDisconnected, didOccurWithMessage: "\(error.localizedDescription) (code: \((error as NSError).code))") - }) + delegate { + $0.dfuError(.deviceDisconnected, didOccurWithMessage: "\(error.localizedDescription) (code: \((error as NSError).code))") + } // Release the cyclic reference peripheral.destroy() } func peripheralDidDisconnectAfterAborting() { - DispatchQueue.main.async(execute: { - self.delegate?.dfuStateDidChange(to: .aborted) - }) + delegate { + $0.dfuStateDidChange(to: .aborted) + } // Release the cyclic reference peripheral.destroy() } @@ -118,21 +110,27 @@ extension BaseDFUExecutor { // MARK: - Helper functions - func logWith(_ level: LogLevel, message: String) { - initiator.logger?.logWith(level, message: message) + /// Calls the delegate method on the delegate queue given in the initiator. + func delegate(callback: @escaping DelegateCallback) { + if let delegate = initiator.delegate { + initiator.delegateQueue.async { + callback(delegate) + } + } } } // MARK: - internal protocol DFUExecutorAPI : BaseExecutorAPI { - /// Required constructor - init(_ initiator: DFUServiceInitiator) + + /// Required constructor. + init(_ initiator: DFUServiceInitiator, _ logger: LoggerHelper) } internal protocol DFUExecutor : DFUExecutorAPI, BaseDFUExecutor, DFUPeripheralDelegate where DFUPeripheralType: DFUPeripheralAPI { - /// The firmware to be sent over-the-air + /// The firmware to be sent over-the-air. var firmware: DFUFirmware { get } } @@ -144,16 +142,15 @@ extension DFUExecutor { // Check if there is another part of the firmware that has to be sent if firmware.hasNextPart() { firmware.switchToNextPart() - DispatchQueue.main.async(execute: { - self.delegate?.dfuStateDidChange(to: .connecting) - }) + delegate { + $0.dfuStateDidChange(to: .connecting) + } return true } // If not, we are done here. Congratulations! - DispatchQueue.main.async(execute: { - // If no, the DFU operation is complete - self.delegate?.dfuStateDidChange(to: .completed) - }) + delegate { + $0.dfuStateDidChange(to: .completed) + } // Release the cyclic reference peripheral.destroy() diff --git a/iOSDFULibrary/Classes/Implementation/GenericDFU/DFUPeripheral.swift b/iOSDFULibrary/Classes/Implementation/GenericDFU/DFUPeripheral.swift index 9f1edb4b..1cebd345 100644 --- a/iOSDFULibrary/Classes/Implementation/GenericDFU/DFUPeripheral.swift +++ b/iOSDFULibrary/Classes/Implementation/GenericDFU/DFUPeripheral.swift @@ -50,6 +50,7 @@ internal protocol BaseDFUPeripheralAPI : class, DFUController { internal class BaseDFUPeripheral : NSObject, BaseDFUPeripheralAPI, CBPeripheralDelegate, CBCentralManagerDelegate { /// Bluetooth Central Manager used to scan for the peripheral. internal let centralManager: CBCentralManager + internal let queue: DispatchQueue /// The DFU Target peripheral. internal var peripheral: CBPeripheral? /// The peripheral delegate. @@ -86,9 +87,10 @@ internal class BaseDFUPeripheral : NSObject, BaseDF /// A flag set when upload has been aborted. fileprivate var aborted: Bool = false - init(_ initiator: DFUServiceInitiator) { + init(_ initiator: DFUServiceInitiator, _ logger: LoggerHelper) { self.centralManager = initiator.centralManager - self.logger = LoggerHelper(initiator.logger) + self.queue = initiator.queue + self.logger = logger self.experimentalButtonlessServiceInSecureDfuEnabled = initiator.enableUnsafeExperimentalButtonlessServiceInSecureDfu self.uuidHelper = initiator.uuidHelper @@ -487,15 +489,15 @@ internal class BaseCommonDFUPeripheral 0 { - logWith(.warning, message: "Retrying...") + logger.w("Retrying...") invalidStateRetryCount -= 1 peripheral.start() } else { @@ -133,14 +135,14 @@ internal class LegacyDFUExecutor : DFUExecutor, LegacyDFUPeripheralDelegate { Sends the current part of the firmware to the target DFU device. */ private func sendFirmware() { - DispatchQueue.main.async(execute: { - self.delegate?.dfuStateDidChange(to: .uploading) - }) + delegate { + $0.dfuStateDidChange(to: .uploading) + } // First the service will send the number of packets of firmware data to be received // by the DFU target before sending a new Packet Receipt Notification. // After receiving status Success it will send the firmware. peripheral.sendFirmware(firmware, withPacketReceiptNotificationNumber: initiator.packetReceiptNotificationParameter, - andReportProgressTo: progressDelegate) + andReportProgressTo: initiator.progressDelegate, on: initiator.progressDelegateQueue) } } diff --git a/iOSDFULibrary/Classes/Implementation/LegacyDFU/DFU/LegacyDFUServiceInitiator.swift b/iOSDFULibrary/Classes/Implementation/LegacyDFU/DFU/LegacyDFUServiceInitiator.swift index e0fdd4c7..076921fd 100644 --- a/iOSDFULibrary/Classes/Implementation/LegacyDFU/DFU/LegacyDFUServiceInitiator.swift +++ b/iOSDFULibrary/Classes/Implementation/LegacyDFU/DFU/LegacyDFUServiceInitiator.swift @@ -31,7 +31,8 @@ import CoreBluetooth return nil } - let executor = LegacyDFUExecutor(self) + let logger = LoggerHelper(self.logger, loggerQueue) + let executor = LegacyDFUExecutor(self, logger) let controller = DFUServiceController() controller.executor = executor executor.start() diff --git a/iOSDFULibrary/Classes/Implementation/LegacyDFU/Peripherals/LegacyDFUPeripheral.swift b/iOSDFULibrary/Classes/Implementation/LegacyDFU/Peripherals/LegacyDFUPeripheral.swift index 5a57e970..ed6ed4c9 100644 --- a/iOSDFULibrary/Classes/Implementation/LegacyDFU/Peripherals/LegacyDFUPeripheral.swift +++ b/iOSDFULibrary/Classes/Implementation/LegacyDFU/Peripherals/LegacyDFUPeripheral.swift @@ -149,8 +149,10 @@ internal class LegacyDFUPeripheral : BaseCommonDFUPeripheral 2) { prn = 2 @@ -158,7 +160,8 @@ internal class LegacyDFUPeripheral : BaseCommonDFUPeripheral 0 it will receive Packet Receit Notifications @@ -419,6 +427,7 @@ import CoreBluetooth self.firmware = nil self.report = nil self.progressDelegate = nil + self.progressQueue = nil success() }, onPacketReceiptNofitication: { @@ -436,7 +445,8 @@ import CoreBluetooth let bytesSent = self.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) + self.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") @@ -446,6 +456,7 @@ import CoreBluetooth self.firmware = nil self.report = nil self.progressDelegate = nil + self.progressQueue = nil self.sendReset(onError: report) } }, @@ -455,6 +466,7 @@ import CoreBluetooth self.firmware = nil self.report = nil self.progressDelegate = nil + self.progressQueue = nil report(error, message) } ) @@ -463,12 +475,13 @@ import CoreBluetooth let start = { self.logger.a("Uploading firmware...") self.logger.v("Sending firmware to DFU Packet characteristic...") - self.dfuPacketCharacteristic!.sendNext(self.packetReceiptNotificationNumber, packetsOf: firmware, andReportProgressTo: progress) + self.dfuPacketCharacteristic!.sendNext(self.packetReceiptNotificationNumber, packetsOf: firmware, + andReportProgressTo: progress, on: queue) } // On devices running SDK 6.0 or older a delay is required before the device is ready to receive data if delay { self.logger.d("wait(1000)") - DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(1000), execute: start) + queue.asyncAfter(deadline: .now() + .milliseconds(1000), execute: start) } else { start() } @@ -477,6 +490,7 @@ import CoreBluetooth self.firmware = nil self.report = nil self.progressDelegate = nil + self.progressQueue = nil self.sendReset(onError: report) } }, diff --git a/iOSDFULibrary/Classes/Implementation/SecureDFU/Characteristics/SecureDFUPacket.swift b/iOSDFULibrary/Classes/Implementation/SecureDFU/Characteristics/SecureDFUPacket.swift index 8a5ed641..e88b0676 100644 --- a/iOSDFULibrary/Classes/Implementation/SecureDFU/Characteristics/SecureDFUPacket.swift +++ b/iOSDFULibrary/Classes/Implementation/SecureDFU/Characteristics/SecureDFUPacket.swift @@ -96,10 +96,12 @@ internal class SecureDFUPacket: DFUCharacteristic { - range: The range of the firmware that is to be sent in this object. - firmware: The whole firmware to be sent in this part. - progress: An optional progress delegate. + - queue: The queue to dispatch progress events on. - complete: The completon callback. */ func sendNext(_ prnValue: UInt16, packetsFrom range: Range, of firmware: DFUFirmware, - andReportProgressTo progress: DFUProgressDelegate?, andCompletionTo complete: @escaping Callback) { + andReportProgressTo progress: DFUProgressDelegate?, on queue: DispatchQueue, + andCompletionTo complete: @escaping Callback) { let peripheral = characteristic.service.peripheral let objectData = firmware.data.subdata(in: range) let objectSizeInBytes = UInt32(objectData.count) @@ -129,7 +131,7 @@ internal class SecureDFUPacket: DFUCharacteristic { totalBytesSentSinceProgessNotification = totalBytesSentWhenDfuStarted // Notify progress delegate that upload has started (0%) - DispatchQueue.main.async(execute: { + queue.async(execute: { progress?.dfuProgressDidChange( for: firmware.currentPart, outOf: firmware.parts, @@ -174,7 +176,7 @@ internal class SecureDFUPacket: DFUCharacteristic { totalBytesSentSinceProgessNotification = totalBytesSent // Notify progress delegate of overall progress - DispatchQueue.main.async(execute: { + queue.async(execute: { progress?.dfuProgressDidChange( for: firmware.currentPart, outOf: firmware.parts, diff --git a/iOSDFULibrary/Classes/Implementation/SecureDFU/DFU/SecureDFUExecutor.swift b/iOSDFULibrary/Classes/Implementation/SecureDFU/DFU/SecureDFUExecutor.swift index 1cb9c708..2c763800 100644 --- a/iOSDFULibrary/Classes/Implementation/SecureDFU/DFU/SecureDFUExecutor.swift +++ b/iOSDFULibrary/Classes/Implementation/SecureDFU/DFU/SecureDFUExecutor.swift @@ -25,6 +25,7 @@ internal class SecureDFUExecutor : DFUExecutor, SecureDFUPeripheralDelegate { typealias DFUPeripheralType = SecureDFUPeripheral internal let initiator : DFUServiceInitiator + internal let logger : LoggerHelper internal let peripheral : SecureDFUPeripheral internal var firmware : DFUFirmware internal var error : (error: DFUError, message: String)? @@ -45,10 +46,11 @@ internal class SecureDFUExecutor : DFUExecutor, SecureDFUPeripheralDelegate { private var retryCount: Int // MARK: - Initialization - required init(_ initiator: DFUServiceInitiator) { + required init(_ initiator: DFUServiceInitiator, _ logger: LoggerHelper) { self.initiator = initiator + self.logger = logger self.firmware = initiator.file! - self.peripheral = SecureDFUPeripheral(initiator) + self.peripheral = SecureDFUPeripheral(initiator, logger) self.retryCount = MaxRetryCount } @@ -68,18 +70,18 @@ internal class SecureDFUExecutor : DFUExecutor, SecureDFUPeripheralDelegate { } resetFirmwareRanges() - DispatchQueue.main.async(execute: { - self.delegate?.dfuStateDidChange(to: .starting) - }) + delegate { + $0.dfuStateDidChange(to: .starting) + } peripheral.enableControlPoint() // -> peripheralDidEnableControlPoint() will be called when done } func peripheralDidEnableControlPoint() { // Check whether the target is in application or bootloader mode if peripheral.isInApplicationMode(initiator.forceDfu) { - DispatchQueue.main.async(execute: { - self.delegate?.dfuStateDidChange(to: .enablingDfuMode) - }) + delegate { + $0.dfuStateDidChange(to: .enablingDfuMode) + } peripheral.jumpToBootloader() // -> peripheralDidBecomeReady() will be called again, when connected to the Bootloader } else { // The device is ready to proceed with DFU @@ -100,13 +102,13 @@ internal class SecureDFUExecutor : DFUExecutor, SecureDFUPeripheralDelegate { if !initiator.disableResume && verifyCRC(for: firmware.initPacket!, andPacketOffset: offset, matches: crc) { // Resume sending Init Packet if offset < UInt32(firmware.initPacket!.count) { - logWith(.application, message: "Resuming sending Init packet...") + logger.a("Resuming sending Init packet...") // We need to send rest of the Init packet, but before that let's make sure the PRNs are disabled peripheral.setPRNValue(0) // -> peripheralDidSetPRNValue() will be called } else { // The same Init Packet was already sent. We must execute it, as it may have not been executed before. - logWith(.application, message: "Received CRC match Init packet") + logger.a("Received CRC match Init packet") peripheral.sendExecuteCommand(forCommandObject: true) // -> peripheralDidExecuteObject() or peripheralRejectedCommandObject(...) will be called } } else { @@ -138,7 +140,7 @@ internal class SecureDFUExecutor : DFUExecutor, SecureDFUPeripheralDelegate { } func peripheralDidReceiveInitPacket() { - logWith(.application, message: String(format: "Command object sent (CRC = %08X)", CRC32(data: firmware.initPacket!).crc)) + logger.a(String(format: "Command object sent (CRC = %08X)", CRC32(data: firmware.initPacket!).crc)) // Init Packet sent. Let's check the CRC before executing it. peripheral.sendCalculateChecksumCommand() // -> peripheralDidSendChecksum(...) will be called @@ -189,7 +191,7 @@ internal class SecureDFUExecutor : DFUExecutor, SecureDFUPeripheralDelegate { if firmware.hasNextPart() { firmware.switchToNextPart() - logWith(.warning, message: "Invalid system components. Trying to send application") + logger.w("Invalid system components. Trying to send application") // New Init Packet has to be sent. Create the Command object. offset = 0 @@ -202,14 +204,14 @@ internal class SecureDFUExecutor : DFUExecutor, SecureDFUPeripheralDelegate { func peripheralDidExecuteObject() { if initPacketSent == false { - logWith(.application, message: "Command object executed") + logger.a("Command object executed") initPacketSent = true // Set the correct PRN value. If initiator.packetReceiptNotificationParameter is 0 // and PRNs were already disabled to send the Init packet, this method will immediately // call peripheralDidSetPRNValue() callback. peripheral.setPRNValue(initiator.packetReceiptNotificationParameter) // -> peripheralDidSetPRNValue() will be called } else { - logWith(.application, message: "Data object executed") + logger.a("Data object executed") if firmwareSent == false { currentRangeIdx += 1 @@ -218,11 +220,11 @@ internal class SecureDFUExecutor : DFUExecutor, SecureDFUPeripheralDelegate { // The last data object was sent // Now the device will reset itself and onTransferCompleted() method will ba called (from the extension) let interval = CFAbsoluteTimeGetCurrent() - uploadStartTime! as CFTimeInterval - logWith(.application, message: "Upload completed in \(interval.format(".2")) seconds") + logger.a("Upload completed in \(interval.format(".2")) seconds") - DispatchQueue.main.async(execute: { - self.delegate?.dfuStateDidChange(to: .disconnecting) - }) + delegate { + $0.dfuStateDidChange(to: .disconnecting) + } } } } @@ -240,9 +242,9 @@ internal class SecureDFUExecutor : DFUExecutor, SecureDFUPeripheralDelegate { currentRangeIdx = 0 } - DispatchQueue.main.async(execute: { - self.delegate?.dfuStateDidChange(to: .uploading) - }) + delegate { + $0.dfuStateDidChange(to: .uploading) + } if offset > 0 { // Find the current range index. @@ -255,13 +257,13 @@ internal class SecureDFUExecutor : DFUExecutor, SecureDFUPeripheralDelegate { } if verifyCRC(for: firmware.data, andPacketOffset: offset, matches: crc) { - logWith(.info, message: "\(offset) bytes of data sent before, CRC match") + logger.i("\(offset) bytes of data sent before, CRC match") // Did we sent the whole firmware? if offset == UInt32(firmware.data.count) { firmwareSent = true peripheral.sendExecuteCommand(andActivateIf: firmwareSent) // -> peripheralDidExecuteObject() will be called } else { - logWith(.info, message: "Resuming uploading firmware...") + logger.i("Resuming uploading firmware...") // If the whole object was sent before, make sure it's executed if (offset % maxLen) == 0 { @@ -290,7 +292,7 @@ internal class SecureDFUExecutor : DFUExecutor, SecureDFUPeripheralDelegate { } func peripheralDidCreateDataObject() { - logWith(.info, message: "Data object \(currentRangeIdx + 1)/\(firmwareRanges!.count) created") + logger.i("Data object \(currentRangeIdx + 1)/\(firmwareRanges!.count) created") sendDataObject(currentRangeIdx) // -> peripheralDidReceiveObject() will be called } @@ -303,10 +305,10 @@ internal class SecureDFUExecutor : DFUExecutor, SecureDFUPeripheralDelegate { private func retryOrReportCrcError(_ operation:()->()) { retryCount -= 1 if retryCount > 0 { - logWith(.warning, message: "CRC does not match! Retrying...") + logger.w("CRC does not match! Retrying...") operation() } else { - logWith(.error, message: "CRC does not match!") + logger.e("CRC does not match!") error(.crcError, didOccurWithMessage: "Sending firmware failed") } } @@ -427,7 +429,8 @@ internal class SecureDFUExecutor : DFUExecutor, SecureDFUPeripheralDelegate { aRange = Int(resumeOffset) ..< newLength + Int(resumeOffset) } - peripheral.sendNextObject(from: aRange, of: firmware, andReportProgressTo: progressDelegate) + peripheral.sendNextObject(from: aRange, of: firmware, + andReportProgressTo: initiator.progressDelegate, on: initiator.progressDelegateQueue) // -> peripheralDidReceiveObject() will be called } } diff --git a/iOSDFULibrary/Classes/Implementation/SecureDFU/DFU/SecureDFUServiceInitiator.swift b/iOSDFULibrary/Classes/Implementation/SecureDFU/DFU/SecureDFUServiceInitiator.swift index 65496cee..a9763d1c 100644 --- a/iOSDFULibrary/Classes/Implementation/SecureDFU/DFU/SecureDFUServiceInitiator.swift +++ b/iOSDFULibrary/Classes/Implementation/SecureDFU/DFU/SecureDFUServiceInitiator.swift @@ -31,7 +31,8 @@ import CoreBluetooth return nil } - let executor = SecureDFUExecutor(self) + let logger = LoggerHelper(self.logger, loggerQueue) + let executor = SecureDFUExecutor(self, logger) let controller = DFUServiceController() controller.executor = executor executor.start() diff --git a/iOSDFULibrary/Classes/Implementation/SecureDFU/Peripheral/SecureDFUPeripheral.swift b/iOSDFULibrary/Classes/Implementation/SecureDFU/Peripheral/SecureDFUPeripheral.swift index 1f0b061f..504050b4 100644 --- a/iOSDFULibrary/Classes/Implementation/SecureDFU/Peripheral/SecureDFUPeripheral.swift +++ b/iOSDFULibrary/Classes/Implementation/SecureDFU/Peripheral/SecureDFUPeripheral.swift @@ -40,9 +40,9 @@ internal class SecureDFUPeripheral : BaseCommonDFUPeripheral, of firmware: DFUFirmware, andReportProgressTo progress: DFUProgressDelegate?) { - dfuService!.sendNextObject(from: range, of: firmware, andReportProgressTo: progress, + func sendNextObject(from range: Range, of firmware: DFUFirmware, + andReportProgressTo progress: DFUProgressDelegate?, on queue: DispatchQueue) { + dfuService!.sendNextObject(from: range, of: firmware, + andReportProgressTo: progress, on: queue, onSuccess: { self.delegate?.peripheralDidReceiveObject() }, onError: defaultErrorCallback ) diff --git a/iOSDFULibrary/Classes/Implementation/SecureDFU/Services/SecureDFUService.swift b/iOSDFULibrary/Classes/Implementation/SecureDFU/Services/SecureDFUService.swift index ad6d920b..6c75d845 100644 --- a/iOSDFULibrary/Classes/Implementation/SecureDFU/Services/SecureDFUService.swift +++ b/iOSDFULibrary/Classes/Implementation/SecureDFU/Services/SecureDFUService.swift @@ -24,6 +24,7 @@ import CoreBluetooth @objc internal class SecureDFUService : NSObject, CBPeripheralDelegate, DFUService { + internal let queue: DispatchQueue internal var targetPeripheral: DFUPeripheralAPI? internal var uuidHelper: DFUUuidHelper @@ -47,6 +48,7 @@ import CoreBluetooth private var report : ErrorCallback? /// A temporaty callback used to report progress status. private var progressDelegate : DFUProgressDelegate? + private var progressQueue : DispatchQueue? // -- Properties stored when upload started in order to resume it -- private var firmware: DFUFirmware? @@ -56,10 +58,11 @@ import CoreBluetooth // MARK: - Initialization - required init(_ service: CBService, _ logger: LoggerHelper, _ uuidHelper: DFUUuidHelper) { + required init(_ service: CBService, _ logger: LoggerHelper, _ uuidHelper: DFUUuidHelper, _ queue: DispatchQueue) { self.service = service self.logger = logger self.uuidHelper = uuidHelper + self.queue = queue super.init() self.logger.v("Secure DFU Service found") @@ -84,7 +87,8 @@ import CoreBluetooth if !aborted && paused && firmware != nil { paused = false dfuPacketCharacteristic!.sendNext(packetReceiptNotificationNumber ?? 0, packetsFrom: range!, of: firmware!, - andReportProgressTo: progressDelegate, andCompletionTo: success!) + andReportProgressTo: progressDelegate, on: progressQueue!, + andCompletionTo: success!) return paused } paused = false @@ -102,6 +106,7 @@ import CoreBluetooth success = nil report = nil progressDelegate = nil + progressQueue = nil // Upload has been aborted. Reset the target device. It will disconnect automatically sendReset(onError: _report) } @@ -294,13 +299,15 @@ import CoreBluetooth /** Sends the next object of firmware. Result it reported using callbacks. - - parameter aRange: Given range of the firmware will be sent. - - parameter aFirmware: The firmware from with part is to be sent. + - parameter range: Given range of the firmware will be sent. + - parameter firmware: The firmware from with part is to be sent. - parameter progressDelegate: An optional progress delegate. + - parameter queue: The queue to dispatch progress events on. - parameter success: Method called when the object was sent. - parameter report: Method called when an error occurred. */ - func sendNextObject(from aRange: Range, of aFirmware: DFUFirmware, andReportProgressTo progressDelegate: DFUProgressDelegate?, + func sendNextObject(from range: Range, of firmware: DFUFirmware, + andReportProgressTo progressDelegate: DFUProgressDelegate?, on queue: DispatchQueue, onSuccess success: @escaping Callback, onError report: @escaping ErrorCallback) { guard !aborted else { sendReset(onError: report) @@ -308,9 +315,10 @@ import CoreBluetooth } // Those will be stored here in case of pause/resume - self.firmware = aFirmware - self.range = aRange + self.firmware = firmware + self.range = range self.progressDelegate = progressDelegate + self.progressQueue = queue self.report = { error, message in @@ -319,6 +327,7 @@ import CoreBluetooth self.success = nil self.report = nil self.progressDelegate = nil + self.progressQueue = nil report(error, message) } self.success = { @@ -327,6 +336,7 @@ import CoreBluetooth self.success = nil self.report = nil self.progressDelegate = nil + self.progressQueue = nil self.dfuControlPointCharacteristic!.peripheralDidReceiveObject() success() } as Callback @@ -341,10 +351,11 @@ import CoreBluetooth } if !self.paused && !self.aborted { - let bytesSent = self.dfuPacketCharacteristic!.bytesSent + UInt32(aRange.lowerBound) + let bytesSent = self.dfuPacketCharacteristic!.bytesSent + UInt32(range.lowerBound) if peripheralIsReadyToSendWriteWithoutRequest || bytesSent == bytesReceived! { - self.dfuPacketCharacteristic!.sendNext(self.packetReceiptNotificationNumber ?? 0, packetsFrom: aRange, of: aFirmware, - andReportProgressTo: progressDelegate, andCompletionTo: self.success!) + self.dfuPacketCharacteristic!.sendNext(self.packetReceiptNotificationNumber ?? 0, packetsFrom: range, of: firmware, + andReportProgressTo: progressDelegate, on: queue, + andCompletionTo: self.success!) } else { // Target device deported invalid number of bytes received report(.bytesLost, "\(bytesSent) bytes were sent while \(bytesReceived!) bytes were reported as received") @@ -365,8 +376,9 @@ import CoreBluetooth if !paused && !aborted { // ...and start sending firmware if - dfuPacketCharacteristic!.sendNext(packetReceiptNotificationNumber ?? 0, packetsFrom: aRange, of: aFirmware, - andReportProgressTo: progressDelegate, andCompletionTo: self.success!) + dfuPacketCharacteristic!.sendNext(packetReceiptNotificationNumber ?? 0, packetsFrom: range, of: firmware, + andReportProgressTo: progressDelegate, on: queue, + andCompletionTo: self.success!) } else if aborted { self.firmware = nil self.range = nil diff --git a/iOSDFULibrary/Classes/Utilities/Logging/LoggerHelper.swift b/iOSDFULibrary/Classes/Utilities/Logging/LoggerHelper.swift index 9a11b027..5b99e8b8 100644 --- a/iOSDFULibrary/Classes/Utilities/Logging/LoggerHelper.swift +++ b/iOSDFULibrary/Classes/Utilities/Logging/LoggerHelper.swift @@ -23,40 +23,50 @@ import Foundation class LoggerHelper { private weak var logger: LoggerDelegate? + private var queue: DispatchQueue - init(_ logger: LoggerDelegate?) { + init(_ logger: LoggerDelegate?, _ queue: DispatchQueue) { self.logger = logger + self.queue = queue } func d(_ message: String) { - logger?.logWith(.debug, message: message) + log(with: .debug, message: message) } func v(_ message: String) { - logger?.logWith(.verbose, message: message) + log(with: .verbose, message: message) } func i(_ message: String) { - logger?.logWith(.info, message: message) + log(with: .info, message: message) } func a(_ message: String) { - logger?.logWith(.application, message: message) + log(with: .application, message: message) } func w(_ message: String) { - logger?.logWith(.warning, message: message) + log(with: .warning, message: message) } func e(_ message: String) { - logger?.logWith(.error, message: message) + log(with: .error, message: message) } func w(_ error: Error) { - logger?.logWith(.warning, message: "Error \((error as NSError).code): \(error.localizedDescription)") + log(with: .warning, message: "Error \((error as NSError).code): \(error.localizedDescription)") } func e(_ error: Error) { - logger?.logWith(.error, message: "Error \((error as NSError).code): \(error.localizedDescription)") + log(with: .error, message: "Error \((error as NSError).code): \(error.localizedDescription)") + } + + private func log(with level: LogLevel, message: String) { + if let logger = logger { + queue.async { + logger.logWith(level, message: message) + } + } } }