Use device ID to distinguish remotes
This commit is contained in:
parent
8a17eef19b
commit
9b14f442b0
@ -50,14 +50,17 @@ extension Message {
|
||||
/// The counter of the message (for freshness)
|
||||
let id: UInt32
|
||||
|
||||
let deviceId: UInt8?
|
||||
|
||||
/**
|
||||
Create new message content.
|
||||
- Parameter time: The time of message creation,
|
||||
- Parameter id: The counter of the message
|
||||
*/
|
||||
init(time: UInt32, id: UInt32) {
|
||||
init(time: UInt32, id: UInt32, device: UInt8) {
|
||||
self.time = time
|
||||
self.id = id
|
||||
self.deviceId = device
|
||||
}
|
||||
|
||||
/**
|
||||
@ -69,17 +72,18 @@ extension Message {
|
||||
*/
|
||||
init<T: Sequence>(decodeFrom data: T) where T.Element == UInt8 {
|
||||
self.time = UInt32(data: Data(data.prefix(MemoryLayout<UInt32>.size)))
|
||||
self.id = UInt32(data: Data(data.dropFirst(MemoryLayout<UInt32>.size)))
|
||||
self.id = UInt32(data: Data(data.dropLast().suffix(MemoryLayout<UInt32>.size)))
|
||||
self.deviceId = data.suffix(1).last!
|
||||
}
|
||||
|
||||
/// The byte length of an encoded message content
|
||||
static var length: Int {
|
||||
MemoryLayout<UInt32>.size * 2
|
||||
MemoryLayout<UInt32>.size * 2 + 1
|
||||
}
|
||||
|
||||
/// The message content encoded to data
|
||||
var encoded: Data {
|
||||
time.encoded + id.encoded
|
||||
time.encoded + id.encoded + Data([deviceId ?? 0])
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -89,6 +93,7 @@ extension Message.Content: Codable {
|
||||
enum CodingKeys: Int, CodingKey {
|
||||
case time = 1
|
||||
case id = 2
|
||||
case deviceId = 3
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,9 @@ enum MessageResult: UInt8 {
|
||||
/// The key was accepted by the device, and the door will be opened
|
||||
case messageAccepted = 7
|
||||
|
||||
/// The device id is invalid
|
||||
case messageDeviceInvalid = 8
|
||||
|
||||
|
||||
/// The request did not contain body data with the key
|
||||
case noBodyData = 10
|
||||
@ -61,6 +64,8 @@ extension MessageResult: CustomStringConvertible {
|
||||
return "Message counter invalid"
|
||||
case .messageAccepted:
|
||||
return "Message accepted"
|
||||
case .messageDeviceInvalid:
|
||||
return "Invalid device ID"
|
||||
case .noBodyData:
|
||||
return "No body data included in the request"
|
||||
case .deviceNotConnected:
|
||||
|
@ -19,6 +19,7 @@ extension ConnectionError: CustomStringConvertible {
|
||||
}
|
||||
|
||||
enum RejectionCause {
|
||||
case invalidDeviceId
|
||||
case invalidCounter
|
||||
case invalidTime
|
||||
case invalidAuthentication
|
||||
@ -30,6 +31,8 @@ extension RejectionCause: CustomStringConvertible {
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case .invalidDeviceId:
|
||||
return "Invalid device ID"
|
||||
case .invalidCounter:
|
||||
return "Invalid counter"
|
||||
case .invalidTime:
|
||||
@ -92,6 +95,8 @@ enum ClientState {
|
||||
self = .messageRejected(.timeout)
|
||||
case .messageAccepted:
|
||||
self = .openSesame
|
||||
case .messageDeviceInvalid:
|
||||
self = .messageRejected(.invalidDeviceId)
|
||||
case .noBodyData, .invalidMessageData, .textReceived, .unexpectedSocketEvent:
|
||||
self = .internalError(keyResult.description)
|
||||
case .deviceNotConnected:
|
||||
@ -207,6 +212,8 @@ extension ClientState {
|
||||
return 6
|
||||
case .messageRejected(let rejectionCause):
|
||||
switch rejectionCause {
|
||||
case .invalidDeviceId:
|
||||
return 19
|
||||
case .invalidCounter:
|
||||
return 7
|
||||
case .invalidTime:
|
||||
@ -230,6 +237,8 @@ extension ClientState {
|
||||
return 15
|
||||
case .missingKey:
|
||||
return 16
|
||||
case .invalidDeviceId:
|
||||
return 20
|
||||
}
|
||||
case .openSesame:
|
||||
return 17
|
||||
@ -276,6 +285,10 @@ extension ClientState {
|
||||
self = .openSesame
|
||||
case 18:
|
||||
self = .internalError("")
|
||||
case 19:
|
||||
self = .messageRejected(.invalidDeviceId)
|
||||
case 20:
|
||||
self = .responseRejected(.invalidDeviceId)
|
||||
default:
|
||||
self = .internalError("Unknown code \(code)")
|
||||
}
|
||||
|
@ -18,6 +18,9 @@ struct ContentView: View {
|
||||
@AppStorage("local")
|
||||
private var useLocalConnection = false
|
||||
|
||||
@AppStorage("deviceID")
|
||||
private var deviceID: Int = 0
|
||||
|
||||
@State
|
||||
var keyManager = KeyManagement()
|
||||
|
||||
@ -133,6 +136,8 @@ struct ContentView: View {
|
||||
keyManager: $keyManager,
|
||||
serverAddress: $serverPath,
|
||||
localAddress: $localAddress,
|
||||
deviceID: $deviceID,
|
||||
nextMessageCounter: $nextMessageCounter,
|
||||
isCompensatingDaylightTime: $isCompensatingDaylightTime,
|
||||
useLocalConnection: $useLocalConnection)
|
||||
}
|
||||
@ -145,7 +150,8 @@ struct ContentView: View {
|
||||
|
||||
func mainButtonPressed() {
|
||||
guard let key = keyManager.get(.remoteKey),
|
||||
let token = keyManager.get(.authToken)?.data else {
|
||||
let token = keyManager.get(.authToken)?.data,
|
||||
let deviceId = UInt8(exactly: deviceID) else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -154,7 +160,8 @@ struct ContentView: View {
|
||||
// Add time to compensate that the device is using daylight savings time
|
||||
let content = Message.Content(
|
||||
time: sentTime.timestamp + compensationTime,
|
||||
id: count)
|
||||
id: count,
|
||||
device: deviceId)
|
||||
let message = content.authenticate(using: key)
|
||||
let historyItem = HistoryItem(sent: message.content, date: sentTime, local: useLocalConnection)
|
||||
state = .waitingForResponse
|
||||
|
@ -123,8 +123,8 @@ extension HistoryItem: Comparable {
|
||||
extension HistoryItem {
|
||||
|
||||
static var mock: HistoryItem {
|
||||
let content = Message.Content(time: Date.now.timestamp, id: 123)
|
||||
let content2 = Message.Content(time: (Date.now + 1).timestamp, id: 124)
|
||||
let content = Message.Content(time: Date.now.timestamp, id: 123, device: 0)
|
||||
let content2 = Message.Content(time: (Date.now + 1).timestamp, id: 124, device: 0)
|
||||
return .init(sent: content, date: .now, local: false)
|
||||
.didReceive(response: .openSesame, date: .now + 2, message: content2)
|
||||
}
|
||||
|
@ -17,11 +17,11 @@ extension KeyManagement {
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .deviceKey:
|
||||
return "Device Key"
|
||||
return "Unlock Key"
|
||||
case .remoteKey:
|
||||
return "Remote Key"
|
||||
return "Response Key"
|
||||
case .authToken:
|
||||
return "Authentication Token"
|
||||
return "Server Token"
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,6 +148,15 @@ final class KeyManagement: ObservableObject {
|
||||
|
||||
func generate(_ type: KeyType) {
|
||||
let key = SymmetricKey(size: type.keyLength)
|
||||
save(type, key: key)
|
||||
}
|
||||
|
||||
func save(_ type: KeyType, data: Data) {
|
||||
let key = SymmetricKey(data: data)
|
||||
save(type, key: key)
|
||||
}
|
||||
|
||||
private func save(_ type: KeyType, key: SymmetricKey) {
|
||||
if keyChain.has(type) {
|
||||
keyChain.delete(type)
|
||||
}
|
||||
|
@ -11,12 +11,31 @@ struct SettingsView: View {
|
||||
@Binding
|
||||
var localAddress: String
|
||||
|
||||
@Binding
|
||||
var deviceID: Int
|
||||
|
||||
@Binding
|
||||
var nextMessageCounter: Int
|
||||
|
||||
@Binding
|
||||
var isCompensatingDaylightTime: Bool
|
||||
|
||||
@Binding
|
||||
var useLocalConnection: Bool
|
||||
|
||||
@State
|
||||
private var showDeviceIdInput = false
|
||||
|
||||
@State
|
||||
private var deviceIdText = ""
|
||||
|
||||
@State
|
||||
private var showCounterInput = false
|
||||
|
||||
@State
|
||||
private var counterText = ""
|
||||
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
ScrollView {
|
||||
@ -25,12 +44,14 @@ struct SettingsView: View {
|
||||
Text("Server address")
|
||||
.bold()
|
||||
TextField("Server address", text: $serverAddress)
|
||||
.foregroundColor(.secondary)
|
||||
.padding(.leading, 8)
|
||||
}.padding(.vertical, 8)
|
||||
VStack(alignment: .leading) {
|
||||
Text("Local address")
|
||||
.bold()
|
||||
TextField("Local address", text: $localAddress)
|
||||
.foregroundColor(.secondary)
|
||||
.padding(.leading, 8)
|
||||
}.padding(.vertical, 8)
|
||||
Toggle(isOn: $useLocalConnection) {
|
||||
@ -39,6 +60,32 @@ struct SettingsView: View {
|
||||
Text("Attempt to communicate directly with the device. This is useful if the server is unavailable. Requires a WiFi connection on the same network as the device.")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
VStack(alignment: .leading) {
|
||||
Text("Device id")
|
||||
.bold()
|
||||
HStack(alignment: .bottom) {
|
||||
Text("\(deviceID)")
|
||||
.font(.system(.body, design: .monospaced))
|
||||
.foregroundColor(.secondary)
|
||||
.padding([.trailing, .bottom])
|
||||
Button("Edit", action: showAlertToChangeDeviceID)
|
||||
.padding([.horizontal, .bottom])
|
||||
.padding(.top, 4)
|
||||
}
|
||||
}.padding(.vertical, 8)
|
||||
VStack(alignment: .leading) {
|
||||
Text("Message counter")
|
||||
.bold()
|
||||
HStack(alignment: .bottom) {
|
||||
Text("\(nextMessageCounter)")
|
||||
.font(.system(.body, design: .monospaced))
|
||||
.foregroundColor(.secondary)
|
||||
.padding([.trailing, .bottom])
|
||||
Button("Edit", action: showAlertToChangeCounter)
|
||||
.padding([.horizontal, .bottom])
|
||||
.padding(.top, 4)
|
||||
}
|
||||
}.padding(.vertical, 8)
|
||||
ForEach(KeyManagement.KeyType.allCases) { keyType in
|
||||
SingleKeyView(
|
||||
keyManager: $keyManager,
|
||||
@ -57,8 +104,54 @@ struct SettingsView: View {
|
||||
}
|
||||
}
|
||||
.navigationTitle("Settings")
|
||||
.alert("Update device ID", isPresented: $showDeviceIdInput, actions: {
|
||||
TextField("Device ID", text: $deviceIdText)
|
||||
.keyboardType(.decimalPad)
|
||||
.font(.system(.body, design: .monospaced))
|
||||
.foregroundColor(.black)
|
||||
Button("Save", action: saveDeviceID)
|
||||
Button("Cancel", role: .cancel, action: {})
|
||||
}, message: {
|
||||
Text("Enter the device ID")
|
||||
})
|
||||
.alert("Update message counter", isPresented: $showCounterInput, actions: {
|
||||
TextField("Message counter", text: $counterText)
|
||||
.keyboardType(.decimalPad)
|
||||
.font(.system(.body, design: .monospaced))
|
||||
.foregroundColor(.black)
|
||||
Button("Save", action: saveCounter)
|
||||
Button("Cancel", role: .cancel, action: {})
|
||||
}, message: {
|
||||
Text("Enter the message counter")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private func showAlertToChangeDeviceID() {
|
||||
deviceIdText = "\(deviceID)"
|
||||
showDeviceIdInput = true
|
||||
}
|
||||
|
||||
private func saveDeviceID() {
|
||||
guard let id = UInt8(deviceIdText) else {
|
||||
print("Invalid device id '\(deviceIdText)'")
|
||||
return
|
||||
}
|
||||
self.deviceID = Int(id)
|
||||
}
|
||||
|
||||
private func showAlertToChangeCounter() {
|
||||
counterText = "\(nextMessageCounter)"
|
||||
showCounterInput = true
|
||||
}
|
||||
|
||||
private func saveCounter() {
|
||||
guard let id = UInt32(counterText) else {
|
||||
print("Invalid message counter '\(counterText)'")
|
||||
return
|
||||
}
|
||||
self.nextMessageCounter = Int(id)
|
||||
}
|
||||
}
|
||||
|
||||
struct SettingsView_Previews: PreviewProvider {
|
||||
@ -67,6 +160,8 @@ struct SettingsView_Previews: PreviewProvider {
|
||||
keyManager: .constant(KeyManagement()),
|
||||
serverAddress: .constant("https://example.com"),
|
||||
localAddress: .constant("192.168.178.42"),
|
||||
deviceID: .constant(0),
|
||||
nextMessageCounter: .constant(12345678),
|
||||
isCompensatingDaylightTime: .constant(true),
|
||||
useLocalConnection: .constant(false))
|
||||
}
|
||||
|
@ -9,6 +9,12 @@ struct SingleKeyView: View {
|
||||
@Binding
|
||||
var keyManager: KeyManagement
|
||||
|
||||
@State
|
||||
private var showEditWindow = false
|
||||
|
||||
@State
|
||||
private var keyText = ""
|
||||
|
||||
let type: KeyManagement.KeyType
|
||||
|
||||
private var generateText: String {
|
||||
@ -54,9 +60,41 @@ struct SingleKeyView: View {
|
||||
.disabled(!hasKey)
|
||||
.padding([.horizontal, .bottom])
|
||||
.padding(.top, 4)
|
||||
Button("Edit") {
|
||||
keyText = keyManager.get(type)?.displayString ?? ""
|
||||
print("Set key text to '\(keyText)'")
|
||||
showEditWindow = true
|
||||
}
|
||||
.padding([.horizontal, .bottom])
|
||||
.padding(.top, 4)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.alert("Update key", isPresented: $showEditWindow, actions: {
|
||||
TextField("Key data", text: $keyText)
|
||||
.lineLimit(4)
|
||||
.font(.system(.body, design: .monospaced))
|
||||
.foregroundColor(.black)
|
||||
Button("Save", action: saveKey)
|
||||
Button("Cancel", role: .cancel, action: {})
|
||||
}, message: {
|
||||
Text("Enter the hex encoded key")
|
||||
})
|
||||
}
|
||||
|
||||
private func saveKey() {
|
||||
let cleanText = keyText.replacingOccurrences(of: " ", with: "")
|
||||
guard let keyData = Data(fromHexEncodedString: cleanText) else {
|
||||
print("Invalid key string")
|
||||
return
|
||||
}
|
||||
let keyLength = type.keyLength.bitCount
|
||||
guard keyData.count * 8 == keyLength else {
|
||||
print("Invalid key length \(keyData.count * 8) bits, expected \(keyLength)")
|
||||
return
|
||||
}
|
||||
keyManager.save(type, data: keyData)
|
||||
print("Key \(type) saved")
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,5 +103,6 @@ struct SingleKeyView_Previews: PreviewProvider {
|
||||
SingleKeyView(
|
||||
keyManager: .constant(KeyManagement()),
|
||||
type: .deviceKey)
|
||||
.previewLayout(.fixed(width: 350, height: 100))
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user