RemoteRF 1.1.4__tar.gz → 1.1.5__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-1.1.4/src/remoteRF.egg-info → remoterf-1.1.5}/PKG-INFO +1 -1
- {remoterf-1.1.4 → remoterf-1.1.5}/setup.py +1 -1
- remoterf-1.1.5/src/remoteRF/config/cert_fetcher.py +170 -0
- {remoterf-1.1.4 → remoterf-1.1.5/src/remoteRF.egg-info}/PKG-INFO +1 -1
- remoterf-1.1.4/src/remoteRF/config/cert_fetcher.py +0 -118
- {remoterf-1.1.4 → remoterf-1.1.5}/LICENSE +0 -0
- {remoterf-1.1.4 → remoterf-1.1.5}/MANIFEST.in +0 -0
- {remoterf-1.1.4 → remoterf-1.1.5}/README.md +0 -0
- {remoterf-1.1.4 → remoterf-1.1.5}/setup.cfg +0 -0
- {remoterf-1.1.4 → remoterf-1.1.5}/src/remoteRF/__init__.py +0 -0
- {remoterf-1.1.4 → remoterf-1.1.5}/src/remoteRF/common/__init__.py +0 -0
- {remoterf-1.1.4 → remoterf-1.1.5}/src/remoteRF/common/grpc/__init__.py +0 -0
- {remoterf-1.1.4 → remoterf-1.1.5}/src/remoteRF/common/grpc/grpc_pb2.py +0 -0
- {remoterf-1.1.4 → remoterf-1.1.5}/src/remoteRF/common/grpc/grpc_pb2_grpc.py +0 -0
- {remoterf-1.1.4 → remoterf-1.1.5}/src/remoteRF/common/utils/__init__.py +0 -0
- {remoterf-1.1.4 → remoterf-1.1.5}/src/remoteRF/common/utils/ansi_codes.py +0 -0
- {remoterf-1.1.4 → remoterf-1.1.5}/src/remoteRF/common/utils/api_token.py +0 -0
- {remoterf-1.1.4 → remoterf-1.1.5}/src/remoteRF/common/utils/list_string.py +0 -0
- {remoterf-1.1.4 → remoterf-1.1.5}/src/remoteRF/common/utils/process_arg.py +0 -0
- {remoterf-1.1.4 → remoterf-1.1.5}/src/remoteRF/config/__init__.py +0 -0
- {remoterf-1.1.4 → remoterf-1.1.5}/src/remoteRF/config/config.py +0 -0
- {remoterf-1.1.4 → remoterf-1.1.5}/src/remoteRF/core/__init__.py +0 -0
- {remoterf-1.1.4 → remoterf-1.1.5}/src/remoteRF/core/acc_login.py +0 -0
- {remoterf-1.1.4 → remoterf-1.1.5}/src/remoteRF/core/app.py +0 -0
- {remoterf-1.1.4 → remoterf-1.1.5}/src/remoteRF/core/grpc_acc.py +0 -0
- {remoterf-1.1.4 → remoterf-1.1.5}/src/remoteRF/core/grpc_client.py +0 -0
- {remoterf-1.1.4 → remoterf-1.1.5}/src/remoteRF/drivers/__init__.py +0 -0
- {remoterf-1.1.4 → remoterf-1.1.5}/src/remoteRF/drivers/adalm_pluto/__init__.py +0 -0
- {remoterf-1.1.4 → remoterf-1.1.5}/src/remoteRF/drivers/adalm_pluto/pluto_remote.py +0 -0
- {remoterf-1.1.4 → remoterf-1.1.5}/src/remoteRF/remoterf_cli.py +0 -0
- {remoterf-1.1.4 → remoterf-1.1.5}/src/remoteRF/version.py +0 -0
- {remoterf-1.1.4 → remoterf-1.1.5}/src/remoteRF.egg-info/SOURCES.txt +0 -0
- {remoterf-1.1.4 → remoterf-1.1.5}/src/remoteRF.egg-info/dependency_links.txt +0 -0
- {remoterf-1.1.4 → remoterf-1.1.5}/src/remoteRF.egg-info/entry_points.txt +0 -0
- {remoterf-1.1.4 → remoterf-1.1.5}/src/remoteRF.egg-info/requires.txt +0 -0
- {remoterf-1.1.4 → remoterf-1.1.5}/src/remoteRF.egg-info/top_level.txt +0 -0
|
@@ -6,7 +6,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
|
|
|
6
6
|
|
|
7
7
|
setup(
|
|
8
8
|
name="remoterf",
|
|
9
|
-
version="1.1.
|
|
9
|
+
version="1.1.5",
|
|
10
10
|
author="Ethan Ge",
|
|
11
11
|
author_email="ethoGalaxy@gmail.com",
|
|
12
12
|
description="A python API to remotely access signal centric hardware. Client-side only! Courtesy of Wireless Lab @ UCLA & Prof. Ian Roberts.",
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# cert_fetcher.py
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import os
|
|
7
|
+
import socket
|
|
8
|
+
import urllib.request
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
CERT_FILENAME_DEFAULT = "ca.crt"
|
|
13
|
+
|
|
14
|
+
def _default_config_dir() -> Path:
|
|
15
|
+
return Path(os.path.expanduser("~")) / ".config" / "remoterf"
|
|
16
|
+
|
|
17
|
+
def _default_env_path() -> Path:
|
|
18
|
+
return _default_config_dir() / ".env"
|
|
19
|
+
|
|
20
|
+
def _ensure_parent_dir(p: Path) -> None:
|
|
21
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
22
|
+
|
|
23
|
+
def _looks_like_pem_cert(data: bytes) -> bool:
|
|
24
|
+
return b"BEGIN CERTIFICATE" in data and b"END CERTIFICATE" in data
|
|
25
|
+
|
|
26
|
+
def sha256_fingerprint_pem(pem_bytes: bytes) -> str:
|
|
27
|
+
h = hashlib.sha256(pem_bytes).hexdigest()
|
|
28
|
+
return ":".join(h[i:i+2] for i in range(0, len(h), 2))
|
|
29
|
+
|
|
30
|
+
def _fetch_http(host: str, port: int, timeout_sec: float) -> bytes:
|
|
31
|
+
url = f"http://{host}:{port}/ca.crt"
|
|
32
|
+
req = urllib.request.Request(url, method="GET")
|
|
33
|
+
with urllib.request.urlopen(req, timeout=timeout_sec) as resp:
|
|
34
|
+
return resp.read()
|
|
35
|
+
|
|
36
|
+
def _fetch_raw_tcp(host: str, port: int, timeout_sec: float) -> bytes:
|
|
37
|
+
chunks: list[bytes] = []
|
|
38
|
+
with socket.create_connection((host, port), timeout=timeout_sec) as s:
|
|
39
|
+
s.settimeout(timeout_sec)
|
|
40
|
+
while True:
|
|
41
|
+
try:
|
|
42
|
+
b = s.recv(4096)
|
|
43
|
+
except socket.timeout:
|
|
44
|
+
break
|
|
45
|
+
if not b:
|
|
46
|
+
break
|
|
47
|
+
chunks.append(b)
|
|
48
|
+
return b"".join(chunks)
|
|
49
|
+
|
|
50
|
+
def _dotenv_escape_value_no_quotes(value: str) -> str:
|
|
51
|
+
"""
|
|
52
|
+
Returns a .env-safe value WITHOUT surrounding quotes.
|
|
53
|
+
We use forward slashes to avoid backslash-escape weirdness.
|
|
54
|
+
"""
|
|
55
|
+
# Trim outer whitespace; keep internal spaces.
|
|
56
|
+
v = value.strip()
|
|
57
|
+
|
|
58
|
+
# Convert Windows backslashes to forward slashes.
|
|
59
|
+
v = v.replace("\\", "/")
|
|
60
|
+
|
|
61
|
+
# Remove any accidental surrounding quotes.
|
|
62
|
+
if (len(v) >= 2) and ((v[0] == v[-1]) and v[0] in ("'", '"')):
|
|
63
|
+
v = v[1:-1]
|
|
64
|
+
|
|
65
|
+
# Disallow newlines (dotenv lines are single-line)
|
|
66
|
+
v = v.replace("\r", "").replace("\n", "")
|
|
67
|
+
|
|
68
|
+
return v
|
|
69
|
+
|
|
70
|
+
def _dotenv_set_key(env_path: Path, key: str, value: str) -> None:
|
|
71
|
+
"""
|
|
72
|
+
Create/update KEY=value in env_path. Writes value WITHOUT quotes.
|
|
73
|
+
Preserves unrelated lines. Comments are kept.
|
|
74
|
+
"""
|
|
75
|
+
_ensure_parent_dir(env_path)
|
|
76
|
+
|
|
77
|
+
new_line = f"{key}={_dotenv_escape_value_no_quotes(value)}\n"
|
|
78
|
+
|
|
79
|
+
if not env_path.exists():
|
|
80
|
+
env_path.write_text(new_line, encoding="utf-8")
|
|
81
|
+
return
|
|
82
|
+
|
|
83
|
+
lines = env_path.read_text(encoding="utf-8", errors="ignore").splitlines(keepends=True)
|
|
84
|
+
|
|
85
|
+
# Replace existing KEY=... (ignores leading whitespace); doesn't touch commented-out keys.
|
|
86
|
+
replaced = False
|
|
87
|
+
out: list[str] = []
|
|
88
|
+
for line in lines:
|
|
89
|
+
stripped = line.lstrip()
|
|
90
|
+
if stripped.startswith("#"):
|
|
91
|
+
out.append(line)
|
|
92
|
+
continue
|
|
93
|
+
|
|
94
|
+
# Match "KEY=" at start (after lstrip). Keep any leading whitespace.
|
|
95
|
+
if stripped.startswith(key + "="):
|
|
96
|
+
prefix_ws = line[: len(line) - len(stripped)]
|
|
97
|
+
out.append(prefix_ws + new_line)
|
|
98
|
+
replaced = True
|
|
99
|
+
else:
|
|
100
|
+
out.append(line)
|
|
101
|
+
|
|
102
|
+
if not replaced:
|
|
103
|
+
# Append with a newline boundary
|
|
104
|
+
if out and not out[-1].endswith("\n"):
|
|
105
|
+
out[-1] += "\n"
|
|
106
|
+
out.append(new_line)
|
|
107
|
+
|
|
108
|
+
env_path.write_text("".join(out), encoding="utf-8")
|
|
109
|
+
|
|
110
|
+
def fetch_and_save_ca_cert(
|
|
111
|
+
host: str,
|
|
112
|
+
port: int,
|
|
113
|
+
*,
|
|
114
|
+
out_path: Optional[str | Path] = None,
|
|
115
|
+
profile: Optional[str] = None,
|
|
116
|
+
timeout_sec: float = 3.0,
|
|
117
|
+
overwrite: bool = True,
|
|
118
|
+
write_env: bool = True,
|
|
119
|
+
env_path: Optional[str | Path] = None,
|
|
120
|
+
) -> bool:
|
|
121
|
+
"""
|
|
122
|
+
Fetch CA cert and save. Optionally writes REMOTERF_CA_CERT into ~/.config/remoterf/.env
|
|
123
|
+
WITHOUT quotes.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
write_env: if True, updates REMOTERF_CA_CERT in env file to point at saved cert
|
|
127
|
+
env_path: override env file location; default ~/.config/remoterf/.env
|
|
128
|
+
"""
|
|
129
|
+
try:
|
|
130
|
+
if not isinstance(port, int):
|
|
131
|
+
port = int(port)
|
|
132
|
+
|
|
133
|
+
# Determine destination path
|
|
134
|
+
if out_path is not None:
|
|
135
|
+
dest = Path(out_path).expanduser().resolve()
|
|
136
|
+
else:
|
|
137
|
+
cfg = _default_config_dir()
|
|
138
|
+
certs_dir = cfg / "certs"
|
|
139
|
+
name = f"{profile}.crt" if profile else CERT_FILENAME_DEFAULT
|
|
140
|
+
dest = certs_dir / name
|
|
141
|
+
|
|
142
|
+
_ensure_parent_dir(dest)
|
|
143
|
+
|
|
144
|
+
if dest.exists() and not overwrite:
|
|
145
|
+
# Still ensure env points to it (helpful for idempotency)
|
|
146
|
+
if write_env:
|
|
147
|
+
ep = Path(env_path).expanduser().resolve() if env_path else _default_env_path()
|
|
148
|
+
_dotenv_set_key(ep, "REMOTERF_CA_CERT", dest.as_posix())
|
|
149
|
+
return True
|
|
150
|
+
|
|
151
|
+
# Fetch (HTTP first, then raw TCP fallback)
|
|
152
|
+
try:
|
|
153
|
+
data = _fetch_http(host, port, timeout_sec)
|
|
154
|
+
except Exception:
|
|
155
|
+
data = _fetch_raw_tcp(host, port, timeout_sec)
|
|
156
|
+
|
|
157
|
+
if not data or not _looks_like_pem_cert(data):
|
|
158
|
+
return False
|
|
159
|
+
|
|
160
|
+
dest.write_bytes(data)
|
|
161
|
+
|
|
162
|
+
# Update .env without quotes
|
|
163
|
+
if write_env:
|
|
164
|
+
ep = Path(env_path).expanduser().resolve() if env_path else _default_env_path()
|
|
165
|
+
_dotenv_set_key(ep, "REMOTERF_CA_CERT", dest.as_posix())
|
|
166
|
+
|
|
167
|
+
return True
|
|
168
|
+
|
|
169
|
+
except Exception:
|
|
170
|
+
return False
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
# cert_fetcher.py
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import hashlib
|
|
6
|
-
import os
|
|
7
|
-
import socket
|
|
8
|
-
import urllib.request
|
|
9
|
-
from pathlib import Path
|
|
10
|
-
from typing import Optional
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
CERT_FILENAME_DEFAULT = "ca.crt"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def _default_config_dir() -> Path:
|
|
17
|
-
# Cross-platform-ish default; adjust if you already have a standard in your project.
|
|
18
|
-
# Linux: ~/.config/remoterf
|
|
19
|
-
# macOS: also acceptable; if you want Apple-standard, use ~/Library/Application Support/remoterf
|
|
20
|
-
base = Path(os.path.expanduser("~")) / ".config" / "remoterf"
|
|
21
|
-
return base
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def _ensure_parent_dir(p: Path) -> None:
|
|
25
|
-
p.parent.mkdir(parents=True, exist_ok=True)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def _looks_like_pem_cert(data: bytes) -> bool:
|
|
29
|
-
return b"BEGIN CERTIFICATE" in data and b"END CERTIFICATE" in data
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def sha256_fingerprint_pem(pem_bytes: bytes) -> str:
|
|
33
|
-
h = hashlib.sha256(pem_bytes).hexdigest()
|
|
34
|
-
return ":".join(h[i:i+2] for i in range(0, len(h), 2))
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def _fetch_http(host: str, port: int, timeout_sec: float) -> bytes:
|
|
38
|
-
url = f"http://{host}:{port}/ca.crt"
|
|
39
|
-
req = urllib.request.Request(url, method="GET")
|
|
40
|
-
with urllib.request.urlopen(req, timeout=timeout_sec) as resp:
|
|
41
|
-
data = resp.read()
|
|
42
|
-
return data
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
def _fetch_raw_tcp(host: str, port: int, timeout_sec: float) -> bytes:
|
|
46
|
-
chunks: list[bytes] = []
|
|
47
|
-
with socket.create_connection((host, port), timeout=timeout_sec) as s:
|
|
48
|
-
s.settimeout(timeout_sec)
|
|
49
|
-
while True:
|
|
50
|
-
try:
|
|
51
|
-
b = s.recv(4096)
|
|
52
|
-
except socket.timeout:
|
|
53
|
-
break
|
|
54
|
-
if not b:
|
|
55
|
-
break
|
|
56
|
-
chunks.append(b)
|
|
57
|
-
return b"".join(chunks)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def fetch_and_save_ca_cert(
|
|
61
|
-
host: str,
|
|
62
|
-
port: int,
|
|
63
|
-
*,
|
|
64
|
-
out_path: Optional[str | Path] = None,
|
|
65
|
-
profile: Optional[str] = None,
|
|
66
|
-
timeout_sec: float = 3.0,
|
|
67
|
-
overwrite: bool = True,
|
|
68
|
-
) -> bool:
|
|
69
|
-
"""
|
|
70
|
-
Fetch CA cert from the server bootstrap endpoint and save it to disk.
|
|
71
|
-
|
|
72
|
-
Args:
|
|
73
|
-
host: server host/ip running cert_provider
|
|
74
|
-
port: cert_provider port (NOT the TLS gRPC port)
|
|
75
|
-
out_path: explicit output path (overrides profile/default location)
|
|
76
|
-
profile: if provided, saves as ~/.config/remoterf/certs/<profile>.crt
|
|
77
|
-
timeout_sec: network timeout
|
|
78
|
-
overwrite: whether to overwrite existing file
|
|
79
|
-
|
|
80
|
-
Returns:
|
|
81
|
-
True on success, False on any failure.
|
|
82
|
-
"""
|
|
83
|
-
try:
|
|
84
|
-
if not isinstance(port, int):
|
|
85
|
-
port = int(port)
|
|
86
|
-
|
|
87
|
-
# Determine destination path
|
|
88
|
-
if out_path is not None:
|
|
89
|
-
dest = Path(out_path).expanduser().resolve()
|
|
90
|
-
else:
|
|
91
|
-
cfg = _default_config_dir()
|
|
92
|
-
certs_dir = cfg / "certs"
|
|
93
|
-
name = f"{profile}.crt" if profile else CERT_FILENAME_DEFAULT
|
|
94
|
-
dest = certs_dir / name
|
|
95
|
-
|
|
96
|
-
_ensure_parent_dir(dest)
|
|
97
|
-
|
|
98
|
-
if dest.exists() and not overwrite:
|
|
99
|
-
return True # already present, treat as success
|
|
100
|
-
|
|
101
|
-
# Fetch (HTTP first, then raw TCP fallback)
|
|
102
|
-
data = b""
|
|
103
|
-
try:
|
|
104
|
-
data = _fetch_http(host, port, timeout_sec)
|
|
105
|
-
except Exception:
|
|
106
|
-
data = _fetch_raw_tcp(host, port, timeout_sec)
|
|
107
|
-
|
|
108
|
-
if not data or not _looks_like_pem_cert(data):
|
|
109
|
-
return False
|
|
110
|
-
|
|
111
|
-
# Save
|
|
112
|
-
dest.write_bytes(data)
|
|
113
|
-
|
|
114
|
-
# Optional: you may want to return/print fingerprint, but requested API is bool.
|
|
115
|
-
return True
|
|
116
|
-
|
|
117
|
-
except Exception:
|
|
118
|
-
return False
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|