Sesame-Server/Sources/App/DeviceManager.swift

151 lines
4.7 KiB
Swift
Raw Normal View History

2022-01-24 17:17:06 +01:00
import Foundation
import WebSocketKit
import Vapor
2023-02-06 21:57:42 +01:00
import Clairvoyant
2022-01-24 17:17:06 +01:00
2022-04-07 23:53:25 +02:00
final class DeviceManager {
2022-01-24 17:17:06 +01:00
/// The connection to the device
private var connection: WebSocket?
2022-04-07 23:53:25 +02:00
/// The authentication token of the device for the socket connection
2022-05-01 13:28:06 +02:00
private let deviceKey: Data
2022-04-07 23:53:25 +02:00
2022-05-01 13:12:16 +02:00
/// The authentication token of the remote
private let remoteKey: Data
2022-04-07 23:53:25 +02:00
/// Indicate that the socket is fully initialized with an authorized device
2022-01-24 17:17:06 +01:00
var deviceIsAuthenticated = false
2022-04-08 23:38:22 +02:00
private var isOpeningNewConnection = false
2023-01-31 19:10:33 +01:00
private let deviceTimeout: Int64
2023-02-06 21:57:42 +01:00
private let deviceConnectedMetric: Metric<Bool>
private let messagesToDeviceMetric: Metric<Int>
2022-01-24 17:17:06 +01:00
/// Indicator for device availability
var deviceIsConnected: Bool {
2022-04-08 23:38:22 +02:00
deviceIsAuthenticated && !(connection?.isClosed ?? true)
2022-01-24 17:17:06 +01:00
}
2022-04-07 23:53:25 +02:00
/// A promise to finish the request once the device responds or times out
private var requestInProgress: EventLoopPromise<DeviceResponse>?
2022-01-24 17:17:06 +01:00
2023-02-17 00:09:51 +01:00
init(deviceKey: Data, remoteKey: Data, deviceTimeout: Int64) async {
2022-01-24 17:17:06 +01:00
self.deviceKey = deviceKey
2022-05-01 13:12:16 +02:00
self.remoteKey = remoteKey
2023-01-31 19:10:33 +01:00
self.deviceTimeout = deviceTimeout
2023-02-17 00:09:51 +01:00
self.deviceConnectedMetric = try! await .init(
2023-02-06 21:57:42 +01:00
"sesame.connected",
name: "Device connected",
description: "Shows if the device is connected via WebSocket")
2023-02-17 00:09:51 +01:00
self.messagesToDeviceMetric = try! await .init(
2023-02-06 21:57:42 +01:00
"sesame.messages",
name: "Forwarded Messages",
description: "The number of messages transmitted to the device")
}
private func updateDeviceConnectionMetric() {
2023-02-17 00:09:51 +01:00
Task {
try? await deviceConnectedMetric.update(deviceIsConnected)
}
2023-02-06 21:57:42 +01:00
}
private func updateMessageCountMetric() {
2023-02-17 00:09:51 +01:00
Task {
let lastValue = await messagesToDeviceMetric.lastValue()?.value ?? 0
try? await messagesToDeviceMetric.update(lastValue + 1)
}
2022-01-24 17:17:06 +01:00
}
// MARK: API
var deviceStatus: String {
deviceIsConnected ? "1" : "0"
}
2022-04-07 23:53:25 +02:00
func sendMessageToDevice(_ message: Message, on eventLoop: EventLoop) -> EventLoopFuture<DeviceResponse> {
2022-01-24 17:17:06 +01:00
guard let socket = connection, !socket.isClosed else {
connection = nil
return eventLoop.makeSucceededFuture(.deviceNotConnected)
2022-01-24 17:17:06 +01:00
}
2022-04-07 23:53:25 +02:00
guard requestInProgress == nil else {
return eventLoop.makeSucceededFuture(.operationInProgress)
}
2023-02-17 00:09:51 +01:00
let result = eventLoop.makePromise(of: DeviceResponse.self)
self.requestInProgress = result
2022-04-07 23:53:25 +02:00
socket.send(message.bytes, promise: nil)
2023-02-06 21:57:42 +01:00
updateMessageCountMetric()
2023-01-31 19:10:33 +01:00
eventLoop.scheduleTask(in: .seconds(deviceTimeout)) { [weak self] in
2022-04-07 23:53:25 +02:00
guard let promise = self?.requestInProgress else {
return
}
2022-04-07 23:53:25 +02:00
self?.requestInProgress = nil
promise.succeed(.deviceTimedOut)
}
2023-02-17 00:09:51 +01:00
return result.futureResult
2022-01-24 17:17:06 +01:00
}
2022-05-01 13:28:06 +02:00
func authenticateDevice(hash: String) {
2023-02-06 21:57:42 +01:00
defer { updateDeviceConnectionMetric() }
2022-05-01 13:28:06 +02:00
guard let key = Data(fromHexEncodedString: hash),
SHA256.hash(data: key) == self.deviceKey else {
2023-02-06 21:44:56 +01:00
log("Invalid device key")
2022-01-24 17:17:06 +01:00
_ = connection?.close()
deviceIsAuthenticated = false
return
}
2023-02-06 21:44:56 +01:00
log("Device authenticated")
2022-01-24 17:17:06 +01:00
deviceIsAuthenticated = true
}
2022-05-01 13:12:16 +02:00
func authenticateRemote(_ token: Data) -> Bool {
let hash = SHA256.hash(data: token)
return hash == remoteKey
}
2022-01-24 17:17:06 +01:00
func processDeviceResponse(_ data: ByteBuffer) {
2022-04-07 23:53:25 +02:00
guard let promise = requestInProgress else {
2022-01-24 17:17:06 +01:00
return
}
2022-04-07 23:53:25 +02:00
defer { requestInProgress = nil }
2023-02-06 21:44:56 +01:00
promise.succeed(DeviceResponse(data, request: RouteAPI.socket.rawValue) ?? .unexpectedSocketEvent)
2022-01-24 17:17:06 +01:00
}
func didCloseDeviceSocket() {
2023-02-06 21:57:42 +01:00
defer { updateDeviceConnectionMetric() }
2022-04-08 23:38:22 +02:00
guard !isOpeningNewConnection else {
return
}
2022-01-24 17:17:06 +01:00
deviceIsAuthenticated = false
guard connection != nil else {
2023-02-06 21:44:56 +01:00
log("Socket closed, but no connection anyway")
2022-01-24 17:17:06 +01:00
return
}
connection = nil
2023-02-06 21:44:56 +01:00
log("Socket closed")
2022-01-24 17:17:06 +01:00
}
func removeDeviceConnection() {
2023-02-06 21:57:42 +01:00
defer { updateDeviceConnectionMetric() }
2022-04-08 23:38:22 +02:00
deviceIsAuthenticated = false
guard let socket = connection else {
return
}
try? socket.close().wait()
2022-01-24 17:17:06 +01:00
connection = nil
2023-02-06 21:44:56 +01:00
log("Removed device connection")
2022-01-24 17:17:06 +01:00
}
func createNewDeviceConnection(_ socket: WebSocket) {
2023-02-06 21:57:42 +01:00
defer { updateDeviceConnectionMetric() }
2022-04-08 23:38:22 +02:00
isOpeningNewConnection = true
2022-01-24 17:17:06 +01:00
removeDeviceConnection()
connection = socket
2023-02-06 21:44:56 +01:00
log("Socket connected")
2022-04-08 23:38:22 +02:00
isOpeningNewConnection = false
2022-01-24 17:17:06 +01:00
}
}