From 0352ff483ed5f389f5005a1abd9ab56f115a2276 Mon Sep 17 00:00:00 2001 From: David Stainton Date: Sun, 18 Jan 2026 15:30:32 +0100 Subject: [PATCH 1/6] Maintain compatibility with latest katzenpost thinclient protocol changes the smallest latest changes is that the PKI doc can be nil. This changes made so that the thinclient always maintains a connection to kpclientd instead of exiting with an error messages. --- katzenpost_thinclient/__init__.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/katzenpost_thinclient/__init__.py b/katzenpost_thinclient/__init__.py index ba3a6a7..bfefe63 100644 --- a/katzenpost_thinclient/__init__.py +++ b/katzenpost_thinclient/__init__.py @@ -706,11 +706,18 @@ def pki_document(self) -> "Dict[str,Any] | None": def parse_pki_doc(self, event: "Dict[str,Any]") -> None: """ Parse and store a new PKI document received from the daemon. + + Handles the case where the daemon may not have a PKI document yet + (e.g., during initial connection before the network is fully available). """ self.logger.debug("parse pki doc") assert event is not None - assert event["payload"] is not None - raw_pki_doc = cbor2.loads(event["payload"]) + payload = event.get("payload") + # Handle empty payload - daemon may not have a PKI document yet + if payload is None or len(payload) == 0: + self.logger.info("No PKI document available yet - will receive when available") + return + raw_pki_doc = cbor2.loads(payload) self.pki_doc = raw_pki_doc self.logger.debug("parse pki doc success") From ec2d6bba599566bb40df1abcf308d9b9a45f9b96 Mon Sep 17 00:00:00 2001 From: David Stainton Date: Sun, 18 Jan 2026 16:35:10 +0100 Subject: [PATCH 2/6] disable python channel tests --- tests/test_channel_api.py | 2 ++ tests/test_channel_api_extended.py | 3 +++ 2 files changed, 5 insertions(+) diff --git a/tests/test_channel_api.py b/tests/test_channel_api.py index 0e05ba5..847c8f7 100644 --- a/tests/test_channel_api.py +++ b/tests/test_channel_api.py @@ -27,6 +27,7 @@ async def setup_thin_client(): return client +@pytest.mark.skip(reason="Channel API tests temporarily disabled") @pytest.mark.asyncio async def test_channel_api_basics(): """ @@ -176,6 +177,7 @@ async def test_channel_api_basics(): print("✅ Channel API basics test completed successfully") +@pytest.mark.skip(reason="Channel API tests temporarily disabled") @pytest.mark.asyncio async def test_resume_write_channel(): """ diff --git a/tests/test_channel_api_extended.py b/tests/test_channel_api_extended.py index 14b6304..a4af196 100644 --- a/tests/test_channel_api_extended.py +++ b/tests/test_channel_api_extended.py @@ -25,6 +25,7 @@ async def setup_thin_client(): return client +@pytest.mark.skip(reason="Channel API tests temporarily disabled") @pytest.mark.asyncio async def test_resume_write_channel_query(): """ @@ -180,6 +181,7 @@ async def test_resume_write_channel_query(): print("✅ Resume write channel query test completed successfully") +@pytest.mark.skip(reason="Channel API tests temporarily disabled") @pytest.mark.asyncio async def test_resume_read_channel(): """ @@ -334,6 +336,7 @@ async def test_resume_read_channel(): print("✅ Resume read channel test completed successfully") +@pytest.mark.skip(reason="Channel API tests temporarily disabled") @pytest.mark.asyncio async def test_resume_read_channel_query(): """ From 182153d852ae43d15e24adab029d144f493a7db7 Mon Sep 17 00:00:00 2001 From: David Stainton Date: Sun, 18 Jan 2026 16:51:38 +0100 Subject: [PATCH 3/6] python test should fail if kpclientd is not available --- tests/test_core.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/test_core.py b/tests/test_core.py index 070ccaa..6855c78 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -21,9 +21,8 @@ async def test_thin_client_send_receive_integration_test(): """Test basic send/receive functionality with the echo service.""" from .conftest import is_daemon_available - # Skip test if daemon is not available - if not is_daemon_available(): - pytest.skip("Katzenpost client daemon not available") + # Fail test if daemon is not available + assert is_daemon_available(), "Katzenpost client daemon not available" from .conftest import get_config_path config_path= get_config_path() From 56882b2a65c9654aa16263c96d0045bb75495e17 Mon Sep 17 00:00:00 2001 From: David Stainton Date: Sun, 18 Jan 2026 17:01:58 +0100 Subject: [PATCH 4/6] Disable rust tests, use dev branch of katzenpost for python tests --- .github/workflows/test-integration-docker.yml | 8 ++------ tests/channel_api_test.rs | 5 +++++ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test-integration-docker.yml b/.github/workflows/test-integration-docker.yml index 84e7349..570be05 100644 --- a/.github/workflows/test-integration-docker.yml +++ b/.github/workflows/test-integration-docker.yml @@ -16,10 +16,11 @@ jobs: with: path: thinclient - - name: Checkout katzenpost repository + - name: Checkout katzenpost repository uses: actions/checkout@v4 with: repository: katzenpost/katzenpost + ref: rm_old_stuff path: katzenpost - name: Set up Docker Buildx @@ -65,11 +66,6 @@ jobs: cd thinclient python -m pytest tests/ -vvv -s --tb=short - - name: Run Rust integration tests - run: | - cd thinclient - cargo test --test '*' -- --nocapture - - name: Stop the mixnet if: always() run: | diff --git a/tests/channel_api_test.rs b/tests/channel_api_test.rs index dfb56fc..e275c23 100644 --- a/tests/channel_api_test.rs +++ b/tests/channel_api_test.rs @@ -26,6 +26,7 @@ async fn setup_thin_client() -> Result, Box Result<(), Box> { let alice_thin_client = setup_thin_client().await?; let bob_thin_client = setup_thin_client().await?; @@ -177,6 +178,7 @@ async fn test_channel_api_basics() -> Result<(), Box> { /// 7. Read first and second message from the channel /// 8. Verify payloads match #[tokio::test] +#[ignore = "Channel API tests temporarily disabled"] async fn test_resume_write_channel() -> Result<(), Box> { let alice_thin_client = setup_thin_client().await?; let bob_thin_client = setup_thin_client().await?; @@ -341,6 +343,7 @@ async fn test_resume_write_channel() -> Result<(), Box> { /// 8. Read both messages from channel /// 9. Verify payloads match #[tokio::test] +#[ignore = "Channel API tests temporarily disabled"] async fn test_resume_write_channel_query() -> Result<(), Box> { let alice_thin_client = setup_thin_client().await?; let bob_thin_client = setup_thin_client().await?; @@ -504,6 +507,7 @@ async fn test_resume_write_channel_query() -> Result<(), Box Result<(), Box> { let alice_thin_client = setup_thin_client().await?; let bob_thin_client = setup_thin_client().await?; @@ -666,6 +670,7 @@ async fn test_resume_read_channel() -> Result<(), Box> { /// 9. Read second message from channel /// 10. Verify received payload matches #[tokio::test] +#[ignore = "Channel API tests temporarily disabled"] async fn test_resume_read_channel_query() -> Result<(), Box> { let alice_thin_client = setup_thin_client().await?; let bob_thin_client = setup_thin_client().await?; From 0b2714bbd3c92884a2d6cf3c2257d545a0124e68 Mon Sep 17 00:00:00 2001 From: David Stainton Date: Sun, 18 Jan 2026 18:43:37 +0100 Subject: [PATCH 5/6] python integration test polls for pki doc --- tests/test_core.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_core.py b/tests/test_core.py index 6855c78..ee8afdc 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -36,6 +36,14 @@ async def test_thin_client_send_receive_integration_test(): try: await client.start(loop) + # Wait for PKI document to be available (received asynchronously) + attempts = 0 + while client.pki_document() is None and attempts < 30: + await asyncio.sleep(1) + attempts += 1 + + assert client.pki_document() is not None, "PKI document not received within 30 seconds" + service_desc = client.get_service("echo") surb_id = client.new_surb_id() payload = "hello" From b35d0642757f9bba80b20fe8f77786cf0ce37895 Mon Sep 17 00:00:00 2001 From: David Stainton Date: Mon, 19 Jan 2026 15:14:00 +0100 Subject: [PATCH 6/6] python handle second startup message pki doc or none --- katzenpost_thinclient/__init__.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/katzenpost_thinclient/__init__.py b/katzenpost_thinclient/__init__.py index bfefe63..8d2903b 100644 --- a/katzenpost_thinclient/__init__.py +++ b/katzenpost_thinclient/__init__.py @@ -556,12 +556,13 @@ async def start(self, loop:asyncio.AbstractEventLoop) -> None: assert response["connection_status_event"] is not None await self.handle_response(response) - # 2nd message is always a new pki doc event - #response = await self.recv(loop) - #assert response is not None - #assert response["new_pki_document_event"] is not None, response - #await self.handle_response(response) - + # 2nd message is always a new pki doc event (payload may be empty if mixnet is still booting) + response = await self.recv(loop) + if response is not None and response.get("new_pki_document_event") is not None: + await self.handle_response(response) + else: + self.logger.info("No PKI document event received during startup - will receive when available") + # Start the read loop as a background task self.logger.debug("starting read loop") self.task = loop.create_task(self.worker_loop(loop))