Skip to content

Commit e5c7912

Browse files
committed
GUACAMOLE-2281: Add generic req/done/cancel instructions to libguac.
Adds a generic request/response RPC primitive to the Guacamole protocol in the form of three instructions: "req", "done", and "cancel". A "req" instruction initiates a typed request with three arguments: a correlation id, a method name (by convention namespaced like "<feature>.<verb>"), and an opaque payload. The receiving side is expected to settle the request with a "done" instruction carrying the same id, a status (typically "ok", "error", or "canceled"), and a response payload. Either side may issue a "cancel" with the matching id to abort an in-flight request before its "done" arrives. The instructions themselves carry no feature-specific semantics; the method name argument identifies what kind of request is being made, and the payload is opaque to libguac. This leaves room for any RPC-style feature to share the same plumbing without needing its own dedicated opcodes. Adds the libguac surface for the new primitive: * guac_protocol_send_req, guac_protocol_send_done, and guac_protocol_send_cancel in protocol.h / protocol.c. * guac_user_req_handler, guac_user_done_handler, and guac_user_cancel_handler typedefs in user-fntypes.h. * req_handler, done_handler, and cancel_handler callback fields on guac_user in user.h. * __guac_handle_req, __guac_handle_done, and __guac_handle_cancel dispatchers in user-handlers.c, registered in the instruction handler map. The first intended consumer is WebAuthn passthrough, which will use this primitive to relay ceremonies between protocol plugins and the user's local authenticator with method names "webauthn.create" and "webauthn.get". The primitive is deliberately generic so other RPC-style features can adopt it without adding more opcodes to the protocol vocabulary.
1 parent e775052 commit e5c7912

8 files changed

Lines changed: 641 additions & 10 deletions

File tree

src/libguac/guacamole/protocol.h

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
* @file protocol.h
2929
*/
3030

31+
#include "client-types.h"
3132
#include "layer-types.h"
3233
#include "object-types.h"
3334
#include "protocol-constants.h"
@@ -1119,6 +1120,91 @@ int guac_protocol_send_clipboard(guac_socket* socket, const guac_stream* stream,
11191120
*/
11201121
int guac_protocol_send_name(guac_socket* socket, const char* name);
11211122

1123+
/**
1124+
* Sends a "req" instruction along the given client's broadcast socket,
1125+
* initiating a typed request/response exchange with the peer. The body
1126+
* (if any) is shipped first over a reserved-mimetype pipe stream whose
1127+
* name is the correlation id; the trailing "req" carries only the id
1128+
* and method name. The peer is expected to settle the request with a
1129+
* "done" instruction carrying the same id, or either side may issue a
1130+
* "cancel" with the same id to abort.
1131+
*
1132+
* @param client
1133+
* The guac_client whose broadcast socket should be used. A stream is
1134+
* allocated against this client for the duration of the body
1135+
* transfer and freed before this function returns.
1136+
*
1137+
* @param id
1138+
* A correlation identifier (typically a UUID string) that uniquely
1139+
* identifies this request/response exchange.
1140+
*
1141+
* @param name
1142+
* The method name identifying what kind of request this is.
1143+
*
1144+
* @param body
1145+
* The request body, opaque to libguac. May be NULL if body_len is
1146+
* zero.
1147+
*
1148+
* @param body_len
1149+
* The length, in bytes, of the request body. May be zero.
1150+
*
1151+
* @return
1152+
* Zero on success, non-zero on error.
1153+
*/
1154+
int guac_protocol_send_req(guac_client* client, const char* id,
1155+
const char* name, const void* body, int body_len);
1156+
1157+
/**
1158+
* Sends a "done" instruction along the given client's broadcast socket,
1159+
* settling a previously-received "req" with a status. The response body
1160+
* (if any) is shipped first over a reserved-mimetype pipe stream whose
1161+
* name is the correlation id; the trailing "done" carries only the id
1162+
* and status.
1163+
*
1164+
* @param client
1165+
* The guac_client whose broadcast socket should be used. A stream is
1166+
* allocated against this client for the duration of the body
1167+
* transfer and freed before this function returns.
1168+
*
1169+
* @param id
1170+
* The correlation identifier of the request being settled.
1171+
*
1172+
* @param status
1173+
* A short status indicator. By convention, "ok" indicates success,
1174+
* "error" indicates a method-level failure (with details in the
1175+
* body), and "canceled" indicates the request was aborted.
1176+
*
1177+
* @param body
1178+
* The response body, opaque to libguac. May be NULL if body_len is
1179+
* zero.
1180+
*
1181+
* @param body_len
1182+
* The length, in bytes, of the response body. May be zero.
1183+
*
1184+
* @return
1185+
* Zero on success, non-zero on error.
1186+
*/
1187+
int guac_protocol_send_done(guac_client* client, const char* id,
1188+
const char* status, const void* body, int body_len);
1189+
1190+
/**
1191+
* Sends a "cancel" instruction along the given guac_socket connection,
1192+
* requesting that a previously-issued "req" instruction be aborted before
1193+
* its "done" arrives. Either side may issue a "cancel"; the peer may
1194+
* still ultimately respond with a "done" if the cancellation arrives
1195+
* after the response was already in flight.
1196+
*
1197+
* @param socket
1198+
* The guac_socket connection to use.
1199+
*
1200+
* @param id
1201+
* The correlation identifier of the request to cancel.
1202+
*
1203+
* @return
1204+
* Zero on success, non-zero on error.
1205+
*/
1206+
int guac_protocol_send_cancel(guac_socket* socket, const char* id);
1207+
11221208
/**
11231209
* Decodes the given base64-encoded string in-place. The base64 string must
11241210
* be NULL-terminated.

src/libguac/guacamole/user-constants.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,5 +64,24 @@
6464
*/
6565
#define GUAC_USER_STREAM_INDEX_MIMETYPE "application/vnd.glyptodon.guacamole.stream-index+json"
6666

67+
/**
68+
* The mimetype reserved for a pipe stream that carries the body of a "req"
69+
* or "done" instruction. The pipe's name is the correlation id of the
70+
* associated request/response exchange; libguac buffers the body and
71+
* delivers it as the body argument to the user's req or done handler
72+
* when the matching instruction arrives. Only one payload may be in
73+
* flight per user at a time; the convention is that senders emit the
74+
* pipe, its blobs, the end, and the trailing req or done as an
75+
* uninterrupted sequence.
76+
*/
77+
#define GUAC_USER_RPC_PAYLOAD_MIMETYPE "application/x-guacamole-rpc-payload"
78+
79+
/**
80+
* The maximum size, in bytes, of any single "req" or "done" body buffered
81+
* by libguac. Bodies larger than this are rejected at the pipe layer
82+
* before reaching the user's handler.
83+
*/
84+
#define GUAC_USER_MAX_RPC_BODY_LENGTH 65536
85+
6786
#endif
6887

src/libguac/guacamole/user-fntypes.h

Lines changed: 101 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,7 @@ typedef int guac_user_put_handler(guac_user* user, guac_object* object,
500500
guac_stream* stream, char* mimetype, char* name);
501501

502502
/**
503-
* Handler for Guacamole USB connect events, invoked when a "usbconnect"
503+
* Handler for Guacamole USB connect events, invoked when a "usbconnect"
504504
* instruction has been received from a user. This indicates that the user
505505
* has connected a USB device via WebUSB and it is available for redirection.
506506
*
@@ -540,10 +540,109 @@ typedef int guac_user_put_handler(guac_user* user, guac_object* object,
540540
* an error occurred.
541541
*/
542542
typedef int guac_user_usbconnect_handler(guac_user* user, const char* device_id,
543-
int vendor_id, int product_id, const char* device_name,
543+
int vendor_id, int product_id, const char* device_name,
544544
const char* serial_number, int device_class, int device_subclass,
545545
int device_protocol, const char* interface_data);
546546

547+
/**
548+
* Handler for Guacamole "req" events, invoked when a "req" instruction has
549+
* been received from a user. A "req" initiates a typed request/response
550+
* exchange that is settled by a later "done" instruction carrying the
551+
* same id, or aborted by a "cancel" with the same id.
552+
*
553+
* The body of the request is carried by a paired pipe stream whose name
554+
* matches the correlation id and whose mimetype is
555+
* GUAC_USER_RPC_PAYLOAD_MIMETYPE. libguac buffers that pipe internally
556+
* and delivers the assembled body inline via the body and body_len
557+
* parameters; the buffer is owned by libguac and remains valid only
558+
* for the duration of this handler call. If the request body is empty,
559+
* body will be NULL and body_len will be zero.
560+
*
561+
* Example:
562+
* @code
563+
* int req_handler(guac_user* user, char* id, char* name,
564+
* void* body, int body_len);
565+
*
566+
* int guac_user_init(guac_user* user, int argc, char** argv) {
567+
* user->req_handler = req_handler;
568+
* }
569+
* @endcode
570+
*
571+
* @param user
572+
* The user that sent the request.
573+
*
574+
* @param id
575+
* The correlation identifier of this request.
576+
*
577+
* @param name
578+
* The method name identifying what kind of request this is.
579+
*
580+
* @param body
581+
* The request body assembled by libguac from the paired pipe stream,
582+
* or NULL if the body was empty.
583+
*
584+
* @param body_len
585+
* The length, in bytes, of the request body.
586+
*
587+
* @return
588+
* Zero if the request was handled successfully, non-zero otherwise.
589+
*/
590+
typedef int guac_user_req_handler(guac_user* user, char* id, char* name,
591+
void* body, int body_len);
592+
593+
/**
594+
* Handler for Guacamole "done" events, invoked when a "done" instruction
595+
* has been received from a user. A "done" settles a previously-issued
596+
* "req" with a status.
597+
*
598+
* The body of the response is carried by a paired pipe stream whose name
599+
* matches the correlation id and whose mimetype is
600+
* GUAC_USER_RPC_PAYLOAD_MIMETYPE. libguac buffers that pipe internally
601+
* and delivers the assembled body inline via the body and body_len
602+
* parameters; the buffer is owned by libguac and remains valid only
603+
* for the duration of this handler call. If the response body is empty,
604+
* body will be NULL and body_len will be zero.
605+
*
606+
* @param user
607+
* The user that sent the response.
608+
*
609+
* @param id
610+
* The correlation identifier of the request being settled.
611+
*
612+
* @param status
613+
* The status of the request. By convention "ok", "error", or
614+
* "canceled".
615+
*
616+
* @param body
617+
* The response body assembled by libguac from the paired pipe
618+
* stream, or NULL if the body was empty.
619+
*
620+
* @param body_len
621+
* The length, in bytes, of the response body.
622+
*
623+
* @return
624+
* Zero if the response was handled successfully, non-zero otherwise.
625+
*/
626+
typedef int guac_user_done_handler(guac_user* user, char* id, char* status,
627+
void* body, int body_len);
628+
629+
/**
630+
* Handler for Guacamole "cancel" events, invoked when a "cancel"
631+
* instruction has been received from a user. A "cancel" requests that a
632+
* previously-issued "req" be aborted before its "done" arrives.
633+
*
634+
* @param user
635+
* The user that sent the cancellation.
636+
*
637+
* @param id
638+
* The correlation identifier of the request to cancel.
639+
*
640+
* @return
641+
* Zero if the cancellation was handled successfully, non-zero
642+
* otherwise.
643+
*/
644+
typedef int guac_user_cancel_handler(guac_user* user, char* id);
645+
547646
/**
548647
* Handler for Guacamole USB data events, invoked when a "usbdata" instruction
549648
* has been received from a user. This carries data from a client-side USB

src/libguac/guacamole/user.h

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,81 @@ struct guac_user {
598598
*/
599599
guac_user_usbdisconnect_handler* usbdisconnect_handler;
600600

601+
/**
602+
* Handler for "req" events sent by the Guacamole web-client. A "req"
603+
* initiates a typed request/response exchange that is settled by a
604+
* later "done" with the same id, or aborted by a "cancel" with the
605+
* same id. The request body, if any, is assembled by libguac from
606+
* the paired payload pipe and delivered inline to this handler.
607+
*
608+
* Example:
609+
* @code
610+
* int req_handler(guac_user* user, char* id, char* name,
611+
* void* body, int body_len);
612+
*
613+
* int guac_user_init(guac_user* user, int argc, char** argv) {
614+
* user->req_handler = req_handler;
615+
* }
616+
* @endcode
617+
*/
618+
guac_user_req_handler* req_handler;
619+
620+
/**
621+
* Handler for "done" events sent by the Guacamole web-client,
622+
* settling a previously-issued "req" with status. The response body,
623+
* if any, is assembled by libguac from the paired payload pipe and
624+
* delivered inline to this handler.
625+
*
626+
* Example:
627+
* @code
628+
* int done_handler(guac_user* user, char* id, char* status,
629+
* void* body, int body_len);
630+
*
631+
* int guac_user_init(guac_user* user, int argc, char** argv) {
632+
* user->done_handler = done_handler;
633+
* }
634+
* @endcode
635+
*/
636+
guac_user_done_handler* done_handler;
637+
638+
/**
639+
* Handler for "cancel" events sent by the Guacamole web-client,
640+
* aborting a previously-issued "req" before its "done" arrives.
641+
*
642+
* Example:
643+
* @code
644+
* int cancel_handler(guac_user* user, char* id);
645+
*
646+
* int guac_user_init(guac_user* user, int argc, char** argv) {
647+
* user->cancel_handler = cancel_handler;
648+
* }
649+
* @endcode
650+
*/
651+
guac_user_cancel_handler* cancel_handler;
652+
653+
/**
654+
* Correlation id of the "req" or "done" payload pipe most recently
655+
* assembled and not yet consumed by its matching instruction.
656+
* malloc'd and NULL-terminated, or NULL if no body is buffered.
657+
* Used internally by libguac; not intended for direct use by user
658+
* code.
659+
*/
660+
char* __rpc_pending_id;
661+
662+
/**
663+
* Body bytes accompanying __rpc_pending_id. malloc'd, not
664+
* NULL-terminated, and undefined when __rpc_pending_id is NULL. Used
665+
* internally by libguac; not intended for direct use by user code.
666+
*/
667+
char* __rpc_pending_body;
668+
669+
/**
670+
* Length, in bytes, of __rpc_pending_body. Undefined when
671+
* __rpc_pending_id is NULL. Used internally by libguac; not intended
672+
* for direct use by user code.
673+
*/
674+
int __rpc_pending_body_len;
675+
601676
};
602677

603678
/**

0 commit comments

Comments
 (0)