docs: def read(self, timeout=None):
          '''
          Read from the transport. If no data is available, should return None.
          The timeout is ignored as this returns only data that has already
          been buffered locally.
          '''
          # NOTE: copying over this comment from Connection, because there is
          # knowledge captured here, even if the details are stale
          # Because of the timer callback to dataRead when we re-buffered,
          # there's a chance that in between we've lost the socket. If that's
          # the case, just silently return as some code elsewhere would have
          # already notified us. That bug could be fixed by improving the
          # message reading so that we consume all possible messages and ensure
          # that only a partial message was rebuffered, so that we can rely on
          # the next read event to read the subsequent message.
          if not hasattr(self, '_sock'):
              return None
  
          # This is sort of a hack because we're faking that data is ready, but
          # it works for purposes of supporting timeouts
          if timeout:
              if self._heartbeat_timeout:
                  self._heartbeat_timeout.delete()
              self._heartbeat_timeout = \
                  event.timeout(timeout, self._sock_read_cb, self._sock)
          elif self._heartbeat_timeout:
              self._heartbeat_timeout.delete()
              self._heartbeat_timeout = None
  
          return self._sock.read()