Sesame-iOS/Sesame/API Extensions/Message.swift
2023-12-20 09:25:53 +01:00

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))"
}
}