[Spice-commits] 5 commits - server/smartcard.c server/tests

GitLab Mirror gitlab-mirror at kemper.freedesktop.org
Wed Oct 9 09:33:58 UTC 2019


 server/smartcard.c                |   27 +-
 server/tests/.gitignore           |    1 
 server/tests/Makefile.am          |    6 
 server/tests/meson.build          |    6 
 server/tests/test-smartcard.c     |  379 ++++++++++++++++++++++++++++++++++++++
 server/tests/test-stream-device.c |  224 ++++++----------------
 server/tests/vmc-emu.c            |  124 ++++++++++++
 server/tests/vmc-emu.h            |   51 +++++
 8 files changed, 648 insertions(+), 170 deletions(-)

New commits:
commit d8d5c48ad66b2fc43b4c0c42d62d01c580d2a7c4
Author: Frediano Ziglio <fziglio at redhat.com>
Date:   Tue Oct 8 15:27:13 2019 +0100

    test-smardcard: Improve test coverage
    
    Using coverage utility exercise more code paths:
    - message from channel with wrong type;
    - remove message from channel with already removed reader;
    - init message from channel (ignored);
    - data from devices, ADPU;
    - error from device;
    - messages split in different ways;
    - invalid reader_id values.
    
    Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
    Acked-by: Victor Toso <victortoso at redhat.com>

diff --git a/server/tests/test-smartcard.c b/server/tests/test-smartcard.c
index aac2e794..c1e4786d 100644
--- a/server/tests/test-smartcard.c
+++ b/server/tests/test-smartcard.c
@@ -41,6 +41,15 @@ static SpiceCoreInterface *core;
 static Test *test;
 static VmcEmu *vmc;
 typedef int TestFixture;
+static int client_socket = -1;
+// buffer when data from channel are stored
+static SpiceBuffer channel_buf;
+// expected buffer in channel
+static SpiceBuffer channel_expected;
+// expected buffer in device
+static SpiceBuffer device_expected;
+
+static void next_test(void);
 
 static void test_smartcard_setup(TestFixture *fixture, gconstpointer user_data)
 {
@@ -101,7 +110,7 @@ static void send_ack_sync(int socket, uint32_t generation)
     g_assert_cmpint(socket_write(socket, &msg.type, 10), ==, 10);
 }
 
-static void send_data(int socket, uint32_t type)
+static void send_data(int socket, uint32_t type, uint32_t reader_id)
 {
     struct {
         uint16_t dummy;
@@ -114,24 +123,184 @@ static void send_data(int socket, uint32_t type)
     msg.type = GUINT16_TO_LE(SPICE_MSGC_SMARTCARD_DATA);
     msg.len = GUINT32_TO_LE(sizeof(VSCMsgHeader)+6);
     msg.vheader.type = GUINT32_TO_LE(type);
-    msg.vheader.reader_id = 0;
+    msg.vheader.reader_id = GUINT32_TO_LE(reader_id);
     msg.vheader.length = GUINT32_TO_LE(6);
     strcpy(msg.data, "hello");
 
     g_assert_cmpint(socket_write(socket, &msg.type, sizeof(msg)-4), ==, sizeof(msg)-4);
 }
 
-static void check_data(void *opaque)
+static void check_data(VmcEmu *vmc)
+{
+    g_assert_cmpint(device_expected.offset, !=, 0);
+    if (vmc->write_pos < device_expected.offset) {
+        return;
+    }
+    g_assert_cmpint(vmc->write_pos, ==, device_expected.offset);
+    g_assert_true(memcmp(vmc->write_buf, device_expected.buffer, device_expected.offset) == 0);
+    vmc->write_pos = 0;
+
+    next_test();
+}
+
+static void data_from_channel(int fd, int event, void *opaque)
+{
+    uint8_t buf[128];
+    ssize_t ret = socket_read(fd, buf, sizeof(buf));
+    if (ret <= 0) {
+        return;
+    }
+    spice_buffer_append(&channel_buf, buf, ret);
+
+    g_assert_cmpint(channel_expected.offset, !=, 0);
+    if (channel_buf.offset < channel_expected.offset) {
+        return;
+    }
+    g_assert_true(memcmp(channel_buf.buffer, channel_expected.buffer, channel_expected.offset) == 0);
+    spice_buffer_remove(&channel_buf, channel_expected.offset);
+
+    next_test();
+}
+
+static void next_test(void)
 {
-    static const char expected_buf[] =
-        // forwarded ReaderAdd message, note that payload is stripped
-        "\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00"
-        // forwarded APDU message
-        "\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x06\x68\x65\x6c\x6c\x6f\x00";
-    const size_t expected_buf_len = sizeof(expected_buf) - 1;
-    g_assert_cmpint(vmc->write_pos, ==, expected_buf_len);
-    g_assert_true(memcmp(vmc->write_buf, expected_buf, expected_buf_len) == 0);
-    basic_event_loop_quit();
+    static int test_num;
+
+    test_num++;
+    printf("Executing subtest %d\n", test_num);
+
+    spice_buffer_reset(&channel_expected);
+    spice_buffer_reset(&device_expected);
+
+    switch (test_num) {
+    // First test, send some message to channel expecting a reply
+    // for each message we are sending
+    case 1: {
+        static const char expected_buf[] =
+            // forwarded ReaderAdd message, note that payload is stripped
+            "\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00"
+            // forwarded APDU message
+            "\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x06\x68\x65\x6c\x6c\x6f\x00"
+            "\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00";
+        spice_buffer_append(&device_expected, expected_buf, sizeof(expected_buf) - 1);
+
+        send_data(client_socket, VSC_ReaderAdd, 0);
+        send_data(client_socket, VSC_APDU, 0);
+        send_data(client_socket, VSC_ReaderRemove, 0);
+        } break;
+    // Second test, send an init and remove a reader that is not present,
+    // we expect an error for the removal (the Init is ignored)
+    case 2: {
+        static const char expected_buf[] =
+            // forwarded Error message
+            "\x65\x00\x10\x00\x00\x00"
+            "\x02\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00";
+        spice_buffer_append(&channel_expected, expected_buf, sizeof(expected_buf) - 1);
+
+        // Init message, ignored
+        send_data(client_socket, VSC_Init, 0);
+        // remove again, this will trigger an error
+        send_data(client_socket, VSC_ReaderRemove, 0);
+        } break;
+    // Third test, APDU messages from device are forwarded to the channel.
+    // We split the header and payload of the first message to check device code can handle it.
+    // The second message is send inside a block with the end of the first to trigger
+    // an hard path in the device code
+    case 3: {
+        static const char expected_buf[] =
+            // forwarded APDU message
+            "\x65\x00\x12\x00\x00\x00"
+            "\x07\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00" "foobaz"
+            "\x65\x00\x12\x00\x00\x00"
+            "\x07\x00\x00\x00\x00\x00\x00\x00\x06\x00\x00\x00" "foobar";
+        spice_buffer_append(&channel_expected, expected_buf, sizeof(expected_buf) - 1);
+
+        vmc_emu_reset(vmc);
+        // data from device
+        uint8_t *p = vmc->message;
+
+        // add VSC_APDU message
+        memcpy(p, "\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x06" "foobaz", 18);
+        p += 18;
+        memcpy(p, "\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x06" "foobar", 18);
+        p += 18;
+        vmc_emu_add_read_till(vmc, vmc->message + 8);
+        vmc_emu_add_read_till(vmc, vmc->message + 14);
+        vmc_emu_add_read_till(vmc, p);
+
+        spice_server_char_device_wakeup(&vmc->instance);
+        } break;
+    // Fourth test, we should get back an error if client tried to remove
+    // a not existing reader
+    case 4: {
+        static const char expected_buf[] =
+            // forwarded Error message
+            "\x65\x00\x10\x00\x00\x00"
+            "\x02\x00\x00\x00\x05\x00\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00";
+        spice_buffer_append(&channel_expected, expected_buf, sizeof(expected_buf) - 1);
+
+        // remove invalid, this will trigger an error
+        send_data(client_socket, VSC_ReaderRemove, 5);
+        } break;
+    // Fifth test, similar to previous but using an huge reader_id field to trigger
+    // possible buffer overflow
+    case 5: {
+        static const char expected_buf[] =
+            // forwarded Error message
+            "\x65\x00\x10\x00\x00\x00"
+            "\x02\x00\x00\x00\x05\x01\x00\x00\x04\x00\x00\x00\x01\x00\x00\x00";
+        spice_buffer_append(&channel_expected, expected_buf, sizeof(expected_buf) - 1);
+
+        // remove invalid and huge, this will trigger an error, should not crash
+        send_data(client_socket, VSC_ReaderRemove, 261);
+        } break;
+    // Sixth test, send an invalid message from client, a log is triggered
+    // but channel continues to work
+    case 6: {
+        static const char expected_buf[] =
+            // forwarded APDU message
+            "\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x06\x68\x65\x6c\x6c\x6f\x00";
+        spice_buffer_append(&device_expected, expected_buf, sizeof(expected_buf) - 1);
+
+        g_test_expect_message(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING,
+                              "*ERROR: unexpected message on smartcard channel*");
+
+        // invalid message type, should log a warning
+        send_data(client_socket, 0xabcd, 0);
+        // APDU just to get an event
+        send_data(client_socket, VSC_APDU, 0);
+        } break;
+    // Seventh test, an Error message from device are forwarded to the channel.
+    // Note that the header is in big endian order while the error from device
+    // is in little endian order. This seems weird but it's correct with the
+    // current libcacard implementation which just send error as host order
+    case 7: {
+        g_test_assert_expected_messages();
+
+        static const char expected_buf[] =
+            // forwarded Error message
+            "\x65\x00\x10\x00\x00\x00"
+            "\x02\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x0a\x0b\x0c\x0d";
+        spice_buffer_append(&channel_expected, expected_buf, sizeof(expected_buf) - 1);
+
+        vmc_emu_reset(vmc);
+        // data from device
+        uint8_t *p = vmc->message;
+
+        // add Error message
+        memcpy(p, "\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x04\x0a\x0b\x0c\x0d", 16);
+        p += 16;
+        vmc_emu_add_read_till(vmc, p);
+
+        spice_server_char_device_wakeup(&vmc->instance);
+        } break;
+    case 8:
+        g_test_assert_expected_messages();
+        basic_event_loop_quit();
+        break;
+    default:
+        abort();
+    }
 }
 
 static void test_smartcard(TestFixture *fixture, gconstpointer user_data)
@@ -144,6 +313,7 @@ static void test_smartcard(TestFixture *fixture, gconstpointer user_data)
     // add VSC_Init message
     memcpy(p, "\x00\x00\x00\x01\x0a\x0b\x0c\x0d\x00\x00\x00\x00", 12);
     p += 12;
+    vmc_emu_add_read_till(vmc, vmc->message + 2); // check header is decoded correctly when split
     vmc_emu_add_read_till(vmc, p);
 
     // find Smartcard channel to connect to
@@ -170,7 +340,6 @@ static void test_smartcard(TestFixture *fixture, gconstpointer user_data)
     red_client_set_main(client, mcc);
 
     // create our testing RedChannelClient
-    int client_socket;
     red_channel_connect(channel, client, create_dummy_stream(server, &client_socket),
                         FALSE, &caps);
     red_channel_capabilities_reset(&caps);
@@ -180,21 +349,20 @@ static void test_smartcard(TestFixture *fixture, gconstpointer user_data)
 
     // push data into channel
     send_ack_sync(client_socket, 1);
-    send_data(client_socket, VSC_ReaderAdd);
-    send_data(client_socket, VSC_APDU);
 
-    // check data are processed after a short time
-    SpiceTimer *watch_timer;
-    watch_timer = core->timer_add(check_data, core);
-    core->timer_start(watch_timer, 100);
+    // check data are processed
+    SpiceWatch *watch = core->watch_add(client_socket, SPICE_WATCH_EVENT_READ,
+                                        data_from_channel, NULL);
+    vmc->data_written_cb = check_data;
 
     // start all test
     alarm(10);
+    next_test();
     basic_event_loop_mainloop();
     alarm(0);
 
     // cleanup
-    core->timer_remove(watch_timer);
+    core->watch_remove(watch);
     red_client_destroy(client);
     g_object_unref(main_channel);
     g_object_unref(channel);
diff --git a/server/tests/vmc-emu.c b/server/tests/vmc-emu.c
index 418a0213..f64ddc37 100644
--- a/server/tests/vmc-emu.c
+++ b/server/tests/vmc-emu.c
@@ -31,6 +31,9 @@ static int vmc_write(SpiceCharDeviceInstance *sin,
     unsigned copy = MIN(sizeof(vmc->write_buf) - vmc->write_pos, len);
     memcpy(vmc->write_buf+vmc->write_pos, buf, copy);
     vmc->write_pos += copy;
+    if (copy && vmc->data_written_cb) {
+        vmc->data_written_cb(vmc);
+    }
     return len;
 }
 
diff --git a/server/tests/vmc-emu.h b/server/tests/vmc-emu.h
index 7c26938e..93cdcc10 100644
--- a/server/tests/vmc-emu.h
+++ b/server/tests/vmc-emu.h
@@ -40,6 +40,9 @@ struct VmcEmu {
 
     unsigned write_pos;
     uint8_t write_buf[2048];
+
+    // this callback will be called when new data arrive to the device
+    void (*data_written_cb)(VmcEmu *vmc);
 };
 
 VmcEmu *vmc_emu_new(const char *subtype, const char *portname);
commit 344ce666cf774b3bf0d2adda3180a09c12e81d95
Author: Frediano Ziglio <fziglio at redhat.com>
Date:   Fri Oct 4 21:28:06 2019 +0100

    test-smartcard: Add test for Smartcard device
    
    Create Smardcard device.
    Connect to it and test some messages are parsed and processed
    as expected.
    
    Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
    Acked-by: Victor Toso <victortoso at redhat.com>

diff --git a/server/tests/.gitignore b/server/tests/.gitignore
index 36e978d4..56cc7eb9 100644
--- a/server/tests/.gitignore
+++ b/server/tests/.gitignore
@@ -29,5 +29,6 @@ test-leaks
 test-sasl
 test-record
 test-websocket
+test-smartcard
 /test-*.log
 /test-*.trs
diff --git a/server/tests/Makefile.am b/server/tests/Makefile.am
index 98250851..dd285c24 100644
--- a/server/tests/Makefile.am
+++ b/server/tests/Makefile.am
@@ -68,6 +68,10 @@ check_PROGRAMS =				\
 	test-record				\
 	$(NULL)
 
+if HAVE_SMARTCARD
+check_PROGRAMS += test-smartcard
+endif
+
 if !OS_WIN32
 check_PROGRAMS +=				\
 	test-stream				\
diff --git a/server/tests/meson.build b/server/tests/meson.build
index 33472f14..95ade60f 100644
--- a/server/tests/meson.build
+++ b/server/tests/meson.build
@@ -65,6 +65,10 @@ if spice_server_has_sasl
   tests += [['test-sasl', true]]
 endif
 
+if spice_server_has_smartcard == true
+  tests += [['test-smartcard', true]]
+endif
+
 if host_machine.system() != 'windows'
   tests += [
     ['test-stream', true],
diff --git a/server/tests/test-smartcard.c b/server/tests/test-smartcard.c
new file mode 100644
index 00000000..aac2e794
--- /dev/null
+++ b/server/tests/test-smartcard.c
@@ -0,0 +1,211 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2019 Red Hat, Inc.
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+/**
+ * Test Smartcard device and channel
+ */
+
+#include <config.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <unistd.h>
+
+#include <spice/protocol.h>
+#include <spice/stream-device.h>
+
+#include "test-display-base.h"
+#include "test-glib-compat.h"
+#include "reds.h"
+#include "vmc-emu.h"
+#include "red-client.h"
+#include "net-utils.h"
+#include "win-alarm.h"
+
+static SpiceCoreInterface *core;
+static Test *test;
+static VmcEmu *vmc;
+typedef int TestFixture;
+
+static void test_smartcard_setup(TestFixture *fixture, gconstpointer user_data)
+{
+    g_assert_null(core);
+    g_assert_null(test);
+    g_assert_null(vmc);
+    core = basic_event_loop_init();
+    g_assert_nonnull(core);
+    test = test_new(core);
+    g_assert_nonnull(test);
+    vmc = vmc_emu_new("smartcard", NULL);
+    g_assert_nonnull(vmc);
+}
+
+static void test_smartcard_teardown(TestFixture *fixture, gconstpointer user_data)
+{
+    g_assert_nonnull(core);
+    g_assert_nonnull(test);
+    g_assert_nonnull(vmc);
+
+    vmc_emu_destroy(vmc);
+    vmc = NULL;
+    test_destroy(test);
+    test = NULL;
+    basic_event_loop_destroy();
+    core = NULL;
+}
+
+static RedStream *create_dummy_stream(SpiceServer *server, int *p_socket)
+{
+    int sv[2];
+    g_assert_cmpint(socketpair(AF_LOCAL, SOCK_STREAM, 0, sv), ==, 0);
+    if (p_socket) {
+        *p_socket = sv[1];
+    }
+    red_socket_set_non_blocking(sv[0], true);
+    red_socket_set_non_blocking(sv[1], true);
+
+    RedStream * stream = red_stream_new(server, sv[0]);
+    g_assert_nonnull(stream);
+
+    return stream;
+}
+
+static void send_ack_sync(int socket, uint32_t generation)
+{
+    struct {
+        uint16_t dummy;
+        uint16_t type;
+        uint32_t len;
+        uint32_t generation;
+    } msg;
+    SPICE_VERIFY(sizeof(msg) == 12);
+    msg.type = GUINT16_TO_LE(SPICE_MSGC_ACK_SYNC);
+    msg.len = GUINT32_TO_LE(sizeof(generation));
+    msg.generation = GUINT32_TO_LE(generation);
+
+    g_assert_cmpint(socket_write(socket, &msg.type, 10), ==, 10);
+}
+
+static void send_data(int socket, uint32_t type)
+{
+    struct {
+        uint16_t dummy;
+        uint16_t type;
+        uint32_t len;
+        VSCMsgHeader vheader;
+        char data[6];
+    } msg;
+    SPICE_VERIFY(sizeof(msg) == 8+12+8);
+    msg.type = GUINT16_TO_LE(SPICE_MSGC_SMARTCARD_DATA);
+    msg.len = GUINT32_TO_LE(sizeof(VSCMsgHeader)+6);
+    msg.vheader.type = GUINT32_TO_LE(type);
+    msg.vheader.reader_id = 0;
+    msg.vheader.length = GUINT32_TO_LE(6);
+    strcpy(msg.data, "hello");
+
+    g_assert_cmpint(socket_write(socket, &msg.type, sizeof(msg)-4), ==, sizeof(msg)-4);
+}
+
+static void check_data(void *opaque)
+{
+    static const char expected_buf[] =
+        // forwarded ReaderAdd message, note that payload is stripped
+        "\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00"
+        // forwarded APDU message
+        "\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00\x06\x68\x65\x6c\x6c\x6f\x00";
+    const size_t expected_buf_len = sizeof(expected_buf) - 1;
+    g_assert_cmpint(vmc->write_pos, ==, expected_buf_len);
+    g_assert_true(memcmp(vmc->write_buf, expected_buf, expected_buf_len) == 0);
+    basic_event_loop_quit();
+}
+
+static void test_smartcard(TestFixture *fixture, gconstpointer user_data)
+{
+    SpiceServer *const server = test->server;
+    uint8_t *p = vmc->message;
+
+    spice_server_add_interface(server, &vmc->instance.base);
+
+    // add VSC_Init message
+    memcpy(p, "\x00\x00\x00\x01\x0a\x0b\x0c\x0d\x00\x00\x00\x00", 12);
+    p += 12;
+    vmc_emu_add_read_till(vmc, p);
+
+    // find Smartcard channel to connect to
+    RedChannel *channel = reds_find_channel(server, SPICE_CHANNEL_SMARTCARD, 0);
+    g_assert_nonnull(channel);
+
+    // create dummy RedClient and MainChannelClient
+    RedChannelCapabilities caps;
+    memset(&caps, 0, sizeof(caps));
+    uint32_t common_caps = 1 << SPICE_COMMON_CAP_MINI_HEADER;
+    caps.num_common_caps = 1;
+    caps.common_caps = spice_memdup(&common_caps, sizeof(common_caps));
+
+    RedClient *client = red_client_new(server, FALSE);
+    g_assert_nonnull(client);
+
+    MainChannel *main_channel = main_channel_new(server);
+    g_assert_nonnull(main_channel);
+
+    MainChannelClient *mcc;
+    mcc = main_channel_link(main_channel, client, create_dummy_stream(server, NULL),
+                            0, FALSE, &caps);
+    g_assert_nonnull(mcc);
+    red_client_set_main(client, mcc);
+
+    // create our testing RedChannelClient
+    int client_socket;
+    red_channel_connect(channel, client, create_dummy_stream(server, &client_socket),
+                        FALSE, &caps);
+    red_channel_capabilities_reset(&caps);
+
+    // push data to device
+    spice_server_char_device_wakeup(&vmc->instance);
+
+    // push data into channel
+    send_ack_sync(client_socket, 1);
+    send_data(client_socket, VSC_ReaderAdd);
+    send_data(client_socket, VSC_APDU);
+
+    // check data are processed after a short time
+    SpiceTimer *watch_timer;
+    watch_timer = core->timer_add(check_data, core);
+    core->timer_start(watch_timer, 100);
+
+    // start all test
+    alarm(10);
+    basic_event_loop_mainloop();
+    alarm(0);
+
+    // cleanup
+    core->timer_remove(watch_timer);
+    red_client_destroy(client);
+    g_object_unref(main_channel);
+    g_object_unref(channel);
+}
+
+int main(int argc, char *argv[])
+{
+    g_test_init(&argc, &argv, NULL);
+
+    g_test_add("/server/smartcard", TestFixture, NULL, test_smartcard_setup,
+               test_smartcard, test_smartcard_teardown);
+
+    return g_test_run();
+}
commit b4e508880a4bfd11c6672cde2036ce73a3fd008d
Author: Frediano Ziglio <fziglio at redhat.com>
Date:   Mon Oct 7 09:57:58 2019 +0100

    test-stream-device: Factor out VMC emulation
    
    Allows to reuse code for emulating a character device.
    It will be used for Smardcard test.
    
    Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
    Acked-by: Victor Toso <victortoso at redhat.com>

diff --git a/server/tests/Makefile.am b/server/tests/Makefile.am
index 1e62557a..98250851 100644
--- a/server/tests/Makefile.am
+++ b/server/tests/Makefile.am
@@ -37,6 +37,8 @@ libtest_a_SOURCES =				\
 	test-glib-compat.h			\
 	win-alarm.c				\
 	win-alarm.h				\
+	vmc-emu.c				\
+	vmc-emu.h				\
 	$(NULL)
 
 LDADD =								\
diff --git a/server/tests/meson.build b/server/tests/meson.build
index c9377f1e..33472f14 100644
--- a/server/tests/meson.build
+++ b/server/tests/meson.build
@@ -13,6 +13,8 @@ test_lib_sources = [
   'test-glib-compat.h',
   'win-alarm.c',
   'win-alarm.h',
+  'vmc-emu.c',
+  'vmc-emu.h',
 ]
 
 test_libs = []
diff --git a/server/tests/test-stream-device.c b/server/tests/test-stream-device.c
index a5e85f11..abf66f04 100644
--- a/server/tests/test-stream-device.c
+++ b/server/tests/test-stream-device.c
@@ -34,84 +34,9 @@
 #include "stream-channel.h"
 #include "reds.h"
 #include "win-alarm.h"
+#include "vmc-emu.h"
 
-static SpiceCharDeviceInstance vmc_instance;
-
-// device buffer to read from
-static uint8_t message[2048];
-// position to read from
-static unsigned pos;
-// array of limits when the read should return
-// the array is defined as [message_sizes_curr, message_sizes_end)
-// then the size is reach we move on next one till exausted
-static unsigned message_sizes[16];
-static unsigned *message_sizes_end, *message_sizes_curr;
-static bool device_enabled = false;
-
-static unsigned vmc_write_pos;
-static uint8_t vmc_write_buf[2048];
-
-// handle writes to the device
-static int vmc_write(SPICE_GNUC_UNUSED SpiceCharDeviceInstance *sin,
-                     SPICE_GNUC_UNUSED const uint8_t *buf,
-                     int len)
-{
-    // just copy into the buffer
-    unsigned copy = MIN(sizeof(vmc_write_buf) - vmc_write_pos, len);
-    memcpy(vmc_write_buf+vmc_write_pos, buf, copy);
-    vmc_write_pos += copy;
-    return len;
-}
-
-static int vmc_read(SPICE_GNUC_UNUSED SpiceCharDeviceInstance *sin,
-                    uint8_t *buf,
-                    int len)
-{
-    int ret;
-
-    if (pos >= *message_sizes_curr && message_sizes_curr < message_sizes_end) {
-        ++message_sizes_curr;
-    }
-    if (message_sizes_curr >= message_sizes_end || pos >= *message_sizes_curr) {
-        return 0;
-    }
-    ret = MIN(*message_sizes_curr - pos, len);
-    memcpy(buf, &message[pos], ret);
-    pos += ret;
-    // kick off next message read
-    // currently Qemu kicks the device so we need to do it manually
-    // here. If not all data are read, the device goes into blocking
-    // state and we get the wake only when we read from the device
-    // again
-    if (pos >= *message_sizes_curr) {
-        spice_server_char_device_wakeup(&vmc_instance);
-    }
-    return ret;
-}
-
-static void vmc_state(SPICE_GNUC_UNUSED SpiceCharDeviceInstance *sin,
-                      SPICE_GNUC_UNUSED int connected)
-{
-    device_enabled = !!connected;
-}
-
-static SpiceCharDeviceInterface vmc_interface = {
-    .base = {
-        .type          = SPICE_INTERFACE_CHAR_DEVICE,
-        .description   = "test spice virtual channel char device",
-        .major_version = SPICE_INTERFACE_CHAR_DEVICE_MAJOR,
-        .minor_version = SPICE_INTERFACE_CHAR_DEVICE_MINOR,
-    },
-    .state              = vmc_state,
-    .write              = vmc_write,
-    .read               = vmc_read,
-};
-
-// this specifically creates a stream device
-static SpiceCharDeviceInstance vmc_instance = {
-    .subtype = "port",
-    .portname = "org.spice-space.stream.0",
-};
+static VmcEmu *vmc;
 
 static uint8_t *add_stream_hdr(uint8_t *p, StreamMsgType type, uint32_t size)
 {
@@ -145,18 +70,18 @@ discard_server_capabilities(void)
 {
     StreamDevHeader hdr;
 
-    if (vmc_write_pos == 0) {
+    if (vmc->write_pos == 0) {
         return;
     }
-    g_assert(vmc_write_pos >= sizeof(hdr));
+    g_assert(vmc->write_pos >= sizeof(hdr));
 
-    memcpy(&hdr, vmc_write_buf, sizeof(hdr));
+    memcpy(&hdr, vmc->write_buf, sizeof(hdr));
     hdr.type = GUINT16_FROM_LE(hdr.type);
     hdr.size = GUINT32_FROM_LE(hdr.size);
     if (hdr.type == STREAM_TYPE_CAPABILITIES) {
-        g_assert_cmpint(hdr.size, <=, vmc_write_pos - sizeof(hdr));
-        vmc_write_pos -= hdr.size + sizeof(hdr);
-        memmove(vmc_write_buf, vmc_write_buf + hdr.size + sizeof(hdr), vmc_write_pos);
+        g_assert_cmpint(hdr.size, <=, vmc->write_pos - sizeof(hdr));
+        vmc->write_pos -= hdr.size + sizeof(hdr);
+        memmove(vmc->write_buf, vmc->write_buf + hdr.size + sizeof(hdr), vmc->write_pos);
     }
 }
 
@@ -168,12 +93,12 @@ check_vmc_error_message(void)
 
     discard_server_capabilities();
 
-    g_assert_cmpint(vmc_write_pos, >= ,sizeof(hdr));
+    g_assert_cmpint(vmc->write_pos, >= ,sizeof(hdr));
 
-    memcpy(&hdr, vmc_write_buf, sizeof(hdr));
+    memcpy(&hdr, vmc->write_buf, sizeof(hdr));
     g_assert_cmpint(hdr.protocol_version, ==, STREAM_DEVICE_PROTOCOL);
     g_assert_cmpint(GUINT16_FROM_LE(hdr.type), ==, STREAM_TYPE_NOTIFY_ERROR);
-    g_assert_cmpint(GUINT32_FROM_LE(hdr.size), <=, vmc_write_pos - sizeof(hdr));
+    g_assert_cmpint(GUINT32_FROM_LE(hdr.size), <=, vmc->write_pos - sizeof(hdr));
 }
 
 static int num_send_data_calls = 0;
@@ -249,15 +174,13 @@ static void test_stream_device_setup(TestFixture *fixture, gconstpointer user_da
 {
     g_assert_null(core);
     g_assert_null(test);
+    g_assert_null(vmc);
     core = basic_event_loop_init();
     g_assert_nonnull(core);
     test = test_new(core);
     g_assert_nonnull(test);
-
-    pos = 0;
-    vmc_write_pos = 0;
-    message_sizes_curr = message_sizes;
-    message_sizes_end = message_sizes;
+    vmc = vmc_emu_new("port", "org.spice-space.stream.0");
+    g_assert_nonnull(vmc);
 
     num_send_data_calls = 0;
     send_data_bytes = 0;
@@ -268,6 +191,8 @@ static void test_stream_device_teardown(TestFixture *fixture, gconstpointer user
     g_assert_nonnull(core);
     g_assert_nonnull(test);
 
+    vmc_emu_destroy(vmc);
+    vmc = NULL;
     test_destroy(test);
     test = NULL;
     basic_event_loop_destroy();
@@ -276,122 +201,109 @@ static void test_stream_device_teardown(TestFixture *fixture, gconstpointer user
 
 static void test_kick(void)
 {
-    vmc_instance.base.sif = &vmc_interface.base;
-    spice_server_add_interface(test->server, &vmc_instance.base);
+    spice_server_add_interface(test->server, &vmc->instance.base);
 
     // we need to open the device and kick the start
     // the alarm is to prevent the program from getting stuck
     alarm(5);
-    spice_server_port_event(&vmc_instance, SPICE_PORT_EVENT_OPENED);
-    spice_server_char_device_wakeup(&vmc_instance);
+    spice_server_port_event(&vmc->instance, SPICE_PORT_EVENT_OPENED);
+    spice_server_char_device_wakeup(&vmc->instance);
     alarm(0);
 }
 
 static void test_stream_device(TestFixture *fixture, gconstpointer user_data)
 {
-    uint8_t *p = message;
-
     for (int test_num=0; test_num < 2; ++test_num) {
-        pos = 0;
-        vmc_write_pos = 0;
-        message_sizes_curr = message_sizes;
-        message_sizes_end = message_sizes;
+        vmc_emu_reset(vmc);
+        uint8_t *p = vmc->message;
 
         // add some messages into device buffer
         // here we are testing the device is reading at least two
         // consecutive format messages
         // first message part has 2 extra bytes to check for header split
         p = add_format(p, 640, 480, SPICE_VIDEO_CODEC_TYPE_MJPEG);
-        *message_sizes_end = p - message + 2;
-        ++message_sizes_end;
+        vmc_emu_add_read_till(vmc, p + 2);
 
         p = add_format(p, 640, 480, SPICE_VIDEO_CODEC_TYPE_VP9);
 
         // this split the second format in half
-        *message_sizes_end = p - message - 4;
-        ++message_sizes_end;
+        vmc_emu_add_read_till(vmc, p - 4);
 
-        *message_sizes_end = p - message;
-        ++message_sizes_end;
+        vmc_emu_add_read_till(vmc, p);
 
         // add a message to stop data to be read
         p = add_stream_hdr(p, STREAM_TYPE_INVALID, 0);
-        *message_sizes_end = p - message;
-        ++message_sizes_end;
+        vmc_emu_add_read_till(vmc, p);
 
         // this message should not be read
         p = add_stream_hdr(p, STREAM_TYPE_INVALID, 0);
-        *message_sizes_end = p - message;
-        ++message_sizes_end;
+        vmc_emu_add_read_till(vmc, p);
 
-        vmc_instance.base.sif = &vmc_interface.base;
-        spice_server_add_interface(test->server, &vmc_instance.base);
+        spice_server_add_interface(test->server, &vmc->instance.base);
 
         // device should not have read data before we open it
-        spice_server_char_device_wakeup(&vmc_instance);
-        g_assert_cmpint(pos, ==, 0);
+        spice_server_char_device_wakeup(&vmc->instance);
+        g_assert_cmpint(vmc->pos, ==, 0);
 
         g_test_expect_message(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "Stream device received invalid message: Invalid message type");
 
         // we need to open the device and kick the start
-        spice_server_port_event(&vmc_instance, SPICE_PORT_EVENT_OPENED);
-        spice_server_char_device_wakeup(&vmc_instance);
-        spice_server_port_event(&vmc_instance, SPICE_PORT_EVENT_CLOSED);
+        spice_server_port_event(&vmc->instance, SPICE_PORT_EVENT_OPENED);
+        spice_server_char_device_wakeup(&vmc->instance);
+        spice_server_port_event(&vmc->instance, SPICE_PORT_EVENT_CLOSED);
 
         // make sure first 3 parts are read completely
-        g_assert(message_sizes_curr - message_sizes >= 3);
+        g_assert(vmc->message_sizes_curr - vmc->message_sizes >= 3);
         // make sure the device readed all or that device was
         // disabled, we need this to make sure that device will be in
         // sync when opened again
-        g_assert(message_sizes_curr - message_sizes == 5 || !device_enabled);
+        g_assert(vmc->message_sizes_curr - vmc->message_sizes == 5 || !vmc->device_enabled);
 
         check_vmc_error_message();
-        spice_server_remove_interface(&vmc_instance.base);
+        spice_server_remove_interface(&vmc->instance.base);
     }
 }
 
 // check if sending a partial message causes issues
 static void test_stream_device_unfinished(TestFixture *fixture, gconstpointer user_data)
 {
-    uint8_t *p = message;
+    uint8_t *p = vmc->message;
 
     // this long and not finished message should not cause an infinite loop
     p = add_stream_hdr(p, STREAM_TYPE_DATA, 100000);
-    *message_sizes_end = p - message;
-    ++message_sizes_end;
+    vmc_emu_add_read_till(vmc, p);
 
     test_kick();
 
     // we should have read all data
-    g_assert(message_sizes_curr - message_sizes == 1);
+    g_assert(vmc->message_sizes_curr - vmc->message_sizes == 1);
 
     // we should have no data from the device
     discard_server_capabilities();
-    g_assert_cmpint(vmc_write_pos, ==, 0);
+    g_assert_cmpint(vmc->write_pos, ==, 0);
 }
 
 // check if sending multiple messages cause stall
 static void test_stream_device_multiple(TestFixture *fixture, gconstpointer user_data)
 {
-    uint8_t *p = message;
+    uint8_t *p = vmc->message;
 
     // add some messages into device buffer
     p = add_format(p, 640, 480, SPICE_VIDEO_CODEC_TYPE_MJPEG);
     p = add_format(p, 640, 480, SPICE_VIDEO_CODEC_TYPE_MJPEG);
     p = add_format(p, 640, 480, SPICE_VIDEO_CODEC_TYPE_MJPEG);
-    *message_sizes_end = p - message;
-    ++message_sizes_end;
+    vmc_emu_add_read_till(vmc, p);
 
     test_kick();
 
     // we should have read all data
-    g_assert(message_sizes_curr - message_sizes == 1);
+    g_assert(vmc->message_sizes_curr - vmc->message_sizes == 1);
 }
 
 // check if data message consume even following message
 static void test_stream_device_format_after_data(TestFixture *fixture, gconstpointer user_data)
 {
-    uint8_t *p = message;
+    uint8_t *p = vmc->message;
 
     // add some messages into device buffer
     p = add_format(p, 640, 480, SPICE_VIDEO_CODEC_TYPE_MJPEG);
@@ -399,15 +311,14 @@ static void test_stream_device_format_after_data(TestFixture *fixture, gconstpoi
     memcpy(p, "hello", 5);
     p += 5;
     p = add_stream_hdr(p, STREAM_TYPE_INVALID, 0);
-    *message_sizes_end = p - message;
-    ++message_sizes_end;
+    vmc_emu_add_read_till(vmc, p);
 
     g_test_expect_message(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "Stream device received invalid message: Invalid message type");
 
     test_kick();
 
     // we should read all data
-    g_assert(message_sizes_curr - message_sizes == 1);
+    g_assert(vmc->message_sizes_curr - vmc->message_sizes == 1);
 
     // we should have an error back
     check_vmc_error_message();
@@ -417,46 +328,42 @@ static void test_stream_device_format_after_data(TestFixture *fixture, gconstpoi
 static void test_stream_device_empty(TestFixture *fixture, gconstpointer user_data)
 {
     const StreamMsgType msg_type = (StreamMsgType) GPOINTER_TO_INT(user_data);
-    uint8_t *p = message;
+    uint8_t *p = vmc->message;
 
     // add some messages into device buffer
     p = add_stream_hdr(p, msg_type, 0);
-    *message_sizes_end = p - message;
-    ++message_sizes_end;
+    vmc_emu_add_read_till(vmc, p);
     p = add_format(p, 640, 480, SPICE_VIDEO_CODEC_TYPE_MJPEG);
-    *message_sizes_end = p - message;
-    ++message_sizes_end;
+    vmc_emu_add_read_till(vmc, p);
     p = add_format(p, 640, 480, SPICE_VIDEO_CODEC_TYPE_MJPEG);
-    *message_sizes_end = p - message;
-    ++message_sizes_end;
+    vmc_emu_add_read_till(vmc, p);
 
     test_kick();
 
     // we should read all data
-    g_assert(message_sizes_curr - message_sizes == 3);
+    g_assert(vmc->message_sizes_curr - vmc->message_sizes == 3);
 
     // we should have no data from the device
     discard_server_capabilities();
-    g_assert_cmpint(vmc_write_pos, ==, 0);
+    g_assert_cmpint(vmc->write_pos, ==, 0);
 }
 
 // check that server refuse huge data messages
 static void test_stream_device_huge_data(TestFixture *fixture, gconstpointer user_data)
 {
-    uint8_t *p = message;
+    uint8_t *p = vmc->message;
 
     // add some messages into device buffer
     p = add_stream_hdr(p, STREAM_TYPE_DATA, 33 * 1024 * 1024);
     p = add_format(p, 640, 480, SPICE_VIDEO_CODEC_TYPE_MJPEG);
-    *message_sizes_end = p - message;
-    ++message_sizes_end;
+    vmc_emu_add_read_till(vmc, p);
 
     g_test_expect_message(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "Stream device received invalid message: STREAM_DATA too large");
 
     test_kick();
 
     // we should read all data
-    g_assert(message_sizes_curr - message_sizes == 1);
+    g_assert(vmc->message_sizes_curr - vmc->message_sizes == 1);
 
     // we should have an error back
     check_vmc_error_message();
@@ -465,7 +372,7 @@ static void test_stream_device_huge_data(TestFixture *fixture, gconstpointer use
 // check that server send all message
 static void test_stream_device_data_message(TestFixture *fixture, gconstpointer user_data)
 {
-    uint8_t *p = message;
+    uint8_t *p = vmc->message;
 
     // add some messages into device buffer
     p = add_format(p, 640, 480, SPICE_VIDEO_CODEC_TYPE_MJPEG);
@@ -473,23 +380,19 @@ static void test_stream_device_data_message(TestFixture *fixture, gconstpointer
     for (int i = 0; i < 1017; ++i, ++p) {
         *p = (uint8_t) (i * 123 + 57);
     }
-    *message_sizes_end = 51;
-    ++message_sizes_end;
-    *message_sizes_end = 123;
-    ++message_sizes_end;
-    *message_sizes_end = 534;
-    ++message_sizes_end;
-    *message_sizes_end = p - message;
-    ++message_sizes_end;
+    vmc_emu_add_read_till(vmc, vmc->message + 51);
+    vmc_emu_add_read_till(vmc, vmc->message + 123);
+    vmc_emu_add_read_till(vmc, vmc->message + 534);
+    vmc_emu_add_read_till(vmc, p);
 
     test_kick();
 
     // we should read all data
-    g_assert(message_sizes_curr - message_sizes == 4);
+    g_assert(vmc->message_sizes_curr - vmc->message_sizes == 4);
 
     // we should have no data from the device
     discard_server_capabilities();
-    g_assert_cmpint(vmc_write_pos, ==, 0);
+    g_assert_cmpint(vmc->write_pos, ==, 0);
 
     // make sure data were collapsed in a single message
     g_assert_cmpint(num_send_data_calls, ==, 1);
@@ -512,15 +415,14 @@ static void test_display_info(TestFixture *fixture, gconstpointer user_data)
         .device_display_id = GUINT32_TO_LE(0x0a0b0c0d),
         .device_address_len = GUINT32_TO_LE(sizeof(address)),
     };
-    uint8_t *p = message;
+    uint8_t *p = vmc->message;
     p = add_stream_hdr(p, STREAM_TYPE_DEVICE_DISPLAY_INFO, sizeof(info) + sizeof(address));
     memcpy(p, &info, sizeof(info));
     p += sizeof(info);
     strcpy((char*)p, address);
     p += sizeof(address);
 
-    *message_sizes_end = p - message;
-    ++message_sizes_end;
+    vmc_emu_add_read_till(vmc, p);
 
     // parse the simulated display info message from the stream device so the server now has display
     // info for the mock stream device
diff --git a/server/tests/vmc-emu.c b/server/tests/vmc-emu.c
new file mode 100644
index 00000000..418a0213
--- /dev/null
+++ b/server/tests/vmc-emu.c
@@ -0,0 +1,121 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2019 Red Hat, Inc.
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <config.h>
+#include <glib.h>
+
+#include "vmc-emu.h"
+
+// handle writes to the device
+static int vmc_write(SpiceCharDeviceInstance *sin,
+                     const uint8_t *buf, int len)
+{
+    VmcEmu *const vmc = SPICE_CONTAINEROF(sin, VmcEmu, instance);
+
+    // just copy into the buffer
+    unsigned copy = MIN(sizeof(vmc->write_buf) - vmc->write_pos, len);
+    memcpy(vmc->write_buf+vmc->write_pos, buf, copy);
+    vmc->write_pos += copy;
+    return len;
+}
+
+static int vmc_read(SpiceCharDeviceInstance *sin,
+                    uint8_t *buf, int len)
+{
+    VmcEmu *const vmc = SPICE_CONTAINEROF(sin, VmcEmu, instance);
+    int ret;
+
+    if (vmc->pos >= *vmc->message_sizes_curr && vmc->message_sizes_curr < vmc->message_sizes_end) {
+        ++vmc->message_sizes_curr;
+    }
+    if (vmc->message_sizes_curr >= vmc->message_sizes_end || vmc->pos >= *vmc->message_sizes_curr) {
+        return 0;
+    }
+    ret = MIN(*vmc->message_sizes_curr - vmc->pos, len);
+    memcpy(buf, &vmc->message[vmc->pos], ret);
+    vmc->pos += ret;
+    // kick off next message read
+    // currently Qemu kicks the device so we need to do it manually
+    // here. If not all data are read, the device goes into blocking
+    // state and we get the wake only when we read from the device
+    // again
+    if (vmc->pos >= *vmc->message_sizes_curr) {
+        spice_server_char_device_wakeup(&vmc->instance);
+    }
+    return ret;
+}
+
+static void vmc_state(SpiceCharDeviceInstance *sin,
+                      int connected)
+{
+    VmcEmu *const vmc = SPICE_CONTAINEROF(sin, VmcEmu, instance);
+    vmc->device_enabled = !!connected;
+}
+
+static const SpiceCharDeviceInterface vmc_interface = {
+    .base = {
+        .type          = SPICE_INTERFACE_CHAR_DEVICE,
+        .description   = "test spice virtual channel char device",
+        .major_version = SPICE_INTERFACE_CHAR_DEVICE_MAJOR,
+        .minor_version = SPICE_INTERFACE_CHAR_DEVICE_MINOR,
+    },
+    .state              = vmc_state,
+    .write              = vmc_write,
+    .read               = vmc_read,
+};
+
+VmcEmu *vmc_emu_new(const char *subtype, const char *portname)
+{
+    VmcEmu *vmc = g_new0(VmcEmu, 1);
+    vmc->vmc_interface = vmc_interface;
+    vmc->instance.base.sif = &vmc->vmc_interface.base;
+    vmc->instance.subtype = g_strdup(subtype);
+    if (portname) {
+        vmc->instance.portname = g_strdup(portname);
+    }
+    vmc_emu_reset(vmc);
+    return vmc;
+}
+
+void vmc_emu_destroy(VmcEmu *vmc)
+{
+    g_free((char *) vmc->instance.portname);
+    g_free((char *) vmc->instance.subtype);
+    g_free(vmc);
+}
+
+void vmc_emu_reset(VmcEmu *vmc)
+{
+    vmc->pos = 0;
+    vmc->write_pos = 0;
+    vmc->message_sizes_curr = vmc->message_sizes;
+    vmc->message_sizes_end = vmc->message_sizes;
+}
+
+void vmc_emu_add_read_till(VmcEmu *vmc, uint8_t *end)
+{
+    g_assert(vmc->message_sizes_end - vmc->message_sizes < G_N_ELEMENTS(vmc->message_sizes));
+    g_assert(end >= vmc->message);
+    g_assert(end - vmc->message <= G_N_ELEMENTS(vmc->message));
+    unsigned prev_size =
+        vmc->message_sizes_end > vmc->message_sizes ? vmc->message_sizes_end[-1] : 0;
+    unsigned size = end - vmc->message;
+    g_assert(size >= prev_size);
+    *vmc->message_sizes_end = size;
+    ++vmc->message_sizes_end;
+}
diff --git a/server/tests/vmc-emu.h b/server/tests/vmc-emu.h
new file mode 100644
index 00000000..7c26938e
--- /dev/null
+++ b/server/tests/vmc-emu.h
@@ -0,0 +1,48 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+   Copyright (C) 2019 Red Hat, Inc.
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   This library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+#pragma once
+
+#include "char-device.h"
+
+typedef struct VmcEmu VmcEmu;
+
+struct VmcEmu {
+    SpiceCharDeviceInterface vmc_interface;
+    SpiceCharDeviceInstance instance;
+
+    // device buffer to read from
+    uint8_t message[2048];
+    // position to read from
+    unsigned pos;
+
+    // array of limits when the read should return
+    // the array is defined as [message_sizes_curr, message_sizes_end)
+    // then the size is reach we move on next one till exausted
+    unsigned message_sizes[16];
+    unsigned *message_sizes_end, *message_sizes_curr;
+
+    bool device_enabled;
+
+    unsigned write_pos;
+    uint8_t write_buf[2048];
+};
+
+VmcEmu *vmc_emu_new(const char *subtype, const char *portname);
+void vmc_emu_destroy(VmcEmu *vmc);
+void vmc_emu_reset(VmcEmu *vmc);
+void vmc_emu_add_read_till(VmcEmu *vmc, uint8_t *end);
commit 07d9328f895c619389bd148cad2a0ffd8bf85ad4
Author: Frediano Ziglio <fziglio at redhat.com>
Date:   Tue Oct 8 14:22:47 2019 +0100

    smartcard: Fix parsing multiple messages from the device
    
    This patch handles the scenario when a single read to guest device
    brings multiple requests to be handled. When this happens, we will
    iterate till all requests are handled and no more requests can be read
    from guest device.
    
    If the remaining buffer contains a full request we don't need to read
    other bytes (note that there could be no bytes left), just parse the
    request.
    
    Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
    Acked-by: Victor Toso <victortoso at redhat.com>

diff --git a/server/smartcard.c b/server/smartcard.c
index bf5e9052..340118e1 100644
--- a/server/smartcard.c
+++ b/server/smartcard.c
@@ -130,19 +130,28 @@ static RedPipeItem *smartcard_read_msg_from_device(RedCharDevice *self,
     RedCharDeviceSmartcard *dev = RED_CHAR_DEVICE_SMARTCARD(self);
     SpiceCharDeviceInterface *sif = spice_char_device_get_interface(sin);
     VSCMsgHeader *vheader = (VSCMsgHeader*)dev->priv->buf;
-    int n;
     int remaining;
     int actual_length;
 
-    while ((n = sif->read(sin, dev->priv->buf_pos, dev->priv->buf_size - dev->priv->buf_used)) > 0) {
+    while (true) {
         RedMsgItem *msg_to_client;
 
-        dev->priv->buf_pos += n;
-        dev->priv->buf_used += n;
-        if (dev->priv->buf_used < sizeof(VSCMsgHeader)) {
-            continue;
+        // it's possible we already got a full message from a previous partial
+        // read. In this case we don't need to read any byte
+        if (dev->priv->buf_used < sizeof(VSCMsgHeader) ||
+            dev->priv->buf_used - sizeof(VSCMsgHeader) < ntohl(vheader->length)) {
+            int n = sif->read(sin, dev->priv->buf_pos, dev->priv->buf_size - dev->priv->buf_used);
+            if (n <= 0) {
+                break;
+            }
+            dev->priv->buf_pos += n;
+            dev->priv->buf_used += n;
+
+            if (dev->priv->buf_used < sizeof(VSCMsgHeader)) {
+                continue;
+            }
+            smartcard_read_buf_prepare(dev, vheader);
         }
-        smartcard_read_buf_prepare(dev, vheader);
         actual_length = ntohl(vheader->length);
         if (dev->priv->buf_used - sizeof(VSCMsgHeader) < actual_length) {
             continue;
commit 6fa12db10490f471e137f6b2fa9b43920b14e42b
Author: Frediano Ziglio <fziglio at redhat.com>
Date:   Wed Oct 9 09:40:55 2019 +0100

    smartcard: Fix copying remaining request
    
    Use memmove instead of memcpy as the buffer can overlap if the second
    request if bigger than the first.
    "buf_pos" points to the point of the buffer after we read, if we want
    the first part of the next request is "buf_pos - remaining".
    Same consideration setting "buf_pos" for the next iteration.
    
    Signed-off-by: Frediano Ziglio <fziglio at redhat.com>
    Acked-by: Victor Toso <victortoso at redhat.com>

diff --git a/server/smartcard.c b/server/smartcard.c
index 4c5bba07..bf5e9052 100644
--- a/server/smartcard.c
+++ b/server/smartcard.c
@@ -150,9 +150,9 @@ static RedPipeItem *smartcard_read_msg_from_device(RedCharDevice *self,
         msg_to_client = smartcard_char_device_on_message_from_device(dev, vheader);
         remaining = dev->priv->buf_used - sizeof(VSCMsgHeader) - actual_length;
         if (remaining > 0) {
-            memcpy(dev->priv->buf, dev->priv->buf_pos, remaining);
+            memmove(dev->priv->buf, dev->priv->buf_pos - remaining, remaining);
         }
-        dev->priv->buf_pos = dev->priv->buf;
+        dev->priv->buf_pos = dev->priv->buf + remaining;
         dev->priv->buf_used = remaining;
         if (msg_to_client) {
             return &msg_to_client->base;


More information about the Spice-commits mailing list