From b170ecfcd8e0a6ab52efd3f56552780c1ab61085 Mon Sep 17 00:00:00 2001 From: marionbarker Date: Thu, 27 Jan 2022 17:28:09 -0800 Subject: [PATCH 1/3] align code with changes from unique-ids commit 5af449d from itsmojo Unique controller ids support, working eap seq # podIds * persistent id created at startup or deactvation as needed * eap seq # now incremented for each new session * podId #'s now cycle between next of 3 values on deactivate * temp fake podState no longer used to implement pairing --- OmniBLE/Bluetooth/Id.swift | 70 +++----- OmniBLE/Bluetooth/Ids.swift | 34 ++-- OmniBLE/Bluetooth/MessagePacket.swift | 6 +- OmniBLE/Bluetooth/Pair/PairMessage.swift | 1 + .../Bluetooth/PeripheralManager+OmniBLE.swift | 5 +- .../Session/SessionEstablisher.swift | 14 +- OmniBLE/PumpManager/MessageTransport.swift | 36 +++- OmniBLE/PumpManager/OmniBLEPumpManager.swift | 31 ++-- .../PumpManager/OmniBLEPumpManagerState.swift | 37 +++- OmniBLE/PumpManager/PodComms.swift | 169 ++++++++---------- OmniBLE/PumpManager/PodState.swift | 23 +-- .../PumpManagerUI/Views/PodDetailsView.swift | 2 +- 12 files changed, 213 insertions(+), 215 deletions(-) diff --git a/OmniBLE/Bluetooth/Id.swift b/OmniBLE/Bluetooth/Id.swift index aaaa1ef2..cf86fdf5 100644 --- a/OmniBLE/Bluetooth/Id.swift +++ b/OmniBLE/Bluetooth/Id.swift @@ -10,19 +10,16 @@ import Foundation class Id: Equatable { - static private let PERIPHERAL_NODE_INDEX: UInt8 = 1 - static func fromInt(_ v: Int) -> Id { return Id(Data(bigEndian: v).subdata(in: 4..<8)) } - static func fromLong(_ v: UInt32) -> Id { + static func fromUInt32(_ v: UInt32) -> Id { return Id(Data(bigEndian: v)) } - let address: Data - + init(_ address: Data) { guard address.count == 4 else { // TODO: Should probably throw an error here. @@ -33,57 +30,34 @@ class Id: Equatable { self.address = address } - /** - * Used to obtain podId from controllerId - * The original PDM seems to rotate over 3 Ids: - * controllerID+1, controllerID+2 and controllerID+3 - */ - func increment() -> Id { - var nodeId = address - - //Zero out last 2 bits on right which would round down in sequence of {4, 8, 12, 16, 20, ...} - nodeId[3] = UInt8(Int(nodeId[3]) & -4) - - //Increment by adding 1 - nodeId[3] = nodeId[3] | Id.PERIPHERAL_NODE_INDEX - return Id(nodeId) - } - - /* - TODO: the above implementation is ported from AndroidAPS while we implemented the version below. - It is not clear if skipping every 4 numbers above is intentional or a bug. It would be preferred to use the - AndroidAPS version though until we can successfully pair so we can send identical data payloads. - */ - /* - func increment() -> Id { - var val = address.toBigEndian(Int.self) - val += 1 - if (val >= 4246) { - val = 4243 - } - return Id.fromInt(val) - } - */ - - // TODO: -// override func toString(): String { -// val asInt = ByteBuffer.wrap(address).int -// return "$asInt/${address.toHex()}" -// } - func toInt64() -> Int64 { return address.toBigEndian(Int64.self) } - func toUInt32() -> UInt32 { return address.toBigEndian(UInt32.self) } - - - //MARK: Comparable - + + // MARK: Comparable + static func == (lhs: Id, rhs: Id) -> Bool { return lhs.address == rhs.address } } + +// The Dash PDM uses the PDM's SN << 2 for the bottom 5 nibbles and some +// unknown values for the top 3 nibbles of its fixed 32-bit controller ID. +func createControllerId() -> UInt32 { + // Use 0x17 for top byte to be similar to, but different from, Eros's 0x1F. + return 0x17000000 | ((arc4random() & 0x003FFFFF) << 2) +} + +// podId's cycle between 3 #'s of controllerId+1, +2, +3, +1, ... +func nextPodId(lastPodId: UInt32) -> UInt32 { + if (lastPodId & 0b11) == 0b11 { + // start over at controllerId + 1 + return (lastPodId & ~0b11) + 1 + } + // return the next sequential podId # + return lastPodId + 1 +} diff --git a/OmniBLE/Bluetooth/Ids.swift b/OmniBLE/Bluetooth/Ids.swift index 2f3d259c..9a14fbc1 100644 --- a/OmniBLE/Bluetooth/Ids.swift +++ b/OmniBLE/Bluetooth/Ids.swift @@ -8,22 +8,36 @@ import Foundation -let CONTROLLER_ID: Int = 4242 +let CONTROLLER_ID: UInt32 = 0x1092 // fixed AAPS controller Id # let POD_ID_NOT_ACTIVATED = Data(hexadecimalString: "FFFFFFFE")! public class Ids { + static func notActivated() -> Id { return Id(POD_ID_NOT_ACTIVATED) } - static func controllerId() -> Id { - return Id.fromInt(CONTROLLER_ID) + + private let controllerId: Id + private let currentPodId: Id + + var myId: Id { + return controllerId + } + + var podId: Id { + return currentPodId + } + + var myIdAddr: UInt32 { + return controllerId.toUInt32() } - let myId: Id - let podId: Id - - init(podState: PodState?) { - myId = Id.fromInt(CONTROLLER_ID) - let uniqueId = podState != nil ? Id.fromLong(podState!.address) : myId - podId = uniqueId.increment() + + var podIdAddr: UInt32 { + return currentPodId.toUInt32() + } + + init(myId: UInt32, podId: UInt32) { + controllerId = Id.fromUInt32(myId) + currentPodId = Id.fromUInt32(podId) } } diff --git a/OmniBLE/Bluetooth/MessagePacket.swift b/OmniBLE/Bluetooth/MessagePacket.swift index 396359f3..ff9f71ca 100644 --- a/OmniBLE/Bluetooth/MessagePacket.swift +++ b/OmniBLE/Bluetooth/MessagePacket.swift @@ -81,10 +81,10 @@ struct MessagePacket { let sas: Bool // TODO: understand, seems to always be true let tfs: Bool // TODO: understand, seems to be false let version: Int16 - init(type: MessageType, source: UInt32 = Ids.controllerId().toUInt32(), destination: UInt32, payload: Data, sequenceNumber: UInt8, ack: Bool = false, ackNumber: UInt8 = 0, eqos: Int16 = 0, priority: Bool = false, lastMessage: Bool = false, gateway: Bool = false, sas: Bool = true, tfs: Bool = false, version: Int16 = 0) { + init(type: MessageType, source: UInt32, destination: UInt32, payload: Data, sequenceNumber: UInt8, ack: Bool = false, ackNumber: UInt8 = 0, eqos: Int16 = 0, priority: Bool = false, lastMessage: Bool = false, gateway: Bool = false, sas: Bool = true, tfs: Bool = false, version: Int16 = 0) { self.type = type - self.source = Id.fromLong(source) - self.destination = Id.fromLong(destination) + self.source = Id.fromUInt32(source) + self.destination = Id.fromUInt32(destination) self.payload = payload self.sequenceNumber = sequenceNumber self.ack = ack diff --git a/OmniBLE/Bluetooth/Pair/PairMessage.swift b/OmniBLE/Bluetooth/Pair/PairMessage.swift index fa1bcb80..5890b17a 100644 --- a/OmniBLE/Bluetooth/Pair/PairMessage.swift +++ b/OmniBLE/Bluetooth/Pair/PairMessage.swift @@ -24,6 +24,7 @@ struct PairMessage { self.payloads = payloads message = MessagePacket( type: MessageType.PAIRING, + source: source.toUInt32(), destination: destination.toUInt32(), payload: StringLengthPrefixEncoding.formatKeys( keys: keys, diff --git a/OmniBLE/Bluetooth/PeripheralManager+OmniBLE.swift b/OmniBLE/Bluetooth/PeripheralManager+OmniBLE.swift index 42f2951c..6be5e844 100644 --- a/OmniBLE/Bluetooth/PeripheralManager+OmniBLE.swift +++ b/OmniBLE/Bluetooth/PeripheralManager+OmniBLE.swift @@ -22,9 +22,10 @@ struct MessageSendSuccess: MessageResult { extension PeripheralManager { /// - Throws: PeripheralManagerError - func sendHello(_ controllerId: Data) throws { + func sendHello(myId: UInt32) throws { dispatchPrecondition(condition: .onQueue(queue)) - + + let controllerId = Id.fromUInt32(myId).address log.default("Sending Hello %{public}@", controllerId.hexadecimalString) guard let characteristic = peripheral.getCommandCharacteristic() else { throw PeripheralManagerError.notReady diff --git a/OmniBLE/Bluetooth/Session/SessionEstablisher.swift b/OmniBLE/Bluetooth/Session/SessionEstablisher.swift index f1c0af69..9330ef0c 100644 --- a/OmniBLE/Bluetooth/Session/SessionEstablisher.swift +++ b/OmniBLE/Bluetooth/Session/SessionEstablisher.swift @@ -25,7 +25,8 @@ class SessionEstablisher { private let manager: PeripheralManager private let ltk: Data private let eapSqn: Data - private let address: UInt32 + private let myId: UInt32 + private let podId: UInt32 private var msgSeq: Int private var controllerIV: Data @@ -34,7 +35,7 @@ class SessionEstablisher { private let milenage: Milenage private let log = OSLog(category: "SessionEstablisher") - init(manager: PeripheralManager, ltk: Data, eapSqn: Int, address: UInt32, msgSeq: Int) throws { + init(manager: PeripheralManager, ltk: Data, eapSqn: Int, myId: UInt32, podId: UInt32, msgSeq: Int) throws { // guard eapSqn.count == 6 else { throw SessionEstablishmentException.InvalidParameter("EAP-SQN has to be 6 bytes long") } guard ltk.count == 16 else { throw SessionEstablishmentException.InvalidParameter("LTK has to be 16 bytes long") } @@ -44,7 +45,8 @@ class SessionEstablisher { self.manager = manager self.ltk = ltk self.eapSqn = Data(bigEndian: eapSqn).subdata(in: 2..<8) - self.address = address + self.myId = myId + self.podId = podId self.msgSeq = msgSeq self.milenage = try Milenage(k: ltk, sqn: self.eapSqn) } @@ -93,7 +95,8 @@ class SessionEstablisher { ) return MessagePacket( type: MessageType.SESSION_ESTABLISHMENT, - destination: address, + source: myId, + destination: podId, payload: eapMsg.toData(), sequenceNumber: UInt8(msgSeq) ) @@ -192,7 +195,8 @@ class SessionEstablisher { return MessagePacket( type: MessageType.SESSION_ESTABLISHMENT, - destination: address, + source: myId, + destination: podId, payload: eapMsg.toData(), sequenceNumber: UInt8(msgSeq) ) diff --git a/OmniBLE/PumpManager/MessageTransport.swift b/OmniBLE/PumpManager/MessageTransport.swift index ad40f186..c322d1cd 100644 --- a/OmniBLE/PumpManager/MessageTransport.swift +++ b/OmniBLE/PumpManager/MessageTransport.swift @@ -21,13 +21,15 @@ public struct MessageTransportState: Equatable, RawRepresentable { public var ck: Data? public var noncePrefix: Data? - public var msgSeq: Int // 8-bit Dash MessagePacket sequence # - public var nonceSeq: Int - public var messageNumber: Int // 4-bit Omnipod Message # + public var eapSeq: Int // per session sequence # + public var msgSeq: Int // 8-bit Dash MessagePacket sequence # (with ck) + public var nonceSeq: Int // nonce sequence # (with noncePrefix) + public var messageNumber: Int // 4-bit Omnipod Message # (for Omnipod command/responses Messages) - init(ck: Data?, noncePrefix: Data?, msgSeq: Int = 0, nonceSeq: Int = 0, messageNumber: Int = 0) { + init(ck: Data?, noncePrefix: Data?, eapSeq: Int = 1, msgSeq: Int = 0, nonceSeq: Int = 0, messageNumber: Int = 0) { self.ck = ck self.noncePrefix = noncePrefix + self.eapSeq = eapSeq self.msgSeq = msgSeq self.nonceSeq = nonceSeq self.messageNumber = messageNumber @@ -46,6 +48,7 @@ public struct MessageTransportState: Equatable, RawRepresentable { } self.ck = Data(hex: ckString) self.noncePrefix = Data(hex: noncePrefixString) + self.eapSeq = rawValue["eapSeq"] as? Int ?? 1 self.msgSeq = msgSeq self.nonceSeq = nonceSeq self.messageNumber = messageNumber @@ -55,6 +58,7 @@ public struct MessageTransportState: Equatable, RawRepresentable { return [ "ck": ck?.hexadecimalString ?? "", "noncePrefix": noncePrefix?.hexadecimalString ?? "", + "eapSeq": eapSeq, "msgSeq": msgSeq, "nonceSeq": nonceSeq, "messageNumber": messageNumber @@ -69,6 +73,7 @@ extension MessageTransportState: CustomDebugStringConvertible { "## MessageTransportState", "ck: " + (ck != nil ? ck!.hexadecimalString : "nil"), "noncePrefix: " + (noncePrefix != nil ? noncePrefix!.hexadecimalString : "nil"), + "eapSeq: \(eapSeq)", "msgSeq: \(msgSeq)", "nonceSeq: \(nonceSeq)", "messageNumber: \(messageNumber)", @@ -103,7 +108,7 @@ class PodMessageTransport: MessageTransport { private let log = OSLog(category: "PodMessageTransport") - private var state: MessageTransportState { + private(set) var state: MessageTransportState { didSet { self.delegate?.messageTransport(self, didUpdate: state) } @@ -127,6 +132,15 @@ class PodMessageTransport: MessageTransport { } } + private(set) var eapSeq: Int { + get { + return state.eapSeq + } + set { + state.eapSeq = newValue + } + } + private(set) var msgSeq: Int { get { return state.msgSeq @@ -154,14 +168,16 @@ class PodMessageTransport: MessageTransport { } } - private let address: UInt32 + private let myId: UInt32 + private let podId: UInt32 weak var messageLogger: MessageLogger? weak var delegate: MessageTransportDelegate? - init(manager: PeripheralManager, address: UInt32, state: MessageTransportState) { + init(manager: PeripheralManager, myId: UInt32, podId: UInt32, state: MessageTransportState) { self.manager = manager - self.address = address + self.myId = myId + self.podId = podId self.state = state guard let noncePrefix = self.noncePrefix, let ck = self.ck else { return } @@ -222,7 +238,8 @@ class PodMessageTransport: MessageTransport { let msg = MessagePacket( type: MessageType.ENCRYPTED, - destination: self.address, + source: self.myId, + destination: self.podId, payload: wrapped, sequenceNumber: UInt8(msgSeq), eqos: 1 @@ -316,6 +333,7 @@ extension PodMessageTransport: CustomDebugStringConvertible { "## PodMessageTransport", "ck: " + (ck != nil ? ck!.hexadecimalString : "nil"), "noncePrefix: " + (noncePrefix != nil ? noncePrefix!.hexadecimalString : "nil"), + "eapSeq: \(eapSeq)", "msgSeq: \(msgSeq)", "nonceSeq: \(nonceSeq)", "messageNumber: \(messageNumber)", diff --git a/OmniBLE/PumpManager/OmniBLEPumpManager.swift b/OmniBLE/PumpManager/OmniBLEPumpManager.swift index d7c54a5d..32690dc2 100644 --- a/OmniBLE/PumpManager/OmniBLEPumpManager.swift +++ b/OmniBLE/PumpManager/OmniBLEPumpManager.swift @@ -84,7 +84,7 @@ public class OmniBLEPumpManager: DeviceManager { self.dateGenerator = dateGenerator - let podComms = PodComms(podState: state.podState, lotNo: state.podState?.lotNo, lotSeq: state.podState?.lotSeq) + let podComms = PodComms(podState: state.podState, myId: state.controllerId, podId: state.podId) self.lockedPodComms = Locked(podComms) self.podExpirationNotificationIdentifier = Alert.Identifier(managerIdentifier: managerIdentifier, @@ -634,7 +634,18 @@ extension OmniBLEPumpManager { public func forgetPod(completion: @escaping () -> Void) { //omnipod.permanentDisconnect() let resetPodState = { (_ state: inout OmniBLEPumpManagerState) in - self.podComms = PodComms(podState: nil, lotNo: nil, lotSeq: nil) + if state.controllerId == CONTROLLER_ID { + // Switch from using the common fixed controllerId to a created semi-unique one + state.controllerId = createControllerId() + state.podId = state.controllerId + 1 + self.log.info("Switched controllerId from %x to %x", CONTROLLER_ID, state.controllerId) + } else { + // Already have a created controllerId, just need to advance podId for the next pod + let lastPodId = state.podId + state.podId = nextPodId(lastPodId: lastPodId) + self.log.info("Advanced podId from %x to %x", lastPodId, state.podId) + } + self.podComms = PodComms(podState: nil, myId: state.controllerId, podId: state.podId) self.podComms.delegate = self self.podComms.messageLogger = self @@ -738,20 +749,8 @@ extension OmniBLEPumpManager { case .failure(let error): completion(.failure(.communication(error as? LocalizedError))) case .success: - // Create random address with 20 bits to match PDM, could easily use 24 bits instead - if self.state.pairingAttemptAddress == nil { - self.lockedState.mutate { (state) in - state.pairingAttemptAddress = 0x1f000000 | (arc4random() & 0x000fffff) - } - } - - self.podComms.pairAndSetupPod(address: self.state.pairingAttemptAddress!, timeZone: .currentFixed, messageLogger: self) { (result) in - - if case .success = result { - self.lockedState.mutate { (state) in - state.pairingAttemptAddress = nil - } - } + self.podComms.pairAndSetupPod(timeZone: .currentFixed, messageLogger: self) + { (result) in // Calls completion primeSession(result) diff --git a/OmniBLE/PumpManager/OmniBLEPumpManagerState.swift b/OmniBLE/PumpManager/OmniBLEPumpManagerState.swift index af1f8b47..be95a7f7 100644 --- a/OmniBLE/PumpManager/OmniBLEPumpManagerState.swift +++ b/OmniBLE/PumpManager/OmniBLEPumpManagerState.swift @@ -19,8 +19,6 @@ public struct OmniBLEPumpManagerState: RawRepresentable, Equatable { public var podState: PodState? - public var pairingAttemptAddress: UInt32? - public var timeZone: TimeZone public var basalSchedule: BasalSchedule @@ -29,6 +27,10 @@ public struct OmniBLEPumpManagerState: RawRepresentable, Equatable { public var confirmationBeeps: Bool + public var controllerId: UInt32 = 0 + + public var podId: UInt32 = 0 + public var scheduledExpirationReminderOffset: TimeInterval? public var defaultExpirationReminderOffset = Pod.defaultExpirationReminderOffset @@ -78,12 +80,20 @@ public struct OmniBLEPumpManagerState: RawRepresentable, Equatable { // MARK: - - public init(podState: PodState?, timeZone: TimeZone, basalSchedule: BasalSchedule, insulinType: InsulinType?) { + public init(podState: PodState?, timeZone: TimeZone, basalSchedule: BasalSchedule, controllerId: UInt32? = nil, podId: UInt32? = nil, insulinType: InsulinType?) { self.podState = podState self.timeZone = timeZone self.basalSchedule = basalSchedule self.unstoredDoses = [] self.confirmationBeeps = false + if controllerId != nil && podId != nil { + self.controllerId = controllerId! + self.podId = podId! + } else { + let myId = createControllerId() + self.controllerId = myId + self.podId = myId + 1 + } self.insulinType = insulinType self.lowReservoirReminderValue = Pod.defaultLowReservoirReminder self.podAttachmentConfirmed = false @@ -133,6 +143,15 @@ public struct OmniBLEPumpManagerState: RawRepresentable, Equatable { timeZone = TimeZone.currentFixed } + var controllerId = rawValue["controllerId"] as? UInt32 + var podId = rawValue["podId"] as? UInt32 + if controllerId == nil || podId == nil { + // continue using the constant controllerId + // value until this pod is deactivated + controllerId = CONTROLLER_ID + podId = podState?.address + } + var insulinType: InsulinType? if let rawInsulinType = rawValue["insulinType"] as? InsulinType.RawValue { insulinType = InsulinType(rawValue: rawInsulinType) @@ -142,6 +161,8 @@ public struct OmniBLEPumpManagerState: RawRepresentable, Equatable { podState: podState, timeZone: timeZone, basalSchedule: basalSchedule, + controllerId: controllerId, + podId: podId, insulinType: insulinType ?? .novolog ) @@ -155,10 +176,6 @@ public struct OmniBLEPumpManagerState: RawRepresentable, Equatable { self.confirmationBeeps = rawValue["confirmationBeeps"] as? Bool ?? rawValue["bolusBeeps"] as? Bool ?? false - if let pairingAttemptAddress = rawValue["pairingAttemptAddress"] as? UInt32 { - self.pairingAttemptAddress = pairingAttemptAddress - } - self.scheduledExpirationReminderOffset = rawValue["scheduledExpirationReminderOffset"] as? TimeInterval self.defaultExpirationReminderOffset = rawValue["defaultExpirationReminderOffset"] as? TimeInterval ?? Pod.defaultExpirationReminderOffset @@ -213,7 +230,8 @@ public struct OmniBLEPumpManagerState: RawRepresentable, Equatable { value["insulinType"] = insulinType?.rawValue value["podState"] = podState?.rawValue - value["pairingAttemptAddress"] = pairingAttemptAddress + value["controllerId"] = controllerId + value["podId"] = podId value["scheduledExpirationReminderOffset"] = scheduledExpirationReminderOffset value["defaultExpirationReminderOffset"] = defaultExpirationReminderOffset value["lowReservoirReminderValue"] = lowReservoirReminderValue @@ -253,7 +271,8 @@ extension OmniBLEPumpManagerState: CustomDebugStringConvertible { "* lastPumpDataReportDate: \(String(describing: lastPumpDataReportDate))", "* isPumpDataStale: \(String(describing: isPumpDataStale))", "* confirmationBeeps: \(String(describing: confirmationBeeps))", - "* pairingAttemptAddress: \(String(describing: pairingAttemptAddress))", + "* controllerId: \(String(format: "%08X", controllerId))", + "* podId: \(String(format: "%08X", podId))", "* insulinType: \(String(describing: insulinType))", "* scheduledExpirationReminderOffset: \(String(describing: scheduledExpirationReminderOffset))", "* defaultExpirationReminderOffset: \(defaultExpirationReminderOffset)", diff --git a/OmniBLE/PumpManager/PodComms.swift b/OmniBLE/PumpManager/PodComms.swift index a829a472..23d3abd7 100644 --- a/OmniBLE/PumpManager/PodComms.swift +++ b/OmniBLE/PumpManager/PodComms.swift @@ -25,11 +25,6 @@ public class PodComms: CustomDebugStringConvertible { } } - private let lotNo: UInt64? - private let lotSeq: UInt32? - -// private let configuredDevices: Locked> = Locked(Set()) - weak var delegate: PodCommsDelegate? weak var messageLogger: MessageLogger? @@ -57,13 +52,16 @@ public class PodComms: CustomDebugStringConvertible { private var needsSessionEstablishment: Bool = false private let bluetoothManager = BluetoothManager() + + private var myId: UInt32 + private var podId: UInt32 - init(podState: PodState?, lotNo: UInt64?, lotSeq: UInt32?) { + init(podState: PodState?, myId: UInt32 = 0, podId: UInt32 = 0) { self.podState = podState self.delegate = nil self.messageLogger = nil - self.lotNo = lotNo - self.lotSeq = lotSeq + self.myId = myId + self.podId = podId bluetoothManager.connectionDelegate = self if let podState = podState { bluetoothManager.connectToDevice(uuidString: podState.bleIdentifier) @@ -143,45 +141,32 @@ public class PodComms: CustomDebugStringConvertible { return versionResponse } - public func pairPod(ids: Ids) throws { + private func pairPod() throws { guard let manager = manager else { throw PodCommsError.podNotConnected } - let address = ids.podId.toUInt32() + let ids = Ids(myId: self.myId, podId: self.podId) let ltkExchanger = LTKExchanger(manager: manager, ids: ids) let response = try ltkExchanger.negotiateLTK() let ltk = response.ltk - guard address == response.address else { - log.debug("podPair: address %{public} doesn't match response value?!: %@", String(format: "%04X", address), String(describing: response)) - throw PodCommsError.invalidAddress(address: response.address, expectedAddress: address) - } - - // XXX need to rework things so that we don't have to create this temp PodState with the LTK to set up the encrypted transport - if podState == nil { - log.debug("pairPod: creating a temp podState for LTK using response %@", String(describing: response)) - podState = PodState( - address: response.address, - ltk: ltk, - firmwareVersion: "", - bleFirmwareVersion: "", - lotNo: 0, - lotSeq: 0, - productId: dashProductId, - bleIdentifier: manager.peripheral.identifier.uuidString - ) + guard podId == response.address else { + log.debug("podId 0x%x doesn't match response value!: %{public}@", podId, String(describing: response)) + throw PodCommsError.invalidAddress(address: response.address, expectedAddress: self.podId) } log.info("Establish an Eap Session") - try self.establishSession(msgSeq: Int(response.msgSeq)) - - log.info("LTK and encrypted transport now ready") - log.debug("pairPod: LTK and encrypted transport now ready, podState messageTransportState: %@", String(reflecting: podState!.messageTransportState)) + guard let messageTransportState = try establishSession(ltk: ltk, eapSeq: 1, msgSeq: Int(response.msgSeq)) else { + log.debug("pairPod: failed to create messageTransportState!") + throw PodCommsError.noPodPaired + } + + log.info("LTK and encrypted transport now ready, messageTransportState: %@", String(reflecting: messageTransportState)) // If we get here, we have the LTK all set up and we should be able use encrypted pod messages - let transport = PodMessageTransport(manager: manager, address: response.address, state: podState!.messageTransportState) + let transport = PodMessageTransport(manager: manager, myId: self.myId, podId: self.podId, state: messageTransportState) transport.messageLogger = messageLogger - // For Dash this command is vestigal and doesn't actually assign the address (ID) + // For Dash this command is vestigal and doesn't actually assign the address (podId) // any more as this is done earlier when the LTK is setup. But this Omnipod comamnd is still // needed albiet using 0xffffffff for the address while the Eros sets the 0x1f0xxxxx ID. let assignAddress = AssignAddressCommand(address: 0xffffffff) @@ -189,38 +174,17 @@ public class PodComms: CustomDebugStringConvertible { let versionResponse = try sendPairMessage(transport: transport, message: message) - // Information checks comparing the version response values to the earlier values - - // N.B., The pod simulator always returns 0xFFFFFFFF regardless of the address used in the AssignAddressCommand command - if versionResponse.address != 0xFFFFFFFF && versionResponse.address != response.address { - log.debug("pairPod: versionResponse.address 0x%08X (%d) doesn't match response.address of 0x%08x (%d)", versionResponse.address, versionResponse.address, response.address, response.address) - } - if let lotSeq = self.lotSeq { - if versionResponse.tid != lotSeq { - log.debug("pairPod: versionResponse.tid %d doesn't match lotSeq of %d", versionResponse.tid, lotSeq) - } - } else { - log.debug("pairPod: got versionResponse.tid of %d with no previous lotSeq", versionResponse.tid) - } - if let lotNo = self.lotNo { - if versionResponse.lot != lotNo { - log.debug("pairPod: versionResponse.lot %d doesn't match lotNo of %d", versionResponse.lot, lotNo) - } - } else { - log.debug("pairPod: got versionResponse.lot of %d with no previous lotNo", versionResponse.lot) - } - - // Now create the real PodState using the versionResponse info - log.debug("pairPod: creating PodState for versionResponse %{public}@", String(describing: versionResponse)) + // Now create the real PodState using the current transport state and the versionResponse info + log.debug("pairPod: creating PodState for versionResponse %{public}@ and transport %{public}@", String(describing: versionResponse), String(describing: transport.state)) self.podState = PodState( - address: response.address, + address: self.podId, ltk: ltk, firmwareVersion: String(describing: versionResponse.firmwareVersion), bleFirmwareVersion: String(describing: versionResponse.iFirmwareVersion), - lotNo: UInt64(versionResponse.lot), // XXX get 5-byte lotNo from attributes or use this 4-byte lotNo in versionResponse? - lotSeq: versionResponse.tid, // XXX get from attributes? + lotNo: versionResponse.lot, + lotSeq: versionResponse.tid, productId: versionResponse.productId, - messageTransportState: podState!.messageTransportState, + messageTransportState: transport.state, bleIdentifier: manager.peripheral.identifier.uuidString ) // podState setupProgress state should be addressAssigned @@ -235,49 +199,55 @@ public class PodComms: CustomDebugStringConvertible { log.debug("pairPod: self.PodState messageTransportState now: %@", String(reflecting: self.podState?.messageTransportState)) } - private func syncSession(_ ltk: Data, _ msgSeq: Int, _ address: UInt32, _ eapSqn: Int) throws -> Int? { + private func establishSession(ltk: Data, eapSeq: Int, msgSeq: Int = 1) throws -> MessageTransportState? { guard let manager = manager else { throw PodCommsError.noPodPaired } - let eapAkaExchanger = try SessionEstablisher(manager: manager, ltk: ltk, eapSqn: eapSqn, address: address, msgSeq: msgSeq) + let eapAkaExchanger = try SessionEstablisher(manager: manager, ltk: ltk, eapSqn: eapSeq, myId: self.myId, podId: self.podId, msgSeq: msgSeq) let result = try eapAkaExchanger.negotiateSessionKeys() switch result { case .SessionNegotiationResynchronization(let keys): - log.info("EAP AKA resynchronization: %@", keys.synchronizedEapSqn.data.hexadecimalString) - return keys.synchronizedEapSqn.toInt() + log.debug("Received EAP SQN resynchronization: %@", keys.synchronizedEapSqn.data.hexadecimalString) + if self.podState != nil { + let eapSeq = keys.synchronizedEapSqn.toInt() + log.debug("Updating EAP SQN to: %d", eapSeq) + self.podState!.messageTransportState.eapSeq = eapSeq + } + return nil case .SessionKeys(let keys): log.debug("Session Established") log.debug("CK: %@", keys.ck.hexadecimalString) log.info("msgSequenceNumber: %@", String(keys.msgSequenceNumber)) log.info("NoncePrefix: %@", keys.nonce.prefix.hexadecimalString) - self.podState?.messageTransportState = MessageTransportState(ck: keys.ck, noncePrefix: keys.nonce.prefix, msgSeq: keys.msgSequenceNumber, nonceSeq: 0) + let omnipodMessageNumber = self.podState?.messageTransportState.messageNumber ?? 0 + let messageTransportState = MessageTransportState( + ck: keys.ck, + noncePrefix: keys.nonce.prefix, + eapSeq: eapSeq, + msgSeq: keys.msgSequenceNumber, + messageNumber: omnipodMessageNumber + ) - log.debug("syncSession: set up podState messageTransportState: %@", String(reflecting: self.podState?.messageTransportState)) - return nil + if self.podState != nil { + log.debug("Setting podState transport state to %{public}@", String(describing: messageTransportState)) + self.podState!.messageTransportState = messageTransportState + } else { + log.debug("Used keys %@ to create messageTransportState: %@", String(reflecting: keys), String(reflecting: messageTransportState)) + } + return messageTransportState } } - public func establishSession(msgSeq: Int) throws { - guard var podState = self.podState else { + private func establishNewSession() throws { + guard self.podState != nil else { throw PodCommsError.noPodPaired } - let eapSqn = podState.increaseEapAkaSequenceNumber() - - guard self.podState!.ltk == podState.ltk else { - throw PodCommsError.invalidData - } - guard self.podState!.address == podState.address else { - throw PodCommsError.invalidData - } - var newSqn = try self.syncSession(podState.ltk, msgSeq, podState.address, eapSqn) - - if (newSqn != nil) { - log.debug("Updating EAP SQN to: %d", newSqn!) - podState.eapAkaSequenceNumber = newSqn! - newSqn = try self.syncSession(podState.ltk, msgSeq, podState.address, podState.increaseEapAkaSequenceNumber()) - if (newSqn != nil) { + let mts = try establishSession(ltk: self.podState!.ltk, eapSeq: self.podState!.incrementEapSeq()) + if mts == nil { + let mts = try establishSession(ltk: self.podState!.ltk, eapSeq: self.podState!.incrementEapSeq()) + if (mts == nil) { throw PodCommsError.diagnosticMessage(str: "Received resynchronization SQN for the second time") } } @@ -286,9 +256,8 @@ public class PodComms: CustomDebugStringConvertible { private func setupPod(podState: PodState, timeZone: TimeZone) throws { guard let manager = manager else { throw PodCommsError.podNotConnected } - let transport = PodMessageTransport(manager: manager, address: podState.address, state: podState.messageTransportState) + let transport = PodMessageTransport(manager: manager, myId: self.myId, podId: self.podId, state: podState.messageTransportState) transport.messageLogger = messageLogger - log.debug("setupPod: created transport %@ using podState %@ with messageTransportState %@", String(reflecting: transport), String(reflecting: podState), String(reflecting: podState.messageTransportState)) let dateComponents = SetupPodCommand.dateComponents(date: Date(), timeZone: timeZone) let setupPod = SetupPodCommand(address: podState.address, dateComponents: dateComponents, lot: UInt32(podState.lotNo), tid: podState.lotSeq) @@ -339,7 +308,6 @@ public class PodComms: CustomDebugStringConvertible { } func pairAndSetupPod( - address: UInt32, timeZone: TimeZone, messageLogger: MessageLogger?, _ block: @escaping (_ result: SessionRunResult) -> Void @@ -350,19 +318,21 @@ public class PodComms: CustomDebugStringConvertible { return } + let myId = self.myId + let podId = self.podId + log.info("Attempting to pair using myId %X and podId %X", myId, podId) + manager.runSession(withName: "Pair and setup pod") { [weak self] in do { guard let self = self else { fatalError() } - try manager.sendHello(Ids.controllerId().address) + try manager.sendHello(myId: myId) try manager.enableNotifications() // Seemingly this cannot be done before the hello command, or the pod disconnects if (!self.isPaired) { - let ids = Ids(podState: self.podState) - try self.pairPod(ids: ids) - } - else { - try self.establishSession(msgSeq: 1) + try self.pairPod() + } else { + try self.establishNewSession() } guard self.podState != nil else { @@ -380,7 +350,7 @@ public class PodComms: CustomDebugStringConvertible { } // Run a session now for any post-pairing commands - let transport = PodMessageTransport(manager: manager, address: self.podState!.address, state: self.podState!.messageTransportState) + let transport = PodMessageTransport(manager: manager, myId: myId, podId: podId, state: self.podState!.messageTransportState) transport.messageLogger = self.messageLogger let podSession = PodCommsSession(podState: self.podState!, transport: transport, delegate: self) @@ -413,7 +383,7 @@ public class PodComms: CustomDebugStringConvertible { } // self.configureDevice(device, with: commandSession) no RL to configure - let transport = PodMessageTransport(manager: manager, address: self.podState!.address, state: self.podState!.messageTransportState) + let transport = PodMessageTransport(manager: manager, myId: self.myId, podId: self.podId, state: self.podState!.messageTransportState) transport.messageLogger = self.messageLogger let podSession = PodCommsSession(podState: self.podState!, transport: transport, delegate: self) block(.success(session: podSession)) @@ -425,6 +395,8 @@ public class PodComms: CustomDebugStringConvertible { public var debugDescription: String { return [ "## PodComms", + "* myId: \(String(format: "%08X", myId))", + "* podId: \(String(format: "%08X", podId))", "podState: \(String(reflecting: podState))", "delegate: \(String(describing: delegate != nil))", "" @@ -464,11 +436,12 @@ extension PodComms: PeripheralManagerDelegate { log.default("PodComms completeConfiguration") if self.isPaired && needsSessionEstablishment { + let myId = self.myId manager.runSession(withName: "establish pod session") { [weak self] in do { - try manager.sendHello(Ids.controllerId().address) + try manager.sendHello(myId: myId) try manager.enableNotifications() // Seemingly this cannot be done before the hello command, or the pod disconnects - try self?.establishSession(msgSeq: 1) + try self?.establishNewSession() } catch { self?.log.error("Pod session sync error: %{public}@", String(describing: error)) } diff --git a/OmniBLE/PumpManager/PodState.swift b/OmniBLE/PumpManager/PodState.swift index ff16fd6b..0336c55f 100644 --- a/OmniBLE/PumpManager/PodState.swift +++ b/OmniBLE/PumpManager/PodState.swift @@ -53,8 +53,7 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl public let address: UInt32 public let ltk: Data - public var eapAkaSequenceNumber: Int - + public var bleIdentifier: String public var activatedAt: Date? @@ -64,7 +63,7 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl public let firmwareVersion: String public let bleFirmwareVersion: String - public let lotNo: UInt64 + public let lotNo: UInt32 public let lotSeq: UInt32 public let productId: UInt8 var activeAlertSlots: AlertSet @@ -106,7 +105,7 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl return active } - public init(address: UInt32, ltk: Data, firmwareVersion: String, bleFirmwareVersion: String, lotNo: UInt64, lotSeq: UInt32, productId: UInt8, messageTransportState: MessageTransportState? = nil, bleIdentifier: String) { + public init(address: UInt32, ltk: Data, firmwareVersion: String, bleFirmwareVersion: String, lotNo: UInt32, lotSeq: UInt32, productId: UInt8, messageTransportState: MessageTransportState? = nil, bleIdentifier: String) { self.address = address self.ltk = ltk self.firmwareVersion = firmwareVersion @@ -123,7 +122,6 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl self.primeFinishTime = nil self.setupProgress = .addressAssigned self.configuredAlerts = [.slot7: .waitingForPairingReminder] - self.eapAkaSequenceNumber = 1 self.bleIdentifier = bleIdentifier } @@ -151,9 +149,9 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl return fault != nil || setupProgress == .activationTimeout || setupProgress == .podIncompatible } - public mutating func increaseEapAkaSequenceNumber() -> Int { - self.eapAkaSequenceNumber += 1 - return eapAkaSequenceNumber + public mutating func incrementEapSeq() -> Int { + self.messageTransportState.eapSeq += 1 + return messageTransportState.eapSeq } public mutating func advanceToNextNonce() { @@ -276,11 +274,10 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl guard let address = rawValue["address"] as? UInt32, let ltkString = rawValue["ltk"] as? String, - let eapAkaSequenceNumber = rawValue["eapAkaSequenceNumber"] as? Int, let firmwareVersion = rawValue["firmwareVersion"] as? String, let bleFirmwareVersion = rawValue["bleFirmwareVersion"] as? String, let bleIdentifier = rawValue["bleIdentifier"] as? String, - let lotNo = rawValue["lotNo"] as? UInt64, + let lotNo = rawValue["lotNo"] as? UInt32, let lotSeq = rawValue["lotSeq"] as? UInt32 else { return nil @@ -288,7 +285,6 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl self.address = address self.ltk = Data(hex: ltkString) - self.eapAkaSequenceNumber = eapAkaSequenceNumber self.firmwareVersion = firmwareVersion self.bleFirmwareVersion = bleFirmwareVersion self.lotNo = lotNo @@ -418,7 +414,7 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl var rawValue: RawValue = [ "address": address, "ltk": ltk.hexadecimalString, - "eapAkaSequenceNumber": eapAkaSequenceNumber, + "eapAkaSequenceNumber": 1, // keep for back migration, was always 1 "firmwareVersion": firmwareVersion, "bleFirmwareVersion": bleFirmwareVersion, "lotNo": lotNo, @@ -485,9 +481,8 @@ public struct PodState: RawRepresentable, Equatable, CustomDebugStringConvertibl public var debugDescription: String { return [ "### PodState", - "* address: \(String(format: "%04X", address))", + "* address: \(String(format: "%08X", address))", "* ltk: \(ltk.hexadecimalString)", - "* eapAkaSequenceNumber: \(eapAkaSequenceNumber)", "* bleIdentifier: \(bleIdentifier)", "* activatedAt: \(String(reflecting: activatedAt))", "* expiresAt: \(String(reflecting: expiresAt))", diff --git a/OmniBLE/PumpManagerUI/Views/PodDetailsView.swift b/OmniBLE/PumpManagerUI/Views/PodDetailsView.swift index 3552e61e..5b7ff36b 100644 --- a/OmniBLE/PumpManagerUI/Views/PodDetailsView.swift +++ b/OmniBLE/PumpManagerUI/Views/PodDetailsView.swift @@ -10,7 +10,7 @@ import SwiftUI import LoopKitUI public struct PodVersion { - var lotNumber: UInt64 + var lotNumber: UInt32 var sequenceNumber: UInt32 var firmwareVersion: String var bleFirmwareVersion: String From 5b06c7fda29f8ae7f7a59f4b4ad445e5fcf41341 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Fri, 28 Jan 2022 10:58:10 -0600 Subject: [PATCH 2/3] Fix build on sim and make another log value public --- OmniBLE/Bluetooth/PeripheralManager.swift | 2 +- OmniBLE/PumpManager/OmniBLEPumpManager.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/OmniBLE/Bluetooth/PeripheralManager.swift b/OmniBLE/Bluetooth/PeripheralManager.swift index eb413187..52fb0225 100644 --- a/OmniBLE/Bluetooth/PeripheralManager.swift +++ b/OmniBLE/Bluetooth/PeripheralManager.swift @@ -128,7 +128,7 @@ extension PeripheralManager { self.log.default("Peripheral configuration completed") } catch let error { - self.log.error("Error applying peripheral configuration: %@", String(describing: error)) + self.log.error("Error applying peripheral configuration: %{public}@", String(describing: error)) // Will retry } } diff --git a/OmniBLE/PumpManager/OmniBLEPumpManager.swift b/OmniBLE/PumpManager/OmniBLEPumpManager.swift index 32690dc2..6b0afb2f 100644 --- a/OmniBLE/PumpManager/OmniBLEPumpManager.swift +++ b/OmniBLE/PumpManager/OmniBLEPumpManager.swift @@ -774,7 +774,7 @@ extension OmniBLEPumpManager { #if targetEnvironment(simulator) let mockDelay = TimeInterval(seconds: 3) DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + mockDelay) { - let result = self.setStateWithResult({ (state) -> PumpManagerResult in + let result = self.setStateWithResult({ (state) -> Result in // Mock fault // let fault = try! DetailedStatus(encodedData: Data(hexadecimalString: "020d0000000e00c36a020703ff020900002899080082")!) // self.state.podState?.fault = fault From 9348a2a3c0926fac0f33ebda7bfbea887f7db37b Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Fri, 28 Jan 2022 14:44:04 -0600 Subject: [PATCH 3/3] Clear out command queue on connect, logging updates, and remove peripheral from auto-connect when discarding pod --- OmniBLE/Bluetooth/PeripheralManager+OmniBLE.swift | 6 +++--- OmniBLE/Bluetooth/PeripheralManager.swift | 9 +++++++-- OmniBLE/PumpManager/OmniBLEPumpManager.swift | 4 +++- OmniBLE/PumpManager/PodComms.swift | 7 +++++++ 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/OmniBLE/Bluetooth/PeripheralManager+OmniBLE.swift b/OmniBLE/Bluetooth/PeripheralManager+OmniBLE.swift index 6be5e844..2761f523 100644 --- a/OmniBLE/Bluetooth/PeripheralManager+OmniBLE.swift +++ b/OmniBLE/Bluetooth/PeripheralManager+OmniBLE.swift @@ -110,7 +110,7 @@ extension PeripheralManager { packet = try MessagePacket.parse(payload: fullPayload) } catch { - log.default("Error reading message: %{public}@", error.localizedDescription) + log.error("Error reading message: %{public}@", error.localizedDescription) try? sendCommandType(PodCommand.NACK) throw PeripheralManagerError.incorrectResponse } @@ -163,7 +163,7 @@ extension PeripheralManager { let value = cmdQueue.remove(at: 0) if command.rawValue != value[0] { - log.default("Data Wrong command.rawValue (%d != %d).", command.rawValue, value[0]) + log.error("Data Wrong command.rawValue != value[0] (%d != %d).", command.rawValue, value[0]) throw PeripheralManagerError.incorrectResponse } return @@ -205,7 +205,7 @@ extension PeripheralManager { let data = dataQueue.remove(at: 0) if (data[0] != sequence) { - log.default("Data Wrong data[0] (%d != %d).", data[0], sequence) + log.error("Data Wrong data[0] != sequence (%d != %d).", data[0], sequence) throw PeripheralManagerError.incorrectResponse } return data diff --git a/OmniBLE/Bluetooth/PeripheralManager.swift b/OmniBLE/Bluetooth/PeripheralManager.swift index 52fb0225..f1029f1a 100644 --- a/OmniBLE/Bluetooth/PeripheralManager.swift +++ b/OmniBLE/Bluetooth/PeripheralManager.swift @@ -36,9 +36,7 @@ class PeripheralManager: NSObject { } var dataQueue: [Data] = [] - var dataEvent: (() -> Void)? var cmdQueue: [Data] = [] - var cmdEvent: (() -> Void)? let queueLock = NSCondition() /// The dispatch queue used to serialize operations on the peripheral @@ -460,6 +458,13 @@ extension PeripheralManager: CBCentralManagerDelegate { self.log.debug("PeripheralManager - didConnect: %@", peripheral) switch peripheral.state { case .connected: + queueLock.lock() + if cmdQueue.count > 0 { + self.log.default("Removing %{public}d leftover elements from command queue", cmdQueue.count) + cmdQueue.removeAll() + } + queueLock.unlock() + self.log.debug("PeripheralManager - didConnect - running assertConfiguration") assertConfiguration() default: diff --git a/OmniBLE/PumpManager/OmniBLEPumpManager.swift b/OmniBLE/PumpManager/OmniBLEPumpManager.swift index 6b0afb2f..89a19e51 100644 --- a/OmniBLE/PumpManager/OmniBLEPumpManager.swift +++ b/OmniBLE/PumpManager/OmniBLEPumpManager.swift @@ -632,7 +632,9 @@ extension OmniBLEPumpManager { // Does not support concurrent callers. Not thread-safe. public func forgetPod(completion: @escaping () -> Void) { - //omnipod.permanentDisconnect() + + self.podComms.forgetCurrentPod() + let resetPodState = { (_ state: inout OmniBLEPumpManagerState) in if state.controllerId == CONTROLLER_ID { // Switch from using the common fixed controllerId to a created semi-unique one diff --git a/OmniBLE/PumpManager/PodComms.swift b/OmniBLE/PumpManager/PodComms.swift index 23d3abd7..50701ef6 100644 --- a/OmniBLE/PumpManager/PodComms.swift +++ b/OmniBLE/PumpManager/PodComms.swift @@ -67,6 +67,13 @@ public class PodComms: CustomDebugStringConvertible { bluetoothManager.connectToDevice(uuidString: podState.bleIdentifier) } } + + public func forgetCurrentPod() { + if let manager = manager { + self.log.default("Removing %{public}@ from auto-connect ids", manager.peripheral) + bluetoothManager.disconnectFromDevice(uuidString: manager.peripheral.identifier.uuidString) + } + } public func connectToNewPod(_ completion: @escaping (Result) -> Void) { let discoveryStartTime = Date()