Change to new message format
This commit is contained in:
parent
9ab32cc758
commit
f9039c7b3a
@ -1,10 +1,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
enum PublicAPI: String {
|
enum PublicAPI: String {
|
||||||
case getDeviceResponse = "response"
|
|
||||||
case getDeviceStatus = "status"
|
case getDeviceStatus = "status"
|
||||||
case clearKeyRequest = "clear"
|
case postMessage = "message"
|
||||||
case postKey = "key"
|
|
||||||
case postKeyIdParameter = "id"
|
|
||||||
case socket = "listen"
|
case socket = "listen"
|
||||||
}
|
}
|
||||||
|
@ -2,13 +2,7 @@ import Foundation
|
|||||||
import WebSocketKit
|
import WebSocketKit
|
||||||
import Vapor
|
import Vapor
|
||||||
|
|
||||||
final class KeyManagement {
|
final class DeviceManager {
|
||||||
|
|
||||||
/// The security parameter for the keys (in bits)
|
|
||||||
private static let keySecurity = 128
|
|
||||||
|
|
||||||
/// The size of the individual keys in bytes
|
|
||||||
static let keySize = keySecurity / 8
|
|
||||||
|
|
||||||
/// 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
|
static let deviceTimeout: Int64 = 20
|
||||||
@ -16,8 +10,10 @@ final class KeyManagement {
|
|||||||
/// The connection to the device
|
/// The connection to the device
|
||||||
private var connection: WebSocket?
|
private var connection: WebSocket?
|
||||||
|
|
||||||
|
/// The authentication token of the device for the socket connection
|
||||||
private let deviceKey: String
|
private let deviceKey: String
|
||||||
|
|
||||||
|
/// Indicate that the socket is fully initialized with an authorized device
|
||||||
var deviceIsAuthenticated = false
|
var deviceIsAuthenticated = false
|
||||||
|
|
||||||
/// Indicator for device availability
|
/// Indicator for device availability
|
||||||
@ -25,8 +21,8 @@ final class KeyManagement {
|
|||||||
!(connection?.isClosed ?? true) && deviceIsAuthenticated
|
!(connection?.isClosed ?? true) && deviceIsAuthenticated
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The id of the key which was sent to the device
|
/// A promise to finish the request once the device responds or times out
|
||||||
private var keyInTransit: (id: UInt16, promise: EventLoopPromise<KeyResult>)?
|
private var requestInProgress: EventLoopPromise<DeviceResponse>?
|
||||||
|
|
||||||
init(deviceKey: String) {
|
init(deviceKey: String) {
|
||||||
self.deviceKey = deviceKey
|
self.deviceKey = deviceKey
|
||||||
@ -38,26 +34,24 @@ final class KeyManagement {
|
|||||||
deviceIsConnected ? "1" : "0"
|
deviceIsConnected ? "1" : "0"
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendKeyToDevice(_ key: Data, keyId: UInt16, on eventLoop: EventLoop) -> EventLoopFuture<KeyResult> {
|
func sendMessageToDevice(_ message: Message, on eventLoop: EventLoop) -> EventLoopFuture<DeviceResponse> {
|
||||||
guard key.count == KeyManagement.keySize else {
|
|
||||||
return eventLoop.makeSucceededFuture(.invalidPayloadSize)
|
|
||||||
}
|
|
||||||
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(.deviceNotConnected)
|
||||||
}
|
}
|
||||||
let keyIdData = [UInt8(keyId >> 8), UInt8(keyId & 0xFF)]
|
guard requestInProgress == nil else {
|
||||||
let promise = eventLoop.makePromise(of: KeyResult.self)
|
return eventLoop.makeSucceededFuture(.operationInProgress)
|
||||||
keyInTransit = (keyId, promise)
|
}
|
||||||
socket.send(keyIdData + key, promise: nil)
|
requestInProgress = eventLoop.makePromise(of: DeviceResponse.self)
|
||||||
|
socket.send(message.bytes, promise: nil)
|
||||||
eventLoop.scheduleTask(in: .seconds(Self.deviceTimeout)) { [weak self] in
|
eventLoop.scheduleTask(in: .seconds(Self.deviceTimeout)) { [weak self] in
|
||||||
guard let (storedKeyId, promise) = self?.keyInTransit, storedKeyId == keyId else {
|
guard let promise = self?.requestInProgress else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self?.keyInTransit = nil
|
self?.requestInProgress = nil
|
||||||
promise.succeed(.deviceTimedOut)
|
promise.succeed(.deviceTimedOut)
|
||||||
}
|
}
|
||||||
return promise.futureResult
|
return requestInProgress!.futureResult
|
||||||
}
|
}
|
||||||
|
|
||||||
func authenticateDevice(psk: String) {
|
func authenticateDevice(psk: String) {
|
||||||
@ -72,27 +66,12 @@ final class KeyManagement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func processDeviceResponse(_ data: ByteBuffer) {
|
func processDeviceResponse(_ data: ByteBuffer) {
|
||||||
guard let (_, promise) = keyInTransit else {
|
guard let promise = requestInProgress else {
|
||||||
print("No key in transit for response from device \(data)")
|
print("No message in transit for response from device \(data)")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer { keyInTransit = nil }
|
defer { requestInProgress = nil }
|
||||||
guard data.readableBytes == 1 else {
|
promise.succeed(DeviceResponse(data) ?? .unexpectedSocketEvent)
|
||||||
print("Unexpected number of bytes received from device")
|
|
||||||
promise.succeed(.unexpectedSocketEvent)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard let rawValue = data.getBytes(at: 0, length: 1)?.first else {
|
|
||||||
print("Unreadable data received from device")
|
|
||||||
promise.succeed(.unexpectedSocketEvent)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard let response = KeyResult(rawValue: rawValue) else {
|
|
||||||
print("Unknown response \(rawValue) received from device")
|
|
||||||
promise.succeed(.unexpectedSocketEvent)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
promise.succeed(response)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func didCloseDeviceSocket() {
|
func didCloseDeviceSocket() {
|
69
Sources/App/DeviceResponse.swift
Normal file
69
Sources/App/DeviceResponse.swift
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import Foundation
|
||||||
|
import NIOCore
|
||||||
|
|
||||||
|
|
||||||
|
struct DeviceResponse {
|
||||||
|
|
||||||
|
static var deviceTimedOut: DeviceResponse {
|
||||||
|
.init(event: .deviceTimedOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
static var deviceNotConnected: DeviceResponse {
|
||||||
|
.init(event: .deviceNotConnected)
|
||||||
|
}
|
||||||
|
|
||||||
|
static var unexpectedSocketEvent: DeviceResponse {
|
||||||
|
.init(event: .unexpectedSocketEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
static var invalidMessageData: DeviceResponse {
|
||||||
|
.init(event: .invalidMessageData)
|
||||||
|
}
|
||||||
|
|
||||||
|
static var noBodyData: DeviceResponse {
|
||||||
|
.init(event: .noBodyData)
|
||||||
|
}
|
||||||
|
|
||||||
|
static var corruptkeyData: DeviceResponse {
|
||||||
|
.init(event: .corruptkeyData)
|
||||||
|
}
|
||||||
|
|
||||||
|
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?
|
||||||
|
|
||||||
|
init?(_ buffer: ByteBuffer) {
|
||||||
|
guard let byte = buffer.getData(at: 0, length: 1) else {
|
||||||
|
print("No bytes received from device")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
guard let event = MessageResult(rawValue: byte[0]) else {
|
||||||
|
print("Unknown response \(byte[0]) received from device")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
self.event = event
|
||||||
|
guard let data = buffer.getSlice(at: 1, length: Message.length) else {
|
||||||
|
self.response = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.response = Message(decodeFrom: data)
|
||||||
|
}
|
||||||
|
|
||||||
|
init(event: MessageResult) {
|
||||||
|
self.event = event
|
||||||
|
self.response = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var encoded: Data {
|
||||||
|
guard let message = response else {
|
||||||
|
return Data([event.rawValue])
|
||||||
|
}
|
||||||
|
return Data([event.rawValue]) + message.encoded
|
||||||
|
}
|
||||||
|
}
|
107
Sources/App/Message.swift
Normal file
107
Sources/App/Message.swift
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import Foundation
|
||||||
|
import CryptoKit
|
||||||
|
import NIOCore
|
||||||
|
import Vapor
|
||||||
|
|
||||||
|
struct Message: Equatable, Hashable {
|
||||||
|
|
||||||
|
static var length: Int {
|
||||||
|
SHA256Digest.byteCount + Content.length
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Content: Equatable, Hashable {
|
||||||
|
|
||||||
|
let time: UInt32
|
||||||
|
|
||||||
|
let id: UInt32
|
||||||
|
|
||||||
|
init(time: UInt32, id: UInt32) {
|
||||||
|
self.time = time
|
||||||
|
self.id = id
|
||||||
|
}
|
||||||
|
|
||||||
|
init(decodeFrom data: Data) {
|
||||||
|
self.time = UInt32(data: data[data.startIndex..<data.startIndex+4])
|
||||||
|
self.id = UInt32(data: data[data.startIndex+4..<data.startIndex+8])
|
||||||
|
}
|
||||||
|
|
||||||
|
static var length: Int {
|
||||||
|
MemoryLayout<UInt32>.size * 2
|
||||||
|
}
|
||||||
|
|
||||||
|
func authenticate(using key: SymmetricKey) -> Message {
|
||||||
|
let mac = HMAC<SHA256>.authenticationCode(for: encoded, using: key)
|
||||||
|
return .init(mac: Data(mac.map { $0 }), content: self)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
var encoded: Data {
|
||||||
|
time.encoded + id.encoded
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes: [UInt8] {
|
||||||
|
time.bytes + id.bytes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mac: Data
|
||||||
|
|
||||||
|
let content: Content
|
||||||
|
|
||||||
|
init(mac: Data, content: Content) {
|
||||||
|
self.mac = mac
|
||||||
|
self.content = content
|
||||||
|
}
|
||||||
|
|
||||||
|
init?(decodeFrom buffer: ByteBuffer) {
|
||||||
|
guard let data = buffer.getData(at: 0, length: Message.length) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
self.init(decodeFrom: data)
|
||||||
|
}
|
||||||
|
|
||||||
|
private init(decodeFrom data: Data) {
|
||||||
|
let count = SHA256Digest.byteCount
|
||||||
|
self.mac = data[data.startIndex..<data.startIndex+count]
|
||||||
|
self.content = .init(decodeFrom: data.advanced(by: count))
|
||||||
|
}
|
||||||
|
|
||||||
|
func isValid(using key: SymmetricKey) -> Bool {
|
||||||
|
HMAC<SHA256>.isValidAuthenticationCode(mac, authenticating: content.encoded, using: key)
|
||||||
|
}
|
||||||
|
|
||||||
|
var encoded: Data {
|
||||||
|
mac + content.encoded
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes: [UInt8] {
|
||||||
|
Array(mac) + content.bytes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension UInt32 {
|
||||||
|
|
||||||
|
init(data: Data) {
|
||||||
|
self = data
|
||||||
|
.reversed()
|
||||||
|
.enumerated()
|
||||||
|
.map { UInt32($0.element) << ($0.offset * 8) }
|
||||||
|
.reduce(0, +)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var encoded: Data {
|
||||||
|
.init(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes: [UInt8] {
|
||||||
|
(0..<4).reversed().map {
|
||||||
|
UInt8((self >> ($0*8)) & 0xFF)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,7 @@ import Foundation
|
|||||||
/**
|
/**
|
||||||
A result from sending a key to the device.
|
A result from sending a key to the device.
|
||||||
*/
|
*/
|
||||||
enum KeyResult: UInt8 {
|
enum MessageResult: UInt8 {
|
||||||
|
|
||||||
/// Text content was received, although binary data was expected
|
/// Text content was received, although binary data was expected
|
||||||
case textReceived = 1
|
case textReceived = 1
|
||||||
@ -11,23 +11,21 @@ enum KeyResult: 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 (key id + key data, or just key) was invalid
|
/// The size of the payload (i.e. message) was invalid, or the data could not be read
|
||||||
case invalidPayloadSize = 3
|
case invalidMessageData = 3
|
||||||
|
|
||||||
/// The index of the key was out of bounds
|
/// The transmitted message could not be authenticated using the key
|
||||||
case invalidKeyIndex = 4
|
case messageAuthenticationFailed = 4
|
||||||
|
|
||||||
/// The transmitted key data did not match the expected key
|
/// The message time was not within the acceptable bounds
|
||||||
case invalidKey = 5
|
case messageTimeMismatch = 5
|
||||||
|
|
||||||
/// The key has been previously used and is no longer valid
|
|
||||||
case keyAlreadyUsed = 6
|
|
||||||
|
|
||||||
/// A later key has been used, invalidating this key (to prevent replay attacks after blocked communication)
|
/// A later key has been used, invalidating this key (to prevent replay attacks after blocked communication)
|
||||||
case keyWasSkipped = 7
|
case messageCounterInvalid = 6
|
||||||
|
|
||||||
/// 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 keyAccepted = 8
|
case messageAccepted = 7
|
||||||
|
|
||||||
|
|
||||||
/// The device produced an unknown error
|
/// The device produced an unknown error
|
||||||
case unknownDeviceError = 9
|
case unknownDeviceError = 9
|
||||||
@ -43,38 +41,41 @@ enum KeyResult: UInt8 {
|
|||||||
|
|
||||||
/// The device did not respond within the timeout
|
/// The device did not respond within the timeout
|
||||||
case deviceTimedOut = 13
|
case deviceTimedOut = 13
|
||||||
|
|
||||||
|
/// Another message is being processed by the device
|
||||||
|
case operationInProgress = 14
|
||||||
}
|
}
|
||||||
|
|
||||||
extension KeyResult: CustomStringConvertible {
|
extension MessageResult: CustomStringConvertible {
|
||||||
|
|
||||||
var description: String {
|
var description: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .invalidKeyIndex:
|
|
||||||
return "Invalid key id (too large)"
|
|
||||||
case .noBodyData:
|
|
||||||
return "No body data included in the request"
|
|
||||||
case .invalidPayloadSize:
|
|
||||||
return "Invalid key size"
|
|
||||||
case .corruptkeyData:
|
|
||||||
return "Key data corrupted"
|
|
||||||
case .deviceNotConnected:
|
|
||||||
return "Device not connected"
|
|
||||||
case .textReceived:
|
case .textReceived:
|
||||||
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 .invalidKey:
|
case .invalidMessageData:
|
||||||
return "The transmitted key was not correct"
|
return "Invalid message data"
|
||||||
case .keyAlreadyUsed:
|
case .messageAuthenticationFailed:
|
||||||
return "The transmitted key was already used"
|
return "Message authentication failed"
|
||||||
case .keyWasSkipped:
|
case .messageTimeMismatch:
|
||||||
return "A newer key was already used"
|
return "Message time invalid"
|
||||||
case .keyAccepted:
|
case .messageCounterInvalid:
|
||||||
return "Key successfully sent"
|
return "Message counter invalid"
|
||||||
|
case .messageAccepted:
|
||||||
|
return "Message accepted"
|
||||||
case .unknownDeviceError:
|
case .unknownDeviceError:
|
||||||
return "The device experienced an unknown error"
|
return "The device experienced an unknown error"
|
||||||
|
case .noBodyData:
|
||||||
|
return "No body data included in the request"
|
||||||
|
case .corruptkeyData:
|
||||||
|
return "Key data corrupted"
|
||||||
|
case .deviceNotConnected:
|
||||||
|
return "Device not connected"
|
||||||
case .deviceTimedOut:
|
case .deviceTimedOut:
|
||||||
return "The device did not respond"
|
return "The device did not respond"
|
||||||
|
case .operationInProgress:
|
||||||
|
return "Another operation is in progress"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,22 +1,22 @@
|
|||||||
import Vapor
|
import Vapor
|
||||||
|
|
||||||
var keyManager: KeyManagement!
|
var deviceManager: DeviceManager!
|
||||||
|
|
||||||
// configures your application
|
// configures your application
|
||||||
public func configure(_ app: Application) throws {
|
public func configure(_ app: Application) throws {
|
||||||
app.http.server.configuration.port = 6003
|
app.http.server.configuration.port = 6003
|
||||||
|
|
||||||
let storageFolder = URL(fileURLWithPath: app.directory.resourcesDirectory)
|
let storageFolder = URL(fileURLWithPath: app.directory.resourcesDirectory)
|
||||||
let keyFile = storageFolder.appendingPathComponent("device.key")
|
let keyFile = storageFolder.appendingPathComponent("deviceKey")
|
||||||
let deviceKey = try String(contentsOf: keyFile)
|
let deviceKey = try String(contentsOf: keyFile)
|
||||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
keyManager = KeyManagement(deviceKey: deviceKey)
|
deviceManager = DeviceManager(deviceKey: deviceKey)
|
||||||
try routes(app)
|
try routes(app)
|
||||||
|
|
||||||
// Gracefully shut down by closing potentially open socket
|
// Gracefully shut down by closing potentially open socket
|
||||||
DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + .seconds(5)) {
|
DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + .seconds(5)) {
|
||||||
_ = app.server.onShutdown.always { _ in
|
_ = app.server.onShutdown.always { _ in
|
||||||
keyManager.removeDeviceConnection()
|
deviceManager.removeDeviceConnection()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,20 +11,14 @@ extension PublicAPI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func keyTransmission(_ req: Request) -> EventLoopFuture<KeyResult> {
|
private func messageTransmission(_ req: Request) -> EventLoopFuture<DeviceResponse> {
|
||||||
guard let keyId = req.parameters.get(PublicAPI.postKeyIdParameter.rawValue, as: UInt16.self) else {
|
|
||||||
return req.eventLoop.makeSucceededFuture(.invalidKeyIndex)
|
|
||||||
}
|
|
||||||
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 body.readableBytes == KeyManagement.keySize else {
|
guard let message = Message(decodeFrom: body) else {
|
||||||
return req.eventLoop.makeSucceededFuture(.invalidPayloadSize)
|
return req.eventLoop.makeSucceededFuture(.invalidMessageData)
|
||||||
}
|
}
|
||||||
guard let key = body.getData(at: 0, length: KeyManagement.keySize) else {
|
return deviceManager.sendMessageToDevice(message, on: req.eventLoop)
|
||||||
return req.eventLoop.makeSucceededFuture(.corruptkeyData)
|
|
||||||
}
|
|
||||||
return keyManager.sendKeyToDevice(key, keyId: keyId, on: req.eventLoop)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func routes(_ app: Application) throws {
|
func routes(_ app: Application) throws {
|
||||||
@ -35,20 +29,19 @@ func routes(_ app: Application) throws {
|
|||||||
The response is a string of either "1" (connected) or "0" (disconnected)
|
The response is a string of either "1" (connected) or "0" (disconnected)
|
||||||
*/
|
*/
|
||||||
app.get(PublicAPI.getDeviceStatus.path) { req -> String in
|
app.get(PublicAPI.getDeviceStatus.path) { req -> String in
|
||||||
keyManager.deviceStatus
|
deviceManager.deviceStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Post a key to the device for unlocking.
|
Post a key to the device for unlocking.
|
||||||
|
|
||||||
The corresponding integer key id for the key data must be contained in the url path.
|
The request returns one or `Message.length+1` bytes of data, where the first byte is the raw value of a `MessageResult`,
|
||||||
|
and the optional following bytes contain the response message of the device. This request does not complete until either the device responds or the request times out. The timeout is specified by `KeyManagement.deviceTimeout`.
|
||||||
The request returns a string containing a `rawValue` of a `KeyPostResponse`
|
|
||||||
A success of this method does not yet signal successful unlocking.
|
|
||||||
The client should request the status by inquiring the device response.
|
|
||||||
*/
|
*/
|
||||||
app.post(PublicAPI.postKey.path, PublicAPI.postKeyIdParameter.pathParameter) { req in
|
app.post(PublicAPI.postMessage.path) { req in
|
||||||
keyTransmission(req).map { String($0.rawValue) }
|
messageTransmission(req).map {
|
||||||
|
Response(status: .ok, body: .init(data: $0.encoded))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -58,15 +51,15 @@ func routes(_ app: Application) throws {
|
|||||||
*/
|
*/
|
||||||
app.webSocket(PublicAPI.socket.path) { req, socket in
|
app.webSocket(PublicAPI.socket.path) { req, socket in
|
||||||
socket.onBinary { _, data in
|
socket.onBinary { _, data in
|
||||||
keyManager.processDeviceResponse(data)
|
deviceManager.processDeviceResponse(data)
|
||||||
}
|
}
|
||||||
socket.onText { _, text in
|
socket.onText { _, text in
|
||||||
keyManager.authenticateDevice(psk: text)
|
deviceManager.authenticateDevice(psk: text)
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = socket.onClose.always { _ in
|
_ = socket.onClose.always { _ in
|
||||||
keyManager.didCloseDeviceSocket()
|
deviceManager.didCloseDeviceSocket()
|
||||||
}
|
}
|
||||||
keyManager.createNewDeviceConnection(socket)
|
deviceManager.createNewDeviceConnection(socket)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,14 +2,44 @@
|
|||||||
import XCTVapor
|
import XCTVapor
|
||||||
|
|
||||||
final class AppTests: XCTestCase {
|
final class AppTests: XCTestCase {
|
||||||
func testHelloWorld() throws {
|
|
||||||
let app = Application(.testing)
|
|
||||||
defer { app.shutdown() }
|
|
||||||
try configure(app)
|
|
||||||
|
|
||||||
try app.test(.GET, "hello", afterResponse: { res in
|
func testEncodingUInt32() {
|
||||||
XCTAssertEqual(res.status, .ok)
|
let input: UInt32 = 123
|
||||||
XCTAssertEqual(res.body.string, "Hello, world!")
|
let data = input.encoded
|
||||||
})
|
let output = UInt32(data: data)
|
||||||
|
XCTAssertEqual(input, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testEncodingContent() {
|
||||||
|
let input = Message.Content(time: 1234567890, id: 23456789)
|
||||||
|
let data = input.encoded
|
||||||
|
let output = Message.Content(decodeFrom: data)
|
||||||
|
XCTAssertEqual(input, output)
|
||||||
|
let data2 = Data([42, 42]) + data
|
||||||
|
let output2 = Message.Content(decodeFrom: Data(data2[2...]))
|
||||||
|
XCTAssertEqual(input, output2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testEncodingMessage() {
|
||||||
|
let input = Message(mac: Data(repeating: 42, count: 32),
|
||||||
|
content: Message.Content(time: 1234567890, id: 23456789))
|
||||||
|
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)
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user