remoteRF-server 0.1.77__tar.gz → 0.1.79__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/PKG-INFO +5 -3
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/README.md +4 -2
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/pyproject.toml +1 -1
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/host/host_tunnel_server.py +122 -73
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/server/device_manager.py +71 -241
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server.egg-info/PKG-INFO +5 -3
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/setup.cfg +0 -0
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/__init__.py +0 -0
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/common/__init__.py +0 -0
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/common/grpc/__init__.py +0 -0
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/common/grpc/grpc_host_pb2.py +0 -0
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/common/grpc/grpc_host_pb2_grpc.py +0 -0
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/common/grpc/grpc_pb2.py +0 -0
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/common/grpc/grpc_pb2_grpc.py +0 -0
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/common/utils/__init__.py +0 -0
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/common/utils/ansi_codes.py +0 -0
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/common/utils/api_token.py +0 -0
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/common/utils/db_connection.py +0 -0
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/common/utils/db_location.py +0 -0
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/common/utils/list_string.py +0 -0
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/common/utils/process_arg.py +0 -0
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/drivers/__init__.py +0 -0
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/drivers/adalm_pluto/__init__.py +0 -0
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/drivers/adalm_pluto/pluto_remote_server.py +0 -0
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/host/__init__.py +0 -0
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/host/host_directory_store.py +0 -0
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/server/__init__.py +0 -0
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/server/acc_perms.py +0 -0
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/server/cert_provider.py +0 -0
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/server/grpc_server.py +0 -0
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/server/reservation.py +0 -0
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/server/rpc_manager.py +0 -0
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/server/user_group_cli.py +0 -0
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/server/user_group_handler.py +0 -0
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/serverrf_cli.py +0 -0
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/tools/__init__.py +0 -0
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/tools/gen_certs.py +0 -0
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server.egg-info/SOURCES.txt +0 -0
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server.egg-info/dependency_links.txt +0 -0
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server.egg-info/entry_points.txt +0 -0
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server.egg-info/requires.txt +0 -0
- {remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: remoteRF-server
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.79
|
|
4
4
|
Description-Content-Type: text/markdown
|
|
5
5
|
Requires-Dist: grpcio
|
|
6
6
|
Requires-Dist: protobuf
|
|
@@ -348,9 +348,11 @@ todo:
|
|
|
348
348
|
|
|
349
349
|
<!-- HostRF meta data -> push upon connection + retry connection on host side. -->
|
|
350
350
|
|
|
351
|
-
Host ID (NEEDS TO BE UNIQUE AND TELL THE HOST THAT) (or if device also does it as well.)
|
|
351
|
+
<!-- Host ID (NEEDS TO BE UNIQUE AND TELL THE HOST THAT) (or if device also does it as well.) -->
|
|
352
352
|
|
|
353
|
-
ServerRF display meta data as part of devices list / reservation
|
|
353
|
+
ServerRF display meta data as part of devices list / reservation
|
|
354
|
+
|
|
355
|
+
+ online/activity status (appended into device name) + (plus host side warning if the device id is NOT globally unique!)
|
|
354
356
|
|
|
355
357
|
set_device token (virtual devices) + routing/relay to proper host (multiplexed)
|
|
356
358
|
|
|
@@ -337,9 +337,11 @@ todo:
|
|
|
337
337
|
|
|
338
338
|
<!-- HostRF meta data -> push upon connection + retry connection on host side. -->
|
|
339
339
|
|
|
340
|
-
Host ID (NEEDS TO BE UNIQUE AND TELL THE HOST THAT) (or if device also does it as well.)
|
|
340
|
+
<!-- Host ID (NEEDS TO BE UNIQUE AND TELL THE HOST THAT) (or if device also does it as well.) -->
|
|
341
341
|
|
|
342
|
-
ServerRF display meta data as part of devices list / reservation
|
|
342
|
+
ServerRF display meta data as part of devices list / reservation
|
|
343
|
+
|
|
344
|
+
+ online/activity status (appended into device name) + (plus host side warning if the device id is NOT globally unique!)
|
|
343
345
|
|
|
344
346
|
set_device token (virtual devices) + routing/relay to proper host (multiplexed)
|
|
345
347
|
|
{remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/host/host_tunnel_server.py
RENAMED
|
@@ -10,7 +10,7 @@ import secrets
|
|
|
10
10
|
import traceback
|
|
11
11
|
import hashlib
|
|
12
12
|
import re
|
|
13
|
-
import
|
|
13
|
+
import uuid
|
|
14
14
|
|
|
15
15
|
from dataclasses import dataclass
|
|
16
16
|
from typing import Dict, Optional, Tuple, Any, List, Set, Iterator
|
|
@@ -47,12 +47,13 @@ from .host_directory_store import (
|
|
|
47
47
|
# LOG_MODE = "ALL" -> show everything (INFO + WARN + ERROR)
|
|
48
48
|
# =============================================================================
|
|
49
49
|
|
|
50
|
-
LOG_MODE = "
|
|
50
|
+
LOG_MODE = "WARN" # <-- set to ALL while debugging session UUIDs
|
|
51
51
|
|
|
52
52
|
_LOG_LOCK = threading.Lock()
|
|
53
53
|
|
|
54
54
|
HOST_ID_RE = re.compile(r"^[A-Za-z0-9_.-]{1,64}$") # adjust if you want
|
|
55
55
|
|
|
56
|
+
|
|
56
57
|
class HostIdConflictError(RuntimeError):
|
|
57
58
|
def __init__(self, host_id: str, *, age_ms: int, replace_after_ms: int) -> None:
|
|
58
59
|
super().__init__(
|
|
@@ -63,6 +64,7 @@ class HostIdConflictError(RuntimeError):
|
|
|
63
64
|
self.age_ms = age_ms
|
|
64
65
|
self.replace_after_ms = replace_after_ms
|
|
65
66
|
|
|
67
|
+
|
|
66
68
|
def _validate_host_id(raw: str) -> str:
|
|
67
69
|
hid = (raw or "").strip()
|
|
68
70
|
if not hid:
|
|
@@ -76,6 +78,7 @@ def _validate_host_id(raw: str) -> str:
|
|
|
76
78
|
)
|
|
77
79
|
return hid
|
|
78
80
|
|
|
81
|
+
|
|
79
82
|
def _ts() -> str:
|
|
80
83
|
t = time.time()
|
|
81
84
|
sec = int(t)
|
|
@@ -83,6 +86,7 @@ def _ts() -> str:
|
|
|
83
86
|
lt = time.localtime(sec)
|
|
84
87
|
return time.strftime("%H:%M:%S", lt) + f".{ms:03d}"
|
|
85
88
|
|
|
89
|
+
|
|
86
90
|
def _should_log(level: str) -> bool:
|
|
87
91
|
lvl = (level or "").upper()
|
|
88
92
|
mode = (LOG_MODE or "").upper()
|
|
@@ -92,28 +96,34 @@ def _should_log(level: str) -> bool:
|
|
|
92
96
|
return lvl in ("WARN", "ERROR")
|
|
93
97
|
if mode == "ALL":
|
|
94
98
|
return lvl in ("INFO", "WARN", "ERROR")
|
|
95
|
-
# safe default if mis-set
|
|
96
99
|
return lvl in ("WARN", "ERROR")
|
|
97
100
|
|
|
101
|
+
|
|
98
102
|
def _log(level: str, msg: str) -> None:
|
|
99
103
|
if not _should_log(level):
|
|
100
104
|
return
|
|
101
105
|
tn = threading.current_thread().name
|
|
102
|
-
|
|
106
|
+
with _LOG_LOCK:
|
|
107
|
+
print(f"{_ts()} {level:<5} [{tn}] hostrf.host_tunnel_server: {msg}", file=sys.stderr, flush=True)
|
|
108
|
+
|
|
103
109
|
|
|
104
110
|
def info(msg: str) -> None:
|
|
105
111
|
_log("INFO", msg)
|
|
106
112
|
|
|
113
|
+
|
|
107
114
|
def warn(msg: str) -> None:
|
|
108
115
|
_log("WARN", msg)
|
|
109
116
|
|
|
117
|
+
|
|
110
118
|
def error(msg: str) -> None:
|
|
111
119
|
_log("ERROR", msg)
|
|
112
120
|
|
|
121
|
+
|
|
113
122
|
def exception(msg: str) -> None:
|
|
114
123
|
_log("ERROR", msg)
|
|
115
124
|
traceback.print_exc(limit=80, file=sys.stderr)
|
|
116
125
|
|
|
126
|
+
|
|
117
127
|
# =============================================================================
|
|
118
128
|
# Helpers
|
|
119
129
|
# =============================================================================
|
|
@@ -122,10 +132,12 @@ def host_key(host_id: str) -> str:
|
|
|
122
132
|
h = hashlib.sha256((host_id or "").encode("utf-8")).hexdigest()
|
|
123
133
|
return h[:16] # short stable key
|
|
124
134
|
|
|
135
|
+
|
|
125
136
|
def _safe_thread_tag(s: str) -> str:
|
|
126
137
|
s = s or "peer"
|
|
127
138
|
return re.sub(r"[^A-Za-z0-9_.-]+", "_", s)[:64]
|
|
128
139
|
|
|
140
|
+
|
|
129
141
|
def _copy_argument_map(dst_map: Any, src_map: Any) -> None:
|
|
130
142
|
if src_map is None:
|
|
131
143
|
return
|
|
@@ -136,6 +148,7 @@ def _copy_argument_map(dst_map: Any, src_map: Any) -> None:
|
|
|
136
148
|
for k, v in dict(src_map).items():
|
|
137
149
|
dst_map[str(k)].CopyFrom(v)
|
|
138
150
|
|
|
151
|
+
|
|
139
152
|
# =============================================================================
|
|
140
153
|
# Types
|
|
141
154
|
# =============================================================================
|
|
@@ -145,12 +158,14 @@ class DeviceRoute:
|
|
|
145
158
|
host_id: str
|
|
146
159
|
host_local_id: int
|
|
147
160
|
|
|
161
|
+
|
|
148
162
|
@dataclass
|
|
149
163
|
class HostStatus:
|
|
150
164
|
host_id: str
|
|
151
165
|
online: bool = False
|
|
152
166
|
last_seen_ms: int = 0
|
|
153
167
|
|
|
168
|
+
|
|
154
169
|
@dataclass
|
|
155
170
|
class DeviceStatus:
|
|
156
171
|
device_id: str
|
|
@@ -159,19 +174,17 @@ class DeviceStatus:
|
|
|
159
174
|
online: bool = False
|
|
160
175
|
last_seen_ms: int = 0
|
|
161
176
|
|
|
177
|
+
|
|
162
178
|
# =============================================================================
|
|
163
179
|
# Session
|
|
164
180
|
# =============================================================================
|
|
165
181
|
|
|
166
182
|
class HostSession:
|
|
167
|
-
"""
|
|
168
|
-
Represents one connected host (one bidi stream).
|
|
169
|
-
Owns:
|
|
170
|
-
- outbound frame queue
|
|
171
|
-
- inflight req_id -> Future
|
|
172
|
-
"""
|
|
173
|
-
def __init__(self, host_id: str, *, outbound_max: int = 2048) -> None:
|
|
183
|
+
def __init__(self, host_id: str, *, peer: str = "<?>", session_uuid: str = "", outbound_max: int = 2048) -> None:
|
|
174
184
|
self.host_id = host_id
|
|
185
|
+
self.peer = peer
|
|
186
|
+
self.session_uuid = (session_uuid or uuid.uuid4().hex[:10])
|
|
187
|
+
|
|
175
188
|
self.out_q: "queue.Queue[Optional[host_tunnel_pb2.HostFrame]]" = queue.Queue(maxsize=int(outbound_max))
|
|
176
189
|
self.inflight: Dict[str, Future] = {}
|
|
177
190
|
self.inflight_lock = threading.Lock()
|
|
@@ -179,34 +192,40 @@ class HostSession:
|
|
|
179
192
|
self.alive = True
|
|
180
193
|
self.outbound_max = int(outbound_max)
|
|
181
194
|
|
|
182
|
-
info(
|
|
195
|
+
info(
|
|
196
|
+
f"[session] created host_id={self.host_id!r} sess={self.session_uuid} "
|
|
197
|
+
f"peer={self.peer} outbound_max={self.outbound_max}"
|
|
198
|
+
)
|
|
183
199
|
|
|
184
200
|
def send(self, frame: host_tunnel_pb2.HostFrame, *, timeout: float = 2.0) -> bool:
|
|
185
201
|
which = frame.WhichOneof("msg")
|
|
186
202
|
if not self.alive:
|
|
187
|
-
warn(f"[session] send refused (dead session) host_id={self.host_id!r} type={which}")
|
|
203
|
+
warn(f"[session] send refused (dead session) host_id={self.host_id!r} sess={self.session_uuid} type={which}")
|
|
188
204
|
return False
|
|
189
205
|
try:
|
|
190
206
|
self.out_q.put(frame, timeout=timeout)
|
|
191
|
-
info(f"[session] queued outbound host_id={self.host_id!r} type={which}")
|
|
207
|
+
info(f"[session] queued outbound host_id={self.host_id!r} sess={self.session_uuid} type={which}")
|
|
192
208
|
return True
|
|
193
209
|
except queue.Full:
|
|
194
|
-
warn(
|
|
210
|
+
warn(
|
|
211
|
+
f"[session] outbound queue FULL host_id={self.host_id!r} sess={self.session_uuid} "
|
|
212
|
+
f"max={self.outbound_max} drop type={which}"
|
|
213
|
+
)
|
|
195
214
|
return False
|
|
196
215
|
except Exception:
|
|
197
|
-
exception(f"[session] send exception host_id={self.host_id!r} type={which}")
|
|
216
|
+
exception(f"[session] send exception host_id={self.host_id!r} sess={self.session_uuid} type={which}")
|
|
198
217
|
return False
|
|
199
218
|
|
|
200
219
|
def close(self) -> None:
|
|
201
220
|
if not self.alive:
|
|
202
221
|
return
|
|
203
|
-
info(f"[session] closing host_id={self.host_id!r}")
|
|
222
|
+
info(f"[session] closing host_id={self.host_id!r} sess={self.session_uuid} peer={self.peer}")
|
|
204
223
|
self.alive = False
|
|
205
224
|
|
|
206
225
|
try:
|
|
207
226
|
self.out_q.put_nowait(None)
|
|
208
227
|
except Exception:
|
|
209
|
-
exception(f"[session] close: failed to enqueue sentinel host_id={self.host_id!r}")
|
|
228
|
+
exception(f"[session] close: failed to enqueue sentinel host_id={self.host_id!r} sess={self.session_uuid}")
|
|
210
229
|
|
|
211
230
|
with self.inflight_lock:
|
|
212
231
|
for rid, fut in list(self.inflight.items()):
|
|
@@ -214,7 +233,8 @@ class HostSession:
|
|
|
214
233
|
fut.set_exception(RuntimeError(f"Host '{self.host_id}' disconnected (req_id={rid})"))
|
|
215
234
|
self.inflight.clear()
|
|
216
235
|
|
|
217
|
-
info(f"[session] closed host_id={self.host_id!r}")
|
|
236
|
+
info(f"[session] closed host_id={self.host_id!r} sess={self.session_uuid}")
|
|
237
|
+
|
|
218
238
|
|
|
219
239
|
# =============================================================================
|
|
220
240
|
# Registry
|
|
@@ -352,7 +372,10 @@ class HostTunnelRegistry:
|
|
|
352
372
|
|
|
353
373
|
self._invalidate_caches_locked()
|
|
354
374
|
|
|
355
|
-
info(
|
|
375
|
+
info(
|
|
376
|
+
f"[registry] persisted directory loaded: routes={len(self._routes)} "
|
|
377
|
+
f"hosts={len(self._host_status)} devices={len(self._device_status)}"
|
|
378
|
+
)
|
|
356
379
|
|
|
357
380
|
# ---------------------------
|
|
358
381
|
# persistence write helpers
|
|
@@ -412,28 +435,33 @@ class HostTunnelRegistry:
|
|
|
412
435
|
# directory/status mutators
|
|
413
436
|
# ---------------------------
|
|
414
437
|
|
|
415
|
-
def register_host(
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
replace_if_stale_ms: int = 15_000, # tweak: how long without heartbeat before we allow replacement
|
|
421
|
-
) -> None:
|
|
422
|
-
info(f"[registry] register_host: host_id={host_id!r}")
|
|
438
|
+
def register_host(self, host_id: str, session: HostSession, *, replace_if_stale_ms: int = 15_000) -> None:
|
|
439
|
+
info(
|
|
440
|
+
f"[registry] register_host: host_id={host_id!r} incoming_sess={session.session_uuid} "
|
|
441
|
+
f"incoming_peer={session.peer}"
|
|
442
|
+
)
|
|
423
443
|
|
|
424
444
|
with self._lock:
|
|
425
445
|
old = self._hosts.get(host_id)
|
|
426
446
|
if old is not None and old.alive:
|
|
427
447
|
age_ms = int(now_ms() - int(getattr(old, "last_heartbeat_ms", 0) or 0))
|
|
448
|
+
|
|
428
449
|
if age_ms < replace_if_stale_ms:
|
|
429
|
-
|
|
450
|
+
warn(
|
|
451
|
+
f"[registry] HostIdConflict host_id={host_id!r} age_ms={age_ms} "
|
|
452
|
+
f"incoming_sess={session.session_uuid} incoming_peer={session.peer} "
|
|
453
|
+
f"existing_sess={getattr(old,'session_uuid','?')} existing_peer={getattr(old,'peer','?')}"
|
|
454
|
+
)
|
|
430
455
|
raise HostIdConflictError(host_id, age_ms=age_ms, replace_after_ms=replace_if_stale_ms)
|
|
431
456
|
|
|
432
|
-
|
|
433
|
-
|
|
457
|
+
warn(
|
|
458
|
+
f"[registry] replacing STALE host_id={host_id!r} age_ms={age_ms} "
|
|
459
|
+
f"incoming_sess={session.session_uuid} incoming_peer={session.peer} "
|
|
460
|
+
f"existing_sess={getattr(old,'session_uuid','?')} existing_peer={getattr(old,'peer','?')}"
|
|
461
|
+
)
|
|
434
462
|
old.close()
|
|
435
463
|
|
|
436
|
-
self._hosts[host_id] = session
|
|
464
|
+
self._hosts[host_id] = session # (removed duplicate assignment)
|
|
437
465
|
|
|
438
466
|
hs = self._host_status.get(host_id) or HostStatus(host_id=host_id)
|
|
439
467
|
hs.online = True
|
|
@@ -444,7 +472,7 @@ class HostTunnelRegistry:
|
|
|
444
472
|
self._persist_host(host_id)
|
|
445
473
|
self._invalidate_caches_locked()
|
|
446
474
|
|
|
447
|
-
info(f"[registry] register_host done: host_id={host_id!r} online=True")
|
|
475
|
+
info(f"[registry] register_host done: host_id={host_id!r} online=True incoming_sess={session.session_uuid}")
|
|
448
476
|
|
|
449
477
|
def drop_host(self, host_id: str) -> None:
|
|
450
478
|
info(f"[registry] drop_host: host_id={host_id!r}")
|
|
@@ -778,6 +806,7 @@ class HostTunnelRegistry:
|
|
|
778
806
|
cancel_on_timeout=cancel_on_timeout,
|
|
779
807
|
)
|
|
780
808
|
|
|
809
|
+
|
|
781
810
|
# =============================================================================
|
|
782
811
|
# Convenience forwarders
|
|
783
812
|
# =============================================================================
|
|
@@ -804,6 +833,7 @@ def handle_host_device(
|
|
|
804
833
|
)
|
|
805
834
|
return dict(resp.results)
|
|
806
835
|
|
|
836
|
+
|
|
807
837
|
def handle_host_device_request(
|
|
808
838
|
registry: HostTunnelRegistry,
|
|
809
839
|
*,
|
|
@@ -824,6 +854,7 @@ def handle_host_device_request(
|
|
|
824
854
|
cancel_on_timeout=True,
|
|
825
855
|
)
|
|
826
856
|
|
|
857
|
+
|
|
827
858
|
# =============================================================================
|
|
828
859
|
# Servicer
|
|
829
860
|
# =============================================================================
|
|
@@ -839,7 +870,8 @@ class HostTunnelServicer(host_tunnel_pb2_grpc.HostTunnelServicer):
|
|
|
839
870
|
peer = "unknown-peer"
|
|
840
871
|
|
|
841
872
|
peer_tag = _safe_thread_tag(peer)
|
|
842
|
-
|
|
873
|
+
stream_uuid = uuid.uuid4().hex[:10]
|
|
874
|
+
info(f"[Connect] new stream peer={peer} stream_sess={stream_uuid}")
|
|
843
875
|
|
|
844
876
|
host_id: Optional[str] = None
|
|
845
877
|
session: Optional[HostSession] = None
|
|
@@ -848,46 +880,50 @@ class HostTunnelServicer(host_tunnel_pb2_grpc.HostTunnelServicer):
|
|
|
848
880
|
|
|
849
881
|
def inbound_loop():
|
|
850
882
|
nonlocal host_id, session
|
|
851
|
-
info(f"[inbound] start peer={peer}")
|
|
883
|
+
info(f"[inbound] start peer={peer} stream_sess={stream_uuid}")
|
|
852
884
|
|
|
853
885
|
try:
|
|
854
886
|
for frame in request_iterator:
|
|
855
887
|
which = frame.WhichOneof("msg")
|
|
856
888
|
if which is None:
|
|
857
|
-
warn(f"[inbound] frame with no msg peer={peer}")
|
|
889
|
+
warn(f"[inbound] frame with no msg peer={peer} stream_sess={stream_uuid}")
|
|
858
890
|
continue
|
|
859
891
|
|
|
860
|
-
info(f"[inbound] IN type={which} peer={peer} host_id={host_id or '?'}")
|
|
892
|
+
info(f"[inbound] IN type={which} peer={peer} stream_sess={stream_uuid} host_id={host_id or '?'}")
|
|
861
893
|
|
|
862
894
|
if which == "hello":
|
|
863
895
|
hello = frame.hello
|
|
864
896
|
raw = str(getattr(hello, "host_id", "") or "")
|
|
897
|
+
|
|
865
898
|
try:
|
|
866
899
|
host_id = _validate_host_id(raw)
|
|
867
900
|
except ValueError as e:
|
|
868
|
-
error(f"[inbound] invalid hello.host_id peer={peer} err={e}")
|
|
901
|
+
error(f"[inbound] invalid hello.host_id peer={peer} stream_sess={stream_uuid} err={e}")
|
|
869
902
|
fatal["code"] = "INVALID_ARGUMENT"
|
|
870
903
|
fatal["details"] = str(e)
|
|
871
904
|
stop.set()
|
|
872
905
|
return
|
|
873
906
|
|
|
874
907
|
ver = str(getattr(hello, "version", "") or "")
|
|
875
|
-
info(f"[inbound] HELLO host_id={host_id!r} version={ver!r} peer={peer}")
|
|
908
|
+
info(f"[inbound] HELLO host_id={host_id!r} version={ver!r} peer={peer} stream_sess={stream_uuid}")
|
|
876
909
|
|
|
877
|
-
session
|
|
910
|
+
# ONE session per stream
|
|
911
|
+
session = HostSession(host_id, peer=peer, session_uuid=stream_uuid)
|
|
912
|
+
|
|
913
|
+
# ONE register per stream
|
|
878
914
|
try:
|
|
879
915
|
self.registry.register_host(host_id, session, replace_if_stale_ms=15_000)
|
|
880
916
|
except HostIdConflictError as e:
|
|
881
|
-
error(f"[inbound] HostIdConflictError host_id={host_id!r} err={e}")
|
|
917
|
+
error(f"[inbound] HostIdConflictError host_id={host_id!r} stream_sess={stream_uuid} err={e}")
|
|
882
918
|
fatal["code"] = "ALREADY_EXISTS"
|
|
883
919
|
fatal["details"] = str(e)
|
|
920
|
+
try:
|
|
921
|
+
session.close()
|
|
922
|
+
except Exception:
|
|
923
|
+
pass
|
|
884
924
|
stop.set()
|
|
885
925
|
return
|
|
886
|
-
ver = str(getattr(hello, "version", "") or "")
|
|
887
|
-
info(f"[inbound] HELLO host_id={host_id!r} version={ver!r} peer={peer}")
|
|
888
926
|
|
|
889
|
-
session = HostSession(host_id)
|
|
890
|
-
self.registry.register_host(host_id, session)
|
|
891
927
|
self.registry.persist_host_meta_from_hello(hello)
|
|
892
928
|
|
|
893
929
|
# Ask host for metadata immediately on join
|
|
@@ -903,26 +939,30 @@ class HostTunnelServicer(host_tunnel_pb2_grpc.HostTunnelServicer):
|
|
|
903
939
|
)
|
|
904
940
|
)
|
|
905
941
|
)
|
|
906
|
-
info(f"[inbound] sent MetaRequest req_id={mrid} ok={ok} host_id={host_id!r}")
|
|
942
|
+
info(f"[inbound] sent MetaRequest req_id={mrid} ok={ok} host_id={host_id!r} stream_sess={stream_uuid}")
|
|
907
943
|
except Exception:
|
|
908
944
|
exception("[inbound] FAILED sending MetaRequest")
|
|
909
945
|
|
|
910
946
|
# heartbeat ack
|
|
911
947
|
try:
|
|
912
|
-
ok = session.send(
|
|
913
|
-
|
|
948
|
+
ok = session.send(
|
|
949
|
+
host_tunnel_pb2.HostFrame(
|
|
950
|
+
heartbeat=host_tunnel_pb2.Heartbeat(unix_ms=now_ms())
|
|
951
|
+
)
|
|
952
|
+
)
|
|
953
|
+
info(f"[inbound] sent heartbeat ack ok={ok} host_id={host_id!r} stream_sess={stream_uuid}")
|
|
914
954
|
except Exception:
|
|
915
955
|
exception("[inbound] FAILED sending heartbeat ack")
|
|
916
956
|
|
|
917
957
|
elif which == "device_announce":
|
|
918
958
|
if session is None or host_id is None:
|
|
919
|
-
warn(f"[inbound] device_announce before hello/session: ignoring peer={peer}")
|
|
959
|
+
warn(f"[inbound] device_announce before hello/session: ignoring peer={peer} stream_sess={stream_uuid}")
|
|
920
960
|
continue
|
|
921
961
|
|
|
922
962
|
ann = frame.device_announce
|
|
923
963
|
n = len(getattr(ann, "devices", []))
|
|
924
964
|
info(
|
|
925
|
-
f"[inbound] DEVICE_ANNOUNCE host_id={host_id!r} n={n} "
|
|
965
|
+
f"[inbound] DEVICE_ANNOUNCE host_id={host_id!r} n={n} stream_sess={stream_uuid} "
|
|
926
966
|
f"unix_ms={int(getattr(ann,'unix_ms',0) or 0)} full_snapshot={bool(getattr(ann,'full_snapshot',False))}"
|
|
927
967
|
)
|
|
928
968
|
|
|
@@ -937,49 +977,53 @@ class HostTunnelServicer(host_tunnel_pb2_grpc.HostTunnelServicer):
|
|
|
937
977
|
|
|
938
978
|
try:
|
|
939
979
|
self.registry.announce_devices(host_id, list(ann.devices))
|
|
940
|
-
info(f"[inbound] registry announce applied host_id={host_id!r}")
|
|
980
|
+
info(f"[inbound] registry announce applied host_id={host_id!r} stream_sess={stream_uuid}")
|
|
941
981
|
except DeviceIdConflictError as e:
|
|
942
|
-
error(f"[inbound] DeviceIdConflictError host_id={host_id!r} err={e}")
|
|
982
|
+
error(f"[inbound] DeviceIdConflictError host_id={host_id!r} stream_sess={stream_uuid} err={e}")
|
|
943
983
|
fatal["code"] = "ALREADY_EXISTS"
|
|
944
984
|
fatal["details"] = str(e)
|
|
945
985
|
stop.set()
|
|
946
986
|
return
|
|
947
987
|
except Exception:
|
|
948
|
-
exception(f"[inbound] announce_devices crashed host_id={host_id!r}")
|
|
988
|
+
exception(f"[inbound] announce_devices crashed host_id={host_id!r} stream_sess={stream_uuid}")
|
|
949
989
|
|
|
950
990
|
# heartbeat ack after announce
|
|
951
991
|
try:
|
|
952
|
-
ok = session.send(
|
|
953
|
-
|
|
992
|
+
ok = session.send(
|
|
993
|
+
host_tunnel_pb2.HostFrame(
|
|
994
|
+
heartbeat=host_tunnel_pb2.Heartbeat(unix_ms=now_ms())
|
|
995
|
+
)
|
|
996
|
+
)
|
|
997
|
+
info(f"[inbound] sent heartbeat ack after announce ok={ok} host_id={host_id!r} stream_sess={stream_uuid}")
|
|
954
998
|
except Exception:
|
|
955
999
|
exception("[inbound] FAILED sending heartbeat ack after announce")
|
|
956
1000
|
|
|
957
1001
|
elif which == "rpc_response":
|
|
958
1002
|
if session is None:
|
|
959
|
-
warn(f"[inbound] rpc_response before session: ignoring peer={peer}")
|
|
1003
|
+
warn(f"[inbound] rpc_response before session: ignoring peer={peer} stream_sess={stream_uuid}")
|
|
960
1004
|
continue
|
|
961
1005
|
|
|
962
1006
|
resp = frame.rpc_response
|
|
963
1007
|
rid = str(getattr(resp, "req_id", "") or "")
|
|
964
1008
|
ok = bool(getattr(resp, "ok", False))
|
|
965
1009
|
err_s = str(getattr(resp, "error", "") or "")
|
|
966
|
-
info(f"[inbound] RPC_RESPONSE req_id={rid} ok={ok} error={err_s!r}")
|
|
1010
|
+
info(f"[inbound] RPC_RESPONSE req_id={rid} ok={ok} error={err_s!r} stream_sess={stream_uuid}")
|
|
967
1011
|
|
|
968
1012
|
with session.inflight_lock:
|
|
969
1013
|
fut = session.inflight.get(rid)
|
|
970
1014
|
|
|
971
1015
|
if fut is not None and not fut.done():
|
|
972
1016
|
fut.set_result(resp)
|
|
973
|
-
info(f"[inbound] delivered rpc_response to waiter req_id={rid}")
|
|
1017
|
+
info(f"[inbound] delivered rpc_response to waiter req_id={rid} stream_sess={stream_uuid}")
|
|
974
1018
|
else:
|
|
975
|
-
warn(f"[inbound] rpc_response has no inflight waiter req_id={rid}")
|
|
1019
|
+
warn(f"[inbound] rpc_response has no inflight waiter req_id={rid} stream_sess={stream_uuid}")
|
|
976
1020
|
|
|
977
1021
|
elif which == "meta_response":
|
|
978
1022
|
try:
|
|
979
1023
|
mr = frame.meta_response
|
|
980
1024
|
ok = bool(getattr(mr, "ok", False))
|
|
981
1025
|
err_s = str(getattr(mr, "error", "") or "")
|
|
982
|
-
info(f"[inbound] META_RESPONSE ok={ok} error={err_s!r}")
|
|
1026
|
+
info(f"[inbound] META_RESPONSE ok={ok} error={err_s!r} stream_sess={stream_uuid}")
|
|
983
1027
|
try:
|
|
984
1028
|
info(f"[inbound] META_RESPONSE proto:\n{mr}")
|
|
985
1029
|
except Exception:
|
|
@@ -992,24 +1036,27 @@ class HostTunnelServicer(host_tunnel_pb2_grpc.HostTunnelServicer):
|
|
|
992
1036
|
ms = int(getattr(frame.heartbeat, "unix_ms", 0) or 0) or now_ms()
|
|
993
1037
|
session.last_heartbeat_ms = ms
|
|
994
1038
|
self.registry.heartbeat(host_id, ms)
|
|
995
|
-
info(f"[inbound] HEARTBEAT host_id={host_id!r} unix_ms={ms}")
|
|
1039
|
+
info(f"[inbound] HEARTBEAT host_id={host_id!r} unix_ms={ms} stream_sess={stream_uuid}")
|
|
996
1040
|
else:
|
|
997
|
-
warn(
|
|
1041
|
+
warn(
|
|
1042
|
+
f"[inbound] heartbeat before hello/session: ignoring peer={peer} stream_sess={stream_uuid} "
|
|
1043
|
+
f"host_id={(host_id or '?')!r}"
|
|
1044
|
+
)
|
|
998
1045
|
|
|
999
1046
|
elif which == "cancel":
|
|
1000
1047
|
try:
|
|
1001
1048
|
rid = str(getattr(frame.cancel, "req_id", "") or "")
|
|
1002
|
-
warn(f"[inbound] CANCEL received from host?? req_id={rid} peer={peer}")
|
|
1049
|
+
warn(f"[inbound] CANCEL received from host?? req_id={rid} peer={peer} stream_sess={stream_uuid}")
|
|
1003
1050
|
except Exception:
|
|
1004
1051
|
exception("[inbound] cancel frame parse failed")
|
|
1005
1052
|
|
|
1006
1053
|
else:
|
|
1007
|
-
warn(f"[inbound] unknown frame type={which} peer={peer}")
|
|
1054
|
+
warn(f"[inbound] unknown frame type={which} peer={peer} stream_sess={stream_uuid}")
|
|
1008
1055
|
|
|
1009
1056
|
except Exception:
|
|
1010
|
-
exception(f"[inbound] loop crashed peer={peer} host_id={host_id or '?'}")
|
|
1057
|
+
exception(f"[inbound] loop crashed peer={peer} stream_sess={stream_uuid} host_id={host_id or '?'}")
|
|
1011
1058
|
finally:
|
|
1012
|
-
info(f"[inbound] exiting peer={peer} host_id={host_id or '?'}")
|
|
1059
|
+
info(f"[inbound] exiting peer={peer} stream_sess={stream_uuid} host_id={host_id or '?'}")
|
|
1013
1060
|
stop.set()
|
|
1014
1061
|
if host_id is not None:
|
|
1015
1062
|
try:
|
|
@@ -1025,14 +1072,14 @@ class HostTunnelServicer(host_tunnel_pb2_grpc.HostTunnelServicer):
|
|
|
1025
1072
|
t = threading.Thread(target=inbound_loop, daemon=True, name=f"inbound_{peer_tag}")
|
|
1026
1073
|
t.start()
|
|
1027
1074
|
|
|
1028
|
-
info(f"[Connect] outbound pump starting peer={peer}")
|
|
1075
|
+
info(f"[Connect] outbound pump starting peer={peer} stream_sess={stream_uuid}")
|
|
1029
1076
|
|
|
1030
1077
|
printed_waiting = False
|
|
1031
1078
|
while not stop.is_set():
|
|
1032
1079
|
if fatal:
|
|
1033
1080
|
code = fatal.get("code", "")
|
|
1034
1081
|
details = fatal.get("details", "fatal")
|
|
1035
|
-
error(f"[Connect] aborting stream peer={peer} code={code} details={details}")
|
|
1082
|
+
error(f"[Connect] aborting stream peer={peer} stream_sess={stream_uuid} code={code} details={details}")
|
|
1036
1083
|
if code == "ALREADY_EXISTS":
|
|
1037
1084
|
context.abort(grpc.StatusCode.ALREADY_EXISTS, details)
|
|
1038
1085
|
if code == "INVALID_ARGUMENT":
|
|
@@ -1041,7 +1088,7 @@ class HostTunnelServicer(host_tunnel_pb2_grpc.HostTunnelServicer):
|
|
|
1041
1088
|
|
|
1042
1089
|
if session is None:
|
|
1043
1090
|
if not printed_waiting:
|
|
1044
|
-
info(f"[Connect] waiting for HELLO peer={peer}")
|
|
1091
|
+
info(f"[Connect] waiting for HELLO peer={peer} stream_sess={stream_uuid}")
|
|
1045
1092
|
printed_waiting = True
|
|
1046
1093
|
time.sleep(0.05)
|
|
1047
1094
|
continue
|
|
@@ -1051,18 +1098,19 @@ class HostTunnelServicer(host_tunnel_pb2_grpc.HostTunnelServicer):
|
|
|
1051
1098
|
except queue.Empty:
|
|
1052
1099
|
continue
|
|
1053
1100
|
except Exception:
|
|
1054
|
-
exception(f"[Connect] outbound queue get failed peer={peer} host_id={host_id or '?'}")
|
|
1101
|
+
exception(f"[Connect] outbound queue get failed peer={peer} stream_sess={stream_uuid} host_id={host_id or '?'}")
|
|
1055
1102
|
continue
|
|
1056
1103
|
|
|
1057
1104
|
if out is None:
|
|
1058
|
-
info(f"[Connect] outbound sentinel -> end stream peer={peer} host_id={host_id or '?'}")
|
|
1105
|
+
info(f"[Connect] outbound sentinel -> end stream peer={peer} stream_sess={stream_uuid} host_id={host_id or '?'}")
|
|
1059
1106
|
break
|
|
1060
1107
|
|
|
1061
1108
|
out_type = out.WhichOneof("msg")
|
|
1062
|
-
info(f"[Connect] OUT type={out_type} peer={peer} host_id={host_id or '?'}")
|
|
1109
|
+
info(f"[Connect] OUT type={out_type} peer={peer} stream_sess={stream_uuid} host_id={host_id or '?'}")
|
|
1063
1110
|
yield out
|
|
1064
1111
|
|
|
1065
|
-
info(f"[Connect] stream ended peer={peer} host_id={host_id or '?'}")
|
|
1112
|
+
info(f"[Connect] stream ended peer={peer} stream_sess={stream_uuid} host_id={host_id or '?'}")
|
|
1113
|
+
|
|
1066
1114
|
|
|
1067
1115
|
# =============================================================================
|
|
1068
1116
|
# Credentials + server start
|
|
@@ -1084,6 +1132,7 @@ def build_server_credentials(
|
|
|
1084
1132
|
)
|
|
1085
1133
|
return grpc.ssl_server_credentials(((private_key_pem, certificate_chain_pem),))
|
|
1086
1134
|
|
|
1135
|
+
|
|
1087
1136
|
def start_host_tunnel_server(
|
|
1088
1137
|
*,
|
|
1089
1138
|
host: str,
|
{remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/server/device_manager.py
RENAMED
|
@@ -1,243 +1,10 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
# import adi
|
|
4
|
-
# # import uhd
|
|
5
|
-
# # from hackrf import *
|
|
6
|
-
# from ..common.utils import *
|
|
7
|
-
# import subprocess
|
|
8
|
-
|
|
9
|
-
# import time
|
|
10
|
-
# import gc
|
|
11
|
-
|
|
12
|
-
# #* Device manager *
|
|
13
|
-
# '''
|
|
14
|
-
# Responsible for starting and managing queues for devices.
|
|
15
|
-
# '''
|
|
16
|
-
|
|
17
|
-
# # TODO: Implement multiple device support
|
|
18
|
-
# #region PlutoSDR
|
|
19
|
-
|
|
20
|
-
# def connect_pluto(*, ip='', usb=''):
|
|
21
|
-
# try:
|
|
22
|
-
# if ip == '':
|
|
23
|
-
# device = adi.Pluto(f'usb:{usb}')
|
|
24
|
-
# print(f"Connected to Pluto usb:{usb}")
|
|
25
|
-
# else:
|
|
26
|
-
# device = adi.Pluto(f'ip:{ip}')
|
|
27
|
-
# print(f"Connected to Pluto ip:{ip}")
|
|
28
|
-
# return device
|
|
29
|
-
# except Exception as e:
|
|
30
|
-
# print(f"Pluto {ip}: {e}")
|
|
31
|
-
# return None
|
|
32
|
-
|
|
33
|
-
# import subprocess
|
|
34
|
-
# import re
|
|
35
|
-
|
|
36
|
-
# def get_usb_port_from_serial(serial):
|
|
37
|
-
# try:
|
|
38
|
-
# # Run iio_info -s and get the output
|
|
39
|
-
# output = subprocess.check_output(["iio_info", "-s"]).decode("utf-8")
|
|
40
|
-
# except Exception as e:
|
|
41
|
-
# print(f"Error running iio_info: {e}")
|
|
42
|
-
# return None
|
|
43
|
-
|
|
44
|
-
# # Regex to find the USB port associated with the given serial number
|
|
45
|
-
# pattern = re.compile(rf"\d+:.*serial={serial}\s+\[usb:(\S+)\]")
|
|
46
|
-
|
|
47
|
-
# for line in output.splitlines():
|
|
48
|
-
# match = pattern.search(line)
|
|
49
|
-
# if match:
|
|
50
|
-
# return match.group(1) # Return the USB port
|
|
51
|
-
|
|
52
|
-
# print(f"No device found with serial {serial}")
|
|
53
|
-
# return None
|
|
54
|
-
|
|
55
|
-
# # def connect_hackrf(device_index):
|
|
56
|
-
# # try:
|
|
57
|
-
# # device = HackRF(device_index=device_index)
|
|
58
|
-
# # print(f"Connected to HackRF {device_index}")
|
|
59
|
-
# # return device
|
|
60
|
-
# # except Exception as e:
|
|
61
|
-
# # print(f"HackRF {device_index}: {e}")
|
|
62
|
-
# # return None
|
|
63
|
-
|
|
64
|
-
# # def connect_usrp2901():
|
|
65
|
-
# # usrp = uhd.usrp.MultiUSRP()
|
|
66
|
-
# # print(f"Connected to USRP 2901")
|
|
67
|
-
# # return usrp
|
|
68
|
-
|
|
69
|
-
# device_serialization = {
|
|
70
|
-
# 0: '10447392da11001009001500f02af8a678',
|
|
71
|
-
# 1: '10447392da110010f9ff1500612ff8ecb0',
|
|
72
|
-
# # 2: '104473b04a06000ffbff3900f5de83267e', # GONE?
|
|
73
|
-
# 2: '10447318ac0f0003050036008365171a2d',
|
|
74
|
-
# 3: '10447376de0b00130b00200023119a89c6',
|
|
75
|
-
# 4: '1044732a98110002faff1500f92ae517bf',
|
|
76
|
-
|
|
77
|
-
# 5: '10447318ac0f001915003200073317bbca',
|
|
78
|
-
# 6: '104473e6a60f00020d003900e21716bf22',
|
|
79
|
-
# 7: '104473e6a60f000cf3ff17000b3917d48f',
|
|
80
|
-
# 8: '10447318ac0f0016faff2500998717eacf',
|
|
81
|
-
# }
|
|
82
|
-
|
|
83
|
-
# master_token = 'SuperCoolTokenForIan'
|
|
84
|
-
|
|
85
|
-
# # print("New Version")
|
|
86
|
-
|
|
87
|
-
# devices = { # device, salt, hash
|
|
88
|
-
|
|
89
|
-
# # iio_info -s
|
|
90
|
-
# # ^ find usb serial ports
|
|
91
|
-
|
|
92
|
-
# # 0: (connect_pluto(usb=get_usb_port_from_serial(device_serialization[0])),'',''),
|
|
93
|
-
# # 1: (connect_pluto(usb=get_usb_port_from_serial(device_serialization[1])),'',''),
|
|
94
|
-
# # 2: (connect_pluto(usb=get_usb_port_from_serial(device_serialization[2])),'',''),
|
|
95
|
-
# # 3: (connect_pluto(usb=get_usb_port_from_serial(device_serialization[3])),'',''),
|
|
96
|
-
# # 4: (connect_pluto(usb=get_usb_port_from_serial(device_serialization[4])),'',''),
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
# # 5: (connect_pluto(usb=get_usb_port_from_serial(device_serialization[5])),'',''),
|
|
100
|
-
# # 6: (connect_pluto(usb=get_usb_port_from_serial(device_serialization[6])),'',''),
|
|
101
|
-
# # 7: (connect_pluto(usb=get_usb_port_from_serial(device_serialization[7])),'',''),
|
|
102
|
-
# # 8: (connect_pluto(usb=get_usb_port_from_serial(device_serialization[8])),'',''),
|
|
103
|
-
|
|
104
|
-
# # 0: (connect_pluto(usb='1.8.5'),'',''),
|
|
105
|
-
# # 1: (connect_pluto(usb='1.18.5'),'',''),
|
|
106
|
-
# # 2: (connect_pluto(usb='1.8.5'),'',''),
|
|
107
|
-
# # 1: (connect_pluto(usb='1.8.5'),'',''),
|
|
108
|
-
|
|
109
|
-
# # hackrf
|
|
110
|
-
# # 10: (connect_hackrf(0),'',''),
|
|
111
|
-
|
|
112
|
-
# # 20: (connect_usrp2901(),'',''),
|
|
113
|
-
|
|
114
|
-
# }
|
|
115
|
-
|
|
116
|
-
# devices_info = { # device, info
|
|
117
|
-
# 0: "Pluto SDR (Loopback)",
|
|
118
|
-
# 1: "Pluto SDR (Loopback)",
|
|
119
|
-
# 2: "Pluto SDR (OTA + BPF)",
|
|
120
|
-
# 3: "Pluto SDR (OTA + BPF)",
|
|
121
|
-
# 4: "Pluto SDR (OTA + BPF)",
|
|
122
|
-
# 5: "Pluto SDR (OTA)",
|
|
123
|
-
# 6: "Pluto SDR (OTA)",
|
|
124
|
-
# 7: "Pluto SDR (OTA)",
|
|
125
|
-
# 8: "Pluto SDR (OTA)",
|
|
126
|
-
# }
|
|
127
|
-
|
|
128
|
-
# # transmitter = subprocess.Popen(['python3', 'transmitter.py'])
|
|
129
|
-
# # transmitter.terminate()
|
|
130
|
-
|
|
131
|
-
# def start_transmitter():
|
|
132
|
-
# pass
|
|
133
|
-
# # global transmitter
|
|
134
|
-
# # transmitter = subprocess.Popen(['python3', 'transmitter.py'])
|
|
135
|
-
|
|
136
|
-
# def terminate_transmitter():
|
|
137
|
-
# pass
|
|
138
|
-
# # global transmitter
|
|
139
|
-
# # transmitter.terminate()
|
|
140
|
-
|
|
141
|
-
# def get_transmitter_state():
|
|
142
|
-
# global transmitter
|
|
143
|
-
# return transmitter.poll() == None
|
|
144
|
-
|
|
145
|
-
# def get_device(device_id):
|
|
146
|
-
# return devices[device_id]
|
|
147
|
-
|
|
148
|
-
# def get_all_devices() -> dict:
|
|
149
|
-
# return {k: v for k, v in devices.items() if v is not None}
|
|
150
|
-
|
|
151
|
-
# def get_all_devices_str() -> dict:
|
|
152
|
-
# return devices_info
|
|
153
|
-
|
|
154
|
-
# def parse_mastertoken(token: str):
|
|
155
|
-
# """
|
|
156
|
-
# Matches:
|
|
157
|
-
# {master_token}_0
|
|
158
|
-
# {master_token}-1
|
|
159
|
-
# {master_token}_0_force
|
|
160
|
-
|
|
161
|
-
# Returns (device_id:int, force:bool) or None.
|
|
162
|
-
# """
|
|
163
|
-
# if not token:
|
|
164
|
-
# return None
|
|
165
|
-
|
|
166
|
-
# prefix = re.escape(master_token)
|
|
167
|
-
# pattern = re.compile(rf"^{prefix}[_-](\d+)(?:_force)?$")
|
|
168
|
-
|
|
169
|
-
# m = pattern.match(token)
|
|
170
|
-
# if not m:
|
|
171
|
-
# return None
|
|
172
|
-
|
|
173
|
-
# device_id = int(m.group(1))
|
|
174
|
-
# force = token.endswith("_force")
|
|
175
|
-
# return (device_id, force)
|
|
176
|
-
# def device_exists(device_id: int) -> bool:
|
|
177
|
-
# if device_id not in devices:
|
|
178
|
-
# return False
|
|
179
|
-
# dev, _, _ = devices[device_id]
|
|
180
|
-
# return dev is not None
|
|
181
|
-
|
|
182
|
-
# def device_is_available(device_id: int) -> bool:
|
|
183
|
-
# if device_id not in devices:
|
|
184
|
-
# return False
|
|
185
|
-
# dev, salt, hsh = devices[device_id]
|
|
186
|
-
# return (dev is not None) and (salt == '') and (hsh == '')
|
|
187
|
-
|
|
188
|
-
# def get_device_by_id(device_id: int):
|
|
189
|
-
# return devices[device_id]
|
|
190
|
-
|
|
191
|
-
# def get_device(*, api_token):
|
|
192
|
-
|
|
193
|
-
# if api_token == master_token:
|
|
194
|
-
# # Check for the first empty device
|
|
195
|
-
# for key, value in devices.items():
|
|
196
|
-
# if value[1] == '' and value[2] == '':
|
|
197
|
-
# return value[0]
|
|
198
|
-
|
|
199
|
-
# return None
|
|
200
|
-
|
|
201
|
-
# parsed = parse_mastertoken(api_token) # Special args for specific devices
|
|
202
|
-
# if parsed:
|
|
203
|
-
# device_id, force = parsed
|
|
204
|
-
|
|
205
|
-
# if not device_exists(device_id):
|
|
206
|
-
# return None
|
|
207
|
-
|
|
208
|
-
# if force:
|
|
209
|
-
# # Force access if the device exists (ignores reservation state)
|
|
210
|
-
# return devices[device_id][0]
|
|
211
|
-
|
|
212
|
-
# # Non-force: only if device exists AND is not reserved
|
|
213
|
-
# if device_is_available(device_id):
|
|
214
|
-
# return devices[device_id][0]
|
|
215
|
-
|
|
216
|
-
# return None
|
|
217
|
-
|
|
218
|
-
# # Regular Behavior
|
|
219
|
-
# for key, value in devices.items():
|
|
220
|
-
# if validate_token(value[1], value[2], api_token):
|
|
221
|
-
# return value[0]
|
|
222
|
-
# return None
|
|
223
|
-
|
|
224
|
-
# def set_device(device_id, salt, hash):
|
|
225
|
-
# devices[device_id] = (devices[device_id][0], salt, hash)
|
|
226
|
-
|
|
227
|
-
# def set_pluto(ip='192.168.2.1'):
|
|
228
|
-
# # try:
|
|
229
|
-
# # global device_pluto
|
|
230
|
-
# # del device_pluto
|
|
231
|
-
# # device_pluto = adi.Pluto(f'ip:{ip}')
|
|
232
|
-
# # except Exception as e:
|
|
233
|
-
# # print(f"PlutoError: {e}")
|
|
234
|
-
# # NO IMPLEMENTATION SHOUlD BE GIVEN, AS THIS IS FOR DEVICE SETUP. Keeping it just to support common Pluto code
|
|
235
|
-
# pass
|
|
236
|
-
# device_manager.py
|
|
237
|
-
# device_manager.py
|
|
1
|
+
# src/remoteRF_server/server/device_manager.py
|
|
238
2
|
|
|
239
3
|
from __future__ import annotations
|
|
240
4
|
|
|
5
|
+
import hashlib
|
|
6
|
+
from typing import Dict, Tuple, Optional, Any
|
|
7
|
+
|
|
241
8
|
import os
|
|
242
9
|
import re
|
|
243
10
|
import subprocess
|
|
@@ -250,7 +17,7 @@ from contextlib import contextmanager
|
|
|
250
17
|
import adi
|
|
251
18
|
|
|
252
19
|
from ..common.utils import validate_token # your salt/hash validator
|
|
253
|
-
|
|
20
|
+
from ..host import host_tunnel_server as hts # gives access to hts.TUNNEL_REGISTRY
|
|
254
21
|
|
|
255
22
|
# -----------------------------
|
|
256
23
|
# Config paths (server-side)
|
|
@@ -266,6 +33,54 @@ def _cfg_dir() -> Path:
|
|
|
266
33
|
def _devices_env_path() -> Path:
|
|
267
34
|
return _cfg_dir() / "devices.env"
|
|
268
35
|
|
|
36
|
+
# -----------------------------
|
|
37
|
+
# Host stuff
|
|
38
|
+
# -----------------------------
|
|
39
|
+
|
|
40
|
+
def _get_host_registry():
|
|
41
|
+
# host_tunnel_server sets this when the tunnel server starts
|
|
42
|
+
return getattr(hts, "TUNNEL_REGISTRY", None)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _host_devices_str_snapshot() -> Dict[int, str]:
|
|
46
|
+
"""
|
|
47
|
+
Pull a FRESH host-device directory snapshot every call.
|
|
48
|
+
Assumes host device_ids are numeric strings that should be treated as gids.
|
|
49
|
+
"""
|
|
50
|
+
reg = _get_host_registry()
|
|
51
|
+
if reg is None:
|
|
52
|
+
return {}
|
|
53
|
+
|
|
54
|
+
# ttl_ms=0 => force refresh (no caching)
|
|
55
|
+
snap = reg.device_directory_cached(ttl_ms=0) # device_id -> (host_id, info_obj, is_active)
|
|
56
|
+
|
|
57
|
+
out: Dict[int, str] = {}
|
|
58
|
+
for device_id, (host_id, info_obj, is_active) in snap.items():
|
|
59
|
+
# You said host device IDs are already globally unique => treat as gid.
|
|
60
|
+
try:
|
|
61
|
+
gid = int(str(device_id))
|
|
62
|
+
except Exception:
|
|
63
|
+
# If something non-numeric ever sneaks in, just skip it.
|
|
64
|
+
continue
|
|
65
|
+
|
|
66
|
+
local_id = int(getattr(info_obj, "local_id", 0) or 0)
|
|
67
|
+
label = str(getattr(info_obj, "label", "") or "").strip()
|
|
68
|
+
serial = str(getattr(info_obj, "serial", "") or "").strip()
|
|
69
|
+
kind = str(getattr(info_obj, "kind", "") or "").strip()
|
|
70
|
+
|
|
71
|
+
status = "online" if bool(is_active) else "offline"
|
|
72
|
+
name = label or f"host-device-{gid}"
|
|
73
|
+
|
|
74
|
+
# keep it human-readable for CLI/UI
|
|
75
|
+
out[gid] = (
|
|
76
|
+
f"{name} "
|
|
77
|
+
f"[host={host_id}, local_id={local_id}"
|
|
78
|
+
f"{', kind=' + kind if kind else ''}"
|
|
79
|
+
f"{', serial=' + serial if serial else ''}"
|
|
80
|
+
f"] ({status})"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
return out
|
|
269
84
|
|
|
270
85
|
# -----------------------------
|
|
271
86
|
# Pluto helpers
|
|
@@ -536,8 +351,13 @@ def get_all_devices() -> Dict[int, Tuple[object, str, str]]:
|
|
|
536
351
|
return out
|
|
537
352
|
|
|
538
353
|
def get_all_devices_str() -> Dict[int, str]:
|
|
354
|
+
# local snapshot (fast)
|
|
539
355
|
with _state_lock:
|
|
540
|
-
|
|
356
|
+
out = dict(devices_info)
|
|
357
|
+
|
|
358
|
+
# host snapshot (fresh pull each call)
|
|
359
|
+
out.update(_host_devices_str_snapshot())
|
|
360
|
+
return out
|
|
541
361
|
|
|
542
362
|
def set_device(device_id: int, salt: str, hash: str):
|
|
543
363
|
# update ONLY reservation/auth state
|
|
@@ -549,9 +369,19 @@ def set_device(device_id: int, salt: str, hash: str):
|
|
|
549
369
|
st.hsh = str(hash or "")
|
|
550
370
|
|
|
551
371
|
def device_exists(device_id: int) -> bool:
|
|
372
|
+
did = int(device_id)
|
|
373
|
+
|
|
374
|
+
# local
|
|
552
375
|
with _state_lock:
|
|
553
|
-
st = _devices.get(
|
|
554
|
-
|
|
376
|
+
st = _devices.get(did)
|
|
377
|
+
if st and st.dev is not None:
|
|
378
|
+
return True
|
|
379
|
+
|
|
380
|
+
# host
|
|
381
|
+
reg = _get_host_registry()
|
|
382
|
+
if reg is None:
|
|
383
|
+
return False
|
|
384
|
+
return bool(reg.is_host_device(str(did)))
|
|
555
385
|
|
|
556
386
|
def device_is_available(device_id: int) -> bool:
|
|
557
387
|
with _state_lock:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: remoteRF-server
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.79
|
|
4
4
|
Description-Content-Type: text/markdown
|
|
5
5
|
Requires-Dist: grpcio
|
|
6
6
|
Requires-Dist: protobuf
|
|
@@ -348,9 +348,11 @@ todo:
|
|
|
348
348
|
|
|
349
349
|
<!-- HostRF meta data -> push upon connection + retry connection on host side. -->
|
|
350
350
|
|
|
351
|
-
Host ID (NEEDS TO BE UNIQUE AND TELL THE HOST THAT) (or if device also does it as well.)
|
|
351
|
+
<!-- Host ID (NEEDS TO BE UNIQUE AND TELL THE HOST THAT) (or if device also does it as well.) -->
|
|
352
352
|
|
|
353
|
-
ServerRF display meta data as part of devices list / reservation
|
|
353
|
+
ServerRF display meta data as part of devices list / reservation
|
|
354
|
+
|
|
355
|
+
+ online/activity status (appended into device name) + (plus host side warning if the device id is NOT globally unique!)
|
|
354
356
|
|
|
355
357
|
set_device token (virtual devices) + routing/relay to proper host (multiplexed)
|
|
356
358
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/common/grpc/__init__.py
RENAMED
|
File without changes
|
{remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/common/grpc/grpc_host_pb2.py
RENAMED
|
File without changes
|
|
File without changes
|
{remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/common/grpc/grpc_pb2.py
RENAMED
|
File without changes
|
{remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/common/grpc/grpc_pb2_grpc.py
RENAMED
|
File without changes
|
{remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/common/utils/__init__.py
RENAMED
|
File without changes
|
{remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/common/utils/ansi_codes.py
RENAMED
|
File without changes
|
{remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/common/utils/api_token.py
RENAMED
|
File without changes
|
{remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/common/utils/db_connection.py
RENAMED
|
File without changes
|
{remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/common/utils/db_location.py
RENAMED
|
File without changes
|
{remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/common/utils/list_string.py
RENAMED
|
File without changes
|
{remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/common/utils/process_arg.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/host/host_directory_store.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/server/cert_provider.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/server/user_group_cli.py
RENAMED
|
File without changes
|
{remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server/server/user_group_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
{remoterf_server-0.1.77 → remoterf_server-0.1.79}/src/remoteRF_server.egg-info/top_level.txt
RENAMED
|
File without changes
|