129 lines
4.1 KiB
Swift
129 lines
4.1 KiB
Swift
import Foundation
|
|
|
|
/**
|
|
The message content without authentication.
|
|
*/
|
|
struct Message: Equatable, Hashable {
|
|
|
|
/// The type of message being sent.
|
|
let messageType: MessageType
|
|
|
|
/**
|
|
* The random nonce created by the remote
|
|
*
|
|
* This nonce is a random number created by the remote, different for each unlock request.
|
|
* It is set for all message types.
|
|
*/
|
|
let clientChallenge: UInt32
|
|
|
|
/**
|
|
* A random number to sign by the remote
|
|
*
|
|
* This nonce is set by the server after receiving an initial message.
|
|
* It is set for the message types `challenge`, `request`, and `response`.
|
|
*/
|
|
let serverChallenge: UInt32
|
|
|
|
/**
|
|
* The response status for the previous message.
|
|
*
|
|
* It is set only for messages from the server, e.g. the `challenge` and `response` message types.
|
|
* Must be set to `MessageAccepted` for other messages.
|
|
*/
|
|
let result: MessageResult
|
|
|
|
init(messageType: MessageType, clientChallenge: UInt32, serverChallenge: UInt32, result: MessageResult) {
|
|
self.messageType = messageType
|
|
self.clientChallenge = clientChallenge
|
|
self.serverChallenge = serverChallenge
|
|
self.result = result
|
|
}
|
|
|
|
/**
|
|
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(decodeFrom data: Data) throws {
|
|
guard data.count == Message.size else {
|
|
print("Invalid message size \(data.count)")
|
|
throw MessageResult.invalidMessageSizeFromDevice
|
|
}
|
|
guard let messageType = MessageType(rawValue: data.first!) else {
|
|
print("Invalid message type \(data.first!)")
|
|
throw MessageResult.invalidMessageTypeFromDevice
|
|
}
|
|
self.messageType = messageType
|
|
let messageTypeEndIndex = data.startIndex+1
|
|
let clientChallengeEndIndex = messageTypeEndIndex + UInt32.byteSize
|
|
let clientChallengeData = Array(data[messageTypeEndIndex..<clientChallengeEndIndex])
|
|
self.clientChallenge = UInt32(bytes: clientChallengeData)
|
|
let serverChallengeEndIndex = clientChallengeEndIndex + UInt32.byteSize
|
|
let serverChallengeData = Array(data[clientChallengeEndIndex..<serverChallengeEndIndex])
|
|
self.serverChallenge = UInt32(bytes: serverChallengeData)
|
|
guard let result = MessageResult(rawValue: data.last!) else {
|
|
print("Invalid message result \(data.last!)")
|
|
throw MessageResult.unknownMessageResultFromDevice
|
|
}
|
|
self.result = result
|
|
}
|
|
|
|
/// The message content encoded to data
|
|
var encoded: Data {
|
|
messageType.encoded + clientChallenge.encoded + serverChallenge.encoded + result.encoded
|
|
}
|
|
}
|
|
|
|
extension Message: Codable {
|
|
|
|
enum CodingKeys: Int, CodingKey {
|
|
case messageType = 1
|
|
case clientChallenge = 2
|
|
case serverChallenge = 3
|
|
case result = 4
|
|
}
|
|
}
|
|
|
|
extension Message {
|
|
|
|
init(error: MessageResult, type: MessageType) {
|
|
self.init(messageType: type, clientChallenge: 0, serverChallenge: 0, result: error)
|
|
}
|
|
|
|
static func initial() -> Message {
|
|
.init(
|
|
messageType: .initial,
|
|
clientChallenge: .random(),
|
|
serverChallenge: 0,
|
|
result: .messageAccepted)
|
|
}
|
|
|
|
func with(result: MessageResult) -> Message {
|
|
.init(
|
|
messageType: messageType.responseType,
|
|
clientChallenge: clientChallenge,
|
|
serverChallenge: serverChallenge,
|
|
result: result)
|
|
}
|
|
|
|
/**
|
|
Create the message to respond to this challenge
|
|
*/
|
|
func requestMessage() -> Message {
|
|
.init(
|
|
messageType: .request,
|
|
clientChallenge: clientChallenge,
|
|
serverChallenge: serverChallenge,
|
|
result: .messageAccepted)
|
|
}
|
|
}
|
|
|
|
extension Message: CustomStringConvertible {
|
|
|
|
var description: String {
|
|
"\(messageType)(\(clientChallenge)->\(serverChallenge), \(result))"
|
|
}
|
|
}
|