Treat messages as data
This commit is contained in:
parent
5d4adf8b15
commit
107b609aea
@ -1,97 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import NIOCore
|
|
||||||
|
|
||||||
/**
|
|
||||||
Encapsulates a response from a device.
|
|
||||||
*/
|
|
||||||
struct DeviceResponse {
|
|
||||||
|
|
||||||
/// Shorthand property for a timeout event.
|
|
||||||
static var deviceTimedOut: DeviceResponse {
|
|
||||||
.init(event: .deviceTimedOut)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Shorthand property for a disconnected event.
|
|
||||||
static var deviceNotConnected: DeviceResponse {
|
|
||||||
.init(event: .deviceNotConnected)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Shorthand property for a connected event.
|
|
||||||
static var deviceConnected: DeviceResponse {
|
|
||||||
.init(event: .deviceConnected)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Shorthand property for an unexpected socket event.
|
|
||||||
static var unexpectedSocketEvent: DeviceResponse {
|
|
||||||
.init(event: .unexpectedSocketEvent)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Shorthand property for an invalid message.
|
|
||||||
static var invalidMessageData: DeviceResponse {
|
|
||||||
.init(event: .invalidMessageData)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Shorthand property for missing body data.
|
|
||||||
static var noBodyData: DeviceResponse {
|
|
||||||
.init(event: .noBodyData)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Shorthand property for a busy connection
|
|
||||||
static var operationInProgress: DeviceResponse {
|
|
||||||
.init(event: .operationInProgress)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The response to a key from the server
|
|
||||||
let event: MessageResult
|
|
||||||
|
|
||||||
/// The index of the next key to use
|
|
||||||
let response: Message?
|
|
||||||
|
|
||||||
/**
|
|
||||||
Decode a message from a buffer.
|
|
||||||
|
|
||||||
The buffer must contain `Message.length+1` bytes. The first byte denotes the event type,
|
|
||||||
the remaining bytes contain the message.
|
|
||||||
- Parameter buffer: The buffer where the message bytes are stored
|
|
||||||
*/
|
|
||||||
init?(_ data: Data, request: String) {
|
|
||||||
guard let byte = data.first else {
|
|
||||||
log("\(request): No bytes received from device")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
guard let event = MessageResult(rawValue: byte) else {
|
|
||||||
log("\(request): Unknown response \(byte) received from device")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
self.event = event
|
|
||||||
let messageData = data.dropFirst()
|
|
||||||
guard !messageData.isEmpty else {
|
|
||||||
// TODO: Check if event should have response message
|
|
||||||
self.response = nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard messageData.count == Message.length else {
|
|
||||||
log("\(request): Insufficient message data received from device (expected \(Message.length), got \(messageData.count))")
|
|
||||||
self.response = nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.response = Message(decodeFrom: data)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Create a response from an event without a message from the device.
|
|
||||||
- Parameter event: The response from the device.
|
|
||||||
*/
|
|
||||||
init(event: MessageResult) {
|
|
||||||
self.event = event
|
|
||||||
self.response = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the reponse encoded in bytes.
|
|
||||||
var encoded: Data {
|
|
||||||
guard let message = response else {
|
|
||||||
return Data([event.rawValue])
|
|
||||||
}
|
|
||||||
return Data([event.rawValue]) + message.encoded
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,179 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import NIOCore
|
|
||||||
|
|
||||||
#if canImport(CryptoKit)
|
|
||||||
import CryptoKit
|
|
||||||
#else
|
|
||||||
import Crypto
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/**
|
|
||||||
An authenticated message to or from the device.
|
|
||||||
*/
|
|
||||||
struct Message: Equatable, Hashable {
|
|
||||||
|
|
||||||
/// The message authentication code for the message (32 bytes)
|
|
||||||
let mac: Data
|
|
||||||
|
|
||||||
/// The message content
|
|
||||||
let content: Content
|
|
||||||
|
|
||||||
/**
|
|
||||||
Create an authenticated message
|
|
||||||
- Parameter mac: The message authentication code
|
|
||||||
- Parameter content: The message content
|
|
||||||
*/
|
|
||||||
init(mac: Data, content: Content) {
|
|
||||||
self.mac = mac
|
|
||||||
self.content = content
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Message: Codable {
|
|
||||||
|
|
||||||
enum CodingKeys: Int, CodingKey {
|
|
||||||
case mac = 1
|
|
||||||
case content = 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Message {
|
|
||||||
|
|
||||||
/**
|
|
||||||
The message content without authentication.
|
|
||||||
*/
|
|
||||||
struct Content: Equatable, Hashable {
|
|
||||||
|
|
||||||
/// The time of message creation, in UNIX time (seconds since 1970)
|
|
||||||
let time: UInt32
|
|
||||||
|
|
||||||
/// The counter of the message (for freshness)
|
|
||||||
let id: UInt32
|
|
||||||
|
|
||||||
let deviceId: UInt8?
|
|
||||||
|
|
||||||
/**
|
|
||||||
Create new message content.
|
|
||||||
- Parameter time: The time of message creation,
|
|
||||||
- Parameter id: The counter of the message
|
|
||||||
*/
|
|
||||||
init(time: UInt32, id: UInt32, device: UInt8) {
|
|
||||||
self.time = time
|
|
||||||
self.id = id
|
|
||||||
self.deviceId = device
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Decode message content from data.
|
|
||||||
|
|
||||||
The data consists of two `UInt32` encoded in little endian format
|
|
||||||
- Warning: The sequence must contain at least 8 bytes, or the function will crash.
|
|
||||||
- Parameter data: The sequence containing the bytes.
|
|
||||||
*/
|
|
||||||
init<T: Sequence>(decodeFrom data: T) where T.Element == UInt8 {
|
|
||||||
self.time = UInt32(data: Data(data.prefix(MemoryLayout<UInt32>.size)))
|
|
||||||
self.id = UInt32(data: Data(data.dropLast().suffix(MemoryLayout<UInt32>.size)))
|
|
||||||
self.deviceId = data.suffix(1).last!
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The byte length of an encoded message content
|
|
||||||
static var length: Int {
|
|
||||||
MemoryLayout<UInt32>.size * 2 + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The message content encoded to data
|
|
||||||
var encoded: Data {
|
|
||||||
time.encoded + id.encoded + Data([deviceId ?? 0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Message.Content: Codable {
|
|
||||||
|
|
||||||
enum CodingKeys: Int, CodingKey {
|
|
||||||
case time = 1
|
|
||||||
case id = 2
|
|
||||||
case deviceId = 3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Message {
|
|
||||||
|
|
||||||
/// The length of a message in bytes
|
|
||||||
static var length: Int {
|
|
||||||
SHA256.byteCount + Content.length
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Decode a message from a byte buffer.
|
|
||||||
The buffer must contain at least `Message.length` bytes, or it will return `nil`.
|
|
||||||
- Parameter buffer: The buffer containing the bytes.
|
|
||||||
*/
|
|
||||||
init?(decodeFrom buffer: ByteBuffer) {
|
|
||||||
guard let data = buffer.getBytes(at: 0, length: Message.length) else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
self.init(decodeFrom: data)
|
|
||||||
}
|
|
||||||
|
|
||||||
init?(decodeFrom data: Data, index: inout Int) {
|
|
||||||
guard index + Message.length <= data.count else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
self.init(decodeFrom: data.advanced(by: index))
|
|
||||||
index += Message.length
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The message encoded to data
|
|
||||||
var encoded: Data {
|
|
||||||
mac + content.encoded
|
|
||||||
}
|
|
||||||
|
|
||||||
var bytes: [UInt8] {
|
|
||||||
Array(encoded)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Create a message from received bytes.
|
|
||||||
- Parameter data: The sequence of bytes
|
|
||||||
- Note: The sequence must contain at least `Message.length` bytes, or the function will crash.
|
|
||||||
*/
|
|
||||||
init<T: Sequence>(decodeFrom data: T) where T.Element == UInt8 {
|
|
||||||
let count = SHA256.byteCount
|
|
||||||
self.mac = Data(data.prefix(count))
|
|
||||||
self.content = .init(decodeFrom: Array(data.dropFirst(count)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Check if the message contains a valid authentication code
|
|
||||||
- Parameter key: The key used to sign the message.
|
|
||||||
- Returns: `true`, if the message is valid.
|
|
||||||
*/
|
|
||||||
func isValid(using key: SymmetricKey) -> Bool {
|
|
||||||
HMAC<SHA256>.isValidAuthenticationCode(mac, authenticating: content.encoded, using: key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Message.Content {
|
|
||||||
|
|
||||||
/**
|
|
||||||
Calculate an authentication code for the message content.
|
|
||||||
- Parameter key: The key to use to sign the content.
|
|
||||||
- Returns: The new message signed with the key.
|
|
||||||
*/
|
|
||||||
func authenticate(using key: SymmetricKey) -> Message {
|
|
||||||
let mac = HMAC<SHA256>.authenticationCode(for: encoded, using: key)
|
|
||||||
return .init(mac: Data(mac.map { $0 }), content: self)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
Calculate an authentication code for the message content and convert everything to data.
|
|
||||||
- Parameter key: The key to use to sign the content.
|
|
||||||
- Returns: The new message signed with the key, serialized to bytes.
|
|
||||||
*/
|
|
||||||
func authenticateAndSerialize(using key: SymmetricKey) -> Data {
|
|
||||||
let encoded = self.encoded
|
|
||||||
let mac = HMAC<SHA256>.authenticationCode(for: encoded, using: key)
|
|
||||||
return Data(mac.map { $0 }) + encoded
|
|
||||||
}
|
|
||||||
}
|
|
@ -11,8 +11,8 @@ enum MessageResult: UInt8 {
|
|||||||
/// A socket event on the device was unexpected (not binary data)
|
/// A socket event on the device was unexpected (not binary data)
|
||||||
case unexpectedSocketEvent = 2
|
case unexpectedSocketEvent = 2
|
||||||
|
|
||||||
/// The size of the payload (i.e. message) was invalid, or the data could not be read
|
/// The size of the payload (i.e. message) was invalid
|
||||||
case invalidMessageData = 3
|
case invalidMessageSize = 3
|
||||||
|
|
||||||
/// The transmitted message could not be authenticated using the key
|
/// The transmitted message could not be authenticated using the key
|
||||||
case messageAuthenticationFailed = 4
|
case messageAuthenticationFailed = 4
|
||||||
@ -44,6 +44,10 @@ enum MessageResult: UInt8 {
|
|||||||
|
|
||||||
/// The device is connected
|
/// The device is connected
|
||||||
case deviceConnected = 15
|
case deviceConnected = 15
|
||||||
|
|
||||||
|
case invalidUrlParameter = 20
|
||||||
|
|
||||||
|
case invalidResponseAuthentication = 21
|
||||||
}
|
}
|
||||||
|
|
||||||
extension MessageResult: CustomStringConvertible {
|
extension MessageResult: CustomStringConvertible {
|
||||||
@ -54,7 +58,7 @@ extension MessageResult: CustomStringConvertible {
|
|||||||
return "The device received unexpected text"
|
return "The device received unexpected text"
|
||||||
case .unexpectedSocketEvent:
|
case .unexpectedSocketEvent:
|
||||||
return "Unexpected socket event for the device"
|
return "Unexpected socket event for the device"
|
||||||
case .invalidMessageData:
|
case .invalidMessageSize:
|
||||||
return "Invalid message data"
|
return "Invalid message data"
|
||||||
case .messageAuthenticationFailed:
|
case .messageAuthenticationFailed:
|
||||||
return "Message authentication failed"
|
return "Message authentication failed"
|
||||||
@ -76,6 +80,17 @@ extension MessageResult: CustomStringConvertible {
|
|||||||
return "Another operation is in progress"
|
return "Another operation is in progress"
|
||||||
case .deviceConnected:
|
case .deviceConnected:
|
||||||
return "The device is connected"
|
return "The device is connected"
|
||||||
|
case .invalidUrlParameter:
|
||||||
|
return "The url parameter could not be found"
|
||||||
|
case .invalidResponseAuthentication:
|
||||||
|
return "The response could not be authenticated"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension MessageResult {
|
||||||
|
|
||||||
|
var encoded: Data {
|
||||||
|
Data([rawValue])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -11,11 +11,11 @@ struct ServerMessage {
|
|||||||
|
|
||||||
static let authTokenSize = SHA256.byteCount
|
static let authTokenSize = SHA256.byteCount
|
||||||
|
|
||||||
static let length = authTokenSize + Message.length
|
static let maxLength = authTokenSize + 200
|
||||||
|
|
||||||
let authToken: Data
|
let authToken: Data
|
||||||
|
|
||||||
let message: Message
|
let message: Data
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Decode a message from a byte buffer.
|
Decode a message from a byte buffer.
|
||||||
@ -23,15 +23,16 @@ struct ServerMessage {
|
|||||||
- Parameter buffer: The buffer containing the bytes.
|
- Parameter buffer: The buffer containing the bytes.
|
||||||
*/
|
*/
|
||||||
init?(decodeFrom buffer: ByteBuffer) {
|
init?(decodeFrom buffer: ByteBuffer) {
|
||||||
guard let data = buffer.getBytes(at: 0, length: ServerMessage.length) else {
|
guard buffer.readableBytes < ServerMessage.maxLength else {
|
||||||
|
log("Received invalid message with \(buffer.readableBytes) bytes")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
guard let data = buffer.getBytes(at: 0, length: buffer.readableBytes) else {
|
||||||
|
log("Failed to read bytes of received message")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
self.authToken = Data(data.prefix(ServerMessage.authTokenSize))
|
self.authToken = Data(data.prefix(ServerMessage.authTokenSize))
|
||||||
self.message = Message(decodeFrom: Data(data.dropFirst(ServerMessage.authTokenSize)))
|
self.message = Data(data.dropFirst(ServerMessage.authTokenSize))
|
||||||
}
|
|
||||||
|
|
||||||
var encoded: Data {
|
|
||||||
authToken + message.encoded
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static func token(from buffer: ByteBuffer) -> Data? {
|
static func token(from buffer: ByteBuffer) -> Data? {
|
||||||
|
@ -31,7 +31,7 @@ final class DeviceManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A promise to finish the request once the device responds or times out
|
/// A promise to finish the request once the device responds or times out
|
||||||
private var requestInProgress: EventLoopPromise<DeviceResponse>?
|
private var requestInProgress: EventLoopPromise<Data>?
|
||||||
|
|
||||||
init(deviceKey: Data, remoteKey: Data, deviceTimeout: Int64) async {
|
init(deviceKey: Data, remoteKey: Data, deviceTimeout: Int64) async {
|
||||||
self.deviceKey = deviceKey
|
self.deviceKey = deviceKey
|
||||||
@ -66,24 +66,25 @@ final class DeviceManager {
|
|||||||
deviceIsConnected ? "1" : "0"
|
deviceIsConnected ? "1" : "0"
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendMessageToDevice(_ message: Message, on eventLoop: EventLoop) -> EventLoopFuture<DeviceResponse> {
|
func sendMessageToDevice(_ message: Data, on eventLoop: EventLoop) -> EventLoopFuture<Data> {
|
||||||
guard let socket = connection, !socket.isClosed else {
|
guard let socket = connection, !socket.isClosed else {
|
||||||
connection = nil
|
connection = nil
|
||||||
return eventLoop.makeSucceededFuture(.deviceNotConnected)
|
return eventLoop.makeSucceededFuture(MessageResult.deviceNotConnected.encoded)
|
||||||
}
|
}
|
||||||
guard requestInProgress == nil else {
|
guard requestInProgress == nil else {
|
||||||
return eventLoop.makeSucceededFuture(.operationInProgress)
|
return eventLoop.makeSucceededFuture(MessageResult.operationInProgress.encoded)
|
||||||
}
|
}
|
||||||
let result = eventLoop.makePromise(of: DeviceResponse.self)
|
let result = eventLoop.makePromise(of: Data.self)
|
||||||
self.requestInProgress = result
|
self.requestInProgress = result
|
||||||
socket.send(message.bytes, promise: nil)
|
socket.send(Array(message), promise: nil)
|
||||||
updateMessageCountMetric()
|
updateMessageCountMetric()
|
||||||
eventLoop.scheduleTask(in: .seconds(deviceTimeout)) { [weak self] in
|
eventLoop.scheduleTask(in: .seconds(deviceTimeout)) { [weak self] in
|
||||||
guard let promise = self?.requestInProgress else {
|
guard let promise = self?.requestInProgress else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self?.requestInProgress = nil
|
self?.requestInProgress = nil
|
||||||
promise.succeed(.deviceTimedOut)
|
log("Timed out waiting for device response")
|
||||||
|
promise.succeed(MessageResult.deviceTimedOut.encoded)
|
||||||
}
|
}
|
||||||
return result.futureResult
|
return result.futureResult
|
||||||
}
|
}
|
||||||
@ -116,9 +117,8 @@ final class DeviceManager {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer { requestInProgress = nil }
|
defer { requestInProgress = nil }
|
||||||
let response = DeviceResponse(data, request: RouteAPI.socket.rawValue) ?? .unexpectedSocketEvent
|
|
||||||
log("Device response received")
|
log("Device response received")
|
||||||
promise.succeed(response)
|
promise.succeed(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func didCloseDeviceSocket() {
|
func didCloseDeviceSocket() {
|
||||||
|
@ -8,6 +8,13 @@ enum ServerError: Error {
|
|||||||
case invalidAuthenticationToken
|
case invalidAuthenticationToken
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private let dateFormatter: DateFormatter = {
|
||||||
|
let df = DateFormatter()
|
||||||
|
df.dateStyle = .short
|
||||||
|
df.timeStyle = .short
|
||||||
|
return df
|
||||||
|
}()
|
||||||
|
|
||||||
// configures your application
|
// configures your application
|
||||||
public func configure(_ app: Application) async throws {
|
public func configure(_ app: Application) async throws {
|
||||||
let storageFolder = URL(fileURLWithPath: app.directory.resourcesDirectory)
|
let storageFolder = URL(fileURLWithPath: app.directory.resourcesDirectory)
|
||||||
@ -50,6 +57,7 @@ public func configure(_ app: Application) async throws {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try await status.update(.nominal)
|
try await status.update(.nominal)
|
||||||
|
print("[\(dateFormatter.string(from: Date()))] Server started")
|
||||||
}
|
}
|
||||||
|
|
||||||
private func loadKeys(at url: URL) throws -> (deviceKey: Data, remoteKey: Data) {
|
private func loadKeys(at url: URL) throws -> (deviceKey: Data, remoteKey: Data) {
|
||||||
|
@ -11,28 +11,28 @@ extension RouteAPI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func messageTransmission(_ req: Request) -> EventLoopFuture<DeviceResponse> {
|
private func messageTransmission(_ req: Request) -> EventLoopFuture<Data> {
|
||||||
guard let body = req.body.data else {
|
guard let body = req.body.data else {
|
||||||
return req.eventLoop.makeSucceededFuture(.noBodyData)
|
return req.eventLoop.makeSucceededFuture(MessageResult.noBodyData.encoded)
|
||||||
}
|
}
|
||||||
guard let message = ServerMessage(decodeFrom: body) else {
|
guard let message = ServerMessage(decodeFrom: body) else {
|
||||||
return req.eventLoop.makeSucceededFuture(.invalidMessageData)
|
return req.eventLoop.makeSucceededFuture(MessageResult.invalidMessageSize.encoded)
|
||||||
}
|
}
|
||||||
guard deviceManager.authenticateRemote(message.authToken) else {
|
guard deviceManager.authenticateRemote(message.authToken) else {
|
||||||
return req.eventLoop.makeSucceededFuture(.invalidMessageData)
|
return req.eventLoop.makeSucceededFuture(MessageResult.messageAuthenticationFailed.encoded)
|
||||||
}
|
}
|
||||||
return deviceManager.sendMessageToDevice(message.message, on: req.eventLoop)
|
return deviceManager.sendMessageToDevice(message.message, on: req.eventLoop)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func deviceStatus(_ req: Request) -> EventLoopFuture<DeviceResponse> {
|
private func deviceStatus(_ req: Request) -> EventLoopFuture<MessageResult> {
|
||||||
guard let body = req.body.data else {
|
guard let body = req.body.data else {
|
||||||
return req.eventLoop.makeSucceededFuture(.noBodyData)
|
return req.eventLoop.makeSucceededFuture(.noBodyData)
|
||||||
}
|
}
|
||||||
guard let authToken = ServerMessage.token(from: body) else {
|
guard let authToken = ServerMessage.token(from: body) else {
|
||||||
return req.eventLoop.makeSucceededFuture(.invalidMessageData)
|
return req.eventLoop.makeSucceededFuture(.invalidMessageSize)
|
||||||
}
|
}
|
||||||
guard deviceManager.authenticateRemote(authToken) else {
|
guard deviceManager.authenticateRemote(authToken) else {
|
||||||
return req.eventLoop.makeSucceededFuture(.invalidMessageData)
|
return req.eventLoop.makeSucceededFuture(.messageAuthenticationFailed)
|
||||||
}
|
}
|
||||||
guard deviceManager.deviceIsConnected else {
|
guard deviceManager.deviceIsConnected else {
|
||||||
return req.eventLoop.makeSucceededFuture(.deviceNotConnected)
|
return req.eventLoop.makeSucceededFuture(.deviceNotConnected)
|
||||||
@ -48,7 +48,7 @@ func routes(_ app: Application) throws {
|
|||||||
The request expects the authentication token of the remote in the body data of the POST request.
|
The request expects the authentication token of the remote in the body data of the POST request.
|
||||||
|
|
||||||
The request returns one byte of data, which is the raw value of a `MessageResult`.
|
The request returns one byte of data, which is the raw value of a `MessageResult`.
|
||||||
Possible results are `noBodyData`, `invalidMessageData`, `deviceNotConnected`, `deviceConnected`.
|
Possible results are `noBodyData`, `invalidMessageSize`, `deviceNotConnected`, `deviceConnected`.
|
||||||
*/
|
*/
|
||||||
app.post(RouteAPI.getDeviceStatus.path) { req in
|
app.post(RouteAPI.getDeviceStatus.path) { req in
|
||||||
deviceStatus(req).map {
|
deviceStatus(req).map {
|
||||||
@ -66,7 +66,7 @@ func routes(_ app: Application) throws {
|
|||||||
*/
|
*/
|
||||||
app.post(RouteAPI.postMessage.path) { req in
|
app.post(RouteAPI.postMessage.path) { req in
|
||||||
messageTransmission(req).map {
|
messageTransmission(req).map {
|
||||||
Response(status: .ok, body: .init(data: $0.encoded))
|
Response(status: .ok, body: .init(data: $0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,44 +10,4 @@ final class AppTests: XCTestCase {
|
|||||||
XCTAssertEqual(input, output)
|
XCTAssertEqual(input, output)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testEncodingContent() {
|
|
||||||
let input = Message.Content(time: 1234567890, id: 23456789, device: 0)
|
|
||||||
let data = Array(input.encoded)
|
|
||||||
let output = Message.Content(decodeFrom: data)
|
|
||||||
XCTAssertEqual(input, output)
|
|
||||||
let data2 = [42, 42] + data
|
|
||||||
let output2 = Message.Content(decodeFrom: data2[2...])
|
|
||||||
XCTAssertEqual(input, output2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testEncodingMessage() {
|
|
||||||
let input = Message(mac: Data(repeating: 42, count: 32),
|
|
||||||
content: Message.Content(time: 1234567890, id: 23456789, device: 0))
|
|
||||||
let data = input.encoded
|
|
||||||
let buffer = ByteBuffer(data: data)
|
|
||||||
let output = Message(decodeFrom: buffer)
|
|
||||||
XCTAssertEqual(input, output)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testSigning() throws {
|
|
||||||
let key = SymmetricKey(size: .bits256)
|
|
||||||
let content = Message.Content(time: 1234567890, id: 23456789, device: 0)
|
|
||||||
let input = content.authenticate(using: key)
|
|
||||||
XCTAssertTrue(input.isValid(using: key))
|
|
||||||
|
|
||||||
let data = content.authenticateAndSerialize(using: key)
|
|
||||||
let decoded = Message(decodeFrom: ByteBuffer(data: data))
|
|
||||||
XCTAssertNotNil(decoded)
|
|
||||||
XCTAssertTrue(decoded!.isValid(using: key))
|
|
||||||
XCTAssertEqual(decoded!, input)
|
|
||||||
XCTAssertEqual(content, input.content)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testMessageTransmission() async throws {
|
|
||||||
let app = Application(.testing)
|
|
||||||
defer { app.shutdown() }
|
|
||||||
try await configure(app)
|
|
||||||
|
|
||||||
// How to open a socket via request?
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user