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 506c877e9a950c8c4014775ea6a3d648c12b485d 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 | 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 92016bea8d92b35fe13c8bf37b753adbbe6ba4ea 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 | 52 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/peps/pep-0748.rst b/peps/pep-0748.rst index a2ca96dab56..789146ba982 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`` @@ -438,6 +438,12 @@ The following code describes these functions in more detail: .. code-block:: python + class TLSShutdownMode(enum.IntEnum): + SHUT_RD = 0 + SHUT_WR = 1 + SHUT_RDWR = 2 + + class TLSSocket(Protocol): """This class implements a socket.socket-like object that creates an OS socket, wraps it in an SSL context, and provides read and write methods @@ -464,15 +470,41 @@ 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: TLSShutdownMode) -> None: + """ + Shutdown TLS and the underlying socket. + + * ``SHUT_RD`` (``0``) unilateraly close the receiving-side: discard + present and future unread messages. + * ``SHUT_WR`` (``1``) gracefully close the sending-side: send a + closing alert and prevent sending more messages. + * ``SHUT_RDWR`` (``2``): both ``SHUT_WR`` and ``SHUT_RD``. + + .. danger:: + + 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 ``shutdown(SHUT_WR)``. + """ + ... + + @abstractmethod + def close(self) -> None: + """ + Close the underlying socket, but only when it is safe to do so. + + 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-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-side of the connection, or (unsafe) take the risk + and unilateraly ``shutdown`` the receiving-side of the connection. + """ ... @abstractmethod