xaal.lib 0.7.6__py3-none-any.whl → 0.7.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- xaal/lib/__init__.py +1 -1
- xaal/lib/aioengine.py +155 -131
- xaal/lib/aiohelpers.py +7 -7
- xaal/lib/aionetwork.py +24 -24
- xaal/lib/bindings.py +30 -32
- xaal/lib/cbor.py +15 -10
- xaal/lib/config.py +71 -42
- xaal/lib/core.py +82 -65
- xaal/lib/devices.py +145 -102
- xaal/lib/engine.py +61 -43
- xaal/lib/exceptions.py +24 -8
- xaal/lib/helpers.py +38 -27
- xaal/lib/messages.py +171 -149
- xaal/lib/network.py +23 -20
- xaal/lib/test.py +25 -27
- xaal/lib/tools.py +50 -36
- {xaal.lib-0.7.6.dist-info → xaal.lib-0.7.8.dist-info}/METADATA +20 -3
- xaal.lib-0.7.8.dist-info/RECORD +22 -0
- {xaal.lib-0.7.6.dist-info → xaal.lib-0.7.8.dist-info}/WHEEL +1 -1
- xaal.lib-0.7.6.dist-info/RECORD +0 -22
- {xaal.lib-0.7.6.dist-info → xaal.lib-0.7.8.dist-info}/top_level.txt +0 -0
xaal/lib/helpers.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""
|
|
2
2
|
This file contains some helpers functions. This functions aren't used in the lib itself
|
|
3
3
|
but can be usefull for xaal packages developpers
|
|
4
4
|
"""
|
|
@@ -7,42 +7,50 @@ import logging
|
|
|
7
7
|
import logging.handlers
|
|
8
8
|
import os
|
|
9
9
|
import time
|
|
10
|
+
from typing import Any, Optional
|
|
11
|
+
|
|
10
12
|
import coloredlogs
|
|
11
13
|
from decorator import decorator
|
|
12
14
|
|
|
13
|
-
from . import config
|
|
15
|
+
from .config import config
|
|
16
|
+
|
|
14
17
|
|
|
15
18
|
def singleton(class_):
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
instances = {}
|
|
20
|
+
|
|
21
|
+
def getinstance(*args, **kwargs):
|
|
22
|
+
if class_ not in instances:
|
|
23
|
+
instances[class_] = class_(*args, **kwargs)
|
|
24
|
+
return instances[class_]
|
|
25
|
+
|
|
26
|
+
return getinstance
|
|
22
27
|
|
|
23
28
|
|
|
24
29
|
@decorator
|
|
25
|
-
def timeit(method
|
|
30
|
+
def timeit(method, *args, **kwargs):
|
|
26
31
|
logger = logging.getLogger(__name__)
|
|
27
32
|
ts = time.time()
|
|
28
33
|
result = method(*args, **kwargs)
|
|
29
34
|
te = time.time()
|
|
30
|
-
logger.debug(
|
|
35
|
+
logger.debug("%r (%r, %r) %2.6f sec" % (method.__name__, args, kwargs, te - ts))
|
|
31
36
|
return result
|
|
32
37
|
|
|
33
|
-
|
|
38
|
+
|
|
39
|
+
def set_console_title(value: str):
|
|
34
40
|
# set xterm title
|
|
35
|
-
print("\
|
|
41
|
+
print("\x1b]0;xAAL => %s\x07" % value, end="\r")
|
|
36
42
|
|
|
37
|
-
def setup_console_logger(level=config.log_level):
|
|
38
|
-
fmt = '%(asctime)s %(name)-25s %(funcName)-18s %(levelname)-8s %(message)s'
|
|
39
|
-
#fmt = '[%(name)s] %(funcName)s %(levelname)s: %(message)s'
|
|
40
|
-
coloredlogs.install(level=level,fmt=fmt)
|
|
41
43
|
|
|
42
|
-
def
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
def setup_console_logger(level: str = config.log_level):
|
|
45
|
+
fmt = "%(asctime)s %(name)-25s %(funcName)-18s %(levelname)-8s %(message)s"
|
|
46
|
+
# fmt = '[%(name)s] %(funcName)s %(levelname)s: %(message)s'
|
|
47
|
+
coloredlogs.install(level=level, fmt=fmt)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def setup_file_logger(name: str, level: str = config.log_level, filename: Optional[str] = None):
|
|
51
|
+
filename = filename or os.path.join(config.log_path, "%s.log" % name)
|
|
52
|
+
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(name)s - %(message)s")
|
|
53
|
+
handler = logging.handlers.RotatingFileHandler(filename, "a", 10000, 1, "utf8")
|
|
46
54
|
handler.setLevel(level)
|
|
47
55
|
handler.setFormatter(formatter)
|
|
48
56
|
# register the new handler
|
|
@@ -50,26 +58,28 @@ def setup_file_logger(name,level=config.log_level,filename = None):
|
|
|
50
58
|
logger.root.addHandler(handler)
|
|
51
59
|
logger.root.setLevel('DEBUG')
|
|
52
60
|
|
|
61
|
+
|
|
53
62
|
# ---------------------------------------------------------------------------
|
|
54
|
-
# TBD: We should merge this stuffs, and add support for default config file
|
|
55
|
-
# and commnand line parsing.
|
|
56
|
-
#
|
|
63
|
+
# TBD: We should merge this stuffs, and add support for default config file
|
|
64
|
+
# and commnand line parsing.
|
|
65
|
+
#
|
|
57
66
|
# Default arguments console_log and file_log are (and should) never be used.
|
|
58
67
|
# ---------------------------------------------------------------------------
|
|
59
|
-
def run_package(pkg_name,pkg_setup,console_log = True,file_log=False):
|
|
68
|
+
def run_package(pkg_name: str, pkg_setup: Any, console_log: bool = True, file_log: bool = False):
|
|
60
69
|
if console_log:
|
|
61
70
|
set_console_title(pkg_name)
|
|
62
71
|
setup_console_logger()
|
|
63
72
|
if file_log:
|
|
64
73
|
setup_file_logger(pkg_name)
|
|
65
74
|
logger = logging.getLogger(pkg_name)
|
|
66
|
-
logger.info(
|
|
75
|
+
logger.info("starting xaal package: %s" % pkg_name)
|
|
67
76
|
|
|
68
77
|
from .engine import Engine
|
|
78
|
+
|
|
69
79
|
eng = Engine()
|
|
70
80
|
result = pkg_setup(eng)
|
|
71
81
|
|
|
72
|
-
if result
|
|
82
|
+
if result is not True:
|
|
73
83
|
logger.critical("something goes wrong with package: %s" % pkg_name)
|
|
74
84
|
try:
|
|
75
85
|
eng.run()
|
|
@@ -77,4 +87,5 @@ def run_package(pkg_name,pkg_setup,console_log = True,file_log=False):
|
|
|
77
87
|
eng.shutdown()
|
|
78
88
|
logger.info("exit")
|
|
79
89
|
|
|
80
|
-
|
|
90
|
+
|
|
91
|
+
__all__ = ['singleton', 'timeit', 'set_console_title', 'setup_console_logger', 'setup_file_logger', 'run_package']
|
xaal/lib/messages.py
CHANGED
|
@@ -18,26 +18,139 @@
|
|
|
18
18
|
# along with xAAL. If not, see <http://www.gnu.org/licenses/>.
|
|
19
19
|
#
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
import datetime
|
|
22
|
+
import logging
|
|
23
|
+
import pprint
|
|
24
|
+
import struct
|
|
25
|
+
import typing
|
|
26
|
+
from enum import Enum
|
|
27
|
+
from typing import Any, Optional
|
|
28
|
+
|
|
29
|
+
import pysodium
|
|
30
|
+
from tabulate import tabulate
|
|
31
|
+
|
|
32
|
+
from . import cbor, tools
|
|
23
33
|
from .bindings import UUID
|
|
34
|
+
from .config import config
|
|
24
35
|
from .exceptions import MessageError, MessageParserError
|
|
25
|
-
from . import cbor
|
|
26
36
|
|
|
27
|
-
|
|
28
|
-
from
|
|
29
|
-
|
|
30
|
-
import datetime
|
|
31
|
-
import pysodium
|
|
32
|
-
import struct
|
|
33
|
-
import sys
|
|
37
|
+
if typing.TYPE_CHECKING:
|
|
38
|
+
from .devices import Device
|
|
39
|
+
|
|
34
40
|
|
|
35
|
-
import logging
|
|
36
41
|
logger = logging.getLogger(__name__)
|
|
37
42
|
|
|
38
43
|
ALIVE_ADDR = UUID("00000000-0000-0000-0000-000000000000")
|
|
39
44
|
|
|
40
45
|
|
|
46
|
+
class MessageType(Enum):
|
|
47
|
+
NOTIFY = 0
|
|
48
|
+
REQUEST = 1
|
|
49
|
+
REPLY = 2
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class MessageAction(Enum):
|
|
53
|
+
ALIVE = 'alive'
|
|
54
|
+
IS_ALIVE = 'is_alive'
|
|
55
|
+
ATTRIBUTES_CHANGE = 'attributes_change'
|
|
56
|
+
GET_ATTRIBUTES = 'get_attributes'
|
|
57
|
+
GET_DESCRIPTION = 'get_description'
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class Message(object):
|
|
61
|
+
"""Message object used for incomming & outgoint message"""
|
|
62
|
+
|
|
63
|
+
__slots__ = ['version', 'timestamp', 'source', 'dev_type', 'msg_type', 'action', 'body', '__targets']
|
|
64
|
+
|
|
65
|
+
def __init__(self):
|
|
66
|
+
self.version = config.STACK_VERSION # message API version
|
|
67
|
+
self.__targets = [] # target property
|
|
68
|
+
self.timestamp: tuple = () # message timestamp
|
|
69
|
+
self.source: Optional[UUID] = None # message source
|
|
70
|
+
self.dev_type: Optional[str] = None # message dev_type
|
|
71
|
+
self.msg_type: Optional[MessageType] = None # message type
|
|
72
|
+
self.action: Optional[str] = None # message action
|
|
73
|
+
self.body = {} # message body
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def targets(self) -> list:
|
|
77
|
+
return self.__targets
|
|
78
|
+
|
|
79
|
+
@targets.setter
|
|
80
|
+
def targets(self, values: list):
|
|
81
|
+
if not isinstance(values, list):
|
|
82
|
+
raise MessageError("Expected a list for targetsList, got %s" % (type(values),))
|
|
83
|
+
for uid in values:
|
|
84
|
+
if not tools.is_valid_address(uid):
|
|
85
|
+
raise MessageError("Bad target addr: %s" % uid)
|
|
86
|
+
self.__targets = values
|
|
87
|
+
|
|
88
|
+
def targets_as_string(self) -> list:
|
|
89
|
+
return [str(k) for k in self.targets]
|
|
90
|
+
|
|
91
|
+
def dump(self):
|
|
92
|
+
r = []
|
|
93
|
+
r.append(['version', self.version])
|
|
94
|
+
r.append(['targets', str(self.targets)])
|
|
95
|
+
r.append(['timestamp', str(self.timestamp)])
|
|
96
|
+
r.append(['source', self.source])
|
|
97
|
+
r.append(['dev_type', self.dev_type])
|
|
98
|
+
r.append(['msg_type', MessageType(self.msg_type)])
|
|
99
|
+
r.append(['action', self.action])
|
|
100
|
+
if self.body:
|
|
101
|
+
tmp = ""
|
|
102
|
+
for k, v in self.body.items():
|
|
103
|
+
k = k + ":"
|
|
104
|
+
v = pprint.pformat(v, width=55)
|
|
105
|
+
tmp = tmp + "- %-12s %s\n" % (k, v)
|
|
106
|
+
# tmp = tmp.strip()
|
|
107
|
+
r.append(['body', tmp])
|
|
108
|
+
print(tabulate(r, headers=['Field', 'Value'], tablefmt='psql'))
|
|
109
|
+
|
|
110
|
+
def __repr__(self) -> str:
|
|
111
|
+
return f"<xaal.Message {id(self):x} {self.source} {self.dev_type} {self.msg_type} {self.action}>"
|
|
112
|
+
|
|
113
|
+
def is_request(self) -> bool:
|
|
114
|
+
if MessageType(self.msg_type) == MessageType.REQUEST:
|
|
115
|
+
return True
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
def is_reply(self) -> bool:
|
|
119
|
+
if MessageType(self.msg_type) == MessageType.REPLY:
|
|
120
|
+
return True
|
|
121
|
+
return False
|
|
122
|
+
|
|
123
|
+
def is_notify(self) -> bool:
|
|
124
|
+
if MessageType(self.msg_type) == MessageType.NOTIFY:
|
|
125
|
+
return True
|
|
126
|
+
return False
|
|
127
|
+
|
|
128
|
+
def is_alive(self) -> bool:
|
|
129
|
+
if self.is_notify() and self.action == MessageAction.ALIVE.value:
|
|
130
|
+
return True
|
|
131
|
+
return False
|
|
132
|
+
|
|
133
|
+
def is_request_isalive(self) -> bool:
|
|
134
|
+
if self.is_request() and self.action == MessageAction.IS_ALIVE.value:
|
|
135
|
+
return True
|
|
136
|
+
return False
|
|
137
|
+
|
|
138
|
+
def is_attributes_change(self) -> bool:
|
|
139
|
+
if self.is_notify() and self.action == MessageAction.ATTRIBUTES_CHANGE.value:
|
|
140
|
+
return True
|
|
141
|
+
return False
|
|
142
|
+
|
|
143
|
+
def is_get_attribute_reply(self) -> bool:
|
|
144
|
+
if self.is_reply() and self.action == MessageAction.GET_ATTRIBUTES.value:
|
|
145
|
+
return True
|
|
146
|
+
return False
|
|
147
|
+
|
|
148
|
+
def is_get_description_reply(self) -> bool:
|
|
149
|
+
if self.is_reply() and self.action == MessageAction.GET_DESCRIPTION.value:
|
|
150
|
+
return True
|
|
151
|
+
return False
|
|
152
|
+
|
|
153
|
+
|
|
41
154
|
class MessageFactory(object):
|
|
42
155
|
"""Message Factory:
|
|
43
156
|
- Build xAAL message
|
|
@@ -45,9 +158,10 @@ class MessageFactory(object):
|
|
|
45
158
|
- Serialize/Deserialize data in CBOR"""
|
|
46
159
|
|
|
47
160
|
def __init__(self, cipher_key):
|
|
48
|
-
|
|
161
|
+
# key encode / decode message built from passphrase
|
|
162
|
+
self.cipher_key = cipher_key
|
|
49
163
|
|
|
50
|
-
def encode_msg(self, msg):
|
|
164
|
+
def encode_msg(self, msg: Message) -> bytes:
|
|
51
165
|
"""Apply security layer and return encode MSG in CBOR
|
|
52
166
|
:param msg: xAAL msg instance
|
|
53
167
|
:type msg: Message
|
|
@@ -65,6 +179,10 @@ class MessageFactory(object):
|
|
|
65
179
|
|
|
66
180
|
# Format payload & ciphering
|
|
67
181
|
buf = []
|
|
182
|
+
if not msg.source:
|
|
183
|
+
raise MessageError("No source address in message")
|
|
184
|
+
if not msg.msg_type:
|
|
185
|
+
raise MessageError("No msg_type in message")
|
|
68
186
|
buf.append(msg.source.bytes)
|
|
69
187
|
buf.append(msg.dev_type)
|
|
70
188
|
buf.append(msg.msg_type.value)
|
|
@@ -73,8 +191,8 @@ class MessageFactory(object):
|
|
|
73
191
|
buf.append(msg.body)
|
|
74
192
|
clear = cbor.dumps(buf)
|
|
75
193
|
# Additionnal Data == cbor serialization of the targets array
|
|
76
|
-
ad
|
|
77
|
-
nonce
|
|
194
|
+
ad = result[3]
|
|
195
|
+
nonce = build_nonce(msg.timestamp)
|
|
78
196
|
payload = pysodium.crypto_aead_chacha20poly1305_ietf_encrypt(clear, ad, nonce, self.cipher_key)
|
|
79
197
|
|
|
80
198
|
# Final CBOR serialization
|
|
@@ -82,7 +200,7 @@ class MessageFactory(object):
|
|
|
82
200
|
pkt = cbor.dumps(result)
|
|
83
201
|
return pkt
|
|
84
202
|
|
|
85
|
-
def decode_msg(self, data, filter_func=None):
|
|
203
|
+
def decode_msg(self, data: bytes, filter_func: Any = None) -> Optional[Message]:
|
|
86
204
|
"""Decode incoming CBOR data and De-Ciphering
|
|
87
205
|
:param data: data received from the multicast bus
|
|
88
206
|
:type data: cbor
|
|
@@ -94,17 +212,17 @@ class MessageFactory(object):
|
|
|
94
212
|
# Decode cbor incoming data
|
|
95
213
|
try:
|
|
96
214
|
data_rx = cbor.loads(data)
|
|
97
|
-
except:
|
|
215
|
+
except Exception:
|
|
98
216
|
raise MessageParserError("Unable to parse CBOR data")
|
|
99
217
|
|
|
100
218
|
# Instanciate Message, parse the security layer
|
|
101
219
|
msg = Message()
|
|
102
220
|
try:
|
|
103
|
-
msg.version
|
|
104
|
-
msg_time
|
|
105
|
-
targets
|
|
106
|
-
msg.targets
|
|
107
|
-
msg.timestamp =
|
|
221
|
+
msg.version = data_rx[0]
|
|
222
|
+
msg_time = data_rx[1]
|
|
223
|
+
targets = cbor.loads(data_rx[3])
|
|
224
|
+
msg.targets = [UUID(bytes=t) for t in targets]
|
|
225
|
+
msg.timestamp = (data_rx[1], data_rx[2])
|
|
108
226
|
except IndexError:
|
|
109
227
|
raise MessageParserError("Bad Message, wrong fields")
|
|
110
228
|
|
|
@@ -116,7 +234,7 @@ class MessageFactory(object):
|
|
|
116
234
|
# Replay attack, window fixed to CIPHER_WINDOW in seconds
|
|
117
235
|
now = build_timestamp()[0] # test done only on seconds ...
|
|
118
236
|
if msg_time < (now - config.cipher_window):
|
|
119
|
-
raise MessageParserError("Potential replay attack, message too old: %d sec" % round(now - msg_time)
|
|
237
|
+
raise MessageParserError("Potential replay attack, message too old: %d sec" % round(now - msg_time))
|
|
120
238
|
|
|
121
239
|
if msg_time > (now + config.cipher_window):
|
|
122
240
|
raise MessageParserError("Potential replay attack, message too young: %d sec" % round(now - msg_time))
|
|
@@ -132,19 +250,19 @@ class MessageFactory(object):
|
|
|
132
250
|
nonce = build_nonce(msg.timestamp)
|
|
133
251
|
try:
|
|
134
252
|
clear = pysodium.crypto_aead_chacha20poly1305_ietf_decrypt(ciph, ad, nonce, self.cipher_key)
|
|
135
|
-
except :
|
|
253
|
+
except Exception:
|
|
136
254
|
raise MessageParserError("Unable to decrypt msg")
|
|
137
255
|
|
|
138
256
|
# Decode application layer (payload)
|
|
139
257
|
try:
|
|
140
258
|
payload = cbor.loads(clear)
|
|
141
|
-
except:
|
|
259
|
+
except Exception:
|
|
142
260
|
raise MessageParserError("Unable to parse CBOR data in payload after decrypt")
|
|
143
261
|
try:
|
|
144
|
-
msg.source
|
|
262
|
+
msg.source = UUID(bytes=payload[0])
|
|
145
263
|
msg.dev_type = payload[1]
|
|
146
264
|
msg.msg_type = payload[2]
|
|
147
|
-
msg.action
|
|
265
|
+
msg.action = payload[3]
|
|
148
266
|
except IndexError:
|
|
149
267
|
raise MessageParserError("Unable to parse payload headers")
|
|
150
268
|
if len(payload) == 5:
|
|
@@ -160,7 +278,14 @@ class MessageFactory(object):
|
|
|
160
278
|
#####################################################
|
|
161
279
|
# MSG builder
|
|
162
280
|
#####################################################
|
|
163
|
-
def build_msg(
|
|
281
|
+
def build_msg(
|
|
282
|
+
self,
|
|
283
|
+
dev: Optional['Device'] = None,
|
|
284
|
+
targets: list = [],
|
|
285
|
+
msg_type: Optional[MessageType] = None,
|
|
286
|
+
action: Optional[str] = None,
|
|
287
|
+
body: Optional[dict] = None,
|
|
288
|
+
):
|
|
164
289
|
"""the build method takes in parameters :
|
|
165
290
|
-A device
|
|
166
291
|
-The list of targets of the message
|
|
@@ -187,150 +312,47 @@ class MessageFactory(object):
|
|
|
187
312
|
data = self.encode_msg(message)
|
|
188
313
|
return data
|
|
189
314
|
|
|
190
|
-
def build_alive_for(self, dev, timeout=0):
|
|
315
|
+
def build_alive_for(self, dev: 'Device', timeout: int = 0) -> bytes:
|
|
191
316
|
"""Build Alive message for a given device
|
|
192
317
|
timeout = 0 is the minimum value
|
|
193
318
|
"""
|
|
194
319
|
body = {}
|
|
195
320
|
body['timeout'] = timeout
|
|
196
|
-
message = self.build_msg(
|
|
321
|
+
message = self.build_msg(
|
|
322
|
+
dev=dev, targets=[], msg_type=MessageType.NOTIFY, action=MessageAction.ALIVE.value, body=body
|
|
323
|
+
)
|
|
197
324
|
return message
|
|
198
325
|
|
|
199
|
-
def build_error_msg(self, dev, errcode, description=None):
|
|
326
|
+
def build_error_msg(self, dev: 'Device', errcode: int, description: Optional[str] = None):
|
|
200
327
|
"""Build a Error message"""
|
|
201
328
|
message = Message()
|
|
202
329
|
body = {}
|
|
203
330
|
body['code'] = errcode
|
|
204
331
|
if description:
|
|
205
332
|
body['description'] = description
|
|
206
|
-
message = self.build_msg(dev, [], MessageType.NOTIFY,
|
|
333
|
+
message = self.build_msg(dev, [], MessageType.NOTIFY, 'error', body)
|
|
207
334
|
return message
|
|
208
335
|
|
|
209
336
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
REPLY = 2
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
class MessageAction(Enum):
|
|
217
|
-
ALIVE = "alive"
|
|
218
|
-
IS_ALIVE = "is_alive"
|
|
219
|
-
ATTRIBUTES_CHANGE = "attributes_change"
|
|
220
|
-
GET_ATTRIBUTES = "get_attributes"
|
|
221
|
-
GET_DESCRIPTION = "get_description"
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
class Message(object):
|
|
225
|
-
"""Message object used for incomming & outgoint message"""
|
|
226
|
-
|
|
227
|
-
__slots__ = ['version', 'timestamp', 'source', 'dev_type', 'msg_type', 'action', 'body', '__targets']
|
|
228
|
-
|
|
229
|
-
def __init__(self):
|
|
230
|
-
self.version = config.STACK_VERSION # message API version
|
|
231
|
-
self.__targets = [] # target property
|
|
232
|
-
self.timestamp = None # message timestamp
|
|
233
|
-
self.source = None # message source
|
|
234
|
-
self.dev_type = None # message dev_type
|
|
235
|
-
self.msg_type = None # message type
|
|
236
|
-
self.action = None # message action
|
|
237
|
-
self.body = {} # message body
|
|
238
|
-
|
|
239
|
-
@property
|
|
240
|
-
def targets(self):
|
|
241
|
-
return self.__targets
|
|
242
|
-
|
|
243
|
-
@targets.setter
|
|
244
|
-
def targets(self, values):
|
|
245
|
-
if not isinstance(values, list):
|
|
246
|
-
raise MessageError("Expected a list for targetsList, got %s" % (type(values),))
|
|
247
|
-
for uid in values:
|
|
248
|
-
if not tools.is_valid_address(uid):
|
|
249
|
-
raise MessageError("Bad target addr: %s" % uid)
|
|
250
|
-
self.__targets = values
|
|
251
|
-
|
|
252
|
-
def targets_as_string(self):
|
|
253
|
-
return [str(k) for k in self.targets]
|
|
254
|
-
|
|
255
|
-
def dump(self):
|
|
256
|
-
r = []
|
|
257
|
-
r.append(["version", self.version])
|
|
258
|
-
r.append(["targets", str(self.targets)])
|
|
259
|
-
r.append(["timestamp", str(self.timestamp)])
|
|
260
|
-
r.append(["source", self.source])
|
|
261
|
-
r.append(["dev_type", self.dev_type])
|
|
262
|
-
r.append(["msg_type", MessageType(self.msg_type)])
|
|
263
|
-
r.append(["action", self.action])
|
|
264
|
-
if self.body:
|
|
265
|
-
tmp=""
|
|
266
|
-
for k,v in self.body.items():
|
|
267
|
-
k = k + ':'
|
|
268
|
-
v = pprint.pformat(v,width=55)
|
|
269
|
-
tmp = tmp+"- %-12s %s\n" % (k,v)
|
|
270
|
-
#tmp = tmp.strip()
|
|
271
|
-
r.append(["body", tmp])
|
|
272
|
-
print(tabulate(r, headers=["Fied", "Value"], tablefmt="psql"))
|
|
273
|
-
|
|
274
|
-
def __repr__(self):
|
|
275
|
-
return f"<xaal.Message {id(self):x} {self.source} {self.dev_type} {self.msg_type} {self.action}>"
|
|
276
|
-
|
|
277
|
-
def is_request(self):
|
|
278
|
-
if MessageType(self.msg_type) == MessageType.REQUEST:
|
|
279
|
-
return True
|
|
280
|
-
return False
|
|
281
|
-
|
|
282
|
-
def is_reply(self):
|
|
283
|
-
if MessageType(self.msg_type) == MessageType.REPLY:
|
|
284
|
-
return True
|
|
285
|
-
return False
|
|
286
|
-
|
|
287
|
-
def is_notify(self):
|
|
288
|
-
if MessageType(self.msg_type) == MessageType.NOTIFY:
|
|
289
|
-
return True
|
|
290
|
-
return False
|
|
291
|
-
|
|
292
|
-
def is_alive(self):
|
|
293
|
-
if self.is_notify() and self.action == MessageAction.ALIVE.value:
|
|
294
|
-
return True
|
|
295
|
-
return False
|
|
296
|
-
|
|
297
|
-
def is_request_isalive(self):
|
|
298
|
-
if self.is_request() and self.action == MessageAction.IS_ALIVE.value:
|
|
299
|
-
return True
|
|
300
|
-
return False
|
|
301
|
-
|
|
302
|
-
def is_attributes_change(self):
|
|
303
|
-
if self.is_notify() and self.action == MessageAction.ATTRIBUTES_CHANGE.value:
|
|
304
|
-
return True
|
|
305
|
-
return False
|
|
306
|
-
|
|
307
|
-
def is_get_attribute_reply(self):
|
|
308
|
-
if self.is_reply() and self.action == MessageAction.GET_ATTRIBUTES.value:
|
|
309
|
-
return True
|
|
310
|
-
return False
|
|
311
|
-
|
|
312
|
-
def is_get_description_reply(self):
|
|
313
|
-
if self.is_reply() and self.action == MessageAction.GET_DESCRIPTION.value:
|
|
314
|
-
return True
|
|
315
|
-
return False
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
def build_nonce(data):
|
|
319
|
-
""" Big-Endian, time in seconds and time in microseconds """
|
|
320
|
-
nonce = struct.pack('>QL', data[0], data[1])
|
|
337
|
+
def build_nonce(data: tuple) -> bytes:
|
|
338
|
+
"""Big-Endian, time in seconds and time in microseconds"""
|
|
339
|
+
nonce = struct.pack(">QL", data[0], data[1])
|
|
321
340
|
return nonce
|
|
322
341
|
|
|
323
342
|
|
|
324
|
-
def build_timestamp():
|
|
343
|
+
def build_timestamp() -> tuple:
|
|
325
344
|
"""Return array [seconds since epoch, microseconds since last seconds] Time = UTC+0000"""
|
|
326
345
|
epoch = datetime.datetime.fromtimestamp(0, datetime.UTC)
|
|
327
346
|
timestamp = datetime.datetime.now(datetime.UTC) - epoch
|
|
328
|
-
return
|
|
347
|
+
return (int(timestamp.total_seconds()), int(timestamp.microseconds))
|
|
348
|
+
|
|
329
349
|
|
|
350
|
+
## This stuff below is for Py2/Py3 compatibility. In the current state of xAAL, we only use
|
|
351
|
+
# Py3. This code is here for archive purpose and could be removed in the future.
|
|
330
352
|
|
|
331
353
|
# for better performance, I choose to use this trick to fix the change in size for Py3.
|
|
332
354
|
# only test once.
|
|
333
|
-
if sys.version_info.major == 2:
|
|
334
|
-
_packtimestamp = lambda t1,t2:
|
|
335
|
-
else:
|
|
336
|
-
_packtimestamp = lambda t1,t2:
|
|
355
|
+
# if sys.version_info.major == 2:
|
|
356
|
+
# _packtimestamp = lambda t1, t2: (long(t1), int(t2)) # pyright: ignore
|
|
357
|
+
# else:
|
|
358
|
+
# _packtimestamp = lambda t1, t2: (int(t1), int(t2))
|
xaal/lib/network.py
CHANGED
|
@@ -18,25 +18,26 @@
|
|
|
18
18
|
# along with xAAL. If not, see <http://www.gnu.org/licenses/>.
|
|
19
19
|
#
|
|
20
20
|
|
|
21
|
+
import logging
|
|
22
|
+
import select
|
|
21
23
|
import socket
|
|
22
24
|
import struct
|
|
23
|
-
import
|
|
24
|
-
import
|
|
25
|
+
import time
|
|
26
|
+
from enum import Enum
|
|
27
|
+
from typing import Optional
|
|
25
28
|
|
|
26
29
|
logger = logging.getLogger(__name__)
|
|
27
30
|
|
|
28
|
-
import time
|
|
29
|
-
from enum import Enum
|
|
30
31
|
|
|
31
32
|
class NetworkState(Enum):
|
|
32
33
|
disconnected = 0
|
|
33
|
-
connected
|
|
34
|
+
connected = 1
|
|
34
35
|
|
|
35
36
|
|
|
36
37
|
class NetworkConnector(object):
|
|
37
38
|
UDP_MAX_SIZE = 65507
|
|
38
39
|
|
|
39
|
-
def __init__(self, addr, port, hops,bind_addr=
|
|
40
|
+
def __init__(self, addr: str, port: int, hops: int, bind_addr="0.0.0.0"):
|
|
40
41
|
self.addr = addr
|
|
41
42
|
self.port = port
|
|
42
43
|
self.hops = hops
|
|
@@ -52,14 +53,14 @@ class NetworkConnector(object):
|
|
|
52
53
|
def __connect(self):
|
|
53
54
|
logger.info("Connecting to %s:%s" % (self.addr, self.port))
|
|
54
55
|
|
|
55
|
-
self.__sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,socket.IPPROTO_UDP)
|
|
56
|
+
self.__sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
|
|
56
57
|
self.__sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
|
|
57
58
|
# #formac os ???
|
|
58
|
-
#self.__sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
59
|
+
# self.__sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
59
60
|
self.__sock.bind((self.bind_addr, self.port))
|
|
60
|
-
mreq = struct.pack("=4s4s",socket.inet_aton(self.addr),socket.inet_aton(self.bind_addr))
|
|
61
|
-
self.__sock.setsockopt(socket.IPPROTO_IP,socket.IP_ADD_MEMBERSHIP,mreq)
|
|
62
|
-
self.__sock.setsockopt(socket.IPPROTO_IP,socket.IP_MULTICAST_TTL,self.hops)
|
|
61
|
+
mreq = struct.pack("=4s4s", socket.inet_aton(self.addr), socket.inet_aton(self.bind_addr))
|
|
62
|
+
self.__sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
|
|
63
|
+
self.__sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, self.hops)
|
|
63
64
|
self.state = NetworkState.connected
|
|
64
65
|
|
|
65
66
|
def disconnect(self):
|
|
@@ -70,31 +71,33 @@ class NetworkConnector(object):
|
|
|
70
71
|
def is_connected(self):
|
|
71
72
|
return self.state == NetworkState.connected
|
|
72
73
|
|
|
73
|
-
def receive(self):
|
|
74
|
+
def receive(self) -> bytes:
|
|
74
75
|
packt = self.__sock.recv(self.UDP_MAX_SIZE)
|
|
75
76
|
return packt
|
|
76
77
|
|
|
77
|
-
def __get_data(self):
|
|
78
|
-
r = select.select([self.__sock,
|
|
78
|
+
def __get_data(self) -> Optional[bytes]:
|
|
79
|
+
r = select.select([self.__sock,], [], [], 0.02)
|
|
79
80
|
if r[0]:
|
|
80
81
|
return self.receive()
|
|
81
82
|
return None
|
|
82
83
|
|
|
83
|
-
def get_data(self):
|
|
84
|
-
if not self.is_connected():
|
|
84
|
+
def get_data(self) -> Optional[bytes]:
|
|
85
|
+
if not self.is_connected():
|
|
86
|
+
self.connect()
|
|
85
87
|
try:
|
|
86
88
|
return self.__get_data()
|
|
87
89
|
except Exception as e:
|
|
88
90
|
self.network_error(e)
|
|
89
91
|
|
|
90
|
-
def send(self,data):
|
|
91
|
-
if not self.is_connected():
|
|
92
|
+
def send(self, data: bytes):
|
|
93
|
+
if not self.is_connected():
|
|
94
|
+
self.connect()
|
|
92
95
|
try:
|
|
93
96
|
self.__sock.sendto(data, (self.addr, self.port))
|
|
94
97
|
except Exception as e:
|
|
95
98
|
self.network_error(e)
|
|
96
99
|
|
|
97
|
-
def network_error(self,
|
|
100
|
+
def network_error(self, ex: Exception):
|
|
98
101
|
self.disconnect()
|
|
99
|
-
logger.info("Network error, reconnect..%s" %
|
|
102
|
+
logger.info("Network error, reconnect..%s" % ex.__str__())
|
|
100
103
|
time.sleep(5)
|