Compressed message type is CompressedData which contains compression type (1 byte) followed by the uncompressed data size (4 bytes - exists only if data was compressed) followed by the compressed data If SPICE_USBREDIR_CAP_DATA_COMPRESS_LZ4 capability is available && data_size > COMPRESS_THRESHOLD && !AF_LOCAL data will be sent compressed otherwise data will be sent uncompressed (also if compression has failed) Update the required protocol to 0.12.12 Signed-off-by: Frediano Ziglio <fziglio@xxxxxxxxxx> Signed-off-by: Snir Sheriber <ssheribe@xxxxxxxxxx> --- configure.ac | 2 +- server/spicevmc.c | 140 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 132 insertions(+), 10 deletions(-) diff --git a/configure.ac b/configure.ac index c743875..856833b 100644 --- a/configure.ac +++ b/configure.ac @@ -98,7 +98,7 @@ AS_IF([test x"$have_smartcard" = "xyes"], [ AS_VAR_APPEND([SPICE_REQUIRES], [" libcacard >= 0.1.2"]) ]) -SPICE_PROTOCOL_MIN_VER=0.12.11 +SPICE_PROTOCOL_MIN_VER=0.12.12 PKG_CHECK_MODULES([SPICE_PROTOCOL], [spice-protocol >= $SPICE_PROTOCOL_MIN_VER]) AC_SUBST([SPICE_PROTOCOL_MIN_VER]) diff --git a/server/spicevmc.c b/server/spicevmc.c index b662d94..908ad5f 100644 --- a/server/spicevmc.c +++ b/server/spicevmc.c @@ -34,6 +34,9 @@ #include "red-channel.h" #include "reds.h" #include "migration-protocol.h" +#ifdef USE_LZ4 +#include <lz4.h> +#endif /* todo: add flow control. i.e., * (a) limit the tokens available for the client @@ -41,10 +44,13 @@ */ /* 64K should be enough for all but the largest writes + 32 bytes hdr */ #define BUF_SIZE (64 * 1024 + 32) +#define COMPRESS_THRESHOLD 1000 typedef struct RedVmcPipeItem { RedPipeItem base; + SpiceDataCompressionType type; + uint32_t uncompressed_data_size; /* writes which don't fit this will get split, this is not a problem */ uint8_t buf[BUF_SIZE]; uint32_t buf_used; @@ -105,6 +111,53 @@ enum { RED_PIPE_ITEM_TYPE_PORT_EVENT, }; +static void spicevmc_red_channel_release_msg_rcv_buf(RedChannelClient *rcc, + uint16_t type, + uint32_t size, + uint8_t *msg); +/* n is the data size (uncompressed) + * msg_item -- the current pipe item with the uncompressed data + * This function returns: + * - NULL upon failure. + * - a new pipe item with the compressed data in it upon success + */ +static RedVmcPipeItem* try_compress_lz4(SpiceVmcState *state, int n, RedVmcPipeItem *msg_item) +{ + RedVmcPipeItem *msg_item_compressed; + int compressed_data_count; + + if (reds_stream_get_family(state->rcc->stream) == AF_UNIX) { + /* AF_LOCAL - data will not be compressed */ + return NULL; + } + if (n <= COMPRESS_THRESHOLD) { + /* n <= threshold - data will not be compressed */ + return NULL; + } + if (!red_channel_test_remote_cap(&state->channel, SPICE_SPICEVMC_CAP_DATA_COMPRESS_LZ4)) { + /* Client doesn't have compression cap - data will not be compressed */ + return NULL; + } + msg_item_compressed = spice_new0(RedVmcPipeItem, 1); + red_pipe_item_init(&msg_item_compressed->base, RED_PIPE_ITEM_TYPE_SPICEVMC_DATA); + compressed_data_count = LZ4_compress_default((char*)&msg_item->buf, + (char*)&msg_item_compressed->buf, + n, + BUF_SIZE); + + if (compressed_data_count > 0 && compressed_data_count < n) { + msg_item_compressed->type = SPICE_DATA_COMPRESSION_TYPE_LZ4; + msg_item_compressed->uncompressed_data_size = n; + msg_item_compressed->buf_used = compressed_data_count; + free(msg_item); + return msg_item_compressed; + } + + /* LZ4 compression failed or did non compress, fallback a non-compressed data is to be sent */ + free(msg_item_compressed); + return NULL; +} + static RedPipeItem *spicevmc_chardev_read_msg_from_dev(SpiceCharDeviceInstance *sin, void *opaque) { @@ -121,6 +174,7 @@ static RedPipeItem *spicevmc_chardev_read_msg_from_dev(SpiceCharDeviceInstance * if (!state->pipe_item) { msg_item = spice_new0(RedVmcPipeItem, 1); + msg_item->type = SPICE_DATA_COMPRESSION_TYPE_NONE; red_pipe_item_init(&msg_item->base, RED_PIPE_ITEM_TYPE_SPICEVMC_DATA); } else { spice_assert(state->pipe_item->buf_used == 0); @@ -132,6 +186,15 @@ static RedPipeItem *spicevmc_chardev_read_msg_from_dev(SpiceCharDeviceInstance * sizeof(msg_item->buf)); if (n > 0) { spice_debug("read from dev %d", n); +#ifdef USE_LZ4 + RedVmcPipeItem *msg_item_compressed; + + msg_item_compressed = try_compress_lz4(state, n, msg_item); + if (msg_item_compressed != NULL) { + return &msg_item_compressed->base; + } +#endif + msg_item->uncompressed_data_size = n; msg_item->buf_used = n; return &msg_item->base; } else { @@ -275,11 +338,52 @@ static int spicevmc_channel_client_handle_migrate_data(RedChannelClient *rcc, return red_char_device_restore(state->chardev, &mig_data->base); } -static int spicevmc_red_channel_client_handle_message(RedChannelClient *rcc, - uint16_t type, - uint32_t size, - uint8_t *msg) +static int handle_compressed_msg(SpiceVmcState *state, RedChannelClient *rcc, + SpiceMsgCompressedData *compressed_data_msg) +{ + /* NOTE: *decompressed is free by the char-device */ + int decompressed_size; + uint8_t *decompressed; + RedCharDeviceWriteBuffer *write_buf; + + write_buf = red_char_device_write_buffer_get(state->chardev, rcc->client, + compressed_data_msg->uncompressed_size); + if (!write_buf) { + return FALSE; + } + decompressed = write_buf->buf; + + switch (compressed_data_msg->type) { +#ifdef USE_LZ4 + case SPICE_DATA_COMPRESSION_TYPE_LZ4: + decompressed_size = LZ4_decompress_safe ((char *)compressed_data_msg->compressed_data, + (char *)decompressed, + compressed_data_msg->compressed_size, + compressed_data_msg->uncompressed_size); + break; +#endif + default: + spice_warning("Invalid Compression Type"); + red_char_device_write_buffer_release(state->chardev, &write_buf); + return FALSE; + } + if (decompressed_size != compressed_data_msg->uncompressed_size) { + spice_warning("Decompression Error"); + red_char_device_write_buffer_release(state->chardev, &write_buf); + return FALSE; + } + write_buf->buf_used = decompressed_size; + red_char_device_write_buffer_add(state->chardev, write_buf); + return TRUE; +} + +static int spicevmc_red_channel_client_handle_message_parsed(RedChannelClient *rcc, + uint32_t size, + uint16_t type, + void *msg) { + /* NOTE: *msg free by free() (when cb to spicevmc_red_channel_release_msg_rcv_buf + * with the compressed msg type) */ SpiceVmcState *state; SpiceCharDeviceInterface *sif; @@ -293,16 +397,19 @@ static int spicevmc_red_channel_client_handle_message(RedChannelClient *rcc, red_char_device_write_buffer_add(state->chardev, state->recv_from_client_buf); state->recv_from_client_buf = NULL; break; + case SPICE_MSGC_SPICEVMC_COMPRESSED_DATA: + return handle_compressed_msg(state, rcc, (SpiceMsgCompressedData*)msg); + break; case SPICE_MSGC_PORT_EVENT: if (size != sizeof(uint8_t)) { spice_warning("bad port event message size"); return FALSE; } if (sif->base.minor_version >= 2 && sif->event != NULL) - sif->event(state->chardev_sin, *msg); + sif->event(state->chardev_sin, *(uint8_t*)msg); break; default: - return red_channel_client_handle_message(rcc, size, type, msg); + return red_channel_client_handle_message(rcc, size, type, (uint8_t*)msg); } return TRUE; @@ -360,7 +467,18 @@ static void spicevmc_red_channel_send_data(RedChannelClient *rcc, { RedVmcPipeItem *i = SPICE_UPCAST(RedVmcPipeItem, item); - red_channel_client_init_send_data(rcc, SPICE_MSG_SPICEVMC_DATA, item); + /* for compatibility send using not compressed data message */ + if (i->type == SPICE_DATA_COMPRESSION_TYPE_NONE) { + red_channel_client_init_send_data(rcc, SPICE_MSG_SPICEVMC_DATA, item); + } else { + /* send as compressed */ + red_channel_client_init_send_data(rcc, SPICE_MSG_SPICEVMC_COMPRESSED_DATA, item); + SpiceMsgCompressedData compressed_msg = { + .type = i->type, + .uncompressed_size = i->uncompressed_data_size + }; + spice_marshall_SpiceMsgCompressedData(m, &compressed_msg); + } spice_marshaller_add_ref(m, i->buf, i->buf_used); } @@ -494,16 +612,20 @@ RedCharDevice *spicevmc_device_connect(RedsState *reds, channel_cbs.handle_migrate_flush_mark = spicevmc_channel_client_handle_migrate_flush_mark; channel_cbs.handle_migrate_data = spicevmc_channel_client_handle_migrate_data; - state = (SpiceVmcState*)red_channel_create(sizeof(SpiceVmcState), reds, + state = (SpiceVmcState*)red_channel_create_parser(sizeof(SpiceVmcState), reds, reds_get_core_interface(reds), channel_type, id[channel_type]++, FALSE /* handle_acks */, - spicevmc_red_channel_client_handle_message, + spice_get_client_channel_parser(SPICE_CHANNEL_USBREDIR, NULL), + spicevmc_red_channel_client_handle_message_parsed, &channel_cbs, SPICE_MIGRATE_NEED_FLUSH | SPICE_MIGRATE_NEED_DATA_TRANSFER); red_channel_init_outgoing_messages_window(&state->channel); client_cbs.connect = spicevmc_connect; red_channel_register_client_cbs(&state->channel, &client_cbs, NULL); +#ifdef USE_LZ4 + red_channel_set_cap(&state->channel, SPICE_SPICEVMC_CAP_DATA_COMPRESS_LZ4); +#endif state->chardev = red_char_device_spicevmc_new(sin, reds, state); state->chardev_sin = sin; -- 2.5.5 _______________________________________________ Spice-devel mailing list Spice-devel@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/spice-devel