157 lines
4.0 KiB
Swift
157 lines
4.0 KiB
Swift
import Foundation
|
|
import SwiftUI
|
|
|
|
enum ConnectionError {
|
|
case serverNotReached
|
|
case deviceDisconnected
|
|
}
|
|
|
|
extension ConnectionError: CustomStringConvertible {
|
|
|
|
var description: String {
|
|
switch self {
|
|
case .serverNotReached:
|
|
return "Server unavailable"
|
|
case .deviceDisconnected:
|
|
return "Device disconnected"
|
|
}
|
|
}
|
|
}
|
|
|
|
enum RejectionCause {
|
|
case invalidCounter
|
|
case invalidTime
|
|
case invalidAuthentication
|
|
case timeout
|
|
}
|
|
|
|
extension RejectionCause: CustomStringConvertible {
|
|
|
|
var description: String {
|
|
switch self {
|
|
case .invalidCounter:
|
|
return "Invalid counter"
|
|
case .invalidTime:
|
|
return "Invalid time"
|
|
case .invalidAuthentication:
|
|
return "Invalid authentication"
|
|
case .timeout:
|
|
return "Device not responding"
|
|
}
|
|
}
|
|
}
|
|
|
|
enum ClientState {
|
|
|
|
/// There is no key stored locally on the client. A new key must be generated before use.
|
|
case noKeyAvailable
|
|
|
|
/// The device status is being requested
|
|
case requestingStatus
|
|
|
|
/// The remote device is not connected (no socket opened)
|
|
case deviceNotAvailable(ConnectionError)
|
|
|
|
/// The device is connected and ready to receive a message
|
|
case ready
|
|
|
|
/// The message is being transmitted and a response is awaited
|
|
case waitingForResponse
|
|
|
|
/// The transmitted message was rejected (multiple possible reasons)
|
|
case messageRejected(RejectionCause)
|
|
|
|
/// The device responded that the opening action was started
|
|
case openSesame
|
|
|
|
case internalError(String)
|
|
|
|
var canSendKey: Bool {
|
|
switch self {
|
|
case .ready, .openSesame, .messageRejected:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
init(keyResult: MessageResult) {
|
|
switch keyResult {
|
|
case .messageAuthenticationFailed:
|
|
self = .messageRejected(.invalidAuthentication)
|
|
case .messageTimeMismatch:
|
|
self = .messageRejected(.invalidTime)
|
|
case .messageCounterInvalid:
|
|
self = .messageRejected(.invalidCounter)
|
|
case .deviceTimedOut:
|
|
self = .messageRejected(.timeout)
|
|
case .messageAccepted:
|
|
self = .openSesame
|
|
case .noBodyData, .invalidMessageData, .textReceived, .unexpectedSocketEvent:
|
|
self = .internalError(keyResult.description)
|
|
case .deviceNotConnected:
|
|
self = .deviceNotAvailable(.deviceDisconnected)
|
|
case .operationInProgress:
|
|
self = .waitingForResponse
|
|
}
|
|
}
|
|
|
|
var openButtonText: String {
|
|
switch self {
|
|
case .noKeyAvailable:
|
|
return "Create key"
|
|
default:
|
|
return "Unlock"
|
|
}
|
|
}
|
|
|
|
var openButtonColor: Color {
|
|
switch self {
|
|
case .noKeyAvailable, .requestingStatus:
|
|
return .yellow
|
|
case .deviceNotAvailable, .messageRejected, .internalError:
|
|
return .red
|
|
case .ready, .waitingForResponse, .openSesame:
|
|
return .green
|
|
}
|
|
}
|
|
|
|
var openButtonIsEnabled: Bool {
|
|
switch self {
|
|
case .requestingStatus, .deviceNotAvailable, .waitingForResponse:
|
|
return false
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
extension ClientState: Equatable {
|
|
|
|
}
|
|
|
|
extension ClientState: CustomStringConvertible {
|
|
|
|
var description: String {
|
|
switch self {
|
|
case .noKeyAvailable:
|
|
return "No key set."
|
|
case .requestingStatus:
|
|
return "Checking device status"
|
|
case .deviceNotAvailable(let status):
|
|
return status.description
|
|
case .ready:
|
|
return "Ready"
|
|
case .waitingForResponse:
|
|
return "Unlocking..."
|
|
case .messageRejected(let cause):
|
|
return cause.description
|
|
case .openSesame:
|
|
return "Unlocked"
|
|
case .internalError(let e):
|
|
return "Error: \(e)"
|
|
}
|
|
}
|
|
|
|
}
|