RemoteRF 2.5.0__tar.gz → 2.5.1__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.
Files changed (99) hide show
  1. {remoterf-2.5.0/src/remoterf.egg-info → remoterf-2.5.1}/PKG-INFO +1 -1
  2. {remoterf-2.5.0 → remoterf-2.5.1}/pyproject.toml +1 -1
  3. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/core/direct_path.py +40 -17
  4. {remoterf-2.5.0 → remoterf-2.5.1/src/remoterf.egg-info}/PKG-INFO +1 -1
  5. {remoterf-2.5.0 → remoterf-2.5.1}/tests/test_direct_path_partner.py +65 -3
  6. {remoterf-2.5.0 → remoterf-2.5.1}/LICENSE +0 -0
  7. {remoterf-2.5.0 → remoterf-2.5.1}/README.md +0 -0
  8. {remoterf-2.5.0 → remoterf-2.5.1}/setup.cfg +0 -0
  9. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/__init__.py +0 -0
  10. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/common/__init__.py +0 -0
  11. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/common/grpc/__init__.py +0 -0
  12. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/common/grpc/deployment_capabilities.proto +0 -0
  13. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/common/grpc/deployment_capabilities_pb2.py +0 -0
  14. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/common/grpc/deployment_capabilities_pb2_grpc.py +0 -0
  15. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/common/grpc/grpc.proto +0 -0
  16. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/common/grpc/grpc_pb2.py +0 -0
  17. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/common/grpc/grpc_pb2_grpc.py +0 -0
  18. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/common/grpc/v2_codec.py +0 -0
  19. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/common/tos_notice.txt +0 -0
  20. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/common/utils/__init__.py +0 -0
  21. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/common/utils/ansi_codes.py +0 -0
  22. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/common/utils/api_token.py +0 -0
  23. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/common/utils/banner.py +0 -0
  24. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/common/utils/list_string.py +0 -0
  25. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/common/utils/process_arg.py +0 -0
  26. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/config/__init__.py +0 -0
  27. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/config/cert_fetcher.py +0 -0
  28. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/config/config.py +0 -0
  29. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/core/__init__.py +0 -0
  30. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/core/acc_login.py +0 -0
  31. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/core/app.py +0 -0
  32. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/core/banner_note.txt +0 -0
  33. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/core/dynamic_v2_transport.py +0 -0
  34. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/core/grpc_acc.py +0 -0
  35. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/core/grpc_client.py +0 -0
  36. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/core/secure_channel.py +0 -0
  37. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/core/v2_errors.py +0 -0
  38. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/deployment/__init__.py +0 -0
  39. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/deployment/backend.py +0 -0
  40. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/deployment/direct.py +0 -0
  41. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/deployment/homes.py +0 -0
  42. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/deployment/http.py +0 -0
  43. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/deployment/state.py +0 -0
  44. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/drivers/__init__.py +0 -0
  45. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/drivers/_virtual.py +0 -0
  46. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/drivers/adalm_pluto/__init__.py +0 -0
  47. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/drivers/adalm_pluto/adalm_pluto_remote.py +0 -0
  48. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/drivers/dynamic_device.py +0 -0
  49. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/drivers/dynamic_v2.py +0 -0
  50. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/drivers/hackrf/__init__.py +0 -0
  51. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/drivers/hackrf/hackrf_remote.py +0 -0
  52. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/drivers/rtl_sdr/__init__.py +0 -0
  53. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/drivers/rtl_sdr/rtl_sdr_remote.py +0 -0
  54. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/drivers/sim_sdr/__init__.py +0 -0
  55. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/drivers/sim_sdr/sim_sdr_remote.py +0 -0
  56. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/drivers/support/__init__.py +0 -0
  57. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/drivers/support/ti_mmwave.py +0 -0
  58. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/drivers/support/uhd.py +0 -0
  59. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/drivers/support/uhd_dsp.py +0 -0
  60. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/drivers/support/uhd_v2.py +0 -0
  61. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/drivers/ti_mmwave/__init__.py +0 -0
  62. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/drivers/ti_mmwave/ti_mmwave_remote.py +0 -0
  63. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/drivers/usrp/__init__.py +0 -0
  64. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/drivers/usrp/usrp_remote.py +0 -0
  65. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/drivers/usrp/usrp_remote.pyi +0 -0
  66. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/remoterf_cli.py +0 -0
  67. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf/version.py +0 -0
  68. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf.egg-info/SOURCES.txt +0 -0
  69. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf.egg-info/dependency_links.txt +0 -0
  70. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf.egg-info/entry_points.txt +0 -0
  71. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf.egg-info/requires.txt +0 -0
  72. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf.egg-info/top_level.txt +0 -0
  73. {remoterf-2.5.0 → remoterf-2.5.1}/src/remoterf.py +0 -0
  74. {remoterf-2.5.0 → remoterf-2.5.1}/tests/test_banner.py +0 -0
  75. {remoterf-2.5.0 → remoterf-2.5.1}/tests/test_batch.py +0 -0
  76. {remoterf-2.5.0 → remoterf-2.5.1}/tests/test_cli_login_fresh.py +0 -0
  77. {remoterf-2.5.0 → remoterf-2.5.1}/tests/test_config_address.py +0 -0
  78. {remoterf-2.5.0 → remoterf-2.5.1}/tests/test_deployment_accounts.py +0 -0
  79. {remoterf-2.5.0 → remoterf-2.5.1}/tests/test_device_routes.py +0 -0
  80. {remoterf-2.5.0 → remoterf-2.5.1}/tests/test_direct_path_fixed.py +0 -0
  81. {remoterf-2.5.0 → remoterf-2.5.1}/tests/test_direct_path_global.py +0 -0
  82. {remoterf-2.5.0 → remoterf-2.5.1}/tests/test_direct_path_race.py +0 -0
  83. {remoterf-2.5.0 → remoterf-2.5.1}/tests/test_dynamic_device_codegen.py +0 -0
  84. {remoterf-2.5.0 → remoterf-2.5.1}/tests/test_dynamic_v2.py +0 -0
  85. {remoterf-2.5.0 → remoterf-2.5.1}/tests/test_federated_devices.py +0 -0
  86. {remoterf-2.5.0 → remoterf-2.5.1}/tests/test_fetch_idl_fallback.py +0 -0
  87. {remoterf-2.5.0 → remoterf-2.5.1}/tests/test_finish_prompt.py +0 -0
  88. {remoterf-2.5.0 → remoterf-2.5.1}/tests/test_gate_i_direct_path.py +0 -0
  89. {remoterf-2.5.0 → remoterf-2.5.1}/tests/test_grpc_client_target.py +0 -0
  90. {remoterf-2.5.0 → remoterf-2.5.1}/tests/test_homes.py +0 -0
  91. {remoterf-2.5.0 → remoterf-2.5.1}/tests/test_native_account_credentials.py +0 -0
  92. {remoterf-2.5.0 → remoterf-2.5.1}/tests/test_native_devices.py +0 -0
  93. {remoterf-2.5.0 → remoterf-2.5.1}/tests/test_partner_dynamic_v2.py +0 -0
  94. {remoterf-2.5.0 → remoterf-2.5.1}/tests/test_pluto_virtual.py +0 -0
  95. {remoterf-2.5.0 → remoterf-2.5.1}/tests/test_process_arg.py +0 -0
  96. {remoterf-2.5.0 → remoterf-2.5.1}/tests/test_remoterf_cli.py +0 -0
  97. {remoterf-2.5.0 → remoterf-2.5.1}/tests/test_rx_readahead.py +0 -0
  98. {remoterf-2.5.0 → remoterf-2.5.1}/tests/test_ti_mmwave.py +0 -0
  99. {remoterf-2.5.0 → remoterf-2.5.1}/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.0
3
+ Version: 2.5.1
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "remoterf"
7
- version = "2.5.0"
7
+ version = "2.5.1"
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"
@@ -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 = {"un": map_arg(username), "pw": map_arg(secret), "ice": map_arg(json.dumps(offer))}
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
- raise PathError(unmap_arg(response.results["ace"]))
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
- return None
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,27 @@ 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
+ partners = "; ".join(
908
+ f"partner {_partner_owners[key]}: "
909
+ + (path.describe() if path.ready else path.describe().replace("relay", "via HOME", 1))
910
+ for key, path in list(_partners.items()) if key in _partner_owners
911
+ )
893
912
  path = _path
894
913
  if path is not None:
895
- return path.describe()
896
- try:
897
- from .grpc_client import _current_profile
898
-
899
- route = _active_route(_current_profile())
900
- except Exception: # noqa: BLE001 - not configured, no home: nothing to add
901
- route = None
902
- if route is None or route["kind"] == "relay":
903
- return "relay (direct path off)"
904
- return "LAN (direct)" if route["kind"] == "lan" else str(route["kind"])
914
+ home = path.describe()
915
+ else:
916
+ try:
917
+ from .grpc_client import _current_profile
918
+
919
+ route = _active_route(_current_profile())
920
+ except Exception: # noqa: BLE001 - not configured, no home: nothing to add
921
+ route = None
922
+ if route is None or route["kind"] == "relay":
923
+ if partners: # the home path is not what these device calls take
924
+ return partners
925
+ return "relay (direct path off)"
926
+ home = "LAN (direct)" if route["kind"] == "lan" else str(route["kind"])
927
+ return f"{home}; {partners}" if partners else home
905
928
 
906
929
 
907
930
  def stop() -> None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: remoterf
3
- Version: 2.5.0
3
+ Version: 2.5.1
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
@@ -51,6 +51,9 @@ def setup_function(function):
51
51
  FakePath.instances.clear()
52
52
 
53
53
 
54
+ teardown_function = setup_function # no fake path outlives its test
55
+
56
+
54
57
  def test_a_marked_token_gets_its_own_path_and_other_tokens_do_not(monkeypatch):
55
58
  monkeypatch.setenv("REMOTERF_DIRECT", "1")
56
59
  monkeypatch.setattr(direct_path, "DirectPath", FakePath)
@@ -105,21 +108,80 @@ def test_rpc_client_routes_by_the_token_the_call_carries(monkeypatch):
105
108
  assert routed[-1] is None # only a string token can name a partner path
106
109
 
107
110
 
108
- def test_no_login_means_no_partner_path_and_disabled_means_none(monkeypatch):
111
+ def test_no_login_offers_with_the_token_alone_and_disabled_means_none(monkeypatch):
109
112
  monkeypatch.setattr(direct_path, "DirectPath", FakePath)
110
113
  monkeypatch.setattr(direct_path, "_from_stored_login", lambda: None)
111
114
  monkeypatch.setattr(direct_path, "_stored_login", lambda: None)
112
115
  direct_path._login = None
113
116
  monkeypatch.setenv("REMOTERF_DIRECT", "1")
114
117
  direct_path.mark_partner("tok", "owner-uuid")
115
- assert direct_path.current(token="tok") is None and FakePath.instances == [] # nothing to log in with
118
+ path = direct_path.current(token="tok")
119
+ assert path is FakePath.instances[0] and path.memo == "owner-uuid|token" # its own memo scope
120
+ seen = {}
121
+
122
+ def fake_rpc(*, function_name, args, connection=None):
123
+ seen.update(function_name=function_name, args={k: unmap_arg(v) for k, v in args.items()})
124
+ return grpc_pb2.GenericRPCResponse(results={"ice": map_arg("{}")})
125
+
126
+ monkeypatch.setattr(grpc_client, "rpc_client", fake_rpc)
127
+ path.offer({"id": "x"})
128
+ assert seen["function_name"] == "ACC:direct_offer" and sorted(seen["args"]) == ["a", "ice"]
129
+ assert seen["args"]["a"] == "tok"
130
+ direct_path.stop()
116
131
  direct_path._login = ("alice", "secret")
117
- assert direct_path.current(token="tok") is FakePath.instances[0]
132
+ assert direct_path.current(token="tok").memo == "owner-uuid" # a login signs as before
118
133
  monkeypatch.setenv("REMOTERF_DIRECT", "0")
119
134
  assert direct_path.current(token="tok") is None # disabled: nothing, however marked
120
135
  direct_path._login = None
121
136
 
122
137
 
138
+ def test_an_old_home_refusing_a_token_offer_means_no_direct_path(monkeypatch):
139
+ replies = []
140
+ monkeypatch.setattr(grpc_client, "rpc_client", lambda **k: grpc_pb2.GenericRPCResponse(
141
+ results={"ace": map_arg(replies.pop())}))
142
+ send = direct_path._offer_rpc(None, None, token="tok")
143
+ # A Server older than token offers fails reading the missing login.
144
+ replies.append("Internal server error; please try again or contact your instructor.")
145
+ try:
146
+ send({})
147
+ except direct_path.PathUnavailable as exc:
148
+ assert "needs a login" in str(exc)
149
+ else:
150
+ raise AssertionError("an old home must stop the path")
151
+ replies.append("That reservation is not active now.") # a new home's refusal: retried later
152
+ try:
153
+ send({})
154
+ except direct_path.PathError as exc:
155
+ assert not isinstance(exc, direct_path.PathUnavailable)
156
+ else:
157
+ raise AssertionError("a refusal is no answer")
158
+
159
+
160
+ def test_describe_reports_partner_paths(monkeypatch):
161
+ monkeypatch.setattr(direct_path, "DirectPath", FakePath)
162
+ monkeypatch.setattr(direct_path, "_from_stored_login", lambda: None)
163
+ monkeypatch.setattr(grpc_client, "_current_profile", lambda: None)
164
+ monkeypatch.setattr(direct_path, "_active_route", lambda profile: None)
165
+ monkeypatch.setattr(direct_path, "_credentials", lambda: None)
166
+ monkeypatch.setenv("REMOTERF_DIRECT", "1")
167
+ assert direct_path.describe() == "relay (direct path off)" # nothing partnered yet
168
+ direct_path.mark_partner("tok", "lab-b")
169
+ path = direct_path.current(token="tok")
170
+ path.describe = lambda: "direct 192.0.2.7:4433 (3 calls)"
171
+ assert direct_path.describe() == "partner lab-b: direct 192.0.2.7:4433 (3 calls)"
172
+ path.state = "down"
173
+ path.describe = lambda: "relay (direct path down: timed out; retrying)"
174
+ assert direct_path.describe() == "partner lab-b: via HOME (direct path down: timed out; retrying)"
175
+ direct_path._path = home = FakePath(offer=None, server_name="", ca_pem=b"")
176
+ home.describe = lambda: "direct 192.0.2.1:4433"
177
+ assert direct_path.describe() == (
178
+ "direct 192.0.2.1:4433; partner lab-b: via HOME (direct path down: timed out; retrying)"
179
+ )
180
+ direct_path._path = None
181
+ monkeypatch.setattr(direct_path, "_active_route", lambda profile: {"kind": "lan"})
182
+ assert direct_path.describe().startswith("LAN (direct); partner lab-b: via HOME")
183
+
184
+
123
185
  def test_a_partner_path_that_is_down_sends_the_call_to_the_home(monkeypatch):
124
186
  monkeypatch.setattr(direct_path, "DirectPath", FakePath)
125
187
  direct_path._login = ("alice", "secret")
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes