remoteRF-server-testing 0.0.5__tar.gz → 0.0.7__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 (49) hide show
  1. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/PKG-INFO +1 -1
  2. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/pyproject.toml +1 -1
  3. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server/server/rpc_manager.py +107 -55
  4. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server/tools/gist_status.py +14 -22
  5. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server_testing.egg-info/PKG-INFO +1 -1
  6. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/README.md +0 -0
  7. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/setup.cfg +0 -0
  8. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server/__init__.py +0 -0
  9. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server/common/__init__.py +0 -0
  10. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server/common/grpc/__init__.py +0 -0
  11. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server/common/grpc/grpc_host_pb2.py +0 -0
  12. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server/common/grpc/grpc_host_pb2_grpc.py +0 -0
  13. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server/common/grpc/grpc_pb2.py +0 -0
  14. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server/common/grpc/grpc_pb2_grpc.py +0 -0
  15. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server/common/idl/__init__.py +0 -0
  16. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server/common/idl/device_schema.py +0 -0
  17. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server/common/idl/pluto_schema.py +0 -0
  18. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server/common/idl/schema.py +0 -0
  19. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server/common/utils/__init__.py +0 -0
  20. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server/common/utils/ansi_codes.py +0 -0
  21. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server/common/utils/api_token.py +0 -0
  22. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server/common/utils/db_connection.py +0 -0
  23. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server/common/utils/db_location.py +0 -0
  24. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server/common/utils/list_string.py +0 -0
  25. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server/common/utils/process_arg.py +0 -0
  26. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server/drivers/__init__.py +0 -0
  27. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server/drivers/adalm_pluto/__init__.py +0 -0
  28. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server/drivers/adalm_pluto/pluto_remote_server.py +0 -0
  29. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server/host/__init__.py +0 -0
  30. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server/host/host_auth_token.py +0 -0
  31. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server/host/host_directory_store.py +0 -0
  32. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server/host/host_tunnel_server.py +0 -0
  33. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server/server/__init__.py +0 -0
  34. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server/server/acc_perms.py +0 -0
  35. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server/server/cert_provider.py +0 -0
  36. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server/server/device_manager.py +0 -0
  37. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server/server/grpc_server.py +0 -0
  38. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server/server/reservation.py +0 -0
  39. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server/server/user_group_cli.py +0 -0
  40. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server/server/user_group_handler.py +0 -0
  41. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server/serverrf_cli.py +0 -0
  42. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server/tools/__init__.py +0 -0
  43. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server/tools/gen_certs.py +0 -0
  44. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server/tools/gist_status_testing.py +0 -0
  45. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server_testing.egg-info/SOURCES.txt +0 -0
  46. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server_testing.egg-info/dependency_links.txt +0 -0
  47. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server_testing.egg-info/entry_points.txt +0 -0
  48. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/src/remoteRF_server_testing.egg-info/requires.txt +0 -0
  49. {remoterf_server_testing-0.0.5 → remoterf_server_testing-0.0.7}/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.5
3
+ Version: 0.0.7
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.5"
7
+ version = "0.0.7"
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()
@@ -8,7 +8,7 @@ import sys
8
8
  import time
9
9
  import threading
10
10
  from pathlib import Path
11
- from typing import Dict, Optional
11
+ from typing import Dict, Optional, Tuple
12
12
 
13
13
  import requests
14
14
 
@@ -34,7 +34,7 @@ def _read_env_kv(path: Path) -> Dict[str, str]:
34
34
  return out
35
35
 
36
36
 
37
- def _load_gist_env() -> tuple[str, str, str]:
37
+ def _load_gist_env() -> Optional[Tuple[str, str, str]]:
38
38
  p = _cfg_dir() / "gist.env"
39
39
  kv = _read_env_kv(p)
40
40
 
@@ -42,25 +42,10 @@ def _load_gist_env() -> tuple[str, str, str]:
42
42
  token = kv.get("GITHUB_TOKEN", "").strip()
43
43
  filename = kv.get("STATUS_GIST_FILENAME", "").strip()
44
44
 
45
- missing = []
46
- if not gist_id:
47
- missing.append("STATUS_GIST_ID")
48
- if not token:
49
- missing.append("GITHUB_TOKEN")
50
- if not filename:
51
- missing.append("STATUS_GIST_FILENAME")
52
-
53
- if missing:
54
- print(f"[gist_status] Missing {', '.join(missing)} in {p}", file=sys.stderr)
55
- print(
56
- "[gist_status] Fix: run:\n"
57
- " serverrf --gist --set --id <gist_id> --file <filename>\n"
58
- "Or manually create gist.env with those keys.",
59
- file=sys.stderr,
60
- )
61
- raise SystemExit(2)
45
+ if not gist_id or not token or not filename:
46
+ return None
62
47
 
63
- return gist_id, token, filename
48
+ return (gist_id, token, filename)
64
49
 
65
50
 
66
51
  def build_status() -> dict:
@@ -96,7 +81,10 @@ def push_gist(*, gist_id: str, gh_token: str, filename: str, payload: dict) -> N
96
81
 
97
82
 
98
83
  def _publisher_loop(period_sec: int) -> None:
99
- gist_id, gh_token, filename = _load_gist_env()
84
+ cfg = _load_gist_env()
85
+ if cfg is None:
86
+ return
87
+ gist_id, gh_token, filename = cfg
100
88
 
101
89
  try:
102
90
  push_gist(gist_id=gist_id, gh_token=gh_token, filename=filename, payload=build_status())
@@ -122,6 +110,10 @@ def start_status_publisher() -> None:
122
110
  if _publisher_thread is not None and _publisher_thread.is_alive():
123
111
  return
124
112
 
113
+ if _load_gist_env() is None:
114
+ print("[gist_status] off (not configured; see README gist docs)", file=sys.stderr)
115
+ return
116
+
125
117
  t = threading.Thread(
126
118
  target=_publisher_loop,
127
119
  args=(int(120),),
@@ -136,4 +128,4 @@ def start_status_publisher() -> None:
136
128
  # if __name__ == "__main__":
137
129
  # start_status_publisher(period_sec=120)
138
130
  # while True:
139
- # time.sleep(3600)
131
+ # time.sleep(3600)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: remoteRF-server-testing
3
- Version: 0.0.5
3
+ Version: 0.0.7
4
4
  Summary: RemoteRF server-side control package
5
5
  Requires-Python: >=3.8
6
6
  Description-Content-Type: text/markdown