RemoteRF 2.5.0__tar.gz → 2.5.2__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-2.5.0/src/remoterf.egg-info → remoterf-2.5.2}/PKG-INFO +9 -1
- {remoterf-2.5.0 → remoterf-2.5.2}/README.md +8 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/pyproject.toml +3 -2
- remoterf-2.5.2/setup.py +16 -0
- remoterf-2.5.2/src/_remoterf_alias.py +60 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/core/direct_path.py +42 -17
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/core/dynamic_v2_transport.py +3 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/core/grpc_client.py +45 -6
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/drivers/dynamic_device.py +12 -2
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/drivers/rtl_sdr/rtl_sdr_remote.py +41 -9
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/drivers/ti_mmwave/ti_mmwave_remote.py +39 -7
- {remoterf-2.5.0 → remoterf-2.5.2/src/remoterf.egg-info}/PKG-INFO +9 -1
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf.egg-info/SOURCES.txt +5 -0
- remoterf-2.5.2/src/remoterf.egg-info/top_level.txt +2 -0
- remoterf-2.5.2/tests/test_bundled_drivers.py +57 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/tests/test_direct_path_global.py +12 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/tests/test_direct_path_partner.py +65 -3
- {remoterf-2.5.0 → remoterf-2.5.2}/tests/test_fetch_idl_fallback.py +19 -0
- remoterf-2.5.2/tests/test_old_import_name.py +31 -0
- remoterf-2.5.2/tests/test_relay_paging.py +63 -0
- remoterf-2.5.0/src/remoterf.egg-info/top_level.txt +0 -1
- {remoterf-2.5.0 → remoterf-2.5.2}/LICENSE +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/setup.cfg +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/__init__.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/common/__init__.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/common/grpc/__init__.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/common/grpc/deployment_capabilities.proto +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/common/grpc/deployment_capabilities_pb2.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/common/grpc/deployment_capabilities_pb2_grpc.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/common/grpc/grpc.proto +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/common/grpc/grpc_pb2.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/common/grpc/grpc_pb2_grpc.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/common/grpc/v2_codec.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/common/tos_notice.txt +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/common/utils/__init__.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/common/utils/ansi_codes.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/common/utils/api_token.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/common/utils/banner.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/common/utils/list_string.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/common/utils/process_arg.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/config/__init__.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/config/cert_fetcher.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/config/config.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/core/__init__.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/core/acc_login.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/core/app.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/core/banner_note.txt +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/core/grpc_acc.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/core/secure_channel.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/core/v2_errors.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/deployment/__init__.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/deployment/backend.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/deployment/direct.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/deployment/homes.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/deployment/http.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/deployment/state.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/drivers/__init__.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/drivers/_virtual.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/drivers/adalm_pluto/__init__.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/drivers/adalm_pluto/adalm_pluto_remote.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/drivers/dynamic_v2.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/drivers/hackrf/__init__.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/drivers/hackrf/hackrf_remote.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/drivers/rtl_sdr/__init__.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/drivers/sim_sdr/__init__.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/drivers/sim_sdr/sim_sdr_remote.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/drivers/support/__init__.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/drivers/support/ti_mmwave.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/drivers/support/uhd.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/drivers/support/uhd_dsp.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/drivers/support/uhd_v2.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/drivers/ti_mmwave/__init__.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/drivers/usrp/__init__.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/drivers/usrp/usrp_remote.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/drivers/usrp/usrp_remote.pyi +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/remoterf_cli.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf/version.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf.egg-info/dependency_links.txt +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf.egg-info/entry_points.txt +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf.egg-info/requires.txt +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/src/remoterf.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/tests/test_banner.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/tests/test_batch.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/tests/test_cli_login_fresh.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/tests/test_config_address.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/tests/test_deployment_accounts.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/tests/test_device_routes.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/tests/test_direct_path_fixed.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/tests/test_direct_path_race.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/tests/test_dynamic_device_codegen.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/tests/test_dynamic_v2.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/tests/test_federated_devices.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/tests/test_finish_prompt.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/tests/test_gate_i_direct_path.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/tests/test_grpc_client_target.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/tests/test_homes.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/tests/test_native_account_credentials.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/tests/test_native_devices.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/tests/test_partner_dynamic_v2.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/tests/test_pluto_virtual.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/tests/test_process_arg.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/tests/test_remoterf_cli.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/tests/test_rx_readahead.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/tests/test_ti_mmwave.py +0 -0
- {remoterf-2.5.0 → remoterf-2.5.2}/tests/test_upgrade_rescue.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: remoterf
|
|
3
|
-
Version: 2.5.
|
|
3
|
+
Version: 2.5.2
|
|
4
4
|
Summary: A python API to remotely access signal centric hardware. Client-side only! Courtesy of Wireless Lab @ UCLA & Prof. Ian Roberts.
|
|
5
5
|
Author-email: Ethan Ge <ethoGalaxy@gmail.com>
|
|
6
6
|
License-Expression: GPL-3.0-or-later
|
|
@@ -52,6 +52,8 @@ source venv/bin/activate # Activate virtual environment
|
|
|
52
52
|
pip install remoterf # Install remoterf
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
+
The package was named `remoteRF` before 2.2.0. Old code that does `import remoteRF` or `from remoteRF.drivers... import ...` still runs, with a one-time warning; change it to `remoterf`.
|
|
56
|
+
|
|
55
57
|
## NI USRP-2901, Ettus B205-mini, B200-family, and USRP N210
|
|
56
58
|
|
|
57
59
|
The packaged client natively includes the shared USRP schema for the qualified
|
|
@@ -145,6 +147,12 @@ not close the physical radio. `read_samples` and `read_bytes` are bounded to a
|
|
|
145
147
|
4 MiB response. Call them repeatedly for longer captures. Native asynchronous
|
|
146
148
|
callbacks are not transported remotely.
|
|
147
149
|
|
|
150
|
+
Through the RemoteRF Global relay one reply is capped at 768 KiB. A lab on a
|
|
151
|
+
current Server keeps a larger reply (up to 4 MiB) and the client fetches it in
|
|
152
|
+
pages; an older lab refuses it with `response_too_large`, so keep reads there
|
|
153
|
+
to about 98k complex64 samples (`read_samples(65536)` is safe). LAN and direct
|
|
154
|
+
paths are not capped.
|
|
155
|
+
|
|
148
156
|
When a Tailscale address differs from the IP or DNS identity in the server
|
|
149
157
|
certificate, keep TLS verification enabled and set the expected identity:
|
|
150
158
|
|
|
@@ -27,6 +27,8 @@ source venv/bin/activate # Activate virtual environment
|
|
|
27
27
|
pip install remoterf # Install remoterf
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
+
The package was named `remoteRF` before 2.2.0. Old code that does `import remoteRF` or `from remoteRF.drivers... import ...` still runs, with a one-time warning; change it to `remoterf`.
|
|
31
|
+
|
|
30
32
|
## NI USRP-2901, Ettus B205-mini, B200-family, and USRP N210
|
|
31
33
|
|
|
32
34
|
The packaged client natively includes the shared USRP schema for the qualified
|
|
@@ -120,6 +122,12 @@ not close the physical radio. `read_samples` and `read_bytes` are bounded to a
|
|
|
120
122
|
4 MiB response. Call them repeatedly for longer captures. Native asynchronous
|
|
121
123
|
callbacks are not transported remotely.
|
|
122
124
|
|
|
125
|
+
Through the RemoteRF Global relay one reply is capped at 768 KiB. A lab on a
|
|
126
|
+
current Server keeps a larger reply (up to 4 MiB) and the client fetches it in
|
|
127
|
+
pages; an older lab refuses it with `response_too_large`, so keep reads there
|
|
128
|
+
to about 98k complex64 samples (`read_samples(65536)` is safe). LAN and direct
|
|
129
|
+
paths are not capped.
|
|
130
|
+
|
|
123
131
|
When a Tailscale address differs from the IP or DNS identity in the server
|
|
124
132
|
certificate, keep TLS verification enabled and set the expected identity:
|
|
125
133
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "remoterf"
|
|
7
|
-
version = "2.5.
|
|
7
|
+
version = "2.5.2"
|
|
8
8
|
description = "A python API to remotely access signal centric hardware. Client-side only! Courtesy of Wireless Lab @ UCLA & Prof. Ian Roberts."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "GPL-3.0-or-later"
|
|
@@ -36,7 +36,8 @@ remoterf = "remoterf.remoterf_cli:main"
|
|
|
36
36
|
[tool.setuptools]
|
|
37
37
|
package-dir = { "" = "src" }
|
|
38
38
|
# src/remoterf.py: see its docstring (upgrades from 2.0.14-2.1.x).
|
|
39
|
-
py
|
|
39
|
+
# src/_remoterf_alias.py: `import remoteRF` (setup.py adds its .pth).
|
|
40
|
+
py-modules = ["remoterf", "_remoterf_alias"]
|
|
40
41
|
|
|
41
42
|
[tool.setuptools.packages.find]
|
|
42
43
|
where = ["src"]
|
remoterf-2.5.2/setup.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from setuptools import setup
|
|
4
|
+
from setuptools.command.build_py import build_py
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BuildPy(build_py):
|
|
8
|
+
"""The wheel also ships _remoterf_alias.pth, so `import remoteRF` works
|
|
9
|
+
(see src/_remoterf_alias.py)."""
|
|
10
|
+
|
|
11
|
+
def run(self):
|
|
12
|
+
super().run()
|
|
13
|
+
Path(self.build_lib, "_remoterf_alias.pth").write_text("import _remoterf_alias\n")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
setup(cmdclass={"build_py": BuildPy})
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Copyright (C) 2026 RemoteRF
|
|
2
|
+
#
|
|
3
|
+
# This program is free software: you can redistribute it and/or modify
|
|
4
|
+
# it under the terms of the GNU General Public License as published by
|
|
5
|
+
# the Free Software Foundation, either version 3 of the License, or
|
|
6
|
+
# (at your option) any later version.
|
|
7
|
+
#
|
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
+
# GNU General Public License for more details.
|
|
12
|
+
#
|
|
13
|
+
# You should have received a copy of the GNU General Public License
|
|
14
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
15
|
+
|
|
16
|
+
"""``import remoteRF``, the package's name before 2.2.0, still works.
|
|
17
|
+
|
|
18
|
+
_remoterf_alias.pth imports this module at startup. It maps ``remoteRF`` and
|
|
19
|
+
every ``remoteRF.*`` to the same module objects as ``remoterf``, warning once.
|
|
20
|
+
Until something imports ``remoteRF``, it imports nothing (no remoterf, grpc, numpy).
|
|
21
|
+
A ``remoteRF.py`` file cannot do this: on a case-insensitive disk it would
|
|
22
|
+
collide with the ``remoterf.py`` rescue module.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import importlib
|
|
26
|
+
import importlib.machinery
|
|
27
|
+
import sys
|
|
28
|
+
import warnings
|
|
29
|
+
|
|
30
|
+
_OLD, _NEW = "remoteRF", "remoterf"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class _Alias: # a meta path finder and its loader
|
|
34
|
+
warned = False
|
|
35
|
+
|
|
36
|
+
def find_spec(self, name, path=None, target=None):
|
|
37
|
+
if name == _OLD or name.startswith(_OLD + "."):
|
|
38
|
+
return importlib.machinery.ModuleSpec(name, self)
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
def create_module(self, spec):
|
|
42
|
+
if not _Alias.warned:
|
|
43
|
+
_Alias.warned = True
|
|
44
|
+
# FutureWarning: shown by default (a DeprecationWarning is hidden outside __main__).
|
|
45
|
+
# stacklevel skips the import machinery, so it points at the importing line.
|
|
46
|
+
warnings.warn(
|
|
47
|
+
"'remoteRF' was renamed 'remoterf' in 2.2.0; import remoterf instead",
|
|
48
|
+
FutureWarning, stacklevel=2,
|
|
49
|
+
)
|
|
50
|
+
module = importlib.import_module(_NEW + spec.name[len(_OLD):])
|
|
51
|
+
spec.loader_state = module.__spec__
|
|
52
|
+
return module
|
|
53
|
+
|
|
54
|
+
def exec_module(self, module):
|
|
55
|
+
module.__spec__ = module.__spec__.loader_state # loading set it to the alias's spec
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# First: on an upgraded machine a surviving remoteRF folder would load a second copy.
|
|
59
|
+
if not any(isinstance(finder, _Alias) for finder in sys.meta_path):
|
|
60
|
+
sys.meta_path.insert(0, _Alias())
|
|
@@ -691,17 +691,25 @@ _partners: dict[str, DirectPath] = {} # device token -> the path to the lab tha
|
|
|
691
691
|
_partner_owners: dict[str, str] = {} # device token -> owner deployment id
|
|
692
692
|
|
|
693
693
|
|
|
694
|
-
def _offer_rpc(username: str, secret: str, token: Optional[str] = None) -> Callable[[dict], dict]:
|
|
695
|
-
"""The offer RPC; with ``token`` the home has the partner lab answer instead.
|
|
694
|
+
def _offer_rpc(username: Optional[str], secret: Optional[str], token: Optional[str] = None) -> Callable[[dict], dict]:
|
|
695
|
+
"""The offer RPC; with ``token`` the home has the partner lab answer instead.
|
|
696
|
+
|
|
697
|
+
Without a login (``username`` None) the partner token alone authorizes it."""
|
|
696
698
|
def send(offer: dict) -> dict:
|
|
697
699
|
from .grpc_client import rpc_client
|
|
698
700
|
|
|
699
|
-
args = {"
|
|
701
|
+
args = {"ice": map_arg(json.dumps(offer))}
|
|
702
|
+
if username is not None:
|
|
703
|
+
args.update(un=map_arg(username), pw=map_arg(secret))
|
|
700
704
|
if token is not None:
|
|
701
705
|
args["a"] = map_arg(token)
|
|
702
706
|
response = rpc_client(function_name="ACC:direct_offer", args=args)
|
|
703
707
|
if "ace" in response.results:
|
|
704
|
-
|
|
708
|
+
reason = unmap_arg(response.results["ace"])
|
|
709
|
+
if username is None and reason.startswith("Internal server error"):
|
|
710
|
+
# A home Server that predates token offers fails on the missing login.
|
|
711
|
+
raise PathUnavailable("the home's Server needs a login for a direct path")
|
|
712
|
+
raise PathError(reason)
|
|
705
713
|
answer = json.loads(unmap_arg(response.results["ice"]))
|
|
706
714
|
for key in ("server_name", "ca_pem"):
|
|
707
715
|
if key in response.results:
|
|
@@ -811,12 +819,18 @@ def _side_path(key: str, make: Callable[[], Optional[DirectPath]]) -> Optional[D
|
|
|
811
819
|
def _partner(token: str) -> Optional[DirectPath]:
|
|
812
820
|
"""The path to the lab owning the device behind ``token``, punched on first use."""
|
|
813
821
|
def make() -> Optional[DirectPath]:
|
|
822
|
+
owner = _partner_owners.get(token)
|
|
814
823
|
credentials = _credentials()
|
|
815
824
|
if credentials is None:
|
|
816
|
-
|
|
825
|
+
# No login (a token-only script): the token is its own credential.
|
|
826
|
+
# Its own memo, so an old home refusing it cannot hold back a
|
|
827
|
+
# logged-in process's path to the same lab.
|
|
828
|
+
return DirectPath(
|
|
829
|
+
offer=_offer_rpc(None, None, token=token), server_name="", ca_pem=b"",
|
|
830
|
+
memo=owner and f"{owner}|token",
|
|
831
|
+
)
|
|
817
832
|
return DirectPath(
|
|
818
|
-
offer=_offer_rpc(*credentials, token=token), server_name="", ca_pem=b"",
|
|
819
|
-
memo=_partner_owners.get(token),
|
|
833
|
+
offer=_offer_rpc(*credentials, token=token), server_name="", ca_pem=b"", memo=owner,
|
|
820
834
|
)
|
|
821
835
|
|
|
822
836
|
return _side_path(token, make)
|
|
@@ -890,18 +904,29 @@ def describe() -> str:
|
|
|
890
904
|
return f"relay (direct path unavailable: {_BROKEN}; fix: pip install --force-reinstall remoterf)"
|
|
891
905
|
if not enabled():
|
|
892
906
|
return "relay (direct path disabled: REMOTERF_DIRECT=0)"
|
|
907
|
+
# A Global ref's path has no owner; its calls fall back to Global's relay.
|
|
908
|
+
partners = "; ".join(
|
|
909
|
+
f"partner {_partner_owners[key]}: "
|
|
910
|
+
+ (path.describe() if path.ready else path.describe().replace("relay", "via HOME", 1))
|
|
911
|
+
if key in _partner_owners else f"Global: {path.describe()}"
|
|
912
|
+
for key, path in list(_partners.items())
|
|
913
|
+
)
|
|
893
914
|
path = _path
|
|
894
915
|
if path is not None:
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
916
|
+
home = path.describe()
|
|
917
|
+
else:
|
|
918
|
+
try:
|
|
919
|
+
from .grpc_client import _current_profile
|
|
920
|
+
|
|
921
|
+
route = _active_route(_current_profile())
|
|
922
|
+
except Exception: # noqa: BLE001 - not configured, no home: nothing to add
|
|
923
|
+
route = None
|
|
924
|
+
if route is None or route["kind"] == "relay":
|
|
925
|
+
if partners: # the home path is not what these device calls take
|
|
926
|
+
return partners
|
|
927
|
+
return "relay (direct path off)"
|
|
928
|
+
home = "LAN (direct)" if route["kind"] == "lan" else str(route["kind"])
|
|
929
|
+
return f"{home}; {partners}" if partners else home
|
|
905
930
|
|
|
906
931
|
|
|
907
932
|
def stop() -> None:
|
|
@@ -27,6 +27,7 @@ from ..common.grpc import grpc_pb2, grpc_pb2_grpc
|
|
|
27
27
|
from ..common.grpc.v2_codec import decode_value, encode_value
|
|
28
28
|
from .v2_errors import (
|
|
29
29
|
RemoteRFError,
|
|
30
|
+
RemoteRFPolicyError,
|
|
30
31
|
RemoteRFProtocolError,
|
|
31
32
|
RemoteRFReservationError,
|
|
32
33
|
RemoteRFTransportError,
|
|
@@ -96,6 +97,8 @@ class _DevicePlaneStub:
|
|
|
96
97
|
args={"a": map_arg(self._token), "wire": map_arg(request.SerializeToString())},
|
|
97
98
|
)
|
|
98
99
|
except RuntimeError as exc:
|
|
100
|
+
if "response_too_large" in str(exc):
|
|
101
|
+
raise RemoteRFPolicyError(f"{self._label}{exc}") from exc
|
|
99
102
|
# A refusal the HOME or the owner carried in "a": no live
|
|
100
103
|
# reservation, a revoked agreement, a device no longer visible.
|
|
101
104
|
raise RemoteRFReservationError(f"{self._label}{exc}") from exc
|
|
@@ -228,21 +228,32 @@ def _federated_call(route, function_name, args):
|
|
|
228
228
|
refused = "a" in response.results and unmap_arg(response.results["a"]) == "the reservation has ended"
|
|
229
229
|
if not refused:
|
|
230
230
|
return response
|
|
231
|
+
def send(request):
|
|
232
|
+
if ref.startswith("rfg_"):
|
|
233
|
+
return _token_backend().device_token_rpc(
|
|
234
|
+
ref, getattr(route, "deployment_id", None), request.SerializeToString()
|
|
235
|
+
)
|
|
236
|
+
return federated_backend().device_rpc(ref, request.SerializeToString())
|
|
237
|
+
|
|
231
238
|
request = grpc_pb2.GenericRPCRequest(function_name=function_name, args=args)
|
|
232
239
|
for retry in (function_name.endswith(":GET"), False):
|
|
233
240
|
try:
|
|
234
|
-
|
|
235
|
-
raw = _token_backend().device_token_rpc(
|
|
236
|
-
ref, getattr(route, "deployment_id", None), request.SerializeToString()
|
|
237
|
-
)
|
|
238
|
-
else:
|
|
239
|
-
raw = federated_backend().device_rpc(ref, request.SerializeToString())
|
|
241
|
+
raw = send(request)
|
|
240
242
|
break
|
|
241
243
|
except AccountBackendError as exc:
|
|
242
244
|
if retry and exc.code == "internal_error" and exc.provenance == "transport":
|
|
243
245
|
# An older lab drops ~0.1% of relayed results when a clock second
|
|
244
246
|
# ticks mid-call; a GET changes nothing, so it is sent once more.
|
|
245
247
|
continue
|
|
248
|
+
if exc.code == "response_too_large" and exc.provenance == "destination":
|
|
249
|
+
raw = _kept_reply(send, args)
|
|
250
|
+
if raw is not None:
|
|
251
|
+
break
|
|
252
|
+
raise RuntimeError(
|
|
253
|
+
"response_too_large [destination]: the reply is over the Global relay's "
|
|
254
|
+
"768 KiB limit for one call and this lab does not page it; read fewer "
|
|
255
|
+
"samples per call (about 98k complex64) or use a direct path"
|
|
256
|
+
) from None
|
|
246
257
|
if ref.startswith("rfg_") and str(exc) == "not_found":
|
|
247
258
|
raise RuntimeError(
|
|
248
259
|
"this RemoteRF Global does not accept device tokens yet; use the device reference"
|
|
@@ -660,6 +671,34 @@ def _rx_readahead(prefix, args, token):
|
|
|
660
671
|
return grpc_pb2.GenericRPCResponse(results={"rx": first})
|
|
661
672
|
|
|
662
673
|
|
|
674
|
+
# A newer lab keeps a reply too large for the relay (up to 4 MiB) for its
|
|
675
|
+
# caller, who fetches it in pages; an older lab refuses the first page unrun.
|
|
676
|
+
_PAGE_FUNCTION = "Federation:page:CALL2"
|
|
677
|
+
_MAX_KEPT_REPLY = 4 * 1024 * 1024 + 65536
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
def _kept_reply(send, args):
|
|
681
|
+
from ..deployment.http import AccountBackendError
|
|
682
|
+
data, nonce = b"", ""
|
|
683
|
+
while True:
|
|
684
|
+
page_args = {"arg1": map_arg(len(data)), "arg2": map_arg(nonce)}
|
|
685
|
+
if "a" in args:
|
|
686
|
+
page_args["a"] = args["a"]
|
|
687
|
+
try:
|
|
688
|
+
raw = send(grpc_pb2.GenericRPCRequest(function_name=_PAGE_FUNCTION, args=page_args))
|
|
689
|
+
except AccountBackendError:
|
|
690
|
+
return None
|
|
691
|
+
reply = grpc_pb2.GenericRPCResponse.FromString(raw)
|
|
692
|
+
if not {"page", "size", "nonce"} <= set(reply.results):
|
|
693
|
+
return None
|
|
694
|
+
page, size, nonce = (unmap_arg(reply.results[key]) for key in ("page", "size", "nonce"))
|
|
695
|
+
if not isinstance(page, bytes) or not page or type(size) is not int or size > _MAX_KEPT_REPLY:
|
|
696
|
+
return None
|
|
697
|
+
data += page
|
|
698
|
+
if len(data) >= size:
|
|
699
|
+
return data if len(data) == size else None
|
|
700
|
+
|
|
701
|
+
|
|
663
702
|
# Device tokens whose Server answered an rx in ci16: it decodes ci16 too (the
|
|
664
703
|
# same Gate I slice 3 change), so their tx may go out that way.
|
|
665
704
|
_ci16_tokens: set = set()
|
|
@@ -252,6 +252,9 @@ def _validate_client_modules(value) -> dict:
|
|
|
252
252
|
out = {}
|
|
253
253
|
for alias, module_path in value.items():
|
|
254
254
|
alias = _require_identifier(alias, field="client_modules alias")
|
|
255
|
+
module_path = str(module_path or "").strip()
|
|
256
|
+
if module_path.startswith("remoteRF."): # Servers still name the package as before 2.2.0
|
|
257
|
+
module_path = "remoterf." + module_path[len("remoteRF."):]
|
|
255
258
|
module_path = _require_dotted_identifier(
|
|
256
259
|
module_path,
|
|
257
260
|
field=f"client_modules[{alias!r}]",
|
|
@@ -1415,7 +1418,8 @@ _DRIVERS_DIR = Path(__file__).parent
|
|
|
1415
1418
|
def _write_driver_files(schema: dict) -> Path:
|
|
1416
1419
|
pkg_dir = _write_v2_driver_files(schema) if str(schema.get("schema_version")) == "2.0" else _write_v1_driver_files(schema)
|
|
1417
1420
|
stale = f"remoterf.drivers.{pkg_dir.name}"
|
|
1418
|
-
|
|
1421
|
+
old = "remoteRF" + stale[len("remoterf"):] # the `import remoteRF` alias keys
|
|
1422
|
+
for name in [m for m in sys.modules if m in (stale, old) or m.startswith((stale + ".", old + "."))]:
|
|
1419
1423
|
del sys.modules[name]
|
|
1420
1424
|
return pkg_dir
|
|
1421
1425
|
|
|
@@ -1685,7 +1689,13 @@ def install_driver_if_stale(*, token: str, current_hash: str) -> bool:
|
|
|
1685
1689
|
return False
|
|
1686
1690
|
|
|
1687
1691
|
device_type = _schema_device_type(schema)
|
|
1688
|
-
|
|
1692
|
+
try:
|
|
1693
|
+
pkg_dir = _write_driver_files(schema)
|
|
1694
|
+
except OSError as exc:
|
|
1695
|
+
# A read-only install (system site-packages): the installed driver keeps working.
|
|
1696
|
+
_verified.add(key)
|
|
1697
|
+
print(f"Driver for '{device_type}' is out of date and could not be updated ({exc}).")
|
|
1698
|
+
return False
|
|
1689
1699
|
if _read_schema_hash(pkg_dir / f"{device_type}_remote.py") == current_hash:
|
|
1690
1700
|
return False
|
|
1691
1701
|
print(
|
|
@@ -14,21 +14,35 @@
|
|
|
14
14
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
15
15
|
|
|
16
16
|
# Auto-generated from IDL schema — do not edit by hand.
|
|
17
|
-
# device_type: rtl_sdr driver_version: 0.1.1 schema_hash: sha256:
|
|
17
|
+
# device_type: rtl_sdr driver_version: 0.1.1 schema_hash: sha256:6603ec8e642c87073c35202980de9a3d9e4bb6eb88967686d3810c389fb4fb97
|
|
18
18
|
|
|
19
19
|
_PREFIX = "Rtl_sdr"
|
|
20
|
-
_SCHEMA_HASH = "sha256:
|
|
20
|
+
_SCHEMA_HASH = "sha256:6603ec8e642c87073c35202980de9a3d9e4bb6eb88967686d3810c389fb4fb97"
|
|
21
21
|
_CLIENT_MODULES = {}
|
|
22
22
|
_CLIENT_OBJECTS = {}
|
|
23
23
|
|
|
24
|
+
import os
|
|
25
|
+
|
|
24
26
|
from importlib import import_module as _import_module
|
|
25
27
|
|
|
26
|
-
from ...core.grpc_client import rpc_client
|
|
27
28
|
from ...common.utils import map_arg, unmap_arg
|
|
29
|
+
from .._virtual import (
|
|
30
|
+
is_virtual_token,
|
|
31
|
+
make_virtual_token,
|
|
32
|
+
virtual_call,
|
|
33
|
+
virtual_get,
|
|
34
|
+
virtual_set,
|
|
35
|
+
)
|
|
28
36
|
|
|
29
37
|
_NO_ARG = object()
|
|
30
38
|
|
|
31
39
|
|
|
40
|
+
def _rpc_client(*, function_name, args):
|
|
41
|
+
from ...core.grpc_client import rpc_client
|
|
42
|
+
|
|
43
|
+
return rpc_client(function_name=function_name, args=args)
|
|
44
|
+
|
|
45
|
+
|
|
32
46
|
for _alias, _module_path in _CLIENT_MODULES.items():
|
|
33
47
|
globals()[_alias] = _import_module(_module_path)
|
|
34
48
|
|
|
@@ -71,27 +85,34 @@ def _wrap_client_return(spec, self_obj, result):
|
|
|
71
85
|
|
|
72
86
|
|
|
73
87
|
def _try_get(prop, token):
|
|
74
|
-
|
|
88
|
+
if is_virtual_token(token):
|
|
89
|
+
return virtual_get(token, prop)
|
|
90
|
+
return unmap_arg(_rpc_client(
|
|
75
91
|
function_name=f"{_PREFIX}:{prop}:GET",
|
|
76
92
|
args={'a': map_arg(token)},
|
|
77
93
|
).results[prop])
|
|
78
94
|
|
|
79
95
|
|
|
80
96
|
def _try_set(prop, value, token):
|
|
81
|
-
|
|
97
|
+
if is_virtual_token(token):
|
|
98
|
+
virtual_set(token, prop, value)
|
|
99
|
+
return None
|
|
100
|
+
_rpc_client(
|
|
82
101
|
function_name=f"{_PREFIX}:{prop}:SET",
|
|
83
102
|
args={prop: map_arg(value), 'a': map_arg(token)},
|
|
84
103
|
)
|
|
85
104
|
|
|
86
105
|
|
|
87
106
|
def _try_call(prop, token, arg=_NO_ARG):
|
|
107
|
+
if is_virtual_token(token):
|
|
108
|
+
return virtual_call(token, prop, arg, has_arg=arg is not _NO_ARG)
|
|
88
109
|
if arg is _NO_ARG:
|
|
89
|
-
resp =
|
|
110
|
+
resp = _rpc_client(
|
|
90
111
|
function_name=f"{_PREFIX}:{prop}:CALL0",
|
|
91
112
|
args={'a': map_arg(token)},
|
|
92
113
|
)
|
|
93
114
|
else:
|
|
94
|
-
resp =
|
|
115
|
+
resp = _rpc_client(
|
|
95
116
|
function_name=f"{_PREFIX}:{prop}:CALL1",
|
|
96
117
|
args={'a': map_arg(token), 'arg1': map_arg(arg)},
|
|
97
118
|
)
|
|
@@ -100,12 +121,15 @@ def _try_call(prop, token, arg=_NO_ARG):
|
|
|
100
121
|
|
|
101
122
|
|
|
102
123
|
def _try_calln(prop, token, kwargs):
|
|
124
|
+
if is_virtual_token(token):
|
|
125
|
+
filtered = {key: value for key, value in dict(kwargs).items() if value is not _NO_ARG}
|
|
126
|
+
return virtual_call(token, prop, filtered, has_arg=True)
|
|
103
127
|
payload = {'a': map_arg(token)}
|
|
104
128
|
for key, value in dict(kwargs).items():
|
|
105
129
|
if value is _NO_ARG:
|
|
106
130
|
continue
|
|
107
131
|
payload[str(key)] = map_arg(value)
|
|
108
|
-
resp =
|
|
132
|
+
resp = _rpc_client(
|
|
109
133
|
function_name=f"{_PREFIX}:{prop}:CALLN",
|
|
110
134
|
args=payload,
|
|
111
135
|
)
|
|
@@ -114,7 +138,15 @@ def _try_calln(prop, token, kwargs):
|
|
|
114
138
|
|
|
115
139
|
class RtlSdr:
|
|
116
140
|
|
|
117
|
-
def __init__(self, token: str):
|
|
141
|
+
def __init__(self, token: str = None, *, virtual: bool = False):
|
|
142
|
+
self.virtual = bool(virtual)
|
|
143
|
+
if self.virtual:
|
|
144
|
+
self.token = make_virtual_token(token, device_type="rtl_sdr")
|
|
145
|
+
return
|
|
146
|
+
if token is None:
|
|
147
|
+
token = os.getenv("REMOTERF_TOKEN")
|
|
148
|
+
if not token:
|
|
149
|
+
raise ValueError("A reservation token is required; pass token= or set REMOTERF_TOKEN")
|
|
118
150
|
self.token = token
|
|
119
151
|
from ..dynamic_device import install_driver_if_stale
|
|
120
152
|
install_driver_if_stale(token=token, current_hash=_SCHEMA_HASH)
|
|
@@ -21,14 +21,28 @@ _SCHEMA_HASH = "sha256:320a89d95075a472d7899ff2f98b9fdee05c0be96fa5651bf5c4f0e5d
|
|
|
21
21
|
_CLIENT_MODULES = {'ti_mmwave': 'remoterf.drivers.support.ti_mmwave'}
|
|
22
22
|
_CLIENT_OBJECTS = {}
|
|
23
23
|
|
|
24
|
+
import os
|
|
25
|
+
|
|
24
26
|
from importlib import import_module as _import_module
|
|
25
27
|
|
|
26
|
-
from ...core.grpc_client import rpc_client
|
|
27
28
|
from ...common.utils import map_arg, unmap_arg
|
|
29
|
+
from .._virtual import (
|
|
30
|
+
is_virtual_token,
|
|
31
|
+
make_virtual_token,
|
|
32
|
+
virtual_call,
|
|
33
|
+
virtual_get,
|
|
34
|
+
virtual_set,
|
|
35
|
+
)
|
|
28
36
|
|
|
29
37
|
_NO_ARG = object()
|
|
30
38
|
|
|
31
39
|
|
|
40
|
+
def _rpc_client(*, function_name, args):
|
|
41
|
+
from ...core.grpc_client import rpc_client
|
|
42
|
+
|
|
43
|
+
return rpc_client(function_name=function_name, args=args)
|
|
44
|
+
|
|
45
|
+
|
|
32
46
|
for _alias, _module_path in _CLIENT_MODULES.items():
|
|
33
47
|
globals()[_alias] = _import_module(_module_path)
|
|
34
48
|
|
|
@@ -71,27 +85,34 @@ def _wrap_client_return(spec, self_obj, result):
|
|
|
71
85
|
|
|
72
86
|
|
|
73
87
|
def _try_get(prop, token):
|
|
74
|
-
|
|
88
|
+
if is_virtual_token(token):
|
|
89
|
+
return virtual_get(token, prop)
|
|
90
|
+
return unmap_arg(_rpc_client(
|
|
75
91
|
function_name=f"{_PREFIX}:{prop}:GET",
|
|
76
92
|
args={'a': map_arg(token)},
|
|
77
93
|
).results[prop])
|
|
78
94
|
|
|
79
95
|
|
|
80
96
|
def _try_set(prop, value, token):
|
|
81
|
-
|
|
97
|
+
if is_virtual_token(token):
|
|
98
|
+
virtual_set(token, prop, value)
|
|
99
|
+
return None
|
|
100
|
+
_rpc_client(
|
|
82
101
|
function_name=f"{_PREFIX}:{prop}:SET",
|
|
83
102
|
args={prop: map_arg(value), 'a': map_arg(token)},
|
|
84
103
|
)
|
|
85
104
|
|
|
86
105
|
|
|
87
106
|
def _try_call(prop, token, arg=_NO_ARG):
|
|
107
|
+
if is_virtual_token(token):
|
|
108
|
+
return virtual_call(token, prop, arg, has_arg=arg is not _NO_ARG)
|
|
88
109
|
if arg is _NO_ARG:
|
|
89
|
-
resp =
|
|
110
|
+
resp = _rpc_client(
|
|
90
111
|
function_name=f"{_PREFIX}:{prop}:CALL0",
|
|
91
112
|
args={'a': map_arg(token)},
|
|
92
113
|
)
|
|
93
114
|
else:
|
|
94
|
-
resp =
|
|
115
|
+
resp = _rpc_client(
|
|
95
116
|
function_name=f"{_PREFIX}:{prop}:CALL1",
|
|
96
117
|
args={'a': map_arg(token), 'arg1': map_arg(arg)},
|
|
97
118
|
)
|
|
@@ -100,12 +121,15 @@ def _try_call(prop, token, arg=_NO_ARG):
|
|
|
100
121
|
|
|
101
122
|
|
|
102
123
|
def _try_calln(prop, token, kwargs):
|
|
124
|
+
if is_virtual_token(token):
|
|
125
|
+
filtered = {key: value for key, value in dict(kwargs).items() if value is not _NO_ARG}
|
|
126
|
+
return virtual_call(token, prop, filtered, has_arg=True)
|
|
103
127
|
payload = {'a': map_arg(token)}
|
|
104
128
|
for key, value in dict(kwargs).items():
|
|
105
129
|
if value is _NO_ARG:
|
|
106
130
|
continue
|
|
107
131
|
payload[str(key)] = map_arg(value)
|
|
108
|
-
resp =
|
|
132
|
+
resp = _rpc_client(
|
|
109
133
|
function_name=f"{_PREFIX}:{prop}:CALLN",
|
|
110
134
|
args=payload,
|
|
111
135
|
)
|
|
@@ -114,7 +138,15 @@ def _try_calln(prop, token, kwargs):
|
|
|
114
138
|
|
|
115
139
|
class TiMmWave:
|
|
116
140
|
|
|
117
|
-
def __init__(self, token: str):
|
|
141
|
+
def __init__(self, token: str = None, *, virtual: bool = False):
|
|
142
|
+
self.virtual = bool(virtual)
|
|
143
|
+
if self.virtual:
|
|
144
|
+
self.token = make_virtual_token(token, device_type="ti_mmwave")
|
|
145
|
+
return
|
|
146
|
+
if token is None:
|
|
147
|
+
token = os.getenv("REMOTERF_TOKEN")
|
|
148
|
+
if not token:
|
|
149
|
+
raise ValueError("A reservation token is required; pass token= or set REMOTERF_TOKEN")
|
|
118
150
|
self.token = token
|
|
119
151
|
from ..dynamic_device import install_driver_if_stale
|
|
120
152
|
install_driver_if_stale(token=token, current_hash=_SCHEMA_HASH)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: remoterf
|
|
3
|
-
Version: 2.5.
|
|
3
|
+
Version: 2.5.2
|
|
4
4
|
Summary: A python API to remotely access signal centric hardware. Client-side only! Courtesy of Wireless Lab @ UCLA & Prof. Ian Roberts.
|
|
5
5
|
Author-email: Ethan Ge <ethoGalaxy@gmail.com>
|
|
6
6
|
License-Expression: GPL-3.0-or-later
|
|
@@ -52,6 +52,8 @@ source venv/bin/activate # Activate virtual environment
|
|
|
52
52
|
pip install remoterf # Install remoterf
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
+
The package was named `remoteRF` before 2.2.0. Old code that does `import remoteRF` or `from remoteRF.drivers... import ...` still runs, with a one-time warning; change it to `remoterf`.
|
|
56
|
+
|
|
55
57
|
## NI USRP-2901, Ettus B205-mini, B200-family, and USRP N210
|
|
56
58
|
|
|
57
59
|
The packaged client natively includes the shared USRP schema for the qualified
|
|
@@ -145,6 +147,12 @@ not close the physical radio. `read_samples` and `read_bytes` are bounded to a
|
|
|
145
147
|
4 MiB response. Call them repeatedly for longer captures. Native asynchronous
|
|
146
148
|
callbacks are not transported remotely.
|
|
147
149
|
|
|
150
|
+
Through the RemoteRF Global relay one reply is capped at 768 KiB. A lab on a
|
|
151
|
+
current Server keeps a larger reply (up to 4 MiB) and the client fetches it in
|
|
152
|
+
pages; an older lab refuses it with `response_too_large`, so keep reads there
|
|
153
|
+
to about 98k complex64 samples (`read_samples(65536)` is safe). LAN and direct
|
|
154
|
+
paths are not capped.
|
|
155
|
+
|
|
148
156
|
When a Tailscale address differs from the IP or DNS identity in the server
|
|
149
157
|
certificate, keep TLS verification enabled and set the expected identity:
|
|
150
158
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
LICENSE
|
|
2
2
|
README.md
|
|
3
3
|
pyproject.toml
|
|
4
|
+
setup.py
|
|
5
|
+
src/_remoterf_alias.py
|
|
4
6
|
src/remoterf.py
|
|
5
7
|
src/remoterf/__init__.py
|
|
6
8
|
src/remoterf/remoterf_cli.py
|
|
@@ -70,6 +72,7 @@ src/remoterf/drivers/usrp/usrp_remote.py
|
|
|
70
72
|
src/remoterf/drivers/usrp/usrp_remote.pyi
|
|
71
73
|
tests/test_banner.py
|
|
72
74
|
tests/test_batch.py
|
|
75
|
+
tests/test_bundled_drivers.py
|
|
73
76
|
tests/test_cli_login_fresh.py
|
|
74
77
|
tests/test_config_address.py
|
|
75
78
|
tests/test_deployment_accounts.py
|
|
@@ -88,9 +91,11 @@ tests/test_grpc_client_target.py
|
|
|
88
91
|
tests/test_homes.py
|
|
89
92
|
tests/test_native_account_credentials.py
|
|
90
93
|
tests/test_native_devices.py
|
|
94
|
+
tests/test_old_import_name.py
|
|
91
95
|
tests/test_partner_dynamic_v2.py
|
|
92
96
|
tests/test_pluto_virtual.py
|
|
93
97
|
tests/test_process_arg.py
|
|
98
|
+
tests/test_relay_paging.py
|
|
94
99
|
tests/test_remoterf_cli.py
|
|
95
100
|
tests/test_rx_readahead.py
|
|
96
101
|
tests/test_ti_mmwave.py
|