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 {
|
2023-12-08 19:40:49 +01:00
|
|
|
|
2022-01-24 17:17:06 +01:00
|
|
|
/// The connection to the device
|
|
|
|
private var connection: WebSocket?
|
2023-12-08 19:40:49 +01:00
|
|
|
|
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
|
2023-12-08 19:40:49 +01:00
|
|
|
|
2022-05-01 13:12:16 +02:00
|
|
|
/// The authentication token of the remote
|
|
|
|
private let remoteKey: Data
|
2023-12-08 19:40:49 +01:00
|
|
|
|
2023-01-31 19:10:33 +01:00
|
|
|
private let deviceTimeout: Int64
|
2023-12-08 19:40:49 +01:00
|
|
|
|
2023-12-06 09:05:41 +01:00
|
|
|
private let deviceConnectedMetric: Metric<Bool>
|
2023-12-08 19:40:49 +01:00
|
|
|
|
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>?
|
2023-12-08 19:40:49 +01:00
|
|
|
private var receivedMessageData: Data?
|
2022-01-24 17:17:06 +01:00
|
|
|
|
2023-12-08 20:26:17 +01:00
|
|
|
var logger: Logger?
|
|
|
|
|
|
|
|
private func printAndFlush(_ message: String) {
|
|
|
|
logger?.notice(.init(stringLiteral: message))
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
}
|
2023-12-10 19:32:09 +01:00
|
|
|
|
|
|
|
func updateDeviceConnectionMetrics() async {
|
|
|
|
let isConnected = deviceIsConnected
|
|
|
|
_ = try? await serverStatus.update(isConnected ? .nominal : .reducedFunctionality)
|
|
|
|
_ = try? await deviceConnectedMetric.update(isConnected)
|
2023-02-06 21:57:42 +01:00
|
|
|
}
|
2023-12-08 19:40:49 +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 {
|
2023-12-08 15:43:29 +01:00
|
|
|
throw MessageResult.invalidMessageSizeFromRemote
|
2023-12-08 12:39:10 +01:00
|
|
|
}
|
|
|
|
guard SHA256.hash(data: authToken) == remoteKey else {
|
2023-12-08 15:43:29 +01:00
|
|
|
throw MessageResult.invalidServerAuthenticationFromRemote
|
2023-12-08 12:39:10 +01:00
|
|
|
}
|
2022-01-24 17:17:06 +01:00
|
|
|
guard let socket = connection, !socket.isClosed else {
|
2024-03-11 00:41:26 +01:00
|
|
|
// Ensure that metric is updated
|
|
|
|
didCloseDeviceSocket()
|
2023-11-10 13:45:42 +01:00
|
|
|
throw MessageResult.deviceNotConnected
|
2022-01-24 17:17:06 +01:00
|
|
|
}
|
2023-12-08 19:40:49 +01:00
|
|
|
guard receivedMessageData == nil else {
|
2023-12-08 12:39:10 +01:00
|
|
|
throw MessageResult.tooManyRequests
|
2022-04-07 23:53:25 +02:00
|
|
|
}
|
2023-12-08 19:40:49 +01:00
|
|
|
// Indicate that a message is in transit
|
|
|
|
receivedMessageData = Data()
|
2023-11-10 13:45:42 +01:00
|
|
|
do {
|
|
|
|
try await socket.send(Array(message))
|
|
|
|
} catch {
|
|
|
|
throw MessageResult.deviceNotConnected
|
|
|
|
}
|
|
|
|
startTimeoutForDeviceRequest(on: eventLoop)
|
|
|
|
|
2023-12-08 19:40:49 +01:00
|
|
|
// Check if a full message has already been received
|
|
|
|
if let receivedMessageData, receivedMessageData.count == SignedMessage.size {
|
|
|
|
self.receivedMessageData = nil
|
|
|
|
return receivedMessageData
|
|
|
|
}
|
|
|
|
// Wait until a fill message is received, or a timeout occurs
|
2023-11-10 13:45:42 +01:00
|
|
|
let result: Data = try await withCheckedThrowingContinuation { continuation in
|
|
|
|
self.requestInProgress = continuation
|
|
|
|
}
|
2023-12-08 16:55:47 +01:00
|
|
|
await updateMessageCountMetric()
|
2023-11-10 13:45:42 +01:00
|
|
|
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-12-08 20:26:17 +01:00
|
|
|
guard let self else {
|
2023-12-12 23:08:50 +01:00
|
|
|
log("[WARN] No reference to self after timeout of message")
|
2023-12-08 20:28:24 +01:00
|
|
|
return
|
2023-12-08 20:26:17 +01:00
|
|
|
}
|
|
|
|
self.resumeDeviceRequest(with: .deviceTimedOut)
|
2022-01-29 10:26:30 +01:00
|
|
|
}
|
2022-01-24 17:17:06 +01:00
|
|
|
}
|
|
|
|
|
2023-11-10 13:45:42 +01:00
|
|
|
private func resumeDeviceRequest(with data: Data) {
|
2023-12-08 19:40:49 +01:00
|
|
|
guard let receivedMessageData else {
|
2023-12-10 19:34:30 +01:00
|
|
|
log("[WARN] Received \(data.count) bytes after message completion")
|
2023-12-08 19:40:49 +01:00
|
|
|
self.requestInProgress = nil
|
|
|
|
return
|
|
|
|
}
|
|
|
|
let newData = receivedMessageData + data
|
|
|
|
if newData.count < SignedMessage.size {
|
|
|
|
// Wait for more data
|
|
|
|
self.receivedMessageData = newData
|
|
|
|
return
|
|
|
|
}
|
|
|
|
self.receivedMessageData = nil
|
|
|
|
guard let requestInProgress else {
|
2023-12-10 19:34:30 +01:00
|
|
|
log("[WARN] Received \(newData.count) bytes, but no continuation to resume")
|
2023-12-08 19:40:49 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
self.requestInProgress = nil
|
|
|
|
guard newData.count == SignedMessage.size else {
|
2023-12-10 19:34:30 +01:00
|
|
|
log("[WARN] Received \(newData.count) bytes, expected \(SignedMessage.size) for a message.")
|
2023-12-08 19:40:49 +01:00
|
|
|
requestInProgress.resume(throwing: MessageResult.invalidMessageSizeFromDevice)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
requestInProgress.resume(returning: newData)
|
2023-11-10 13:45:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private func resumeDeviceRequest(with result: MessageResult) {
|
2023-12-08 19:40:49 +01:00
|
|
|
guard let receivedMessageData else {
|
|
|
|
self.requestInProgress = nil
|
|
|
|
return
|
|
|
|
}
|
|
|
|
self.receivedMessageData = nil
|
|
|
|
guard let requestInProgress else {
|
2023-12-10 19:34:30 +01:00
|
|
|
log("[WARN] Request in progress (\(receivedMessageData.count) bytes), but no continuation found for result: \(result)")
|
2023-12-08 19:40:49 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
self.requestInProgress = nil
|
|
|
|
requestInProgress.resume(throwing: result)
|
2023-11-10 13:45:42 +01:00
|
|
|
}
|
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 15:53:01 +01:00
|
|
|
guard let data = buffer.getData(at: 0, length: buffer.readableBytes) else {
|
2023-12-12 23:08:50 +01:00
|
|
|
log("[WARN] Failed to get data buffer received from device")
|
2023-12-08 15:53:01 +01:00
|
|
|
self.resumeDeviceRequest(with: .invalidMessageSizeFromDevice)
|
|
|
|
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
|
2024-03-11 00:41:26 +01:00
|
|
|
Task {
|
|
|
|
await updateDeviceConnectionMetrics()
|
|
|
|
}
|
2022-01-24 17:17:06 +01:00
|
|
|
}
|
|
|
|
|
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-12-10 19:32:09 +01:00
|
|
|
await updateDeviceConnectionMetrics()
|
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 {
|
2023-12-08 19:40:49 +01:00
|
|
|
log("[WARN] Invalid device key while opening socket")
|
2023-12-08 16:28:48 +01:00
|
|
|
try? await socket.close()
|
2023-12-06 09:05:41 +01:00
|
|
|
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-12-08 19:40:49 +01:00
|
|
|
socket.onText { [weak self] socket, text in
|
2023-12-08 20:28:24 +01:00
|
|
|
self?.printAndFlush("[WARN] Received text over socket: \(text)")
|
2023-12-08 16:28:48 +01:00
|
|
|
// Close connection to prevent spamming the log
|
|
|
|
try? await socket.close()
|
2023-12-08 19:40:49 +01:00
|
|
|
|
|
|
|
guard let self else {
|
2023-12-10 19:34:30 +01:00
|
|
|
log("[WARN] No reference to self to handle text over socket")
|
2023-12-08 19:40:49 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
self.didCloseDeviceSocket()
|
2023-12-08 16:28:48 +01:00
|
|
|
}
|
|
|
|
|
2023-11-10 15:01:37 +01:00
|
|
|
socket.onBinary { [weak self] _, data in
|
2023-12-08 19:40:49 +01:00
|
|
|
guard let self else {
|
2023-12-10 19:34:30 +01:00
|
|
|
log("[WARN] No reference to self to process binary data on socket")
|
2023-12-08 19:40:49 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
self.processDeviceResponse(data)
|
2023-11-10 15:01:37 +01:00
|
|
|
}
|
2023-12-08 19:40:49 +01:00
|
|
|
|
2023-12-06 09:05:41 +01:00
|
|
|
socket.onClose.whenComplete { [weak self] _ in
|
2023-12-08 19:40:49 +01:00
|
|
|
guard let self else {
|
2023-12-10 19:34:30 +01:00
|
|
|
log("[WARN] No reference to self to handle socket closing")
|
2023-12-08 19:40:49 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
self.didCloseDeviceSocket()
|
2023-11-10 15:01:37 +01:00
|
|
|
}
|
2023-09-07 14:05:41 +02:00
|
|
|
}
|
2023-12-10 19:32:09 +01:00
|
|
|
await updateDeviceConnectionMetrics()
|
2022-01-24 17:17:06 +01:00
|
|
|
}
|
|
|
|
}
|