diff options
author | Peter Maydell <peter.maydell@linaro.org> | 2018-01-12 16:01:30 +0000 |
---|---|---|
committer | Peter Maydell <peter.maydell@linaro.org> | 2018-01-12 16:01:30 +0000 |
commit | 7398166ddf7c6dbbc9cae6ac69bb2feda14b40ac (patch) | |
tree | 834c30585313a68f100249b06d72bc7c229ce574 | |
parent | a3380cf658e130be35ad36c5fc284986258781bc (diff) | |
parent | 30b80fd5269257f55203b7072c505b4ebaab5115 (diff) | |
download | qemu-7398166ddf7c6dbbc9cae6ac69bb2feda14b40ac.zip qemu-7398166ddf7c6dbbc9cae6ac69bb2feda14b40ac.tar.gz qemu-7398166ddf7c6dbbc9cae6ac69bb2feda14b40ac.tar.bz2 |
Merge remote-tracking branch 'remotes/kraxel/tags/vnc-20180112-pull-request' into staging
vnc: limit memory usage (CVE-2017-15124)
# gpg: Signature made Fri 12 Jan 2018 12:57:22 GMT
# gpg: using RSA key 0x4CB6D8EED3E87138
# gpg: Good signature from "Gerd Hoffmann (work) <kraxel@redhat.com>"
# gpg: aka "Gerd Hoffmann <gerd@kraxel.org>"
# gpg: aka "Gerd Hoffmann (private) <kraxel@gmail.com>"
# Primary key fingerprint: A032 8CFF B93A 17A7 9901 FE7D 4CB6 D8EE D3E8 7138
* remotes/kraxel/tags/vnc-20180112-pull-request:
ui: mix misleading comments & return types of VNC I/O helper methods
ui: add trace events related to VNC client throttling
ui: place a hard cap on VNC server output buffer size
ui: fix VNC client throttling when forced update is requested
ui: fix VNC client throttling when audio capture is active
ui: refactor code for determining if an update should be sent to the client
ui: correctly reset framebuffer update state after processing dirty regions
ui: introduce enum to track VNC client framebuffer update request state
ui: track how much decoded data we consumed when doing SASL encoding
ui: avoid pointless VNC updates if framebuffer isn't dirty
ui: remove redundant indentation in vnc_client_update
ui: remove unreachable code in vnc_update_client
ui: remove 'sync' parameter from vnc_update_client
vnc: fix debug spelling
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
-rw-r--r-- | ui/trace-events | 7 | ||||
-rw-r--r-- | ui/vnc-auth-sasl.c | 16 | ||||
-rw-r--r-- | ui/vnc-auth-sasl.h | 5 | ||||
-rw-r--r-- | ui/vnc-jobs.c | 5 | ||||
-rw-r--r-- | ui/vnc.c | 322 | ||||
-rw-r--r-- | ui/vnc.h | 28 |
6 files changed, 278 insertions, 105 deletions
diff --git a/ui/trace-events b/ui/trace-events index 1a9f126..85f74f9 100644 --- a/ui/trace-events +++ b/ui/trace-events @@ -35,6 +35,13 @@ vnc_client_connect(void *state, void *ioc) "VNC client connect state=%p ioc=%p" vnc_client_disconnect_start(void *state, void *ioc) "VNC client disconnect start state=%p ioc=%p" vnc_client_disconnect_finish(void *state, void *ioc) "VNC client disconnect finish state=%p ioc=%p" vnc_client_io_wrap(void *state, void *ioc, const char *type) "VNC client I/O wrap state=%p ioc=%p type=%s" +vnc_client_throttle_threshold(void *state, void *ioc, size_t oldoffset, size_t offset, int client_width, int client_height, int bytes_per_pixel, void *audio_cap) "VNC client throttle threshold state=%p ioc=%p oldoffset=%zu newoffset=%zu width=%d height=%d bpp=%d audio=%p" +vnc_client_throttle_incremental(void *state, void *ioc, int job_update, size_t offset) "VNC client throttle incremental state=%p ioc=%p job-update=%d offset=%zu" +vnc_client_throttle_forced(void *state, void *ioc, int job_update, size_t offset) "VNC client throttle forced state=%p ioc=%p job-update=%d offset=%zu" +vnc_client_throttle_audio(void *state, void *ioc, size_t offset) "VNC client throttle audio state=%p ioc=%p offset=%zu" +vnc_client_unthrottle_forced(void *state, void *ioc) "VNC client unthrottle forced offset state=%p ioc=%p" +vnc_client_unthrottle_incremental(void *state, void *ioc, size_t offset) "VNC client unthrottle incremental state=%p ioc=%p offset=%zu" +vnc_client_output_limit(void *state, void *ioc, size_t offset, size_t threshold) "VNC client output limit state=%p ioc=%p offset=%zu threshold=%zu" vnc_auth_init(void *display, int websock, int auth, int subauth) "VNC auth init state=%p websock=%d auth=%d subauth=%d" vnc_auth_start(void *state, int method) "VNC client auth start state=%p method=%d" vnc_auth_pass(void *state, int method) "VNC client auth passed state=%p method=%d" diff --git a/ui/vnc-auth-sasl.c b/ui/vnc-auth-sasl.c index 23f2828..74a5f51 100644 --- a/ui/vnc-auth-sasl.c +++ b/ui/vnc-auth-sasl.c @@ -48,9 +48,9 @@ void vnc_sasl_client_cleanup(VncState *vs) } -long vnc_client_write_sasl(VncState *vs) +size_t vnc_client_write_sasl(VncState *vs) { - long ret; + size_t ret; VNC_DEBUG("Write SASL: Pending output %p size %zd offset %zd " "Encoded: %p size %d offset %d\n", @@ -67,6 +67,7 @@ long vnc_client_write_sasl(VncState *vs) if (err != SASL_OK) return vnc_client_io_error(vs, -1, NULL); + vs->sasl.encodedRawLength = vs->output.offset; vs->sasl.encodedOffset = 0; } @@ -78,7 +79,12 @@ long vnc_client_write_sasl(VncState *vs) vs->sasl.encodedOffset += ret; if (vs->sasl.encodedOffset == vs->sasl.encodedLength) { - vs->output.offset = 0; + if (vs->sasl.encodedRawLength >= vs->force_update_offset) { + vs->force_update_offset = 0; + } else { + vs->force_update_offset -= vs->sasl.encodedRawLength; + } + vs->output.offset -= vs->sasl.encodedRawLength; vs->sasl.encoded = NULL; vs->sasl.encodedOffset = vs->sasl.encodedLength = 0; } @@ -100,9 +106,9 @@ long vnc_client_write_sasl(VncState *vs) } -long vnc_client_read_sasl(VncState *vs) +size_t vnc_client_read_sasl(VncState *vs) { - long ret; + size_t ret; uint8_t encoded[4096]; const char *decoded; unsigned int decodedLen; diff --git a/ui/vnc-auth-sasl.h b/ui/vnc-auth-sasl.h index cb42745..2ae224e 100644 --- a/ui/vnc-auth-sasl.h +++ b/ui/vnc-auth-sasl.h @@ -53,6 +53,7 @@ struct VncStateSASL { */ const uint8_t *encoded; unsigned int encodedLength; + unsigned int encodedRawLength; unsigned int encodedOffset; char *username; char *mechlist; @@ -64,8 +65,8 @@ struct VncDisplaySASL { void vnc_sasl_client_cleanup(VncState *vs); -long vnc_client_read_sasl(VncState *vs); -long vnc_client_write_sasl(VncState *vs); +size_t vnc_client_read_sasl(VncState *vs); +size_t vnc_client_write_sasl(VncState *vs); void start_auth_sasl(VncState *vs); diff --git a/ui/vnc-jobs.c b/ui/vnc-jobs.c index f786777..e326679 100644 --- a/ui/vnc-jobs.c +++ b/ui/vnc-jobs.c @@ -152,6 +152,11 @@ void vnc_jobs_consume_buffer(VncState *vs) vs->ioc, G_IO_IN | G_IO_OUT, vnc_client_io, vs, NULL); } buffer_move(&vs->output, &vs->jobs_buffer); + + if (vs->job_update == VNC_STATE_UPDATE_FORCE) { + vs->force_update_offset = vs->output.offset; + } + vs->job_update = VNC_STATE_UPDATE_NONE; } flush = vs->ioc != NULL && vs->abort != true; vnc_unlock_output(vs); @@ -60,6 +60,7 @@ static QTAILQ_HEAD(, VncDisplay) vnc_displays = static int vnc_cursor_define(VncState *vs); static void vnc_release_modifiers(VncState *vs); +static void vnc_update_throttle_offset(VncState *vs); static void vnc_set_share_mode(VncState *vs, VncShareMode mode) { @@ -596,7 +597,7 @@ VncInfo2List *qmp_query_vnc_servers(Error **errp) 3) resolutions > 1024 */ -static int vnc_update_client(VncState *vs, int has_dirty, bool sync); +static int vnc_update_client(VncState *vs, int has_dirty); static void vnc_disconnect_start(VncState *vs); static void vnc_colordepth(VncState *vs); @@ -766,6 +767,7 @@ static void vnc_dpy_switch(DisplayChangeListener *dcl, vnc_set_area_dirty(vs->dirty, vd, 0, 0, vnc_width(vd), vnc_height(vd)); + vnc_update_throttle_offset(vs); } } @@ -961,85 +963,168 @@ static int find_and_clear_dirty_height(VncState *vs, return h; } -static int vnc_update_client(VncState *vs, int has_dirty, bool sync) +/* + * Figure out how much pending data we should allow in the output + * buffer before we throttle incremental display updates, and/or + * drop audio samples. + * + * We allow for equiv of 1 full display's worth of FB updates, + * and 1 second of audio samples. If audio backlog was larger + * than that the client would already suffering awful audio + * glitches, so dropping samples is no worse really). + */ +static void vnc_update_throttle_offset(VncState *vs) { - if (vs->disconnecting) { - vnc_disconnect_finish(vs); - return 0; + size_t offset = + vs->client_width * vs->client_height * vs->client_pf.bytes_per_pixel; + + if (vs->audio_cap) { + int freq = vs->as.freq; + /* We don't limit freq when reading settings from client, so + * it could be upto MAX_INT in size. 48khz is a sensible + * upper bound for trustworthy clients */ + int bps; + if (freq > 48000) { + freq = 48000; + } + switch (vs->as.fmt) { + default: + case AUD_FMT_U8: + case AUD_FMT_S8: + bps = 1; + break; + case AUD_FMT_U16: + case AUD_FMT_S16: + bps = 2; + break; + case AUD_FMT_U32: + case AUD_FMT_S32: + bps = 4; + break; + } + offset += freq * bps * vs->as.nchannels; } - vs->has_dirty += has_dirty; - if (vs->need_update && !vs->disconnecting) { - VncDisplay *vd = vs->vd; - VncJob *job; - int y; - int height, width; - int n = 0; - - if (vs->output.offset && !vs->audio_cap && !vs->force_update) - /* kernel send buffers are full -> drop frames to throttle */ - return 0; + /* Put a floor of 1MB on offset, so that if we have a large pending + * buffer and the display is resized to a small size & back again + * we don't suddenly apply a tiny send limit + */ + offset = MAX(offset, 1024 * 1024); - if (!vs->has_dirty && !vs->audio_cap && !vs->force_update) - return 0; + if (vs->throttle_output_offset != offset) { + trace_vnc_client_throttle_threshold( + vs, vs->ioc, vs->throttle_output_offset, offset, vs->client_width, + vs->client_height, vs->client_pf.bytes_per_pixel, vs->audio_cap); + } - /* - * Send screen updates to the vnc client using the server - * surface and server dirty map. guest surface updates - * happening in parallel don't disturb us, the next pass will - * send them to the client. + vs->throttle_output_offset = offset; +} + +static bool vnc_should_update(VncState *vs) +{ + switch (vs->update) { + case VNC_STATE_UPDATE_NONE: + break; + case VNC_STATE_UPDATE_INCREMENTAL: + /* Only allow incremental updates if the pending send queue + * is less than the permitted threshold, and the job worker + * is completely idle. */ - job = vnc_job_new(vs); - - height = pixman_image_get_height(vd->server); - width = pixman_image_get_width(vd->server); - - y = 0; - for (;;) { - int x, h; - unsigned long x2; - unsigned long offset = find_next_bit((unsigned long *) &vs->dirty, - height * VNC_DIRTY_BPL(vs), - y * VNC_DIRTY_BPL(vs)); - if (offset == height * VNC_DIRTY_BPL(vs)) { - /* no more dirty bits */ - break; - } - y = offset / VNC_DIRTY_BPL(vs); - x = offset % VNC_DIRTY_BPL(vs); - x2 = find_next_zero_bit((unsigned long *) &vs->dirty[y], - VNC_DIRTY_BPL(vs), x); - bitmap_clear(vs->dirty[y], x, x2 - x); - h = find_and_clear_dirty_height(vs, y, x, x2, height); - x2 = MIN(x2, width / VNC_DIRTY_PIXELS_PER_BIT); - if (x2 > x) { - n += vnc_job_add_rect(job, x * VNC_DIRTY_PIXELS_PER_BIT, y, - (x2 - x) * VNC_DIRTY_PIXELS_PER_BIT, h); - } - if (!x && x2 == width / VNC_DIRTY_PIXELS_PER_BIT) { - y += h; - if (y == height) { - break; - } - } + if (vs->output.offset < vs->throttle_output_offset && + vs->job_update == VNC_STATE_UPDATE_NONE) { + return true; } - - vnc_job_push(job); - if (sync) { - vnc_jobs_join(vs); + trace_vnc_client_throttle_incremental( + vs, vs->ioc, vs->job_update, vs->output.offset); + break; + case VNC_STATE_UPDATE_FORCE: + /* Only allow forced updates if the pending send queue + * does not contain a previous forced update, and the + * job worker is completely idle. + * + * Note this means we'll queue a forced update, even if + * the output buffer size is otherwise over the throttle + * output limit. + */ + if (vs->force_update_offset == 0 && + vs->job_update == VNC_STATE_UPDATE_NONE) { + return true; } - vs->force_update = 0; - vs->has_dirty = 0; - return n; + trace_vnc_client_throttle_forced( + vs, vs->ioc, vs->job_update, vs->force_update_offset); + break; } + return false; +} + +static int vnc_update_client(VncState *vs, int has_dirty) +{ + VncDisplay *vd = vs->vd; + VncJob *job; + int y; + int height, width; + int n = 0; if (vs->disconnecting) { vnc_disconnect_finish(vs); - } else if (sync) { - vnc_jobs_join(vs); + return 0; } - return 0; + vs->has_dirty += has_dirty; + if (!vnc_should_update(vs)) { + return 0; + } + + if (!vs->has_dirty && vs->update != VNC_STATE_UPDATE_FORCE) { + return 0; + } + + /* + * Send screen updates to the vnc client using the server + * surface and server dirty map. guest surface updates + * happening in parallel don't disturb us, the next pass will + * send them to the client. + */ + job = vnc_job_new(vs); + + height = pixman_image_get_height(vd->server); + width = pixman_image_get_width(vd->server); + + y = 0; + for (;;) { + int x, h; + unsigned long x2; + unsigned long offset = find_next_bit((unsigned long *) &vs->dirty, + height * VNC_DIRTY_BPL(vs), + y * VNC_DIRTY_BPL(vs)); + if (offset == height * VNC_DIRTY_BPL(vs)) { + /* no more dirty bits */ + break; + } + y = offset / VNC_DIRTY_BPL(vs); + x = offset % VNC_DIRTY_BPL(vs); + x2 = find_next_zero_bit((unsigned long *) &vs->dirty[y], + VNC_DIRTY_BPL(vs), x); + bitmap_clear(vs->dirty[y], x, x2 - x); + h = find_and_clear_dirty_height(vs, y, x, x2, height); + x2 = MIN(x2, width / VNC_DIRTY_PIXELS_PER_BIT); + if (x2 > x) { + n += vnc_job_add_rect(job, x * VNC_DIRTY_PIXELS_PER_BIT, y, + (x2 - x) * VNC_DIRTY_PIXELS_PER_BIT, h); + } + if (!x && x2 == width / VNC_DIRTY_PIXELS_PER_BIT) { + y += h; + if (y == height) { + break; + } + } + } + + vs->job_update = vs->update; + vs->update = VNC_STATE_UPDATE_NONE; + vnc_job_push(job); + vs->has_dirty = 0; + return n; } /* audio */ @@ -1077,11 +1162,15 @@ static void audio_capture(void *opaque, void *buf, int size) VncState *vs = opaque; vnc_lock_output(vs); - vnc_write_u8(vs, VNC_MSG_SERVER_QEMU); - vnc_write_u8(vs, VNC_MSG_SERVER_QEMU_AUDIO); - vnc_write_u16(vs, VNC_MSG_SERVER_QEMU_AUDIO_DATA); - vnc_write_u32(vs, size); - vnc_write(vs, buf, size); + if (vs->output.offset < vs->throttle_output_offset) { + vnc_write_u8(vs, VNC_MSG_SERVER_QEMU); + vnc_write_u8(vs, VNC_MSG_SERVER_QEMU_AUDIO); + vnc_write_u16(vs, VNC_MSG_SERVER_QEMU_AUDIO_DATA); + vnc_write_u32(vs, size); + vnc_write(vs, buf, size); + } else { + trace_vnc_client_throttle_audio(vs, vs->ioc, vs->output.offset); + } vnc_unlock_output(vs); vnc_flush(vs); } @@ -1183,7 +1272,7 @@ void vnc_disconnect_finish(VncState *vs) g_free(vs); } -ssize_t vnc_client_io_error(VncState *vs, ssize_t ret, Error **errp) +size_t vnc_client_io_error(VncState *vs, ssize_t ret, Error **errp) { if (ret <= 0) { if (ret == 0) { @@ -1226,9 +1315,9 @@ void vnc_client_error(VncState *vs) * * Returns the number of bytes written, which may be less than * the requested 'datalen' if the socket would block. Returns - * -1 on error, and disconnects the client socket. + * 0 on I/O error, and disconnects the client socket. */ -ssize_t vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen) +size_t vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen) { Error *err = NULL; ssize_t ret; @@ -1246,12 +1335,13 @@ ssize_t vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen) * will switch the FD poll() handler back to read monitoring. * * Returns the number of bytes written, which may be less than - * the buffered output data if the socket would block. Returns - * -1 on error, and disconnects the client socket. + * the buffered output data if the socket would block. Returns + * 0 on I/O error, and disconnects the client socket. */ -static ssize_t vnc_client_write_plain(VncState *vs) +static size_t vnc_client_write_plain(VncState *vs) { - ssize_t ret; + size_t offset; + size_t ret; #ifdef CONFIG_VNC_SASL VNC_DEBUG("Write Plain: Pending output %p size %zd offset %zd. Wait SSF %d\n", @@ -1270,7 +1360,20 @@ static ssize_t vnc_client_write_plain(VncState *vs) if (!ret) return 0; + if (ret >= vs->force_update_offset) { + if (vs->force_update_offset != 0) { + trace_vnc_client_unthrottle_forced(vs, vs->ioc); + } + vs->force_update_offset = 0; + } else { + vs->force_update_offset -= ret; + } + offset = vs->output.offset; buffer_advance(&vs->output, ret); + if (offset >= vs->throttle_output_offset && + vs->output.offset < vs->throttle_output_offset) { + trace_vnc_client_unthrottle_incremental(vs, vs->ioc, vs->output.offset); + } if (vs->output.offset == 0) { if (vs->ioc_tag) { @@ -1339,9 +1442,9 @@ void vnc_read_when(VncState *vs, VncReadEvent *func, size_t expecting) * * Returns the number of bytes read, which may be less than * the requested 'datalen' if the socket would block. Returns - * -1 on error, and disconnects the client socket. + * 0 on I/O error or EOF, and disconnects the client socket. */ -ssize_t vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen) +size_t vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen) { ssize_t ret; Error *err = NULL; @@ -1357,12 +1460,13 @@ ssize_t vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen) * when not using any SASL SSF encryption layers. Will read as much * data as possible without blocking. * - * Returns the number of bytes read. Returns -1 on error, and - * disconnects the client socket. + * Returns the number of bytes read, which may be less than + * the requested 'datalen' if the socket would block. Returns + * 0 on I/O error or EOF, and disconnects the client socket. */ -static ssize_t vnc_client_read_plain(VncState *vs) +static size_t vnc_client_read_plain(VncState *vs) { - ssize_t ret; + size_t ret; VNC_DEBUG("Read plain %p size %zd offset %zd\n", vs->input.buffer, vs->input.capacity, vs->input.offset); buffer_reserve(&vs->input, 4096); @@ -1388,7 +1492,7 @@ static void vnc_jobs_bh(void *opaque) */ static int vnc_client_read(VncState *vs) { - ssize_t ret; + size_t ret; #ifdef CONFIG_VNC_SASL if (vs->sasl.conn && vs->sasl.runSSF) @@ -1439,8 +1543,39 @@ gboolean vnc_client_io(QIOChannel *ioc G_GNUC_UNUSED, } +/* + * Scale factor to apply to vs->throttle_output_offset when checking for + * hard limit. Worst case normal usage could be x2, if we have a complete + * incremental update and complete forced update in the output buffer. + * So x3 should be good enough, but we pick x5 to be conservative and thus + * (hopefully) never trigger incorrectly. + */ +#define VNC_THROTTLE_OUTPUT_LIMIT_SCALE 5 + void vnc_write(VncState *vs, const void *data, size_t len) { + if (vs->disconnecting) { + return; + } + /* Protection against malicious client/guest to prevent our output + * buffer growing without bound if client stops reading data. This + * should rarely trigger, because we have earlier throttling code + * which stops issuing framebuffer updates and drops audio data + * if the throttle_output_offset value is exceeded. So we only reach + * this higher level if a huge number of pseudo-encodings get + * triggered while data can't be sent on the socket. + * + * NB throttle_output_offset can be zero during early protocol + * handshake, or from the job thread's VncState clone + */ + if (vs->throttle_output_offset != 0 && + vs->output.offset > (vs->throttle_output_offset * + VNC_THROTTLE_OUTPUT_LIMIT_SCALE)) { + trace_vnc_client_output_limit(vs, vs->ioc, vs->output.offset, + vs->throttle_output_offset); + vnc_disconnect_start(vs); + return; + } buffer_reserve(&vs->output, len); if (vs->ioc != NULL && buffer_empty(&vs->output)) { @@ -1876,14 +2011,14 @@ static void ext_key_event(VncState *vs, int down, static void framebuffer_update_request(VncState *vs, int incremental, int x, int y, int w, int h) { - vs->need_update = 1; - if (incremental) { - return; + if (vs->update != VNC_STATE_UPDATE_FORCE) { + vs->update = VNC_STATE_UPDATE_INCREMENTAL; + } + } else { + vs->update = VNC_STATE_UPDATE_FORCE; + vnc_set_area_dirty(vs->dirty, vs->vd, x, y, w, h); } - - vs->force_update = 1; - vnc_set_area_dirty(vs->dirty, vs->vd, x, y, w, h); } static void send_ext_key_event_ack(VncState *vs) @@ -2255,7 +2390,7 @@ static int protocol_client_msg(VncState *vs, uint8_t *data, size_t len) } vs->as.nchannels = read_u8(data, 5); if (vs->as.nchannels != 1 && vs->as.nchannels != 2) { - VNC_DEBUG("Invalid audio channel coount %d\n", + VNC_DEBUG("Invalid audio channel count %d\n", read_u8(data, 5)); vnc_client_error(vs); break; @@ -2281,6 +2416,7 @@ static int protocol_client_msg(VncState *vs, uint8_t *data, size_t len) break; } + vnc_update_throttle_offset(vs); vnc_read_when(vs, protocol_client_msg, 1); return 0; } @@ -2863,7 +2999,7 @@ static void vnc_refresh(DisplayChangeListener *dcl) vnc_unlock_display(vd); QTAILQ_FOREACH_SAFE(vs, &vd->clients, next, vn) { - rects += vnc_update_client(vs, has_dirty, false); + rects += vnc_update_client(vs, has_dirty); /* vs might be free()ed here */ } @@ -252,6 +252,12 @@ struct VncJob QTAILQ_ENTRY(VncJob) next; }; +typedef enum { + VNC_STATE_UPDATE_NONE, + VNC_STATE_UPDATE_INCREMENTAL, + VNC_STATE_UPDATE_FORCE, +} VncStateUpdate; + struct VncState { QIOChannelSocket *sioc; /* The underlying socket */ @@ -264,8 +270,8 @@ struct VncState * vnc-jobs-async.c */ VncDisplay *vd; - int need_update; - int force_update; + VncStateUpdate update; /* Most recent pending request from client */ + VncStateUpdate job_update; /* Currently processed by job thread */ int has_dirty; uint32_t features; int absolute; @@ -293,6 +299,18 @@ struct VncState VncClientInfo *info; + /* Job thread bottom half has put data for a forced update + * into the output buffer. This offset points to the end of + * the update data in the output buffer. This lets us determine + * when a force update is fully sent to the client, allowing + * us to process further forced updates. */ + size_t force_update_offset; + /* We allow multiple incremental updates or audio capture + * samples to be queued in output buffer, provided the + * buffer size doesn't exceed this threshold. The value + * is calculating dynamically based on framebuffer size + * and audio sample settings in vnc_update_throttle_offset() */ + size_t throttle_output_offset; Buffer output; Buffer input; /* current output mode information */ @@ -506,8 +524,8 @@ gboolean vnc_client_io(QIOChannel *ioc, GIOCondition condition, void *opaque); -ssize_t vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen); -ssize_t vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen); +size_t vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen); +size_t vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen); /* Protocol I/O functions */ void vnc_write(VncState *vs, const void *data, size_t len); @@ -526,7 +544,7 @@ uint32_t read_u32(uint8_t *data, size_t offset); /* Protocol stage functions */ void vnc_client_error(VncState *vs); -ssize_t vnc_client_io_error(VncState *vs, ssize_t ret, Error **errp); +size_t vnc_client_io_error(VncState *vs, ssize_t ret, Error **errp); void start_client_init(VncState *vs); void start_auth_vnc(VncState *vs); |