whad-lab 0.1.0__tar.gz
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.
- whad_lab-0.1.0/PKG-INFO +20 -0
- whad_lab-0.1.0/README.rst +4 -0
- whad_lab-0.1.0/pyproject.toml +35 -0
- whad_lab-0.1.0/setup.cfg +4 -0
- whad_lab-0.1.0/whad_lab.egg-info/PKG-INFO +20 -0
- whad_lab-0.1.0/whad_lab.egg-info/SOURCES.txt +19 -0
- whad_lab-0.1.0/whad_lab.egg-info/dependency_links.txt +1 -0
- whad_lab-0.1.0/whad_lab.egg-info/entry_points.txt +2 -0
- whad_lab-0.1.0/whad_lab.egg-info/requires.txt +4 -0
- whad_lab-0.1.0/whad_lab.egg-info/top_level.txt +1 -0
- whad_lab-0.1.0/wlab/arena.py +381 -0
- whad_lab-0.1.0/wlab/assets/tracker-profile.json +1 -0
- whad_lab-0.1.0/wlab/assets/tracker.png +0 -0
- whad_lab-0.1.0/wlab/exercises/ble_tracker/device.py +234 -0
- whad_lab-0.1.0/wlab/exercises/ble_tracker/lab.yaml +45 -0
- whad_lab-0.1.0/wlab/progress.py +322 -0
- whad_lab-0.1.0/wlab/server.py +539 -0
- whad_lab-0.1.0/wlab/static/index.html +593 -0
- whad_lab-0.1.0/wlab/static/landing.html +220 -0
- whad_lab-0.1.0/wlab/static/marked.min.js +69 -0
- whad_lab-0.1.0/wlab/tracker_v2.py +480 -0
whad_lab-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: whad-lab
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: WHAD Lab environment for hands-on exercises (Experimental)
|
|
5
|
+
Author-email: Damien Cauquil <virtualabs@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: whad,lab,hands-on,exercise,learn
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Topic :: Security
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: websockets>=13
|
|
18
|
+
Requires-Dist: pyyaml>=6
|
|
19
|
+
Requires-Dist: whad>=1.2
|
|
20
|
+
Requires-Dist: scapy>=2.5
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
|
|
4
|
+
[project]
|
|
5
|
+
name = "whad-lab"
|
|
6
|
+
version = "0.1.0"
|
|
7
|
+
description = "WHAD Lab environment for hands-on exercises (Experimental)"
|
|
8
|
+
requires-python = ">=3.10"
|
|
9
|
+
dependencies = [
|
|
10
|
+
"websockets>=13",
|
|
11
|
+
"pyyaml>=6",
|
|
12
|
+
"whad>=1.2",
|
|
13
|
+
"scapy>=2.5",
|
|
14
|
+
]
|
|
15
|
+
readme = "README.md"
|
|
16
|
+
license = "MIT"
|
|
17
|
+
keywords = ["whad", "lab", "hands-on", "exercise", "learn"]
|
|
18
|
+
classifiers = [
|
|
19
|
+
"Development Status :: 3 - Alpha",
|
|
20
|
+
"Environment :: Console",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Topic :: Security",
|
|
26
|
+
]
|
|
27
|
+
authors = [
|
|
28
|
+
{name = "Damien Cauquil", email = "virtualabs@gmail.com"},
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.scripts]
|
|
32
|
+
wlab = "wlab.server:cli"
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.package-data]
|
|
35
|
+
wlab = ['assets/*', 'static/*', 'exercises/*', 'exercises/ble_tracker/*']
|
whad_lab-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: whad-lab
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: WHAD Lab environment for hands-on exercises (Experimental)
|
|
5
|
+
Author-email: Damien Cauquil <virtualabs@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: whad,lab,hands-on,exercise,learn
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Topic :: Security
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: websockets>=13
|
|
18
|
+
Requires-Dist: pyyaml>=6
|
|
19
|
+
Requires-Dist: whad>=1.2
|
|
20
|
+
Requires-Dist: scapy>=2.5
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
README.rst
|
|
2
|
+
pyproject.toml
|
|
3
|
+
whad_lab.egg-info/PKG-INFO
|
|
4
|
+
whad_lab.egg-info/SOURCES.txt
|
|
5
|
+
whad_lab.egg-info/dependency_links.txt
|
|
6
|
+
whad_lab.egg-info/entry_points.txt
|
|
7
|
+
whad_lab.egg-info/requires.txt
|
|
8
|
+
whad_lab.egg-info/top_level.txt
|
|
9
|
+
wlab/arena.py
|
|
10
|
+
wlab/progress.py
|
|
11
|
+
wlab/server.py
|
|
12
|
+
wlab/tracker_v2.py
|
|
13
|
+
wlab/assets/tracker-profile.json
|
|
14
|
+
wlab/assets/tracker.png
|
|
15
|
+
wlab/exercises/ble_tracker/device.py
|
|
16
|
+
wlab/exercises/ble_tracker/lab.yaml
|
|
17
|
+
wlab/static/index.html
|
|
18
|
+
wlab/static/landing.html
|
|
19
|
+
wlab/static/marked.min.js
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
wlab
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
"""
|
|
2
|
+
WHAD wireless arena
|
|
3
|
+
|
|
4
|
+
This arena can host multiple nodes and ensures communication between them
|
|
5
|
+
as it would be the case in a real wireless environment. The implementation
|
|
6
|
+
of nodes and the messages they exchange is entirely flexible and can be
|
|
7
|
+
defined to fit any scenario.
|
|
8
|
+
|
|
9
|
+
Combined with a custom control node and a specific PHY layer, it is possible
|
|
10
|
+
to equip any node with a protocol stack and make it interact with a control
|
|
11
|
+
node, a specific node acting as an interface between the arena and its nodes
|
|
12
|
+
and the outside world.
|
|
13
|
+
"""
|
|
14
|
+
from threading import Thread
|
|
15
|
+
from typing import Optional, Any, Union, Generator, Tuple, List
|
|
16
|
+
from queue import Queue, Empty
|
|
17
|
+
from scapy.packet import Packet
|
|
18
|
+
|
|
19
|
+
class NodeMessage:
|
|
20
|
+
"""Root class for all node messages.
|
|
21
|
+
|
|
22
|
+
This class shall be used to define any message designed to be exchanged
|
|
23
|
+
between nodes in a wireless arena.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, from_id: str, to_id: Optional[str] = None):
|
|
27
|
+
"""Initialization of a node message.
|
|
28
|
+
|
|
29
|
+
This message keeps track of the originating node and optionally of
|
|
30
|
+
its destination node.
|
|
31
|
+
|
|
32
|
+
:param from_id: Identifier of the origin node
|
|
33
|
+
:type from_id: str
|
|
34
|
+
:param to_id: Identifier of the destination node
|
|
35
|
+
:type to_id: str, optional
|
|
36
|
+
"""
|
|
37
|
+
self.__origin = from_id
|
|
38
|
+
self.__dest = to_id
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def origin(self) -> str:
|
|
42
|
+
"""Originating node's identifier."""
|
|
43
|
+
return self.__origin
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def destination(self) -> Optional[str]:
|
|
47
|
+
"""Destination node's identifier."""
|
|
48
|
+
return self.__dest
|
|
49
|
+
|
|
50
|
+
class MetadataMsg(NodeMessage):
|
|
51
|
+
"""This node message class holds a set of metadata in the form
|
|
52
|
+
of a set of parameters. It provides a very flexible way to store
|
|
53
|
+
and access metadata.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def __init__(self, from_id: str, to_id: str, **parameters):
|
|
57
|
+
"""Initialization.
|
|
58
|
+
|
|
59
|
+
:param from_id: Identifier of the origin node
|
|
60
|
+
:type from_id: str
|
|
61
|
+
:param to_id: Identifier of the destination node
|
|
62
|
+
:type to_id: str, optional
|
|
63
|
+
:param **parameters: set of parameters to attach to the message as metadata
|
|
64
|
+
:type **parameters: dict
|
|
65
|
+
"""
|
|
66
|
+
super().__init__(from_id, to_id)
|
|
67
|
+
self.__parameters = parameters
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def parameters(self) -> dict:
|
|
71
|
+
"""Set of parameters as dict.
|
|
72
|
+
|
|
73
|
+
:return: Dictionary of parameters (read-only)
|
|
74
|
+
:rtype: dict
|
|
75
|
+
"""
|
|
76
|
+
return self.__parameters
|
|
77
|
+
|
|
78
|
+
def __getitem__(self, key: str) -> Any:
|
|
79
|
+
"""Parameters read primitives, implementing an array behavior."""
|
|
80
|
+
if key in self.__parameters:
|
|
81
|
+
return self.__parameters[key]
|
|
82
|
+
raise IndexError
|
|
83
|
+
|
|
84
|
+
class ConnectionMsg(MetadataMsg):
|
|
85
|
+
"""Generic connection request message."""
|
|
86
|
+
|
|
87
|
+
class ConnectionResultMsg(MetadataMsg):
|
|
88
|
+
"""Generic connection response message."""
|
|
89
|
+
|
|
90
|
+
class DisconnectionMsg(MetadataMsg):
|
|
91
|
+
"""Generic disconnection request."""
|
|
92
|
+
|
|
93
|
+
class DisconnectionResultMsg(MetadataMsg):
|
|
94
|
+
"""Generic disconnection response."""
|
|
95
|
+
|
|
96
|
+
class DataMsg(MetadataMsg):
|
|
97
|
+
"""Generic data (PDU) message."""
|
|
98
|
+
|
|
99
|
+
def __init__(self, from_id: str, to_id: str, data: Packet):
|
|
100
|
+
super().__init__(from_id, to_id, data=data)
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def data(self) -> Packet:
|
|
104
|
+
return self['data']
|
|
105
|
+
|
|
106
|
+
class Arena:
|
|
107
|
+
"""Wireless arena where devices are placed."""
|
|
108
|
+
|
|
109
|
+
def __init__(self):
|
|
110
|
+
"""Initialize wireless arena."""
|
|
111
|
+
self.__nodes = {}
|
|
112
|
+
|
|
113
|
+
def send(self, message: NodeMessage, node_id: Optional[str]) -> bool:
|
|
114
|
+
"""Send message to one or more nodes in the arena.
|
|
115
|
+
|
|
116
|
+
:param message: Message to send to other nodes.
|
|
117
|
+
:type message: NodeMessage
|
|
118
|
+
:param node_id: Destination node's identifier (set to `None` to broadcast to other nodes)
|
|
119
|
+
:type node_id: str, optional
|
|
120
|
+
:return: `True` if message was successfully sent, `False` otherwise.
|
|
121
|
+
:rtype: bool
|
|
122
|
+
"""
|
|
123
|
+
if node_id is not None:
|
|
124
|
+
# Add message into the target node's message queue, if found
|
|
125
|
+
if node_id in self.__nodes:
|
|
126
|
+
self.__nodes[node_id].add_message(message)
|
|
127
|
+
return True
|
|
128
|
+
|
|
129
|
+
# Could not find node, cannot send.
|
|
130
|
+
return False
|
|
131
|
+
else:
|
|
132
|
+
# Broadcast message to other nodes
|
|
133
|
+
for node_id, node in self.__nodes.items():
|
|
134
|
+
if node_id != message.origin:
|
|
135
|
+
node.add_message(message)
|
|
136
|
+
return True
|
|
137
|
+
|
|
138
|
+
def find_node(self, node_id: str) -> Optional['Node']:
|
|
139
|
+
"""Find a node in the arena based on its identifier.
|
|
140
|
+
|
|
141
|
+
:param node_id: Node's identifier
|
|
142
|
+
:type node_id: str
|
|
143
|
+
|
|
144
|
+
:return: Corresponding node if found, `None` otherwise.
|
|
145
|
+
:rtype: Node, optional
|
|
146
|
+
"""
|
|
147
|
+
if node_id in self.__nodes:
|
|
148
|
+
return self.__nodes[node_id]
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
def add_node(self, node: 'Node') -> bool:
|
|
152
|
+
"""Add node into the arena.
|
|
153
|
+
|
|
154
|
+
:param node: Node to put inside the arena.
|
|
155
|
+
:type node: Node
|
|
156
|
+
:return: `True` on success, `False` otherwise.
|
|
157
|
+
:rtype: bool
|
|
158
|
+
"""
|
|
159
|
+
if node.id not in self.__nodes:
|
|
160
|
+
self.__nodes[node.id] = node
|
|
161
|
+
node.enter_arena(self)
|
|
162
|
+
return True
|
|
163
|
+
return False
|
|
164
|
+
|
|
165
|
+
def remove_node(self, node: Union[str, 'Node']) -> bool:
|
|
166
|
+
"""Remove a node from the arena.
|
|
167
|
+
|
|
168
|
+
:param node: Node to remove from the arena.
|
|
169
|
+
:type node: Node
|
|
170
|
+
:type node: str
|
|
171
|
+
:return: `True` on success, `False` otherwise.
|
|
172
|
+
:rtype: bool
|
|
173
|
+
"""
|
|
174
|
+
if isinstance(node, Node):
|
|
175
|
+
if node.id in self.__nodes:
|
|
176
|
+
self.__nodes[node.id].leave_arena()
|
|
177
|
+
del self.__nodes[node.id]
|
|
178
|
+
return True
|
|
179
|
+
elif isinstance(node, str):
|
|
180
|
+
if node in self.__nodes:
|
|
181
|
+
self.__nodes[node].leave_arena()
|
|
182
|
+
del self.__nodes[node]
|
|
183
|
+
return True
|
|
184
|
+
return False
|
|
185
|
+
|
|
186
|
+
def enable_node(self, node: Union[str, 'Node']) -> bool:
|
|
187
|
+
"""Enable a node present in the arena.
|
|
188
|
+
|
|
189
|
+
:param node: Node to enable
|
|
190
|
+
:type node: Node
|
|
191
|
+
:type node: str
|
|
192
|
+
|
|
193
|
+
:return: `True` if node has been enabled, `False` otherwise.
|
|
194
|
+
:rtype: bool
|
|
195
|
+
"""
|
|
196
|
+
if isinstance(node, str):
|
|
197
|
+
n = self.find_node(node)
|
|
198
|
+
else:
|
|
199
|
+
n = node
|
|
200
|
+
|
|
201
|
+
# Node found, enable.
|
|
202
|
+
if n is not None:
|
|
203
|
+
n.enable()
|
|
204
|
+
return True
|
|
205
|
+
|
|
206
|
+
# Failure.
|
|
207
|
+
return False
|
|
208
|
+
|
|
209
|
+
def disable_node(self, node: Union[str, 'Node']) -> bool:
|
|
210
|
+
"""Disable a node present in the arena.
|
|
211
|
+
|
|
212
|
+
:param node: Node to enable
|
|
213
|
+
:type node: Node
|
|
214
|
+
:type node: str
|
|
215
|
+
|
|
216
|
+
:return: `True` if node has been disabled, `False` otherwise.
|
|
217
|
+
:rtype: bool
|
|
218
|
+
"""
|
|
219
|
+
if isinstance(node, str):
|
|
220
|
+
n = self.find_node(node)
|
|
221
|
+
else:
|
|
222
|
+
n = node
|
|
223
|
+
|
|
224
|
+
# Node found, enable.
|
|
225
|
+
if n is not None:
|
|
226
|
+
n.disable()
|
|
227
|
+
return True
|
|
228
|
+
|
|
229
|
+
# Failure.
|
|
230
|
+
return False
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def list_all_nodes(self) -> Generator[Tuple[str, 'Node'], None, None]:
|
|
234
|
+
"""Enumerate all nodes (active and inactive)."""
|
|
235
|
+
yield from self.__nodes.items()
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def list_active_nodes(self) -> Generator[Tuple[str, 'Node'], None, None]:
|
|
239
|
+
"""Enumerate only active nodes."""
|
|
240
|
+
for node_id,node in self.__nodes.items():
|
|
241
|
+
if node.active:
|
|
242
|
+
yield (node_id, node)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
class Node:
|
|
246
|
+
"""Wireless device present in the arena.
|
|
247
|
+
|
|
248
|
+
Each device has its own thread to manage its state at its own
|
|
249
|
+
pace. Communication with a node is done through message queues,
|
|
250
|
+
as generic WHAD devices do.
|
|
251
|
+
|
|
252
|
+
The arena acts as an arbiter for nodes and dispatch messages
|
|
253
|
+
between nodes.
|
|
254
|
+
"""
|
|
255
|
+
|
|
256
|
+
def __init__(self, node_id: str):
|
|
257
|
+
"""Initialize a node with its own identifier. A node is not
|
|
258
|
+
placed into a specific arena when created.
|
|
259
|
+
|
|
260
|
+
:param node_id: Node identifier
|
|
261
|
+
:type node_id: str
|
|
262
|
+
"""
|
|
263
|
+
self.__id = node_id
|
|
264
|
+
self.__arena: Optional[Arena] = None
|
|
265
|
+
self.__msg_queue = Queue()
|
|
266
|
+
self.__active = True
|
|
267
|
+
|
|
268
|
+
@property
|
|
269
|
+
def id(self) -> str:
|
|
270
|
+
"""Node identifier"""
|
|
271
|
+
return self.__id
|
|
272
|
+
|
|
273
|
+
@property
|
|
274
|
+
def arena(self) -> Optional[Arena]:
|
|
275
|
+
"""Current arena."""
|
|
276
|
+
return self.__arena
|
|
277
|
+
|
|
278
|
+
@property
|
|
279
|
+
def active(self) -> bool:
|
|
280
|
+
"""Node active state"""
|
|
281
|
+
return self.__active
|
|
282
|
+
|
|
283
|
+
def disable(self):
|
|
284
|
+
"""Disable node."""
|
|
285
|
+
self.__active = False
|
|
286
|
+
|
|
287
|
+
def enable(self):
|
|
288
|
+
"""Enable node."""
|
|
289
|
+
self.__active = True
|
|
290
|
+
|
|
291
|
+
def add_message(self, message: NodeMessage):
|
|
292
|
+
"""Add a node message into our queue.
|
|
293
|
+
|
|
294
|
+
:param message: Message to add into the node's message queue.
|
|
295
|
+
:type message: NodeMessage
|
|
296
|
+
"""
|
|
297
|
+
self.__msg_queue.put(message)
|
|
298
|
+
|
|
299
|
+
def get_message(self, timeout: Optional[float] = None) -> Optional[NodeMessage]:
|
|
300
|
+
"""Retrieve a message from the message queue.
|
|
301
|
+
This method blocks until a message is available in
|
|
302
|
+
the queue.
|
|
303
|
+
|
|
304
|
+
:param timeout: Timeout in seconds
|
|
305
|
+
:type timeout: float, optional
|
|
306
|
+
|
|
307
|
+
:return: Node message to process if any, `None` otherwise.
|
|
308
|
+
:rtype: NodeMessage, optional
|
|
309
|
+
"""
|
|
310
|
+
if timeout is None:
|
|
311
|
+
return self.__msg_queue.get()
|
|
312
|
+
else:
|
|
313
|
+
try:
|
|
314
|
+
return self.__msg_queue.get(timeout=timeout)
|
|
315
|
+
except Empty:
|
|
316
|
+
return None
|
|
317
|
+
|
|
318
|
+
def enter_arena(self, arena: Arena):
|
|
319
|
+
"""Set the node's arena.
|
|
320
|
+
|
|
321
|
+
:param arena: Arena where the node is placed.
|
|
322
|
+
:type arena: Arena
|
|
323
|
+
"""
|
|
324
|
+
self.__arena = arena
|
|
325
|
+
|
|
326
|
+
def leave_arena(self):
|
|
327
|
+
"""Clear the node's arena."""
|
|
328
|
+
self.__arena = None
|
|
329
|
+
|
|
330
|
+
def send(self, message: NodeMessage, node_id: Optional[str] = None) -> bool:
|
|
331
|
+
"""Send a packet to a node identified by `node_id`.
|
|
332
|
+
|
|
333
|
+
:param message: Message to send to other node(s).
|
|
334
|
+
:type message: NodeMessage
|
|
335
|
+
:param node_id: Destination node's identifier
|
|
336
|
+
:type node_id: str, optional
|
|
337
|
+
|
|
338
|
+
:return: `True` if message has been sent, `False` otherwise
|
|
339
|
+
:rtype: bool
|
|
340
|
+
"""
|
|
341
|
+
if self.__arena is not None:
|
|
342
|
+
return self.__arena.send(message, node_id)
|
|
343
|
+
return False
|
|
344
|
+
|
|
345
|
+
class DeviceNode(Node, Thread):
|
|
346
|
+
"""This class defines a node that has its own thread. This configuration
|
|
347
|
+
helps when a node needs to have its own protocol stack, like an emulated
|
|
348
|
+
device.
|
|
349
|
+
"""
|
|
350
|
+
def __init__(self, node_id: str):
|
|
351
|
+
"""Initialization.
|
|
352
|
+
|
|
353
|
+
:param node_id: Node identifier
|
|
354
|
+
:type node_id: str
|
|
355
|
+
"""
|
|
356
|
+
Node.__init__(self, node_id)
|
|
357
|
+
Thread.__init__(self)
|
|
358
|
+
|
|
359
|
+
class ControlNode(Node):
|
|
360
|
+
"""This class defines a control node, a node that is driven by a component
|
|
361
|
+
outside the arena and that interacts with other nodes inside the arena. It
|
|
362
|
+
acts like a bridge and has access to the whole arena.
|
|
363
|
+
|
|
364
|
+
It shares the same communication mechanism all nodes in the arena use, is able
|
|
365
|
+
to send and receive messages, but has control on other nodes as well.
|
|
366
|
+
"""
|
|
367
|
+
|
|
368
|
+
def __init__(self, node_id: str):
|
|
369
|
+
"""Initialize our control node."""
|
|
370
|
+
super().__init__(node_id)
|
|
371
|
+
|
|
372
|
+
def list_all_nodes(self) -> List[Node]:
|
|
373
|
+
"""Return a list of all nodes present in the arena.
|
|
374
|
+
|
|
375
|
+
:return: List of all nodes.
|
|
376
|
+
:rtype: list
|
|
377
|
+
"""
|
|
378
|
+
if self.arena is not None:
|
|
379
|
+
return [n[1] for n in list(self.arena.list_all_nodes())]
|
|
380
|
+
return []
|
|
381
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"services": [{"uuid": "1800", "type_uuid": "2800", "start_handle": 1, "end_handle": 5, "characteristics": [{"handle": 2, "uuid": "2803", "properties": 18, "security": 0, "value": {"handle": 3, "uuid": "2A00", "data": "69544147202020202020202020202020"}, "descriptors": []}, {"handle": 4, "uuid": "2803", "properties": 2, "security": 0, "value": {"handle": 5, "uuid": "2A01", "data": "0000"}, "descriptors": []}]}, {"uuid": "180F", "type_uuid": "2800", "start_handle": 6, "end_handle": 8, "characteristics": [{"handle": 7, "uuid": "2803", "properties": 18, "security": 0, "value": {"handle": 8, "uuid": "2A19", "data": "63"}, "descriptors": []}]}, {"uuid": "1802", "type_uuid": "2800", "start_handle": 9, "end_handle": 11, "characteristics": [{"handle": 10, "uuid": "2803", "properties": 28, "security": 0, "value": {"handle": 11, "uuid": "2A06", "data": ""}, "descriptors": []}]}, {"uuid": "FFE0", "type_uuid": "2800", "start_handle": 12, "end_handle": 14, "characteristics": [{"handle": 13, "uuid": "2803", "properties": 18, "security": 0, "value": {"handle": 14, "uuid": "FFE1", "data": "00"}, "descriptors": []}]}], "devinfo": {"adv_data": "020105020a000319c1030302e0ff", "bd_addr": "ff:ff:30:03:70:72", "addr_type": 0, "scan_rsp": "110969544147202020202020202020202020"}}
|
|
Binary file
|