diff --git a/README.md b/README.md index 77ba543..a5457be 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,10 @@ retransmissions within that same deadline: sess.connect(timeout=4.0) ``` +The deadline stops further setup, retries, and network waits. If OpenSSL +reports that the handshake completed at the deadline boundary, the completed +session is retained rather than torn down as a timeout. + Connection attempts can also use a one-way cancellation signal. The signal is backed by a socketpair, so setting it wakes the network wait immediately while OpenSSL retains control of DTLS retransmission timing: diff --git a/smartthings_local/protocol/dtls_handshake.py b/smartthings_local/protocol/dtls_handshake.py index ffc8e63..12fa30a 100644 --- a/smartthings_local/protocol/dtls_handshake.py +++ b/smartthings_local/protocol/dtls_handshake.py @@ -34,14 +34,16 @@ def _drive_dtls_handshake( bounded only by its deadline, while the diagnostic probe retains its explicit retry budget. - Return ``True`` only when the handshake completes before the deadline. - TLS and socket failures are left to the caller to classify. + Return ``True`` once OpenSSL reports the handshake complete. The deadline + prevents another setup, retry, or network-wait iteration; it does not tear + down a session that completed while ``do_handshake()`` was running. TLS and + socket failures are left to the caller to classify. """ retransmits = 0 while time.monotonic() < deadline: try: connection.do_handshake() - return time.monotonic() < deadline + return True except SSL.WantReadError: pass diff --git a/smartthings_local/protocol/dtls_session.py b/smartthings_local/protocol/dtls_session.py index 4b34487..2da687c 100644 --- a/smartthings_local/protocol/dtls_session.py +++ b/smartthings_local/protocol/dtls_session.py @@ -322,13 +322,15 @@ class DtlsCoapSession: timeout: float | None = None, cancel: ConnectCancellation | None = None, ): - """Perform a cancellable DTLS handshake within a monotonic deadline. + """Perform a cancellable DTLS handshake using a monotonic deadline. ``timeout`` overrides ``HANDSHAKE_TIMEOUT_S`` for this call. OpenSSL owns DTLS retransmission timing while every receive is capped by the remaining budget, so wall-clock adjustments cannot change the bound. - A ``ConnectCancellation`` wakes the network wait immediately and does - not alter an already established session. + Once OpenSSL reports completion, that completed session is retained + even if the call returns just after the deadline. A + ``ConnectCancellation`` wakes the network wait immediately and does not + alter an already established session. """ handshake_timeout = _validate_handshake_timeout( timeout, self.HANDSHAKE_TIMEOUT_S) diff --git a/tests/test_session_connect_deadline.py b/tests/test_session_connect_deadline.py index f9bd0a4..0a35df6 100644 --- a/tests/test_session_connect_deadline.py +++ b/tests/test_session_connect_deadline.py @@ -323,12 +323,12 @@ def test_connect_services_openssl_retransmit_timer(monkeypatch): @pytest.mark.parametrize("success_delay", (0.1, 0.2)) -def test_handshake_success_at_or_after_deadline_is_rejected( +def test_handshake_success_at_or_after_deadline_is_retained( monkeypatch, success_delay, ): clock = _Clock() - connection, sock, _endpoint, _open_calls = _install_handshake( + connection, sock, endpoint, _open_calls = _install_handshake( monkeypatch, clock, ) @@ -338,7 +338,11 @@ def test_handshake_success_at_or_after_deadline_is_rejected( connection.do_handshake = late_success - with pytest.raises(SessionTimeoutError): - _session().connect(timeout=0.1) + session = _session() + session.connect(timeout=0.1) - assert sock.closed + assert session.conn is connection + assert session.sock is sock + assert session.endpoint is endpoint + assert session.dest == endpoint.sockaddr + assert not sock.closed