Sesame-Server/Sources/App/DeviceManager.swift

157 lines
5.1 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
2023-01-31 19:10:33 +01:00
private let deviceTimeout: Int64
2023-02-06 21:57:42 +01:00
2023-12-06 09:05:41 +01:00
private let deviceConnectedMetric: Metric<Bool>
2023-02-06 21:57:42 +01:00
private let messagesToDeviceMetric: Metric<Int>
2022-01-24 17:17:06 +01:00
2023-12-08 12:39:10 +01:00
let serverStatus: Metric<ServerStatus>
2023-12-06 09:05:41 +01:00
var deviceIsConnected: Bool {
2023-11-10 13:45:42 +01:00
guard let connection, !connection.isClosed else {
2023-12-06 09:05:41 +01:00
return false
2023-11-10 13:45:42 +01:00
}
2023-12-06 09:05:41 +01:00
return 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
2023-11-10 13:45:42 +01:00
private var requestInProgress: CheckedContinuation<Data, Error>?
2022-01-24 17:17:06 +01:00
2023-12-08 12:39:10 +01:00
init(deviceKey: Data, remoteKey: Data, deviceTimeout: Int64, serverStatus: Metric<ServerStatus>) {
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-12-06 09:05:41 +01:00
self.deviceConnectedMetric = .init(
"sesame.connected",
name: "Device connection",
description: "Shows if the device is connected via WebSocket")
2023-10-01 19:26:31 +02:00
self.messagesToDeviceMetric = .init(
2023-02-06 21:57:42 +01:00
"sesame.messages",
name: "Forwarded Messages",
description: "The number of messages transmitted to the device")
2023-12-08 12:39:10 +01:00
self.serverStatus = serverStatus
}
func updateMetricsAfterSystemStart() async {
_ = try? await serverStatus.update(deviceIsConnected ? .nominal : .reducedFunctionality)
await updateDeviceConnectionMetric()
2023-02-06 21:57:42 +01:00
}
2023-12-08 12:39:10 +01:00
private func updateDeviceConnectionMetric() async {
2023-12-06 09:05:41 +01:00
_ = try? await deviceConnectedMetric.update(deviceIsConnected)
2023-02-06 21:57:42 +01:00
}
2023-11-10 13:45:42 +01:00
private func updateMessageCountMetric() async {
let lastValue = await messagesToDeviceMetric.lastValue()?.value ?? 0
_ = try? await messagesToDeviceMetric.update(lastValue + 1)
2022-01-24 17:17:06 +01:00
}
// MARK: API
2023-12-08 12:39:10 +01:00
func sendMessageToDevice(_ message: Data, authToken: Data, on eventLoop: EventLoop) async throws -> Data {
guard message.count == SignedMessage.size else {
throw MessageResult.invalidMessageSizeFromDevice
}
guard SHA256.hash(data: authToken) == remoteKey else {
throw MessageResult.invalidServerAuthentication
}
2022-01-24 17:17:06 +01:00
guard let socket = connection, !socket.isClosed else {
connection = nil
2023-11-10 13:45:42 +01:00
throw MessageResult.deviceNotConnected
2022-01-24 17:17:06 +01:00
}
2022-04-07 23:53:25 +02:00
guard requestInProgress == nil else {
2023-12-08 12:39:10 +01:00
throw MessageResult.tooManyRequests
2022-04-07 23:53:25 +02:00
}
2023-11-10 13:45:42 +01:00
do {
try await socket.send(Array(message))
await updateMessageCountMetric()
} catch {
throw MessageResult.deviceNotConnected
}
startTimeoutForDeviceRequest(on: eventLoop)
let result: Data = try await withCheckedThrowingContinuation { continuation in
self.requestInProgress = continuation
}
return result
}
private func startTimeoutForDeviceRequest(on eventLoop: EventLoop) {
2023-01-31 19:10:33 +01:00
eventLoop.scheduleTask(in: .seconds(deviceTimeout)) { [weak self] in
2023-11-10 13:45:42 +01:00
self?.resumeDeviceRequest(with: .deviceTimedOut)
}
2022-01-24 17:17:06 +01:00
}
2023-11-10 13:45:42 +01:00
private func resumeDeviceRequest(with data: Data) {
requestInProgress?.resume(returning: data)
requestInProgress = nil
}
private func resumeDeviceRequest(with result: MessageResult) {
requestInProgress?.resume(throwing: result)
requestInProgress = nil
}
2022-01-24 17:17:06 +01:00
2022-05-01 13:12:16 +02:00
func authenticateRemote(_ token: Data) -> Bool {
let hash = SHA256.hash(data: token)
return hash == remoteKey
}
2023-08-08 16:32:25 +02:00
func processDeviceResponse(_ buffer: ByteBuffer) {
2023-12-08 12:39:10 +01:00
guard let data = buffer.getData(at: 0, length: buffer.readableBytes),
data.count == SignedMessage.size else {
2023-08-08 16:32:25 +02:00
log("Failed to get data buffer received from device")
2023-12-08 12:39:10 +01:00
self.resumeDeviceRequest(with: .invalidMessageSizeFromDevice)
2023-08-08 16:32:25 +02:00
return
}
2023-11-10 13:45:42 +01:00
self.resumeDeviceRequest(with: data)
2022-01-24 17:17:06 +01:00
}
func didCloseDeviceSocket() {
connection = nil
}
2023-11-10 13:45:42 +01:00
func removeDeviceConnection() async {
try? await connection?.close()
2022-01-24 17:17:06 +01:00
connection = nil
2023-11-10 13:45:42 +01:00
await updateDeviceConnectionMetric()
2022-01-24 17:17:06 +01:00
}
2023-12-06 09:05:41 +01:00
func createNewDeviceConnection(socket: WebSocket, auth: String) async {
guard let key = Data(fromHexEncodedString: auth),
SHA256.hash(data: key) == self.deviceKey else {
log("Invalid device key")
return
}
2023-11-10 13:45:42 +01:00
await removeDeviceConnection()
2023-12-06 09:05:41 +01:00
connection = socket
2023-11-10 15:01:37 +01:00
socket.eventLoop.execute {
2023-12-06 09:05:41 +01:00
socket.pingInterval = .seconds(10)
2023-11-10 15:01:37 +01:00
socket.onBinary { [weak self] _, data in
self?.processDeviceResponse(data)
}
2023-12-06 09:05:41 +01:00
socket.onClose.whenComplete { [weak self] _ in
2023-11-10 15:01:37 +01:00
self?.didCloseDeviceSocket()
}
}
2023-11-10 13:45:42 +01:00
await updateDeviceConnectionMetric()
2022-01-24 17:17:06 +01:00
}
}