diff options
author | Frediano Ziglio <fziglio@redhat.com> | 2020-03-18 11:09:47 +0000 |
---|---|---|
committer | Frediano Ziglio <freddy77@gmail.com> | 2020-04-24 09:42:46 +0100 |
commit | bb03ff099bc9449357aa412ee38aed70bacd93ab (patch) | |
tree | 869943330b5613e823f643c24a7c2a2962c7cdef | |
parent | 8470ef9df21b22dfad7bc1dfb0bed223b7b0009d (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.am | 2 | ||||
-rw-r--r-- | common/agent.c | 343 | ||||
-rw-r--r-- | common/agent.h | 75 | ||||
-rw-r--r-- | common/meson.build | 2 | ||||
-rw-r--r-- | configure.ac | 2 | ||||
-rw-r--r-- | m4/spice-deps.m4 | 4 | ||||
-rw-r--r-- | meson.build | 2 |
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)) |