diff --git a/docs/DOCS-workout-poke-contract.md b/docs/DOCS-workout-poke-contract.md index ecde2f5..470ad66 100644 --- a/docs/DOCS-workout-poke-contract.md +++ b/docs/DOCS-workout-poke-contract.md @@ -8,9 +8,10 @@ session that arrives both ways is credited once. ## Transport -- `POST http://:8773/v1/workout`, `Content-Type: application/json`. +- `POST http://:8774/v1/workout`, `Content-Type: application/json`. + (8773 until 2026-10-10: that is book-guard's desktop wrapper's port.) - `` is a phone setting, default `192.168.1.43` (static on the PC). - LAN only: the PC firewall accepts tcp/8773 from `192.168.1.0/24` and nothing + LAN only: the PC firewall accepts tcp/8774 from `192.168.1.0/24` and nothing else. No WireGuard. Off the home LAN the poke fails fast and the regular sync is the only path. - Header `X-Poke-Signature: `. diff --git a/screen_locker/_poke_server.py b/screen_locker/_poke_server.py index d444db4..c4c3eb1 100644 --- a/screen_locker/_poke_server.py +++ b/screen_locker/_poke_server.py @@ -1,7 +1,7 @@ """The resident LAN listener that credits a phone workout within a second. ``workout-poke.service`` runs ``python3 -m screen_locker.poke_server``: a -stdlib ``ThreadingHTTPServer`` on ``0.0.0.0:8773`` (the firewall admits only +stdlib ``ThreadingHTTPServer`` on ``0.0.0.0:8774`` (the firewall admits only 192.168.1.0/24) answering ``POST /v1/workout`` per ``docs/DOCS-workout-poke-contract.md``. Every import -- the locker, the earned-time registry, gatelock -- is paid once at startup, so a request costs diff --git a/screen_locker/poke_server.py b/screen_locker/poke_server.py index 4aeeb85..b2b7de1 100644 --- a/screen_locker/poke_server.py +++ b/screen_locker/poke_server.py @@ -3,7 +3,7 @@ What ``workout-poke.service`` runs: load the shared key, warm the imports and the budget sum, start the Firebase session stream (off-LAN credit, :mod:`screen_locker._session_stream`) on a daemon thread, then serve ``POST -/v1/workout`` on port 8773 for good. The request handling lives in +/v1/workout`` on port 8774 for good. The request handling lives in :mod:`screen_locker._poke_server`; the contract is ``docs/DOCS-workout-poke-contract.md``. @@ -29,7 +29,8 @@ __all__ = ["DEFAULT_KEY_FILE", "DEFAULT_PORT", "load_key", "main"] _logger: Final = logging.getLogger(__name__) -DEFAULT_PORT: Final = 8773 +DEFAULT_PORT: Final = 8774 +"""Not 8773: that is book-guard's desktop wrapper (loopback, older).""" DEFAULT_KEY_FILE: Final = Path.home() / ".config" / "workout_poke" / "key" _KEYGEN_HINT: Final = "run scripts/workout_poke_keygen.sh" @@ -50,7 +51,7 @@ def load_key(path: Path) -> bytes: def _parse_args(argv: list[str] | None) -> argparse.Namespace: parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) - # "" binds every interface; the firewall admits tcp/8773 from the LAN only. + # "" binds every interface; the firewall admits tcp/8774 from the LAN only. parser.add_argument("--bind", default="", help="address (default: all)") parser.add_argument("--port", type=int, default=DEFAULT_PORT) parser.add_argument("--key-file", type=Path, default=DEFAULT_KEY_FILE) diff --git a/scripts/poke_selftest.py b/scripts/poke_selftest.py index b134124..5e3ec38 100755 --- a/scripts/poke_selftest.py +++ b/scripts/poke_selftest.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 """Drive the running workout poke listener from this PC, signed with the real key. -Each case POSTs to ``http://127.0.0.1:8773/v1/workout`` exactly as the phone +Each case POSTs to ``http://127.0.0.1:8774/v1/workout`` exactly as the phone would (``docs/DOCS-workout-poke-contract.md``) and prints the status and reply: * ``sandbox`` -- a signed sandbox request: 200, ok, credited=false; @@ -109,7 +109,7 @@ def main() -> int: "case", choices=["sandbox", "badsig", "replay", "stale", "duplicate", "latency"] ) parser.add_argument("--count", type=int, default=20) - parser.add_argument("--port", type=int, default=8773) + parser.add_argument("--port", type=int, default=8774) args = parser.parse_args() key = bytes.fromhex(KEY_FILE.read_text(encoding="ascii").strip()) if args.case == "sandbox": diff --git a/stronglift_replacement/workout_app/lib/sandbox/sandbox_http.dart b/stronglift_replacement/workout_app/lib/sandbox/sandbox_http.dart index d5fc120..7b5f230 100644 --- a/stronglift_replacement/workout_app/lib/sandbox/sandbox_http.dart +++ b/stronglift_replacement/workout_app/lib/sandbox/sandbox_http.dart @@ -15,7 +15,7 @@ import 'package:workout_app/services/pc_poke_wire.dart'; /// guard per service. A refused connection surfaces as a normal sync failure /// in the UI, which is the honest answer: the sandbox is offline by design. /// -/// The single exception is `http://:8773/v1/workout`, so a sandbox +/// The single exception is `http://:8774/v1/workout`, so a sandbox /// workout can time the PC round trip. The PC verifies the signed /// `"sandbox": true` and never writes or credits it. Firebase and GitHub are /// https on 443 and can never match. diff --git a/stronglift_replacement/workout_app/lib/services/pc_poke_service.dart b/stronglift_replacement/workout_app/lib/services/pc_poke_service.dart index 7dc681b..0c2d301 100644 --- a/stronglift_replacement/workout_app/lib/services/pc_poke_service.dart +++ b/stronglift_replacement/workout_app/lib/services/pc_poke_service.dart @@ -19,7 +19,7 @@ import 'package:workout_app/services/workout_sync_service.dart'; export 'package:workout_app/services/pc_poke_result.dart'; -/// Signs and POSTs one session to `http://:8773/v1/workout`. +/// Signs and POSTs one session to `http://:8774/v1/workout`. /// /// Runs beside [WorkoutSyncService.push], never instead of it: the PC dedups /// by record id, so whichever path lands first credits and the other is a diff --git a/stronglift_replacement/workout_app/lib/services/pc_poke_wire.dart b/stronglift_replacement/workout_app/lib/services/pc_poke_wire.dart index 4dd7bec..6479a86 100644 --- a/stronglift_replacement/workout_app/lib/services/pc_poke_wire.dart +++ b/stronglift_replacement/workout_app/lib/services/pc_poke_wire.dart @@ -11,8 +11,9 @@ import 'dart:typed_data'; import 'package:crypto/crypto.dart'; -/// TCP port the PC's poke listener binds (8771 belongs to another service). -const int kPcPokePort = 8773; +/// TCP port the PC's poke listener binds (8771 and 8773 -- book-guard's +/// desktop wrapper -- belong to other services). +const int kPcPokePort = 8774; /// Request path on the PC. const String kPcPokePath = '/v1/workout'; diff --git a/stronglift_replacement/workout_app/test/sandbox/sandbox_http_test.dart b/stronglift_replacement/workout_app/test/sandbox/sandbox_http_test.dart index c620cee..263db89 100644 --- a/stronglift_replacement/workout_app/test/sandbox/sandbox_http_test.dart +++ b/stronglift_replacement/workout_app/test/sandbox/sandbox_http_test.dart @@ -36,7 +36,7 @@ void main() { expect(SandboxHttpOverrides.allows(poke), isFalse); }); - test('allows exactly http + configured host + 8773 + /v1/workout', () { + test('allows exactly http + configured host + 8774 + /v1/workout', () { SandboxHttpOverrides.pokeHost = host; expect(SandboxHttpOverrides.allows(poke), isTrue); for (final other in [ @@ -87,12 +87,12 @@ void main() { } on SocketException catch (error) { outcome = error; } - // No listener on this host's 8773 is fine -- what matters is that a + // No listener on this host's poke port is fine -- what matters is that a // refusal, if any, did not come from the sandbox. if (outcome is SocketException) { expect(outcome.message, isNot(contains('sandbox'))); } - expect(printed.first, contains('allowing the PC poke to $host:8773')); + expect(printed.first, contains('allowing the PC poke to $host:$kPcPokePort')); client.close(force: true); }); }); diff --git a/workout-poke.service b/workout-poke.service index 6c2f652..910ddbe 100644 --- a/workout-poke.service +++ b/workout-poke.service @@ -1,5 +1,5 @@ [Unit] -Description=Credit a phone workout the second it is poked over the LAN (tcp/8773) +Description=Credit a phone workout the second it is poked over the LAN (tcp/8774) [Service] # Resident stdlib HTTP server (screen_locker/_poke_server.py): the phone POSTs