summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFrediano Ziglio <fziglio@redhat.com>2020-03-18 11:09:47 +0000
committerFrediano Ziglio <freddy77@gmail.com>2020-04-24 09:42:46 +0100
commitbb03ff099bc9449357aa412ee38aed70bacd93ab (patch)
tree869943330b5613e823f643c24a7c2a2962c7cdef
parent8470ef9df21b22dfad7bc1dfb0bed223b7b0009d (diff)
Add helper code for agent messages
Add agent.h and agent.c to deal with some common agent job: - checking message from network and fixing network order. - send back file transfer status. Code based on Linux agent and Windows agent. AgentXxxx and agent_xxx are used to avoid conflicts with protocol. See agent.h for more detail on how to use these APIs. Signed-off-by: Frediano Ziglio <fziglio@redhat.com>
-rw-r--r--common/Makefile.am2
-rw-r--r--common/agent.c343
-rw-r--r--common/agent.h75
-rw-r--r--common/meson.build2
-rw-r--r--configure.ac2
-rw-r--r--m4/spice-deps.m44
-rw-r--r--meson.build2
7 files changed, 426 insertions, 4 deletions
diff --git a/common/Makefile.am b/common/Makefile.am
index b6c29e1..5200c81 100644
--- a/common/Makefile.am
+++ b/common/Makefile.am
@@ -17,6 +17,8 @@ BUILT_SOURCES = $(CLIENT_MARSHALLERS) $(SERVER_MARSHALLERS)
noinst_LTLIBRARIES = libspice-common.la libspice-common-server.la libspice-common-client.la
libspice_common_la_SOURCES = \
+ agent.c \
+ agent.h \
backtrace.c \
backtrace.h \
canvas_utils.c \
diff --git a/common/agent.c b/common/agent.c
new file mode 100644
index 0000000..e7df786
--- /dev/null
+++ b/common/agent.c
@@ -0,0 +1,343 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ Copyright (C) 2020 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 "agent.h"
+
+#ifdef _WIN32
+// Windows is always little endian
+# define FIX_ENDIAN16(x) (x) = (x)
+# define FIX_ENDIAN32(x) (x) = (x)
+# define FIX_ENDIAN64(x) (x) = (x)
+#else
+# include <glib.h>
+# define FIX_ENDIAN16(x) (x) = GUINT16_FROM_LE(x)
+# define FIX_ENDIAN32(x) (x) = GUINT32_FROM_LE(x)
+# define FIX_ENDIAN64(x) (x) = GUINT64_FROM_LE(x)
+#endif
+
+#include <spice/start-packed.h>
+typedef struct SPICE_ATTR_PACKED {
+ uint16_t v;
+} uint16_unaligned_t;
+
+typedef struct SPICE_ATTR_PACKED {
+ uint32_t v;
+} uint32_unaligned_t;
+
+typedef struct SPICE_ATTR_PACKED {
+ uint64_t v;
+} uint64_unaligned_t;
+#include <spice/end-packed.h>
+
+static const int agent_message_min_size[] =
+{
+ -1, /* Does not exist */
+ sizeof(VDAgentMouseState), /* VD_AGENT_MOUSE_STATE */
+ sizeof(VDAgentMonitorsConfig), /* VD_AGENT_MONITORS_CONFIG */
+ sizeof(VDAgentReply), /* VD_AGENT_REPLY */
+ sizeof(VDAgentClipboard), /* VD_AGENT_CLIPBOARD */
+ sizeof(VDAgentDisplayConfig), /* VD_AGENT_DISPLAY_CONFIG */
+ sizeof(VDAgentAnnounceCapabilities), /* VD_AGENT_ANNOUNCE_CAPABILITIES */
+ sizeof(VDAgentClipboardGrab), /* VD_AGENT_CLIPBOARD_GRAB */
+ sizeof(VDAgentClipboardRequest), /* VD_AGENT_CLIPBOARD_REQUEST */
+ sizeof(VDAgentClipboardRelease), /* VD_AGENT_CLIPBOARD_RELEASE */
+ sizeof(VDAgentFileXferStartMessage), /* VD_AGENT_FILE_XFER_START */
+ sizeof(VDAgentFileXferStatusMessage), /* VD_AGENT_FILE_XFER_STATUS */
+ sizeof(VDAgentFileXferDataMessage), /* VD_AGENT_FILE_XFER_DATA */
+ 0, /* VD_AGENT_CLIENT_DISCONNECTED */
+ sizeof(VDAgentMaxClipboard), /* VD_AGENT_MAX_CLIPBOARD */
+ sizeof(VDAgentAudioVolumeSync), /* VD_AGENT_AUDIO_VOLUME_SYNC */
+ sizeof(VDAgentGraphicsDeviceInfo), /* VD_AGENT_GRAPHICS_DEVICE_INFO */
+};
+
+static AgentCheckResult
+agent_message_check_size(const VDAgentMessage *message_header,
+ const uint32_t *capabilities, uint32_t capabilities_size)
+{
+ if (message_header->protocol != VD_AGENT_PROTOCOL) {
+ return AGENT_CHECK_WRONG_PROTOCOL_VERSION;
+ }
+
+ if (message_header->type >= SPICE_N_ELEMENTS(agent_message_min_size) ||
+ agent_message_min_size[message_header->type] < 0) {
+ return AGENT_CHECK_UNKNOWN_MESSAGE;
+ }
+
+ uint32_t min_size = agent_message_min_size[message_header->type];
+
+ if (VD_AGENT_HAS_CAPABILITY(capabilities, capabilities_size,
+ VD_AGENT_CAP_CLIPBOARD_SELECTION)) {
+ switch (message_header->type) {
+ case VD_AGENT_CLIPBOARD_GRAB:
+ case VD_AGENT_CLIPBOARD_REQUEST:
+ case VD_AGENT_CLIPBOARD:
+ case VD_AGENT_CLIPBOARD_RELEASE:
+ min_size += 4;
+ }
+ }
+
+ if (VD_AGENT_HAS_CAPABILITY(capabilities, capabilities_size,
+ VD_AGENT_CAP_CLIPBOARD_GRAB_SERIAL)
+ && message_header->type == VD_AGENT_CLIPBOARD_GRAB) {
+ min_size += 4;
+ }
+
+ switch (message_header->type) {
+ case VD_AGENT_MONITORS_CONFIG:
+ case VD_AGENT_FILE_XFER_START:
+ case VD_AGENT_FILE_XFER_DATA:
+ case VD_AGENT_CLIPBOARD:
+ case VD_AGENT_CLIPBOARD_GRAB:
+ case VD_AGENT_AUDIO_VOLUME_SYNC:
+ case VD_AGENT_ANNOUNCE_CAPABILITIES:
+ case VD_AGENT_GRAPHICS_DEVICE_INFO:
+ case VD_AGENT_FILE_XFER_STATUS:
+ if (message_header->size < min_size) {
+ return AGENT_CHECK_INVALID_SIZE;
+ }
+ break;
+ case VD_AGENT_MOUSE_STATE:
+ case VD_AGENT_DISPLAY_CONFIG:
+ case VD_AGENT_REPLY:
+ case VD_AGENT_CLIPBOARD_REQUEST:
+ case VD_AGENT_CLIPBOARD_RELEASE:
+ case VD_AGENT_MAX_CLIPBOARD:
+ case VD_AGENT_CLIENT_DISCONNECTED:
+ if (message_header->size != min_size) {
+ return AGENT_CHECK_INVALID_SIZE;
+ }
+ break;
+ default:
+ return AGENT_CHECK_UNKNOWN_MESSAGE;
+ }
+ return AGENT_CHECK_NO_ERROR;
+}
+
+static void uint16_from_le(uint8_t *_msg, uint32_t size, uint32_t offset)
+{
+ uint32_t i;
+ uint16_unaligned_t *msg = (uint16_unaligned_t *)(_msg + offset);
+
+ /* offset - size % 2 should be 0 - extra bytes are ignored */
+ for (i = 0; i < (size - offset) / 2; i++) {
+ FIX_ENDIAN16(msg[i].v);
+ }
+}
+
+static void uint32_from_le(uint8_t *_msg, uint32_t size, uint32_t offset)
+{
+ uint32_t i;
+ uint32_unaligned_t *msg = (uint32_unaligned_t *)(_msg + offset);
+
+ /* offset - size % 4 should be 0 - extra bytes are ignored */
+ for (i = 0; i < (size - offset) / 4; i++) {
+ FIX_ENDIAN32(msg[i].v);
+ }
+}
+
+static void
+agent_message_clipboard_from_le(const VDAgentMessage *message_header, uint8_t *data,
+ const uint32_t *capabilities, uint32_t capabilities_size)
+{
+ size_t min_size = agent_message_min_size[message_header->type];
+ uint32_unaligned_t *data_type = (uint32_unaligned_t *) data;
+
+ if (VD_AGENT_HAS_CAPABILITY(capabilities, capabilities_size,
+ VD_AGENT_CAP_CLIPBOARD_SELECTION)) {
+ min_size += 4;
+ data_type++;
+ }
+
+ switch (message_header->type) {
+ case VD_AGENT_CLIPBOARD_REQUEST:
+ case VD_AGENT_CLIPBOARD:
+ FIX_ENDIAN32(data_type->v);
+ break;
+ case VD_AGENT_CLIPBOARD_GRAB:
+ uint32_from_le(data, message_header->size, min_size);
+ break;
+ case VD_AGENT_CLIPBOARD_RELEASE:
+ // empty
+ break;
+ }
+}
+
+static void
+agent_message_file_xfer_from_le(const VDAgentMessage *message_header, uint8_t *data)
+{
+ uint32_unaligned_t *id = (uint32_unaligned_t *)data;
+ FIX_ENDIAN32(id->v);
+ id++; // result
+
+ switch (message_header->type) {
+ case VD_AGENT_FILE_XFER_DATA: {
+ VDAgentFileXferDataMessage *msg = (VDAgentFileXferDataMessage *) data;
+ FIX_ENDIAN64(msg->size);
+ break;
+ }
+ case VD_AGENT_FILE_XFER_STATUS: {
+ VDAgentFileXferStatusMessage *msg = (VDAgentFileXferStatusMessage *) data;
+ FIX_ENDIAN32(msg->result);
+ // from client/server we don't expect any detail
+ switch (msg->result) {
+ case VD_AGENT_FILE_XFER_STATUS_NOT_ENOUGH_SPACE:
+ if (message_header->size >= sizeof(VDAgentFileXferStatusMessage) +
+ sizeof(VDAgentFileXferStatusNotEnoughSpace)) {
+ VDAgentFileXferStatusNotEnoughSpace *err =
+ (VDAgentFileXferStatusNotEnoughSpace*) msg->data;
+ FIX_ENDIAN64(err->disk_free_space);
+ }
+ break;
+ case VD_AGENT_FILE_XFER_STATUS_ERROR:
+ if (message_header->size >= sizeof(VDAgentFileXferStatusMessage) +
+ sizeof(VDAgentFileXferStatusError)) {
+ VDAgentFileXferStatusError *err =
+ (VDAgentFileXferStatusError *) msg->data;
+ FIX_ENDIAN32(err->error_code);
+ }
+ break;
+ }
+ break;
+ }
+ }
+}
+
+static AgentCheckResult
+agent_message_graphics_device_info_check_from_le(const VDAgentMessage *message_header,
+ uint8_t *data)
+{
+ uint8_t *const end = data + message_header->size;
+ uint32_unaligned_t *u32 = (uint32_unaligned_t *) data;
+ FIX_ENDIAN32(u32->v);
+ const uint32_t count = u32->v;
+ data += 4;
+
+ for (size_t i = 0; i < count; ++i) {
+ if ((size_t) (end - data) < sizeof(VDAgentDeviceDisplayInfo)) {
+ return AGENT_CHECK_TRUNCATED;
+ }
+ uint32_from_le(data, sizeof(VDAgentDeviceDisplayInfo), 0);
+ VDAgentDeviceDisplayInfo *info = (VDAgentDeviceDisplayInfo *) data;
+ data += sizeof(VDAgentDeviceDisplayInfo);
+ if (!info->device_address_len) {
+ return AGENT_CHECK_INVALID_DATA;
+ }
+ if ((size_t) (end - data) < info->device_address_len) {
+ return AGENT_CHECK_TRUNCATED;
+ }
+ info->device_address[info->device_address_len - 1] = 0;
+ data += info->device_address_len;
+ }
+ return AGENT_CHECK_NO_ERROR;
+}
+
+AgentCheckResult
+agent_check_message(const VDAgentMessage *message_header, uint8_t *message,
+ const uint32_t *capabilities, uint32_t capabilities_size)
+{
+ AgentCheckResult res;
+
+ res = agent_message_check_size(message_header, capabilities, capabilities_size);
+ if (res != AGENT_CHECK_NO_ERROR) {
+ return res;
+ }
+
+ switch (message_header->type) {
+ case VD_AGENT_MOUSE_STATE:
+ uint32_from_le(message, 3 * sizeof(uint32_t), 0);
+ break;
+ case VD_AGENT_REPLY:
+ case VD_AGENT_DISPLAY_CONFIG:
+ case VD_AGENT_MAX_CLIPBOARD:
+ case VD_AGENT_ANNOUNCE_CAPABILITIES:
+ uint32_from_le(message, message_header->size, 0);
+ break;
+ case VD_AGENT_MONITORS_CONFIG: {
+ uint32_from_le(message, message_header->size, 0);
+ VDAgentMonitorsConfig *vdata = (VDAgentMonitorsConfig*) message;
+ const size_t max_monitors =
+ (message_header->size - sizeof(*vdata)) / sizeof(vdata->monitors[0]);
+ if (vdata->num_of_monitors > max_monitors) {
+ return AGENT_CHECK_TRUNCATED;
+ }
+ break;
+ }
+ case VD_AGENT_CLIPBOARD:
+ case VD_AGENT_CLIPBOARD_GRAB:
+ case VD_AGENT_CLIPBOARD_REQUEST:
+ case VD_AGENT_CLIPBOARD_RELEASE:
+ agent_message_clipboard_from_le(message_header, message,
+ capabilities, capabilities_size);
+ break;
+ case VD_AGENT_FILE_XFER_START:
+ case VD_AGENT_FILE_XFER_STATUS:
+ case VD_AGENT_FILE_XFER_DATA:
+ agent_message_file_xfer_from_le(message_header, message);
+ break;
+ case VD_AGENT_CLIENT_DISCONNECTED:
+ break;
+ case VD_AGENT_GRAPHICS_DEVICE_INFO:
+ return agent_message_graphics_device_info_check_from_le(message_header, message);
+
+ case VD_AGENT_AUDIO_VOLUME_SYNC: {
+ VDAgentAudioVolumeSync *vdata = (VDAgentAudioVolumeSync *)message;
+ const size_t max_channels =
+ (message_header->size - sizeof(*vdata)) / sizeof(vdata->volume[0]);
+ if (vdata->nchannels > max_channels) {
+ return AGENT_CHECK_TRUNCATED;
+ }
+ uint16_from_le(message, message_header->size, sizeof(*vdata));
+ break;
+ }
+ default:
+ return AGENT_CHECK_UNKNOWN_MESSAGE;
+ }
+ return AGENT_CHECK_NO_ERROR;
+}
+
+void
+agent_prepare_filexfer_status(AgentFileXferStatusMessageFull *status, size_t *status_size,
+ const uint32_t *capabilities, uint32_t capabilities_size)
+{
+ if (*status_size < sizeof(status->common)) {
+ *status_size = sizeof(status->common);
+ }
+
+ // if there are details but no cap for detail remove it
+ if (!VD_AGENT_HAS_CAPABILITY(capabilities, capabilities_size,
+ VD_AGENT_CAP_FILE_XFER_DETAILED_ERRORS)) {
+ *status_size = sizeof(status->common);
+
+ // if detail cap is not provided and error > threshold set to error
+ if (status->common.result >= VD_AGENT_FILE_XFER_STATUS_NOT_ENOUGH_SPACE) {
+ status->common.result = VD_AGENT_FILE_XFER_STATUS_ERROR;
+ }
+ }
+
+ // fix endian
+ switch (status->common.result) {
+ case VD_AGENT_FILE_XFER_STATUS_NOT_ENOUGH_SPACE:
+ FIX_ENDIAN64(status->not_enough_space.disk_free_space);
+ break;
+ case VD_AGENT_FILE_XFER_STATUS_ERROR:
+ FIX_ENDIAN32(status->error.error_code);
+ break;
+ }
+ // header should be done last
+ FIX_ENDIAN32(status->common.id);
+ FIX_ENDIAN32(status->common.result);
+}
diff --git a/common/agent.h b/common/agent.h
new file mode 100644
index 0000000..f0eee44
--- /dev/null
+++ b/common/agent.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ Copyright (C) 2020 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 <spice/macros.h>
+#include <spice/vd_agent.h>
+
+SPICE_BEGIN_DECLS
+
+#include <spice/start-packed.h>
+
+/**
+ * Structure to fill with transfer status.
+ * Fill as much details as you can and call agent_prepare_filexfer_status
+ * before sending to adjust for capabilities and endianness.
+ * If any detail are filled the status_size passed to agent_prepare_filexfer_status
+ * should be updated.
+ */
+typedef struct SPICE_ATTR_PACKED AgentFileXferStatusMessageFull {
+ VDAgentFileXferStatusMessage common;
+ union SPICE_ATTR_PACKED {
+ VDAgentFileXferStatusNotEnoughSpace not_enough_space;
+ VDAgentFileXferStatusError error;
+ };
+} AgentFileXferStatusMessageFull;
+
+#include <spice/end-packed.h>
+
+/**
+ * Prepare AgentFileXferStatusMessageFull to
+ * be sent to network.
+ * Avoid protocol incompatibilities and endian issues
+ */
+void
+agent_prepare_filexfer_status(AgentFileXferStatusMessageFull *status, size_t *status_size,
+ const uint32_t *capabilities, uint32_t capabilities_size);
+
+/**
+ * Possible results checking a message.
+ * Beside AGENT_CHECK_NO_ERROR all other conditions are errors.
+ */
+typedef enum AgentCheckResult {
+ AGENT_CHECK_NO_ERROR,
+ AGENT_CHECK_WRONG_PROTOCOL_VERSION,
+ AGENT_CHECK_UNKNOWN_MESSAGE,
+ AGENT_CHECK_INVALID_SIZE,
+ AGENT_CHECK_TRUNCATED,
+ AGENT_CHECK_INVALID_DATA,
+} AgentCheckResult;
+
+/**
+ * Check message from network and fix endianness
+ * Returns AGENT_CHECK_NO_ERROR if message is valid.
+ * message buffer size should be message_header->size.
+ */
+AgentCheckResult
+agent_check_message(const VDAgentMessage *message_header, uint8_t *message,
+ const uint32_t *capabilities, uint32_t capabilities_size);
+
+SPICE_END_DECLS
diff --git a/common/meson.build b/common/meson.build
index 14bf242..20263c8 100644
--- a/common/meson.build
+++ b/common/meson.build
@@ -2,6 +2,8 @@
# libspice-common
#
spice_common_sources = [
+ 'agent.c',
+ 'agent.h',
'backtrace.c',
'backtrace.h',
'canvas_utils.c',
diff --git a/configure.ac b/configure.ac
index 471ecac..0d4c22b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -51,7 +51,7 @@ AS_IF([test "x$enable_alignment_checks" = "xyes"],
SPICE_CHECK_INSTRUMENTATION
# Checks for libraries
-PKG_CHECK_MODULES([PROTOCOL], [spice-protocol >= 0.12.12])
+PKG_CHECK_MODULES([PROTOCOL], [spice-protocol >= 0.14.2])
SPICE_CHECK_PYTHON_MODULES()
diff --git a/m4/spice-deps.m4 b/m4/spice-deps.m4
index f16266c..d8c3254 100644
--- a/m4/spice-deps.m4
+++ b/m4/spice-deps.m4
@@ -337,8 +337,8 @@ dnl The flags are necessary in order to make included header working
AC_REQUIRE([SPICE_EXTRA_CHECKS])dnl
AC_REQUIRE([SPICE_CHECK_INSTRUMENTATION])dnl
dnl Get the required spice protocol version
- m4_define([SPICE_PROTOCOL_MIN_VER],m4_ifdef([SPICE_PROTOCOL_MIN_VER],SPICE_PROTOCOL_MIN_VER,[0.12.12]))dnl
- m4_define([SPICE_PROTOCOL_MIN_VER],m4_if(m4_version_compare(SPICE_PROTOCOL_MIN_VER,[0.12.12]),[1],SPICE_PROTOCOL_MIN_VER,[0.12.12]))dnl
+ m4_define([SPICE_PROTOCOL_MIN_VER],m4_ifdef([SPICE_PROTOCOL_MIN_VER],SPICE_PROTOCOL_MIN_VER,[0.14.2]))dnl
+ m4_define([SPICE_PROTOCOL_MIN_VER],m4_if(m4_version_compare(SPICE_PROTOCOL_MIN_VER,[0.14.2]),[1],SPICE_PROTOCOL_MIN_VER,[0.14.2]))dnl
[SPICE_PROTOCOL_MIN_VER]=SPICE_PROTOCOL_MIN_VER
m4_undefine([SPICE_PROTOCOL_MIN_VER])dnl
PKG_CHECK_MODULES([SPICE_PROTOCOL], [spice-protocol >= $SPICE_PROTOCOL_MIN_VER])
diff --git a/meson.build b/meson.build
index c93b8a6..31940d9 100644
--- a/meson.build
+++ b/meson.build
@@ -95,7 +95,7 @@ endif
glib_version = '2.38'
glib_version_info = '>= @0@'.format(glib_version)
-spice_protocol_version = '0.12.12'
+spice_protocol_version = '0.14.2'
spice_protocol_version_req = get_option('spice-protocol-version')
if spice_protocol_version_req.version_compare('> @0@'.format(spice_protocol_version))