Перейти к содержимому

Назад

sslproto.py

Lib/asyncio/sslproto.pyCPython 3.15
python
# Содержит код из https://github.com/MagicStack/uvloop/tree/v0.16.0
# SPDX-License-Identifier: PSF-2.0 AND (MIT OR Apache-2.0)
# SPDX-FileCopyrightText: Copyright (c) 2015-2021 MagicStack Inc.  http://magic.io

import collections
import enum
import warnings
try:
    import ssl
except ImportError:  # pragma: no cover
    ssl = None

from . import constants
from . import exceptions
from . import protocols
from . import transports
from .log import logger

if ssl is not None:
    SSLAgainErrors = (ssl.SSLWantReadError, ssl.SSLSyscallError)


class SSLProtocolState(enum.Enum):
    UNWRAPPED = "UNWRAPPED"
    DO_HANDSHAKE = "DO_HANDSHAKE"
    WRAPPED = "WRAPPED"
    FLUSHING = "FLUSHING"
    SHUTDOWN = "SHUTDOWN"


class AppProtocolState(enum.Enum):
    # Это отслеживает состояние протокола приложения (https://git.io/fj59P):
    #
    #     INIT -cm-> CON_MADE [-dr*->] [-er-> EOF?] -cl-> CON_LOST
    #
    # * cm: connection_made()
    # * dr: data_received()
    # * er: eof_received()
    # * cl: connection_lost()

    STATE_INIT = "STATE_INIT"
    STATE_CON_MADE = "STATE_CON_MADE"
    STATE_EOF = "STATE_EOF"
    STATE_CON_LOST = "STATE_CON_LOST"


def _create_transport_context(server_side, server_hostname):
    if server_side:
        raise ValueError('Server side SSL needs a valid SSLContext')

    # Клиентская сторона может передать ssl=True, чтобы использовать умолчательный
    # контекст; в этом случае переданный sslcontext равен None.
    # По умолчанию используется безопасный контекст для клиентских соединений.
    # Python 3.4+: использовать современные надежные настройки.
    sslcontext = ssl.create_default_context()
    if not server_hostname:
        sslcontext.check_hostname = False
    return sslcontext


def add_flowcontrol_defaults(high, low, kb):
    if high is None:
        if low is None:
            hi = kb * 1024
        else:
            lo = low
            hi = 4 * lo
    else:
        hi = high
    if low is None:
        lo = hi // 4
    else:
        lo = low

    if not hi >= lo >= 0:
        raise ValueError('high (%r) must be >= low (%r) must be >= 0' %
                         (hi, lo))

    return hi, lo


class _SSLProtocolTransport(transports._FlowControlMixin,
                            transports.Transport):

    _start_tls_compatible = True
    _sendfile_compatible = constants._SendfileMode.FALLBACK

    def __init__(self, loop, ssl_protocol):
        self._loop = loop
        self._ssl_protocol = ssl_protocol
        self._closed = False

    def get_extra_info(self, name, default=None):
        """Получить дополнительную информацию о транспорте."""
        return self._ssl_protocol._get_extra_info(name, default)

    def set_protocol(self, protocol):
        self._ssl_protocol._set_app_protocol(protocol)

    def get_protocol(self):
        return self._ssl_protocol._app_protocol

    def is_closing(self):
        return self._closed or self._ssl_protocol._is_transport_closing()

    def close(self):
        """Закрыть транспорт.

        Буферизованные данные будут асинхронно сброшены. Больше данных
        не будет получено. После того, как все буферизованные данные будут сброшены,
        метод connection_lost() протокола будет (в конечном итоге) вызван
        с None в качестве аргумента.
        """
        if not self._closed:
            self._closed = True
            self._ssl_protocol._start_shutdown()
        else:
            self._ssl_protocol = None

    def __del__(self, _warnings=warnings):
        if not self._closed:
            self._closed = True
            _warnings.warn(
                "unclosed transport <asyncio._SSLProtocolTransport "
                "object>", ResourceWarning)

    def is_reading(self):
        return not self._ssl_protocol._app_reading_paused

    def pause_reading(self):
        """Приостановить приём.

        Данные не будут передаваться методу data_received() протокола
        до тех пор, пока не будет вызван resume_reading().
        """
        self._ssl_protocol._pause_reading()

    def resume_reading(self):
        """Возобновить приём.

        Полученные данные снова будут передаваться методу data_received()
        протокола.
        """
        self._ssl_protocol._resume_reading()

    def set_write_buffer_limits(self, high=None, low=None):
        """Установить верхний и нижний пороги для управления потоком записи.

        Эти два значения определяют, когда вызывать методы pause_writing()
        и resume_writing() протокола. Если они заданы,
        нижний порог должен быть меньше или равен
        верхнему порогу. Ни одно из значений не может быть отрицательным.

        Значения по умолчанию зависят от реализации. Если указан только
        верхний порог, нижний порог по умолчанию устанавливается в
        значение, зависящее от реализации, меньшее или равное
        верхнему порогу. Установка верхнего порога в ноль также обнуляет
        нижний порог и приводит к вызову pause_writing() каждый раз,
        когда буфер становится непустым. Установка нижнего порога в ноль
        приводит к вызову resume_writing() только после опустошения буфера.
        Использование нуля для любого порога, как правило, неоптимально, так как
        это уменьшает возможности для одновременного выполнения операций ввода-вывода
        и вычислений.
        """
        self._ssl_protocol._set_write_buffer_limits(high, low)
        self._ssl_protocol._control_app_writing()

    def get_write_buffer_limits(self):
        return (self._ssl_protocol._outgoing_low_water,
                self._ssl_protocol._outgoing_high_water)

    def get_write_buffer_size(self):
        """Возвращает текущий размер буферов записи."""
        return self._ssl_protocol._get_write_buffer_size()

    def set_read_buffer_limits(self, high=None, low=None):
        """Установить верхний и нижний пороги для управления потоком чтения.

        Эти два значения управляют тем, когда вызывать методы pause_reading() и resume_reading() вышестоящего транспорта. Если заданы,
        нижний порог должен быть меньше или равен верхнему порогу. Ни одно значение не может быть отрицательным.

        Значения по умолчанию зависят от реализации. Если задан только
        верхний порог, нижний порог по умолчанию равен значению, зависящему от реализации, которое меньше или равно
        верхнему порогу. Установка верхнего порога в ноль устанавливает нижний порог также в ноль,
        и вызывает pause_reading() каждый раз, когда буфер становится непустым. Установка нижнего порога в ноль вызывает
        resume_reading() только когда буфер становится пустым.
        Использование нуля для любого из порогов обычно неоптимально, так как
        уменьшает возможности для параллельного выполнения ввода-вывода и вычислений.
        """
        self._ssl_protocol._set_read_buffer_limits(high, low)
        self._ssl_protocol._control_ssl_reading()

    def get_read_buffer_limits(self):
        return (self._ssl_protocol._incoming_low_water,
                self._ssl_protocol._incoming_high_water)

    def get_read_buffer_size(self):
        """Возвращает текущий размер буфера чтения."""
        return self._ssl_protocol._get_read_buffer_size()

    @property
    def _protocol_paused(self):
        # Требуется для логики pause_writing/resume_writing в запасном варианте sendfile
        return self._ssl_protocol._app_writing_paused

    def write(self, data):
        """Записать некоторые байты данных в транспорт.

        Этот вызов не блокируется; он буферизует данные и обеспечивает их
        асинхронную отправку.
        """
        if not isinstance(data, (bytes, bytearray, memoryview)):
            raise TypeError(f"data: expecting a bytes-like instance, "
                            f"got {type(data).__name__}")
        if not data:
            return
        self._ssl_protocol._write_appdata((data,))

    def writelines(self, list_of_data):
        """Записывает список (или любую итерируемую последовательность) байтов данных в транспорт.

        Реализация по умолчанию объединяет аргументы и
        вызывает write() для результата.
        """
        self._ssl_protocol._write_appdata(list_of_data)

    def write_eof(self):
        """Закрыть конец записи после сброса буферизованных данных.

        В настоящее время вызывает :exc:`NotImplementedError`.
        """
        raise NotImplementedError

    def can_write_eof(self):
        """Вернуть True, если этот транспорт поддерживает write_eof(), иначе False."""
        return False

    def abort(self):
        """Закрыть транспорт немедленно.

        Буферизованные данные будут потеряны. Новые данные приниматься не будут.
        Метод connection_lost() протокола будет (в конечном итоге) вызван
        с None в качестве аргумента.
        """
        self._force_close(None)

    def _force_close(self, exc):
        self._closed = True
        if self._ssl_protocol is not None:
            self._ssl_protocol._abort(exc)

    def _test__append_write_backlog(self, data):
        # только для тестирования
        self._ssl_protocol._write_backlog.append(data)
        self._ssl_protocol._write_buffer_size += len(data)


class SSLProtocol(protocols.BufferedProtocol):
    max_size = 256 * 1024   # Размер буфера, передаваемый в read()

    _handshake_start_time = None
    _handshake_timeout_handle = None
    _shutdown_timeout_handle = None

    def __init__(self, loop, app_protocol, sslcontext, waiter,
                 server_side=False, server_hostname=None,
                 call_connection_made=True,
                 ssl_handshake_timeout=None,
                 ssl_shutdown_timeout=None):
        if ssl is None:
            raise RuntimeError("stdlib ssl module not available")

        self._ssl_buffer = bytearray(self.max_size)
        self._ssl_buffer_view = memoryview(self._ssl_buffer)

        if ssl_handshake_timeout is None:
            ssl_handshake_timeout = constants.SSL_HANDSHAKE_TIMEOUT
        elif ssl_handshake_timeout <= 0:
            raise ValueError(
                f"ssl_handshake_timeout should be a positive number, "
                f"got {ssl_handshake_timeout}")
        if ssl_shutdown_timeout is None:
            ssl_shutdown_timeout = constants.SSL_SHUTDOWN_TIMEOUT
        elif ssl_shutdown_timeout <= 0:
            raise ValueError(
                f"ssl_shutdown_timeout should be a positive number, "
                f"got {ssl_shutdown_timeout}")

        if not sslcontext:
            sslcontext = _create_transport_context(
                server_side, server_hostname)

        self._server_side = server_side
        if server_hostname and not server_side:
            self._server_hostname = server_hostname
        else:
            self._server_hostname = None
        self._sslcontext = sslcontext
        # Дополнительная информация, специфичная для SSL. Дополнительные сведения устанавливаются при рукопожатии
        # завершается.
        self._extra = dict(sslcontext=sslcontext)

        # Буферизация записи данных приложения
        self._write_backlog = collections.deque()
        self._write_buffer_size = 0

        self._waiter = waiter
        self._loop = loop
        self._set_app_protocol(app_protocol)
        self._app_transport = None
        self._app_transport_created = False
        # транспорт, например: SelectorSocketTransport
        self._transport = None
        self._ssl_handshake_timeout = ssl_handshake_timeout
        self._ssl_shutdown_timeout = ssl_shutdown_timeout
        # SSL и конечный автомат
        self._incoming = ssl.MemoryBIO()
        self._outgoing = ssl.MemoryBIO()
        self._state = SSLProtocolState.UNWRAPPED
        self._conn_lost = 0  # Устанавливается при вызове connection_lost
        if call_connection_made:
            self._app_state = AppProtocolState.STATE_INIT
        else:
            self._app_state = AppProtocolState.STATE_CON_MADE
        self._sslobj = self._sslcontext.wrap_bio(
            self._incoming, self._outgoing,
            server_side=self._server_side,
            server_hostname=self._server_hostname)

        # Управление потоком

        self._ssl_writing_paused = False

        self._app_reading_paused = False

        self._ssl_reading_paused = False
        self._incoming_high_water = 0
        self._incoming_low_water = 0
        self._set_read_buffer_limits()
        self._eof_received = False

        self._app_writing_paused = False
        self._outgoing_high_water = 0
        self._outgoing_low_water = 0
        self._set_write_buffer_limits()
        self._get_app_transport()

    def _set_app_protocol(self, app_protocol):
        self._app_protocol = app_protocol
        # Сначала выполнить быструю проверку hasattr
        if (hasattr(app_protocol, 'get_buffer') and
                isinstance(app_protocol, protocols.BufferedProtocol)):
            self._app_protocol_get_buffer = app_protocol.get_buffer
            self._app_protocol_buffer_updated = app_protocol.buffer_updated
            self._app_protocol_is_buffer = True
        else:
            self._app_protocol_is_buffer = False

    def _wakeup_waiter(self, exc=None):
        if self._waiter is None:
            return
        if not self._waiter.cancelled():
            if exc is not None:
                self._waiter.set_exception(exc)
            else:
                self._waiter.set_result(None)
        self._waiter = None

    def _get_app_transport(self):
        if self._app_transport is None:
            if self._app_transport_created:
                raise RuntimeError('Creating _SSLProtocolTransport twice')
            self._app_transport = _SSLProtocolTransport(self._loop, self)
            self._app_transport_created = True
        return self._app_transport

    def _is_transport_closing(self):
        return self._transport is not None and self._transport.is_closing()

    def connection_made(self, transport):
        """Вызывается при установлении низкоуровневого соединения.

        Запустить SSL-рукопожатие.
        """
        self._transport = transport
        self._start_handshake()

    def connection_lost(self, exc):
        """Вызывается при потере или закрытии низкоуровневого соединения.

        Аргументом является объект исключения или None (последнее
        означает получение обычного EOF или что соединение было
        прервано или закрыто).
        """
        self._write_backlog.clear()
        self._outgoing.read()
        self._conn_lost += 1

        # Просто пометить транспорт приложения как закрытый, чтобы его __dealloc__
        # не жаловался.
        if self._app_transport is not None:
            self._app_transport._closed = True

        if self._state != SSLProtocolState.DO_HANDSHAKE:
            if (
                self._app_state == AppProtocolState.STATE_CON_MADE or
                self._app_state == AppProtocolState.STATE_EOF
            ):
                self._app_state = AppProtocolState.STATE_CON_LOST
                self._loop.call_soon(self._app_protocol.connection_lost, exc)
        self._set_state(SSLProtocolState.UNWRAPPED)
        self._transport = None
        self._app_transport = None
        self._app_protocol = None
        self._wakeup_waiter(exc)

        if self._shutdown_timeout_handle:
            self._shutdown_timeout_handle.cancel()
            self._shutdown_timeout_handle = None
        if self._handshake_timeout_handle:
            self._handshake_timeout_handle.cancel()
            self._handshake_timeout_handle = None

    def get_buffer(self, n):
        want = n
        if want <= 0 or want > self.max_size:
            want = self.max_size
        if len(self._ssl_buffer) < want:
            self._ssl_buffer = bytearray(want)
            self._ssl_buffer_view = memoryview(self._ssl_buffer)
        return self._ssl_buffer_view

    def buffer_updated(self, nbytes):
        self._incoming.write(self._ssl_buffer_view[:nbytes])

        if self._state == SSLProtocolState.DO_HANDSHAKE:
            self._do_handshake()

        elif self._state == SSLProtocolState.WRAPPED:
            self._do_read()

        elif self._state == SSLProtocolState.FLUSHING:
            self._do_flush()

        elif self._state == SSLProtocolState.SHUTDOWN:
            self._do_shutdown()

    def eof_received(self):
        """Вызывается, когда другой конец низкоуровневого потока данных
        наполовину закрыт.

        Если этот метод возвращает ложное значение (включая None), транспорт
        закроет себя. Если он возвращает истинное значение, закрытие
        транспорта остаётся на усмотрение протокола.
        """
        self._eof_received = True
        try:
            if self._loop.get_debug():
                logger.debug("%r received EOF", self)

            if self._state == SSLProtocolState.DO_HANDSHAKE:
                self._on_handshake_complete(ConnectionResetError)

            elif self._state == SSLProtocolState.WRAPPED:
                self._set_state(SSLProtocolState.FLUSHING)
                if self._app_reading_paused:
                    return True
                else:
                    self._do_flush()

            elif self._state == SSLProtocolState.FLUSHING:
                self._do_write()
                self._set_state(SSLProtocolState.SHUTDOWN)
                self._do_shutdown()

            elif self._state == SSLProtocolState.SHUTDOWN:
                self._do_shutdown()

        except Exception:
            self._transport.close()
            raise

    def _get_extra_info(self, name, default=None):
        if name in self._extra:
            return self._extra[name]
        elif self._transport is not None:
            return self._transport.get_extra_info(name, default)
        else:
            return default

    def _set_state(self, new_state):
        allowed = False

        if new_state == SSLProtocolState.UNWRAPPED:
            allowed = True

        elif (
            self._state == SSLProtocolState.UNWRAPPED and
            new_state == SSLProtocolState.DO_HANDSHAKE
        ):
            allowed = True

        elif (
            self._state == SSLProtocolState.DO_HANDSHAKE and
            new_state == SSLProtocolState.WRAPPED
        ):
            allowed = True

        elif (
            self._state == SSLProtocolState.WRAPPED and
            new_state == SSLProtocolState.FLUSHING
        ):
            allowed = True

        elif (
            self._state == SSLProtocolState.FLUSHING and
            new_state == SSLProtocolState.SHUTDOWN
        ):
            allowed = True

        if allowed:
            self._state = new_state

        else:
            raise RuntimeError(
                'cannot switch state from {} to {}'.format(
                    self._state, new_state))

    # Поток рукопожатия

    def _start_handshake(self):
        if self._loop.get_debug():
            logger.debug("%r starts SSL handshake", self)
            self._handshake_start_time = self._loop.time()
        else:
            self._handshake_start_time = None

        self._set_state(SSLProtocolState.DO_HANDSHAKE)

        # начать отсчет тайм-аута рукопожатия
        self._handshake_timeout_handle = \
            self._loop.call_later(self._ssl_handshake_timeout,
                                  self._check_handshake_timeout)

        self._do_handshake()

    def _check_handshake_timeout(self):
        if self._state == SSLProtocolState.DO_HANDSHAKE:
            msg = (
                f"SSL handshake is taking longer than "
                f"{self._ssl_handshake_timeout} seconds: "
                f"aborting the connection"
            )
            self._fatal_error(ConnectionAbortedError(msg))

    def _do_handshake(self):
        try:
            self._sslobj.do_handshake()
        except SSLAgainErrors:
            self._process_outgoing()
        except ssl.SSLError as exc:
            self._on_handshake_complete(exc)
        else:
            self._on_handshake_complete(None)

    def _on_handshake_complete(self, handshake_exc):
        if self._handshake_timeout_handle is not None:
            self._handshake_timeout_handle.cancel()
            self._handshake_timeout_handle = None

        sslobj = self._sslobj
        try:
            if handshake_exc is None:
                self._set_state(SSLProtocolState.WRAPPED)
            else:
                raise handshake_exc

            peercert = sslobj.getpeercert()
        except Exception as exc:
            handshake_exc = None
            self._set_state(SSLProtocolState.UNWRAPPED)
            if isinstance(exc, ssl.CertificateError):
                msg = 'SSL handshake failed on verifying the certificate'
            else:
                msg = 'SSL handshake failed'
            self._fatal_error(exc, msg)
            self._wakeup_waiter(exc)
            return

        if self._loop.get_debug():
            dt = self._loop.time() - self._handshake_start_time
            logger.debug("%r: SSL handshake took %.1f ms", self, dt * 1e3)

        # Добавить дополнительную информацию, которая становится доступной после рукопожатия.
        self._extra.update(peercert=peercert,
                           cipher=sslobj.cipher(),
                           compression=sslobj.compression(),
                           ssl_object=sslobj)
        if self._app_state == AppProtocolState.STATE_INIT:
            self._app_state = AppProtocolState.STATE_CON_MADE
            self._app_protocol.connection_made(self._get_app_transport())
        self._wakeup_waiter()
        self._do_read()

    # Поток завершения

    def _start_shutdown(self):
        if (
            self._state in (
                SSLProtocolState.FLUSHING,
                SSLProtocolState.SHUTDOWN,
                SSLProtocolState.UNWRAPPED
            )
        ):
            return
        if self._app_transport is not None:
            self._app_transport._closed = True
        if self._state == SSLProtocolState.DO_HANDSHAKE:
            self._abort(None)
        else:
            self._set_state(SSLProtocolState.FLUSHING)
            self._shutdown_timeout_handle = self._loop.call_later(
                self._ssl_shutdown_timeout,
                self._check_shutdown_timeout
            )
            self._do_flush()

    def _check_shutdown_timeout(self):
        if (
            self._state in (
                SSLProtocolState.FLUSHING,
                SSLProtocolState.SHUTDOWN
            )
        ):
            self._transport._force_close(
                exceptions.TimeoutError('SSL shutdown timed out'))

    def _do_flush(self):
        self._do_read()
        self._set_state(SSLProtocolState.SHUTDOWN)
        self._do_shutdown()

    def _do_shutdown(self):
        try:
            if not self._eof_received:
                self._sslobj.unwrap()
        except SSLAgainErrors:
            self._process_outgoing()
        except ssl.SSLError as exc:
            self._on_shutdown_complete(exc)
        else:
            self._process_outgoing()
            self._call_eof_received()
            self._on_shutdown_complete(None)

    def _on_shutdown_complete(self, shutdown_exc):
        if self._shutdown_timeout_handle is not None:
            self._shutdown_timeout_handle.cancel()
            self._shutdown_timeout_handle = None

        if shutdown_exc:
            self._fatal_error(shutdown_exc)
        else:
            self._loop.call_soon(self._transport.close)

    def _abort(self, exc):
        self._set_state(SSLProtocolState.UNWRAPPED)
        if self._transport is not None:
            self._transport._force_close(exc)

    # Исходящий поток

    def _write_appdata(self, list_of_data):
        if (
            self._state in (
                SSLProtocolState.FLUSHING,
                SSLProtocolState.SHUTDOWN,
                SSLProtocolState.UNWRAPPED
            )
        ):
            if self._conn_lost >= constants.LOG_THRESHOLD_FOR_CONNLOST_WRITES:
                logger.warning('SSL connection is closed')
            self._conn_lost += 1
            return

        for data in list_of_data:
            self._write_backlog.append(data)
            self._write_buffer_size += len(data)

        try:
            if self._state == SSLProtocolState.WRAPPED:
                self._do_write()

        except Exception as ex:
            self._fatal_error(ex, 'Fatal error on SSL protocol')

    def _do_write(self):
        try:
            while self._write_backlog:
                data = self._write_backlog[0]
                count = self._sslobj.write(data)
                data_len = len(data)
                if count < data_len:
                    self._write_backlog[0] = data[count:]
                    self._write_buffer_size -= count
                else:
                    del self._write_backlog[0]
                    self._write_buffer_size -= data_len
        except SSLAgainErrors:
            pass
        self._process_outgoing()

    def _process_outgoing(self):
        if not self._ssl_writing_paused:
            data = self._outgoing.read()
            if len(data):
                self._transport.write(data)
        self._control_app_writing()

    # Входящий поток

    def _do_read(self):
        if (
            self._state not in (
                SSLProtocolState.WRAPPED,
                SSLProtocolState.FLUSHING,
            )
        ):
            return
        try:
            if not self._app_reading_paused:
                if self._app_protocol_is_buffer:
                    self._do_read__buffered()
                else:
                    self._do_read__copied()
                if self._write_backlog:
                    self._do_write()
                else:
                    self._process_outgoing()
            self._control_ssl_reading()
        except Exception as ex:
            self._fatal_error(ex, 'Fatal error on SSL protocol')

    def _do_read__buffered(self):
        offset = 0
        count = 1

        buf = self._app_protocol_get_buffer(self._get_read_buffer_size())
        wants = len(buf)

        try:
            count = self._sslobj.read(wants, buf)

            if count > 0:
                offset = count
                while offset < wants:
                    count = self._sslobj.read(wants - offset, buf[offset:])
                    if count > 0:
                        offset += count
                    else:
                        break
                else:
                    self._loop.call_soon(self._do_read)
        except SSLAgainErrors:
            pass
        if offset > 0:
            self._app_protocol_buffer_updated(offset)
        if not count:
            # close_notify
            self._call_eof_received()
            self._start_shutdown()

    def _do_read__copied(self):
        chunk = b'1'
        zero = True
        one = False

        try:
            while True:
                chunk = self._sslobj.read(self.max_size)
                if not chunk:
                    break
                if zero:
                    zero = False
                    one = True
                    first = chunk
                elif one:
                    one = False
                    data = [first, chunk]
                else:
                    data.append(chunk)
        except SSLAgainErrors:
            pass
        if one:
            self._app_protocol.data_received(first)
        elif not zero:
            self._app_protocol.data_received(b''.join(data))
        if not chunk:
            # close_notify
            self._call_eof_received()
            self._start_shutdown()

    def _call_eof_received(self):
        try:
            if self._app_state == AppProtocolState.STATE_CON_MADE:
                self._app_state = AppProtocolState.STATE_EOF
                keep_open = self._app_protocol.eof_received()
                if keep_open:
                    logger.warning('returning true from eof_received() '
                                   'has no effect when using ssl')
        except (KeyboardInterrupt, SystemExit):
            raise
        except BaseException as ex:
            self._fatal_error(ex, 'Error calling eof_received()')

    # Управление потоком для записи из сокета приложения

    def _control_app_writing(self):
        size = self._get_write_buffer_size()
        if size >= self._outgoing_high_water and not self._app_writing_paused:
            self._app_writing_paused = True
            try:
                self._app_protocol.pause_writing()
            except (KeyboardInterrupt, SystemExit):
                raise
            except BaseException as exc:
                self._loop.call_exception_handler({
                    'message': 'protocol.pause_writing() failed',
                    'exception': exc,
                    'transport': self._app_transport,
                    'protocol': self,
                })
        elif size <= self._outgoing_low_water and self._app_writing_paused:
            self._app_writing_paused = False
            try:
                self._app_protocol.resume_writing()
            except (KeyboardInterrupt, SystemExit):
                raise
            except BaseException as exc:
                self._loop.call_exception_handler({
                    'message': 'protocol.resume_writing() failed',
                    'exception': exc,
                    'transport': self._app_transport,
                    'protocol': self,
                })

    def _get_write_buffer_size(self):
        return self._outgoing.pending + self._write_buffer_size

    def _set_write_buffer_limits(self, high=None, low=None):
        high, low = add_flowcontrol_defaults(
            high, low, constants.FLOW_CONTROL_HIGH_WATER_SSL_WRITE)
        self._outgoing_high_water = high
        self._outgoing_low_water = low

    # Управление потоком для чтения в сокет приложения

    def _pause_reading(self):
        self._app_reading_paused = True

    def _resume_reading(self):
        if self._app_reading_paused:
            self._app_reading_paused = False

            def resume():
                if self._state == SSLProtocolState.WRAPPED:
                    self._do_read()
                elif self._state == SSLProtocolState.FLUSHING:
                    self._do_flush()
                elif self._state == SSLProtocolState.SHUTDOWN:
                    self._do_shutdown()
            self._loop.call_soon(resume)

    # Управление потоком для чтения из SSL-сокета

    def _control_ssl_reading(self):
        size = self._get_read_buffer_size()
        if size >= self._incoming_high_water and not self._ssl_reading_paused:
            self._ssl_reading_paused = True
            self._transport.pause_reading()
        elif size <= self._incoming_low_water and self._ssl_reading_paused:
            self._ssl_reading_paused = False
            self._transport.resume_reading()

    def _set_read_buffer_limits(self, high=None, low=None):
        high, low = add_flowcontrol_defaults(
            high, low, constants.FLOW_CONTROL_HIGH_WATER_SSL_READ)
        self._incoming_high_water = high
        self._incoming_low_water = low

    def _get_read_buffer_size(self):
        return self._incoming.pending

    # Управление потоком для записи в SSL-сокет

    def pause_writing(self):
        """Вызывается, когда буфер низкоуровневого транспорта превышает
        верхнюю отметку.
        """
        assert not self._ssl_writing_paused
        self._ssl_writing_paused = True

    def resume_writing(self):
        """Вызывается, когда буфер низкоуровневого транспорта опускается ниже
        нижней отметки.
        """
        assert self._ssl_writing_paused
        self._ssl_writing_paused = False
        self._process_outgoing()

    def _fatal_error(self, exc, message='Fatal error on transport'):
        if self._transport:
            self._transport._force_close(exc)

        if isinstance(exc, OSError):
            if self._loop.get_debug():
                logger.debug("%r: %s", self, message, exc_info=True)
        elif not isinstance(exc, exceptions.CancelledError):
            self._loop.call_exception_handler({
                'message': message,
                'exception': exc,
                'transport': self._transport,
                'protocol': self,
            })