Skip to content

Commit 4eca0a8

Browse files
committed
GUACAMOLE-2281: Add generic req/done/cancel instructions to libguac.
Adds three new protocol instructions in libguac: * "req" - id, name * "done" - id, status * "cancel" - id "req" starts a typed request/response exchange with a correlation id and a method name (by convention "<feature>.<verb>"). The peer settles it with a "done" carrying the same id and a status, typically "ok", "error", or "canceled". Either side can issue a "cancel" with the matching id to abort an in-flight exchange. The bodies of "req" and "done" ride a paired pipe whose name is the correlation id and whose mimetype is GUAC_USER_RPC_PAYLOAD_MIMETYPE ("application/x-guacamole-rpc-payload"). libguac buffers the body under that id and hands it to the user's req or done handler when the matching instruction arrives. Only one body can be in flight per user; senders should emit the pipe, its blobs, the end, and the trailing req or done back to back. The body is capped at GUAC_USER_MAX_RPC_BODY_LENGTH (64 KiB). API surface: * guac_protocol_send_req(client, id, name, body, body_len) and guac_protocol_send_done(client, id, status, body, body_len) in protocol.h / protocol.c. Each allocates a stream against the client, ships the body over the paired pipe, then sends the trailing instruction. Body may be NULL with body_len 0. * guac_protocol_send_cancel(socket, id) for the abort case. * guac_user_req_handler, guac_user_done_handler, and guac_user_cancel_handler typedefs in user-fntypes.h. req and done handlers receive the assembled body via (void* body, int body_len), owned by libguac and valid only during the call. * 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. __guac_handle_pipe intercepts the reserved mimetype and assembles the body into per-user pending fields. The instructions carry no feature-specific semantics. The first consumer is WebAuthn passthrough using "webauthn.create" and "webauthn.get"; other RPC-style features can reuse the same plumbing.
1 parent e775052 commit 4eca0a8

8 files changed

Lines changed: 617 additions & 10 deletions

File tree

src/libguac/guacamole/protocol.h

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

src/libguac/guacamole/user-constants.h

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

67+
/**
68+
* Reserved pipe mimetype for the body of a "req" or "done" instruction.
69+
* The pipe's name is the correlation id; libguac buffers the body and
70+
* hands it to the user's req or done handler when the matching
71+
* instruction arrives. Only one body can be in flight per user, so
72+
* senders should emit the pipe, its blobs, the end, and the trailing
73+
* req or done back to back.
74+
*/
75+
#define GUAC_USER_RPC_PAYLOAD_MIMETYPE "application/x-guacamole-rpc-payload"
76+
77+
/**
78+
* Maximum size, in bytes, of a single "req" or "done" body buffered by
79+
* libguac. Larger bodies are rejected before the user's handler runs.
80+
*/
81+
#define GUAC_USER_MAX_RPC_BODY_LENGTH 65536
82+
6783
#endif
6884

src/libguac/guacamole/user-fntypes.h

Lines changed: 92 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,100 @@ 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
549+
* has been received from a user. A "req" starts a typed request/response
550+
* exchange that is settled by a later "done" with the same id, or
551+
* aborted by a "cancel" with the same id.
552+
*
553+
* The body comes in on a paired pipe with the correlation id as the name
554+
* and GUAC_USER_RPC_PAYLOAD_MIMETYPE as the mimetype. libguac buffers it
555+
* and hands it to the handler via body and body_len. The buffer belongs
556+
* to libguac and is only valid for this call. If the body is empty,
557+
* body is NULL and body_len is 0.
558+
*
559+
* Example:
560+
* @code
561+
* int req_handler(guac_user* user, char* id, char* name,
562+
* void* body, int body_len);
563+
*
564+
* int guac_user_init(guac_user* user, int argc, char** argv) {
565+
* user->req_handler = req_handler;
566+
* }
567+
* @endcode
568+
*
569+
* @param user
570+
* The user that sent the request.
571+
*
572+
* @param id
573+
* Correlation identifier of this request.
574+
*
575+
* @param name
576+
* Method name identifying what kind of request this is.
577+
*
578+
* @param body
579+
* The request body assembled from the paired pipe, or NULL if
580+
* empty.
581+
*
582+
* @param body_len
583+
* Length of the request body in bytes.
584+
*
585+
* @return
586+
* Zero on success, non-zero on error.
587+
*/
588+
typedef int guac_user_req_handler(guac_user* user, char* id, char* name,
589+
void* body, int body_len);
590+
591+
/**
592+
* Handler for Guacamole "done" events, invoked when a "done" instruction
593+
* has been received from a user. A "done" settles a previously issued
594+
* "req" with a status. The body comes in on a paired pipe in the same
595+
* way as the req handler.
596+
*
597+
* @param user
598+
* The user that sent the response.
599+
*
600+
* @param id
601+
* Correlation identifier of the request being settled.
602+
*
603+
* @param status
604+
* Status of the request. By convention "ok", "error", or
605+
* "canceled".
606+
*
607+
* @param body
608+
* The response body assembled from the paired pipe, or NULL if
609+
* empty.
610+
*
611+
* @param body_len
612+
* Length of the response body in bytes.
613+
*
614+
* @return
615+
* Zero on success, non-zero on error.
616+
*/
617+
typedef int guac_user_done_handler(guac_user* user, char* id, char* status,
618+
void* body, int body_len);
619+
620+
/**
621+
* Handler for Guacamole "cancel" events, invoked when a "cancel"
622+
* instruction has been received from a user. A "cancel" requests that a
623+
* previously-issued "req" be aborted before its "done" arrives.
624+
*
625+
* @param user
626+
* The user that sent the cancellation.
627+
*
628+
* @param id
629+
* The correlation identifier of the request to cancel.
630+
*
631+
* @return
632+
* Zero if the cancellation was handled successfully, non-zero
633+
* otherwise.
634+
*/
635+
typedef int guac_user_cancel_handler(guac_user* user, char* id);
636+
547637
/**
548638
* Handler for Guacamole USB data events, invoked when a "usbdata" instruction
549639
* has been received from a user. This carries data from a client-side USB

src/libguac/guacamole/user.h

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,77 @@ 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+
* starts a typed request/response exchange settled by a later "done"
604+
* with the same id, or aborted by a "cancel" with the same id. The
605+
* request body, if any, is assembled by libguac from the paired
606+
* payload pipe and handed 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 a status. The response
623+
* body, if any, is assembled by libguac from the paired payload pipe
624+
* and handed 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 most recent payload pipe assembled, still
655+
* waiting for the matching req or done. malloc'd, NULL-terminated,
656+
* or NULL if nothing is buffered. Internal to libguac.
657+
*/
658+
char* __rpc_pending_id;
659+
660+
/**
661+
* Body bytes for __rpc_pending_id. malloc'd, not NULL-terminated,
662+
* undefined when __rpc_pending_id is NULL. Internal to libguac.
663+
*/
664+
char* __rpc_pending_body;
665+
666+
/**
667+
* Length of __rpc_pending_body in bytes. Undefined when
668+
* __rpc_pending_id is NULL. Internal to libguac.
669+
*/
670+
int __rpc_pending_body_len;
671+
601672
};
602673

603674
/**

0 commit comments

Comments
 (0)