| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007 |
- cdef _create_transport_context(server_side, server_hostname):
- if server_side:
- raise ValueError('Server side SSL needs a valid SSLContext')
- # Client side may pass ssl=True to use a default
- # context; in that case the sslcontext passed is None.
- # The default is secure for client connections.
- # Python 3.4+: use up-to-date strong settings.
- sslcontext = ssl_create_default_context()
- if not server_hostname:
- sslcontext.check_hostname = False
- return sslcontext
- cdef class _SSLProtocolTransport:
- # TODO:
- # _sendfile_compatible = constants._SendfileMode.FALLBACK
- def __cinit__(self, Loop loop, ssl_protocol, context):
- self._loop = loop
- # SSLProtocol instance
- self._ssl_protocol = ssl_protocol
- self._closed = False
- if context is None:
- context = Context_CopyCurrent()
- self.context = context
- def get_extra_info(self, name, default=None):
- """Get optional transport information."""
- 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
- def close(self):
- """Close the transport.
- Buffered data will be flushed asynchronously. No more data
- will be received. After all buffered data is flushed, the
- protocol's connection_lost() method will (eventually) called
- with None as its argument.
- """
- self._closed = True
- self._ssl_protocol._start_shutdown(self.context.copy())
- def __dealloc__(self):
- if not self._closed:
- self._closed = True
- warnings_warn(
- "unclosed transport <uvloop.loop._SSLProtocolTransport "
- "object>", ResourceWarning)
- def is_reading(self):
- return not self._ssl_protocol._app_reading_paused
- def pause_reading(self):
- """Pause the receiving end.
- No data will be passed to the protocol's data_received()
- method until resume_reading() is called.
- """
- self._ssl_protocol._pause_reading()
- def resume_reading(self):
- """Resume the receiving end.
- Data received will once again be passed to the protocol's
- data_received() method.
- """
- self._ssl_protocol._resume_reading(self.context.copy())
- def set_write_buffer_limits(self, high=None, low=None):
- """Set the high- and low-water limits for write flow control.
- These two values control when to call the protocol's
- pause_writing() and resume_writing() methods. If specified,
- the low-water limit must be less than or equal to the
- high-water limit. Neither value can be negative.
- The defaults are implementation-specific. If only the
- high-water limit is given, the low-water limit defaults to an
- implementation-specific value less than or equal to the
- high-water limit. Setting high to zero forces low to zero as
- well, and causes pause_writing() to be called whenever the
- buffer becomes non-empty. Setting low to zero causes
- resume_writing() to be called only once the buffer is empty.
- Use of zero for either limit is generally sub-optimal as it
- reduces opportunities for doing I/O and computation
- concurrently.
- """
- self._ssl_protocol._set_write_buffer_limits(high, low)
- self._ssl_protocol._control_app_writing(self.context.copy())
- 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 the current size of the write buffers."""
- return self._ssl_protocol._get_write_buffer_size()
- def set_read_buffer_limits(self, high=None, low=None):
- """Set the high- and low-water limits for read flow control.
- These two values control when to call the upstream transport's
- pause_reading() and resume_reading() methods. If specified,
- the low-water limit must be less than or equal to the
- high-water limit. Neither value can be negative.
- The defaults are implementation-specific. If only the
- high-water limit is given, the low-water limit defaults to an
- implementation-specific value less than or equal to the
- high-water limit. Setting high to zero forces low to zero as
- well, and causes pause_reading() to be called whenever the
- buffer becomes non-empty. Setting low to zero causes
- resume_reading() to be called only once the buffer is empty.
- Use of zero for either limit is generally sub-optimal as it
- reduces opportunities for doing I/O and computation
- concurrently.
- """
- 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 the current size of the read buffer."""
- return self._ssl_protocol._get_read_buffer_size()
- @property
- def _protocol_paused(self):
- # Required for sendfile fallback pause_writing/resume_writing logic
- return self._ssl_protocol._app_writing_paused
- def write(self, data):
- """Write some data bytes to the transport.
- This does not block; it buffers the data and arranges for it
- to be sent out asynchronously.
- """
- 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,), self.context.copy())
- def writelines(self, list_of_data):
- """Write a list (or any iterable) of data bytes to the transport.
- The default implementation concatenates the arguments and
- calls write() on the result.
- """
- self._ssl_protocol._write_appdata(list_of_data, self.context.copy())
- def write_eof(self):
- """Close the write end after flushing buffered data.
- This raises :exc:`NotImplementedError` right now.
- """
- raise NotImplementedError
- def can_write_eof(self):
- """Return True if this transport supports write_eof(), False if not."""
- return False
- def abort(self):
- """Close the transport immediately.
- Buffered data will be lost. No more data will be received.
- The protocol's connection_lost() method will (eventually) be
- called with None as its argument.
- """
- self._force_close(None)
- def _force_close(self, exc):
- self._closed = True
- self._ssl_protocol._abort(exc)
- def _test__append_write_backlog(self, data):
- # for test only
- self._ssl_protocol._write_backlog.append(data)
- self._ssl_protocol._write_buffer_size += len(data)
- cdef class SSLProtocol:
- """SSL protocol.
- Implementation of SSL on top of a socket using incoming and outgoing
- buffers which are ssl.MemoryBIO objects.
- """
- def __cinit__(self, *args, **kwargs):
- self._ssl_buffer_len = SSL_READ_MAX_SIZE
- self._ssl_buffer = <char*>PyMem_RawMalloc(self._ssl_buffer_len)
- if not self._ssl_buffer:
- raise MemoryError()
- def __dealloc__(self):
- PyMem_RawFree(self._ssl_buffer)
- self._ssl_buffer = NULL
- self._ssl_buffer_len = 0
- 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_handshake_timeout is None:
- ssl_handshake_timeout = 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 = 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-specific extra info. More info are set when the handshake
- # completes.
- self._extra = dict(sslcontext=sslcontext)
- # App data write buffering
- self._write_backlog = col_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
- # transport, ex: SelectorSocketTransport
- self._transport = None
- self._ssl_handshake_timeout = ssl_handshake_timeout
- self._ssl_shutdown_timeout = ssl_shutdown_timeout
- # SSL and state machine
- self._sslobj = None
- self._incoming = ssl_MemoryBIO()
- self._incoming_write = self._incoming.write
- self._outgoing = ssl_MemoryBIO()
- self._outgoing_read = self._outgoing.read
- self._state = UNWRAPPED
- self._conn_lost = 0 # Set when connection_lost called
- if call_connection_made:
- self._app_state = STATE_INIT
- else:
- self._app_state = STATE_CON_MADE
- # Flow Control
- 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._app_writing_paused = False
- self._outgoing_high_water = 0
- self._outgoing_low_water = 0
- self._set_write_buffer_limits()
- cdef _set_app_protocol(self, app_protocol):
- self._app_protocol = app_protocol
- if (hasattr(app_protocol, 'get_buffer') and
- not isinstance(app_protocol, aio_Protocol)):
- 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
- cdef _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, context=None):
- if self._app_transport is None:
- if self._app_transport_created:
- raise RuntimeError('Creating _SSLProtocolTransport twice')
- self._app_transport = _SSLProtocolTransport(self._loop, self,
- context)
- self._app_transport_created = True
- return self._app_transport
- def connection_made(self, transport):
- """Called when the low-level connection is made.
- Start the SSL handshake.
- """
- self._transport = transport
- self._start_handshake()
- def connection_lost(self, exc):
- """Called when the low-level connection is lost or closed.
- The argument is an exception object or None (the latter
- meaning a regular EOF is received or the connection was
- aborted or closed).
- """
- self._write_backlog.clear()
- self._outgoing_read()
- self._conn_lost += 1
- # Just mark the app transport as closed so that its __dealloc__
- # doesn't complain.
- if self._app_transport is not None:
- self._app_transport._closed = True
- if self._state != DO_HANDSHAKE:
- if self._app_state == STATE_CON_MADE or \
- self._app_state == STATE_EOF:
- self._app_state = STATE_CON_LOST
- self._loop.call_soon(self._app_protocol.connection_lost, exc)
- self._set_state(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
- cdef get_buffer_impl(self, size_t n, char** buf, size_t* buf_size):
- cdef size_t want = n
- if want > SSL_READ_MAX_SIZE:
- want = SSL_READ_MAX_SIZE
- if self._ssl_buffer_len < want:
- self._ssl_buffer = <char*>PyMem_RawRealloc(self._ssl_buffer, want)
- if not self._ssl_buffer:
- raise MemoryError()
- self._ssl_buffer_len = want
- buf[0] = self._ssl_buffer
- buf_size[0] = self._ssl_buffer_len
- cdef buffer_updated_impl(self, size_t nbytes):
- self._incoming_write(PyMemoryView_FromMemory(
- self._ssl_buffer, nbytes, PyBUF_WRITE))
- if self._state == DO_HANDSHAKE:
- self._do_handshake()
- elif self._state == WRAPPED:
- self._do_read()
- elif self._state == FLUSHING:
- self._do_flush()
- elif self._state == SHUTDOWN:
- self._do_shutdown()
- def get_buffer(self, size_t n):
- # This pure python call is still used by some very peculiar test cases
- cdef:
- char* buf
- size_t buf_size
- self.get_buffer_impl(n, &buf, &buf_size)
- return PyMemoryView_FromMemory(buf, buf_size, PyBUF_WRITE)
- def buffer_updated(self, size_t nbytes):
- self.buffer_updated_impl(nbytes)
- def eof_received(self):
- """Called when the other end of the low-level stream
- is half-closed.
- If this returns a false value (including None), the transport
- will close itself. If it returns a true value, closing the
- transport is up to the protocol.
- """
- try:
- if self._loop.get_debug():
- aio_logger.debug("%r received EOF", self)
- if self._state == DO_HANDSHAKE:
- self._on_handshake_complete(ConnectionResetError)
- elif self._state == WRAPPED or self._state == FLUSHING:
- # We treat a low-level EOF as a critical situation similar to a
- # broken connection - just send whatever is in the buffer and
- # close. No application level eof_received() is called -
- # because we don't want the user to think that this is a
- # graceful shutdown triggered by SSL "close_notify".
- self._set_state(SHUTDOWN)
- self._on_shutdown_complete(None)
- elif self._state == SHUTDOWN:
- self._on_shutdown_complete(None)
- except Exception:
- self._transport.close()
- raise
- cdef _get_extra_info(self, name, default=None):
- if name == 'uvloop.sslproto':
- return self
- elif 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
- cdef _set_state(self, SSLProtocolState new_state):
- cdef bint allowed = False
- if new_state == UNWRAPPED:
- allowed = True
- elif self._state == UNWRAPPED and new_state == DO_HANDSHAKE:
- allowed = True
- elif self._state == DO_HANDSHAKE and new_state == WRAPPED:
- allowed = True
- elif self._state == WRAPPED and new_state == FLUSHING:
- allowed = True
- elif self._state == WRAPPED and new_state == SHUTDOWN:
- allowed = True
- elif self._state == FLUSHING and new_state == SHUTDOWN:
- allowed = True
- if allowed:
- self._state = new_state
- else:
- raise RuntimeError(
- 'cannot switch state from {} to {}'.format(
- self._state, new_state))
- # Handshake flow
- cdef _start_handshake(self):
- if self._loop.get_debug():
- aio_logger.debug("%r starts SSL handshake", self)
- self._handshake_start_time = self._loop.time()
- else:
- self._handshake_start_time = None
- self._set_state(DO_HANDSHAKE)
- # start handshake timeout count down
- self._handshake_timeout_handle = \
- self._loop.call_later(self._ssl_handshake_timeout,
- lambda: self._check_handshake_timeout())
- try:
- self._sslobj = self._sslcontext.wrap_bio(
- self._incoming, self._outgoing,
- server_side=self._server_side,
- server_hostname=self._server_hostname)
- self._sslobj_read = self._sslobj.read
- self._sslobj_write = self._sslobj.write
- self._sslobj_pending = self._sslobj.pending
- except Exception as ex:
- self._on_handshake_complete(ex)
- else:
- self._do_handshake()
- cdef _check_handshake_timeout(self):
- if self._state == 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))
- cdef _do_handshake(self):
- try:
- self._sslobj.do_handshake()
- except ssl_SSLAgainErrors as exc:
- self._process_outgoing()
- except ssl_SSLError as exc:
- self._on_handshake_complete(exc)
- else:
- self._on_handshake_complete(None)
- cdef _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(WRAPPED)
- else:
- raise handshake_exc
- peercert = sslobj.getpeercert()
- except Exception as exc:
- self._set_state(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
- aio_logger.debug("%r: SSL handshake took %.1f ms", self, dt * 1e3)
- # Add extra info that becomes available after handshake.
- self._extra.update(peercert=peercert,
- cipher=sslobj.cipher(),
- compression=sslobj.compression(),
- ssl_object=sslobj)
- if self._app_state == STATE_INIT:
- self._app_state = STATE_CON_MADE
- self._app_protocol.connection_made(self._get_app_transport())
- self._wakeup_waiter()
- # We should wakeup user code before sending the first data below. In
- # case of `start_tls()`, the user can only get the SSLTransport in the
- # wakeup callback, because `connection_made()` is not called again.
- # We should schedule the first data later than the wakeup callback so
- # that the user get a chance to e.g. check ALPN with the transport
- # before having to handle the first data.
- self._loop._call_soon_handle(
- new_MethodHandle(self._loop,
- "SSLProtocol._do_read",
- <method_t> self._do_read,
- None, # current context is good
- self))
- # Shutdown flow
- cdef _start_shutdown(self, object context=None):
- if self._state in (FLUSHING, SHUTDOWN, UNWRAPPED):
- return
- # we don't need the context for _abort or the timeout, because
- # TCP transport._force_close() should be able to call
- # connection_lost() in the right context
- if self._app_transport is not None:
- self._app_transport._closed = True
- if self._state == DO_HANDSHAKE:
- self._abort(None)
- else:
- self._set_state(FLUSHING)
- self._shutdown_timeout_handle = \
- self._loop.call_later(self._ssl_shutdown_timeout,
- lambda: self._check_shutdown_timeout())
- self._do_flush(context)
- cdef _check_shutdown_timeout(self):
- if self._state in (FLUSHING, SHUTDOWN):
- self._transport._force_close(
- aio_TimeoutError('SSL shutdown timed out'))
- cdef _do_read_into_void(self, object context):
- """Consume and discard incoming application data.
- If close_notify is received for the first time, call eof_received.
- """
- cdef:
- bint close_notify = False
- try:
- while True:
- if not self._sslobj_read(SSL_READ_MAX_SIZE):
- close_notify = True
- break
- except ssl_SSLAgainErrors as exc:
- pass
- except ssl_SSLZeroReturnError:
- close_notify = True
- if close_notify:
- self._call_eof_received(context)
- cdef _do_flush(self, object context=None):
- """Flush the write backlog, discarding new data received.
- We don't send close_notify in FLUSHING because we still want to send
- the remaining data over SSL, even if we received a close_notify. Also,
- no application-level resume_writing() or pause_writing() will be called
- in FLUSHING, as we could fully manage the flow control internally.
- """
- try:
- self._do_read_into_void(context)
- self._do_write()
- self._process_outgoing()
- self._control_ssl_reading()
- except Exception as ex:
- self._on_shutdown_complete(ex)
- else:
- if not self._get_write_buffer_size():
- self._set_state(SHUTDOWN)
- self._do_shutdown(context)
- cdef _do_shutdown(self, object context=None):
- """Send close_notify and wait for the same from the peer."""
- try:
- # we must skip all application data (if any) before unwrap
- self._do_read_into_void(context)
- try:
- self._sslobj.unwrap()
- except ssl_SSLAgainErrors as exc:
- self._process_outgoing()
- else:
- self._process_outgoing()
- if not self._get_write_buffer_size():
- self._on_shutdown_complete(None)
- except Exception as ex:
- self._on_shutdown_complete(ex)
- cdef _on_shutdown_complete(self, shutdown_exc):
- if self._shutdown_timeout_handle is not None:
- self._shutdown_timeout_handle.cancel()
- self._shutdown_timeout_handle = None
- # we don't need the context here because TCP transport.close() should
- # be able to call connection_made() in the right context
- if shutdown_exc:
- self._fatal_error(shutdown_exc, 'Error occurred during shutdown')
- else:
- self._transport.close()
- cdef _abort(self, exc):
- self._set_state(UNWRAPPED)
- if self._transport is not None:
- self._transport._force_close(exc)
- # Outgoing flow
- cdef _write_appdata(self, list_of_data, object context):
- if self._state in (FLUSHING, SHUTDOWN, UNWRAPPED):
- if self._conn_lost >= LOG_THRESHOLD_FOR_CONNLOST_WRITES:
- aio_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 == WRAPPED:
- self._do_write()
- self._process_outgoing()
- self._control_app_writing(context)
- except Exception as ex:
- self._fatal_error(ex, 'Fatal error on SSL protocol')
- cdef _do_write(self):
- """Do SSL write, consumes write backlog and fills outgoing BIO."""
- cdef size_t data_len, count
- try:
- while self._write_backlog:
- data = self._write_backlog[0]
- count = self._sslobj_write(data)
- data_len = len(data)
- if count < data_len:
- if not PyMemoryView_Check(data):
- data = PyMemoryView_FromObject(data)
- self._write_backlog[0] = data[count:]
- self._write_buffer_size -= count
- else:
- del self._write_backlog[0]
- self._write_buffer_size -= data_len
- except ssl_SSLAgainErrors as exc:
- pass
- cdef _process_outgoing(self):
- """Send bytes from the outgoing BIO."""
- if not self._ssl_writing_paused:
- data = self._outgoing_read()
- if len(data):
- if isinstance(self._transport, UVStream):
- (<UVStream>self._transport).write(data)
- else:
- self._transport.write(data)
- # Incoming flow
- cdef _do_read(self):
- if self._state != WRAPPED:
- 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()
- self._process_outgoing()
- self._control_app_writing()
- self._control_ssl_reading()
- except Exception as ex:
- self._fatal_error(ex, 'Fatal error on SSL protocol')
- cdef _do_read__buffered(self):
- cdef:
- Py_ssize_t total_pending = (<Py_ssize_t>self._incoming.pending
- + <Py_ssize_t>self._sslobj_pending())
- # Ask for a little extra in case when decrypted data is bigger
- # than original
- object app_buffer = self._app_protocol_get_buffer(
- total_pending + 256)
- Py_ssize_t app_buffer_size = len(app_buffer)
- if app_buffer_size == 0:
- return
- cdef:
- Py_ssize_t last_bytes_read = -1
- Py_ssize_t total_bytes_read = 0
- Py_buffer pybuf
- bint pybuf_initialized = False
- try:
- # SSLObject.read may not return all available data in one go.
- # We have to keep calling read until it throw SSLWantReadError.
- # However, throwing SSLWantReadError is very expensive even in
- # the master trunk of cpython.
- # See https://github.com/python/cpython/issues/123954
- # One way to reduce reliance on SSLWantReadError is to check
- # self._incoming.pending > 0 and SSLObject.pending() > 0.
- # SSLObject.read may still throw SSLWantReadError even when
- # self._incoming.pending > 0 and SSLObject.pending() == 0,
- # but this should happen relatively rarely, only when ssl frame
- # is partially received.
- # This optimization works really well especially for peers
- # exchanging small messages and wanting to have minimal latency.
- # self._incoming.pending means how much data hasn't
- # been processed by ssl yet (read: "still encrypted"). The final
- # unencrypted data size maybe different.
- # self._sslobj.pending() means how much data has been already
- # decrypted and can be directly read with SSLObject.read.
- # Run test_create_server_ssl_over_ssl to reproduce different cases
- # for this method.
- while total_pending > 0:
- if total_bytes_read > 0:
- if not pybuf_initialized:
- PyObject_GetBuffer(app_buffer, &pybuf, PyBUF_WRITABLE)
- pybuf_initialized = True
- app_buffer = PyMemoryView_FromMemory(
- (<char*>pybuf.buf) + total_bytes_read,
- app_buffer_size - total_bytes_read,
- PyBUF_WRITE)
- last_bytes_read = <Py_ssize_t>self._sslobj_read(
- app_buffer_size - total_bytes_read, app_buffer)
- total_bytes_read += last_bytes_read
- if last_bytes_read == 0:
- break
- # User buffer may not fit all available data.
- if total_bytes_read == app_buffer_size:
- self._loop._call_soon_handle(
- new_MethodHandle(self._loop,
- "SSLProtocol._do_read",
- <method_t> self._do_read,
- None, # current context is good
- self))
- break
- total_pending = (<Py_ssize_t>self._incoming.pending +
- <Py_ssize_t>self._sslobj_pending())
- except ssl_SSLAgainErrors as exc:
- pass
- finally:
- if pybuf_initialized:
- PyBuffer_Release(&pybuf)
- if total_bytes_read > 0:
- self._app_protocol_buffer_updated(total_bytes_read)
- # SSLObject.read() may return 0 instead of throwing SSLWantReadError
- # This indicates that we reached EOF
- if last_bytes_read == 0:
- # close_notify
- self._call_eof_received()
- self._start_shutdown()
- cdef _do_read__copied(self):
- cdef:
- list data
- bytes first, chunk = b'1'
- bint zero = True, one = False
- try:
- while (<Py_ssize_t>self._incoming.pending > 0 or
- <Py_ssize_t>self._sslobj_pending() > 0):
- chunk = self._sslobj_read(SSL_READ_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 ssl_SSLAgainErrors as exc:
- 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()
- cdef _call_eof_received(self, object context=None):
- if self._app_state == STATE_CON_MADE:
- self._app_state = STATE_EOF
- try:
- if context is None:
- # If the caller didn't provide a context, we assume the
- # caller is already in the right context, which is usually
- # inside the upstream callbacks like buffer_updated()
- keep_open = self._app_protocol.eof_received()
- else:
- keep_open = run_in_context(
- context, self._app_protocol.eof_received,
- )
- except (KeyboardInterrupt, SystemExit):
- raise
- except BaseException as ex:
- self._fatal_error(ex, 'Error calling eof_received()')
- else:
- if keep_open:
- aio_logger.warning('returning true from eof_received() '
- 'has no effect when using ssl')
- # Flow control for writes from APP socket
- cdef _control_app_writing(self, object context=None):
- cdef size_t size = self._get_write_buffer_size()
- if size >= self._outgoing_high_water and not self._app_writing_paused:
- self._app_writing_paused = True
- try:
- if context is None:
- # If the caller didn't provide a context, we assume the
- # caller is already in the right context, which is usually
- # inside the upstream callbacks like buffer_updated()
- self._app_protocol.pause_writing()
- else:
- run_in_context(context, 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:
- if context is None:
- # If the caller didn't provide a context, we assume the
- # caller is already in the right context, which is usually
- # inside the upstream callbacks like resume_writing()
- self._app_protocol.resume_writing()
- else:
- run_in_context(context, 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,
- })
- cdef size_t _get_write_buffer_size(self):
- return self._outgoing.pending + self._write_buffer_size
- cdef _set_write_buffer_limits(self, high=None, low=None):
- high, low = add_flowcontrol_defaults(
- high, low, FLOW_CONTROL_HIGH_WATER_SSL_WRITE)
- self._outgoing_high_water = high
- self._outgoing_low_water = low
- # Flow control for reads to APP socket
- cdef _pause_reading(self):
- self._app_reading_paused = True
- cdef _resume_reading(self, object context):
- if self._app_reading_paused:
- self._app_reading_paused = False
- if self._state == WRAPPED:
- self._loop._call_soon_handle(
- new_MethodHandle(self._loop,
- "SSLProtocol._do_read",
- <method_t>self._do_read,
- context,
- self))
- # Flow control for reads from SSL socket
- cdef _control_ssl_reading(self):
- cdef size_t 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()
- cdef _set_read_buffer_limits(self, high=None, low=None):
- high, low = add_flowcontrol_defaults(
- high, low, FLOW_CONTROL_HIGH_WATER_SSL_READ)
- self._incoming_high_water = high
- self._incoming_low_water = low
- cdef size_t _get_read_buffer_size(self):
- return self._incoming.pending
- # Flow control for writes to SSL socket
- def pause_writing(self):
- """Called when the low-level transport's buffer goes over
- the high-water mark.
- """
- assert not self._ssl_writing_paused
- self._ssl_writing_paused = True
- def resume_writing(self):
- """Called when the low-level transport's buffer drains below
- the low-water mark.
- """
- assert self._ssl_writing_paused
- self._ssl_writing_paused = False
- if self._state == WRAPPED:
- self._process_outgoing()
- self._control_app_writing()
- elif self._state == FLUSHING:
- self._do_flush()
- elif self._state == SHUTDOWN:
- self._do_shutdown()
- cdef _fatal_error(self, exc, message='Fatal error on transport'):
- if self._app_transport:
- self._app_transport._force_close(exc)
- elif self._transport:
- self._transport._force_close(exc)
- if isinstance(exc, OSError):
- if self._loop.get_debug():
- aio_logger.debug("%r: %s", self, message, exc_info=True)
- elif not isinstance(exc, aio_CancelledError):
- self._loop.call_exception_handler({
- 'message': message,
- 'exception': exc,
- 'transport': self._transport,
- 'protocol': self,
- })
|