_libssh2_channel_write(LIBSSH2_CHANNEL *channel, int stream_id,
                       const unsigned char *buf, size_t buflen)
{
    int rc = 0;
    LIBSSH2_SESSION *session = channel->session;
    ssize_t wrote = 0; /* counter for this specific this call */

    /* In theory we could split larger buffers into several smaller packets
     * but it turns out to be really hard and nasty to do while still offering
     * the API/prototype.
     *
     * Instead we only deal with the first 32K in this call and for the parent
     * function to call it again with the remainder! 32K is a conservative
     * limit based on the text in RFC4253 section 6.1.
     */
    if(buflen > 32700)
        buflen = 32700;

    if(channel->write_state == libssh2_NB_state_idle) {
        unsigned char *s = channel->write_packet;

        _libssh2_debug(channel->session, LIBSSH2_TRACE_CONN,
                       "Writing %d bytes on channel %lu/%lu, stream #%d",
                       (int) buflen, channel->local.id, channel->remote.id,
                       stream_id);

        if(channel->local.close)
            return _libssh2_error(channel->session,
                                  LIBSSH2_ERROR_CHANNEL_CLOSED,
                                  "We've already closed this channel");
        else if(channel->local.eof)
            return _libssh2_error(channel->session,
                                  LIBSSH2_ERROR_CHANNEL_EOF_SENT,
                                  "EOF has already been received, "
                                  "data might be ignored");

        /* drain the incoming flow first, mostly to make sure we get all
         * pending window adjust packets */
        do
            rc = _libssh2_transport_read(session);
        while(rc > 0);

        if((rc < 0) && (rc != LIBSSH2_ERROR_EAGAIN)) {
            return _libssh2_error(channel->session, rc,
                                  "Failure while draining incoming flow");
        }

        if(channel->local.window_size <= 0) {
            /* there's no room for data so we stop */

            /* Waiting on the socket to be writable would be wrong because we
             * would be back here immediately, but a readable socket might
             * herald an incoming window adjustment.
             */
            session->socket_block_directions = LIBSSH2_SESSION_BLOCK_INBOUND;

            return (rc == LIBSSH2_ERROR_EAGAIN?rc:0);
        }

        channel->write_bufwrite = buflen;

        *(s++) = stream_id ? SSH_MSG_CHANNEL_EXTENDED_DATA :
            SSH_MSG_CHANNEL_DATA;
        _libssh2_store_u32(&s, channel->remote.id);
        if(stream_id)
            _libssh2_store_u32(&s, stream_id);

        /* Don't exceed the remote end's limits */
        /* REMEMBER local means local as the SOURCE of the data */
        if(channel->write_bufwrite > channel->local.window_size) {
            _libssh2_debug(session, LIBSSH2_TRACE_CONN,
                           "Splitting write block due to %lu byte "
                           "window_size on %lu/%lu/%d",
                           channel->local.window_size, channel->local.id,
                           channel->remote.id, stream_id);
            channel->write_bufwrite = channel->local.window_size;
        }
        if(channel->write_bufwrite > channel->local.packet_size) {
            _libssh2_debug(session, LIBSSH2_TRACE_CONN,
                           "Splitting write block due to %lu byte "
                           "packet_size on %lu/%lu/%d",
                           channel->local.packet_size, channel->local.id,
                           channel->remote.id, stream_id);
            channel->write_bufwrite = channel->local.packet_size;
        }
        /* store the size here only, the buffer is passed in as-is to
           _libssh2_transport_send() */
        _libssh2_store_u32(&s, channel->write_bufwrite);
        channel->write_packet_len = s - channel->write_packet;

        _libssh2_debug(session, LIBSSH2_TRACE_CONN,
                       "Sending %d bytes on channel %lu/%lu, stream_id=%d",
                       (int) channel->write_bufwrite, channel->local.id,
                       channel->remote.id, stream_id);

        channel->write_state = libssh2_NB_state_created;
    }

    if(channel->write_state == libssh2_NB_state_created) {
        rc = _libssh2_transport_send(session, channel->write_packet,
                                     channel->write_packet_len,
                                     buf, channel->write_bufwrite);
        if(rc == LIBSSH2_ERROR_EAGAIN) {
            return _libssh2_error(session, rc,
                                  "Unable to send channel data");
        }
        else if(rc) {
            channel->write_state = libssh2_NB_state_idle;
            return _libssh2_error(session, rc,
                                  "Unable to send channel data");
        }
        /* Shrink local window size */
        channel->local.window_size -= channel->write_bufwrite;

        wrote += channel->write_bufwrite;

        /* Since _libssh2_transport_write() succeeded, we must return
           now to allow the caller to provide the next chunk of data.

           We cannot move on to send the next piece of data that may
           already have been provided in this same function call, as we
           risk getting EAGAIN for that and we can't return information
           both about sent data as well as EAGAIN. So, by returning short
           now, the caller will call this function again with new data to
           send */

        channel->write_state = libssh2_NB_state_idle;

        return wrote;
    }

    return LIBSSH2_ERROR_INVAL; /* reaching this point is really bad */
}