remoteRF-server 1.0.4__tar.gz → 1.0.6__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.
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/PKG-INFO +11 -28
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/README.md +10 -27
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/pyproject.toml +1 -1
- remoterf_server-1.0.6/src/remoteRF_server/drivers/usrp/policy.py +41 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/drivers/usrp/uhd_manifest.py +8 -25
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/drivers/usrp/usrp_schema.py +10 -3
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/server/dynamic_v2.py +4 -34
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/serverrf_cli.py +5 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server.egg-info/PKG-INFO +11 -28
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/tests/test_dynamic_v2.py +54 -77
- remoterf_server-1.0.4/src/remoteRF_server/drivers/usrp/policy.py +0 -363
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/setup.cfg +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/__init__.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/common/__init__.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/common/grpc/__init__.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/common/grpc/grpc_host_pb2.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/common/grpc/grpc_host_pb2_grpc.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/common/grpc/grpc_pb2.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/common/grpc/grpc_pb2_grpc.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/common/grpc/v2_codec.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/common/idl/__init__.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/common/idl/device_schema.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/common/idl/pluto_mimo_schema.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/common/idl/pluto_schema.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/common/idl/schema.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/common/idl/v2.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/common/utils/__init__.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/common/utils/ansi_codes.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/common/utils/api_token.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/common/utils/db_connection.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/common/utils/db_location.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/common/utils/list_string.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/common/utils/process_arg.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/drivers/__init__.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/drivers/adalm_pluto/__init__.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/drivers/adalm_pluto/pluto_mimo_schema.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/drivers/adalm_pluto/pluto_remote_server.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/drivers/adalm_pluto/pluto_schema.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/drivers/usrp/__init__.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/host/__init__.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/host/host_auth_token.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/host/host_directory_store.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/host/host_tunnel_server.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/server/__init__.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/server/acc_perms.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/server/cert_provider.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/server/device_manager.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/server/grpc_server.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/server/reservation.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/server/rpc_manager.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/server/user_group_cli.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/server/user_group_handler.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/server/v2_errors.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/tools/__init__.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/tools/gen_certs.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/tools/gist_status.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/tools/gist_status_testing.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/tools/introspect_uhd.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server.egg-info/SOURCES.txt +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server.egg-info/dependency_links.txt +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server.egg-info/entry_points.txt +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server.egg-info/requires.txt +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server.egg-info/top_level.txt +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/tests/test_cert_provider.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/tests/test_default_usrp_schema.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/tests/test_idl_schema.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/tests/test_pluto_compat.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/tests/test_process_arg.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/tests/test_reservation_timestamps.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/tests/test_rpc_manager_calln.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/tests/test_usrp_client_server_hardware.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/tests/test_usrp_client_server_localhost.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/tests/test_usrp_remoterf_rx_hardware.py +0 -0
- {remoterf_server-1.0.4 → remoterf_server-1.0.6}/tests/test_usrp_uhd410_hardware.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: remoteRF-server
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.6
|
|
4
4
|
Summary: RemoteRF server-side control package
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -329,35 +329,18 @@ devices:
|
|
|
329
329
|
serial: 31A2B3C
|
|
330
330
|
```
|
|
331
331
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
332
|
+
All USRP operations exposed by the Dynamic v2 schema are available without a
|
|
333
|
+
separate policy file. This includes transmit controls, transmit streaming,
|
|
334
|
+
GPIO/register methods, and property-tree writes. `usrp_policy.yml` is not read
|
|
335
|
+
or required by the current server.
|
|
336
336
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
337
|
+
RemoteRF sets `UHD_LOG_CONSOLE_LEVEL=off` before importing UHD so native UHD
|
|
338
|
+
messages do not overwrite the interactive `serverrf` prompt. To temporarily
|
|
339
|
+
restore UHD diagnostics, set a level such as `warning`, `info`, or `debug`
|
|
340
|
+
before starting the server:
|
|
341
341
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
tx_enabled: true
|
|
345
|
-
tx_frequency_ranges_hz:
|
|
346
|
-
- [902000000, 928000000]
|
|
347
|
-
max_tx_gain_db: 20
|
|
348
|
-
max_tx_normalized_gain: 0.5
|
|
349
|
-
max_tx_power_dbm: -10
|
|
350
|
-
max_tx_bandwidth_hz: 2000000
|
|
351
|
-
max_tx_sample_rate_sps: 2000000
|
|
352
|
-
allowed_tx_antennas: [TX/RX]
|
|
353
|
-
max_burst_seconds: 1.0
|
|
354
|
-
max_duty_cycle: 0.1
|
|
355
|
-
duty_cycle_window_seconds: 60
|
|
356
|
-
low_level_write_enabled: false
|
|
357
|
-
low_level_write_methods: []
|
|
358
|
-
gpio_write_masks: {}
|
|
359
|
-
property_write_paths: []
|
|
360
|
-
register_write_addresses: []
|
|
342
|
+
```bash
|
|
343
|
+
UHD_LOG_CONSOLE_LEVEL=warning serverrf -s
|
|
361
344
|
```
|
|
362
345
|
|
|
363
346
|
On every qualified target, generate the introspection record and enforce the
|
|
@@ -314,35 +314,18 @@ devices:
|
|
|
314
314
|
serial: 31A2B3C
|
|
315
315
|
```
|
|
316
316
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
317
|
+
All USRP operations exposed by the Dynamic v2 schema are available without a
|
|
318
|
+
separate policy file. This includes transmit controls, transmit streaming,
|
|
319
|
+
GPIO/register methods, and property-tree writes. `usrp_policy.yml` is not read
|
|
320
|
+
or required by the current server.
|
|
321
321
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
322
|
+
RemoteRF sets `UHD_LOG_CONSOLE_LEVEL=off` before importing UHD so native UHD
|
|
323
|
+
messages do not overwrite the interactive `serverrf` prompt. To temporarily
|
|
324
|
+
restore UHD diagnostics, set a level such as `warning`, `info`, or `debug`
|
|
325
|
+
before starting the server:
|
|
326
326
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
tx_enabled: true
|
|
330
|
-
tx_frequency_ranges_hz:
|
|
331
|
-
- [902000000, 928000000]
|
|
332
|
-
max_tx_gain_db: 20
|
|
333
|
-
max_tx_normalized_gain: 0.5
|
|
334
|
-
max_tx_power_dbm: -10
|
|
335
|
-
max_tx_bandwidth_hz: 2000000
|
|
336
|
-
max_tx_sample_rate_sps: 2000000
|
|
337
|
-
allowed_tx_antennas: [TX/RX]
|
|
338
|
-
max_burst_seconds: 1.0
|
|
339
|
-
max_duty_cycle: 0.1
|
|
340
|
-
duty_cycle_window_seconds: 60
|
|
341
|
-
low_level_write_enabled: false
|
|
342
|
-
low_level_write_methods: []
|
|
343
|
-
gpio_write_masks: {}
|
|
344
|
-
property_write_paths: []
|
|
345
|
-
register_write_addresses: []
|
|
327
|
+
```bash
|
|
328
|
+
UHD_LOG_CONSOLE_LEVEL=warning serverrf -s
|
|
346
329
|
```
|
|
347
330
|
|
|
348
331
|
On every qualified target, generate the introspection record and enforce the
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Compatibility shim for unrestricted USRP access.
|
|
2
|
+
|
|
3
|
+
USRP policy enforcement is currently disabled. ``USRPPolicy`` remains importable
|
|
4
|
+
so older integrations do not fail at import time, but every supported control,
|
|
5
|
+
streaming, property-tree, GPIO, and register operation passes through without
|
|
6
|
+
policy checks.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Mapping
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class USRPPolicy:
|
|
15
|
+
"""No-op policy retained for protocol and integration compatibility."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, config: Mapping[str, Any] | None = None, *, source: str = ""):
|
|
18
|
+
self.config = dict(config or {})
|
|
19
|
+
self.source = source
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def load(cls, *, gid: int, serial: str = "", path: Path | None = None) -> "USRPPolicy":
|
|
23
|
+
del gid, serial, path
|
|
24
|
+
return cls()
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def tx_enabled(self) -> bool:
|
|
28
|
+
return True
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def restrictions(self) -> dict[str, Any]:
|
|
32
|
+
return {}
|
|
33
|
+
|
|
34
|
+
def check_control(self, method: str, args: Mapping[str, Any]) -> None:
|
|
35
|
+
del method, args
|
|
36
|
+
|
|
37
|
+
def check_tx_duration(self, duration: float) -> None:
|
|
38
|
+
del duration
|
|
39
|
+
|
|
40
|
+
def check_handle_call(self, type_id: str, method: str, args: Mapping[str, Any]) -> None:
|
|
41
|
+
del type_id, method, args
|
{remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/drivers/usrp/uhd_manifest.py
RENAMED
|
@@ -135,11 +135,6 @@ HANDLE_METHOD_RETURN_TYPES = {
|
|
|
135
135
|
("uhd.property.dboard_iface", "get_desired"): "uhd.usrp.dboard_iface",
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
LOW_LEVEL_WRITES = {
|
|
139
|
-
"set_gpio_attr", "set_gpio_src", "set_user_register",
|
|
140
|
-
"set_rx_filter", "set_tx_filter",
|
|
141
|
-
}
|
|
142
|
-
|
|
143
138
|
USRP2901_UNSUPPORTED_HANDLE_TYPES = {
|
|
144
139
|
"uhd.usrp.radio_control",
|
|
145
140
|
"uhd.usrp.mboard_controller",
|
|
@@ -1305,7 +1300,7 @@ def _objects() -> list[dict]:
|
|
|
1305
1300
|
"uhd.property_tree",
|
|
1306
1301
|
kind="remote_handle",
|
|
1307
1302
|
mutability="remote_handle",
|
|
1308
|
-
ownership={"scope": "session", "default_access": "
|
|
1303
|
+
ownership={"scope": "session", "default_access": "read_write"},
|
|
1309
1304
|
),
|
|
1310
1305
|
*[
|
|
1311
1306
|
object_type(
|
|
@@ -1315,7 +1310,7 @@ def _objects() -> list[dict]:
|
|
|
1315
1310
|
ownership={
|
|
1316
1311
|
"scope": "session",
|
|
1317
1312
|
"parent": "uhd.property_tree",
|
|
1318
|
-
"default_access": "
|
|
1313
|
+
"default_access": "read_write",
|
|
1319
1314
|
},
|
|
1320
1315
|
)
|
|
1321
1316
|
for suffix in (
|
|
@@ -1331,25 +1326,25 @@ def _objects() -> list[dict]:
|
|
|
1331
1326
|
"uhd.usrp.radio_control",
|
|
1332
1327
|
kind="remote_handle",
|
|
1333
1328
|
mutability="remote_handle",
|
|
1334
|
-
ownership={"scope": "session", "default_access": "
|
|
1329
|
+
ownership={"scope": "session", "default_access": "read_write"},
|
|
1335
1330
|
),
|
|
1336
1331
|
object_type(
|
|
1337
1332
|
"uhd.usrp.mboard_controller",
|
|
1338
1333
|
kind="remote_handle",
|
|
1339
1334
|
mutability="remote_handle",
|
|
1340
|
-
ownership={"scope": "session", "default_access": "
|
|
1335
|
+
ownership={"scope": "session", "default_access": "read_write"},
|
|
1341
1336
|
),
|
|
1342
1337
|
object_type(
|
|
1343
1338
|
"uhd.usrp.dboard_iface",
|
|
1344
1339
|
kind="remote_handle",
|
|
1345
1340
|
mutability="remote_handle",
|
|
1346
|
-
ownership={"scope": "session", "default_access": "
|
|
1341
|
+
ownership={"scope": "session", "default_access": "read_write"},
|
|
1347
1342
|
),
|
|
1348
1343
|
object_type(
|
|
1349
1344
|
"uhd.usrp.user_settings_iface",
|
|
1350
1345
|
kind="remote_handle",
|
|
1351
1346
|
mutability="remote_handle",
|
|
1352
|
-
ownership={"scope": "session", "default_access": "
|
|
1347
|
+
ownership={"scope": "session", "default_access": "read_write"},
|
|
1353
1348
|
),
|
|
1354
1349
|
]
|
|
1355
1350
|
|
|
@@ -1415,7 +1410,6 @@ def _streamer_methods() -> list[dict]:
|
|
|
1415
1410
|
)
|
|
1416
1411
|
],
|
|
1417
1412
|
execution="sample_stream",
|
|
1418
|
-
policy="tx_samples",
|
|
1419
1413
|
),
|
|
1420
1414
|
method(
|
|
1421
1415
|
"recv_async_msg", "uhd.usrp.TXStreamer",
|
|
@@ -1514,7 +1508,6 @@ def _property_tree_methods() -> list[dict]:
|
|
|
1514
1508
|
)
|
|
1515
1509
|
],
|
|
1516
1510
|
execution="server_handle",
|
|
1517
|
-
policy="low_level_write",
|
|
1518
1511
|
)
|
|
1519
1512
|
)
|
|
1520
1513
|
return methods
|
|
@@ -1531,13 +1524,13 @@ CAPABILITY_FIELDS = (
|
|
|
1531
1524
|
"tx_bandwidth_ranges_per_channel", "master_clock_rate_range", "time_sources",
|
|
1532
1525
|
"clock_sources", "gpio_banks", "sensors", "supported_cpu_formats",
|
|
1533
1526
|
"supported_otw_formats", "maximum_remote_payload_bytes",
|
|
1534
|
-
"supported_streaming_protocol_versions",
|
|
1527
|
+
"supported_streaming_protocol_versions",
|
|
1535
1528
|
"remote_resource_limits", "advanced_interfaces",
|
|
1536
1529
|
)
|
|
1537
1530
|
|
|
1538
1531
|
ERRORS = (
|
|
1539
1532
|
"RemoteRFTransportError", "RemoteRFSessionExpiredError",
|
|
1540
|
-
"RemoteRFReservationError",
|
|
1533
|
+
"RemoteRFReservationError",
|
|
1541
1534
|
"RemoteRFStaleHandleError", "RemoteRFProtocolError",
|
|
1542
1535
|
"RemoteRFOverloadError",
|
|
1543
1536
|
"RemoteRFServerOverloadedError", "RemoteRFNativeUHDError",
|
|
@@ -1558,15 +1551,6 @@ def build_usrp_schema_v2(driver_version: str = "0.0.1") -> dict:
|
|
|
1558
1551
|
coverage: dict[str, str] = {}
|
|
1559
1552
|
for name in MULTIUSRP_METHODS:
|
|
1560
1553
|
execution = "server_optimized" if name in {"recv_num_samps", "send_waveform"} else "server_control"
|
|
1561
|
-
policy = None
|
|
1562
|
-
if (
|
|
1563
|
-
name.startswith("set_tx_")
|
|
1564
|
-
or name == "set_normalized_tx_gain"
|
|
1565
|
-
or name == "send_waveform"
|
|
1566
|
-
):
|
|
1567
|
-
policy = "tx_control"
|
|
1568
|
-
elif name in LOW_LEVEL_WRITES:
|
|
1569
|
-
policy = "low_level_write"
|
|
1570
1554
|
availability = {"runtime_member": name}
|
|
1571
1555
|
if name in OPTIONAL_METHODS:
|
|
1572
1556
|
availability["optional"] = True
|
|
@@ -1577,7 +1561,6 @@ def build_usrp_schema_v2(driver_version: str = "0.0.1") -> dict:
|
|
|
1577
1561
|
signatures.get(name, _fallback_overload()),
|
|
1578
1562
|
execution=execution,
|
|
1579
1563
|
availability=availability,
|
|
1580
|
-
policy=policy,
|
|
1581
1564
|
)
|
|
1582
1565
|
)
|
|
1583
1566
|
coverage[f"uhd.usrp.MultiUSRP.{name}"] = (
|
{remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/drivers/usrp/usrp_schema.py
RENAMED
|
@@ -6,6 +6,15 @@ from remoteRF_server.drivers.usrp.uhd_manifest import build_usrp_schema_v2
|
|
|
6
6
|
c = idl_client
|
|
7
7
|
|
|
8
8
|
import itertools
|
|
9
|
+
import os
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _configure_uhd_console_logging():
|
|
13
|
+
"""Keep native UHD logs out of the interactive server console by default."""
|
|
14
|
+
os.environ.setdefault("UHD_LOG_CONSOLE_LEVEL", "off")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
_configure_uhd_console_logging()
|
|
9
18
|
|
|
10
19
|
try:
|
|
11
20
|
import uhd
|
|
@@ -77,7 +86,6 @@ def _connect_usrp(args=None, serial=None, addr=None, **kwargs):
|
|
|
77
86
|
|
|
78
87
|
args = _device_args(args)
|
|
79
88
|
dev = uhd_module.usrp.MultiUSRP(args)
|
|
80
|
-
print(f"Connected to USRP with args={args!r}")
|
|
81
89
|
return dev
|
|
82
90
|
|
|
83
91
|
|
|
@@ -518,7 +526,7 @@ class UsrpSchema(DeviceSchema):
|
|
|
518
526
|
return str(value)
|
|
519
527
|
return ""
|
|
520
528
|
|
|
521
|
-
def get_capabilities_v2(self
|
|
529
|
+
def get_capabilities_v2(self):
|
|
522
530
|
device = self.device
|
|
523
531
|
if device is None:
|
|
524
532
|
raise RuntimeError("USRP session is not open")
|
|
@@ -615,7 +623,6 @@ class UsrpSchema(DeviceSchema):
|
|
|
615
623
|
},
|
|
616
624
|
"maximum_remote_payload_bytes": 4 * 1024 * 1024,
|
|
617
625
|
"supported_streaming_protocol_versions": ["1.0"],
|
|
618
|
-
"policy_restrictions": dict(policy_restrictions or {}),
|
|
619
626
|
}
|
|
620
627
|
|
|
621
628
|
def _streamers(self):
|
|
@@ -23,6 +23,8 @@ from ..common.idl.v2 import (
|
|
|
23
23
|
SCHEMA_VERSION,
|
|
24
24
|
STREAMING_PROTOCOL_VERSION,
|
|
25
25
|
)
|
|
26
|
+
# Retained as a module attribute for integrations that patched the former
|
|
27
|
+
# policy loader. Runtime access is no longer gated by it.
|
|
26
28
|
from ..drivers.usrp.policy import USRPPolicy
|
|
27
29
|
from ..drivers.usrp.uhd_manifest import (
|
|
28
30
|
HANDLE_METHOD_RETURN_TYPES,
|
|
@@ -39,7 +41,6 @@ from .device_manager import (
|
|
|
39
41
|
from .v2_errors import (
|
|
40
42
|
RemoteRFNativeUHDError,
|
|
41
43
|
RemoteRFOverloadError,
|
|
42
|
-
RemoteRFPolicyError,
|
|
43
44
|
RemoteRFProtocolError,
|
|
44
45
|
RemoteRFReservationError,
|
|
45
46
|
RemoteRFServerOverloadedError,
|
|
@@ -466,7 +467,6 @@ class DeviceSession:
|
|
|
466
467
|
gid: int
|
|
467
468
|
schema: Any
|
|
468
469
|
schema_v2: dict
|
|
469
|
-
policy: USRPPolicy
|
|
470
470
|
root_handle: str
|
|
471
471
|
capabilities: dict
|
|
472
472
|
handles: dict[str, HandleEntry] = field(default_factory=dict)
|
|
@@ -569,10 +569,7 @@ class SessionManager:
|
|
|
569
569
|
"the USRP is already open through a legacy v1 session"
|
|
570
570
|
)
|
|
571
571
|
live_schema = create()
|
|
572
|
-
|
|
573
|
-
capabilities = live_schema.get_capabilities_v2(
|
|
574
|
-
policy_restrictions=policy.restrictions
|
|
575
|
-
)
|
|
572
|
+
capabilities = live_schema.get_capabilities_v2()
|
|
576
573
|
capabilities["remote_resource_limits"] = {
|
|
577
574
|
"maximum_sessions": _MAX_SESSIONS,
|
|
578
575
|
"maximum_handles_per_session": _MAX_HANDLES_PER_SESSION,
|
|
@@ -601,7 +598,6 @@ class SessionManager:
|
|
|
601
598
|
gid=gid,
|
|
602
599
|
schema=live_schema,
|
|
603
600
|
schema_v2=schema_v2,
|
|
604
|
-
policy=policy,
|
|
605
601
|
root_handle=root_handle,
|
|
606
602
|
capabilities=capabilities,
|
|
607
603
|
)
|
|
@@ -865,10 +861,6 @@ class SessionManager:
|
|
|
865
861
|
args,
|
|
866
862
|
positional_args,
|
|
867
863
|
)
|
|
868
|
-
policy_args = dict(args)
|
|
869
|
-
if positional_args:
|
|
870
|
-
policy_args["__args__"] = positional_args
|
|
871
|
-
session.policy.check_control(method_name, policy_args)
|
|
872
864
|
native_args = {
|
|
873
865
|
key: self._resolve_handle_argument(session, value)
|
|
874
866
|
for key, value in args.items()
|
|
@@ -884,7 +876,7 @@ class SessionManager:
|
|
|
884
876
|
native_args,
|
|
885
877
|
positional_args=native_positional,
|
|
886
878
|
)
|
|
887
|
-
except
|
|
879
|
+
except RemoteRFProtocolError:
|
|
888
880
|
raise
|
|
889
881
|
except Exception as exc:
|
|
890
882
|
raise RemoteRFNativeUHDError(
|
|
@@ -953,15 +945,6 @@ class SessionManager:
|
|
|
953
945
|
except queue.Empty:
|
|
954
946
|
return (False if mutate_metadata else None), mutations
|
|
955
947
|
|
|
956
|
-
if not entry.type_id.endswith(("RXStreamer", "TXStreamer")):
|
|
957
|
-
policy_args = dict(args)
|
|
958
|
-
if positional_args:
|
|
959
|
-
policy_args["__args__"] = positional_args
|
|
960
|
-
session.policy.check_handle_call(
|
|
961
|
-
entry.type_id,
|
|
962
|
-
method_name,
|
|
963
|
-
policy_args,
|
|
964
|
-
)
|
|
965
948
|
target = getattr(entry.value, method_name, None)
|
|
966
949
|
if not callable(target):
|
|
967
950
|
raise RemoteRFProtocolError(
|
|
@@ -1413,19 +1396,6 @@ class SampleDataV1Servicer(grpc_pb2_grpc.SampleDataV1Servicer):
|
|
|
1413
1396
|
):
|
|
1414
1397
|
if not entry.type_id.endswith("TXStreamer"):
|
|
1415
1398
|
raise RemoteRFStaleHandleError("TX data used a non-TX handle")
|
|
1416
|
-
channels = entry.stream_channels or [0]
|
|
1417
|
-
rates = []
|
|
1418
|
-
get_rate = getattr(session.schema.device, "get_tx_rate", None)
|
|
1419
|
-
if callable(get_rate):
|
|
1420
|
-
for channel in channels:
|
|
1421
|
-
rates.append(float(get_rate(channel)))
|
|
1422
|
-
if not rates or min(rates) <= 0:
|
|
1423
|
-
raise RemoteRFPolicyError(
|
|
1424
|
-
"TX sample rate is unavailable for duty-cycle enforcement"
|
|
1425
|
-
)
|
|
1426
|
-
session.policy.check_tx_duration(
|
|
1427
|
-
float(request.sample_count) / min(rates)
|
|
1428
|
-
)
|
|
1429
1399
|
if len(request.payload) != byte_count:
|
|
1430
1400
|
raise RemoteRFProtocolError(
|
|
1431
1401
|
"TX payload byte length does not match dtype/shape"
|
|
@@ -337,6 +337,11 @@ def _serve(*, headless: bool = False) -> int:
|
|
|
337
337
|
if not _ensure_tos_acknowledged():
|
|
338
338
|
return 1
|
|
339
339
|
|
|
340
|
+
# UHD writes native log messages to stderr/std::clog. Set this before the
|
|
341
|
+
# server imports device drivers so those messages cannot corrupt the
|
|
342
|
+
# interactive prompt. An explicit administrator setting still wins.
|
|
343
|
+
os.environ.setdefault("UHD_LOG_CONSOLE_LEVEL", "off")
|
|
344
|
+
|
|
340
345
|
from remoteRF_server.server.grpc_server import (
|
|
341
346
|
headless_main,
|
|
342
347
|
main as interactive_main,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: remoteRF-server
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.6
|
|
4
4
|
Summary: RemoteRF server-side control package
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -329,35 +329,18 @@ devices:
|
|
|
329
329
|
serial: 31A2B3C
|
|
330
330
|
```
|
|
331
331
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
332
|
+
All USRP operations exposed by the Dynamic v2 schema are available without a
|
|
333
|
+
separate policy file. This includes transmit controls, transmit streaming,
|
|
334
|
+
GPIO/register methods, and property-tree writes. `usrp_policy.yml` is not read
|
|
335
|
+
or required by the current server.
|
|
336
336
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
337
|
+
RemoteRF sets `UHD_LOG_CONSOLE_LEVEL=off` before importing UHD so native UHD
|
|
338
|
+
messages do not overwrite the interactive `serverrf` prompt. To temporarily
|
|
339
|
+
restore UHD diagnostics, set a level such as `warning`, `info`, or `debug`
|
|
340
|
+
before starting the server:
|
|
341
341
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
tx_enabled: true
|
|
345
|
-
tx_frequency_ranges_hz:
|
|
346
|
-
- [902000000, 928000000]
|
|
347
|
-
max_tx_gain_db: 20
|
|
348
|
-
max_tx_normalized_gain: 0.5
|
|
349
|
-
max_tx_power_dbm: -10
|
|
350
|
-
max_tx_bandwidth_hz: 2000000
|
|
351
|
-
max_tx_sample_rate_sps: 2000000
|
|
352
|
-
allowed_tx_antennas: [TX/RX]
|
|
353
|
-
max_burst_seconds: 1.0
|
|
354
|
-
max_duty_cycle: 0.1
|
|
355
|
-
duty_cycle_window_seconds: 60
|
|
356
|
-
low_level_write_enabled: false
|
|
357
|
-
low_level_write_methods: []
|
|
358
|
-
gpio_write_masks: {}
|
|
359
|
-
property_write_paths: []
|
|
360
|
-
register_write_addresses: []
|
|
342
|
+
```bash
|
|
343
|
+
UHD_LOG_CONSOLE_LEVEL=warning serverrf -s
|
|
361
344
|
```
|
|
362
345
|
|
|
363
346
|
On every qualified target, generate the introspection record and enforce the
|
|
@@ -14,6 +14,7 @@ from remoteRF_server.common.grpc import grpc_pb2
|
|
|
14
14
|
from remoteRF_server.common.grpc.v2_codec import decode_value, encode_value
|
|
15
15
|
from remoteRF_server.common.idl.v2 import validate_schema
|
|
16
16
|
from remoteRF_server.drivers.usrp.policy import USRPPolicy
|
|
17
|
+
from remoteRF_server.drivers.usrp.usrp_schema import _configure_uhd_console_logging
|
|
17
18
|
from remoteRF_server.drivers.usrp.uhd_manifest import (
|
|
18
19
|
MULTIUSRP_METHODS,
|
|
19
20
|
TARGET_UHD_VERSION,
|
|
@@ -29,7 +30,6 @@ from remoteRF_server.server.dynamic_v2 import (
|
|
|
29
30
|
)
|
|
30
31
|
from remoteRF_server.server.v2_errors import (
|
|
31
32
|
RemoteRFOverloadError,
|
|
32
|
-
RemoteRFPolicyError,
|
|
33
33
|
RemoteRFProtocolError,
|
|
34
34
|
RemoteRFServerOverloadedError,
|
|
35
35
|
RemoteRFSessionExpiredError,
|
|
@@ -139,6 +139,19 @@ class FakeInventorySchema:
|
|
|
139
139
|
|
|
140
140
|
|
|
141
141
|
class DynamicV2ServerTests(unittest.TestCase):
|
|
142
|
+
def test_usrp_console_logging_is_off_by_default_but_admin_override_wins(self):
|
|
143
|
+
with patch.dict(os.environ, {}, clear=True):
|
|
144
|
+
_configure_uhd_console_logging()
|
|
145
|
+
self.assertEqual(os.environ["UHD_LOG_CONSOLE_LEVEL"], "off")
|
|
146
|
+
|
|
147
|
+
with patch.dict(
|
|
148
|
+
os.environ,
|
|
149
|
+
{"UHD_LOG_CONSOLE_LEVEL": "warning"},
|
|
150
|
+
clear=True,
|
|
151
|
+
):
|
|
152
|
+
_configure_uhd_console_logging()
|
|
153
|
+
self.assertEqual(os.environ["UHD_LOG_CONSOLE_LEVEL"], "warning")
|
|
154
|
+
|
|
142
155
|
def test_usrp2901_manifest_is_valid_and_has_no_deferred_reachable_api(self):
|
|
143
156
|
schema = build_usrp_schema_v2()
|
|
144
157
|
validate_schema(schema)
|
|
@@ -241,76 +254,19 @@ class DynamicV2ServerTests(unittest.TestCase):
|
|
|
241
254
|
)
|
|
242
255
|
self.assertEqual(rejected.error.code, "RemoteRFProtocolError")
|
|
243
256
|
|
|
244
|
-
def
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
policy = USRPPolicy(
|
|
254
|
-
{
|
|
255
|
-
"tx_enabled": True,
|
|
256
|
-
"tx_frequency_ranges_hz": [[900e6, 930e6]],
|
|
257
|
-
"max_tx_gain_db": 20,
|
|
258
|
-
"max_tx_sample_rate_sps": 2e6,
|
|
259
|
-
"max_burst_seconds": 1.0,
|
|
260
|
-
"max_duty_cycle": 0.1,
|
|
261
|
-
"duty_cycle_window_seconds": 60,
|
|
262
|
-
}
|
|
263
|
-
)
|
|
264
|
-
policy.check_control("set_tx_freq", {"tune_request": 915e6})
|
|
265
|
-
policy.check_control("set_tx_gain", {"gain": 19})
|
|
266
|
-
with self.assertRaises(RemoteRFPolicyError):
|
|
267
|
-
policy.check_control("set_tx_freq", {"tune_request": 2.4e9})
|
|
268
|
-
with self.assertRaises(RemoteRFPolicyError):
|
|
269
|
-
policy.check_control("set_tx_gain", {"gain": 21})
|
|
270
|
-
|
|
271
|
-
normalized = USRPPolicy(
|
|
272
|
-
{
|
|
273
|
-
"tx_enabled": True,
|
|
274
|
-
"max_tx_normalized_gain": 0.5,
|
|
275
|
-
}
|
|
276
|
-
)
|
|
277
|
-
normalized.check_control("set_normalized_tx_gain", {"gain": 0.5})
|
|
278
|
-
with self.assertRaises(RemoteRFPolicyError):
|
|
279
|
-
normalized.check_control("set_normalized_tx_gain", {"gain": 0.6})
|
|
280
|
-
|
|
281
|
-
low_level = USRPPolicy(
|
|
282
|
-
{
|
|
283
|
-
"low_level_write_enabled": True,
|
|
284
|
-
"low_level_write_methods": ["set_gpio_attr", "set"],
|
|
285
|
-
"gpio_write_masks": {"FP0": 0x03},
|
|
286
|
-
"property_write_paths": ["/mboards/0/user"],
|
|
287
|
-
}
|
|
288
|
-
)
|
|
289
|
-
low_level.check_control(
|
|
290
|
-
"set_gpio_attr",
|
|
291
|
-
{"bank": "FP0", "mask": 0x03},
|
|
292
|
-
)
|
|
293
|
-
with self.assertRaises(RemoteRFPolicyError):
|
|
294
|
-
low_level.check_control(
|
|
295
|
-
"set_gpio_attr",
|
|
296
|
-
{"bank": "FP0", "mask": 0x04},
|
|
297
|
-
)
|
|
298
|
-
with self.assertRaises(RemoteRFPolicyError):
|
|
299
|
-
low_level.check_control(
|
|
300
|
-
"set_gpio_attr",
|
|
301
|
-
{"bank": "FP0"},
|
|
302
|
-
)
|
|
303
|
-
low_level.check_handle_call(
|
|
257
|
+
def test_policy_compatibility_shim_allows_all_operations(self):
|
|
258
|
+
policy = USRPPolicy({"tx_enabled": False, "max_tx_gain_db": 0})
|
|
259
|
+
self.assertTrue(policy.tx_enabled)
|
|
260
|
+
self.assertEqual(policy.restrictions, {})
|
|
261
|
+
policy.check_control("set_tx_freq", {"tune_request": 2.4e9})
|
|
262
|
+
policy.check_control("set_tx_gain", {"gain": 100})
|
|
263
|
+
policy.check_control("set_gpio_attr", {"bank": "FP0", "mask": 0xFFFFFFFF})
|
|
264
|
+
policy.check_tx_duration(3600)
|
|
265
|
+
policy.check_handle_call(
|
|
304
266
|
"uhd.property_tree",
|
|
305
267
|
"set",
|
|
306
|
-
{"__args__": ["/mboards/0/
|
|
268
|
+
{"__args__": ["/mboards/0/unsafe", 1]},
|
|
307
269
|
)
|
|
308
|
-
with self.assertRaises(RemoteRFPolicyError):
|
|
309
|
-
low_level.check_handle_call(
|
|
310
|
-
"uhd.property_tree",
|
|
311
|
-
"set",
|
|
312
|
-
{"__args__": ["/mboards/0/unsafe", 1]},
|
|
313
|
-
)
|
|
314
270
|
|
|
315
271
|
def test_sessions_use_opaque_typed_generation_checked_handles(self):
|
|
316
272
|
inventory = FakeInventorySchema()
|
|
@@ -344,6 +300,20 @@ class DynamicV2ServerTests(unittest.TestCase):
|
|
|
344
300
|
self.assertTrue(session.session_id.startswith("sess_"))
|
|
345
301
|
self.assertTrue(session.root_handle.startswith("dev_"))
|
|
346
302
|
self.assertNotIn("rx:1", session.root_handle)
|
|
303
|
+
manager.invoke(
|
|
304
|
+
session.session_id,
|
|
305
|
+
session.root_handle,
|
|
306
|
+
"set_tx_freq",
|
|
307
|
+
{"tune_request": 2.4e9},
|
|
308
|
+
overload_id="default",
|
|
309
|
+
)
|
|
310
|
+
manager.invoke(
|
|
311
|
+
session.session_id,
|
|
312
|
+
session.root_handle,
|
|
313
|
+
"set_gpio_attr",
|
|
314
|
+
{"bank": "FP0", "attr": "OUT", "value": 1},
|
|
315
|
+
overload_id="default",
|
|
316
|
+
)
|
|
347
317
|
result, _ = manager.invoke(
|
|
348
318
|
session.session_id,
|
|
349
319
|
session.root_handle,
|
|
@@ -413,15 +383,22 @@ class DynamicV2ServerTests(unittest.TestCase):
|
|
|
413
383
|
overload_id="default",
|
|
414
384
|
)
|
|
415
385
|
self.assertEqual(value, 3)
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
386
|
+
manager.invoke(
|
|
387
|
+
session.session_id,
|
|
388
|
+
prop["__remoterf_handle__"],
|
|
389
|
+
"set",
|
|
390
|
+
{},
|
|
391
|
+
positional_args=[4],
|
|
392
|
+
overload_id="default",
|
|
393
|
+
)
|
|
394
|
+
updated, _ = manager.invoke(
|
|
395
|
+
session.session_id,
|
|
396
|
+
prop["__remoterf_handle__"],
|
|
397
|
+
"get",
|
|
398
|
+
{},
|
|
399
|
+
overload_id="default",
|
|
400
|
+
)
|
|
401
|
+
self.assertEqual(updated, 4)
|
|
425
402
|
with self.assertRaises(RemoteRFProtocolError):
|
|
426
403
|
manager.invoke(
|
|
427
404
|
session.session_id,
|
|
@@ -1,363 +0,0 @@
|
|
|
1
|
-
"""Server-owned USRP policy enforcement.
|
|
2
|
-
|
|
3
|
-
Missing, unreadable, or incomplete policy files intentionally deny TX and
|
|
4
|
-
low-level writes. Read-only control and RX remain available.
|
|
5
|
-
"""
|
|
6
|
-
from __future__ import annotations
|
|
7
|
-
|
|
8
|
-
import os
|
|
9
|
-
import time
|
|
10
|
-
from collections import deque
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
from typing import Any, Mapping
|
|
13
|
-
|
|
14
|
-
from ...server.v2_errors import RemoteRFPolicyError
|
|
15
|
-
|
|
16
|
-
try:
|
|
17
|
-
import yaml
|
|
18
|
-
except ImportError: # pragma: no cover - deployment dependency, deny-safe fallback
|
|
19
|
-
yaml = None
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def default_policy_path() -> Path:
|
|
23
|
-
root = Path(
|
|
24
|
-
os.getenv(
|
|
25
|
-
"REMOTERF_CONFIG_DIR",
|
|
26
|
-
Path(os.getenv("XDG_CONFIG_HOME", Path.home() / ".config")) / "remoterf",
|
|
27
|
-
)
|
|
28
|
-
)
|
|
29
|
-
return root / "usrp_policy.yml"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def _inside_ranges(value: float, ranges) -> bool:
|
|
33
|
-
for entry in ranges or ():
|
|
34
|
-
try:
|
|
35
|
-
low, high = float(entry[0]), float(entry[1])
|
|
36
|
-
except (TypeError, ValueError, IndexError):
|
|
37
|
-
continue
|
|
38
|
-
if low <= float(value) <= high:
|
|
39
|
-
return True
|
|
40
|
-
return False
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def _argument(args: Mapping[str, Any], *names: str, position: int | None = None):
|
|
44
|
-
for name in names:
|
|
45
|
-
if name in args:
|
|
46
|
-
return args[name]
|
|
47
|
-
positional = args.get("__args__", ())
|
|
48
|
-
if position is not None and isinstance(positional, (list, tuple)):
|
|
49
|
-
if 0 <= position < len(positional):
|
|
50
|
-
return positional[position]
|
|
51
|
-
return None
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
class USRPPolicy:
|
|
55
|
-
def __init__(self, config: Mapping[str, Any] | None = None, *, source: str = ""):
|
|
56
|
-
self.config = dict(config or {})
|
|
57
|
-
self.source = source
|
|
58
|
-
self._tx_history: deque[tuple[float, float]] = deque()
|
|
59
|
-
|
|
60
|
-
@classmethod
|
|
61
|
-
def load(cls, *, gid: int, serial: str = "", path: Path | None = None) -> "USRPPolicy":
|
|
62
|
-
path = path or default_policy_path()
|
|
63
|
-
if yaml is None or not path.exists():
|
|
64
|
-
return cls({}, source=str(path))
|
|
65
|
-
try:
|
|
66
|
-
raw = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
|
|
67
|
-
if not isinstance(raw, Mapping):
|
|
68
|
-
raise TypeError("policy root must be a mapping")
|
|
69
|
-
defaults = dict(raw.get("defaults") or {})
|
|
70
|
-
devices = dict(raw.get("devices") or {})
|
|
71
|
-
selected = devices.get(str(gid)) or devices.get(serial) or {}
|
|
72
|
-
merged = dict(defaults)
|
|
73
|
-
merged.update(dict(selected or {}))
|
|
74
|
-
except Exception:
|
|
75
|
-
return cls({}, source=str(path))
|
|
76
|
-
return cls(merged, source=str(path))
|
|
77
|
-
|
|
78
|
-
@property
|
|
79
|
-
def tx_enabled(self) -> bool:
|
|
80
|
-
return self.config.get("tx_enabled") is True
|
|
81
|
-
|
|
82
|
-
@property
|
|
83
|
-
def restrictions(self) -> dict[str, Any]:
|
|
84
|
-
return {
|
|
85
|
-
"tx_enabled": self.tx_enabled,
|
|
86
|
-
"tx_frequency_ranges_hz": self.config.get("tx_frequency_ranges_hz", []),
|
|
87
|
-
"max_tx_gain_db": self.config.get("max_tx_gain_db"),
|
|
88
|
-
"max_tx_power_dbm": self.config.get("max_tx_power_dbm"),
|
|
89
|
-
"max_tx_bandwidth_hz": self.config.get("max_tx_bandwidth_hz"),
|
|
90
|
-
"max_tx_sample_rate_sps": self.config.get("max_tx_sample_rate_sps"),
|
|
91
|
-
"allowed_tx_antennas": self.config.get("allowed_tx_antennas", []),
|
|
92
|
-
"max_burst_seconds": self.config.get("max_burst_seconds"),
|
|
93
|
-
"max_duty_cycle": self.config.get("max_duty_cycle"),
|
|
94
|
-
"low_level_write_enabled": self.config.get("low_level_write_enabled") is True,
|
|
95
|
-
"low_level_write_methods": self.config.get("low_level_write_methods", []),
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
def _require_tx(self) -> None:
|
|
99
|
-
if not self.tx_enabled:
|
|
100
|
-
raise RemoteRFPolicyError(
|
|
101
|
-
"USRP transmit is disabled until an administrator configures usrp_policy.yml",
|
|
102
|
-
details={"policy_source": self.source},
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
def check_control(self, method: str, args: Mapping[str, Any]) -> None:
|
|
106
|
-
if (
|
|
107
|
-
method.startswith("set_tx_")
|
|
108
|
-
or method == "set_normalized_tx_gain"
|
|
109
|
-
or method == "send_waveform"
|
|
110
|
-
):
|
|
111
|
-
self._require_tx()
|
|
112
|
-
|
|
113
|
-
if method in {"set_tx_freq", "set_tx_lo_freq", "send_waveform"}:
|
|
114
|
-
value = _argument(
|
|
115
|
-
args,
|
|
116
|
-
"tune_request",
|
|
117
|
-
"freq",
|
|
118
|
-
"frequency",
|
|
119
|
-
position=0,
|
|
120
|
-
)
|
|
121
|
-
if isinstance(value, Mapping):
|
|
122
|
-
value = value.get("target_freq", value.get("freq"))
|
|
123
|
-
ranges = self.config.get("tx_frequency_ranges_hz") or []
|
|
124
|
-
if value is None or not ranges or not _inside_ranges(float(value), ranges):
|
|
125
|
-
raise RemoteRFPolicyError(
|
|
126
|
-
f"TX frequency is outside the configured policy ranges",
|
|
127
|
-
details={"method": method, "requested_hz": value, "allowed_ranges_hz": ranges},
|
|
128
|
-
method=method,
|
|
129
|
-
)
|
|
130
|
-
|
|
131
|
-
if method == "set_tx_gain":
|
|
132
|
-
limit = self.config.get("max_tx_gain_db")
|
|
133
|
-
value = _argument(args, "gain", position=0)
|
|
134
|
-
if limit is None or value is None or float(value) > float(limit):
|
|
135
|
-
raise RemoteRFPolicyError(
|
|
136
|
-
"TX gain exceeds or lacks an administrator limit",
|
|
137
|
-
details={"requested_db": value, "maximum_db": limit},
|
|
138
|
-
method=method,
|
|
139
|
-
)
|
|
140
|
-
|
|
141
|
-
if method == "set_normalized_tx_gain":
|
|
142
|
-
limit = self.config.get("max_tx_normalized_gain")
|
|
143
|
-
value = _argument(args, "gain", position=0)
|
|
144
|
-
if limit is None or value is None or float(value) > float(limit):
|
|
145
|
-
raise RemoteRFPolicyError(
|
|
146
|
-
"normalized TX gain exceeds or lacks an administrator limit",
|
|
147
|
-
details={"requested": value, "maximum": limit},
|
|
148
|
-
method=method,
|
|
149
|
-
)
|
|
150
|
-
|
|
151
|
-
if method == "set_tx_power_reference":
|
|
152
|
-
limit = self.config.get("max_tx_power_dbm")
|
|
153
|
-
value = _argument(args, "power_dbm", "power", position=0)
|
|
154
|
-
if limit is None or value is None or float(value) > float(limit):
|
|
155
|
-
raise RemoteRFPolicyError(
|
|
156
|
-
"TX power reference exceeds or lacks an administrator limit",
|
|
157
|
-
details={"requested_dbm": value, "maximum_dbm": limit},
|
|
158
|
-
method=method,
|
|
159
|
-
)
|
|
160
|
-
|
|
161
|
-
if method == "set_tx_bandwidth":
|
|
162
|
-
limit = self.config.get("max_tx_bandwidth_hz")
|
|
163
|
-
value = _argument(args, "bandwidth", position=0)
|
|
164
|
-
if limit is None or value is None or float(value) > float(limit):
|
|
165
|
-
raise RemoteRFPolicyError(
|
|
166
|
-
"TX bandwidth exceeds or lacks an administrator limit",
|
|
167
|
-
details={"requested_hz": value, "maximum_hz": limit},
|
|
168
|
-
method=method,
|
|
169
|
-
)
|
|
170
|
-
|
|
171
|
-
if method == "set_tx_rate":
|
|
172
|
-
limit = self.config.get("max_tx_sample_rate_sps")
|
|
173
|
-
value = _argument(args, "rate", position=0)
|
|
174
|
-
if limit is None or value is None or float(value) > float(limit):
|
|
175
|
-
raise RemoteRFPolicyError(
|
|
176
|
-
"TX sample rate exceeds or lacks an administrator limit",
|
|
177
|
-
details={"requested_sps": value, "maximum_sps": limit},
|
|
178
|
-
method=method,
|
|
179
|
-
)
|
|
180
|
-
|
|
181
|
-
if method == "set_tx_antenna":
|
|
182
|
-
allowed = {
|
|
183
|
-
str(value)
|
|
184
|
-
for value in (self.config.get("allowed_tx_antennas") or ())
|
|
185
|
-
}
|
|
186
|
-
antenna = str(_argument(args, "antenna", position=0) or "")
|
|
187
|
-
if not allowed or antenna not in allowed:
|
|
188
|
-
raise RemoteRFPolicyError(
|
|
189
|
-
"TX antenna is not in the administrator allowlist",
|
|
190
|
-
details={"requested": antenna, "allowed": sorted(allowed)},
|
|
191
|
-
method=method,
|
|
192
|
-
)
|
|
193
|
-
|
|
194
|
-
if method == "set_tx_lo_source":
|
|
195
|
-
allowed = {
|
|
196
|
-
str(value)
|
|
197
|
-
for value in (self.config.get("allowed_tx_lo_sources") or ())
|
|
198
|
-
}
|
|
199
|
-
source = str(_argument(args, "source", position=0) or "")
|
|
200
|
-
if not allowed or source not in allowed:
|
|
201
|
-
raise RemoteRFPolicyError(
|
|
202
|
-
"TX LO source is not in the administrator allowlist",
|
|
203
|
-
details={"requested": source, "allowed": sorted(allowed)},
|
|
204
|
-
method=method,
|
|
205
|
-
)
|
|
206
|
-
|
|
207
|
-
if (
|
|
208
|
-
method == "set_tx_lo_export_enabled"
|
|
209
|
-
and bool(_argument(args, "enabled", "enable", position=0))
|
|
210
|
-
and self.config.get("allow_tx_lo_export") is not True
|
|
211
|
-
):
|
|
212
|
-
raise RemoteRFPolicyError(
|
|
213
|
-
"TX LO export is disabled by administrator policy",
|
|
214
|
-
method=method,
|
|
215
|
-
)
|
|
216
|
-
|
|
217
|
-
if (
|
|
218
|
-
method == "set_rx_lo_export_enabled"
|
|
219
|
-
and bool(_argument(args, "enabled", "enable", position=0))
|
|
220
|
-
and self.config.get("allow_rx_lo_export") is not True
|
|
221
|
-
):
|
|
222
|
-
raise RemoteRFPolicyError(
|
|
223
|
-
"RX LO export is disabled by administrator policy",
|
|
224
|
-
method=method,
|
|
225
|
-
)
|
|
226
|
-
|
|
227
|
-
if method == "send_waveform":
|
|
228
|
-
self.check_control(
|
|
229
|
-
"set_tx_rate",
|
|
230
|
-
{"rate": _argument(args, "rate", position=3)},
|
|
231
|
-
)
|
|
232
|
-
self.check_control(
|
|
233
|
-
"set_tx_gain",
|
|
234
|
-
{"gain": _argument(args, "gain", position=5)},
|
|
235
|
-
)
|
|
236
|
-
self.check_tx_duration(
|
|
237
|
-
float(_argument(args, "duration", position=1) or 0.0)
|
|
238
|
-
)
|
|
239
|
-
|
|
240
|
-
if method in {
|
|
241
|
-
"set_gpio_attr", "set_gpio_src", "set_user_register",
|
|
242
|
-
"set_rx_filter", "set_tx_filter",
|
|
243
|
-
}:
|
|
244
|
-
self._check_low_level_write("", method, args)
|
|
245
|
-
|
|
246
|
-
def check_tx_duration(self, duration: float) -> None:
|
|
247
|
-
self._require_tx()
|
|
248
|
-
if duration < 0:
|
|
249
|
-
raise RemoteRFPolicyError("TX duration cannot be negative")
|
|
250
|
-
maximum = self.config.get("max_burst_seconds")
|
|
251
|
-
try:
|
|
252
|
-
maximum_value = float(maximum)
|
|
253
|
-
except (TypeError, ValueError):
|
|
254
|
-
maximum_value = -1.0
|
|
255
|
-
if maximum_value < 0 or duration > maximum_value:
|
|
256
|
-
raise RemoteRFPolicyError(
|
|
257
|
-
"TX duration exceeds or lacks an administrator limit",
|
|
258
|
-
details={"requested_seconds": duration, "maximum_seconds": maximum},
|
|
259
|
-
)
|
|
260
|
-
|
|
261
|
-
try:
|
|
262
|
-
window = float(self.config.get("duty_cycle_window_seconds", 60.0))
|
|
263
|
-
except (TypeError, ValueError):
|
|
264
|
-
window = 0.0
|
|
265
|
-
fraction = self.config.get("max_duty_cycle")
|
|
266
|
-
try:
|
|
267
|
-
fraction_value = float(fraction)
|
|
268
|
-
except (TypeError, ValueError):
|
|
269
|
-
fraction_value = -1.0
|
|
270
|
-
if window <= 0 or not 0 <= fraction_value <= 1:
|
|
271
|
-
raise RemoteRFPolicyError("TX duty-cycle policy is not configured")
|
|
272
|
-
now = time.monotonic()
|
|
273
|
-
while self._tx_history and self._tx_history[0][0] < now - window:
|
|
274
|
-
self._tx_history.popleft()
|
|
275
|
-
used = sum(item[1] for item in self._tx_history)
|
|
276
|
-
if used + duration > window * fraction_value:
|
|
277
|
-
raise RemoteRFPolicyError(
|
|
278
|
-
"TX duty-cycle limit would be exceeded",
|
|
279
|
-
details={"window_seconds": window, "maximum_fraction": fraction},
|
|
280
|
-
)
|
|
281
|
-
self._tx_history.append((now, duration))
|
|
282
|
-
|
|
283
|
-
def check_handle_call(self, type_id: str, method: str, args: Mapping[str, Any]) -> None:
|
|
284
|
-
"""Keep returned low-level interfaces read-only unless allowlisted."""
|
|
285
|
-
read_prefixes = ("get", "read", "list", "exists", "to_", "peek")
|
|
286
|
-
if (
|
|
287
|
-
method.startswith(read_prefixes)
|
|
288
|
-
or method.startswith("access_")
|
|
289
|
-
or method in {"access", "subtree"}
|
|
290
|
-
):
|
|
291
|
-
return
|
|
292
|
-
self._check_low_level_write(type_id, method, args)
|
|
293
|
-
|
|
294
|
-
def _check_low_level_write(
|
|
295
|
-
self,
|
|
296
|
-
type_id: str,
|
|
297
|
-
method: str,
|
|
298
|
-
args: Mapping[str, Any],
|
|
299
|
-
) -> None:
|
|
300
|
-
allowed = set(self.config.get("low_level_write_methods") or ())
|
|
301
|
-
if self.config.get("low_level_write_enabled") is not True or method not in allowed:
|
|
302
|
-
raise RemoteRFPolicyError(
|
|
303
|
-
f"{type_id}.{method} is not allowlisted for low-level writes",
|
|
304
|
-
details={"type_id": type_id, "method": method},
|
|
305
|
-
method=method,
|
|
306
|
-
)
|
|
307
|
-
|
|
308
|
-
if method == "set_gpio_attr":
|
|
309
|
-
bank = str(args.get("bank", ""))
|
|
310
|
-
mask = _argument(args, "mask", position=3)
|
|
311
|
-
requested_mask = int((1 << 32) - 1 if mask is None else mask)
|
|
312
|
-
allowed_mask = int(
|
|
313
|
-
dict(self.config.get("gpio_write_masks") or {}).get(bank, 0)
|
|
314
|
-
)
|
|
315
|
-
if requested_mask & ~allowed_mask:
|
|
316
|
-
raise RemoteRFPolicyError(
|
|
317
|
-
"GPIO write mask exceeds the administrator allowlist",
|
|
318
|
-
details={
|
|
319
|
-
"bank": bank,
|
|
320
|
-
"requested_mask": requested_mask,
|
|
321
|
-
"allowed_mask": allowed_mask,
|
|
322
|
-
},
|
|
323
|
-
method=method,
|
|
324
|
-
)
|
|
325
|
-
|
|
326
|
-
path_value = _argument(
|
|
327
|
-
args,
|
|
328
|
-
"path",
|
|
329
|
-
"property_path",
|
|
330
|
-
"key",
|
|
331
|
-
position=0,
|
|
332
|
-
)
|
|
333
|
-
path = str(path_value) if path_value is not None else ""
|
|
334
|
-
if path:
|
|
335
|
-
allowed_paths = tuple(
|
|
336
|
-
str(value)
|
|
337
|
-
for value in (self.config.get("property_write_paths") or ())
|
|
338
|
-
)
|
|
339
|
-
if not allowed_paths or not any(
|
|
340
|
-
path == prefix or path.startswith(prefix.rstrip("/") + "/")
|
|
341
|
-
for prefix in allowed_paths
|
|
342
|
-
):
|
|
343
|
-
raise RemoteRFPolicyError(
|
|
344
|
-
"property-tree write path is not allowlisted",
|
|
345
|
-
details={"path": path, "allowed_paths": list(allowed_paths)},
|
|
346
|
-
method=method,
|
|
347
|
-
)
|
|
348
|
-
|
|
349
|
-
if method in {"poke32", "poke64", "write_register", "set_user_register"}:
|
|
350
|
-
address = _argument(args, "addr", "address", position=0)
|
|
351
|
-
allowed_addresses = {
|
|
352
|
-
int(value, 0) if isinstance(value, str) else int(value)
|
|
353
|
-
for value in (self.config.get("register_write_addresses") or ())
|
|
354
|
-
}
|
|
355
|
-
if address is None or int(address) not in allowed_addresses:
|
|
356
|
-
raise RemoteRFPolicyError(
|
|
357
|
-
"register write address is not allowlisted",
|
|
358
|
-
details={
|
|
359
|
-
"address": address,
|
|
360
|
-
"allowed_addresses": sorted(allowed_addresses),
|
|
361
|
-
},
|
|
362
|
-
method=method,
|
|
363
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/common/grpc/grpc_host_pb2.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/common/grpc/grpc_pb2_grpc.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/common/idl/device_schema.py
RENAMED
|
File without changes
|
{remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/common/idl/pluto_mimo_schema.py
RENAMED
|
File without changes
|
{remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/common/idl/pluto_schema.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/common/utils/__init__.py
RENAMED
|
File without changes
|
{remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/common/utils/ansi_codes.py
RENAMED
|
File without changes
|
{remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/common/utils/api_token.py
RENAMED
|
File without changes
|
{remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/common/utils/db_connection.py
RENAMED
|
File without changes
|
{remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/common/utils/db_location.py
RENAMED
|
File without changes
|
{remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/common/utils/list_string.py
RENAMED
|
File without changes
|
{remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/common/utils/process_arg.py
RENAMED
|
File without changes
|
|
File without changes
|
{remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/drivers/adalm_pluto/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/drivers/usrp/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/host/host_directory_store.py
RENAMED
|
File without changes
|
{remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/host/host_tunnel_server.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/server/device_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/server/user_group_cli.py
RENAMED
|
File without changes
|
{remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/server/user_group_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server/tools/gist_status_testing.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{remoterf_server-1.0.4 → remoterf_server-1.0.6}/src/remoteRF_server.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|