f-layer 2.0.0__py3-none-any.whl

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 (45) hide show
  1. f_layer-2.0.0.dist-info/METADATA +232 -0
  2. f_layer-2.0.0.dist-info/RECORD +45 -0
  3. f_layer-2.0.0.dist-info/WHEEL +4 -0
  4. f_layer-2.0.0.dist-info/entry_points.txt +2 -0
  5. f_layer-2.0.0.dist-info/licenses/LICENSE +201 -0
  6. flayer/__init__.py +5 -0
  7. flayer/__main__.py +247 -0
  8. flayer/_vpn_guest.py +130 -0
  9. flayer/core/__init__.py +1 -0
  10. flayer/core/config.py +198 -0
  11. flayer/core/contracts.py +114 -0
  12. flayer/core/lifecycle.py +767 -0
  13. flayer/core/state.py +351 -0
  14. flayer/diagnostics/__init__.py +20 -0
  15. flayer/diagnostics/benchmark.py +272 -0
  16. flayer/diagnostics/contracts.py +167 -0
  17. flayer/diagnostics/health.py +154 -0
  18. flayer/diagnostics/reporting.py +24 -0
  19. flayer/extensions/__init__.py +25 -0
  20. flayer/extensions/builtins.py +165 -0
  21. flayer/extensions/contracts.py +196 -0
  22. flayer/extensions/discovery.py +152 -0
  23. flayer/extensions/registry.py +347 -0
  24. flayer/guest.py +270 -0
  25. flayer/integrations/__init__.py +23 -0
  26. flayer/integrations/adapters.py +92 -0
  27. flayer/integrations/contracts.py +241 -0
  28. flayer/integrations/wire.py +273 -0
  29. flayer/profiles/__init__.py +33 -0
  30. flayer/profiles/_amnezia_export.py +176 -0
  31. flayer/profiles/_vless_guest.py +432 -0
  32. flayer/profiles/amneziawg.py +555 -0
  33. flayer/profiles/artifacts.py +609 -0
  34. flayer/profiles/gateway.py +729 -0
  35. flayer/profiles/static_site.py +596 -0
  36. flayer/profiles/vless.py +422 -0
  37. flayer/profiles/vpn.py +524 -0
  38. flayer/providers/__init__.py +1 -0
  39. flayer/providers/contracts.py +207 -0
  40. flayer/providers/lifecycle.py +177 -0
  41. flayer/providers/yandex.py +387 -0
  42. flayer/providers/yandex_lifecycle.py +781 -0
  43. flayer/py.typed +0 -0
  44. flayer/vpn_export.py +72 -0
  45. flayer/vpn_project.py +828 -0
flayer/core/state.py ADDED
@@ -0,0 +1,351 @@
1
+ """Minimal owned-resource snapshots with strict reads and atomic local persistence."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import stat
8
+ import tempfile
9
+ from collections.abc import Iterator, Mapping
10
+ from contextlib import contextmanager
11
+ from dataclasses import asdict, dataclass
12
+ from pathlib import Path
13
+ from typing import IO
14
+
15
+ from .contracts import (
16
+ SCHEMA_VERSION,
17
+ ContractError,
18
+ ParseIdentity,
19
+ RequireTable,
20
+ StackIdentity,
21
+ ValidateFields,
22
+ ValidateIdentifier,
23
+ ValidateName,
24
+ ValidateSchemaVersion,
25
+ )
26
+
27
+ MAX_JSON_BYTES = 1024 * 1024
28
+
29
+
30
+ class StateError(ContractError):
31
+ """Owned state is malformed, mismatched, busy, or cannot be persisted safely."""
32
+
33
+
34
+ @dataclass(frozen=True)
35
+ class ResourceState:
36
+ """Minimum provider locator for a resource recorded by one managed stack."""
37
+
38
+ logical_id: str
39
+ kind: str
40
+ resource_id: str
41
+
42
+ def __post_init__(self) -> None:
43
+ """Keep persisted locators explicit and free of arbitrary provider payloads."""
44
+
45
+ ValidateName(self.logical_id, "resources.logical_id")
46
+ ValidateName(self.kind, "resources.kind")
47
+ ValidateIdentifier(self.resource_id, "resources.resource_id")
48
+
49
+
50
+ @dataclass(frozen=True)
51
+ class StackState:
52
+ """Immutable resource inventory scoped to a complete ownership identity."""
53
+
54
+ identity: StackIdentity
55
+ resources: tuple[ResourceState, ...] = ()
56
+ schema_version: int = SCHEMA_VERSION
57
+
58
+ def __post_init__(self) -> None:
59
+ """Reject duplicate locators and invalid direct model construction."""
60
+
61
+ ValidateSchemaVersion(self.schema_version)
62
+
63
+ if not isinstance(self.identity, StackIdentity):
64
+ raise ContractError("identity must be a StackIdentity")
65
+
66
+ if not isinstance(self.resources, tuple) or any(
67
+ not isinstance(resource, ResourceState) for resource in self.resources
68
+ ):
69
+ raise ContractError("resources must be a tuple of ResourceState values")
70
+
71
+ if len({resource.logical_id for resource in self.resources}) != len(self.resources):
72
+ raise ContractError("resources.logical_id values must be unique")
73
+
74
+ if len({(resource.kind, resource.resource_id) for resource in self.resources}) != len(
75
+ self.resources
76
+ ):
77
+ raise ContractError("resources must not contain duplicate provider locators")
78
+
79
+
80
+ def _RequireIdentity(actual: StackIdentity, expected: StackIdentity) -> None:
81
+ """Block all state operations when any ownership or provider-scope field differs."""
82
+
83
+ if not isinstance(expected, StackIdentity) or actual != expected:
84
+ raise StateError("State identity does not match the expected ownership boundary")
85
+
86
+
87
+ def ParseState(data: Mapping[str, object], expected_identity: StackIdentity) -> StackState:
88
+ """Decode a versioned snapshot and require explicit caller ownership expectations."""
89
+
90
+ try:
91
+ ValidateFields(
92
+ data, frozenset({"schema_version", "identity", "resources"}), frozenset(), "state"
93
+ )
94
+ ValidateSchemaVersion(data["schema_version"])
95
+ identity = ParseIdentity(data["identity"])
96
+ _RequireIdentity(identity, expected_identity)
97
+ raw_resources = data["resources"]
98
+
99
+ if not isinstance(raw_resources, list):
100
+ raise ContractError("resources must be an array of tables")
101
+
102
+ resources: list[ResourceState] = []
103
+
104
+ for item in raw_resources:
105
+ table = RequireTable(item, "resources")
106
+ ValidateFields(
107
+ table,
108
+ frozenset({"logical_id", "kind", "resource_id"}),
109
+ frozenset(),
110
+ "resources",
111
+ )
112
+ resources.append(
113
+ ResourceState(
114
+ logical_id=ValidateName(table["logical_id"], "resources.logical_id"),
115
+ kind=ValidateName(table["kind"], "resources.kind"),
116
+ resource_id=ValidateIdentifier(table["resource_id"], "resources.resource_id"),
117
+ )
118
+ )
119
+
120
+ return StackState(identity=identity, resources=tuple(resources))
121
+
122
+ except ContractError as error:
123
+ raise StateError(str(error)) from error
124
+
125
+
126
+ def _UniqueObject(pairs: list[tuple[str, object]]) -> dict[str, object]:
127
+ """Reject duplicate JSON fields instead of silently accepting the last value."""
128
+
129
+ result: dict[str, object] = {}
130
+
131
+ for key, value in pairs:
132
+ if key in result:
133
+ raise StateError("State JSON contains duplicate fields")
134
+
135
+ result[key] = value
136
+
137
+ return result
138
+
139
+
140
+ def _ValidatePath(path: Path) -> None:
141
+ """Reject traversal, symlinks, and nonregular targets within caller-owned storage."""
142
+
143
+ if not path.name or ".." in path.parts:
144
+ raise StateError("State path must name a file without parent traversal")
145
+
146
+ for candidate in (path, *path.parents):
147
+ if candidate.is_symlink():
148
+ raise StateError("State path must not contain symlinks")
149
+
150
+ if path.exists() and not path.is_file():
151
+ raise StateError("State path must refer to a regular file")
152
+
153
+
154
+ def _OpenTextStream(descriptor: int, mode: str) -> IO[str]:
155
+ """Transfer descriptor ownership to a stream or close it when the transfer fails."""
156
+
157
+ try:
158
+ return os.fdopen(descriptor, mode, encoding="utf-8")
159
+
160
+ except BaseException:
161
+ os.close(descriptor)
162
+
163
+ raise
164
+
165
+
166
+ def _ReadOwnedJson(path: Path) -> object:
167
+ """Read bounded regular JSON without blocking on a post-validation FIFO substitution."""
168
+
169
+ opened_descriptor = os.open(
170
+ path, os.O_RDONLY | getattr(os, "O_NOFOLLOW", 0) | getattr(os, "O_NONBLOCK", 0)
171
+ )
172
+ descriptor: int | None = opened_descriptor
173
+
174
+ try:
175
+ opened = os.fstat(opened_descriptor)
176
+
177
+ if not stat.S_ISREG(opened.st_mode) or opened.st_size > MAX_JSON_BYTES:
178
+ raise StateError("Owned JSON must be a bounded regular file")
179
+
180
+ stream_descriptor = opened_descriptor
181
+ descriptor = None
182
+
183
+ with _OpenTextStream(stream_descriptor, "r") as stream:
184
+ text = stream.read(MAX_JSON_BYTES + 1)
185
+
186
+ if len(text.encode("utf-8")) > MAX_JSON_BYTES:
187
+ raise StateError("Owned JSON exceeds its bounded read limit")
188
+
189
+ return json.loads(text, object_pairs_hook=_UniqueObject)
190
+
191
+ except FileNotFoundError as error:
192
+ raise StateError("Unable to read an opened owned JSON file") from error
193
+
194
+ finally:
195
+ if descriptor is not None:
196
+ os.close(descriptor)
197
+
198
+
199
+ def LoadState(path: str | Path, expected_identity: StackIdentity) -> StackState | None:
200
+ """Return a strictly owned snapshot, or None only when the file is absent."""
201
+
202
+ state_path = Path(path)
203
+
204
+ if not isinstance(expected_identity, StackIdentity):
205
+ raise StateError("expected_identity must be a StackIdentity")
206
+
207
+ try:
208
+ _ValidatePath(state_path)
209
+ data = _ReadOwnedJson(state_path)
210
+
211
+ except FileNotFoundError:
212
+ return None
213
+
214
+ except StateError:
215
+ raise
216
+
217
+ except (OSError, ValueError, RecursionError) as error:
218
+ raise StateError("Unable to read a valid state snapshot") from error
219
+
220
+ try:
221
+ return ParseState(RequireTable(data, "state"), expected_identity)
222
+
223
+ except ContractError as error:
224
+ raise StateError(str(error)) from error
225
+
226
+
227
+ @contextmanager
228
+ def _StateLock(path: Path) -> Iterator[None]:
229
+ """Serialize cooperating writers and leave crash locks for explicit operator recovery."""
230
+
231
+ descriptor: int | None = None
232
+
233
+ try:
234
+ _ValidatePath(path)
235
+ lock_path = path.with_name(f".{path.name}.lock")
236
+ path.parent.mkdir(parents=True, exist_ok=True)
237
+ descriptor = os.open(lock_path, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600)
238
+ os.close(descriptor)
239
+ descriptor = None
240
+
241
+ except OSError as error:
242
+ if descriptor is not None:
243
+ try:
244
+ os.close(descriptor)
245
+
246
+ except OSError:
247
+ pass
248
+
249
+ raise StateError("State storage is unavailable or another writer holds its lock") from error
250
+
251
+ try:
252
+ yield
253
+
254
+ finally:
255
+ try:
256
+ lock_path.unlink()
257
+
258
+ except OSError as error:
259
+ raise StateError("Unable to release the state writer lock") from error
260
+
261
+
262
+ def _SyncDirectory(path: Path) -> None:
263
+ """Persist directory updates on platforms with a usable directory fsync operation."""
264
+
265
+ if os.name != "posix":
266
+ return
267
+
268
+ descriptor = os.open(path, os.O_RDONLY | getattr(os, "O_DIRECTORY", 0))
269
+
270
+ try:
271
+ os.fsync(descriptor)
272
+
273
+ finally:
274
+ os.close(descriptor)
275
+
276
+
277
+ def SaveState(path: str | Path, state: StackState, expected_identity: StackIdentity) -> None:
278
+ """Atomically save owned state; never replace a foreign or malformed existing snapshot."""
279
+
280
+ if not isinstance(state, StackState):
281
+ raise StateError("state must be a StackState")
282
+
283
+ _RequireIdentity(state.identity, expected_identity)
284
+ serialized = json.dumps(asdict(state), indent=2, sort_keys=True) + "\n"
285
+
286
+ if len(serialized.encode("utf-8")) > MAX_JSON_BYTES:
287
+ raise StateError("Owned state snapshot exceeds its bounded JSON storage limit")
288
+
289
+ state_path = Path(path)
290
+ temporary_path: Path | None = None
291
+
292
+ with _StateLock(state_path):
293
+ LoadState(state_path, expected_identity)
294
+
295
+ try:
296
+ descriptor, temporary_name = tempfile.mkstemp(
297
+ prefix=f".{state_path.name}.", suffix=".tmp", dir=state_path.parent
298
+ )
299
+ temporary_path = Path(temporary_name)
300
+
301
+ with _OpenTextStream(descriptor, "w") as stream:
302
+ stream.write(serialized)
303
+ stream.flush()
304
+ os.fsync(stream.fileno())
305
+
306
+ os.replace(temporary_path, state_path)
307
+ temporary_path = None
308
+ _SyncDirectory(state_path.parent)
309
+
310
+ except OSError as error:
311
+ raise StateError(
312
+ "Unable to persist state; a completed replacement may be visible"
313
+ ) from error
314
+
315
+ finally:
316
+ if temporary_path is not None:
317
+ try:
318
+ temporary_path.unlink(missing_ok=True)
319
+
320
+ except OSError as error:
321
+ raise StateError(
322
+ "Unable to clean temporary state; "
323
+ "incomplete local cleanup requires recovery"
324
+ ) from error
325
+
326
+
327
+ def RemoveState(path: str | Path, expected_identity: StackIdentity) -> bool:
328
+ """Remove only a matching local snapshot; never delete infrastructure resources."""
329
+
330
+ state_path = Path(path)
331
+
332
+ if not isinstance(expected_identity, StackIdentity):
333
+ raise StateError("expected_identity must be a StackIdentity")
334
+
335
+ try:
336
+ _ValidatePath(state_path)
337
+
338
+ if not state_path.parent.exists():
339
+ return False
340
+
341
+ with _StateLock(state_path):
342
+ if LoadState(state_path, expected_identity) is None:
343
+ return False
344
+
345
+ state_path.unlink()
346
+ _SyncDirectory(state_path.parent)
347
+
348
+ except OSError as error:
349
+ raise StateError("Unable to remove the owned state snapshot") from error
350
+
351
+ return True
@@ -0,0 +1,20 @@
1
+ """Safe diagnostic contracts with explicit provider and endpoint boundaries."""
2
+
3
+ from .benchmark import (
4
+ BenchmarkLimits,
5
+ EndpointMeasurement,
6
+ EndpointTransport,
7
+ MeasureEndpoint,
8
+ ProbeEndpoint,
9
+ RunBenchmark,
10
+ )
11
+ from .contracts import CheckResult, DiagnosticReport, DiagnosticStatus, Redact
12
+ from .health import EvaluateHealthReport, HealthProbe, ProviderStatus, RunChecks, RuntimeChecks
13
+ from .reporting import RenderJson, RenderText
14
+
15
+ __all__ = [
16
+ "BenchmarkLimits", "CheckResult", "DiagnosticReport", "DiagnosticStatus",
17
+ "EndpointMeasurement", "EndpointTransport", "EvaluateHealthReport", "HealthProbe",
18
+ "MeasureEndpoint", "ProbeEndpoint", "ProviderStatus", "Redact", "RenderJson", "RenderText",
19
+ "RunBenchmark", "RunChecks", "RuntimeChecks",
20
+ ]
@@ -0,0 +1,272 @@
1
+ """Bounded HTTP measurements against an explicitly authorized endpoint."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import http.client
6
+ import math
7
+ import multiprocessing
8
+ import time
9
+ from dataclasses import dataclass
10
+ from multiprocessing.connection import Connection
11
+ from typing import Protocol
12
+ from urllib.parse import SplitResult, urlsplit
13
+
14
+ from .contracts import CheckResult, DiagnosticStatus
15
+
16
+ MAX_TIMEOUT_SECONDS = 30.0
17
+ MAX_REQUEST_COUNT = 10
18
+ MAX_BYTE_LIMIT = 1024 * 1024
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class BenchmarkLimits:
23
+ """Hard per-request deadline, request count, and response payload limits."""
24
+
25
+ timeout_seconds: float = 3.0
26
+ count: int = 3
27
+ byte_limit: int = 64 * 1024
28
+
29
+ def __post_init__(self) -> None:
30
+ """Reject invalid or unbounded work before starting a transport."""
31
+
32
+ if (
33
+ isinstance(self.timeout_seconds, bool)
34
+ or not math.isfinite(self.timeout_seconds)
35
+ or not 0 < self.timeout_seconds <= MAX_TIMEOUT_SECONDS
36
+ or isinstance(self.count, bool)
37
+ or not isinstance(self.count, int)
38
+ or not 1 <= self.count <= MAX_REQUEST_COUNT
39
+ or isinstance(self.byte_limit, bool)
40
+ or not isinstance(self.byte_limit, int)
41
+ or not 1 <= self.byte_limit <= MAX_BYTE_LIMIT
42
+ ):
43
+ raise ValueError("Benchmark limits exceed the supported bounds")
44
+
45
+
46
+ @dataclass(frozen=True)
47
+ class EndpointMeasurement:
48
+ """Safe metrics exclude response content, credentials, and target identifiers."""
49
+
50
+ status: DiagnosticStatus
51
+ duration_seconds: float
52
+ received_bytes: int = 0
53
+ http_status: int | None = None
54
+
55
+
56
+ class EndpointTransport(Protocol):
57
+ """A measurement implementation enforces supplied limits without emitting raw data."""
58
+
59
+ def __call__(
60
+ self, endpoint: str, timeout_seconds: float, byte_limit: int
61
+ ) -> EndpointMeasurement:
62
+ """Return one bounded HTTP measurement without exposing response payloads."""
63
+
64
+ ...
65
+
66
+
67
+ def ValidateEndpoint(endpoint: str) -> SplitResult:
68
+ """Allow explicit HTTP(S) URLs without credentials, query strings, or fragments."""
69
+
70
+ try:
71
+ parsed = urlsplit(endpoint)
72
+ port = parsed.port
73
+
74
+ if (
75
+ parsed.scheme not in ("http", "https")
76
+ or not parsed.hostname
77
+ or parsed.username is not None
78
+ or parsed.password is not None
79
+ or parsed.query
80
+ or parsed.fragment
81
+ or any(character.isspace() or ord(character) < 32 for character in endpoint)
82
+ or (port is not None and port < 1)
83
+ ):
84
+ raise ValueError("Invalid endpoint")
85
+
86
+ except ValueError:
87
+ raise ValueError("Endpoint must be an HTTP(S) URL without credentials, query or fragment") \
88
+ from None
89
+
90
+ return parsed
91
+
92
+
93
+ def HttpMeasurement(endpoint: str, timeout_seconds: float, byte_limit: int) -> EndpointMeasurement:
94
+ """Read a limited payload with TLS verification and no redirects or proxy discovery."""
95
+
96
+ parsed = ValidateEndpoint(endpoint)
97
+ connection_type = (
98
+ http.client.HTTPSConnection if parsed.scheme == "https" else http.client.HTTPConnection
99
+ )
100
+ connection = connection_type(str(parsed.hostname), port=parsed.port, timeout=timeout_seconds)
101
+ started = time.monotonic()
102
+
103
+ try:
104
+ connection.request(
105
+ "GET", parsed.path or "/",
106
+ headers={"Range": f"bytes=0-{byte_limit - 1}", "Connection": "close"},
107
+ )
108
+ response = connection.getresponse()
109
+ received_bytes = len(response.read(byte_limit))
110
+ status = DiagnosticStatus.OK if 200 <= response.status < 300 else DiagnosticStatus.FAILED
111
+
112
+ return EndpointMeasurement(status, time.monotonic() - started, received_bytes, response.status)
113
+
114
+ finally:
115
+ connection.close()
116
+
117
+
118
+ def EndpointWorker(
119
+ sender: Connection, endpoint: str, timeout_seconds: float, byte_limit: int
120
+ ) -> None:
121
+ """Isolate network work so DNS, TLS, connect, and response reads share a hard deadline."""
122
+
123
+ try:
124
+ measurement = HttpMeasurement(endpoint, timeout_seconds, byte_limit)
125
+
126
+ except Exception:
127
+ measurement = EndpointMeasurement(DiagnosticStatus.FAILED, 0.0)
128
+
129
+ try:
130
+ sender.send(measurement)
131
+
132
+ finally:
133
+ sender.close()
134
+
135
+
136
+ def MeasureEndpoint(endpoint: str, timeout_seconds: float, byte_limit: int) -> EndpointMeasurement:
137
+ """Terminate isolated network work at the deadline, including a stalled DNS resolver."""
138
+
139
+ ValidateEndpoint(endpoint)
140
+ BenchmarkLimits(timeout_seconds=timeout_seconds, count=1, byte_limit=byte_limit)
141
+ context = multiprocessing.get_context("spawn")
142
+ receiver, sender = context.Pipe(duplex=False)
143
+ process = context.Process(
144
+ target=EndpointWorker, args=(sender, endpoint, timeout_seconds, byte_limit), daemon=True
145
+ )
146
+ started = time.monotonic()
147
+ launched = False
148
+
149
+ try:
150
+ process.start()
151
+ launched = True
152
+ sender.close()
153
+ remaining = max(0.0, timeout_seconds - (time.monotonic() - started))
154
+
155
+ if receiver.poll(remaining):
156
+ measurement: EndpointMeasurement = receiver.recv()
157
+
158
+ return measurement
159
+
160
+ return EndpointMeasurement(DiagnosticStatus.FAILED, timeout_seconds)
161
+
162
+ except (OSError, EOFError, RuntimeError):
163
+ return EndpointMeasurement(DiagnosticStatus.FAILED, time.monotonic() - started)
164
+
165
+ finally:
166
+ sender.close()
167
+ receiver.close()
168
+
169
+ if launched:
170
+ if process.is_alive():
171
+ process.terminate()
172
+
173
+ process.join(timeout=1.0)
174
+
175
+ if process.is_alive():
176
+ process.kill()
177
+ process.join(timeout=1.0)
178
+
179
+ process.close()
180
+
181
+
182
+ def SafeMeasurement(
183
+ endpoint: str, limits: BenchmarkLimits, transport: EndpointTransport
184
+ ) -> EndpointMeasurement:
185
+ """Reject invalid adapter metrics and suppress all unstructured exception output."""
186
+
187
+ try:
188
+ measurement = transport(endpoint, limits.timeout_seconds, limits.byte_limit)
189
+
190
+ if (
191
+ not isinstance(measurement, EndpointMeasurement)
192
+ or not isinstance(measurement.status, DiagnosticStatus)
193
+ or isinstance(measurement.duration_seconds, bool)
194
+ or not math.isfinite(measurement.duration_seconds)
195
+ or not 0 <= measurement.duration_seconds <= limits.timeout_seconds
196
+ or isinstance(measurement.received_bytes, bool)
197
+ or not isinstance(measurement.received_bytes, int)
198
+ or not 0 <= measurement.received_bytes <= limits.byte_limit
199
+ or (
200
+ measurement.http_status is not None
201
+ and (
202
+ isinstance(measurement.http_status, bool)
203
+ or not isinstance(measurement.http_status, int)
204
+ or not 100 <= measurement.http_status <= 599
205
+ )
206
+ )
207
+ or (
208
+ measurement.status is DiagnosticStatus.OK
209
+ and (measurement.http_status is None or not 200 <= measurement.http_status < 300)
210
+ )
211
+ ):
212
+ raise ValueError("Adapter returned invalid metrics")
213
+
214
+ return measurement
215
+
216
+ except Exception:
217
+ return EndpointMeasurement(DiagnosticStatus.FAILED, 0.0)
218
+
219
+
220
+ def ProbeEndpoint(
221
+ endpoint: str, timeout_seconds: float = 3.0,
222
+ transport: EndpointTransport = MeasureEndpoint,
223
+ ) -> CheckResult:
224
+ """Check explicit HTTP reachability without pretending it validates guest hardening."""
225
+
226
+ ValidateEndpoint(endpoint)
227
+ limits = BenchmarkLimits(timeout_seconds=timeout_seconds, count=1, byte_limit=1)
228
+ measurement = SafeMeasurement(endpoint, limits, transport)
229
+
230
+ return CheckResult(
231
+ "endpoint", measurement.status,
232
+ "Endpoint answered successfully" if measurement.status is DiagnosticStatus.OK
233
+ else "Endpoint probe did not succeed",
234
+ {"duration_seconds": measurement.duration_seconds, "http_status": measurement.http_status},
235
+ )
236
+
237
+
238
+ def RunBenchmark(
239
+ endpoint: str, limits: BenchmarkLimits | None = None,
240
+ transport: EndpointTransport = MeasureEndpoint,
241
+ ) -> tuple[CheckResult, ...]:
242
+ """Measure a fixed count of bounded downloads without inferring upload or tunnel speed."""
243
+
244
+ ValidateEndpoint(endpoint)
245
+ resolved_limits = limits or BenchmarkLimits()
246
+ checks: list[CheckResult] = []
247
+
248
+ for index in range(resolved_limits.count):
249
+ measurement = SafeMeasurement(endpoint, resolved_limits, transport)
250
+ status = measurement.status
251
+
252
+ if status is DiagnosticStatus.OK and measurement.received_bytes == 0:
253
+ status = DiagnosticStatus.FAILED
254
+
255
+ throughput = (
256
+ measurement.received_bytes * 8 / measurement.duration_seconds / 1_000_000
257
+ if measurement.duration_seconds > 0 else None
258
+ )
259
+ checks.append(CheckResult(
260
+ f"http-download-{index + 1}", status,
261
+ "Bounded HTTP download measured" if status is DiagnosticStatus.OK
262
+ else "Bounded HTTP download did not succeed",
263
+ {
264
+ "received_bytes": measurement.received_bytes,
265
+ "duration_seconds": measurement.duration_seconds,
266
+ "throughput_mbps": throughput,
267
+ "http_status": measurement.http_status,
268
+ "byte_limit": resolved_limits.byte_limit,
269
+ },
270
+ ))
271
+
272
+ return tuple(checks)