Skip to content

Commit 8258dcc

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). All three send functions take guac_user, so each RPC exchange targets one specific user rather than broadcasting via the client socket. This avoids the N-done-per-req problem in shared sessions where every joined user would otherwise receive the same req. API surface: * guac_protocol_send_req(user, id, name, body, body_len) and guac_protocol_send_done(user, id, status, body, body_len) in protocol.h / protocol.c. Each allocates a stream against the user, ships the body over the paired pipe via user->socket, then sends the trailing instruction. Body may be NULL with body_len 0. * guac_protocol_send_cancel(user, 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. Also adds an optional destructor callback on guac_stream, called from guac_user_free_stream and guac_client_free_stream, and from guac_user_free and guac_client_free for any streams still open at disconnect. The RPC body assembly installs the destructor when it allocates, so an in-progress body that never reaches end is freed at disconnect. Existing handlers are unaffected since the field defaults to NULL. 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 8258dcc

15 files changed

Lines changed: 1819 additions & 11 deletions

src/libguac/client.c

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,13 +139,21 @@ guac_stream* guac_client_alloc_stream(guac_client* client) {
139139
allocd_stream->ack_handler = NULL;
140140
allocd_stream->blob_handler = NULL;
141141
allocd_stream->end_handler = NULL;
142+
allocd_stream->destructor = NULL;
142143

143144
return allocd_stream;
144145

145146
}
146147

147148
void guac_client_free_stream(guac_client* client, guac_stream* stream) {
148149

150+
/* Run the registered destructor for the stream's data, if any */
151+
if (stream->destructor != NULL) {
152+
stream->destructor(stream->data);
153+
stream->destructor = NULL;
154+
stream->data = NULL;
155+
}
156+
149157
/* Mark stream as closed */
150158
int freed_index = stream->index;
151159
stream->index = GUAC_CLIENT_CLOSED_STREAM_INDEX;
@@ -289,6 +297,7 @@ guac_client* guac_client_alloc(void) {
289297

290298
for (i=0; i<GUAC_CLIENT_MAX_STREAMS; i++) {
291299
client->__output_streams[i].index = GUAC_CLIENT_CLOSED_STREAM_INDEX;
300+
client->__output_streams[i].destructor = NULL;
292301
}
293302

294303
/* Init locks */
@@ -344,6 +353,15 @@ void guac_client_free(guac_client* client) {
344353
guac_pool_free(client->__buffer_pool);
345354
guac_pool_free(client->__layer_pool);
346355

356+
/* Run any registered destructors for streams still open at
357+
* disconnect so their data pointers do not leak */
358+
for (int i = 0; i < GUAC_CLIENT_MAX_STREAMS; i++) {
359+
guac_stream* stream = &client->__output_streams[i];
360+
if (stream->index != GUAC_CLIENT_CLOSED_STREAM_INDEX
361+
&& stream->destructor != NULL)
362+
stream->destructor(stream->data);
363+
}
364+
347365
/* Free streams */
348366
guac_mem_free(client->__output_streams);
349367

src/libguac/guacamole/protocol.h

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,15 @@
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"
3435
#include "protocol-types.h"
3536
#include "socket-types.h"
3637
#include "stream-types.h"
3738
#include "timestamp-types.h"
39+
#include "user-types.h"
3840

3941
#include <cairo/cairo.h>
4042
#include <stdarg.h>
@@ -1119,6 +1121,93 @@ int guac_protocol_send_clipboard(guac_socket* socket, const guac_stream* stream,
11191121
*/
11201122
int guac_protocol_send_name(guac_socket* socket, const char* name);
11211123

1124+
/**
1125+
* Sends a "req" to the given user, starting a typed request/response
1126+
* exchange. The body ships first on a pipe named for the correlation id
1127+
* with the reserved GUAC_USER_RPC_PAYLOAD_MIMETYPE; the trailing "req"
1128+
* carries only the id and method name. The peer settles the request with
1129+
* a "done" carrying the same id, or either side can issue a "cancel"
1130+
* with the same id to abort.
1131+
*
1132+
* Callers on the same guac_user must serialize calls to this and
1133+
* guac_protocol_send_done so the four wire instructions (pipe, blobs,
1134+
* end, req or done) for one exchange are not interleaved with those of
1135+
* another. The receive side tracks at most one in-flight body per user;
1136+
* concurrent senders that interleave would race that slot.
1137+
*
1138+
* @param user
1139+
* The guac_user to send the request to. The body stream is
1140+
* allocated against this user and freed before this function
1141+
* returns.
1142+
*
1143+
* @param id
1144+
* Correlation identifier, typically a UUID string.
1145+
*
1146+
* @param name
1147+
* Method name identifying what kind of request this is.
1148+
*
1149+
* @param body
1150+
* The request body, opaque to libguac. May be NULL if body_len is
1151+
* zero.
1152+
*
1153+
* @param body_len
1154+
* Length of the request body in bytes. May be zero.
1155+
*
1156+
* @return
1157+
* Zero on success, non-zero on error.
1158+
*/
1159+
int guac_protocol_send_req(guac_user* user, const char* id,
1160+
const char* name, const void* body, int body_len);
1161+
1162+
/**
1163+
* Sends a "done" to the given user, settling a previously received "req"
1164+
* with a status. The body ships first on a pipe in the same way as
1165+
* guac_protocol_send_req, including the same per-user serialization
1166+
* requirement.
1167+
*
1168+
* @param user
1169+
* The guac_user to send the response to. The body stream is
1170+
* allocated against this user and freed before this function
1171+
* returns.
1172+
*
1173+
* @param id
1174+
* Correlation id of the request being settled.
1175+
*
1176+
* @param status
1177+
* Status of the request. By convention "ok" for success, "error"
1178+
* for a method-level failure (with details in the body), or
1179+
* "canceled" if the request was aborted.
1180+
*
1181+
* @param body
1182+
* The response body, opaque to libguac. May be NULL if body_len is
1183+
* zero.
1184+
*
1185+
* @param body_len
1186+
* Length of the response body in bytes. May be zero.
1187+
*
1188+
* @return
1189+
* Zero on success, non-zero on error.
1190+
*/
1191+
int guac_protocol_send_done(guac_user* user, const char* id,
1192+
const char* status, const void* body, int body_len);
1193+
1194+
/**
1195+
* Sends a "cancel" to the given user, requesting that a previously-issued
1196+
* "req" be aborted before its "done" arrives. Either side may issue a
1197+
* "cancel"; the peer may still ultimately respond with a "done" if the
1198+
* cancellation arrives after the response was already in flight.
1199+
*
1200+
* @param user
1201+
* The guac_user to send the cancellation to.
1202+
*
1203+
* @param id
1204+
* The correlation identifier of the request to cancel.
1205+
*
1206+
* @return
1207+
* Zero on success, non-zero on error.
1208+
*/
1209+
int guac_protocol_send_cancel(guac_user* user, const char* id);
1210+
11221211
/**
11231212
* Decodes the given base64-encoded string in-place. The base64 string must
11241213
* be NULL-terminated.

src/libguac/guacamole/stream.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,34 @@ struct guac_stream {
105105
*/
106106
guac_user_end_handler* end_handler;
107107

108+
/**
109+
* Optional cleanup callback for whatever the stream's data pointer
110+
* holds. Called from guac_user_free_stream when the stream is freed,
111+
* and from guac_user_free for any streams still open when the user
112+
* disconnects. NULL if no cleanup is needed.
113+
*
114+
* Example:
115+
* @code
116+
* static void my_data_free(void* data) {
117+
* my_data* d = (my_data*) data;
118+
* free(d->buffer);
119+
* free(d);
120+
* }
121+
*
122+
* int my_pipe_handler(guac_user* user, guac_stream* stream,
123+
* char* mimetype, char* name) {
124+
* my_data* d = malloc(sizeof(*d));
125+
* d->buffer = NULL;
126+
* stream->data = d;
127+
* stream->blob_handler = my_blob_handler;
128+
* stream->end_handler = my_end_handler;
129+
* stream->destructor = my_data_free;
130+
* return 0;
131+
* }
132+
* @endcode
133+
*/
134+
void (*destructor)(void* data);
135+
108136
};
109137

110138
#endif

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

0 commit comments

Comments
 (0)