TrioRemoteControl.swift 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. import CoreData
  2. import Foundation
  3. import Swinject
  4. class TrioRemoteControl: Injectable {
  5. static let shared = TrioRemoteControl()
  6. @Injected() internal var tempTargetsStorage: TempTargetsStorage!
  7. @Injected() internal var carbsStorage: CarbsStorage!
  8. @Injected() internal var nightscoutManager: NightscoutManager!
  9. @Injected() internal var overrideStorage: OverrideStorage!
  10. @Injected() internal var settings: SettingsManager!
  11. @Injected() internal var bolusSafetyValidator: BolusSafetyValidator!
  12. private let timeWindow: TimeInterval = 600
  13. internal let viewContext: NSManagedObjectContext
  14. private init() {
  15. viewContext = CoreDataStack.shared.persistentContainer.viewContext
  16. injectServices(TrioApp.resolver)
  17. }
  18. func handleRemoteNotification(encryptedData: String) async throws {
  19. let isTrioRemoteControlEnabled = UserDefaults.standard.bool(forKey: "isTrioRemoteControlEnabled")
  20. guard isTrioRemoteControlEnabled else {
  21. await logError("Remote command received, but remote control is disabled in settings. Ignoring the command.")
  22. return
  23. }
  24. let storedSecret = UserDefaults.standard.string(forKey: "trioRemoteControlSharedSecret") ?? ""
  25. guard !storedSecret.isEmpty else {
  26. await logError("Command rejected: shared secret is missing in settings. Cannot authenticate the command.")
  27. return
  28. }
  29. guard let messenger = SecureMessenger(sharedSecret: storedSecret) else {
  30. await logError("Command rejected: Failed to initialize security module. The shared secret might be invalid.")
  31. return
  32. }
  33. let commandPayload: CommandPayload
  34. do {
  35. commandPayload = try messenger.decrypt(base64EncodedString: encryptedData)
  36. } catch {
  37. await logError(
  38. "Command rejected: Decryption failed. Mismatched shared secret or corrupted message. Error: \(error.localizedDescription)"
  39. )
  40. return
  41. }
  42. let currentTime = Date().timeIntervalSince1970
  43. let timeDifference = currentTime - commandPayload.timestamp
  44. if timeDifference > timeWindow {
  45. await logError(
  46. "Command rejected: the message is too old (sent \(Int(timeDifference)) seconds ago).",
  47. payload: commandPayload
  48. )
  49. return
  50. } else if timeDifference < -timeWindow {
  51. await logError(
  52. "Command rejected: the message has an invalid future timestamp.",
  53. payload: commandPayload
  54. )
  55. return
  56. }
  57. debug(
  58. .remoteControl,
  59. "Command successfully decrypted and authenticated. Time difference: \(Int(timeDifference)) seconds."
  60. )
  61. switch commandPayload.commandType {
  62. case .bolus:
  63. try await handleBolusCommand(commandPayload)
  64. case .tempTarget:
  65. try await handleTempTargetCommand(commandPayload)
  66. case .cancelTempTarget:
  67. await cancelTempTarget(commandPayload)
  68. case .meal:
  69. try await handleMealCommand(commandPayload)
  70. if commandPayload.bolusAmount != nil {
  71. try await handleBolusCommand(commandPayload)
  72. }
  73. case .startOverride:
  74. await handleStartOverrideCommand(commandPayload)
  75. case .cancelOverride:
  76. await handleCancelOverrideCommand(commandPayload)
  77. }
  78. }
  79. }