Sesame-iOS/Sesame/ClientState.swift
2022-05-01 14:07:43 +02:00

173 lines
4.4 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
case .deviceConnected:
self = .ready
}
}
var actionText: String {
"Unlock"
}
var requiresDescription: Bool {
switch self {
case .deviceNotAvailable, .messageRejected, .internalError:
return true
default:
return false
}
}
var color: Color {
switch self {
case .noKeyAvailable:
return .gray
case .requestingStatus:
return .yellow
case .deviceNotAvailable:
return Color(red: 1.0, green: 0.6, blue: 0.6)
case .messageRejected:
return .red
case .internalError:
return Color(red: 0.7, green: 0, blue: 0)
case .ready:
return Color(red: 0.7, green: 1.0, blue: 0.5)
case .waitingForResponse:
return Color(red: 0.9, green: 1.0, blue: 0.5)
case .openSesame:
return .green
}
}
var allowsAction: Bool {
switch self {
case .requestingStatus, .deviceNotAvailable, .waitingForResponse, .noKeyAvailable:
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)"
}
}
}