Send data, respecting the receiver's flow control instructions. If
        the provided data is larger than the connection's maximum outbound
        frame size, it will be broken into several frames as appropriate.

async def send_data(
        self,
        stream_id: int,
        data: bytes,
        end_stream: bool = False,
    ):
        """Send data, respecting the receiver's flow control instructions. If
        the provided data is larger than the connection's maximum outbound
        frame size, it will be broken into several frames as appropriate.
        """
        if self.closed:
            raise ConnectionClosedError
        stream = self._get_stream(stream_id)
        if stream.closed:
            raise StreamClosedError(stream_id)

        remaining = data
        while len(remaining) > 0:
            await asyncio.gather(
                self._writable.wait(),
                self._window_open(stream.id),
            )

            remaining_size = len(remaining)
            window_size = self._h2.local_flow_control_window(stream.id)
            max_frame_size = self._h2.max_outbound_frame_size

            send_size = min(remaining_size, window_size, max_frame_size)
            if send_size == 0:
                continue

            logger.debug(
                f'[{stream.id}] Sending {send_size} of {remaining_size} '
                f'bytes (window {window_size}, frame max {max_frame_size})'
            )

            to_send = remaining[:send_size]
            remaining = remaining[send_size:]
            end = (end_stream is True and len(remaining) == 0)

            self._h2.send_data(stream.id, to_send, end_stream=end)
            self._flush()

            if self._h2.local_flow_control_window(stream.id) == 0:
                stream.window_open.clear()