remoteRF-server-testing 0.0.4__tar.gz → 0.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.
Files changed (50) hide show
  1. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/PKG-INFO +1 -1
  2. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/pyproject.toml +1 -1
  3. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server/server/rpc_manager.py +107 -55
  4. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server_testing.egg-info/PKG-INFO +1 -1
  5. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server_testing.egg-info/SOURCES.txt +2 -1
  6. remoterf_server_testing-0.0.6/tests/test_rpc_manager.py +166 -0
  7. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/README.md +0 -0
  8. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/setup.cfg +0 -0
  9. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server/__init__.py +0 -0
  10. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server/common/__init__.py +0 -0
  11. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server/common/grpc/__init__.py +0 -0
  12. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server/common/grpc/grpc_host_pb2.py +0 -0
  13. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server/common/grpc/grpc_host_pb2_grpc.py +0 -0
  14. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server/common/grpc/grpc_pb2.py +0 -0
  15. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server/common/grpc/grpc_pb2_grpc.py +0 -0
  16. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server/common/idl/__init__.py +0 -0
  17. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server/common/idl/device_schema.py +0 -0
  18. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server/common/idl/pluto_schema.py +0 -0
  19. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server/common/idl/schema.py +0 -0
  20. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server/common/utils/__init__.py +0 -0
  21. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server/common/utils/ansi_codes.py +0 -0
  22. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server/common/utils/api_token.py +0 -0
  23. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server/common/utils/db_connection.py +0 -0
  24. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server/common/utils/db_location.py +0 -0
  25. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server/common/utils/list_string.py +0 -0
  26. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server/common/utils/process_arg.py +0 -0
  27. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server/drivers/__init__.py +0 -0
  28. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server/drivers/adalm_pluto/__init__.py +0 -0
  29. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server/drivers/adalm_pluto/pluto_remote_server.py +0 -0
  30. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server/host/__init__.py +0 -0
  31. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server/host/host_auth_token.py +0 -0
  32. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server/host/host_directory_store.py +0 -0
  33. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server/host/host_tunnel_server.py +0 -0
  34. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server/server/__init__.py +0 -0
  35. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server/server/acc_perms.py +0 -0
  36. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server/server/cert_provider.py +0 -0
  37. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server/server/device_manager.py +0 -0
  38. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server/server/grpc_server.py +0 -0
  39. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server/server/reservation.py +0 -0
  40. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server/server/user_group_cli.py +0 -0
  41. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server/server/user_group_handler.py +0 -0
  42. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server/serverrf_cli.py +0 -0
  43. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server/tools/__init__.py +0 -0
  44. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server/tools/gen_certs.py +0 -0
  45. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server/tools/gist_status.py +0 -0
  46. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server/tools/gist_status_testing.py +0 -0
  47. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server_testing.egg-info/dependency_links.txt +0 -0
  48. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server_testing.egg-info/entry_points.txt +0 -0
  49. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server_testing.egg-info/requires.txt +0 -0
  50. {remoterf_server_testing-0.0.4 → remoterf_server_testing-0.0.6}/src/remoteRF_server_testing.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: remoteRF-server-testing
3
- Version: 0.0.4
3
+ Version: 0.0.6
4
4
  Summary: RemoteRF server-side control package
5
5
  Requires-Python: >=3.8
6
6
  Description-Content-Type: text/markdown
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "remoteRF-server-testing"
7
- version = "0.0.4"
7
+ version = "0.0.6"
8
8
  description = "RemoteRF server-side control package"
9
9
  requires-python = ">=3.8"
10
10
  dependencies = [
@@ -55,6 +55,11 @@ from ..common.utils import *
55
55
  from .device_manager import acquire_device, VirtualDevice, get_device_schema, find_device_id_by_name, get_schema_by_token, resolve_gid_for_token
56
56
  from ..host import host_tunnel_server as hts
57
57
 
58
+
59
+ def _normalize_device_rpc_type(value: str) -> str:
60
+ return "".join(ch for ch in str(value or "").lower() if ch.isalnum())
61
+
62
+
58
63
  class RpcManager:
59
64
  def __init__(self):
60
65
  self.debug = False
@@ -62,60 +67,6 @@ class RpcManager:
62
67
  def run_rpc(self, *, function_name, args) -> map:
63
68
  fn_type = function_name.split(':', 1)[0]
64
69
 
65
- if fn_type == "Pluto":
66
- if 'a' not in args:
67
- return {'a': map_arg('No token provided')}
68
-
69
- api_token = unmap_arg(args['a'])
70
-
71
- try:
72
- with acquire_device(api_token) as (gid, dev):
73
-
74
- if isinstance(dev, VirtualDevice):
75
- fwd_args = dict(args)
76
- fwd_args.pop('a', None)
77
- fwd_args['g'] = map_arg(int(gid))
78
- return hts.handle_host_device(
79
- hts.get_tunnel_registry(create=False),
80
- host_id=dev.host_id,
81
- device_id=dev.device_id,
82
- function_name=function_name,
83
- args=fwd_args,
84
- timeout_sec=10.0,
85
- )
86
-
87
- # Schema-based local dispatch.
88
- # Wire format: "Pluto:{prop}:{verb}" verb = GET | SET | CALL0 | CALL1
89
- parts = function_name.split(':')
90
- prop = parts[1]
91
- verb = parts[2] if len(parts) > 2 else ""
92
-
93
- if verb == "GET":
94
- result = dev.dispatch(f"get_{prop}", {})
95
- return {prop: map_arg(result)}
96
-
97
- elif verb == "SET":
98
- value = unmap_arg(args[prop])
99
- dev.dispatch(f"set_{prop}", {"value": value})
100
- return {}
101
-
102
- elif verb == "CALL0":
103
- result = dev.dispatch(f"call_{prop}", {})
104
- return {prop: map_arg(result)} if result is not None else {prop: map_arg('None')}
105
-
106
- elif verb == "CALL1":
107
- if 'arg1' not in args:
108
- raise ValueError("CALL1 requires 'arg1' in args")
109
- value = unmap_arg(args['arg1'])
110
- result = dev.dispatch(f"call_{prop}", {"value": value})
111
- return {prop: map_arg(result)} if result is not None else {prop: map_arg('None')}
112
-
113
- else:
114
- raise ValueError(f"Unknown verb {verb!r} in {function_name!r}")
115
-
116
- except Exception as e:
117
- return {'a': map_arg(str(e))}
118
-
119
70
  if fn_type == "IDL":
120
71
  return self._handle_idl(function_name.split(':', 1)[1], args)
121
72
 
@@ -125,10 +76,111 @@ class RpcManager:
125
76
  if function_name == "echo":
126
77
  return args
127
78
 
79
+ if self._looks_like_device_rpc(function_name):
80
+ return self._handle_device_rpc(function_name=function_name, args=args)
81
+
128
82
  return {'a': map_arg(f'Unknown RPC: {function_name}')}
129
83
 
130
84
  # ── IDL control-plane ────────────────────────────────────────────
131
85
 
86
+ def _looks_like_device_rpc(self, function_name: str) -> bool:
87
+ parts = function_name.split(':', 2)
88
+ return len(parts) == 3 and all(parts)
89
+
90
+ def _handle_device_rpc(self, *, function_name: str, args: dict) -> dict:
91
+ if 'a' not in args:
92
+ return {'a': map_arg('No token provided')}
93
+
94
+ api_token = unmap_arg(args['a'])
95
+
96
+ try:
97
+ with acquire_device(api_token) as (gid, dev):
98
+ self._validate_device_rpc_type(function_name=function_name, gid=gid, dev=dev)
99
+
100
+ if self._should_use_builtin_ip_fallback(function_name=function_name, gid=gid, dev=dev):
101
+ return {"ip": map_arg(self._builtin_ip_fallback(gid=gid, dev=dev))}
102
+
103
+ if isinstance(dev, VirtualDevice):
104
+ fwd_args = dict(args)
105
+ fwd_args.pop('a', None)
106
+ fwd_args['g'] = map_arg(int(gid))
107
+ return hts.handle_host_device(
108
+ hts.get_tunnel_registry(create=False),
109
+ host_id=dev.host_id,
110
+ device_id=dev.device_id,
111
+ function_name=function_name,
112
+ args=fwd_args,
113
+ timeout_sec=10.0,
114
+ )
115
+
116
+ return self._dispatch_local_schema(function_name=function_name, args=args, dev=dev)
117
+
118
+ except Exception as e:
119
+ return {'a': map_arg(str(e))}
120
+
121
+ def _validate_device_rpc_type(self, *, function_name: str, gid: int, dev: object) -> None:
122
+ requested_type = function_name.split(':', 1)[0]
123
+ actual_type = self._resolve_device_type(gid=gid, dev=dev)
124
+
125
+ if not actual_type:
126
+ return
127
+
128
+ if _normalize_device_rpc_type(requested_type) != _normalize_device_rpc_type(actual_type):
129
+ raise ValueError(
130
+ f"RPC device type {requested_type!r} does not match reserved device type {actual_type!r}"
131
+ )
132
+
133
+ def _should_use_builtin_ip_fallback(self, *, function_name: str, gid: int, dev: object) -> bool:
134
+ _, prop, verb = function_name.split(':', 2)
135
+ if prop != "ip" or verb != "CALL0":
136
+ return False
137
+
138
+ schema = dev if hasattr(dev, "list_exposed") else get_device_schema(gid)
139
+ if schema is None or not hasattr(schema, "list_exposed"):
140
+ return True
141
+
142
+ calls = schema.list_exposed().get("calls", [])
143
+ return "call_ip" not in calls
144
+
145
+ def _builtin_ip_fallback(self, *, gid: int, dev: object) -> str:
146
+ actual_type = self._resolve_device_type(gid=gid, dev=dev) or "device"
147
+ return f"{actual_type}:{int(gid)}"
148
+
149
+ def _resolve_device_type(self, *, gid: int, dev: object) -> str:
150
+ for candidate in (getattr(dev, "device_type", None), getattr(dev, "kind", None)):
151
+ if candidate:
152
+ return str(candidate)
153
+
154
+ schema = get_device_schema(gid)
155
+ if schema is None:
156
+ return ""
157
+ return str(getattr(schema, "device_type", "") or "")
158
+
159
+ def _dispatch_local_schema(self, *, function_name: str, args: dict, dev: object) -> dict:
160
+ _, prop, verb = function_name.split(':', 2)
161
+
162
+ if verb == "GET":
163
+ result = dev.dispatch(f"get_{prop}", {})
164
+ return {prop: map_arg(result)}
165
+
166
+ if verb == "SET":
167
+ value = unmap_arg(args[prop])
168
+ dev.dispatch(f"set_{prop}", {"value": value})
169
+ return {}
170
+
171
+ if verb == "CALL0":
172
+ result = dev.dispatch(f"call_{prop}", {})
173
+ return {prop: map_arg(result)} if result is not None else {prop: map_arg('None')}
174
+
175
+ if verb == "CALL1":
176
+ if 'arg1' not in args:
177
+ raise ValueError("CALL1 requires 'arg1' in args")
178
+ value = unmap_arg(args['arg1'])
179
+ result = dev.dispatch(f"call_{prop}", {"value": value})
180
+ return {prop: map_arg(result)} if result is not None else {prop: map_arg('None')}
181
+
182
+ raise ValueError(f"Unknown verb {verb!r} in {function_name!r}")
183
+
132
184
  def _handle_idl(self, sub: str, args: dict) -> dict:
133
185
  """
134
186
  IDL:get_drivers → schema + driver metadata for a device.
@@ -190,4 +242,4 @@ class RpcManager:
190
242
 
191
243
  return {'error': map_arg(f'Unknown IDL call: {sub!r}')}
192
244
 
193
- rpc_manager = RpcManager()
245
+ rpc_manager = RpcManager()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: remoteRF-server-testing
3
- Version: 0.0.4
3
+ Version: 0.0.6
4
4
  Summary: RemoteRF server-side control package
5
5
  Requires-Python: >=3.8
6
6
  Description-Content-Type: text/markdown
@@ -44,4 +44,5 @@ src/remoteRF_server_testing.egg-info/SOURCES.txt
44
44
  src/remoteRF_server_testing.egg-info/dependency_links.txt
45
45
  src/remoteRF_server_testing.egg-info/entry_points.txt
46
46
  src/remoteRF_server_testing.egg-info/requires.txt
47
- src/remoteRF_server_testing.egg-info/top_level.txt
47
+ src/remoteRF_server_testing.egg-info/top_level.txt
48
+ tests/test_rpc_manager.py
@@ -0,0 +1,166 @@
1
+ from contextlib import contextmanager
2
+ import importlib
3
+ import sys
4
+ import types
5
+ import unittest
6
+ from unittest.mock import patch
7
+
8
+
9
+ def _install_rpc_manager_stubs():
10
+ reservation_mod = types.ModuleType("remoteRF_server.server.reservation")
11
+
12
+ class _ReservationHandler:
13
+ def handle_call(self, *, function_name, args):
14
+ return {"handled": (function_name, args)}
15
+
16
+ reservation_mod.reservation_handler = _ReservationHandler()
17
+ sys.modules["remoteRF_server.server.reservation"] = reservation_mod
18
+
19
+ utils_mod = types.ModuleType("remoteRF_server.common.utils")
20
+ utils_mod.map_arg = lambda value: value
21
+ utils_mod.unmap_arg = lambda value: value
22
+ sys.modules["remoteRF_server.common.utils"] = utils_mod
23
+
24
+ device_manager_mod = types.ModuleType("remoteRF_server.server.device_manager")
25
+
26
+ class VirtualDevice:
27
+ def __init__(self, *, gid, host_id, device_id, label="", serial="", kind=""):
28
+ self.gid = gid
29
+ self.host_id = host_id
30
+ self.device_id = device_id
31
+ self.label = label
32
+ self.serial = serial
33
+ self.kind = kind
34
+
35
+ @contextmanager
36
+ def acquire_device(_api_token):
37
+ raise NotImplementedError("patched per test")
38
+ yield
39
+
40
+ device_manager_mod.VirtualDevice = VirtualDevice
41
+ device_manager_mod.acquire_device = acquire_device
42
+ device_manager_mod.get_device_schema = lambda _gid: None
43
+ device_manager_mod.find_device_id_by_name = lambda _name: None
44
+ device_manager_mod.get_schema_by_token = lambda _token: None
45
+ device_manager_mod.resolve_gid_for_token = lambda _token: None
46
+ sys.modules["remoteRF_server.server.device_manager"] = device_manager_mod
47
+
48
+ host_tunnel_mod = types.ModuleType("remoteRF_server.host.host_tunnel_server")
49
+ host_tunnel_mod.get_tunnel_registry = lambda create=False: None
50
+ host_tunnel_mod.handle_host_device = lambda *args, **kwargs: {}
51
+ sys.modules["remoteRF_server.host.host_tunnel_server"] = host_tunnel_mod
52
+
53
+
54
+ _install_rpc_manager_stubs()
55
+ sys.modules.pop("remoteRF_server.server.rpc_manager", None)
56
+ rpc_manager_module = importlib.import_module("remoteRF_server.server.rpc_manager")
57
+
58
+
59
+ class _FakeSchemaDevice:
60
+ device_type = "hackrf"
61
+
62
+ def __init__(self):
63
+ self.calls = []
64
+
65
+ def dispatch(self, method_name, args):
66
+ self.calls.append((method_name, args))
67
+
68
+ if method_name == "call_get_serial_no":
69
+ return "SERIAL-123"
70
+
71
+ if method_name == "set_center_freq":
72
+ return None
73
+
74
+ raise AssertionError(f"Unexpected dispatch: {method_name} {args}")
75
+
76
+
77
+ @contextmanager
78
+ def _acquired_device(gid, dev):
79
+ yield gid, dev
80
+
81
+
82
+ class RpcManagerDeviceDispatchTests(unittest.TestCase):
83
+ def setUp(self):
84
+ self.rpc = rpc_manager_module.RpcManager()
85
+
86
+ def test_routes_non_pluto_schema_call(self):
87
+ dev = _FakeSchemaDevice()
88
+
89
+ with patch.object(
90
+ rpc_manager_module,
91
+ "acquire_device",
92
+ return_value=_acquired_device(7, dev),
93
+ ):
94
+ result = self.rpc.run_rpc(
95
+ function_name="Hackrf:get_serial_no:CALL0",
96
+ args={"a": "token"},
97
+ )
98
+
99
+ self.assertEqual(result["get_serial_no"], "SERIAL-123")
100
+ self.assertEqual(dev.calls, [("call_get_serial_no", {})])
101
+
102
+ def test_rejects_mismatched_schema_namespace(self):
103
+ dev = _FakeSchemaDevice()
104
+
105
+ with patch.object(
106
+ rpc_manager_module,
107
+ "acquire_device",
108
+ return_value=_acquired_device(7, dev),
109
+ ):
110
+ result = self.rpc.run_rpc(
111
+ function_name="Pluto:get_serial_no:CALL0",
112
+ args={"a": "token"},
113
+ )
114
+
115
+ self.assertIn("does not match", result["a"])
116
+
117
+ def test_uses_builtin_ip_ping_for_schemas_without_call_ip(self):
118
+ dev = _FakeSchemaDevice()
119
+
120
+ with patch.object(
121
+ rpc_manager_module,
122
+ "acquire_device",
123
+ return_value=_acquired_device(7, dev),
124
+ ):
125
+ result = self.rpc.run_rpc(
126
+ function_name="Hackrf:ip:CALL0",
127
+ args={"a": "token"},
128
+ )
129
+
130
+ self.assertEqual(result["ip"], "hackrf:7")
131
+ self.assertEqual(dev.calls, [])
132
+
133
+ def test_forwards_host_devices_for_non_pluto_schema_calls(self):
134
+ dev = rpc_manager_module.VirtualDevice(
135
+ gid=9,
136
+ host_id="host-1",
137
+ device_id="9",
138
+ kind="hackrf",
139
+ )
140
+
141
+ with patch.object(
142
+ rpc_manager_module,
143
+ "acquire_device",
144
+ return_value=_acquired_device(9, dev),
145
+ ), patch.object(
146
+ rpc_manager_module.hts,
147
+ "get_tunnel_registry",
148
+ return_value=object(),
149
+ ), patch.object(
150
+ rpc_manager_module.hts,
151
+ "handle_host_device",
152
+ return_value={"get_serial_no": "HOST-SERIAL"},
153
+ ) as handle_host_device:
154
+ result = self.rpc.run_rpc(
155
+ function_name="Hackrf:get_serial_no:CALL0",
156
+ args={"a": "token"},
157
+ )
158
+
159
+ self.assertEqual(result["get_serial_no"], "HOST-SERIAL")
160
+ forwarded_args = handle_host_device.call_args.kwargs["args"]
161
+ self.assertNotIn("a", forwarded_args)
162
+ self.assertEqual(forwarded_args["g"], 9)
163
+
164
+
165
+ if __name__ == "__main__":
166
+ unittest.main()