mirror of
https://github.com/gnif/LookingGlass.git
synced 2026-08-22 07:01:30 +00:00
515 lines
13 KiB
C
515 lines
13 KiB
C
/**
|
|
* Looking Glass
|
|
* Copyright © 2017-2026 The Looking Glass Authors
|
|
* https://looking-glass.io
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the Free
|
|
* Software Foundation; either version 2 of the License, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program 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 General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc., 59
|
|
* Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "usbredir.h"
|
|
|
|
#include "common/debug.h"
|
|
#include "common/time.h"
|
|
|
|
#include <usbredirparser.h>
|
|
|
|
#include <stdatomic.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#define USB_REDIR_CHANNEL_COUNT 256
|
|
#define USB_REDIR_DISCONNECT_TIMEOUT_NS INT64_C(500000000)
|
|
#define USB_REDIR_RECONNECT_DELAY_NS INT64_C(250000000)
|
|
#define USB_REDIR_MAX_OUTPUT_BYTES UINT64_C(1048576)
|
|
|
|
struct LG_USBRedir
|
|
{
|
|
const LG_USBRedirDeviceOps * deviceOps;
|
|
void * deviceOpaque;
|
|
LG_USBRedirStatusFn status;
|
|
void * statusOpaque;
|
|
|
|
PSUSBRedirChannel * channels[USB_REDIR_CHANNEL_COUNT];
|
|
PSUSBRedirChannel * channel;
|
|
struct usbredirparser * parser;
|
|
|
|
const uint8_t * readData;
|
|
size_t readSize;
|
|
size_t readOffset;
|
|
|
|
atomic_bool desiredPlugged;
|
|
atomic_bool available;
|
|
bool plugged;
|
|
bool disconnectPending;
|
|
int64_t disconnectDeadline;
|
|
int64_t reconnectDeadline;
|
|
uint64_t mainPingCount;
|
|
bool waitingForMainPing;
|
|
};
|
|
|
|
static void setAvailable(LG_USBRedir * usbredir, bool available)
|
|
{
|
|
const bool previous = atomic_exchange_explicit(
|
|
&usbredir->available, available, memory_order_acq_rel);
|
|
if (previous != available && usbredir->status)
|
|
usbredir->status(usbredir->statusOpaque, available);
|
|
}
|
|
|
|
static int readUSBRedir(void * opaque, uint8_t * data, int count)
|
|
{
|
|
LG_USBRedir * usbredir = opaque;
|
|
const size_t remaining = usbredir->readSize - usbredir->readOffset;
|
|
if (remaining < (size_t)count)
|
|
count = (int)remaining;
|
|
|
|
if (!count)
|
|
return 0;
|
|
|
|
memcpy(data, usbredir->readData + usbredir->readOffset, count);
|
|
usbredir->readOffset += count;
|
|
return count;
|
|
}
|
|
|
|
static int writeUSBRedir(void * opaque, uint8_t * data, int count)
|
|
{
|
|
LG_USBRedir * usbredir = opaque;
|
|
if (!usbredir->channel)
|
|
return -1;
|
|
|
|
const ssize_t written = purespice_usbRedirWriteNonblocking(
|
|
usbredir->channel, data, count);
|
|
return written >= 0 && written <= count ? (int)written : -1;
|
|
}
|
|
|
|
static void logUSBRedir(void * opaque, int level, const char * message)
|
|
{
|
|
(void)opaque;
|
|
switch (level)
|
|
{
|
|
case usbredirparser_error:
|
|
DEBUG_ERROR("USB redirection: %s", message);
|
|
break;
|
|
|
|
case usbredirparser_warning:
|
|
DEBUG_WARN("USB redirection: %s", message);
|
|
break;
|
|
|
|
case usbredirparser_info:
|
|
DEBUG_INFO("USB redirection: %s", message);
|
|
break;
|
|
|
|
case usbredirparser_debug:
|
|
case usbredirparser_debug_data:
|
|
DEBUG_TRACE("USB redirection: %s", message);
|
|
break;
|
|
|
|
case usbredirparser_none:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void helloUSBRedir(void * opaque,
|
|
struct usb_redir_hello_header * hello)
|
|
{
|
|
(void)hello;
|
|
LG_USBRedir * usbredir = opaque;
|
|
setAvailable(usbredir, true);
|
|
}
|
|
|
|
static void deviceDisconnectAck(void * opaque)
|
|
{
|
|
LG_USBRedir * usbredir = opaque;
|
|
usbredir->disconnectPending = false;
|
|
usbredir->disconnectDeadline = 0;
|
|
}
|
|
|
|
static void unplugDevice(LG_USBRedir * usbredir)
|
|
{
|
|
if (!usbredir->plugged)
|
|
return;
|
|
|
|
usbredir->deviceOps->unplug(usbredir->deviceOpaque);
|
|
usbredir->plugged = false;
|
|
}
|
|
|
|
static void destroyParser(LG_USBRedir * usbredir)
|
|
{
|
|
setAvailable(usbredir, false);
|
|
unplugDevice(usbredir);
|
|
usbredir->disconnectPending = false;
|
|
usbredir->disconnectDeadline = 0;
|
|
|
|
if (!usbredir->parser)
|
|
return;
|
|
|
|
usbredirparser_destroy(usbredir->parser);
|
|
usbredir->parser = NULL;
|
|
}
|
|
|
|
static bool flushUSBRedir(LG_USBRedir * usbredir)
|
|
{
|
|
if (!usbredir->parser)
|
|
return true;
|
|
|
|
if (usbredirparser_do_write(usbredir->parser) != 0)
|
|
return false;
|
|
|
|
const uint64_t buffered =
|
|
usbredirparser_get_bufferered_output_size(usbredir->parser);
|
|
if (buffered <= USB_REDIR_MAX_OUTPUT_BYTES)
|
|
return true;
|
|
|
|
DEBUG_ERROR("USB redirection output queue exceeded %u bytes",
|
|
(unsigned int)USB_REDIR_MAX_OUTPUT_BYTES);
|
|
return false;
|
|
}
|
|
|
|
static bool connectChannel(LG_USBRedir * usbredir,
|
|
PSUSBRedirChannel * channel)
|
|
{
|
|
usbredir->channel = channel;
|
|
if (purespice_usbRedirConnect(channel))
|
|
return true;
|
|
|
|
DEBUG_WARN("Failed to connect USB redirection channel %u",
|
|
purespice_usbRedirChannelId(channel));
|
|
usbredir->channel = NULL;
|
|
return false;
|
|
}
|
|
|
|
static bool selectChannel(LG_USBRedir * usbredir)
|
|
{
|
|
if (usbredir->channel)
|
|
return true;
|
|
|
|
if (!purespice_channelConnected(PS_CHANNEL_MAIN))
|
|
{
|
|
usbredir->reconnectDeadline =
|
|
(int64_t)nanotime() + USB_REDIR_RECONNECT_DELAY_NS;
|
|
return false;
|
|
}
|
|
|
|
if (usbredir->waitingForMainPing)
|
|
{
|
|
if (purespice_mainPingCount() == usbredir->mainPingCount)
|
|
{
|
|
usbredir->reconnectDeadline =
|
|
(int64_t)nanotime() + USB_REDIR_RECONNECT_DELAY_NS;
|
|
return false;
|
|
}
|
|
|
|
usbredir->waitingForMainPing = false;
|
|
}
|
|
|
|
for (unsigned int i = 0; i < USB_REDIR_CHANNEL_COUNT; ++i)
|
|
{
|
|
PSUSBRedirChannel * channel = usbredir->channels[i];
|
|
if (!channel || !purespice_usbRedirAvailable(channel))
|
|
continue;
|
|
|
|
if (connectChannel(usbredir, channel))
|
|
{
|
|
usbredir->reconnectDeadline = 0;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
usbredir->reconnectDeadline =
|
|
(int64_t)nanotime() + USB_REDIR_RECONNECT_DELAY_NS;
|
|
return false;
|
|
}
|
|
|
|
static void resetChannel(LG_USBRedir * usbredir)
|
|
{
|
|
PSUSBRedirChannel * channel = usbredir->channel;
|
|
usbredir->channel = NULL;
|
|
destroyParser(usbredir);
|
|
|
|
if (channel && purespice_usbRedirConnected(channel))
|
|
purespice_usbRedirDisconnect(channel);
|
|
|
|
usbredir->reconnectDeadline =
|
|
(int64_t)nanotime() + USB_REDIR_RECONNECT_DELAY_NS;
|
|
}
|
|
|
|
static bool createParser(LG_USBRedir * usbredir)
|
|
{
|
|
if (usbredir->parser)
|
|
return true;
|
|
|
|
usbredir->parser = usbredirparser_create();
|
|
if (!usbredir->parser)
|
|
{
|
|
DEBUG_ERROR("Failed to create the USB redirection parser");
|
|
return false;
|
|
}
|
|
|
|
usbredir->deviceOps->setup(usbredir->deviceOpaque, usbredir->parser);
|
|
usbredir->parser->priv = usbredir;
|
|
usbredir->parser->log_func = logUSBRedir;
|
|
usbredir->parser->read_func = readUSBRedir;
|
|
usbredir->parser->write_func = writeUSBRedir;
|
|
usbredir->parser->hello_func = helloUSBRedir;
|
|
usbredir->parser->device_disconnect_ack_func = deviceDisconnectAck;
|
|
|
|
uint32_t caps[USB_REDIR_CAPS_SIZE] = { 0 };
|
|
usbredirparser_caps_set_cap(caps,
|
|
usb_redir_cap_device_disconnect_ack);
|
|
usbredirparser_caps_set_cap(caps,
|
|
usb_redir_cap_connect_device_version);
|
|
usbredirparser_caps_set_cap(caps,
|
|
usb_redir_cap_ep_info_max_packet_size);
|
|
usbredirparser_caps_set_cap(caps, usb_redir_cap_64bits_ids);
|
|
usbredirparser_caps_set_cap(caps, usb_redir_cap_32bits_bulk_length);
|
|
usbredirparser_init(usbredir->parser, "Looking Glass", caps,
|
|
USB_REDIR_CAPS_SIZE, usbredirparser_fl_usb_host);
|
|
|
|
if (flushUSBRedir(usbredir))
|
|
return true;
|
|
|
|
DEBUG_ERROR("Failed to send the USB redirection hello packet");
|
|
destroyParser(usbredir);
|
|
return false;
|
|
}
|
|
|
|
LG_USBRedir * lgUsbRedir_create(
|
|
const LG_USBRedirDeviceOps * deviceOps, void * deviceOpaque,
|
|
LG_USBRedirStatusFn status, void * statusOpaque)
|
|
{
|
|
if (!deviceOps || !deviceOps->setup || !deviceOps->plug ||
|
|
!deviceOps->unplug)
|
|
return NULL;
|
|
|
|
LG_USBRedir * usbredir = calloc(1, sizeof(*usbredir));
|
|
if (!usbredir)
|
|
return NULL;
|
|
|
|
usbredir->deviceOps = deviceOps;
|
|
usbredir->deviceOpaque = deviceOpaque;
|
|
usbredir->status = status;
|
|
usbredir->statusOpaque = statusOpaque;
|
|
atomic_init(&usbredir->desiredPlugged, false);
|
|
atomic_init(&usbredir->available, false);
|
|
return usbredir;
|
|
}
|
|
|
|
void lgUsbRedir_destroy(LG_USBRedir * usbredir)
|
|
{
|
|
if (!usbredir)
|
|
return;
|
|
|
|
destroyParser(usbredir);
|
|
free(usbredir);
|
|
}
|
|
|
|
void lgUsbRedir_setPlugged(LG_USBRedir * usbredir, bool plugged)
|
|
{
|
|
atomic_store_explicit(&usbredir->desiredPlugged, plugged,
|
|
memory_order_release);
|
|
}
|
|
|
|
bool lgUsbRedir_available(const LG_USBRedir * usbredir)
|
|
{
|
|
return atomic_load_explicit(&usbredir->available, memory_order_acquire);
|
|
}
|
|
|
|
bool lgUsbRedir_disconnectPending(const LG_USBRedir * usbredir)
|
|
{
|
|
return usbredir->disconnectPending;
|
|
}
|
|
|
|
static bool processUSBRedir(LG_USBRedir * usbredir, bool recover)
|
|
{
|
|
const int64_t now = (int64_t)nanotime();
|
|
if (!usbredir->channel &&
|
|
now >= usbredir->reconnectDeadline)
|
|
selectChannel(usbredir);
|
|
|
|
if (!usbredir->parser ||
|
|
!atomic_load_explicit(&usbredir->available, memory_order_acquire))
|
|
{
|
|
const bool result = flushUSBRedir(usbredir);
|
|
if (!result && recover)
|
|
resetChannel(usbredir);
|
|
return result;
|
|
}
|
|
|
|
bool result = flushUSBRedir(usbredir);
|
|
if (!result)
|
|
{
|
|
if (recover)
|
|
resetChannel(usbredir);
|
|
return false;
|
|
}
|
|
|
|
if (usbredir->disconnectPending)
|
|
{
|
|
if (usbredir->disconnectDeadline &&
|
|
now >= usbredir->disconnectDeadline)
|
|
{
|
|
DEBUG_WARN("USB redirection disconnect acknowledgement timed out");
|
|
resetChannel(usbredir);
|
|
return true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
const bool desired = atomic_load_explicit(&usbredir->desiredPlugged,
|
|
memory_order_acquire);
|
|
if (desired != usbredir->plugged)
|
|
{
|
|
if (desired)
|
|
{
|
|
usbredir->deviceOps->plug(
|
|
usbredir->deviceOpaque, usbredir->parser);
|
|
usbredir->plugged = true;
|
|
}
|
|
else
|
|
{
|
|
unplugDevice(usbredir);
|
|
usbredir->disconnectPending = usbredirparser_peer_has_cap(
|
|
usbredir->parser, usb_redir_cap_device_disconnect_ack);
|
|
usbredir->disconnectDeadline = usbredir->disconnectPending ?
|
|
now + USB_REDIR_DISCONNECT_TIMEOUT_NS : 0;
|
|
usbredirparser_send_device_disconnect(usbredir->parser);
|
|
}
|
|
|
|
result = flushUSBRedir(usbredir);
|
|
if (!result)
|
|
{
|
|
if (recover)
|
|
resetChannel(usbredir);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (usbredir->plugged && usbredir->deviceOps->process)
|
|
{
|
|
const bool writable =
|
|
usbredirparser_has_data_to_write(usbredir->parser) == 0 &&
|
|
purespice_usbRedirWritable(usbredir->channel);
|
|
usbredir->deviceOps->process(usbredir->deviceOpaque, writable);
|
|
}
|
|
|
|
result = flushUSBRedir(usbredir);
|
|
if (!result && recover)
|
|
resetChannel(usbredir);
|
|
return result;
|
|
}
|
|
|
|
bool lgUsbRedir_process(LG_USBRedir * usbredir)
|
|
{
|
|
return processUSBRedir(usbredir, true);
|
|
}
|
|
|
|
void lgUsbRedir_state(PSUSBRedirChannel * channel,
|
|
PSUSBRedirState state, void * opaque)
|
|
{
|
|
LG_USBRedir * usbredir = opaque;
|
|
const uint8_t id = purespice_usbRedirChannelId(channel);
|
|
|
|
switch (state)
|
|
{
|
|
case PS_USB_REDIR_AVAILABLE:
|
|
usbredir->channels[id] = channel;
|
|
selectChannel(usbredir);
|
|
break;
|
|
|
|
case PS_USB_REDIR_CONNECTED:
|
|
if (channel != usbredir->channel)
|
|
{
|
|
purespice_usbRedirDisconnect(channel);
|
|
break;
|
|
}
|
|
|
|
if (!createParser(usbredir))
|
|
purespice_usbRedirDisconnect(channel);
|
|
break;
|
|
|
|
case PS_USB_REDIR_DISCONNECTED:
|
|
if (channel == usbredir->channel)
|
|
{
|
|
destroyParser(usbredir);
|
|
usbredir->channel = NULL;
|
|
if (purespice_usbRedirAvailable(channel))
|
|
{
|
|
/* Wait for a fresh MAIN PING/PONG before reconnecting this
|
|
* optional channel. The server shuts it down before MAIN during a
|
|
* session teardown, so this prevents reconnect attempts to a
|
|
* disappearing SPICE server. */
|
|
usbredir->mainPingCount = purespice_mainPingCount();
|
|
usbredir->waitingForMainPing = true;
|
|
usbredir->reconnectDeadline =
|
|
(int64_t)nanotime() + USB_REDIR_RECONNECT_DELAY_NS;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case PS_USB_REDIR_UNAVAILABLE:
|
|
if (usbredir->channels[id] == channel)
|
|
usbredir->channels[id] = NULL;
|
|
|
|
if (channel == usbredir->channel)
|
|
{
|
|
destroyParser(usbredir);
|
|
usbredir->channel = NULL;
|
|
selectChannel(usbredir);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool lgUsbRedir_data(PSUSBRedirChannel * channel,
|
|
const uint8_t * data, size_t size, void * opaque)
|
|
{
|
|
LG_USBRedir * usbredir = opaque;
|
|
if (channel != usbredir->channel || !usbredir->parser ||
|
|
usbredir->readData)
|
|
return false;
|
|
|
|
usbredir->readData = data;
|
|
usbredir->readSize = size;
|
|
usbredir->readOffset = 0;
|
|
|
|
const int result = usbredirparser_do_read(usbredir->parser);
|
|
const bool consumed = usbredir->readOffset == usbredir->readSize;
|
|
usbredir->readData = NULL;
|
|
usbredir->readSize = 0;
|
|
usbredir->readOffset = 0;
|
|
|
|
if (result == usbredirparser_read_parse_error)
|
|
{
|
|
DEBUG_ERROR("Invalid USB redirection packet");
|
|
return false;
|
|
}
|
|
|
|
if (!consumed)
|
|
{
|
|
DEBUG_ERROR("USB redirection parser did not consume its input");
|
|
return false;
|
|
}
|
|
|
|
/* Returning false lets PureSpice disconnect after this callback unwinds;
|
|
* resetting it here would destroy the channel during its own dispatch. */
|
|
return processUSBRedir(usbredir, false);
|
|
}
|
|
|
|
void * lgUsbRedir_device(void * parserOpaque)
|
|
{
|
|
LG_USBRedir * usbredir = parserOpaque;
|
|
return usbredir->deviceOpaque;
|
|
}
|