From 539e879939c2682ff35d1300ea6f30c0a64ca13f Mon Sep 17 00:00:00 2001 From: Julien Castiaux Date: Tue, 28 Apr 2026 23:47:53 +0200 Subject: [PATCH 1/5] 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/5] 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 506c877e9a950c8c4014775ea6a3d648c12b485d Mon Sep 17 00:00:00 2001 From: Julien Castiaux Date: Wed, 29 Apr 2026 00:07:46 +0200 Subject: [PATCH 3/5] 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 | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/peps/pep-0748.rst b/peps/pep-0748.rst index 48af3140d33..a2ca96dab56 100644 --- a/peps/pep-0748.rst +++ b/peps/pep-0748.rst @@ -351,7 +351,15 @@ 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, bytes | str | int | None], + timeout: float | None = ..., + source_address: _Address | None = None, + *, + all_errors: bool = False, + sever_hostname: str | None = None, + ) -> 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 +390,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: _Address, + *, + family: int = socket.AF_INET, + backlog: int | None = None, + reuse_port: bool = False, + dualstack_ipv6: bool = 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 +421,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 +475,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 54723e3b4b4405dfb674fe54722ace5621f9de6f Mon Sep 17 00:00:00 2001 From: Julien Castiaux Date: Wed, 29 Apr 2026 01:51:41 +0200 Subject: [PATCH 4/5] 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 a2ca96dab56..ebf0c1be0df 100644 --- a/peps/pep-0748.rst +++ b/peps/pep-0748.rst @@ -422,7 +422,7 @@ need to implement the following: * ``recv`` and ``send`` * ``accept`` (server-side) -* ``close`` +* ``shutdown`` and ``close`` * ``getsockname`` * ``getpeername`` @@ -464,15 +464,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 From bc67f9faade5ab568a90912c9cd32153e99e07fa Mon Sep 17 00:00:00 2001 From: Julien Castiaux Date: Tue, 12 May 2026 23:49:06 +0200 Subject: [PATCH 5/5] fixup! PEP 748: shutdown(show: 0|1|2) instead of close(force: bool) --- peps/pep-0748.rst | 53 ++++++++++++++--------------------------------- 1 file changed, 15 insertions(+), 38 deletions(-) diff --git a/peps/pep-0748.rst b/peps/pep-0748.rst index ebf0c1be0df..964cfe6e5de 100644 --- a/peps/pep-0748.rst +++ b/peps/pep-0748.rst @@ -468,42 +468,19 @@ The following code describes these functions in more detail: """ 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``. + * ``socket.SHUT_RD`` (``0``) unilateraly close the receiving-side: + discard present and future unread messages. + * ``socket.SHUT_WR`` (``1``) gracefully close the sending-side: + send a closing alert and prevent sending more messages. + * ``socket.SHUT_RDWR`` (``2``): both ``SHUT_WR`` and ``SHUT_RD``. .. 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. + Both ``shutdown(SHUT_RD)`` and ``shutdown(SHUT_RDWR)`` pose a + risk of data loss: they are unsafe unless the connection is + otherwise known to be over or when truncation is not an issue. - In TLS 1.2 the same risk applies also to ``socket.SHUT_WR`` (``1``). + In TLS 1.2, the same risk applies also to ``shutdown(SHUT_WR)``. """ ... @@ -512,15 +489,15 @@ The following code describes these functions in more detail: """ 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 sending-side of the connection is still open, it gracefully + closes the TLS sending-side before closing the socket, raising + ``WantWriteError`` when it fails. - When the receiving-end of the connection is still open, it always + When the receiving-side 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. + closes its sending-side of the connection, or (unsafe) take the risk + and unilateraly ``shutdown`` the receiving-side of the connection. """ ...