static int reds_agent_state_restore(RedsState *reds, SpiceMigrateDataMain *mig_data)
{
    RedCharDeviceVDIPort *agent_dev = reds->agent_dev.get();
    uint32_t chunk_header_remaining;

    agent_dev->priv->vdi_chunk_header = mig_data->agent2client.chunk_header;
    spice_assert(mig_data->agent2client.chunk_header_size <= sizeof(VDIChunkHeader));
    chunk_header_remaining = sizeof(VDIChunkHeader) - mig_data->agent2client.chunk_header_size;
    if (chunk_header_remaining) {
        agent_dev->priv->read_state = VDI_PORT_READ_STATE_READ_HEADER;
        agent_dev->priv->receive_pos = (uint8_t *)&agent_dev->priv->vdi_chunk_header +
            mig_data->agent2client.chunk_header_size;
        agent_dev->priv->receive_len = chunk_header_remaining;
    } else {
        agent_dev->priv->message_receive_len = agent_dev->priv->vdi_chunk_header.size;
    }

    if (!mig_data->agent2client.msg_header_done) {
        uint8_t *partial_msg_header;

        if (!chunk_header_remaining) {
            uint32_t cur_buf_size;

            agent_dev->priv->read_state = VDI_PORT_READ_STATE_READ_DATA;
            agent_dev->priv->current_read_buf = vdi_port_get_read_buf(agent_dev);
            spice_assert(agent_dev->priv->current_read_buf);
            partial_msg_header = (uint8_t *)mig_data + mig_data->agent2client.msg_header_ptr -
                sizeof(SpiceMiniDataHeader);
            memcpy(agent_dev->priv->current_read_buf->data,
                   partial_msg_header,
                   mig_data->agent2client.msg_header_partial_len);
            agent_dev->priv->receive_pos = agent_dev->priv->current_read_buf->data +
                                      mig_data->agent2client.msg_header_partial_len;
            cur_buf_size = sizeof(agent_dev->priv->current_read_buf->data) -
                           mig_data->agent2client.msg_header_partial_len;
            agent_dev->priv->receive_len = MIN(agent_dev->priv->message_receive_len, cur_buf_size);
            agent_dev->priv->current_read_buf->len = agent_dev->priv->receive_len +
                                                 mig_data->agent2client.msg_header_partial_len;
            agent_dev->priv->message_receive_len -= agent_dev->priv->receive_len;
        } else {
            spice_assert(mig_data->agent2client.msg_header_partial_len == 0);
        }
    } else {
            agent_dev->priv->read_state = VDI_PORT_READ_STATE_GET_BUFF;
            agent_dev->priv->current_read_buf.reset();
            agent_dev->priv->receive_pos = NULL;
            agent_dev->priv->read_filter.msg_data_to_read = mig_data->agent2client.msg_remaining;
            agent_dev->priv->read_filter.result = (AgentMsgFilterResult) mig_data->agent2client.msg_filter_result;
    }

    agent_dev->priv->read_filter.discard_all = FALSE;
    agent_dev->priv->write_filter.discard_all = !mig_data->client_agent_started;
    agent_dev->priv->client_agent_started = mig_data->client_agent_started;

    agent_dev->priv->write_filter.msg_data_to_read = mig_data->client2agent.msg_remaining;
    agent_dev->priv->write_filter.result = (AgentMsgFilterResult) mig_data->client2agent.msg_filter_result;

    spice_debug("to agent filter: discard all %d, wait_msg %u, msg_filter_result %d",
                agent_dev->priv->write_filter.discard_all,
                agent_dev->priv->write_filter.msg_data_to_read,
                agent_dev->priv->write_filter.result);
    spice_debug("from agent filter: discard all %d, wait_msg %u, msg_filter_result %d",
                agent_dev->priv->read_filter.discard_all,
                agent_dev->priv->read_filter.msg_data_to_read,
                agent_dev->priv->read_filter.result);
    return agent_dev->restore(&mig_data->agent_base);
}


With OpenSSL 1.1: Disable client-initiated renegotiation.

Fixes issue #49
Fixes BZ#1904459

Signed-off-by: Julien Ropé <jrope@redhat.com>
Reported-by: BlackKD
Acked-by: Frediano Ziglio <fziglio@redhat.com>

static void reds_client_monitors_config(RedsState *reds, VDAgentMonitorsConfig *monitors_config)
{
    FOREACH_QXL_INSTANCE(reds, qxl) {
        if (!red_qxl_client_monitors_config(qxl, monitors_config)) {
            /* this is a normal condition, some qemu devices might not implement it */
            spice_debug("QXLInterface::client_monitors_config failed");
        }
    }
}

A flaw was found in spice in versions before 0.14.92. A DoS tool might make it easier for remote attackers to cause a denial of service (CPU consumption) by performing many renegotiations within a single connection.


static void reds_update_agent_properties(RedsState *reds)
{
    if (!reds->agent_dev || reds->config == NULL) {
        return;
    }
    /* copy & paste */
    reds->agent_dev->priv->write_filter.copy_paste_enabled = reds->config->agent_copypaste;
    reds->agent_dev->priv->read_filter.copy_paste_enabled = reds->config->agent_copypaste;
    /* file transfer */
    reds->agent_dev->priv->write_filter.file_xfer_enabled = reds->config->agent_file_xfer;
    reds->agent_dev->priv->read_filter.file_xfer_enabled = reds->config->agent_file_xfer;
}


static bool red_link_info_test_capability(const RedLinkInfo *link, uint32_t cap)
{
    const uint32_t *caps = red_link_info_get_caps(link);

    return test_capability(caps, link->link_mess->num_common_caps, cap);
}


SPICE_GNUC_VISIBLE int spice_server_set_channel_security(SpiceServer *s, const char *channel, int security)
{
    int type;
    if (channel == NULL) {
        s->config->default_channel_security = security;
        return 0;
    }
    type = red_channel_name_to_type(channel);
#ifndef USE_SMARTCARD
    if (type == SPICE_CHANNEL_SMARTCARD) {
        type = -1;
    }
#endif
    if (type == -1) {
        return -1;
    }

    reds_set_one_channel_security(s, type, security);
    return 0;
}


static int load_dh_params(SSL_CTX *ctx, char *file)
{
    DH *ret = 0;
    BIO *bio;

    if ((bio = BIO_new_file(file, "r")) == NULL) {
        spice_warning("Could not open DH file");
        red_dump_openssl_errors();
        return -1;
    }

    ret = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
    BIO_free(bio);
    if (ret == 0) {
        spice_warning("Could not read DH params");
        red_dump_openssl_errors();
        return -1;
    }

    if (SSL_CTX_set_tmp_dh(ctx, ret) < 0) {
        spice_warning("Could not set DH params");
        red_dump_openssl_errors();
        return -1;
    }

    return 0;
}


static void reds_add_char_device(RedsState *reds, const red::shared_ptr<RedCharDevice> &dev)
{
    reds->char_devices.push_front(dev);
}


static void reds_migrate_channels_seamless(RedsState *reds)
{
    RedClient *client;

    /* seamless migration is supported for only one client for now */
    client = reds_get_client(reds);
    client->migrate();
}


static void reds_mig_target_client_add(RedsState *reds, RedClient *client)
{
    RedsMigTargetClient *mig_client;

    g_return_if_fail(reds);
    spice_debug("trace");
    mig_client = g_new0(RedsMigTargetClient, 1);
    mig_client->client = client;
    reds->mig_target_clients = g_list_append(reds->mig_target_clients, mig_client);
}


static void reds_fill_channels(RedsState *reds, SpiceMsgChannels *channels_info)
{
    int used_channels = 0;

    for (const auto channel: reds->channels) {
        if (reds->clients.size() > 1 &&
            !channel_supports_multiple_clients(channel.get())) {
            continue;
        }
        channels_info->channels[used_channels].type = channel->type();
        channels_info->channels[used_channels].id = channel->id();
        used_channels++;
    }

    channels_info->num_of_channels = used_channels;
    if (used_channels != reds->channels.size()) {
        spice_warning("sent %d out of %zd", used_channels, reds->channels.size());
    }
}


static void reds_send_mm_time(RedsState *reds)
{
    if (!reds_main_channel_connected(reds)) {
        return;
    }
    spice_debug("trace");
    reds->main_channel->push_multi_media_time(reds_get_mm_time() - reds->mm_time_latency);
}


static void reds_mig_started(RedsState *reds)
{
    spice_debug("trace");
    spice_assert(reds->config->mig_spice);

    reds->mig_inprogress = TRUE;
    reds->mig_wait_connect = TRUE;
    red_timer_start(reds->mig_timer, MIGRATE_TIMEOUT);
}


RedChannel *reds_find_channel(RedsState *reds, uint32_t type, uint32_t id)
{
    for (auto channel: reds->channels) {
        if (channel->type() == type && channel->id() == id) {
            return channel.get();
        }
    }
    return NULL;
}


SPICE_GNUC_VISIBLE int spice_server_add_interface(SpiceServer *reds,
                                                  SpiceBaseInstance *sin)
{
    const SpiceBaseInterface *base_interface = sin->sif;

    if (strcmp(base_interface->type, SPICE_INTERFACE_KEYBOARD) == 0) {
        spice_debug("SPICE_INTERFACE_KEYBOARD");
        if (base_interface->major_version != SPICE_INTERFACE_KEYBOARD_MAJOR ||
            base_interface->minor_version > SPICE_INTERFACE_KEYBOARD_MINOR) {
            spice_warning("unsupported keyboard interface");
            return -1;
        }
        if (reds->inputs_channel->set_keyboard(SPICE_UPCAST(SpiceKbdInstance, sin)) != 0) {
            return -1;
        }
    } else if (strcmp(base_interface->type, SPICE_INTERFACE_MOUSE) == 0) {
        spice_debug("SPICE_INTERFACE_MOUSE");
        if (base_interface->major_version != SPICE_INTERFACE_MOUSE_MAJOR ||
            base_interface->minor_version > SPICE_INTERFACE_MOUSE_MINOR) {
            spice_warning("unsupported mouse interface");
            return -1;
        }
        if (reds->inputs_channel->set_mouse(SPICE_UPCAST(SpiceMouseInstance, sin)) != 0) {
            return -1;
        }
    } else if (strcmp(base_interface->type, SPICE_INTERFACE_QXL) == 0) {
        QXLInstance *qxl;

        spice_debug("SPICE_INTERFACE_QXL");
        if (base_interface->major_version != SPICE_INTERFACE_QXL_MAJOR ||
            base_interface->minor_version > SPICE_INTERFACE_QXL_MINOR) {
            spice_warning("unsupported qxl interface");
            return -1;
        }

        qxl = SPICE_UPCAST(QXLInstance, sin);
        if (qxl->id < 0) {
            spice_warning("invalid QXL ID");
            return -1;
        }
        if (reds_find_channel(reds, SPICE_CHANNEL_DISPLAY, qxl->id)) {
            spice_warning("QXL ID already allocated");
            return -1;
        }
        red_qxl_init(reds, qxl);
        reds->qxl_instances.push_front(qxl);

        /* this function has to be called after the qxl is on the list
         * as QXLInstance clients expect the qxl to be on the list when
         * this callback is called. This as clients assume they can start the
         * qxl_instances. Also note that this should be the first callback to
         * be called. */
        red_qxl_attach_worker(qxl);
        red_qxl_set_compression_level(qxl, calc_compression_level(reds));
    } else if (strcmp(base_interface->type, SPICE_INTERFACE_TABLET) == 0) {
        SpiceTabletInstance *tablet = SPICE_UPCAST(SpiceTabletInstance, sin);
        spice_debug("SPICE_INTERFACE_TABLET");
        if (base_interface->major_version != SPICE_INTERFACE_TABLET_MAJOR ||
            base_interface->minor_version > SPICE_INTERFACE_TABLET_MINOR) {
            spice_warning("unsupported tablet interface");
            return -1;
        }
        if (reds->inputs_channel->set_tablet(tablet) != 0) {
            return -1;
        }
        reds_update_mouse_mode(reds);
        if (reds->is_client_mouse_allowed) {
            reds->inputs_channel->set_tablet_logical_size(reds->monitor_mode.x_res, reds->monitor_mode.y_res);
        }

    } else if (strcmp(base_interface->type, SPICE_INTERFACE_PLAYBACK) == 0) {
        spice_debug("SPICE_INTERFACE_PLAYBACK");
        if (base_interface->major_version != SPICE_INTERFACE_PLAYBACK_MAJOR ||
            base_interface->minor_version > SPICE_INTERFACE_PLAYBACK_MINOR) {
            spice_warning("unsupported playback interface");
            return -1;
        }
        snd_attach_playback(reds, SPICE_UPCAST(SpicePlaybackInstance, sin));

    } else if (strcmp(base_interface->type, SPICE_INTERFACE_RECORD) == 0) {
        spice_debug("SPICE_INTERFACE_RECORD");
        if (base_interface->major_version != SPICE_INTERFACE_RECORD_MAJOR ||
            base_interface->minor_version > SPICE_INTERFACE_RECORD_MINOR) {
            spice_warning("unsupported record interface");
            return -1;
        }
        snd_attach_record(reds, SPICE_UPCAST(SpiceRecordInstance, sin));

    } else if (strcmp(base_interface->type, SPICE_INTERFACE_CHAR_DEVICE) == 0) {
        if (base_interface->major_version != SPICE_INTERFACE_CHAR_DEVICE_MAJOR ||
            base_interface->minor_version > SPICE_INTERFACE_CHAR_DEVICE_MINOR) {
            spice_warning("unsupported char device interface");
            return -1;
        }
        spice_server_char_device_add_interface(reds, sin);

    } else if (strcmp(base_interface->type, SPICE_INTERFACE_MIGRATION) == 0) {
        spice_debug("SPICE_INTERFACE_MIGRATION");
        if (reds->migration_interface) {
            spice_warning("already have migration");
            return -1;
        }

        if (base_interface->major_version != SPICE_INTERFACE_MIGRATION_MAJOR ||
            base_interface->minor_version > SPICE_INTERFACE_MIGRATION_MINOR) {
            spice_warning("unsupported migration interface");
            return -1;
        }
        reds->migration_interface = SPICE_UPCAST(SpiceMigrateInstance, sin);
        reds->migration_interface->st = (SpiceMigrateState *)(intptr_t)1; // dummy pointer
    }

    return 0;
}


static void reds_set_video_codecs(RedsState *reds, GArray *video_codecs)
{
    /* The video_codecs array is immutable */
    g_clear_pointer(&reds->config->video_codecs, g_array_unref);

    spice_return_if_fail(video_codecs != NULL);

    reds->config->video_codecs = video_codecs;
}


static void reds_on_main_agent_monitors_config(RedsState *reds,
        MainChannelClient *mcc, const void *message, size_t size)
{
    const unsigned int MAX_NUM_MONITORS = 256;
    const unsigned int MAX_MONITOR_CONFIG_SIZE =
        sizeof(VDAgentMonitorsConfig) +
        MAX_NUM_MONITORS * (sizeof(VDAgentMonConfig) + sizeof(VDAgentMonitorMM));

    VDAgentMessage *msg_header;
    VDAgentMonitorsConfig *monitors_config;
    SpiceBuffer *cmc = &reds->client_monitors_config;
    uint32_t msg_size;

    // limit size of message sent by the client as this can cause a DoS through
    // memory exhaustion, or potentially some integer overflows
    if (sizeof(VDAgentMessage) + MAX_MONITOR_CONFIG_SIZE - cmc->offset < size) {
        goto overflow;
    }
    spice_buffer_append(cmc, message, size);
    if (sizeof(VDAgentMessage) > cmc->offset) {
        spice_debug("not enough data yet. %" G_GSSIZE_FORMAT, cmc->offset);
        return;
    }
    msg_header = (VDAgentMessage *)cmc->buffer;
    msg_size = GUINT32_FROM_LE(msg_header->size);
    if (msg_size > MAX_MONITOR_CONFIG_SIZE) {
        goto overflow;
    }
    if (msg_size > cmc->offset - sizeof(VDAgentMessage)) {
        spice_debug("not enough data yet. %" G_GSSIZE_FORMAT, cmc->offset);
        return;
    }

    // convert VDAgentMessage endianness
    msg_header->protocol = GUINT32_FROM_LE(msg_header->protocol);
    msg_header->type = GUINT32_FROM_LE(msg_header->type);
    msg_header->opaque = GUINT64_FROM_LE(msg_header->opaque);
    msg_header->size = GUINT32_FROM_LE(msg_header->size);

    monitors_config = (VDAgentMonitorsConfig *)(cmc->buffer + sizeof(*msg_header));
    if (agent_check_message(msg_header, (uint8_t *) monitors_config,
                            NULL, 0) != AGENT_CHECK_NO_ERROR) {
        goto overflow;
    }
    spice_debug("monitors_config->num_of_monitors: %d", monitors_config->num_of_monitors);
    reds_client_monitors_config(reds, monitors_config);
    spice_buffer_free(cmc);
    return;

overflow:
    spice_warning("received invalid MonitorsConfig request from client, disconnecting");
    mcc->disconnect();
    spice_buffer_free(cmc);
}


static gboolean reds_add_renderer(RedsState *reds, const char *name)
{
    uint32_t index;

    if (reds->config->renderers->len == RED_RENDERER_LAST ||
        !get_name_index(renderer_names, name, &index)) {
        return FALSE;
    }
    g_array_append_val(reds->config->renderers, renderer_names[index].id);
    return TRUE;
}


static const char *get_index_name(const EnumNames names[], uint32_t index)
{
    while (names->name != NULL && names->id != index) {
        names++;
    }

    return names->name;
}


RedVDIReadBuf::~RedVDIReadBuf()
{
    dev->priv->num_read_buf--;

    /* read_one_msg_from_vdi_port may have never completed because we
       reached buffer limit. So we call it again so it can complete its work if
       necessary. Note that since we can be called from red_char_device_wakeup
       this can cause recursion, but we have protection for that */
    if (dev->priv->agent_attached) {
       dev->wakeup();
    }
}


static void reds_reset_vdp(RedsState *reds)
{
    RedCharDeviceVDIPort *dev = reds->agent_dev.get();
    SpiceCharDeviceInterface *sif;

    dev->priv->read_state = VDI_PORT_READ_STATE_READ_HEADER;
    dev->priv->receive_pos = (uint8_t *)&dev->priv->vdi_chunk_header;
    dev->priv->receive_len = sizeof(dev->priv->vdi_chunk_header);
    dev->priv->message_receive_len = 0;
    dev->priv->current_read_buf.reset();

    /* Reset read filter to start with clean state when the agent reconnects */
    agent_msg_filter_init(&dev->priv->read_filter, reds->config->agent_copypaste,
                          reds->config->agent_file_xfer,
                          reds_use_client_monitors_config(reds), TRUE);
    /* Throw away pending chunks from the current (if any) and future
     * messages written by the client.
     * TODO: client should clear its agent messages queue when the agent
     * is disconnected. Currently, when an agent gets disconnected and reconnected,
     * messages that were directed to the previous instance of the agent continue
     * to be sent from the client. This TODO will require server, protocol, and client changes */
    dev->priv->write_filter.result = AGENT_MSG_FILTER_DISCARD;
    dev->priv->write_filter.discard_all = TRUE;
    dev->priv->client_agent_started = false;
    dev->priv->agent_supports_graphics_device_info = false;

    /*  The client's tokens are set once when the main channel is initialized
     *  and once upon agent's connection with SPICE_MSG_MAIN_AGENT_CONNECTED_TOKENS.
     *  The client tokens are tracked as part of the RedCharDeviceClient. Thus,
     *  in order to be backward compatible with the client, we need to track the tokens
     *  even if the agent is detached. We don't destroy the char_device, and
     *  instead we just reset it.
     *  The tokens are also reset to avoid mismatch in upon agent reconnection.
     */
    dev->priv->agent_attached = FALSE;
    dev->stop();
    dev->reset();
    dev->reset_dev_instance(NULL);

    sif = spice_char_device_get_interface(reds->vdagent);
    if (sif->state) {
        sif->state(reds->vdagent, 0);
    }
}


void RedCharDeviceVDIPort::send_msg_to_client(RedPipeItem *msg, RedCharDeviceClientOpaque *opaque)
{
    RedClient *client = (RedClient *) opaque;
    RedVDIReadBuf *agent_data_buf = static_cast<RedVDIReadBuf*>(msg);

    client->get_main()->push_agent_data(red::shared_ptr<RedAgentDataPipeItem>(agent_data_buf));
}


static void pthreads_thread_id(CRYPTO_THREADID *tid)
{
    CRYPTO_THREADID_set_numeric(tid, (unsigned long)pthread_self());
}


SPICE_GNUC_VISIBLE void spice_server_set_addr(SpiceServer *reds, const char *addr, int flags)
{
    g_strlcpy(reds->config->spice_addr, addr, sizeof(reds->config->spice_addr));

    if (flags == SPICE_ADDR_FLAG_IPV4_ONLY) {
        reds->config->spice_family = PF_INET;
    } else if (flags == SPICE_ADDR_FLAG_IPV6_ONLY) {
        reds->config->spice_family = PF_INET6;
    } else if (flags == SPICE_ADDR_FLAG_UNIX_ONLY) {
        reds->config->spice_family = AF_UNIX;
    } else if (flags != 0) {
        spice_warning("unknown address flag: 0x%X", flags);
    }
}


SPICE_GNUC_VISIBLE int spice_server_set_playback_compression(SpiceServer *reds, int enable)
{
    reds->config->playback_compression = !!enable;
    snd_set_playback_compression(enable);
    return 0;
}


void reds_on_vm_stop(RedsState *reds)
{
    FOREACH_QXL_INSTANCE(reds, qxl) {
        red_qxl_stop(qxl);
    }
}


SpiceWatch *reds_core_watch_add(RedsState *reds,
                                int fd, int event_mask,
                                SpiceWatchFunc func,
                                void *opaque)
{
   g_return_val_if_fail(reds != NULL, NULL);
   g_return_val_if_fail(reds->core.watch_add != NULL, NULL);

   return reds->core.watch_add(&reds->core, fd, event_mask, func, opaque);
}


static red::shared_ptr<RedVDIReadBuf> vdi_read_buf_new(RedCharDeviceVDIPort *dev)
{
    auto buf = red::make_shared<RedVDIReadBuf>();
    buf->dev = dev;
    return buf;
}


SPICE_GNUC_VISIBLE int spice_server_set_compat_version(SpiceServer *reds,
                                                       spice_compat_version_t version)
{
    if (version < SPICE_COMPAT_VERSION_0_6) {
        /* We don't support 0.4 compat mode atm */
        return -1;
    }

    if (version > SPICE_COMPAT_VERSION_CURRENT) {
        /* Not compatible with future versions */
        return -1;
    }
    return 0;
}


spice_wan_compression_t reds_get_zlib_glz_state(const RedsState *reds)
{
    return reds->config->zlib_glz_state;
}


static int do_spice_init(RedsState *reds, SpiceCoreInterface *core_interface)
{
    spice_debug("starting %s", VERSION);

    if (core_interface->base.major_version != SPICE_INTERFACE_CORE_MAJOR) {
        spice_warning("bad core interface version");
        goto err;
    }
    reds->core = core_interface_adapter;
    reds->core.public_interface = core_interface;
    reds->agent_dev = red::make_shared<RedCharDeviceVDIPort>(reds);
    reds_update_agent_properties(reds);
    reds->main_dispatcher = red::make_shared<MainDispatcher>(reds);
    reds->mig_target_clients = NULL;
    reds->vm_running = TRUE; /* for backward compatibility */

    if (!(reds->mig_timer = reds->core.timer_new(migrate_timeout, reds))) {
        spice_error("migration timer create failed");
    }
    /* Note that this will not actually send the mm_time to the client because
     * the main channel is not connected yet. This would have been redundant
     * with the RED_PIPE_ITEM_TYPE_MAIN_INIT message anyway.
     */
    reds_enable_mm_time(reds);

    if (reds_init_net(reds) < 0) {
        spice_warning("Failed to open SPICE sockets");
        goto err;
    }
    if (reds->secure_listen_socket != -1) {
        if (reds_init_ssl(reds) < 0) {
            goto err;
        }
    }
#if HAVE_SASL
    int saslerr;
    if ((saslerr = sasl_server_init(NULL, reds->config->sasl_appname ?
                                    reds->config->sasl_appname : "spice")) != SASL_OK) {
        spice_error("Failed to initialize SASL auth %s",
                  sasl_errstring(saslerr, NULL, NULL));
        goto err;
    }
#endif

    reds->main_channel = main_channel_new(reds);
    reds->inputs_channel = inputs_channel_new(reds);

    reds->mouse_mode = SPICE_MOUSE_MODE_SERVER;

    spice_buffer_free(&reds->client_monitors_config);

    reds->allow_multiple_clients = getenv(SPICE_DEBUG_ALLOW_MC_ENV) != NULL;
    if (reds->allow_multiple_clients) {
        spice_warning("spice: allowing multiple client connections");
    }
    pthread_mutex_lock(&global_reds_lock);
    servers = g_list_prepend(servers, reds);
    pthread_mutex_unlock(&global_reds_lock);
    return 0;

err:
    reds_cleanup_net(reds);
    return -1;
}


SPICE_GNUC_VISIBLE int spice_server_set_agent_mouse(SpiceServer *reds, int enable)
{
    reds->config->agent_mouse = enable;
    reds_update_mouse_mode(reds);
    return 0;
}


RedCharDeviceVDIPort::~RedCharDeviceVDIPort()
{
    /* make sure we have no other references to RedVDIReadBuf buffers */
    reset();
    priv->current_read_buf.reset(); // needed to pass the assert below
    g_free(priv->mig_data);
    spice_extra_assert(priv->num_read_buf == 0);
}


static void reds_mig_target_client_add_pending_link(RedsMigTargetClient *client,
                                                    SpiceLinkMess *link_msg,
                                                    RedStream *stream)
{
    RedsMigPendingLink *mig_link;

    spice_assert(client);
    mig_link = g_new0(RedsMigPendingLink, 1);
    mig_link->link_msg = link_msg;
    mig_link->stream = stream;

    client->pending_links = g_list_append(client->pending_links, mig_link);
}


static void reds_handle_read_link_done(void *opaque)
{
    RedLinkInfo *link = (RedLinkInfo *)opaque;
    RedsState *reds = link->reds;
    SpiceLinkMess *link_mess = link->link_mess;
    uint32_t num_caps;
    uint32_t *caps;
    int auth_selection;
    unsigned int i;

    link_mess->caps_offset = GUINT32_FROM_LE(link_mess->caps_offset);
    link_mess->connection_id = GUINT32_FROM_LE(link_mess->connection_id);
    link_mess->num_channel_caps = GUINT32_FROM_LE(link_mess->num_channel_caps);
    link_mess->num_common_caps = GUINT32_FROM_LE(link_mess->num_common_caps);

    /* Prevent DoS. Currently we defined only 13 capabilities,
     * I expect 1024 to be valid for quite a lot time */
    if (link_mess->num_channel_caps > 1024 || link_mess->num_common_caps > 1024) {
        reds_send_link_error(link, SPICE_LINK_ERR_INVALID_DATA);
        reds_link_free(link);
        return;
    }

    num_caps = link_mess->num_common_caps + link_mess->num_channel_caps;
    caps = (uint32_t *)((uint8_t *)link_mess + link_mess->caps_offset);

    if (num_caps && (num_caps * sizeof(uint32_t) + link_mess->caps_offset >
                     link->link_header.size ||
                     link_mess->caps_offset < sizeof(*link_mess))) {
        reds_send_link_error(link, SPICE_LINK_ERR_INVALID_DATA);
        reds_link_free(link);
        return;
    }

    for(i = 0; i < num_caps;i++)
        caps[i] = GUINT32_FROM_LE(caps[i]);

    auth_selection = red_link_info_test_capability(link,
                                                   SPICE_COMMON_CAP_PROTOCOL_AUTH_SELECTION);

    if (!reds_security_check(link)) {
        if (red_stream_is_ssl(link->stream)) {
            spice_warning("spice channels %d should not be encrypted", link_mess->channel_type);
            reds_send_link_error(link, SPICE_LINK_ERR_NEED_UNSECURED);
        } else {
            spice_warning("spice channels %d should be encrypted", link_mess->channel_type);
            reds_send_link_error(link, SPICE_LINK_ERR_NEED_SECURED);
        }
        reds_link_free(link);
        return;
    }

    if (!reds_send_link_ack(reds, link)) {
        reds_link_free(link);
        return;
    }

    if (!auth_selection) {
        if (reds->config->sasl_enabled && !link->skip_auth) {
            spice_warning("SASL enabled, but peer supports only spice authentication");
            reds_send_link_error(link, SPICE_LINK_ERR_VERSION_MISMATCH);
            return;
        }
        spice_warning("Peer doesn't support AUTH selection");
        reds_get_spice_ticket(link);
    } else {
        red_stream_async_read(link->stream,
                              (uint8_t *)&link->auth_mechanism,
                              sizeof(SpiceLinkAuthMechanism),
                              reds_handle_auth_mechanism,
                              link);
    }
}


void RedCharDeviceVDIPort::send_tokens_to_client(RedCharDeviceClientOpaque *opaque, uint32_t tokens)
{
    RedClient *client = (RedClient *) opaque;
    client->get_main()->push_agent_tokens(tokens);
}


void reds_send_device_display_info(RedsState *reds)
{
    if (!reds->agent_dev->priv->agent_attached) {
        return;
    }
    if (!reds->agent_dev->priv->agent_supports_graphics_device_info) {
        return;
    }

    g_debug("Sending device display info to the agent:");

    SpiceMarshaller *m = spice_marshaller_new();
    reds_marshall_device_display_info(reds, m);

    RedCharDeviceWriteBuffer *char_dev_buf = vdagent_new_write_buffer(reds->agent_dev.get(),
                                         VD_AGENT_GRAPHICS_DEVICE_INFO,
                                         spice_marshaller_get_total_size(m),
                                         true);

    if (!char_dev_buf) {
        spice_marshaller_destroy(m);
        reds->pending_device_display_info_message = true;
        return;
    }

    VDInternalBuf *internal_buf = (VDInternalBuf *)char_dev_buf->buf;

    int free_info;
    size_t len_info;
    uint8_t *info = spice_marshaller_linearize(m, 0, &len_info, &free_info);
    memcpy(&internal_buf->u.graphics_device_info, info, len_info);
    if (free_info) {
        free(info);
    }
    spice_marshaller_destroy(m);

    reds->pending_device_display_info_message = false;

    reds->agent_dev->write_buffer_add(char_dev_buf);
}


void reds_register_channel(RedsState *reds, RedChannel *channel)
{
    spice_assert(reds);

    uint32_t this_type = channel->type();
    uint32_t this_id = channel->id();
    if (spice_extra_checks) {
        g_assert(reds_find_channel(reds, this_type, this_id) == NULL);
    } else {
        g_warn_if_fail(reds_find_channel(reds, this_type, this_id) == NULL);
    }
    reds->channels.push_front(red::shared_ptr<RedChannel>(channel));
    // create new channel in the client if possible
    reds->main_channel->registered_new_channel(channel);
}


SPICE_GNUC_VISIBLE int spice_server_migrate_switch(SpiceServer *reds)
{
    spice_debug("trace");
    if (reds->clients.empty()) {
       return 0;
    }
    reds->expect_migrate = FALSE;
    if (!reds->config->mig_spice) {
        spice_warning("spice_server_migrate_switch called without migrate_info set");
        return 0;
    }
    reds->main_channel->migrate_switch(reds->config->mig_spice);
    reds_mig_release(reds->config);
    return 0;
}


SPICE_GNUC_VISIBLE int spice_server_set_jpeg_compression(SpiceServer *s, spice_wan_compression_t comp)
{
    if (comp == SPICE_WAN_COMPRESSION_INVALID) {
        spice_error("invalid jpeg state");
        return -1;
    }
    // todo: support dynamically changing the state
    s->config->jpeg_state = comp;
    return 0;
}


static void reds_cleanup_net(SpiceServer *reds)
{
    if (reds->listen_socket != -1) {
       red_watch_remove(reds->listen_watch);
       if (reds->config->spice_listen_socket_fd != reds->listen_socket) {
          socket_close(reds->listen_socket);
       }
       reds->listen_watch = NULL;
       reds->listen_socket = -1;
    }
    if (reds->secure_listen_socket != -1) {
       red_watch_remove(reds->secure_listen_watch);
       socket_close(reds->secure_listen_socket);
       reds->secure_listen_watch = NULL;
       reds->secure_listen_socket = -1;
    }
}


static int calc_compression_level(RedsState *reds)
{
    spice_assert(reds_get_streaming_video(reds) != SPICE_STREAM_VIDEO_INVALID);

    if ((reds_get_streaming_video(reds) != SPICE_STREAM_VIDEO_OFF) ||
        (spice_server_get_image_compression(reds) != SPICE_IMAGE_COMPRESSION_QUIC)) {
        return 0;
    } else {
        return 1;
    }
}


void reds_on_vc_change(RedsState *reds)
{
    FOREACH_QXL_INSTANCE(reds, qxl) {
        red_qxl_on_vc_change(qxl, reds_get_video_codecs(reds));
    }
}


static void reds_cleanup(RedsState *reds)
{
#ifdef RED_STATISTICS
    stat_file_unlink(reds->stat_file);
#endif
}


SPICE_GNUC_VISIBLE SpiceServer *spice_server_new(void)
{
    const char *record_filename;
    RedsState *reds = new RedsState;

    reds->config = g_new0(RedServerConfig, 1);
    reds->config->default_channel_security =
        SPICE_CHANNEL_SECURITY_NONE | SPICE_CHANNEL_SECURITY_SSL;
    reds->config->renderers = g_array_sized_new(FALSE, TRUE, sizeof(uint32_t), RED_RENDERER_LAST);
    reds->config->spice_port = -1;
    reds->config->spice_secure_port = -1;
    reds->config->spice_listen_socket_fd = -1;
    reds->config->spice_family = PF_UNSPEC;
    reds->config->sasl_enabled = 0; // sasl disabled by default
#if HAVE_SASL
    reds->config->sasl_appname = NULL; // default to "spice" if NULL
#endif
    reds->config->spice_uuid_is_set = FALSE;
    memset(reds->config->spice_uuid, 0, sizeof(reds->config->spice_uuid));
    reds->config->ticketing_enabled = TRUE; /* ticketing enabled by default */
    reds->config->streaming_video = SPICE_STREAM_VIDEO_FILTER;
    reds->config->video_codecs = g_array_new(FALSE, FALSE, sizeof(RedVideoCodec));
    reds->config->image_compression = SPICE_IMAGE_COMPRESSION_AUTO_GLZ;
    reds->config->playback_compression = TRUE;
    reds->config->jpeg_state = SPICE_WAN_COMPRESSION_AUTO;
    reds->config->zlib_glz_state = SPICE_WAN_COMPRESSION_AUTO;
    reds->config->agent_mouse = TRUE;
    reds->config->agent_copypaste = TRUE;
    reds->config->agent_file_xfer = TRUE;
    reds->config->exit_on_disconnect = FALSE;
#ifdef RED_STATISTICS
    reds->stat_file = stat_file_new(REDS_MAX_STAT_NODES);
    /* Create an initial node. This will be the 0 node making easier
     * to initialize node references.
     */
    stat_file_add_node(reds->stat_file, INVALID_STAT_REF, "default_channel", TRUE);
#endif
    reds->listen_socket = -1;
    reds->secure_listen_socket = -1;

    /* This environment was in red-worker so the "WORKER" in it.
     * For compatibility reason we maintain the old name */
    record_filename = getenv("SPICE_WORKER_RECORD_FILENAME");
    if (record_filename) {
        reds->record = red_record_new(record_filename);
    }
    return reds;
}


SPICE_GNUC_VISIBLE int spice_server_set_sasl(SpiceServer *s, int enabled)
{
#if HAVE_SASL
    s->config->sasl_enabled = enabled;
    return 0;
#else
    return -1;
#endif
}


attach_to_red_agent(RedsState *reds, SpiceCharDeviceInstance *sin)
{
    RedCharDeviceVDIPort *dev = reds->agent_dev.get();
    SpiceCharDeviceInterface *sif;

    dev->priv->agent_attached = true;
    dev->reset_dev_instance(sin);

    reds->vdagent = sin;
    reds_update_mouse_mode(reds);

    sif = spice_char_device_get_interface(sin);
    if (sif->state) {
        sif->state(sin, 1);
    }

    if (!reds_main_channel_connected(reds)) {
        return reds->agent_dev;
    }

    dev->priv->read_filter.discard_all = FALSE;
    dev->priv->plug_generation++;

    if (dev->priv->mig_data ||
        reds->main_channel->is_waiting_for_migrate_data()) {
        /* Migration in progress (code is running on the destination host):
         * 1.  Add the client to spice char device, if it was not already added.
         * 2.a If this (qemu-kvm state load side of migration) happens first
         *     then wait for spice migration data to arrive. Otherwise
         * 2.b If this happens second ==> we already have spice migrate data
         *     then restore state
         */
        RedCharDeviceClientOpaque *client_opaque =
            (RedCharDeviceClientOpaque *) reds_get_client(reds);
        if (!dev->client_exists(client_opaque)) {
            int client_added;

            client_added = dev->client_add(client_opaque, TRUE,
                                           REDS_VDI_PORT_NUM_RECEIVE_BUFFS,
                                           REDS_AGENT_WINDOW_SIZE, ~0, TRUE);

            if (!client_added) {
                spice_warning("failed to add client to agent");
                reds_disconnect(reds);
            }
        }

        if (dev->priv->mig_data) {
            spice_debug("restoring dev from stored migration data");
            spice_assert(dev->priv->plug_generation == 1);
            reds_agent_state_restore(reds, dev->priv->mig_data);
            g_free(dev->priv->mig_data);
            dev->priv->mig_data = NULL;
        }
        else {
            spice_debug("waiting for migration data");
        }
    } else {
        /* we will associate the client with the char device, upon reds_on_main_agent_start,
         * in response to MSGC_AGENT_START */
        reds->main_channel->push_agent_connected();
    }

    return reds->agent_dev;
}


static void reds_mig_cleanup_wait_disconnect(RedsState *reds)
{
    reds->mig_wait_disconnect_clients.clear();
    reds->mig_wait_disconnect = FALSE;
}


void reds_on_sv_change(RedsState *reds)
{
    int compression_level = calc_compression_level(reds);

    FOREACH_QXL_INSTANCE(reds, qxl) {
        red_qxl_set_compression_level(qxl, compression_level);
        red_qxl_on_sv_change(qxl, reds_get_streaming_video(reds));
    }
}


static int reds_set_migration_dest_info(RedsState *reds,
                                        const char* dest,
                                        int port, int secure_port,
                                        const char* cert_subject)
{
    RedsMigSpice *spice_migration = NULL;

    reds_mig_release(reds->config);
    if ((port == -1 && secure_port == -1) || !dest) {
        return FALSE;
    }

    spice_migration = g_new0(RedsMigSpice, 1);
    spice_migration->port = port;
    spice_migration->sport = secure_port;
    spice_migration->host = g_strdup(dest);
    if (cert_subject) {
        spice_migration->cert_subject = g_strdup(cert_subject);
    }

    reds->config->mig_spice = spice_migration;

    return TRUE;
}


static void reds_handle_other_links(RedsState *reds, RedLinkInfo *link)
{
    RedChannel *channel;
    RedClient *client = NULL;
    SpiceLinkMess *link_mess;
    RedsMigTargetClient *mig_client;

    link_mess = link->link_mess;
    if (reds->main_channel) {
        client = reds->main_channel->get_client_by_link_id(link_mess->connection_id);
    }

    // TODO: MC: broke migration (at least for the dont-drop-connection kind).
    // On migration we should get a connection_id to expect (must be a security measure)
    // where do we store it? on reds, but should be a list (MC).
    if (!client) {
        reds_send_link_result(link, SPICE_LINK_ERR_BAD_CONNECTION_ID);
        return;
    }

    // TODO: MC: be less lenient. Tally connections from same connection_id (by same client).
    if (!(channel = reds_find_channel(reds, link_mess->channel_type,
                                      link_mess->channel_id))) {
        reds_send_link_result(link, SPICE_LINK_ERR_CHANNEL_NOT_AVAILABLE);
        return;
    }

    reds_send_link_result(link, SPICE_LINK_ERR_OK);
    reds_info_new_channel(link, link_mess->connection_id);

    mig_client = reds_mig_target_client_find(reds, client);
    /*
     * In semi-seamless migration, we activate the channels only
     * after migration is completed. Since, the session starts almost from
     * scratch we don't mind if we skip some messages in between the src session end and
     * dst session start.
     * In seamless migration, in order to keep the continuousness of the session, and
     * in order not to lose any data, we activate the target channels before
     * migration completes, as soon as we receive SPICE_MSGC_MAIN_MIGRATE_DST_DO_SEAMLESS.
     * If a channel connects before receiving SPICE_MSGC_MAIN_MIGRATE_DST_DO_SEAMLESS,
     * reds_on_migrate_dst_set_seamless will take care of activating it */
    if (client->during_migrate_at_target() && !reds->dst_do_seamless_migrate) {
        spice_assert(mig_client);
        reds_mig_target_client_add_pending_link(mig_client, link_mess, link->stream);
        link->link_mess = NULL;
    } else {
        spice_assert(!mig_client);
        reds_channel_do_link(channel, client, link_mess, link->stream);
    }
    link->stream = NULL;
}


SPICE_GNUC_VISIBLE int spice_server_migrate_info(SpiceServer *reds, const char* dest,
                                          int port, int secure_port,
                                          const char* cert_subject)
{
    spice_debug("trace");
    spice_assert(!reds->migration_interface);

    if (!reds_set_migration_dest_info(reds, dest, port, secure_port, cert_subject)) {
        return -1;
    }
    return 0;
}


bool reds_config_get_playback_compression(RedsState *reds)
{
    return reds->config->playback_compression;
}


void reds_on_ic_change(RedsState *reds)
{
    int compression_level = calc_compression_level(reds);

    FOREACH_QXL_INSTANCE(reds, qxl) {
        red_qxl_set_compression_level(qxl, compression_level);
        red_qxl_on_ic_change(qxl, spice_server_get_image_compression(reds));
    }
}


static void reds_handle_sasl_result(void *opaque, RedSaslError status)
{
    RedLinkInfo *link = (RedLinkInfo *)opaque;

    switch (status) {
    case RED_SASL_ERROR_OK:
        reds_handle_link(link);
        break;
    case RED_SASL_ERROR_INVALID_DATA:
        reds_send_link_error(link, SPICE_LINK_ERR_INVALID_DATA);
        reds_link_free(link);
        break;
    default:
        // in these cases error was reported using SASL protocol
        // (RED_SASL_ERROR_AUTH_FAILED) or we just need to close the
        // connection
        reds_link_free(link);
        break;
    }
}


static gpointer openssl_global_init_once(gpointer arg)
{
    SSL_library_init();
    SSL_load_error_strings();

    openssl_thread_setup();

    return NULL;
}


static bool channel_supports_multiple_clients(const RedChannel *channel)
{
    switch (channel->type()) {
    case SPICE_CHANNEL_MAIN:
    case SPICE_CHANNEL_DISPLAY:
    case SPICE_CHANNEL_CURSOR:
    case SPICE_CHANNEL_INPUTS:
        return TRUE;
    }
    return FALSE;
}


SPICE_GNUC_VISIBLE void spice_server_set_uuid(SpiceServer *s, const uint8_t uuid[16])
{
    memcpy(s->config->spice_uuid, uuid, sizeof(s->config->spice_uuid));
    s->config->spice_uuid_is_set = TRUE;
}


void reds_on_main_mouse_mode_request(RedsState *reds, void *message, size_t size)
{
    switch (((SpiceMsgcMainMouseModeRequest *)message)->mode) {
    case SPICE_MOUSE_MODE_CLIENT:
        if (reds->is_client_mouse_allowed) {
            reds_set_mouse_mode(reds, SPICE_MOUSE_MODE_CLIENT);
        } else {
            spice_debug("client mouse is disabled");
        }
        break;
    case SPICE_MOUSE_MODE_SERVER:
        reds_set_mouse_mode(reds, SPICE_MOUSE_MODE_SERVER);
        break;
    default:
        spice_warning("unsupported mouse mode");
    }
}


static int ssl_password_cb(char *buf, int size, int flags, void *userdata)
{
    RedsState *reds = (RedsState*) userdata;
    char *pass = reds->config->ssl_parameters.keyfile_password;
    int len = g_strlcpy(buf, pass, size);
    if (len >= size) {
        return 0;
    }

    return len;
}


void reds_marshall_device_display_info(RedsState *reds, SpiceMarshaller *m)
{
    uint32_t device_count = 0;
    void *device_count_ptr = spice_marshaller_add_uint32(m, device_count);

    // add the qxl devices to the message
    FOREACH_QXL_INSTANCE(reds, qxl) {
        device_count += red_qxl_marshall_device_display_info(qxl, m);
    }

    // add the stream devices to the message
    for (auto dev: reds->char_devices) {
        auto stream_dev = dynamic_cast<StreamDevice*>(dev.get());
        if (stream_dev) {
            const StreamDeviceDisplayInfo *info = stream_dev->get_device_display_info();
            size_t device_address_len = strlen(info->device_address) + 1;

            if (device_address_len == 1) {
                // the device info wasn't set (yet), don't send it
                continue;
            }

            int32_t channel_id = stream_dev->get_stream_channel_id();
            if (channel_id == -1) {
                g_warning("DeviceDisplayInfo set but no stream channel exists");
                continue;
            }

            spice_marshaller_add_uint32(m, channel_id);
            spice_marshaller_add_uint32(m, info->stream_id);
            spice_marshaller_add_uint32(m, info->device_display_id);
            spice_marshaller_add_uint32(m, device_address_len);
            spice_marshaller_add(m, (const uint8_t*) (void*) info->device_address, device_address_len);
            ++device_count;

            g_debug("   (stream) channel_id: %u monitor_id: %u, device_address: %s, "
                    "device_display_id: %u",
                    channel_id, info->stream_id, info->device_address,
                    info->device_display_id);
        }
    }
    spice_marshaller_set_uint32(m, device_count_ptr, device_count);
}


static void reds_get_spice_ticket(RedLinkInfo *link)
{
    red_stream_async_read(link->stream,
                          (uint8_t *)&link->tiTicketing.encrypted_ticket.encrypted_data,
                          link->tiTicketing.rsa_size, reds_handle_ticket, link);
}


static void reds_adjust_agent_capabilities(RedsState *reds, VDAgentMessage *message)
{
    VDAgentAnnounceCapabilities *capabilities;

    if (message->type != VD_AGENT_ANNOUNCE_CAPABILITIES) {
        return;
    }
    capabilities = (VDAgentAnnounceCapabilities *) message->data;

    if (!reds->config->agent_copypaste) {
        VD_AGENT_CLEAR_CAPABILITY(capabilities->caps, VD_AGENT_CAP_CLIPBOARD);
        VD_AGENT_CLEAR_CAPABILITY(capabilities->caps, VD_AGENT_CAP_CLIPBOARD_BY_DEMAND);
        VD_AGENT_CLEAR_CAPABILITY(capabilities->caps, VD_AGENT_CAP_CLIPBOARD_SELECTION);
    }

    if (!reds->config->agent_file_xfer) {
        VD_AGENT_SET_CAPABILITY(capabilities->caps, VD_AGENT_CAP_FILE_XFER_DISABLED);
    }

    size_t caps_size = VD_AGENT_CAPS_SIZE_FROM_MSG_SIZE(message->size);
    reds->agent_dev->priv->agent_supports_graphics_device_info =
        VD_AGENT_HAS_CAPABILITY(capabilities->caps, caps_size, VD_AGENT_CAP_GRAPHICS_DEVICE_INFO);
    reds_send_device_display_info(reds);
}


static bool reds_link_mig_target_channels(RedsState *reds, RedClient *client)
{
    RedsMigTargetClient *mig_client;
    GList *item;

    spice_debug("%p", client);
    mig_client = reds_mig_target_client_find(reds, client);
    if (!mig_client) {
        spice_debug("Error: mig target client was not found");
        return FALSE;
    }

    /* Each channel should check if we are during migration, and
     * act accordingly. */
    for(item = mig_client->pending_links; item != NULL; item = item->next) {
        RedsMigPendingLink *mig_link = (RedsMigPendingLink*) item->data;
        RedChannel *channel;

        channel = reds_find_channel(reds, mig_link->link_msg->channel_type,
                                    mig_link->link_msg->channel_id);
        if (!channel) {
            spice_warning("client %p channel (%d, %d) (type, id) wasn't found",
                          client,
                          mig_link->link_msg->channel_type,
                          mig_link->link_msg->channel_id);
            continue;
        }
        reds_channel_do_link(channel, client, mig_link->link_msg, mig_link->stream);
    }

    reds_mig_target_client_free(reds, mig_client);

    return TRUE;
}


static void reds_config_free(RedServerConfig *config)
{
    ChannelSecurityOptions *curr, *next;

    reds_mig_release(config);
    for (curr = config->channels_security; curr; curr = next) {
        next = curr->next;
        g_free(curr);
    }
#if HAVE_SASL
    g_free(config->sasl_appname);
#endif
    g_free(config->spice_name);
    g_array_unref(config->renderers);
    g_array_unref(config->video_codecs);
    g_free(config);
}


void reds_enable_mm_time(RedsState *reds)
{
    reds->mm_time_enabled = TRUE;
    reds->mm_time_latency = MM_TIME_DELTA;
    reds_send_mm_time(reds);
}


static void reds_send_link_result(RedLinkInfo *link, uint32_t error)
{
    error = GUINT32_TO_LE(error);
    red_stream_write_all(link->stream, &error, sizeof(error));
}


RedCharDeviceVDIPort::read_one_msg_from_device()
{
    RedsState *reds;
    int n;

    reds = get_server();
    while (reds->vdagent) {
        switch (priv->read_state) {
        case VDI_PORT_READ_STATE_READ_HEADER:
            n = read(priv->receive_pos, priv->receive_len);
            if (!n) {
                return RedPipeItemPtr();
            }
            if ((priv->receive_len -= n)) {
                priv->receive_pos += n;
                return RedPipeItemPtr();
            }
            priv->message_receive_len = priv->vdi_chunk_header.size;
            priv->read_state = VDI_PORT_READ_STATE_GET_BUFF;
            /* fall through */
        case VDI_PORT_READ_STATE_GET_BUFF: {
            if (!(priv->current_read_buf = vdi_port_get_read_buf(this))) {
                return RedPipeItemPtr();
            }
            priv->receive_pos = priv->current_read_buf->data;
            priv->receive_len = MIN(priv->message_receive_len,
                                    sizeof(priv->current_read_buf->data));
            priv->current_read_buf->len = priv->receive_len;
            priv->message_receive_len -= priv->receive_len;
            priv->read_state = VDI_PORT_READ_STATE_READ_DATA;
        }
            /* fall through */
        case VDI_PORT_READ_STATE_READ_DATA: {
            n = read(priv->receive_pos, priv->receive_len);
            if (!n) {
                return RedPipeItemPtr();
            }
            if ((priv->receive_len -= n)) {
                priv->receive_pos += n;
                break;
            }
            auto dispatch_buf = std::move(priv->current_read_buf);
            priv->receive_pos = NULL;
            if (priv->message_receive_len == 0) {
                priv->read_state = VDI_PORT_READ_STATE_READ_HEADER;
                priv->receive_pos = (uint8_t *)&priv->vdi_chunk_header;
                priv->receive_len = sizeof(priv->vdi_chunk_header);
            } else {
                priv->read_state = VDI_PORT_READ_STATE_GET_BUFF;
            }
            switch (vdi_port_read_buf_process(this, *dispatch_buf)) {
            case AGENT_MSG_FILTER_OK:
                reds_adjust_agent_capabilities(reds, (VDAgentMessage *) dispatch_buf->data);
                return dispatch_buf;
            case AGENT_MSG_FILTER_PROTO_ERROR:
                reds_agent_remove(reds);
                /* fall through */
            case AGENT_MSG_FILTER_MONITORS_CONFIG:
                /* fall through */
            case AGENT_MSG_FILTER_DISCARD:
                dispatch_buf.reset();
            }
        }
        } /* END switch */
    } /* END while */
    return RedPipeItemPtr();
}


SPICE_GNUC_VISIBLE int spice_server_set_noauth(SpiceServer *s)
{
    memset(s->config->taTicket.password, 0, sizeof(s->config->taTicket.password));
    s->config->ticketing_enabled = FALSE;
    return 0;
}


uint32_t reds_get_streaming_video(const RedsState *reds)
{
    return reds->config->streaming_video;
}


SPICE_GNUC_VISIBLE int spice_server_set_agent_file_xfer(SpiceServer *reds, int enable)
{
    reds->config->agent_file_xfer = enable;
    reds_update_agent_properties(reds);
    return 0;
}


void stat_remove_counter(SpiceServer *reds, RedStatCounter *counter)
{
    if (counter->counter) {
        stat_file_remove_counter(reds->stat_file, counter->counter);
        counter->counter = NULL;
    }
}


SPICE_DESTRUCTOR_FUNC(reds_exit)
{
    GList *l;

    pthread_mutex_lock(&global_reds_lock);
    for (l = servers; l != NULL; l = l->next) {
        RedsState *reds = (RedsState*) l->data;
        reds_cleanup(reds);
    }
    pthread_mutex_unlock(&global_reds_lock);
}


static void reds_mig_cleanup(RedsState *reds)
{
    if (reds->mig_inprogress) {

        if (reds->mig_wait_connect || reds->mig_wait_disconnect) {
            SpiceMigrateInterface *sif;
            spice_assert(reds->migration_interface);
            sif = SPICE_UPCAST(SpiceMigrateInterface, reds->migration_interface->base.sif);
            if (reds->mig_wait_connect) {
                sif->migrate_connect_complete(reds->migration_interface);
            } else {
                if (sif->migrate_end_complete) {
                    sif->migrate_end_complete(reds->migration_interface);
                }
            }
        }
        reds->mig_inprogress = FALSE;
        reds->mig_wait_connect = FALSE;
        reds->mig_wait_disconnect = FALSE;
        red_timer_cancel(reds->mig_timer);
        reds_mig_cleanup_wait_disconnect(reds);
    }
}


RedRecord *reds_get_record(RedsState *reds)
{
    if (reds->record) {
        return red_record_ref(reds->record);
    }

    return NULL;
}


reds_core_timer_add_internal(RedsState *reds,
                             SpiceTimerFunc func,
                             void *opaque)
{
   g_return_val_if_fail(reds != NULL, NULL);
   g_return_val_if_fail(reds->core.timer_add != NULL, NULL);

   return reds->core.timer_add(&reds->core, func, opaque);

}


void reds_on_vm_start(RedsState *reds)
{
    FOREACH_QXL_INSTANCE(reds, qxl) {
        red_qxl_start(qxl);
    }
}


void reds_on_main_migrate_connected(RedsState *reds, int seamless)
{
    reds->src_do_seamless_migrate = seamless;
    if (reds->mig_wait_connect) {
        reds_mig_cleanup(reds);
    }
}


static void reds_remove_char_device(RedsState *reds, RedCharDevice *dev)
{
    g_return_if_fail(reds != NULL);
    auto &devs(reds->char_devices);
    g_warn_if_fail(std::find(devs.begin(), devs.end(),
                             red::shared_ptr<RedCharDevice>(dev)) != devs.end());

    devs.remove(red::shared_ptr<RedCharDevice>(dev));
}


static RedClient *reds_get_client(RedsState *reds)
{
    spice_assert(reds->clients.size() <= 1);

    if (reds->clients.empty()) {
        return NULL;
    }

    return *reds->clients.begin();
}


SpiceMsgChannels *reds_msg_channels_new(RedsState *reds)
{
    SpiceMsgChannels* channels_info;

    spice_assert(reds != NULL);

    channels_info = (SpiceMsgChannels *)g_malloc(sizeof(SpiceMsgChannels)
                            + reds->channels.size() * sizeof(SpiceChannelId));

    reds_fill_channels(reds, channels_info);

    return channels_info;
}


static const uint32_t *red_link_info_get_caps(const RedLinkInfo *link)
{
    const uint8_t *caps_start = (const uint8_t *)link->link_mess;

    return (const uint32_t *)(caps_start + link->link_mess->caps_offset);
}


SPICE_GNUC_VISIBLE const char *spice_server_get_video_codecs(SpiceServer *reds)
{
    return video_codecs_to_string(reds_get_video_codecs(reds), ";");
}


void reds_client_disconnect(RedsState *reds, RedClient *client)
{
    RedsMigTargetClient *mig_client;

    if (reds->config->exit_on_disconnect)
    {
        spice_debug("Exiting server because of client disconnect.");
        exit(0);
    }

    if (!client || client->is_disconnecting()) {
        spice_debug("client %p already during disconnection", client);
        return;
    }

    spice_debug("trace");
    /* disconnecting is set to prevent recursion because of the following:
     * main_channel_client_on_disconnect->
     *  reds_client_disconnect->red_client_destroy->main_channel...
     */
    client->set_disconnecting();

    // TODO: we need to handle agent properly for all clients!!!! (e.g., cut and paste, how?)
    // We shouldn't initialize the agent when there are still clients connected

    mig_client = reds_mig_target_client_find(reds, client);
    if (mig_client) {
        reds_mig_target_client_free(reds, mig_client);
    }

    if (reds->mig_wait_disconnect) {
        reds_mig_remove_wait_disconnect_client(reds, client);
    }

    /* note that client might be NULL, if the vdagent was once
     * up and than was removed */
    RedCharDeviceClientOpaque *client_opaque = (RedCharDeviceClientOpaque *) client;
    if (reds->agent_dev->client_exists(client_opaque)) {
        reds->agent_dev->client_remove(client_opaque);
    }

    reds->clients.remove(client);
    client->destroy();

   // TODO: we need to handle agent properly for all clients!!!! (e.g., cut and paste, how? Maybe throw away messages
   // if we are in the middle of one from another client)
    if (reds->clients.empty()) {
        /* Let the agent know the client is disconnected */
        if (reds->agent_dev->priv->agent_attached) {
            RedCharDeviceWriteBuffer *char_dev_buf =
                vdagent_new_write_buffer(reds->agent_dev.get(),
                                         VD_AGENT_CLIENT_DISCONNECTED,
                                         0,
                                         false);

            reds->agent_dev->write_buffer_add(char_dev_buf);
        }

        /* Reset write filter to start with clean state on client reconnect */
        agent_msg_filter_init(&reds->agent_dev->priv->write_filter, reds->config->agent_copypaste,
                              reds->config->agent_file_xfer,
                              reds_use_client_monitors_config(reds), TRUE);

        /* Throw away pending chunks from the current (if any) and future
         *  messages read from the agent */
        reds->agent_dev->priv->read_filter.result = AGENT_MSG_FILTER_DISCARD;
        reds->agent_dev->priv->read_filter.discard_all = TRUE;
        g_free(reds->agent_dev->priv->mig_data);
        reds->agent_dev->priv->mig_data = NULL;

        reds_mig_cleanup(reds);
    }
}


SPICE_GNUC_VISIBLE int spice_server_migrate_connect(SpiceServer *reds, const char* dest,
                                                    int port, int secure_port,
                                                    const char* cert_subject)
{
    SpiceMigrateInterface *sif;
    int try_seamless;

    spice_debug("trace");
    spice_assert(reds->migration_interface);

    if (reds->expect_migrate) {
        spice_debug("consecutive calls without migration. Canceling previous call");
        reds->main_channel->migrate_src_complete(FALSE);
    }

    sif = SPICE_UPCAST(SpiceMigrateInterface, reds->migration_interface->base.sif);

    if (!reds_set_migration_dest_info(reds, dest, port, secure_port, cert_subject)) {
        sif->migrate_connect_complete(reds->migration_interface);
        return -1;
    }

    reds->expect_migrate = TRUE;

    /*
     * seamless migration support was added to the client after the support in
     * agent_connect_tokens, so there shouldn't be contradicition - if
     * the client is capable of seamless migration, it is capbable of agent_connected_tokens.
     * The demand for agent_connected_tokens support is in order to assure that if migration
     * occured when the agent was not connected, the tokens state after migration will still
     * be valid (see reds_reset_vdp for more details).
     */
    try_seamless = reds->seamless_migration_enabled &&
                   reds->main_channel->test_remote_cap(SPICE_MAIN_CAP_AGENT_CONNECTED_TOKENS);
    /* main channel will take care of clients that are still during migration (at target)*/
    if (reds->main_channel->migrate_connect(reds->config->mig_spice, try_seamless)) {
        reds_mig_started(reds);
    } else {
        if (reds->clients.empty()) {
            reds_mig_release(reds->config);
            spice_debug("no client connected");
        }
        sif->migrate_connect_complete(reds->migration_interface);
    }

    return 0;
}


static void reds_channel_init_auth_caps(RedLinkInfo *link, RedChannel *channel)
{
    RedsState *reds = link->reds;
    if (reds->config->sasl_enabled && !link->skip_auth) {
        channel->set_common_cap(SPICE_COMMON_CAP_AUTH_SASL);
    } else {
        channel->set_common_cap(SPICE_COMMON_CAP_AUTH_SPICE);
    }
}


static void reds_handle_read_magic_done(void *opaque)
{
    RedLinkInfo *link = (RedLinkInfo *)opaque;
    const SpiceLinkHeader *header = &link->link_header;

    if (header->magic != SPICE_MAGIC) {
        /* Attempt to detect and support a WebSocket connection,
           which will be proceeded by a variable length GET style request.
           We cannot know we are dealing with a WebSocket connection
           until we have read at least 3 bytes, and we will have to
           read many more bytes than are contained in a SpiceLinkHeader.
           So we may as well read a SpiceLinkHeader's worth of data, and if it's
           clear that a WebSocket connection was requested, we switch
           before proceeding further. */
        if (red_stream_is_websocket(link->stream, &header->magic, sizeof(header->magic))) {
            reds_handle_new_link(link);
            return;
        }
        reds_send_link_error(link, SPICE_LINK_ERR_INVALID_MAGIC);
        reds_link_free(link);
        return;
    }

    red_stream_async_read(link->stream,
                          ((uint8_t *)&link->link_header) + sizeof(header->magic),
                          sizeof(SpiceLinkHeader) - sizeof(header->magic),
                          reds_handle_read_header_done,
                          link);
}


static gboolean reds_use_client_monitors_config(RedsState *reds)
{
    if (reds->qxl_instances.empty()) {
        return FALSE;
    }

    FOREACH_QXL_INSTANCE(reds, qxl) {
        if (!red_qxl_client_monitors_config(qxl, NULL))
            return FALSE;
    }
    return TRUE;
}


static void reds_mig_target_client_free(RedsState *reds, RedsMigTargetClient *mig_client)
{
    reds->mig_target_clients = g_list_remove(reds->mig_target_clients, mig_client);
    g_list_free_full(mig_client->pending_links, g_free);
    g_free(mig_client);
}


SPICE_GNUC_VISIBLE int spice_server_add_client(SpiceServer *reds, int socket, int skip_auth)
{
    RedLinkInfo *link;

    if (!(link = reds_init_client_connection(reds, socket))) {
        spice_warning("accept failed");
        return -1;
    }

    link->skip_auth = skip_auth;

    reds_handle_new_link(link);
    return 0;
}


SPICE_GNUC_VISIBLE int spice_server_set_agent_copypaste(SpiceServer *reds, int enable)
{
    reds->config->agent_copypaste = enable;
    reds_update_agent_properties(reds);
    return 0;
}


static RedCharDeviceWriteBuffer *vdagent_new_write_buffer(RedCharDeviceVDIPort *agent_dev,
                                                          uint32_t type,
                                                          size_t size,
                                                          bool use_token)
{
    uint32_t total_msg_size = sizeof(VDIChunkHeader) + sizeof(VDAgentMessage) + size;

    RedCharDeviceWriteBuffer *char_dev_buf;
        char_dev_buf = agent_dev->write_buffer_get_server(total_msg_size,
                                                          use_token);
    if (!char_dev_buf) {
        return NULL;  // no token was available
    }

    char_dev_buf->buf_used = total_msg_size;
    VDInternalBuf *internal_buf = (VDInternalBuf *)char_dev_buf->buf;
    internal_buf->chunk_header.port = VDP_SERVER_PORT;
    internal_buf->chunk_header.size = sizeof(VDAgentMessage) + size;
    internal_buf->header.protocol = VD_AGENT_PROTOCOL;
    internal_buf->header.type = type;
    internal_buf->header.opaque = 0;
    internal_buf->header.size = size;

    return char_dev_buf;
}


static void reds_handle_main_link(RedsState *reds, RedLinkInfo *link)
{
    RedClient *client;
    RedStream *stream;
    SpiceLinkMess *link_mess;
    uint32_t connection_id;
    MainChannelClient *mcc;
    int mig_target = FALSE;
    RedChannelCapabilities caps;

    spice_debug("trace");
    spice_assert(reds->main_channel);

    reds_late_initialization(reds);

    link_mess = link->link_mess;
    if (!reds->allow_multiple_clients) {
        reds_disconnect(reds);
    }

    if (link_mess->connection_id == 0) {
        reds_send_link_result(link, SPICE_LINK_ERR_OK);
        while((connection_id = rand()) == 0);
        mig_target = FALSE;
    } else {
        // TODO: make sure link_mess->connection_id is the same
        // connection id the migration src had (use vmstate to store the connection id)
        reds_send_link_result(link, SPICE_LINK_ERR_OK);
        connection_id = link_mess->connection_id;
        mig_target = TRUE;
    }

    reds->mig_inprogress = FALSE;
    reds->mig_wait_connect = FALSE;
    reds->mig_wait_disconnect = FALSE;

    reds_info_new_channel(link, connection_id);
    stream = link->stream;
    link->stream = NULL;
    client = red_client_new(reds, mig_target);
    reds->clients.push_front(client);

    red_channel_capabilities_init_from_link_message(&caps, link_mess);
    mcc = main_channel_link(reds->main_channel.get(), client,
                            stream, connection_id, mig_target,
                            &caps);
    red_channel_capabilities_reset(&caps);
    spice_debug("NEW Client %p mcc %p connect-id %d", client, mcc, connection_id);

    if (reds->vdagent) {
        if (mig_target) {
            spice_warning("unexpected: vdagent attached to destination during migration");
        }
        agent_msg_filter_config(&reds->agent_dev->priv->read_filter,
                                reds->config->agent_copypaste,
                                reds->config->agent_file_xfer,
                                reds_use_client_monitors_config(reds));
        reds->agent_dev->priv->read_filter.discard_all = FALSE;
        reds->agent_dev->priv->plug_generation++;
    }

    if (!mig_target) {
        mcc->push_init(reds->qxl_instances.size(), reds->mouse_mode,
                       reds->is_client_mouse_allowed,
                       reds_get_mm_time() - MM_TIME_DELTA,
                       reds_qxl_ram_size(reds));
        if (reds->config->spice_name)
            mcc->push_name(reds->config->spice_name);
        if (reds->config->spice_uuid_is_set)
            mcc->push_uuid(reds->config->spice_uuid);
    } else {
        reds_mig_target_client_add(reds, client);
    }

    if (red_stream_get_family(stream) != AF_UNIX) {
        mcc->start_net_test(!mig_target);
    }
}


static int reds_main_channel_connected(RedsState *reds)
{
    return reds->main_channel && reds->main_channel->is_connected();
}


static void reds_handle_new_link(RedLinkInfo *link)
{
    red_stream_set_async_error_handler(link->stream, reds_handle_link_error);
    red_stream_async_read(link->stream,
                          (uint8_t *)&link->link_header,
                          sizeof(link->link_header.magic),
                          reds_handle_read_magic_done,
                          link);
}


red_channel_capabilities_init_from_link_message(RedChannelCapabilities *caps,
                                                const SpiceLinkMess *link_mess)
{
    const uint8_t *raw_caps = (const uint8_t *)link_mess + link_mess->caps_offset;

    caps->num_common_caps = link_mess->num_common_caps;
    caps->common_caps = NULL;
    if (caps->num_common_caps) {
        caps->common_caps = (uint32_t*) g_memdup(raw_caps,
                                     link_mess->num_common_caps * sizeof(uint32_t));
    }
    caps->num_caps = link_mess->num_channel_caps;
    caps->caps = NULL;
    if (link_mess->num_channel_caps) {
        caps->caps = (uint32_t*) g_memdup(raw_caps + link_mess->num_common_caps * sizeof(uint32_t),
                              link_mess->num_channel_caps * sizeof(uint32_t));
    }
}


spice_wan_compression_t reds_get_jpeg_state(const RedsState *reds)
{
    return reds->config->jpeg_state;
}


int reds_get_free_channel_id(RedsState *reds, uint32_t type)
{
    // this mark if some IDs are used.
    // The size of the array limits the possible id returned but
    // usually the IDs used for a channel type are not much.
    bool used_ids[256];

    unsigned n;

    // mark id used for the specific channel type
    memset(used_ids, 0, sizeof(used_ids));
    for (const auto channel: reds->channels) {
        if (channel->type() == type && channel->id() < SPICE_N_ELEMENTS(used_ids)) {
            used_ids[channel->id()] = true;
        }
    }

    // find first ID not marked as used
    for (n = 0; n < SPICE_N_ELEMENTS(used_ids); ++n) {
        if (!used_ids[n]) {
            return n;
        }
    }
    return -1;
}


static void reds_handle_link(RedLinkInfo *link)
{
    RedsState *reds = link->reds;

    red_stream_remove_watch(link->stream);
    if (link->link_mess->channel_type == SPICE_CHANNEL_MAIN) {
        reds_handle_main_link(reds, link);
    } else {
        reds_handle_other_links(reds, link);
    }
    reds_link_free(link);
}


uint32_t reds_get_mm_time(void)
{
    return spice_get_monotonic_time_ns() / NSEC_PER_MILLISEC;
}


static void reds_update_mouse_mode(RedsState *reds)
{
    int allowed = 0;
    int qxl_count = reds->qxl_instances.size();
    int display_channel_count = 0;

    for (const auto channel: reds->channels) {
        if (channel->type() == SPICE_CHANNEL_DISPLAY) {
            ++display_channel_count;
        }
    }

    if ((reds->config->agent_mouse && reds->vdagent) ||
        (reds->inputs_channel && reds->inputs_channel->has_tablet() &&
            qxl_count == 1 && display_channel_count == 1)) {
        allowed = reds->dispatcher_allows_client_mouse;
    }
    if (allowed == reds->is_client_mouse_allowed) {
        return;
    }
    reds->is_client_mouse_allowed = allowed;
    if (reds->mouse_mode == SPICE_MOUSE_MODE_CLIENT && !allowed) {
        reds_set_mouse_mode(reds, SPICE_MOUSE_MODE_SERVER);
        return;
    }
    if (reds->main_channel) {
        reds->main_channel->push_mouse_mode(reds->mouse_mode,
                                            reds->is_client_mouse_allowed);
    }
}


static void reds_late_initialization(RedsState *reds)
{
    // do only once
    if (reds->late_initialization_done) {
        return;
    }

    // create stream channels for streaming devices
    for (auto dev: reds->char_devices) {
        auto stream_dev = dynamic_cast<StreamDevice*>(dev.get());
        if (stream_dev) {
            stream_dev->create_channel();
        }
    }
    reds->late_initialization_done = true;
}


SPICE_GNUC_VISIBLE int spice_server_set_image_compression(SpiceServer *s,
                                                          SpiceImageCompression comp)
{
#ifndef USE_LZ4
    if (comp == SPICE_IMAGE_COMPRESSION_LZ4) {
        spice_warning("LZ4 compression not supported, falling back to auto GLZ");
        comp = SPICE_IMAGE_COMPRESSION_AUTO_GLZ;
        reds_config_set_image_compression(s, comp);
        return -1;
    }
#endif
    reds_config_set_image_compression(s, comp);
    return 0;
}


SpiceMouseMode reds_get_mouse_mode(RedsState *reds)
{
    return reds->mouse_mode;
}


static void reds_mig_disconnect(RedsState *reds)
{
    if (reds_main_channel_connected(reds)) {
        reds_disconnect(reds);
    } else {
        reds_mig_cleanup(reds);
    }
}


static int reds_security_check(RedLinkInfo *link)
{
    RedsState *reds = link->reds;
    ChannelSecurityOptions *security_option = reds_find_channel_security(reds, link->link_mess->channel_type);
    uint32_t security = security_option ? security_option->options : reds->config->default_channel_security;
    return (red_stream_is_ssl(link->stream) && (security & SPICE_CHANNEL_SECURITY_SSL)) ||
        (!red_stream_is_ssl(link->stream) && (security & SPICE_CHANNEL_SECURITY_NONE));
}


static void reds_accept_ssl_connection(int fd, int event, void *data)
{
    RedsState *reds = (RedsState*) data;
    RedLinkInfo *link;
    int socket;

    if ((socket = accept(fd, NULL, 0)) == -1) {
        spice_warning("accept failed, %s", strerror(errno));
        return;
    }

    if (!(link = reds_init_client_ssl_connection(reds, socket))) {
        socket_close(socket);
    }
}


static void openssl_thread_setup(void)
{
    int i;

    /* Somebody else already setup threading for OpenSSL,
     * don't do it twice to avoid possible races.
     */
    if (CRYPTO_get_locking_callback() != NULL) {
        red_dump_openssl_errors();
        return;
    }

    lock_cs = (pthread_mutex_t*) OPENSSL_malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t));

    for (i = 0; i < CRYPTO_num_locks(); i++) {
        pthread_mutex_init(&(lock_cs[i]), NULL);
    }

    CRYPTO_THREADID_set_callback(pthreads_thread_id);
    CRYPTO_set_locking_callback(pthreads_locking_callback);
}


gboolean reds_config_get_agent_mouse(const RedsState *reds)
{
    return reds->config->agent_mouse;
}


void reds_on_main_agent_tokens(RedsState *reds, MainChannelClient *mcc, uint32_t num_tokens)
{
    RedClient *client = mcc->get_client();
    if (!reds->vdagent) {
        return;
    }
    spice_assert(reds->vdagent->st);
    reds->vdagent->st->send_to_client_tokens_add((RedCharDeviceClientOpaque *)client,
                                                 num_tokens);
}


SPICE_GNUC_VISIBLE int spice_server_get_peer_info(SpiceServer *reds, struct sockaddr *sa, socklen_t *salen)
{
    return -1;
}


static void migrate_timeout(RedsState *reds)
{
    spice_debug("trace");
    spice_assert(reds->mig_wait_connect || reds->mig_wait_disconnect);
    if (reds->mig_wait_connect) {
        /* we will fall back to the switch host scheme when migration completes */
        reds->main_channel->migrate_cancel_wait();
        /* in case part of the client haven't yet completed the previous migration, disconnect them */
        reds_mig_target_client_disconnect_all(reds);
        reds_mig_cleanup(reds);
    } else {
        reds_mig_disconnect(reds);
    }
}


void reds_handle_channel_event(RedsState *reds, int event, SpiceChannelEventInfo *info)
{
    reds->core.channel_event(&reds->core, event, info);

    if (event == SPICE_CHANNEL_EVENT_DISCONNECTED) {
        g_free(info);
    }
}


SPICE_GNUC_VISIBLE void spice_server_free_video_codecs(SpiceServer *reds, const char *video_codecs)
{
    g_free((char *) video_codecs);
}


static void reds_handle_ticket(void *opaque)
{
    RedLinkInfo *link = (RedLinkInfo *)opaque;
    RedsState *reds = link->reds;
    char *password;
    int password_size;

    if (RSA_size(link->tiTicketing.rsa) < SPICE_MAX_PASSWORD_LENGTH) {
        spice_warning("RSA modulus size is smaller than SPICE_MAX_PASSWORD_LENGTH (%d < %d), "
                      "SPICE ticket sent from client may be truncated",
                      RSA_size(link->tiTicketing.rsa), SPICE_MAX_PASSWORD_LENGTH);
    }

    password = (char *) alloca(RSA_size(link->tiTicketing.rsa) + 1);
    password_size = RSA_private_decrypt(link->tiTicketing.rsa_size,
                                        link->tiTicketing.encrypted_ticket.encrypted_data,
                                        (unsigned char *)password,
                                        link->tiTicketing.rsa,
                                        RSA_PKCS1_OAEP_PADDING);
    if (password_size == -1) {
        spice_warning("failed to decrypt RSA encrypted password");
        red_dump_openssl_errors();
        goto error;
    }
    password[password_size] = '\0';

    if (reds->config->ticketing_enabled && !link->skip_auth) {
        time_t ltime;
        bool expired;

        if (strlen(reds->config->taTicket.password) == 0) {
            spice_warning("Ticketing is enabled, but no password is set. "
                          "please set a ticket first");
            goto error;
        }

        ltime = spice_get_monotonic_time_ns() / NSEC_PER_SEC;
        expired = (reds->config->taTicket.expiration_time < ltime);

        if (expired) {
            spice_warning("Ticket has expired");
            goto error;
        }

        if (strcmp(password, reds->config->taTicket.password) != 0) {
            spice_warning("Invalid password");
            goto error;
        }
    }

    reds_handle_link(link);
    return;

error:
    reds_send_link_result(link, SPICE_LINK_ERR_PERMISSION_DENIED);
    reds_link_free(link);
}


void reds_handle_agent_mouse_event(RedsState *reds, const VDAgentMouseState *mouse_state)
{
    if (!reds->inputs_channel || !reds->agent_dev->priv->agent_attached) {
        return;
    }

    RedCharDeviceWriteBuffer *char_dev_buf = vdagent_new_write_buffer(reds->agent_dev.get(),
                                                                      VD_AGENT_MOUSE_STATE,
                                                                      sizeof(VDAgentMouseState),
                                                                      true);

    if (!char_dev_buf) {
        reds->pending_mouse_event = TRUE;
        return;
    }

    reds->pending_mouse_event = FALSE;

    VDInternalBuf *internal_buf = (VDInternalBuf *)char_dev_buf->buf;
    internal_buf->u.mouse_state = *mouse_state;

    reds->agent_dev->write_buffer_add(char_dev_buf);
}


int reds_has_vdagent(RedsState *reds)
{
    return !!reds->vdagent;
}


static inline void openssl_global_init(void)
{
    static GOnce openssl_once = G_ONCE_INIT;
    g_once(&openssl_once, openssl_global_init_once, NULL);
}


SPICE_GNUC_VISIBLE int spice_server_set_ticket(SpiceServer *reds,
                                               const char *passwd, int lifetime,
                                               int fail_if_connected,
                                               int disconnect_if_connected)
{
    if (reds_main_channel_connected(reds)) {
        if (fail_if_connected) {
            return -1;
        }
        if (disconnect_if_connected) {
            reds_disconnect(reds);
        }
    }

    on_activating_ticketing(reds);
    reds->config->ticketing_enabled = TRUE;
    if (lifetime == 0) {
        reds->config->taTicket.expiration_time = INT_MAX;
    } else {
        time_t now = spice_get_monotonic_time_ns() / NSEC_PER_SEC;
        reds->config->taTicket.expiration_time = now + lifetime;
    }
    if (passwd != NULL) {
        if (strlen(passwd) > SPICE_MAX_PASSWORD_LENGTH)
            return -1;
        g_strlcpy(reds->config->taTicket.password, passwd, sizeof(reds->config->taTicket.password));
    } else {
        memset(reds->config->taTicket.password, 0, sizeof(reds->config->taTicket.password));
        reds->config->taTicket.expiration_time = 0;
    }
    return 0;
}


SPICE_GNUC_VISIBLE int spice_server_remove_interface(SpiceBaseInstance *sin)
{
    RedsState *reds;
    const SpiceBaseInterface *base_interface;

    g_return_val_if_fail(sin != NULL, -1);

    base_interface = sin->sif;
    if (strcmp(base_interface->type, SPICE_INTERFACE_TABLET) == 0) {
        SpiceTabletInstance *tablet = SPICE_UPCAST(SpiceTabletInstance, sin);
        g_return_val_if_fail(tablet->st != NULL, -1);
        reds = spice_tablet_state_get_server(tablet->st);
        spice_debug("remove SPICE_INTERFACE_TABLET");
        reds->inputs_channel->detach_tablet(tablet);
        reds_update_mouse_mode(reds);
    } else if (strcmp(base_interface->type, SPICE_INTERFACE_PLAYBACK) == 0) {
        spice_debug("remove SPICE_INTERFACE_PLAYBACK");
        snd_detach_playback(SPICE_UPCAST(SpicePlaybackInstance, sin));
    } else if (strcmp(base_interface->type, SPICE_INTERFACE_RECORD) == 0) {
        spice_debug("remove SPICE_INTERFACE_RECORD");
        snd_detach_record(SPICE_UPCAST(SpiceRecordInstance, sin));
    } else if (strcmp(base_interface->type, SPICE_INTERFACE_CHAR_DEVICE) == 0) {
        SpiceCharDeviceInstance *char_device = SPICE_UPCAST(SpiceCharDeviceInstance, sin);
        g_return_val_if_fail(char_device->st != NULL, -1);
        reds = char_device->st->get_server();
        return spice_server_char_device_remove_interface(reds, sin);
    } else if (strcmp(base_interface->type, SPICE_INTERFACE_QXL) == 0) {
        QXLInstance *qxl;

        qxl = SPICE_UPCAST(QXLInstance, sin);
        g_return_val_if_fail(qxl->st != NULL, -1);
        reds = red_qxl_get_server(qxl->st);
        reds->qxl_instances.remove(qxl); // XXX owning
        red_qxl_destroy(qxl);
    } else {
        spice_warning("VD_INTERFACE_REMOVING unsupported");
        return -1;
    }

    return 0;
}


static red::shared_ptr<RedVDIReadBuf> vdi_port_get_read_buf(RedCharDeviceVDIPort *dev)
{
    if (dev->priv->num_read_buf >= REDS_VDI_PORT_NUM_RECEIVE_BUFFS) {
        return red::shared_ptr<RedVDIReadBuf>();
    }

    dev->priv->num_read_buf++;
    return vdi_read_buf_new(dev);
}


static int reds_init_socket(const char *addr, int portnr, int family)
{
    static const int on=1, off=0;
    struct addrinfo ai,*res,*e;
    char port[33];
    int slisten, rc;

    if (family == AF_UNIX) {
#ifndef _WIN32
        int len;
        struct sockaddr_un local = { 0, };

        if ((slisten = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
            perror("socket");
            return -1;
        }

        local.sun_family = AF_UNIX;
        g_strlcpy(local.sun_path, addr, sizeof(local.sun_path));
        len = SUN_LEN(&local);
        if (local.sun_path[0] == '@') {
            local.sun_path[0] = 0;
        } else {
            unlink(local.sun_path);
        }
        if (bind(slisten, (struct sockaddr *)&local, len) == -1) {
            perror("bind");
            socket_close(slisten);
            return -1;
        }

        goto listen;
#else
        return -1;
#endif
    }

    memset(&ai,0, sizeof(ai));
    ai.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
    ai.ai_socktype = SOCK_STREAM;
    ai.ai_family = family;

    snprintf(port, sizeof(port), "%d", portnr);
    rc = getaddrinfo(strlen(addr) ? addr : NULL, port, &ai, &res);
    if (rc != 0) {
        spice_warning("getaddrinfo(%s,%s): %s", addr, port,
                      gai_strerror(rc));
        return -1;
    }

    for (e = res; e != NULL; e = e->ai_next) {
        slisten = socket(e->ai_family, e->ai_socktype, e->ai_protocol);
        if (slisten < 0) {
            continue;
        }

        setsockopt(slisten,SOL_SOCKET,SO_REUSEADDR,(void*)&on,sizeof(on));
#ifdef IPV6_V6ONLY
        if (e->ai_family == PF_INET6) {
            /* listen on both ipv4 and ipv6 */
            setsockopt(slisten,IPPROTO_IPV6,IPV6_V6ONLY,(void*)&off,
                       sizeof(off));
        }
#endif
        if (bind(slisten, e->ai_addr, e->ai_addrlen) == 0) {
            char uaddr[INET6_ADDRSTRLEN+1];
            char uport[33];
            rc = getnameinfo((struct sockaddr*)e->ai_addr,e->ai_addrlen,
                             uaddr,INET6_ADDRSTRLEN, uport,32,
                             NI_NUMERICHOST | NI_NUMERICSERV);
            if (rc == 0) {
                spice_debug("bound to %s:%s", uaddr, uport);
            } else {
                spice_debug("cannot resolve address spice-server is bound to");
            }
            freeaddrinfo(res);
            goto listen;
        }
        socket_close(slisten);
    }
    spice_warning("binding socket to %s:%d failed", addr, portnr);
    freeaddrinfo(res);
    return -1;

listen:
    if (listen(slisten, SOMAXCONN) != 0) {
        spice_warning("listen: %s", strerror(errno));
        socket_close(slisten);
        return -1;
    }
    return slisten;
}


SPICE_GNUC_VISIBLE int spice_server_set_listen_socket_fd(SpiceServer *s, int listen_fd)
{
    s->config->spice_listen_socket_fd = listen_fd;
    return 0;
}


static void reds_mig_finished(RedsState *reds, int completed)
{
    spice_debug("trace");

    reds->mig_inprogress = TRUE;

    if (reds->src_do_seamless_migrate && completed) {
        reds_migrate_channels_seamless(reds);
    } else {
        reds->main_channel->migrate_src_complete(completed);
    }

    if (completed) {
        reds_mig_fill_wait_disconnect(reds);
    } else {
        reds_mig_cleanup(reds);
    }
    reds_mig_release(reds->config);
}


SPICE_GNUC_VISIBLE int spice_server_set_exit_on_disconnect(SpiceServer *s, int flag)
{
    s->config->exit_on_disconnect = !!flag;
    return 0;
}


SPICE_GNUC_VISIBLE void spice_server_set_seamless_migration(SpiceServer *reds, int enable)
{
    /* seamless migration is not supported with multiple clients */
    reds->seamless_migration_enabled = enable && !reds->allow_multiple_clients;
    spice_debug("seamless migration enabled=%d", enable);
}


void reds_on_main_channel_migrate(RedsState *reds, MainChannelClient *mcc)
{
    RedCharDeviceVDIPort *agent_dev = reds->agent_dev.get();
    uint32_t read_data_len;

    spice_assert(reds->clients.size() == 1);

    if (agent_dev->priv->read_state != VDI_PORT_READ_STATE_READ_DATA) {
        return;
    }
    spice_assert(agent_dev->priv->current_read_buf &&
                 agent_dev->priv->receive_pos > agent_dev->priv->current_read_buf->data);
    read_data_len = agent_dev->priv->receive_pos - agent_dev->priv->current_read_buf->data;

    if (agent_dev->priv->read_filter.msg_data_to_read ||
        read_data_len > sizeof(VDAgentMessage)) { /* msg header has been read */
        red::shared_ptr<RedVDIReadBuf> read_buf = std::move(agent_dev->priv->current_read_buf);

        spice_debug("push partial read %u (msg first chunk? %d)", read_data_len,
                    !agent_dev->priv->read_filter.msg_data_to_read);

        read_buf->len = read_data_len;
        switch (vdi_port_read_buf_process(agent_dev, *read_buf)) {
        case AGENT_MSG_FILTER_OK:
            reds_adjust_agent_capabilities(reds, (VDAgentMessage *)read_buf->data);
            mcc->push_agent_data(std::move(read_buf));
            break;
        case AGENT_MSG_FILTER_PROTO_ERROR:
            reds_agent_remove(reds);
            /* fall through */
        case AGENT_MSG_FILTER_MONITORS_CONFIG:
            /* fall through */
        case AGENT_MSG_FILTER_DISCARD:
            read_buf.reset();
        }

        spice_assert(agent_dev->priv->receive_len);
        agent_dev->priv->message_receive_len += agent_dev->priv->receive_len;
        agent_dev->priv->read_state = VDI_PORT_READ_STATE_GET_BUFF;
        agent_dev->priv->receive_pos = NULL;
    }
}


static void reds_handle_read_header_done(void *opaque)
{
    RedLinkInfo *link = (RedLinkInfo *)opaque;
    SpiceLinkHeader *header = &link->link_header;

    header->major_version = GUINT32_FROM_LE(header->major_version);
    header->minor_version = GUINT32_FROM_LE(header->minor_version);
    header->size = GUINT32_FROM_LE(header->size);

    if (header->major_version != SPICE_VERSION_MAJOR) {
        if (header->major_version > 0) {
            reds_send_link_error(link, SPICE_LINK_ERR_VERSION_MISMATCH);
        }

        spice_warning("version mismatch");
        reds_link_free(link);
        return;
    }

    /* the check for 4096 is to avoid clients to cause arbitrary big memory allocations */
    if (header->size < sizeof(SpiceLinkMess) || header->size > 4096) {
        reds_send_link_error(link, SPICE_LINK_ERR_INVALID_DATA);
        spice_warning("bad size %u", header->size);
        reds_link_free(link);
        return;
    }

    link->link_mess = (SpiceLinkMess*) g_malloc(header->size);

    red_stream_async_read(link->stream,
                          (uint8_t *)link->link_mess,
                          header->size,
                          reds_handle_read_link_done,
                          link);
}


static AgentMsgFilterResult vdi_port_read_buf_process(RedCharDeviceVDIPort *dev,
                                                      RedVDIReadBuf& buf)
{
    switch (dev->priv->vdi_chunk_header.port) {
    case VDP_CLIENT_PORT:
        return agent_msg_filter_process_data(&dev->priv->read_filter, buf.data, buf.len);
    case VDP_SERVER_PORT:
        return AGENT_MSG_FILTER_DISCARD;
    default:
        spice_warning("invalid port");
        return AGENT_MSG_FILTER_PROTO_ERROR;
    }
}


GArray* reds_get_renderers(RedsState *reds)
{
    return reds->config->renderers;
}


void stat_remove_node(SpiceServer *reds, RedStatNode *node)
{
    if (node->ref != INVALID_STAT_REF) {
        stat_file_remove_node(reds->stat_file, node->ref);
        node->ref = INVALID_STAT_REF;
    }
}


static gboolean get_name_index(const EnumNames names[], const char *name, uint32_t *index)
{
    if (name) {
        int i;
        for (i = 0; names[i].name; i++) {
            if (strcmp(name, names[i].name) == 0) {
                *index = i;
                return TRUE;
            }
        }
    }
    return FALSE;
}


SPICE_GNUC_VISIBLE const char** spice_server_char_device_recognized_subtypes(void)
{
    return (const char **) spice_server_char_device_recognized_subtypes_list;
}


SPICE_GNUC_VISIBLE int spice_server_migrate_start(SpiceServer *reds)
{
    spice_debug("trace");
    if (!reds->config->mig_spice) {
        return -1;
    }
    return 0;
}


SPICE_GNUC_VISIBLE int spice_server_set_port(SpiceServer *reds, int port)
{
    if (port < 0 || port > 0xffff) {
        return -1;
    }
    reds->config->spice_port = port;
    return 0;
}


SPICE_GNUC_VISIBLE int spice_server_get_sock_info(SpiceServer *reds, struct sockaddr *sa, socklen_t *salen)
{
    return -1;
}


void reds_update_client_mouse_allowed(RedsState *reds)
{
    int allow_now = FALSE;
    int x_res = 0;
    int y_res = 0;
    int num_active_workers = reds->qxl_instances.size();

    if (num_active_workers > 0) {

        allow_now = TRUE;
        FOREACH_QXL_INSTANCE(reds, qxl) {
            if (red_qxl_get_allow_client_mouse(qxl, &x_res, &y_res, &allow_now)) {
                break;
            }
        }
    }

    if (allow_now || allow_now != reds->dispatcher_allows_client_mouse) {
        reds->monitor_mode.x_res = x_res;
        reds->monitor_mode.y_res = y_res;
        reds->dispatcher_allows_client_mouse = allow_now;
        reds_update_mouse_mode(reds);
        if (reds->is_client_mouse_allowed && reds->inputs_channel &&
            reds->inputs_channel->has_tablet()) {
            reds->inputs_channel->set_tablet_logical_size(reds->monitor_mode.x_res, reds->monitor_mode.y_res);
        }
    }
}


void stat_init_counter(RedStatCounter *counter, SpiceServer *reds,
                       const RedStatNode *parent, const char *name, int visible)
{
    StatNodeRef parent_ref = parent ? parent->ref : INVALID_STAT_REF;
    counter->counter =
        stat_file_add_counter(reds->stat_file, parent_ref, name, visible);
}


void reds_release_agent_data_buffer(RedsState *reds, uint8_t *buf)
{
    RedCharDeviceVDIPort *dev = reds->agent_dev.get();

    if (!dev->priv->recv_from_client_buf) {
        g_free(buf);
        return;
    }

    spice_assert(buf == dev->priv->recv_from_client_buf->buf + sizeof(VDIChunkHeader));
    /* if we pushed the buffer the buffer is attached to the channel so don't free it */
    if (!dev->priv->recv_from_client_buf_pushed) {
        RedCharDevice::write_buffer_release(dev,
                                            &dev->priv->recv_from_client_buf);
    }
    dev->priv->recv_from_client_buf = NULL;
    dev->priv->recv_from_client_buf_pushed = FALSE;
}


SPICE_GNUC_VISIBLE void spice_server_set_name(SpiceServer *s, const char *name)
{
    g_free(s->config->spice_name);
    s->config->spice_name = g_strdup(name);
}


Bug 701785: fixed sanitizer heap-buffer-overflow in lprn_is_black().

In contrib/lips4/gdevlprn.c:lprn_is_black(), it seems that bpl is not
necessarily a multiple of lprn->nBw, so we need to explicitly avoid straying
into the next line's data.

This also avoids accessing beyond our buffer if we are already on the last
line, and so fixes the sanitizer error.

Fixes:
    ./sanbin/gs -sOutputFile=tmp -sDEVICE=lips2p ../bug-701785.pdf

lprn_bubble_flush_all(gx_device_printer * pdev, gp_file * fp)
{
    gx_device_lprn *const lprn = (gx_device_lprn *) pdev;
    int i = 0;
    int bpl = gdev_mem_bytes_per_scan_line(pdev);
    int maxBx = (bpl + lprn->nBw - 1) / lprn->nBw;

    for (i = 0; i < maxBx; i++) {
        if (lprn->bubbleTbl[i] != NULL) {
            lprn_bubble_flush(pdev, fp, lprn->bubbleTbl[i]);
        } else
            break;
    }
}

A buffer overflow vulnerability in lprn_is_black() in contrib/lips4/gdevlprn.c of Artifex Software GhostScript v9.50 allows a remote attacker to cause a denial of service via a crafted PDF file. This is fixed in v9.51.


lprn_print_image(gx_device_printer * pdev, gp_file * fp)
{
    gx_device_lprn *const lprn = (gx_device_lprn *) pdev;
    int y;
    int bpl = gdev_mem_bytes_per_scan_line(pdev);
    Bubble *bbtbl;
    Bubble *bbl;
    int i;
    int ri, rmin, read_y;
    int code = 0;
    Bubble *bubbleBuffer;
    int maxBx, maxBy, maxY;
    int start_y_block = 0;	/* start of data in buffer */
    int num_y_blocks = 0;	/* number of data line [r:r+h-1] */

    maxBx = (bpl + lprn->nBw - 1) / lprn->nBw;
    maxBy = (pdev->height + lprn->nBh - 1) / lprn->nBh;
    maxY = lprn->BlockLine / lprn->nBh * lprn->nBh;

    if (!(lprn->ImageBuf = gs_malloc(pdev->memory->non_gc_memory, bpl, maxY, "lprn_print_image(ImageBuf)")))
        return_error(gs_error_VMerror);
    if (!(lprn->TmpBuf = gs_malloc(pdev->memory->non_gc_memory, bpl, maxY, "lprn_print_iamge(TmpBuf)")))
        return_error(gs_error_VMerror);
    if (!(lprn->bubbleTbl = gs_malloc(pdev->memory->non_gc_memory, sizeof(Bubble *), maxBx, "lprn_print_image(bubbleTbl)")))
        return_error(gs_error_VMerror);
    if (!(bubbleBuffer = gs_malloc(pdev->memory->non_gc_memory, sizeof(Bubble), maxBx, "lprn_print_image(bubbleBuffer)")))
        return_error(gs_error_VMerror);

    for (i = 0; i < maxBx; i++)
        lprn->bubbleTbl[i] = NULL;
    bbtbl = bubbleBuffer;

    for (i = 0; i < maxBx - 1; i++)
        bbtbl[i].next = &bbtbl[i + 1];
    bbtbl[i].next = NULL;
    lprn->freeBubbleList = &bbtbl[0];

    for (y = 0; y < maxBy; y++) {
        if (num_y_blocks + lprn->nBh > maxY) {	/* bubble flush for next data */
            rmin = start_y_block + lprn->nBh;	/* process the data under rmin */
            for (i = 0; i < maxBx; i++) {
                bbl = lprn->bubbleTbl[i];
                if (bbl != NULL && bbl->brect.p.y < rmin)
                    lprn_bubble_flush(pdev, fp, bbl);
            }
            num_y_blocks -= lprn->nBh;	/* data if keeps in [r:r+h] */
            start_y_block += lprn->nBh;

        }
        ri = start_y_block + num_y_blocks;	/* read position */
        read_y = ri % maxY;	/* end of read position */
        code = gdev_prn_copy_scan_lines(pdev, ri, lprn->ImageBuf + bpl * read_y, bpl * lprn->nBh);
        if (code < 0)
            return code;

        num_y_blocks += lprn->nBh;

        lprn_process_line(pdev, fp, start_y_block, num_y_blocks);
    }
    lprn_bubble_flush_all(pdev, fp);	/* flush the rest of bubble */

    gs_free(pdev->memory->non_gc_memory, lprn->ImageBuf, bpl, maxY, "lprn_print_image(ImageBuf)");
    gs_free(pdev->memory->non_gc_memory, lprn->TmpBuf, bpl, maxY, "lprn_print_iamge(TmpBuf)");
    gs_free(pdev->memory->non_gc_memory, lprn->bubbleTbl, sizeof(Bubble *), maxBx, "lprn_print_image(bubbleTbl)");
    gs_free(pdev->memory->non_gc_memory, bubbleBuffer, sizeof(Bubble), maxBx, "lprn_print_image(bubbleBuffer)");

    return code;
}


lprn_bubble_gen(gx_device_printer * pdev, int x0, int x1, int y0, int y1)
{
    gx_device_lprn *const lprn = (gx_device_lprn *) pdev;

    Bubble *bbl;
    int bx0, bx1, bx;

    assert(lprn->freeBubbleList != NULL);

    bbl = lprn->freeBubbleList;
    lprn->freeBubbleList = bbl->next;

    bbl->brect.p.x = x0;
    bbl->brect.q.x = x1;
    bbl->brect.p.y = y0;
    bbl->brect.q.y = y1;

    bx0 = x0 / lprn->nBw;
    bx1 = (x1 + lprn->nBw - 1) / lprn->nBw;

    for (bx = bx0; bx <= bx1; bx++) {
        assert(lprn->bubbleTbl[bx] == NULL);
        lprn->bubbleTbl[bx] = bbl;
    }
}


lprn_bubble_flush(gx_device_printer * pdev, gp_file * fp, Bubble * bbl)
{
    gx_device_lprn *const lprn = (gx_device_lprn *) pdev;
    int i, j, bx;
    byte *p;
    int bx0 = bbl->brect.p.x / lprn->nBw;
    int bx1 = (bbl->brect.q.x + lprn->nBw - 1) / lprn->nBw;
    int bpl = gdev_mem_bytes_per_scan_line(pdev);
    int x = bbl->brect.p.x * 8;
    int y = bbl->brect.p.y;
    int width = (bbl->brect.q.x - bbl->brect.p.x + 1) * 8;
    int height = bbl->brect.q.y - bbl->brect.p.y + 1;
    int maxY = lprn->BlockLine / lprn->nBh * lprn->nBh;

    for (i = 0; i < height; i++) {
        p = lprn->ImageBuf + ((i + y) % maxY) * bpl;
        for (j = 0; j < width / 8; j++) {
            if (lprn->NegativePrint)
                *(lprn->TmpBuf + i * width / 8 + j) = ~*(p + j + bbl->brect.p.x);
            else
                *(lprn->TmpBuf + i * width / 8 + j) = *(p + j + bbl->brect.p.x);
        }
    }

    (*lprn->image_out) (pdev, fp, x, y, width, height);

    /* Initialize bubbleTbl */
    for (bx = bx0; bx <= bx1; bx++) {
        assert(lprn->bubbleTbl[bx] == bbl);
        lprn->bubbleTbl[bx] = NULL;
    }

    bbl->next = lprn->freeBubbleList;
    lprn->freeBubbleList = bbl;
}


lprn_process_line(gx_device_printer * pdev, gp_file * fp, int r, int h)
{
    gx_device_lprn *const lprn = (gx_device_lprn *) pdev;

    int bx, bInBlack, bBlack, start;
    int bpl = gdev_mem_bytes_per_scan_line(pdev);
    int maxBx = (bpl + lprn->nBw - 1) / lprn->nBw;

    bInBlack = 0;
    for (bx = 0; bx < maxBx; bx++) {
        bBlack = lprn_is_black(pdev, r, h, bx);
        if (!bInBlack) {
            if (bBlack) {
                start = bx;
                bInBlack = 1;
            }
        } else {
            if (!bBlack) {
                bInBlack = 0;
                lprn_rect_add(pdev, fp, r, h, start, bx);
            }
        }
    }
    if (bInBlack)
        lprn_rect_add(pdev, fp, r, h, start, bx - 1);
}


lprn_get_params(gx_device * dev, gs_param_list * plist)
{
    gx_device_lprn *const lprn = (gx_device_lprn *) dev;
    int code = gdev_prn_get_params(dev, plist);
    int ncode;

    if (code < 0)
        return code;

    if ((ncode = param_write_bool(plist, "ManualFeed", &lprn->ManualFeed)) < 0)
        code = ncode;

    if ((ncode = param_write_bool(plist, "NegativePrint", &lprn->NegativePrint)) < 0)
        code = ncode;

    if ((ncode = param_write_bool(plist, "Tumble", &lprn->Tumble)) < 0)
        code = ncode;

    if ((ncode = param_write_bool(plist, "RITOff", &lprn->RITOff)) < 0)
        code = ncode;

    if ((ncode = param_write_int(plist, "BlockLine", &lprn->BlockLine)) < 0)
        code = ncode;

    if ((ncode = param_write_int(plist, "BlockWidth", &lprn->nBw)) < 0)
        code = ncode;

    if ((ncode = param_write_int(plist, "BlockHeight", &lprn->nBh)) < 0)
        code = ncode;

    if ((ncode = param_write_bool(plist, "ShowBubble", &lprn->ShowBubble)) < 0)
        code = ncode;

    return code;
}


lprn_rect_add(gx_device_printer * pdev, gp_file * fp, int r, int h, int start, int end)
{
    gx_device_lprn *const lprn = (gx_device_lprn *) pdev;

    int x0 = start * lprn->nBw;
    int x1 = end * lprn->nBw - 1;
    int y0 = r + h - lprn->nBh;
    int y1 = y0 + lprn->nBh - 1;
    int i;
    Bubble *bbl;

    if ((bbl = lprn->bubbleTbl[start]) != NULL &&
        bbl->brect.q.y == y0 - 1 &&
        bbl->brect.p.x == x0 &&
        bbl->brect.q.x == x1) {
        bbl->brect.q.y = y1;
    } else {
        for (i = start; i <= end; i++)
            if (lprn->bubbleTbl[i] != NULL)
                lprn_bubble_flush(pdev, fp, lprn->bubbleTbl[i]);
        lprn_bubble_gen(pdev, x0, x1, y0, y1);
    }
}


lprn_is_black(gx_device_printer * pdev, int r, int h, int bx)
{
    gx_device_lprn *const lprn = (gx_device_lprn *) pdev;

    int bh = lprn->nBh;
    int bpl = gdev_mem_bytes_per_scan_line(pdev);
    int x, y, y0;
    byte *p;
    int maxY = lprn->BlockLine / lprn->nBh * lprn->nBh;

    y0 = (r + h - bh) % maxY;
    for (y = 0; y < bh; y++) {
        p = &lprn->ImageBuf[(y0 + y) * bpl + bx * lprn->nBw];
        for (x = 0; x < lprn->nBw; x++) {
            /* bpl isn't necessarily a multiple of lprn->nBw, so
            we need to explicitly stop after the last byte in this
            line to avoid accessing either the next line's data or
            going off the end of our buffer completely. This avoids
            https://bugs.ghostscript.com/show_bug.cgi?id=701785. */
            if (bx * lprn->nBw + x >= bpl)  break;
            if (p[x] != 0)
                return 1;
        }
    }
    return 0;
}