-
Notifications
You must be signed in to change notification settings - Fork 124
Expand file tree
/
Copy pathencoder.py
More file actions
126 lines (111 loc) · 4.81 KB
/
encoder.py
File metadata and controls
126 lines (111 loc) · 4.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
"""This module contains various Accessory encoders.
These are used to persist and load the state of the Accessory, so that
it can work properly after a restart.
"""
import json
import uuid
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ed25519
from .const import CLIENT_PROP_PERMS
from .state import State
class AccessoryEncoder:
"""This class defines the Accessory encoder interface.
The AccessoryEncoder is used by the AccessoryDriver to persist and restore the
state of an Accessory between restarts. This is needed in order to allow iOS
clients to see the same MAC, public key, etc. of the Accessory they paired with, thus
allowing an Accessory to "be remembered".
The idea is:
- The Accessory(ies) is created and added to an AccessoryDriver.
- The AccessoryDriver checks if a given file, containing the Accessory's state
exists. If so, it loads the state into the Accessory. Otherwise, it
creates the file and persists the state of the Accessory.
- On every change of the accessory - config change, new (un)paired clients,
the state is updated.
You can implement your own encoding logic, but the minimum set of properties that
must be persisted are:
- Public and private keys.
- UUID and public key of all paired clients.
- MAC address.
- Config version - ok, this is debatable, but it retains the consistency.
- Accessories Hash
The default implementation persists the above properties.
Note also that AIDs and IIDs must also survive a restore. However, this is managed
by the Accessory and Bridge classes.
@see: AccessoryDriver.persist AccessoryDriver.load AccessoryDriver.__init__
"""
@staticmethod
def persist(fp, state: State):
"""Persist the state of the given Accessory to the given file object.
Persists:
- MAC address.
- Public and private key.
- UUID and public key of paired clients.
- Config version.
- Accessories Hash
"""
paired_clients = {
str(client): bytes.hex(key) for client, key in state.paired_clients.items()
}
client_properties = {
str(client): props for client, props in state.client_properties.items()
}
client_uuid_to_bytes = {
str(client): bytes.hex(key) for client, key in state.uuid_to_bytes.items()
}
config_state = {
"mac": state.mac,
"config_version": state.config_version,
"paired_clients": paired_clients,
"client_properties": client_properties,
"accessories_hash": state.accessories_hash,
"client_uuid_to_bytes": client_uuid_to_bytes,
"private_key": bytes.hex(
state.private_key.private_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PrivateFormat.Raw,
encryption_algorithm=serialization.NoEncryption(),
)
),
"public_key": bytes.hex(
state.public_key.public_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PublicFormat.Raw,
)
),
}
json.dump(config_state, fp)
@staticmethod
def load_into(fp, state: State) -> None:
"""Load the accessory state from the given file object into the given Accessory.
@see: AccessoryEncoder.persist
"""
loaded = json.load(fp)
state.mac = loaded["mac"]
state.accessories_hash = loaded.get("accessories_hash")
state.config_version = loaded["config_version"]
if "client_properties" in loaded:
state.client_properties = {
uuid.UUID(client): props
for client, props in loaded["client_properties"].items()
}
else:
# If "client_properties" does not exist, everyone
# before that was paired as an admin
state.client_properties = {
uuid.UUID(client): {CLIENT_PROP_PERMS: 1}
for client in loaded["paired_clients"]
}
state.paired_clients = {
uuid.UUID(client): bytes.fromhex(key)
for client, key in loaded["paired_clients"].items()
}
state.private_key = ed25519.Ed25519PrivateKey.from_private_bytes(
bytes.fromhex(loaded["private_key"])
)
state.public_key = ed25519.Ed25519PublicKey.from_public_bytes(
bytes.fromhex(loaded["public_key"])
)
state.uuid_to_bytes = {
uuid.UUID(client): bytes.fromhex(key)
for client, key in loaded.get("client_uuid_to_bytes", {}).items()
}