Transmitter.swift 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577
  1. //
  2. // Transmitter.swift
  3. // xDripG5
  4. //
  5. // Created by Nathan Racklyeft on 11/22/15.
  6. // Copyright © 2015 Nathan Racklyeft. All rights reserved.
  7. //
  8. import Foundation
  9. import CoreBluetooth
  10. import HealthKit
  11. import os.log
  12. public protocol TransmitterDelegate: AnyObject {
  13. func transmitterDidConnect(_ transmitter: Transmitter)
  14. func transmitter(_ transmitter: Transmitter, didError error: Error)
  15. func transmitter(_ transmitter: Transmitter, didRead glucose: Glucose)
  16. func transmitter(_ transmitter: Transmitter, didReadBackfill glucose: [Glucose])
  17. func transmitter(_ transmitter: Transmitter, didReadUnknownData data: Data)
  18. }
  19. /// These methods are called on a private background queue. It is the responsibility of the client to ensure thread-safety.
  20. public protocol TransmitterCommandSource: AnyObject {
  21. func dequeuePendingCommand(for transmitter: Transmitter) -> Command?
  22. func transmitter(_ transmitter: Transmitter, didFail command: Command, with error: Error)
  23. func transmitter(_ transmitter: Transmitter, didComplete command: Command)
  24. }
  25. public enum TransmitterError: Error {
  26. case authenticationError(String)
  27. case controlError(String)
  28. case observationError(String)
  29. }
  30. extension TransmitterError: CustomStringConvertible {
  31. public var description: String {
  32. switch self {
  33. case .authenticationError(let description):
  34. return description
  35. case .controlError(let description):
  36. return description
  37. case .observationError(let description):
  38. return description
  39. }
  40. }
  41. }
  42. public final class Transmitter: BluetoothManagerDelegate {
  43. /// The ID of the transmitter to connect to
  44. public var ID: String {
  45. return id.id
  46. }
  47. private var id: TransmitterID
  48. public var passiveModeEnabled: Bool
  49. public weak var delegate: TransmitterDelegate?
  50. public weak var commandSource: TransmitterCommandSource?
  51. // MARK: - Passive observation state, confined to `bluetoothManager.managerQueue`
  52. /// The initial activation date of the transmitter
  53. private var activationDate: Date?
  54. /// The last-observed time message
  55. private var lastTimeMessage: TransmitterTimeRxMessage? {
  56. didSet {
  57. if let time = lastTimeMessage {
  58. activationDate = Date(timeIntervalSinceNow: -TimeInterval(time.currentTime))
  59. } else {
  60. activationDate = nil
  61. }
  62. }
  63. }
  64. /// The last-observed calibration message
  65. private var lastCalibrationMessage: CalibrationDataRxMessage?
  66. /// The backfill data buffer
  67. private var backfillBuffer: GlucoseBackfillFrameBuffer?
  68. // MARK: -
  69. private let log = OSLog(category: "Transmitter")
  70. private let bluetoothManager = BluetoothManager()
  71. private let delegateQueue = DispatchQueue(label: "com.loudnate.CGMBLEKit.delegateQueue", qos: .unspecified)
  72. public init(id: String, peripheralIdentifier: UUID? = nil, passiveModeEnabled: Bool = false) {
  73. self.id = TransmitterID(id: id)
  74. self.passiveModeEnabled = passiveModeEnabled
  75. bluetoothManager.peripheralIdentifier = peripheralIdentifier
  76. bluetoothManager.delegate = self
  77. }
  78. public func resumeScanning() {
  79. if stayConnected {
  80. bluetoothManager.scanForPeripheral()
  81. }
  82. }
  83. public func stopScanning() {
  84. bluetoothManager.disconnect()
  85. }
  86. public var isScanning: Bool {
  87. return bluetoothManager.isScanning
  88. }
  89. public var peripheralIdentifier: UUID? {
  90. get {
  91. return bluetoothManager.peripheralIdentifier
  92. }
  93. set {
  94. bluetoothManager.peripheralIdentifier = newValue
  95. }
  96. }
  97. public var stayConnected: Bool {
  98. get {
  99. return bluetoothManager.stayConnected
  100. }
  101. set {
  102. bluetoothManager.stayConnected = newValue
  103. if newValue {
  104. bluetoothManager.scanForPeripheral()
  105. }
  106. }
  107. }
  108. // MARK: - BluetoothManagerDelegate
  109. func bluetoothManager(_ manager: BluetoothManager, peripheralManager: PeripheralManager, isReadyWithError error: Error?) {
  110. if let error = error {
  111. delegateQueue.async {
  112. self.delegate?.transmitter(self, didError: error)
  113. }
  114. return
  115. }
  116. delegateQueue.async {
  117. self.delegate?.transmitterDidConnect(self)
  118. }
  119. peripheralManager.perform { (peripheral) in
  120. if self.passiveModeEnabled {
  121. self.log.debug("Listening for authentication responses in passive mode")
  122. do {
  123. try peripheral.listenToCharacteristic(.authentication)
  124. } catch let error {
  125. self.delegateQueue.async {
  126. self.delegate?.transmitter(self, didError: error)
  127. }
  128. }
  129. } else {
  130. do {
  131. self.log.debug("Authenticating with transmitter")
  132. let status = try peripheral.authenticate(id: self.id)
  133. if !status.isBonded {
  134. self.log.debug("Requesting bond")
  135. try peripheral.requestBond()
  136. self.log.debug("Bonding request sent. Waiting user to respond.")
  137. }
  138. try peripheral.enableNotify(shouldWaitForBond: !status.isBonded)
  139. defer {
  140. self.log.debug("Initiating a disconnect")
  141. peripheral.disconnect()
  142. }
  143. self.log.debug("Reading time")
  144. let timeMessage = try peripheral.readTimeMessage()
  145. let activationDate = Date(timeIntervalSinceNow: -TimeInterval(timeMessage.currentTime))
  146. self.log.debug("Determined activation date: %@", String(describing: activationDate))
  147. while let command = self.commandSource?.dequeuePendingCommand(for: self) {
  148. self.log.debug("Sending command: %@", String(describing: command))
  149. do {
  150. _ = try peripheral.sendCommand(command, activationDate: activationDate)
  151. self.commandSource?.transmitter(self, didComplete: command)
  152. } catch let error {
  153. self.commandSource?.transmitter(self, didFail: command, with: error)
  154. }
  155. }
  156. self.log.debug("Reading glucose")
  157. let glucoseMessage = try peripheral.readGlucose()
  158. self.log.debug("Reading calibration data")
  159. let calibrationMessage = try? peripheral.readCalibrationData()
  160. let glucose = Glucose(
  161. transmitterID: self.id.id,
  162. glucoseMessage: glucoseMessage,
  163. timeMessage: timeMessage,
  164. calibrationMessage: calibrationMessage,
  165. activationDate: activationDate
  166. )
  167. self.delegateQueue.async {
  168. self.delegate?.transmitter(self, didRead: glucose)
  169. }
  170. } catch let error {
  171. self.delegateQueue.async {
  172. self.delegate?.transmitter(self, didError: error)
  173. }
  174. }
  175. }
  176. }
  177. }
  178. func bluetoothManager(_ manager: BluetoothManager, shouldConnectPeripheral peripheral: CBPeripheral) -> Bool {
  179. /// The Dexcom G5 advertises a peripheral name of "DexcomXX"
  180. /// where "XX" is the last-two characters of the transmitter ID.
  181. if let name = peripheral.name, name.suffix(2) == id.id.suffix(2) {
  182. return true
  183. } else {
  184. self.log.info("Not connecting to peripheral: %{public}@", peripheral.name ?? String(describing: peripheral))
  185. return false
  186. }
  187. }
  188. func bluetoothManager(_ manager: BluetoothManager, peripheralManager: PeripheralManager, didReceiveControlResponse response: Data) {
  189. guard passiveModeEnabled else { return }
  190. guard response.count > 0 else { return }
  191. switch Opcode(rawValue: response[0]) {
  192. case .glucoseRx?, .glucoseG6Rx?:
  193. if let glucoseMessage = GlucoseRxMessage(data: response),
  194. let timeMessage = lastTimeMessage,
  195. let activationDate = activationDate
  196. {
  197. delegateQueue.async {
  198. self.delegate?.transmitter(self, didRead: Glucose(transmitterID: self.id.id, glucoseMessage: glucoseMessage, timeMessage: timeMessage, calibrationMessage: self.lastCalibrationMessage, activationDate: activationDate))
  199. }
  200. } else {
  201. delegateQueue.async {
  202. self.delegate?.transmitter(self, didError: TransmitterError.observationError("Unable to handle glucose control response"))
  203. }
  204. }
  205. peripheralManager.perform { (peripheral) in
  206. // Subscribe to backfill updates
  207. do {
  208. try peripheral.listenToCharacteristic(.backfill)
  209. } catch let error {
  210. self.log.error("Error trying to enable notifications on backfill characteristic: %{public}@", String(describing: error))
  211. self.delegateQueue.async {
  212. self.delegate?.transmitter(self, didError: error)
  213. }
  214. }
  215. }
  216. case .transmitterTimeRx?:
  217. if let timeMessage = TransmitterTimeRxMessage(data: response) {
  218. self.lastTimeMessage = timeMessage
  219. }
  220. case .glucoseBackfillRx?:
  221. guard let backfillMessage = GlucoseBackfillRxMessage(data: response) else {
  222. break
  223. }
  224. guard let backfillBuffer = backfillBuffer else {
  225. log.error("Received GlucoseBackfillRxMessage %{public}@ but backfillBuffer is nil", String(describing: backfillMessage))
  226. self.delegateQueue.async {
  227. self.delegate?.transmitter(self, didError: TransmitterError.observationError("Received GlucoseBackfillRxMessage but backfillBuffer is nil"))
  228. }
  229. break
  230. }
  231. guard let timeMessage = lastTimeMessage, let activationDate = activationDate else {
  232. log.error("Received GlucoseBackfillRxMessage %{public}@ but activationDate is unknown", String(describing: backfillMessage))
  233. self.delegateQueue.async {
  234. self.delegate?.transmitter(self, didError: TransmitterError.observationError("Received GlucoseBackfillRxMessage but activationDate is unknown"))
  235. }
  236. break
  237. }
  238. guard backfillMessage.bufferLength == backfillBuffer.count else {
  239. log.error("GlucoseBackfillRxMessage expected buffer length %d, but was %d", backfillMessage.bufferLength, backfillBuffer.count)
  240. self.delegateQueue.async {
  241. self.delegate?.transmitter(self, didError: TransmitterError.observationError("GlucoseBackfillRxMessage expected buffer length \(backfillMessage.bufferLength), but was \(backfillBuffer.count): \(response.hexadecimalString) "))
  242. }
  243. break
  244. }
  245. guard backfillMessage.bufferCRC == backfillBuffer.crc16 else {
  246. log.error("GlucoseBackfillRxMessage expected CRC %04x, but was %04x", backfillMessage.bufferCRC, backfillBuffer.crc16)
  247. self.delegateQueue.async {
  248. self.delegate?.transmitter(self, didError: TransmitterError.observationError("GlucoseBackfillRxMessage expected CRC \(backfillMessage.bufferCRC), but was \(backfillBuffer.crc16)"))
  249. }
  250. break
  251. }
  252. let glucose = backfillBuffer.glucose.map {
  253. Glucose(transmitterID: id.id, status: backfillMessage.status, glucoseMessage: $0, timeMessage: timeMessage, activationDate: activationDate)
  254. }
  255. guard glucose.count > 0 else {
  256. break
  257. }
  258. guard glucose.first!.glucoseMessage.timestamp == backfillMessage.startTime,
  259. glucose.last!.glucoseMessage.timestamp == backfillMessage.endTime,
  260. glucose.first!.glucoseMessage.timestamp <= glucose.last!.glucoseMessage.timestamp
  261. else {
  262. log.error("GlucoseBackfillRxMessage time interval not reflected in glucose: %{public}@, buffer: %{public}@", response.hexadecimalString, String(reflecting: backfillBuffer))
  263. self.delegateQueue.async {
  264. self.delegate?.transmitter(self, didError: TransmitterError.observationError("GlucoseBackfillRxMessage time interval not reflected in glucose: \(backfillMessage.startTime) - \(backfillMessage.endTime), buffer: \(glucose.first!.glucoseMessage.timestamp) - \(glucose.last!.glucoseMessage.timestamp)"))
  265. }
  266. break
  267. }
  268. delegateQueue.async {
  269. self.delegate?.transmitter(self, didReadBackfill: glucose)
  270. }
  271. case .calibrationDataRx?:
  272. guard let calibrationDataMessage = CalibrationDataRxMessage(data: response) else {
  273. break
  274. }
  275. lastCalibrationMessage = calibrationDataMessage
  276. case .none:
  277. delegateQueue.async {
  278. self.delegate?.transmitter(self, didReadUnknownData: response)
  279. }
  280. default:
  281. // We ignore all other known opcodes
  282. break
  283. }
  284. }
  285. func bluetoothManager(_ manager: BluetoothManager, didReceiveBackfillResponse response: Data) {
  286. guard response.count > 2 else {
  287. return
  288. }
  289. if response[0] == 1 {
  290. log.info("Starting new backfill buffer with ID %d", response[1])
  291. self.backfillBuffer = GlucoseBackfillFrameBuffer(identifier: response[1])
  292. }
  293. log.info("appending to backfillBuffer: %@", response.hexadecimalString)
  294. self.backfillBuffer?.append(response)
  295. }
  296. func bluetoothManager(_ manager: BluetoothManager, peripheralManager: PeripheralManager, didReceiveAuthenticationResponse response: Data) {
  297. if let message = AuthChallengeRxMessage(data: response), message.isBonded, message.isAuthenticated {
  298. self.log.debug("Observed authenticated session. enabling notifications for control characteristic.")
  299. peripheralManager.perform { (peripheral) in
  300. // Stopping updates from authentication simultaneously with Dexcom's app causes CoreBluetooth to get into a weird state.
  301. /*
  302. do {
  303. try peripheral.stopListeningToCharacteristic(.authentication)
  304. } catch let error {
  305. self.log.error("Error trying to disable notifications on authentication characteristic: %{public}@", String(describing: error))
  306. }
  307. */
  308. do {
  309. try peripheral.listenToCharacteristic(.control)
  310. } catch let error {
  311. self.log.error("Error trying to enable notifications on control characteristic: %{public}@", String(describing: error))
  312. self.delegateQueue.async {
  313. self.delegate?.transmitter(self, didError: error)
  314. }
  315. }
  316. }
  317. } else {
  318. self.log.debug("Ignoring authentication response: %{public}@", response.hexadecimalString)
  319. }
  320. }
  321. }
  322. extension Transmitter: CustomDebugStringConvertible {
  323. public var debugDescription: String {
  324. return [
  325. "## Transmitter",
  326. String(reflecting: bluetoothManager),
  327. ].joined(separator: "\n")
  328. }
  329. }
  330. struct TransmitterID {
  331. let id: String
  332. init(id: String) {
  333. self.id = id
  334. }
  335. private var cryptKey: Data? {
  336. return "00\(id)00\(id)".data(using: .utf8)
  337. }
  338. func computeHash(of data: Data) -> Data? {
  339. guard data.count == 8, let key = cryptKey else {
  340. return nil
  341. }
  342. var doubleData = Data(capacity: data.count * 2)
  343. doubleData.append(data)
  344. doubleData.append(data)
  345. guard let outData = try? AESCrypt.encryptData(doubleData, usingKey: key) else {
  346. return nil
  347. }
  348. return outData[0..<8]
  349. }
  350. }
  351. // MARK: - Helpers
  352. fileprivate extension PeripheralManager {
  353. func authenticate(id: TransmitterID) throws -> AuthChallengeRxMessage {
  354. let authMessage = AuthRequestTxMessage()
  355. do {
  356. try writeMessage(authMessage, for: .authentication)
  357. } catch let error {
  358. throw TransmitterError.authenticationError("Error writing transmitter challenge: \(error)")
  359. }
  360. let authResponse: AuthRequestRxMessage
  361. do {
  362. authResponse = try readMessage(for: .authentication)
  363. } catch let error {
  364. throw TransmitterError.authenticationError("Unable to parse auth challenge: \(error)")
  365. }
  366. guard authResponse.tokenHash == id.computeHash(of: authMessage.singleUseToken) else {
  367. throw TransmitterError.authenticationError("Transmitter failed auth challenge")
  368. }
  369. guard let challengeHash = id.computeHash(of: authResponse.challenge) else {
  370. throw TransmitterError.authenticationError("Failed to compute challenge hash for transmitter ID")
  371. }
  372. do {
  373. try writeMessage(AuthChallengeTxMessage(challengeHash: challengeHash), for: .authentication)
  374. } catch let error {
  375. throw TransmitterError.authenticationError("Error writing challenge response: \(error)")
  376. }
  377. let challengeResponse: AuthChallengeRxMessage
  378. do {
  379. challengeResponse = try readMessage(for: .authentication)
  380. } catch let error {
  381. throw TransmitterError.authenticationError("Unable to parse auth status: \(error)")
  382. }
  383. guard challengeResponse.isAuthenticated else {
  384. throw TransmitterError.authenticationError("Transmitter rejected auth challenge")
  385. }
  386. return challengeResponse
  387. }
  388. func requestBond() throws {
  389. do {
  390. try writeMessage(KeepAliveTxMessage(time: 25), for: .authentication)
  391. } catch let error {
  392. throw TransmitterError.authenticationError("Error writing keep-alive for bond: \(error)")
  393. }
  394. do {
  395. try writeMessage(BondRequestTxMessage(), for: .authentication)
  396. } catch let error {
  397. throw TransmitterError.authenticationError("Error writing bond request: \(error)")
  398. }
  399. }
  400. func enableNotify(shouldWaitForBond: Bool = false) throws {
  401. do {
  402. if shouldWaitForBond {
  403. try setNotifyValue(true, for: .control, timeout: 15)
  404. } else {
  405. try setNotifyValue(true, for: .control)
  406. }
  407. } catch let error {
  408. throw TransmitterError.controlError("Error enabling notification: \(error)")
  409. }
  410. }
  411. func readTimeMessage() throws -> TransmitterTimeRxMessage {
  412. do {
  413. return try writeMessage(TransmitterTimeTxMessage(), for: .control)
  414. } catch let error {
  415. throw TransmitterError.controlError("Error getting time: \(error)")
  416. }
  417. }
  418. /// - Throws: TransmitterError.controlError
  419. func sendCommand(_ command: Command, activationDate: Date) throws -> TransmitterRxMessage {
  420. do {
  421. switch command {
  422. case .startSensor(let date):
  423. let startTime = UInt32(date.timeIntervalSince(activationDate))
  424. let secondsSince1970 = UInt32(date.timeIntervalSince1970)
  425. return try writeMessage(SessionStartTxMessage(startTime: startTime, secondsSince1970: secondsSince1970), for: .control)
  426. case .stopSensor(let date):
  427. let stopTime = UInt32(date.timeIntervalSince(activationDate))
  428. return try writeMessage(SessionStopTxMessage(stopTime: stopTime), for: .control)
  429. case .calibrateSensor(let glucose, let date):
  430. let glucoseValue = UInt16(glucose.doubleValue(for: .milligramsPerDeciliter).rounded())
  431. let time = UInt32(date.timeIntervalSince(activationDate))
  432. return try writeMessage(CalibrateGlucoseTxMessage(time: time, glucose: glucoseValue), for: .control)
  433. case .resetTransmitter:
  434. return try writeMessage(ResetTxMessage(), for: .control)
  435. }
  436. } catch let error {
  437. throw TransmitterError.controlError("Error during \(command): \(error)")
  438. }
  439. }
  440. func readGlucose() throws -> GlucoseRxMessage {
  441. do {
  442. return try writeMessage(GlucoseTxMessage(), for: .control)
  443. } catch let error {
  444. throw TransmitterError.controlError("Error getting glucose: \(error)")
  445. }
  446. }
  447. func readCalibrationData() throws -> CalibrationDataRxMessage {
  448. do {
  449. return try writeMessage(CalibrationDataTxMessage(), for: .control)
  450. } catch let error {
  451. throw TransmitterError.controlError("Error getting calibration data: \(error)")
  452. }
  453. }
  454. func disconnect() {
  455. do {
  456. try setNotifyValue(false, for: .control)
  457. try writeMessage(DisconnectTxMessage(), for: .control)
  458. } catch {
  459. }
  460. }
  461. func listenToCharacteristic(_ characteristic: CGMServiceCharacteristicUUID) throws {
  462. do {
  463. try setNotifyValue(true, for: characteristic)
  464. } catch let error {
  465. throw TransmitterError.controlError("Error enabling notification for \(characteristic): \(error)")
  466. }
  467. }
  468. func stopListeningToCharacteristic(_ characteristic: CGMServiceCharacteristicUUID) throws {
  469. do {
  470. try setNotifyValue(false, for: characteristic)
  471. } catch let error {
  472. throw TransmitterError.controlError("Error disabling notification for \(characteristic): \(error)")
  473. }
  474. }
  475. }