def receive_data(self, data: bytes) -> layer.CommandGenerator[None]: if data: self.tls.bio_write(data) yield from self.tls_interact() plaintext = bytearray() close = False while True: try: plaintext.extend(self.tls.recv(65535)) except SSL.WantReadError: break except SSL.ZeroReturnError: close = True break if plaintext: yield from self.event_to_child( events.DataReceived(self.conn, bytes(plaintext)) ) if close: self.conn.state &= ~context.ConnectionState.CAN_READ if self.debug: yield commands.Log(f"{self.debug}[tls] close_notify {self.conn}", level="debug") yield from self.event_to_child( events.ConnectionClosed(self.conn) )
def passthrough(self, event: events.Event) -> layer.CommandGenerator[None]: assert self.flow.response assert self.child_layer # HTTP events -> normal connection events if isinstance(event, RequestData): event = events.DataReceived(self.context.client, event.data) elif isinstance(event, ResponseData): event = events.DataReceived(self.context.server, event.data) elif isinstance(event, RequestEndOfMessage): event = events.ConnectionClosed(self.context.client) elif isinstance(event, ResponseEndOfMessage): event = events.ConnectionClosed(self.context.server) for command in self.child_layer.handle_event(event): # normal connection events -> HTTP events if isinstance(command, commands.SendData): if command.connection == self.context.client: yield SendHttp(ResponseData(self.stream_id, command.data), self.context.client) elif command.connection == self.context.server and self.flow.response.status_code == 101: # there only is a HTTP server connection if we have switched protocols, # not if a connection is established via CONNECT. yield SendHttp(RequestData(self.stream_id, command.data), self.context.server) else: yield command elif isinstance(command, commands.CloseConnection): if command.connection == self.context.client: yield SendHttp(ResponseProtocolError(self.stream_id, "EOF"), self.context.client) elif command.connection == self.context.server and self.flow.response.status_code == 101: yield SendHttp(RequestProtocolError(self.stream_id, "EOF"), self.context.server) else: # If we are running TCP over HTTP we want to be consistent with half-closes. # The easiest approach for this is to just always full close for now. # Alternatively, we could signal that we want a half close only through ResponseProtocolError, # but that is more complex to implement. command.half_close = False yield command else: yield command
async def handle_connection(self, connection: Connection) -> None: """ Handle a connection for its entire lifetime. This means we read until EOF, but then possibly also keep on waiting for our side of the connection to be closed. """ cancelled = None reader = self.transports[connection].reader assert reader while True: try: data = await reader.read(65535) if not data: raise OSError("Connection closed by peer.") except OSError: break except asyncio.CancelledError as e: cancelled = e break else: self.server_event(events.DataReceived(connection, data)) if cancelled is None: connection.state &= ~ConnectionState.CAN_READ else: connection.state = ConnectionState.CLOSED self.server_event(events.ConnectionClosed(connection)) if cancelled is None and connection.state is ConnectionState.CAN_WRITE: # we may still use this connection to *send* stuff, # even though the remote has closed their side of the connection. # to make this work we keep this task running and wait for cancellation. await asyncio.Event().wait() try: writer = self.transports[connection].writer assert writer writer.close() except OSError: pass self.transports.pop(connection) if cancelled: raise cancelled
def test_dataclasses(tconn): assert repr(events.Start()) assert repr(events.DataReceived(tconn, b"foo")) assert repr(events.ConnectionClosed(tconn))
def receive_close(self) -> layer.CommandGenerator[None]: yield from self.event_to_child(events.ConnectionClosed(self.conn))