# SPDX-License-Identifier: BSD-3-Clause OR Apache-2.0 import asyncio import cbor2 import pytest from pyd3tn.bundle7 import BundleStatusReport, RecordType from ud3tn_utils.aap2 import ( AAP2AsyncUnixClient, BundleADU, BundleADUFlags, ResponseStatus, ) from .helpers import ( AAP2_AGENT_ID, AAP2_SECRET, AAP2_SOCKET, TEST_AAP2_ASYNC, UD3TN_EID, ) async def _wait_for_status_report(sub_client, timeout=5): """Wait for and return a status report message.""" async def _receive_status_report(): while True: msg = await sub_client.receive_msg() if msg.WhichOneof("msg") == "adu": adu_msg, bundle_data = await sub_client.receive_adu(msg.adu) status_report_flag = BundleADUFlags.BUNDLE_ADU_STATUS_REPORT if status_report_flag in adu_msg.adu_flags: await sub_client.send_response_status( ResponseStatus.RESPONSE_STATUS_SUCCESS ) return adu_msg, bundle_data else: # Not a status report, acknowledge and continue waiting await sub_client.send_response_status( ResponseStatus.RESPONSE_STATUS_SUCCESS ) else: # Handle other message types if needed pass try: return await asyncio.wait_for(_receive_status_report(), timeout=timeout) except asyncio.TimeoutError: raise TimeoutError("No status report received within timeout") @pytest.mark.skipif(not TEST_AAP2_ASYNC, reason="TEST_AAP2_ASYNC disabled via environment") @pytest.mark.asyncio async def test_aap2_status_reports(): """ Test the status report feature by: 1. Sending a bundle with a report-to EID set to the sender 2. Receiving the generated status report 3. Verifying the status report contains correct data """ rpc_client = AAP2AsyncUnixClient(address=AAP2_SOCKET) sub_client = AAP2AsyncUnixClient(address=AAP2_SOCKET) async with rpc_client, sub_client: print("Configuring the RPC client...") secret = await rpc_client.configure( AAP2_AGENT_ID + "_sr_sender", subscribe=False, secret=AAP2_SECRET, ) assert rpc_client.agent_id == AAP2_AGENT_ID + "_sr_sender" expected_eid = UD3TN_EID + AAP2_AGENT_ID + "_sr_sender" assert rpc_client.eid == expected_eid print("Configuring the subscriber client...") await sub_client.configure( rpc_client.agent_id, subscribe=True, secret=secret, ) assert sub_client.agent_id == rpc_client.agent_id # Send a bundle to an external destination with report-to set to # ourselves. As a result, a status report should be generated # indicating that the bundle cannot be forwarded. external_dest = "dtn://external.node/agent" print(f"Sending bundle with status reporting to {rpc_client.eid}...") payload = b"test_status_report_payload" # Send bundle to ourselves with report-to EID set await rpc_client.send_adu( BundleADU( dst_eid=external_dest, report_to_eid=rpc_client.eid, # Request status reports payload_length=len(payload), ), payload, ) response = await rpc_client.receive_response() expected_status = ResponseStatus.RESPONSE_STATUS_SUCCESS assert response.response_status == expected_status # Store original bundle info for comparison original_bundle = response.bundle_headers assert original_bundle is not None print(f"Original bundle: src={original_bundle.src_eid}, " f"dst={original_bundle.dst_eid}") print(f"Original bundle: timestamp=" f"{original_bundle.creation_timestamp_ms}, " f"seq={original_bundle.sequence_number}") # Receive the status report print("Waiting for status report...") status_report_adu, status_report_data = await _wait_for_status_report( sub_client) # Verify the status report print(f"Received status report: src={status_report_adu.src_eid}, " f"dst={status_report_adu.dst_eid}") print(f"Status report flags: {status_report_adu.adu_flags}") print(f"Status report payload length: " f"{status_report_adu.payload_length}") # Verify status report metadata assert (BundleADUFlags.BUNDLE_ADU_STATUS_REPORT in status_report_adu.adu_flags) # Report sent to report-to EID assert status_report_adu.dst_eid == rpc_client.eid # Status report from local node assert status_report_adu.src_eid == UD3TN_EID # Should have status report payload assert status_report_adu.payload_length > 0 # The status report payload should be a bundle administrative record # Parse and validate the status report as a proper BPv7 status report assert len(status_report_data) > 0 # Show first 50 bytes print(f"Status report payload: {status_report_data[:50]}...") # Parse the status report according to BPv7 specification sr = BundleStatusReport.from_cbor(cbor2.loads(status_report_data)) print(f"Parsed status report: {sr}") # Validate the parsed status report assert sr.record_type_code == RecordType.BUNDLE_STATUS_REPORT assert str(sr.subject_source_eid) == rpc_client.eid assert (sr.subject_creation_timestamp[0] == original_bundle.creation_timestamp_ms) assert sr.is_deleted assert not sr.is_forwarded assert not sr.is_delivered assert not sr.is_received REASON_CODE_DEST_EID_UNINTELLIGIBLE = 5 REASON_CODE_NO_KNOWN_ROUTE = 6 valid_reason_codes = [ REASON_CODE_DEST_EID_UNINTELLIGIBLE, REASON_CODE_NO_KNOWN_ROUTE, ] assert sr.reason_code in valid_reason_codes