Compare commits
7 Commits
1.0.0
...
9dd0045c4b
Author | SHA1 | Date | |
---|---|---|---|
|
9dd0045c4b | ||
|
23fd5055cd | ||
|
e96b85b1cc | ||
|
b3c58ce4c7 | ||
|
790662a1ec | ||
|
21a4f4ecae | ||
|
52cb76d4c8 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,3 +2,4 @@
|
|||||||
Package.resolved
|
Package.resolved
|
||||||
.swiftpm
|
.swiftpm
|
||||||
.build
|
.build
|
||||||
|
Resources/config.json
|
||||||
|
@@ -4,16 +4,19 @@ import PackageDescription
|
|||||||
let package = Package(
|
let package = Package(
|
||||||
name: "SesameServer",
|
name: "SesameServer",
|
||||||
platforms: [
|
platforms: [
|
||||||
.macOS(.v10_15)
|
.macOS(.v12)
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"),
|
.package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"),
|
||||||
|
.package(url: "https://github.com/christophhagen/clairvoyant.git", from: "0.5.0"),
|
||||||
],
|
],
|
||||||
targets: [
|
targets: [
|
||||||
.target(
|
.target(
|
||||||
name: "App",
|
name: "App",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.product(name: "Vapor", package: "vapor")
|
.product(name: "Vapor", package: "vapor"),
|
||||||
|
.product(name: "Clairvoyant", package: "Clairvoyant"),
|
||||||
|
|
||||||
],
|
],
|
||||||
swiftSettings: [
|
swiftSettings: [
|
||||||
// Enable better optimizations when building in Release configuration. Despite the use of
|
// Enable better optimizations when building in Release configuration. Despite the use of
|
||||||
|
6
Resources/config_example.json
Normal file
6
Resources/config_example.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"port": 6003,
|
||||||
|
"keyFileName": "keys",
|
||||||
|
"deviceTimeout": 20,
|
||||||
|
"authenticationTokens" : [],
|
||||||
|
}
|
@@ -1,2 +0,0 @@
|
|||||||
0000000000000000000000000000000000000000000000000000000000000000
|
|
||||||
0000000000000000000000000000000000000000000000000000000000000000
|
|
@@ -54,13 +54,13 @@ struct DeviceResponse {
|
|||||||
the remaining bytes contain the message.
|
the remaining bytes contain the message.
|
||||||
- Parameter buffer: The buffer where the message bytes are stored
|
- Parameter buffer: The buffer where the message bytes are stored
|
||||||
*/
|
*/
|
||||||
init?(_ buffer: ByteBuffer) {
|
init?(_ buffer: ByteBuffer, request: String) {
|
||||||
guard let byte = buffer.getBytes(at: 0, length: 1) else {
|
guard let byte = buffer.getBytes(at: 0, length: 1) else {
|
||||||
print("No bytes received from device")
|
log("\(request): No bytes received from device")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
guard let event = MessageResult(rawValue: byte[0]) else {
|
guard let event = MessageResult(rawValue: byte[0]) else {
|
||||||
print("Unknown response \(byte[0]) received from device")
|
log("\(request): Unknown response \(byte[0]) received from device")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
self.event = event
|
self.event = event
|
||||||
|
@@ -29,6 +29,14 @@ struct Message: Equatable, Hashable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Message: Codable {
|
||||||
|
|
||||||
|
enum CodingKeys: Int, CodingKey {
|
||||||
|
case mac = 1
|
||||||
|
case content = 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension Message {
|
extension Message {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -42,14 +50,17 @@ extension Message {
|
|||||||
/// The counter of the message (for freshness)
|
/// The counter of the message (for freshness)
|
||||||
let id: UInt32
|
let id: UInt32
|
||||||
|
|
||||||
|
let deviceId: UInt8?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Create new message content.
|
Create new message content.
|
||||||
- Parameter time: The time of message creation,
|
- Parameter time: The time of message creation,
|
||||||
- Parameter id: The counter of the message
|
- Parameter id: The counter of the message
|
||||||
*/
|
*/
|
||||||
init(time: UInt32, id: UInt32) {
|
init(time: UInt32, id: UInt32, device: UInt8) {
|
||||||
self.time = time
|
self.time = time
|
||||||
self.id = id
|
self.id = id
|
||||||
|
self.deviceId = device
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -61,20 +72,29 @@ extension Message {
|
|||||||
*/
|
*/
|
||||||
init<T: Sequence>(decodeFrom data: T) where T.Element == UInt8 {
|
init<T: Sequence>(decodeFrom data: T) where T.Element == UInt8 {
|
||||||
self.time = UInt32(data: Data(data.prefix(MemoryLayout<UInt32>.size)))
|
self.time = UInt32(data: Data(data.prefix(MemoryLayout<UInt32>.size)))
|
||||||
self.id = UInt32(data: Data(data.dropFirst(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
|
/// The byte length of an encoded message content
|
||||||
static var length: Int {
|
static var length: Int {
|
||||||
MemoryLayout<UInt32>.size * 2
|
MemoryLayout<UInt32>.size * 2 + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The message content encoded to data
|
/// The message content encoded to data
|
||||||
var encoded: Data {
|
var encoded: Data {
|
||||||
time.encoded + id.encoded
|
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 {
|
extension Message {
|
||||||
@@ -96,6 +116,14 @@ extension Message {
|
|||||||
self.init(decodeFrom: data)
|
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
|
/// The message encoded to data
|
||||||
var encoded: Data {
|
var encoded: Data {
|
||||||
mac + content.encoded
|
mac + content.encoded
|
||||||
|
@@ -26,6 +26,9 @@ enum MessageResult: UInt8 {
|
|||||||
/// The key was accepted by the device, and the door will be opened
|
/// The key was accepted by the device, and the door will be opened
|
||||||
case messageAccepted = 7
|
case messageAccepted = 7
|
||||||
|
|
||||||
|
/// The device id is invalid
|
||||||
|
case messageDeviceInvalid = 8
|
||||||
|
|
||||||
|
|
||||||
/// The request did not contain body data with the key
|
/// The request did not contain body data with the key
|
||||||
case noBodyData = 10
|
case noBodyData = 10
|
||||||
@@ -61,6 +64,8 @@ extension MessageResult: CustomStringConvertible {
|
|||||||
return "Message counter invalid"
|
return "Message counter invalid"
|
||||||
case .messageAccepted:
|
case .messageAccepted:
|
||||||
return "Message accepted"
|
return "Message accepted"
|
||||||
|
case .messageDeviceInvalid:
|
||||||
|
return "Invalid device ID"
|
||||||
case .noBodyData:
|
case .noBodyData:
|
||||||
return "No body data included in the request"
|
return "No body data included in the request"
|
||||||
case .deviceNotConnected:
|
case .deviceNotConnected:
|
||||||
|
@@ -3,11 +3,41 @@ import Foundation
|
|||||||
struct Config {
|
struct Config {
|
||||||
|
|
||||||
/// The port where the server runs
|
/// The port where the server runs
|
||||||
static let port = 6003
|
let port: Int
|
||||||
|
|
||||||
/// The name of the file in the `Resources` folder containing the device authentication token
|
/// The name of the file in the `Resources` folder containing the device authentication token
|
||||||
static let keyFileName = "keys"
|
let keyFileName: String
|
||||||
|
|
||||||
/// The seconds to wait for a response from the device
|
/// The seconds to wait for a response from the device
|
||||||
static let deviceTimeout: Int64 = 20
|
let deviceTimeout: Int64
|
||||||
|
|
||||||
|
/// The authentication tokens to use for monitoring of the service
|
||||||
|
let authenticationTokens: Set<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Config: Codable {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Config {
|
||||||
|
|
||||||
|
init(loadFrom url: URL) throws {
|
||||||
|
guard FileManager.default.fileExists(atPath: url.path) else {
|
||||||
|
log("No configuration file found at \(url.path)")
|
||||||
|
fatalError("No configuration file found")
|
||||||
|
}
|
||||||
|
let data: Data
|
||||||
|
do {
|
||||||
|
data = try Data(contentsOf: url)
|
||||||
|
} catch {
|
||||||
|
log("Failed to read config data: \(error)")
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
do {
|
||||||
|
self = try JSONDecoder().decode(Config.self, from: data)
|
||||||
|
} catch {
|
||||||
|
log("Failed to decode config data: \(error)")
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import WebSocketKit
|
import WebSocketKit
|
||||||
import Vapor
|
import Vapor
|
||||||
|
import Clairvoyant
|
||||||
|
|
||||||
final class DeviceManager {
|
final class DeviceManager {
|
||||||
|
|
||||||
@@ -17,6 +18,12 @@ final class DeviceManager {
|
|||||||
var deviceIsAuthenticated = false
|
var deviceIsAuthenticated = false
|
||||||
|
|
||||||
private var isOpeningNewConnection = false
|
private var isOpeningNewConnection = false
|
||||||
|
|
||||||
|
private let deviceTimeout: Int64
|
||||||
|
|
||||||
|
private let deviceConnectedMetric: Metric<Bool>
|
||||||
|
|
||||||
|
private let messagesToDeviceMetric: Metric<Int>
|
||||||
|
|
||||||
/// Indicator for device availability
|
/// Indicator for device availability
|
||||||
var deviceIsConnected: Bool {
|
var deviceIsConnected: Bool {
|
||||||
@@ -26,9 +33,31 @@ 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<DeviceResponse>?
|
||||||
|
|
||||||
init(deviceKey: Data, remoteKey: Data) {
|
init(deviceKey: Data, remoteKey: Data, deviceTimeout: Int64) async {
|
||||||
self.deviceKey = deviceKey
|
self.deviceKey = deviceKey
|
||||||
self.remoteKey = remoteKey
|
self.remoteKey = remoteKey
|
||||||
|
self.deviceTimeout = deviceTimeout
|
||||||
|
self.deviceConnectedMetric = try! await .init(
|
||||||
|
"sesame.connected",
|
||||||
|
name: "Device connected",
|
||||||
|
description: "Shows if the device is connected via WebSocket")
|
||||||
|
self.messagesToDeviceMetric = try! await .init(
|
||||||
|
"sesame.messages",
|
||||||
|
name: "Forwarded Messages",
|
||||||
|
description: "The number of messages transmitted to the device")
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateDeviceConnectionMetric() {
|
||||||
|
Task {
|
||||||
|
try? await deviceConnectedMetric.update(deviceIsConnected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateMessageCountMetric() {
|
||||||
|
Task {
|
||||||
|
let lastValue = await messagesToDeviceMetric.lastValue()?.value ?? 0
|
||||||
|
try? await messagesToDeviceMetric.update(lastValue + 1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: API
|
// MARK: API
|
||||||
@@ -45,27 +74,30 @@ final class DeviceManager {
|
|||||||
guard requestInProgress == nil else {
|
guard requestInProgress == nil else {
|
||||||
return eventLoop.makeSucceededFuture(.operationInProgress)
|
return eventLoop.makeSucceededFuture(.operationInProgress)
|
||||||
}
|
}
|
||||||
requestInProgress = eventLoop.makePromise(of: DeviceResponse.self)
|
let result = eventLoop.makePromise(of: DeviceResponse.self)
|
||||||
|
self.requestInProgress = result
|
||||||
socket.send(message.bytes, promise: nil)
|
socket.send(message.bytes, promise: nil)
|
||||||
eventLoop.scheduleTask(in: .seconds(Config.deviceTimeout)) { [weak self] in
|
updateMessageCountMetric()
|
||||||
|
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)
|
promise.succeed(.deviceTimedOut)
|
||||||
}
|
}
|
||||||
return requestInProgress!.futureResult
|
return result.futureResult
|
||||||
}
|
}
|
||||||
|
|
||||||
func authenticateDevice(hash: String) {
|
func authenticateDevice(hash: String) {
|
||||||
|
defer { updateDeviceConnectionMetric() }
|
||||||
guard let key = Data(fromHexEncodedString: hash),
|
guard let key = Data(fromHexEncodedString: hash),
|
||||||
SHA256.hash(data: key) == self.deviceKey else {
|
SHA256.hash(data: key) == self.deviceKey else {
|
||||||
print("Invalid device key")
|
log("Invalid device key")
|
||||||
_ = connection?.close()
|
_ = connection?.close()
|
||||||
deviceIsAuthenticated = false
|
deviceIsAuthenticated = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
print("Device authenticated")
|
log("Device authenticated")
|
||||||
deviceIsAuthenticated = true
|
deviceIsAuthenticated = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,37 +111,40 @@ final class DeviceManager {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer { requestInProgress = nil }
|
defer { requestInProgress = nil }
|
||||||
promise.succeed(DeviceResponse(data) ?? .unexpectedSocketEvent)
|
promise.succeed(DeviceResponse(data, request: RouteAPI.socket.rawValue) ?? .unexpectedSocketEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
func didCloseDeviceSocket() {
|
func didCloseDeviceSocket() {
|
||||||
|
defer { updateDeviceConnectionMetric() }
|
||||||
guard !isOpeningNewConnection else {
|
guard !isOpeningNewConnection else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
deviceIsAuthenticated = false
|
deviceIsAuthenticated = false
|
||||||
guard connection != nil else {
|
guard connection != nil else {
|
||||||
print("Socket closed, but no connection anyway")
|
log("Socket closed, but no connection anyway")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
connection = nil
|
connection = nil
|
||||||
print("Socket closed")
|
log("Socket closed")
|
||||||
}
|
}
|
||||||
|
|
||||||
func removeDeviceConnection() {
|
func removeDeviceConnection() {
|
||||||
|
defer { updateDeviceConnectionMetric() }
|
||||||
deviceIsAuthenticated = false
|
deviceIsAuthenticated = false
|
||||||
guard let socket = connection else {
|
guard let socket = connection else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try? socket.close().wait()
|
try? socket.close().wait()
|
||||||
connection = nil
|
connection = nil
|
||||||
print("Removed device connection")
|
log("Removed device connection")
|
||||||
}
|
}
|
||||||
|
|
||||||
func createNewDeviceConnection(_ socket: WebSocket) {
|
func createNewDeviceConnection(_ socket: WebSocket) {
|
||||||
|
defer { updateDeviceConnectionMetric() }
|
||||||
isOpeningNewConnection = true
|
isOpeningNewConnection = true
|
||||||
removeDeviceConnection()
|
removeDeviceConnection()
|
||||||
connection = socket
|
connection = socket
|
||||||
print("Socket connected")
|
log("Socket connected")
|
||||||
isOpeningNewConnection = false
|
isOpeningNewConnection = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import Vapor
|
import Vapor
|
||||||
|
import Clairvoyant
|
||||||
|
|
||||||
var deviceManager: DeviceManager!
|
var deviceManager: DeviceManager!
|
||||||
|
|
||||||
@@ -8,12 +9,51 @@ enum ServerError: Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// configures your application
|
// configures your application
|
||||||
public func configure(_ app: Application) throws {
|
public func configure(_ app: Application) async throws {
|
||||||
app.http.server.configuration.port = Config.port
|
|
||||||
|
|
||||||
let storageFolder = URL(fileURLWithPath: app.directory.resourcesDirectory)
|
let storageFolder = URL(fileURLWithPath: app.directory.resourcesDirectory)
|
||||||
let keyFile = storageFolder.appendingPathComponent(Config.keyFileName)
|
let logFolder = storageFolder.appendingPathComponent("logs")
|
||||||
let authContent: [Data] = try String(contentsOf: keyFile)
|
|
||||||
|
let accessManager = AccessTokenManager([])
|
||||||
|
let monitor = await MetricObserver(
|
||||||
|
logFolder: logFolder,
|
||||||
|
accessManager: accessManager,
|
||||||
|
logMetricId: "sesame.log")
|
||||||
|
MetricObserver.standard = monitor
|
||||||
|
|
||||||
|
let status = try await Metric<ServerStatus>("sesame.status")
|
||||||
|
try await status.update(.initializing)
|
||||||
|
|
||||||
|
await monitor.registerRoutes(app)
|
||||||
|
|
||||||
|
let configUrl = storageFolder.appendingPathComponent("config.json")
|
||||||
|
let config = try Config(loadFrom: configUrl)
|
||||||
|
|
||||||
|
config.authenticationTokens.map { $0.data(using: .utf8)! }.forEach(accessManager.add)
|
||||||
|
|
||||||
|
app.http.server.configuration.port = config.port
|
||||||
|
|
||||||
|
let keyFile = storageFolder.appendingPathComponent(config.keyFileName)
|
||||||
|
|
||||||
|
let (deviceKey, remoteKey) = try loadKeys(at: keyFile)
|
||||||
|
deviceManager = await DeviceManager(
|
||||||
|
deviceKey: deviceKey,
|
||||||
|
remoteKey: remoteKey,
|
||||||
|
deviceTimeout: config.deviceTimeout)
|
||||||
|
|
||||||
|
try routes(app)
|
||||||
|
|
||||||
|
// Gracefully shut down by closing potentially open socket
|
||||||
|
DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + .seconds(5)) {
|
||||||
|
_ = app.server.onShutdown.always { _ in
|
||||||
|
deviceManager.removeDeviceConnection()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try await status.update(.nominal)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func loadKeys(at url: URL) throws -> (deviceKey: Data, remoteKey: Data) {
|
||||||
|
let authContent: [Data] = try String(contentsOf: url)
|
||||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
.components(separatedBy: "\n")
|
.components(separatedBy: "\n")
|
||||||
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
|
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
|
||||||
@@ -29,15 +69,15 @@ public func configure(_ app: Application) throws {
|
|||||||
guard authContent.count == 2 else {
|
guard authContent.count == 2 else {
|
||||||
throw ServerError.invalidAuthenticationFileContent
|
throw ServerError.invalidAuthenticationFileContent
|
||||||
}
|
}
|
||||||
let deviceKey = authContent[0]
|
return (deviceKey: authContent[0], remoteKey: authContent[1])
|
||||||
let remoteKey = authContent[1]
|
}
|
||||||
deviceManager = DeviceManager(deviceKey: deviceKey, remoteKey: remoteKey)
|
|
||||||
try routes(app)
|
func log(_ message: String) {
|
||||||
|
guard let observer = MetricObserver.standard else {
|
||||||
// Gracefully shut down by closing potentially open socket
|
print(message)
|
||||||
DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + .seconds(5)) {
|
return
|
||||||
_ = app.server.onShutdown.always { _ in
|
}
|
||||||
deviceManager.removeDeviceConnection()
|
Task {
|
||||||
}
|
await observer.log(message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,15 @@
|
|||||||
import App
|
import App
|
||||||
import Vapor
|
import Vapor
|
||||||
|
|
||||||
var env = try Environment.detect()
|
var env = Environment.production //.detect()
|
||||||
try LoggingSystem.bootstrap(from: &env)
|
try LoggingSystem.bootstrap(from: &env)
|
||||||
let app = Application(env)
|
let app = Application(env)
|
||||||
defer { app.shutdown() }
|
defer { app.shutdown() }
|
||||||
try configure(app)
|
|
||||||
|
private let semaphore = DispatchSemaphore(value: 0)
|
||||||
|
Task {
|
||||||
|
try await configure(app)
|
||||||
|
semaphore.signal()
|
||||||
|
}
|
||||||
|
semaphore.wait()
|
||||||
try app.run()
|
try app.run()
|
||||||
|
@@ -11,7 +11,7 @@ final class AppTests: XCTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testEncodingContent() {
|
func testEncodingContent() {
|
||||||
let input = Message.Content(time: 1234567890, id: 23456789)
|
let input = Message.Content(time: 1234567890, id: 23456789, device: 0)
|
||||||
let data = Array(input.encoded)
|
let data = Array(input.encoded)
|
||||||
let output = Message.Content(decodeFrom: data)
|
let output = Message.Content(decodeFrom: data)
|
||||||
XCTAssertEqual(input, output)
|
XCTAssertEqual(input, output)
|
||||||
@@ -22,7 +22,7 @@ final class AppTests: XCTestCase {
|
|||||||
|
|
||||||
func testEncodingMessage() {
|
func testEncodingMessage() {
|
||||||
let input = Message(mac: Data(repeating: 42, count: 32),
|
let input = Message(mac: Data(repeating: 42, count: 32),
|
||||||
content: Message.Content(time: 1234567890, id: 23456789))
|
content: Message.Content(time: 1234567890, id: 23456789, device: 0))
|
||||||
let data = input.encoded
|
let data = input.encoded
|
||||||
let buffer = ByteBuffer(data: data)
|
let buffer = ByteBuffer(data: data)
|
||||||
let output = Message(decodeFrom: buffer)
|
let output = Message(decodeFrom: buffer)
|
||||||
@@ -31,7 +31,7 @@ final class AppTests: XCTestCase {
|
|||||||
|
|
||||||
func testSigning() throws {
|
func testSigning() throws {
|
||||||
let key = SymmetricKey(size: .bits256)
|
let key = SymmetricKey(size: .bits256)
|
||||||
let content = Message.Content(time: 1234567890, id: 23456789)
|
let content = Message.Content(time: 1234567890, id: 23456789, device: 0)
|
||||||
let input = content.authenticate(using: key)
|
let input = content.authenticate(using: key)
|
||||||
XCTAssertTrue(input.isValid(using: key))
|
XCTAssertTrue(input.isValid(using: key))
|
||||||
|
|
||||||
@@ -43,10 +43,10 @@ final class AppTests: XCTestCase {
|
|||||||
XCTAssertEqual(content, input.content)
|
XCTAssertEqual(content, input.content)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testMessageTransmission() throws {
|
func testMessageTransmission() async throws {
|
||||||
let app = Application(.testing)
|
let app = Application(.testing)
|
||||||
defer { app.shutdown() }
|
defer { app.shutdown() }
|
||||||
try configure(app)
|
try await configure(app)
|
||||||
|
|
||||||
// How to open a socket via request?
|
// How to open a socket via request?
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user