diff --git a/src/libguac/guacamole/protocol.h b/src/libguac/guacamole/protocol.h index 409a4083ea..1a690d1c04 100644 --- a/src/libguac/guacamole/protocol.h +++ b/src/libguac/guacamole/protocol.h @@ -1155,5 +1155,44 @@ guac_protocol_version guac_protocol_string_to_version(const char* version_string */ const char* guac_protocol_version_to_string(guac_protocol_version version); -#endif +/** + * Sends a usbdisconnect instruction over the given guac_socket connection, + * requesting that the client disconnect the specified USB device. + * + * @param socket + * The guac_socket connection to use. + * + * @param device_id + * The unique identifier of the USB device that should be disconnected. + * + * @return + * Zero on success, non-zero on error. + */ +int guac_protocol_send_usbdisconnect(guac_socket* socket, + const char* device_id); + +/** + * Sends a usbdata instruction over the given guac_socket connection, + * sending USB data to a specific endpoint on a client-side USB device. + * + * @param socket + * The guac_socket connection to use. + * + * @param device_id + * The unique identifier of the USB device. + * + * @param endpoint_number + * The USB endpoint number identifying the logical communication channel + * for this data transfer. The valid endpoint numbers for a device are + * provided in the interface_data parameter during usbconnect. + * + * @param data + * The base64-encoded data to send to the USB device. + * + * @return + * Zero on success, non-zero on error. + */ +int guac_protocol_send_usbdata(guac_socket* socket, const char* device_id, + int endpoint_number, const char* data); +#endif diff --git a/src/libguac/guacamole/user-fntypes.h b/src/libguac/guacamole/user-fntypes.h index 646a3b0076..fa3cac6f8e 100644 --- a/src/libguac/guacamole/user-fntypes.h +++ b/src/libguac/guacamole/user-fntypes.h @@ -499,5 +499,95 @@ typedef int guac_user_get_handler(guac_user* user, guac_object* object, typedef int guac_user_put_handler(guac_user* user, guac_object* object, guac_stream* stream, char* mimetype, char* name); -#endif +/** + * Handler for Guacamole USB connect events, invoked when a "usbconnect" + * instruction has been received from a user. This indicates that the user + * has connected a USB device via WebUSB and it is available for redirection. + * + * @param device_id + * The unique identifier for the USB device. Required. + * + * @param vendor_id + * The vendor ID of the USB device. Required. + * + * @param product_id + * The product ID of the USB device. Required. + * + * @param device_name + * The human-readable name of the device. Required (may be empty string). + * + * @param serial_number + * The serial number of the device. Optional (may be NULL or empty string + * if not available). + * + * @param device_class + * The USB device class. Required. + * + * @param device_subclass + * The USB device subclass. Required. + * + * @param device_protocol + * The USB device protocol. Required. + * + * @param interface_data + * Encoded string containing interface and endpoint information. Required. + * Format: "ifaceNum:class:subclass:protocol:ep1Num:ep1Dir:ep1Type:ep1Size;ep2...,iface2..." + * where multiple endpoints within an interface are separated by semicolons + * and multiple interfaces are separated by commas. + * + * @return + * Zero if the USB connect event was handled successfully, or non-zero if + * an error occurred. + */ +typedef int guac_user_usbconnect_handler(guac_user* user, const char* device_id, + int vendor_id, int product_id, const char* device_name, + const char* serial_number, int device_class, int device_subclass, + int device_protocol, const char* interface_data); +/** +* Handler for Guacamole USB data events, invoked when a "usbdata" instruction +* has been received from a user. This carries data from a client-side USB +* device to be processed on the server side. +* +* @param user +* The user that sent the USB data. +* +* @param device_id +* The unique identifier for the USB device. +* +* @param endpoint_number +* The USB endpoint number that the data originated from. Endpoint +* numbers correspond to the endpoints defined in the device's +* interface_data from the usbconnect instruction. +* +* @param data +* The base64-encoded USB data. +* +* @param transfer_type +* The type of USB transfer (bulk, interrupt, isochronous, control). +* +* @return +* Zero if the USB data was handled successfully, or non-zero if an error +* occurred. +*/ +typedef int guac_user_usbdata_handler(guac_user* user, const char* device_id, + int endpoint_number, const char* data, const char* transfer_type); + +/** +* Handler for Guacamole USB disconnect events, invoked when a "usbdisconnect" +* instruction has been received from a user. This indicates that the user +* has disconnected a USB device that was previously available for redirection. +* +* @param user +* The user that disconnected the USB device. +* +* @param device_id +* The unique identifier for the USB device that was disconnected. +* +* @return +* Zero if the USB disconnect event was handled successfully, or non-zero +* if an error occurred. +*/ +typedef int guac_user_usbdisconnect_handler(guac_user* user, const char* device_id); + +#endif diff --git a/src/libguac/guacamole/user.h b/src/libguac/guacamole/user.h index 13fb146d4d..fa251944d7 100644 --- a/src/libguac/guacamole/user.h +++ b/src/libguac/guacamole/user.h @@ -538,6 +538,66 @@ struct guac_user { */ guac_user_touch_handler* touch_handler; +/** + * Handler for USB connect events sent by the Guacamole web-client. + * + * The handler takes the user that connected the device, the device ID + * (required), vendor ID (required), product ID (required), device name + * (required), serial number (optional - may be NULL or empty), device class + * (required), device subclass (required), device protocol (required), and + * interface data (required). + * + * Example: + * @code + * int usbconnect_handler(guac_user* user, const char* device_id, + * int vendor_id, int product_id, const char* device_name, + * const char* serial_number, int device_class, + * int device_subclass, int device_protocol, + * const char* interface_data); + * + * int guac_user_init(guac_user* user, int argc, char** argv) { + * user->usbconnect_handler = usbconnect_handler; + * } + * @endcode + */ + guac_user_usbconnect_handler* usbconnect_handler; + + /** + * Handler for USB data events sent by the Guacamole web-client. + * + * The handler takes the user that sent the data, the device ID, + * endpoint number, the base64-encoded data, and the transfer type. + * + * Example: + * @code + * int usbdata_handler(guac_user* user, const char* device_id, + * int endpoint_number, const char* data, + * const char* transfer_type); + * + * int guac_user_init(guac_user* user, int argc, char** argv) { + * user->usbdata_handler = usbdata_handler; + * } + * @endcode + */ + guac_user_usbdata_handler* usbdata_handler; + + /** + * Handler for USB disconnect events sent by the Guacamole web-client. + * + * The handler takes the user that disconnected the device and the + * device ID. + * + * Example: + * @code + * int usbdisconnect_handler(guac_user* user, const char* device_id); + * + * int guac_user_init(guac_user* user, int argc, char** argv) { + * user->usbdisconnect_handler = usbdisconnect_handler; + * } + * @endcode + */ + guac_user_usbdisconnect_handler* usbdisconnect_handler; + }; /** @@ -1002,4 +1062,3 @@ int guac_user_parse_args_boolean(guac_user* user, const char** arg_names, const char** argv, int index, int default_value); #endif - diff --git a/src/libguac/protocol.c b/src/libguac/protocol.c index b415dac8af..b634a8abc6 100644 --- a/src/libguac/protocol.c +++ b/src/libguac/protocol.c @@ -1406,3 +1406,38 @@ const char* guac_protocol_version_to_string(guac_protocol_version version) { } +int guac_protocol_send_usbdisconnect(guac_socket* socket, + const char* device_id) { + + int ret_val; + + guac_socket_instruction_begin(socket); + ret_val = + guac_socket_write_string(socket, "13.usbdisconnect,") + || __guac_socket_write_length_string(socket, device_id) + || guac_socket_write_string(socket, ";"); + + guac_socket_instruction_end(socket); + return ret_val; + +} + +int guac_protocol_send_usbdata(guac_socket* socket, const char* device_id, + int endpoint_number, const char* data) { + + int ret_val; + + guac_socket_instruction_begin(socket); + ret_val = + guac_socket_write_string(socket, "7.usbdata,") + || __guac_socket_write_length_string(socket, device_id) + || guac_socket_write_string(socket, ",") + || __guac_socket_write_length_int(socket, endpoint_number) + || guac_socket_write_string(socket, ",") + || __guac_socket_write_length_string(socket, data) + || guac_socket_write_string(socket, ";"); + + guac_socket_instruction_end(socket); + return ret_val; + +} \ No newline at end of file diff --git a/src/libguac/user-handlers.c b/src/libguac/user-handlers.c index b9013bc7c2..179bac76b0 100644 --- a/src/libguac/user-handlers.c +++ b/src/libguac/user-handlers.c @@ -55,6 +55,9 @@ __guac_instruction_handler_mapping __guac_instruction_handler_map[] = { {"audio", __guac_handle_audio}, {"argv", __guac_handle_argv}, {"nop", __guac_handle_nop}, + {"usbconnect", __guac_handle_usbconnect}, + {"usbdata", __guac_handle_usbdata}, + {"usbdisconnect", __guac_handle_usbdisconnect}, {NULL, NULL} }; @@ -627,6 +630,83 @@ int __guac_handle_disconnect(guac_user* user, int argc, char** argv) { return 0; } +int __guac_handle_usbconnect(guac_user* user, int argc, char** argv) { + + if (argc != 9) { + guac_user_log(user, GUAC_LOG_WARNING, "Received \"usbconnect\" " + "instruction with wrong number of arguments (expected 9, got %d).", + argc); + return 0; + } + + if (user->usbconnect_handler) + return user->usbconnect_handler( + user, + argv[0], /* device_id */ + atoi(argv[1]), /* vendor_id */ + atoi(argv[2]), /* product_id */ + argv[3], /* device_name */ + argv[4], /* serial_number */ + atoi(argv[5]), /* device_class */ + atoi(argv[6]), /* device_subclass */ + atoi(argv[7]), /* device_protocol */ + argv[8] /* interface_data */ + ); + + guac_user_log(user, GUAC_LOG_DEBUG, "USB redirection not supported, " + "ignoring usbconnect instruction."); + return 0; + +} + +int __guac_handle_usbdata(guac_user* user, int argc, char** argv) { + + if (argc != 4) { + guac_user_log(user, GUAC_LOG_WARNING, "Received \"usbdata\" " + "instruction with wrong number of arguments (expected 4, got %d).", + argc); + return 0; + } + + if (user->usbdata_handler) { + /* Decode base64 data */ + guac_protocol_decode_base64(argv[2]); + return user->usbdata_handler( + user, + argv[0], /* device_id */ + atoi(argv[1]), /* endpoint_number */ + argv[2], /* data (now decoded) */ + argv[3] /* transfer_type */ + ); + } + + guac_user_log(user, GUAC_LOG_DEBUG, "USB redirection not supported, " + "ignoring usbdata instruction."); + return 0; + +} + +int __guac_handle_usbdisconnect(guac_user* user, int argc, char** argv) { + + if (argc != 1) { + guac_user_log(user, GUAC_LOG_WARNING, "Received \"usbdisconnect\" " + "instruction with wrong number of arguments (expected 1, got %d).", + argc); + return 0; + } + + if (user->usbdisconnect_handler) + return user->usbdisconnect_handler( + user, + argv[0] /* device_id */ + ); + + guac_user_log(user, GUAC_LOG_DEBUG, "USB redirection not supported, " + "ignoring usbdisconnect instruction."); + return 0; + +} + /* Guacamole handshake handler functions. */ int __guac_handshake_size_handler(guac_user* user, int argc, char** argv) { @@ -777,4 +857,3 @@ int __guac_user_call_opcode_handler(__guac_instruction_handler_mapping* map, return 0; } - diff --git a/src/libguac/user-handlers.h b/src/libguac/user-handlers.h index 2f3ecbe9b3..c55d667810 100644 --- a/src/libguac/user-handlers.h +++ b/src/libguac/user-handlers.h @@ -231,6 +231,27 @@ __guac_instruction_handler __guac_handshake_name_handler; */ __guac_instruction_handler __guac_handshake_timezone_handler; +/** + * Internal initial handler for the usbconnect instruction. When a usbconnect + * instruction is received, this handler will be called. The user's + * usbconnect handler will be invoked if defined. + */ +__guac_instruction_handler __guac_handle_usbconnect; + +/** + * Internal initial handler for the usbdata instruction. When a usbdata + * instruction is received, this handler will be called. The user's + * usbdata handler will be invoked if defined. + */ +__guac_instruction_handler __guac_handle_usbdata; + +/** + * Internal initial handler for the usbdisconnect instruction. When a + * usbdisconnect instruction is received, this handler will be called. + * The user's usbdisconnect handler will be invoked if defined. + */ +__guac_instruction_handler __guac_handle_usbdisconnect; + /** * Instruction handler mapping table. This is a NULL-terminated array of * __guac_instruction_handler_mapping structures, each mapping an opcode