73 lines
2.1 KiB
Python
73 lines
2.1 KiB
Python
"""Shared memory-BIO driver for bounded DTLS handshakes."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import time
|
|
from collections.abc import Callable
|
|
|
|
from OpenSSL import SSL
|
|
|
|
from .coap import split_dtls
|
|
|
|
_HANDSHAKE_POLL_S = 0.5
|
|
_MAX_DATAGRAM_SIZE = 65535
|
|
|
|
|
|
def _drive_dtls_handshake(
|
|
connection,
|
|
sock,
|
|
*,
|
|
deadline: float,
|
|
retries: int | None = None,
|
|
on_datagram: Callable[[bytes], None] | None = None,
|
|
) -> bool:
|
|
"""Drive one memory-BIO DTLS handshake up to a monotonic deadline.
|
|
|
|
OpenSSL owns the retransmission schedule. ``retries`` optionally limits
|
|
how many expired retransmission timers are serviced; a normal session is
|
|
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.
|
|
"""
|
|
retransmits = 0
|
|
while time.monotonic() < deadline:
|
|
try:
|
|
connection.do_handshake()
|
|
return time.monotonic() < deadline
|
|
except SSL.WantReadError:
|
|
pass
|
|
|
|
try:
|
|
output = connection.bio_read(_MAX_DATAGRAM_SIZE)
|
|
except SSL.WantReadError:
|
|
output = None
|
|
if output:
|
|
for record in split_dtls(output):
|
|
if sock.send(record) != len(record):
|
|
raise OSError("incomplete UDP send")
|
|
|
|
remaining = deadline - time.monotonic()
|
|
if remaining <= 0:
|
|
break
|
|
sock.settimeout(min(_HANDSHAKE_POLL_S, remaining))
|
|
try:
|
|
datagram = sock.recv(_MAX_DATAGRAM_SIZE)
|
|
except TimeoutError:
|
|
timer = connection.DTLSv1_get_timeout()
|
|
if timer is not None and timer <= 0:
|
|
if retries is not None and retransmits >= retries:
|
|
break
|
|
connection.DTLSv1_handle_timeout()
|
|
retransmits += 1
|
|
continue
|
|
|
|
if not datagram:
|
|
continue
|
|
if on_datagram is not None:
|
|
on_datagram(datagram)
|
|
connection.bio_write(datagram)
|
|
|
|
return False
|