From 539e879939c2682ff35d1300ea6f30c0a64ca13f Mon Sep 17 00:00:00 2001 From: Julien Castiaux Date: Tue, 28 Apr 2026 23:47:53 +0200 Subject: [PATCH 1/4] PEP 748: materialize the server context Ease reading the diff of the next commits. --- peps/pep-0748.rst | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/peps/pep-0748.rst b/peps/pep-0748.rst index 333c24fe432..3201cda0e1a 100644 --- a/peps/pep-0748.rst +++ b/peps/pep-0748.rst @@ -365,7 +365,36 @@ The ``ClientContext`` protocol class has the following class definition: (cipher, negotiated_protocol, negotiated_tls_version, etc.).""" ... -The ``ServerContext`` is similar, taking a ``TLSServerConfiguration`` instead. +The ``ServerContext`` protocol class has the following class definition: + +.. code-block:: python + + class ServerContext(Protocol): + @abstractmethod + def __init__(self, configuration: TLSServerConfiguration) -> None: + """Create a new server context object from a given TLS server configuration.""" + ... + + @property + @abstractmethod + def configuration(self) -> TLSServerConfiguration: + """Returns the TLS server configuration that was used to create the server context.""" + ... + + @abstractmethod + def connect(self, address: tuple[str | None, int]) -> TLSSocket: + """Creates a TLSSocket that behaves like a socket.socket, and + contains information about the TLS exchange + (cipher, negotiated_protocol, negotiated_tls_version, etc.). + """ + ... + + @abstractmethod + def create_buffer(self, server_hostname: str) -> TLSBuffer: + """Creates a TLSBuffer that acts as an in-memory channel, + and contains information about the TLS exchange + (cipher, negotiated_protocol, negotiated_tls_version, etc.).""" + ... Socket ~~~~~~ From 791d34e5a758da11899da8a542c422008439a531 Mon Sep 17 00:00:00 2001 From: Julien Castiaux Date: Tue, 28 Apr 2026 23:50:11 +0200 Subject: [PATCH 2/4] PEP 748: server context create_buffer takes no server_hostname --- peps/pep-0748.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/peps/pep-0748.rst b/peps/pep-0748.rst index 3201cda0e1a..48af3140d33 100644 --- a/peps/pep-0748.rst +++ b/peps/pep-0748.rst @@ -390,7 +390,7 @@ The ``ServerContext`` protocol class has the following class definition: ... @abstractmethod - def create_buffer(self, server_hostname: str) -> TLSBuffer: + def create_buffer(self) -> TLSBuffer: """Creates a TLSBuffer that acts as an in-memory channel, and contains information about the TLS exchange (cipher, negotiated_protocol, negotiated_tls_version, etc.).""" From 785c0c4e0b4b78d4869c933cd28cce6a7273a349 Mon Sep 17 00:00:00 2001 From: Julien Castiaux Date: Wed, 29 Apr 2026 00:07:46 +0200 Subject: [PATCH 3/4] PEP 748: create_connection and create_server instead of connect The `connect()` function is poorly named server-side as it actually `bind()` the socket. Server-side the function lacks a `family` parameter to support IPv6 without a DNS lookup. Actually all three `connect()`, `bind()` and `listen()` socket function are pretty low-level. The high-level `create_connection` (for `connect`) and `create_server` (for `bind()` + `listen()`) are more pythonic. Wrap the latter and not the former, also to remove the need of a `listen()` method on the created socket (which is useless client-side). --- peps/pep-0748.rst | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/peps/pep-0748.rst b/peps/pep-0748.rst index 48af3140d33..81b9646cff9 100644 --- a/peps/pep-0748.rst +++ b/peps/pep-0748.rst @@ -351,7 +351,14 @@ The ``ClientContext`` protocol class has the following class definition: ... @abstractmethod - def connect(self, address: tuple[str | None, int]) -> TLSSocket: + def create_connection( + self, + address: tuple[str | None, int], + timeout=GLOBAL_DEFAULT, + source_address=None, + *, + all_errors=False, + ) -> TLSSocket: """Creates a TLSSocket that behaves like a socket.socket, and contains information about the TLS exchange (cipher, negotiated_protocol, negotiated_tls_version, etc.). @@ -382,9 +389,18 @@ The ``ServerContext`` protocol class has the following class definition: ... @abstractmethod - def connect(self, address: tuple[str | None, int]) -> TLSSocket: - """Creates a TLSSocket that behaves like a socket.socket, and - contains information about the TLS exchange + def create_server( + self, + address: tuple[str | None, int], + *, + family=AF_INET, + backlog=None, + reuse_port=False, + dualstack_ipv6=False, + ) -> TLSSocket: + """Creates a listening socket with an accept() function that returns + a TLSSocket that behaves like a socket.socket, and contains + information about the TLS exchange (cipher, negotiated_protocol, negotiated_tls_version, etc.). """ ... @@ -404,7 +420,7 @@ specification of the ``TLSSocket`` protocol class. Specifically, implementations need to implement the following: * ``recv`` and ``send`` -* ``listen`` and ``accept`` +* ``accept`` (server-side) * ``close`` * ``getsockname`` * ``getpeername`` @@ -458,13 +474,6 @@ The following code describes these functions in more detail: close_notify alert currently fails.""" ... - @abstractmethod - def listen(self, backlog: int) -> None: - """Enable a server to accept connections. If backlog is specified, it - specifies the number of unaccepted connections that the system will allow - before refusing new connections.""" - ... - @abstractmethod def accept(self) -> tuple[TLSSocket, tuple[str | None, int]]: """Accept a connection. The socket must be bound to an address and listening From 3d40e2fe33caab5986a6a52c30d8c0126da157c5 Mon Sep 17 00:00:00 2001 From: Julien Castiaux Date: Wed, 29 Apr 2026 01:51:41 +0200 Subject: [PATCH 4/4] PEP 748: shutdown(show: 0|1|2) instead of close(force: bool) --- peps/pep-0748.rst | 69 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 59 insertions(+), 10 deletions(-) diff --git a/peps/pep-0748.rst b/peps/pep-0748.rst index 81b9646cff9..6ac53798ec6 100644 --- a/peps/pep-0748.rst +++ b/peps/pep-0748.rst @@ -421,7 +421,7 @@ need to implement the following: * ``recv`` and ``send`` * ``accept`` (server-side) -* ``close`` +* ``shutdown`` and ``close`` * ``getsockname`` * ``getpeername`` @@ -463,15 +463,64 @@ The following code describes these functions in more detail: ... @abstractmethod - def close(self, force: bool = False) -> None: - """Shuts down the connection and mark the socket closed. - If force is True, this method should send the close_notify alert and shut down - the socket without waiting for the other side. - If force is False, this method should send the close_notify alert and raise - the WantReadError exception until a corresponding close_notify alert has been - received from the other side. - In either case, this method should return WantWriteError if sending the - close_notify alert currently fails.""" + def shutdown(self, how: Literal[0, 1, 2]) -> None: + """ + Shutdown TLS and the underlying socket. + + Proper TLS applications ought to signal their peers when they're + done sending data by sending a closing alert. + + * ``socket.SHUT_WR`` (``1``) sends the closing alert to the peer and + then prevents sending any further message. Proper TLS application + **MUST** call this method with this parameter to gracefully close + the TLS connection. *Safe* in TLS 1.3. Actually acts like + ``SHUT_RDWD`` in TLS 1.2 and is as *unsafe* as ``SHUT_RD``. + + * ``socket.SHUT_RD`` (``0``) simulates receiving the closing alert + from the peer and then ignores all further received messages. + *Unsafe* (risk of data loss) unless the connection is otherwise + known to be over thanks to the application-layer protocol (e.g. + HTTP Content-Length). + + * ``socket.SHUT_RDWR`` (``2``) does both ``SHUT_RD`` and ``SHUT_WR``. + As *unsafe* as ``SHUT_RD``. + + In TLS 1.2, the closing alert is synchronous, receiving it triggers + an immediate closing alert response, and both connections are + immediately shut. It means that ``SHUT_WR`` acts like ``SHUT_RDWR`` + and is unsafe unless all the data have been received. + + In TLS 1.3, the closing alert is asynchronous, sending the closing + alert only closes the sender's sending-end and receiver's + receiving-end. The peer can keep on sending data until he + independently decides to close its own sending-end of the + connection. There is not risk of data truncation with ``SHUT_WR``. + + .. danger:: + + Both ``socket.SHUT_RD`` (``0``) and ``socket.SHUT_RDWR`` (``2``) + pose a risk of data loss, only use them when facing a bad actor + or when the connection is otherwise known to be over. + + In TLS 1.2 the same risk applies also to ``socket.SHUT_WR`` (``1``). + """ + ... + + @abstractmethod + def close(self) -> None: + """ + Close the underlying socket, but only when it is safe to do so. + + When the sending-end of the connection is still open, it sends a + closing alert before closing the socket, raising ``WantWriteError`` + when it fails. + + When the receiving-end of the connection is still open, it always + raises ``WantReadError`` as there's a risk of data loss / truncation + attack. The user must first either: (safe) ``recv`` until the peer + closes its sending-end of the connection, or (unsafe) take the risk + and unilateraly ``shutdown`` the reading-end of the connection. + """ ... @abstractmethod