From 1f2e4873027466248a041e9a06f0793213e9ff40 Mon Sep 17 00:00:00 2001 From: Přemysl Janouch Date: Mon, 27 Oct 2014 13:54:53 +0100 Subject: Woo we can draw over the network now --- autistdraw.c | 419 ++++++++++++++++++++++++++++++++++++++++++++++++++++++----- utils.c | 216 ++++++++++++++++++++++++++++++ 2 files changed, 603 insertions(+), 32 deletions(-) diff --git a/autistdraw.c b/autistdraw.c index 3e7b6c4..f6dd2ec 100644 --- a/autistdraw.c +++ b/autistdraw.c @@ -34,10 +34,21 @@ #include "config.h" #include "utils.c" -#define PALETTE_WIDTH 9 ///< Width of the palette -#define TOP_BAR_CUTOFF 3 ///< Height of the top bar +#define PALETTE_WIDTH 9 ///< Width of the palette +#define TOP_BAR_CUTOFF 3 ///< Height of the top bar -#define BITMAP_BLOCK_SIZE 50 ///< Step for extending bitmap size +#define BITMAP_BLOCK_SIZE 50 ///< Step for extending bitmap size + +#define PROTOCOL_VERSION 1 ///< Network protocol version + +enum +{ + MESSAGE_HELLO, ///< Server/client hello + MESSAGE_GET_BITMAP, ///< Request bitmap data + MESSAGE_PUT_POINT, ///< Request to place a point + + MESSAGE_COUNT ///< Total number of messages +}; typedef enum network_mode network_mode_t; enum network_mode @@ -47,12 +58,20 @@ enum network_mode NETWORK_MODE_CLIENT ///< We're a client }; +typedef struct write_req write_req_t; +struct write_req +{ + uv_write_t req; ///< libuv write request + uv_buf_t buf; ///< The data to be written +}; + typedef struct client client_t; struct client { LIST_HEADER (client_t) uv_tcp_t handle; ///< TCP connection handle + struct msg_reader msg_reader; ///< Client message reader }; typedef struct app_context app_context_t; @@ -70,6 +89,7 @@ struct app_context // Client: uv_tcp_t server_fd; ///< Connection to the server + struct msg_reader msg_reader; ///< Server message reader // Server: uv_tcp_t listen_fd; ///< Listening FD @@ -98,20 +118,14 @@ struct app_context uint8_t current_color_right; ///< Right mouse button color }; +static void remove_client (app_context_t *app, client_t *client); +static void on_server_disconnected (app_context_t *app); + static void app_init (app_context_t *self) { memset (self, 0, sizeof *self); -} - -static void -remove_client (app_context_t *app, client_t *client) -{ - // TODO: cancel any write requests? - // XXX: should we unref it? - uv_close ((uv_handle_t *) &client->handle, NULL); - LIST_UNLINK (app->clients, client); - free (client); + msg_reader_init (&self->msg_reader); } static void @@ -120,11 +134,150 @@ app_free (app_context_t *self) if (self->tk) termo_destroy (self->tk); while (self->clients) + // XXX: we probably shouldn't do this from here remove_client (self, self->clients); free (self->bitmap); + msg_reader_free (&self->msg_reader); +} + +// --- Server-client messaging ------------------------------------------------- + +static write_req_t * +flush_writer (struct msg_writer *writer) +{ + size_t len; + void *data = msg_writer_flush (writer, &len); + write_req_t *req = xcalloc (1, sizeof *req); + req->buf = uv_buf_init (data, len); + return req; +} + +static void +on_data_written_to_client (uv_write_t *req, int status) +{ + write_req_t *wr = (write_req_t *) req; + app_context_t *app = req->handle->loop->data; + client_t *client = req->data; + + if (status) + // Write failed + remove_client (app, client); + + free (wr->buf.base); + free (wr); +} + +static void +flush_writer_to_client (struct msg_writer *writer, client_t *client) +{ + write_req_t *wr = flush_writer (writer); + wr->req.data = client; + (void) uv_write (&wr->req, (uv_stream_t *) &client->handle, + &wr->buf, 1, on_data_written_to_client); + // XXX: should we put the request on a list so that we can get rid of it? } +static void +on_data_written_to_server (uv_write_t *req, int status) +{ + write_req_t *wr = (write_req_t *) req; + app_context_t *app = req->handle->loop->data; + + if (status) + // Write failed + on_server_disconnected (app); + + free (wr->buf.base); + free (wr); +} + +static void +flush_writer_to_server (struct msg_writer *writer, app_context_t *app) +{ + write_req_t *wr = flush_writer (writer); + (void) uv_write (&wr->req, (uv_stream_t *) &app->server_fd, + &wr->buf, 1, on_data_written_to_server); + // XXX: should we put the request on a list so that we can get rid of it? +} + +static void +send_draw_point_response (client_t *client, int x, int y, uint8_t color) +{ + struct msg_writer writer; + msg_writer_init (&writer); + msg_writer_u8 (&writer, MESSAGE_PUT_POINT); + msg_writer_i32 (&writer, x); + msg_writer_i32 (&writer, y); + msg_writer_u8 (&writer, color); + + flush_writer_to_client (&writer, client); +} + +static void +send_draw_point_request (app_context_t *app, int x, int y, uint8_t color) +{ + struct msg_writer writer; + msg_writer_init (&writer); + msg_writer_u8 (&writer, MESSAGE_PUT_POINT); + msg_writer_i32 (&writer, x); + msg_writer_i32 (&writer, y); + msg_writer_u8 (&writer, color); + + flush_writer_to_server (&writer, app); +} + +static void +send_hello_request (app_context_t *app) +{ + struct msg_writer writer; + msg_writer_init (&writer); + msg_writer_u8 (&writer, MESSAGE_HELLO); + msg_writer_u8 (&writer, PROTOCOL_VERSION); + + flush_writer_to_server (&writer, app); +} + +static void +send_hello_response (client_t *client) +{ + struct msg_writer writer; + msg_writer_init (&writer); + msg_writer_u8 (&writer, MESSAGE_HELLO); + msg_writer_u8 (&writer, PROTOCOL_VERSION); + + flush_writer_to_client (&writer, client); +} + +static void +send_get_bitmap_request (app_context_t *app) +{ + struct msg_writer writer; + msg_writer_init (&writer); + msg_writer_u8 (&writer, MESSAGE_GET_BITMAP); + + flush_writer_to_server (&writer, app); +} + +static void +send_get_bitmap_response (client_t *client, app_context_t *app) +{ + struct msg_writer writer; + msg_writer_init (&writer); + msg_writer_u8 (&writer, MESSAGE_GET_BITMAP); + msg_writer_i32 (&writer, app->bitmap_x); + msg_writer_i32 (&writer, app->bitmap_y); + msg_writer_u64 (&writer, app->bitmap_w); + msg_writer_u64 (&writer, app->bitmap_h); + + msg_writer_blob (&writer, app->bitmap, + app->bitmap_w * app->bitmap_h * sizeof *app->bitmap); + + flush_writer_to_client (&writer, client); +} + +// --- Server-client messaging ------------------------------------------------- + static void display (const char *format, ...) { @@ -306,6 +459,11 @@ draw_point (app_context_t *app, int x, int y, uint8_t color) addch (app->palette[color]); refresh (); } + + // Broadcast the clients about the event + if (app->mode == NETWORK_MODE_SERVER) + for (client_t *iter = app->clients; iter; iter = iter->next) + send_draw_point_response (iter, x, y, color); } // --- Exports ----------------------------------------------------------------- @@ -538,7 +696,8 @@ on_key (app_context_t *app, termo_key_t *key) if (event != TERMO_MOUSE_PRESS && event != TERMO_MOUSE_DRAG) return true; - if (button == 2) + // Middle mouse button, or Ctrl + left mouse button, moves the canvas + if (button == 2 || (button == 1 && key->modifiers == TERMO_KEYMOD_CTRL)) { if (event == TERMO_MOUSE_DRAG) { @@ -568,7 +727,12 @@ on_key (app_context_t *app, termo_key_t *key) int canvas_y = app->corner_y + screen_y - TOP_BAR_CUTOFF; if (screen_y >= TOP_BAR_CUTOFF) - draw_point (app, canvas_x, canvas_y, *color); + { + if (app->mode == NETWORK_MODE_CLIENT) + send_draw_point_request (app, canvas_x, canvas_y, *color); + else + draw_point (app, canvas_x, canvas_y, *color); + } else if (screen_y > 0 && event != TERMO_MOUSE_DRAG) { int pair = (float) screen_x / COLS * PALETTE_WIDTH; @@ -650,44 +814,231 @@ app_uv_allocator (uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) buf->len = sizeof app->read_buf; } +// --- Client-specific stuff --------------------------------------------------- + +typedef void (*server_handler_fn) (app_context_t *, struct msg_unpacker *); + static void -on_server_data (uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) +on_server_disconnected (app_context_t *app) +{ + // TODO: cancel any write requests? + // XXX: should we unref it? + uv_close ((uv_handle_t *) &app->server_fd, NULL); + + display ("Disconnected!"); + beep (); // Beep beep! Made a boo-boo. + + // Let the user save the picture at least. + // Also prevents us from trying to use the dead server handle. + app->mode = NETWORK_MODE_STANDALONE; +} + +static void +on_server_hello (app_context_t *app, struct msg_unpacker *unpacker) +{ + (void) app; + + uint8_t version; + if (!msg_unpacker_u8 (unpacker, &version)) + return; + + if (version != PROTOCOL_VERSION) + // XXX: possibly incompatible version, disconnect? + return; +} + +static void +on_server_get_bitmap (app_context_t *app, struct msg_unpacker *unpacker) +{ + int32_t x, y; + uint64_t w, h; + if (!msg_unpacker_i32 (unpacker, &x) + || !msg_unpacker_i32 (unpacker, &y) + || !msg_unpacker_u64 (unpacker, &w) + || !msg_unpacker_u64 (unpacker, &h)) + return; + + // TODO: employ at least some RLE encoding + size_t size = w * h * sizeof *app->bitmap; + if ((h && w > SIZE_MAX / h) || w > SIZE_MAX || h > SIZE_MAX) + return; + + const void *data; + if (!msg_unpacker_blob (unpacker, &data, size)) + return; + + free (app->bitmap); + app->bitmap = memcpy (xcalloc (1, size), data, size); + app->bitmap_x = x; + app->bitmap_y = y; + app->bitmap_w = w; + app->bitmap_h = h; + + redraw_canvas (app); +} + +static void +on_server_put_point (app_context_t *app, struct msg_unpacker *unpacker) { - (void) buf; + int32_t x, y; + uint8_t color; + + if (!msg_unpacker_i32 (unpacker, &x) + || !msg_unpacker_i32 (unpacker, &y) + || !msg_unpacker_u8 (unpacker, &color)) + return; + draw_point (app, x, y, color); +} + +static void +on_server_data (uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) +{ app_context_t *app = stream->loop->data; if (nread == UV_EOF || nread < 0) { - // TODO: cancel any write requests? - // XXX: should we unref it? - uv_close ((uv_handle_t *) &app->server_fd, NULL); + on_server_disconnected (app); + return; + } - display ("Disconnected!"); - beep (); // Beep beep! Made a boo-boo. + msg_reader_feed (&app->msg_reader, buf->base, nread); - // Let the user save the picture at least. - // Also prevents us from trying to use the dead server handle. - app->mode = NETWORK_MODE_STANDALONE; - return; + static const server_handler_fn handlers[MESSAGE_COUNT] = + { + [MESSAGE_HELLO] = on_server_hello, + [MESSAGE_GET_BITMAP] = on_server_get_bitmap, + [MESSAGE_PUT_POINT] = on_server_put_point, + }; + + void *msg; + size_t len; + while ((msg = msg_reader_get (&app->msg_reader, &len))) + { + struct msg_unpacker unpacker; + msg_unpacker_init (&unpacker, msg, len); + + uint8_t type; + if (!msg_unpacker_u8 (&unpacker, &type) + || type >= MESSAGE_COUNT) + // XXX: unknown message, disconnect? + continue; + + server_handler_fn handler = handlers[type]; + if (!handler) + // XXX: unknown message, disconnect? + continue; + + handler (app, &unpacker); + + if (msg_unpacker_get_available (&unpacker) > 0) + // XXX: overlong message, disconnect? + continue; } +} + +// --- Server-specific stuff --------------------------------------------------- + +typedef bool (*client_handler_fn) + (app_context_t *, client_t *, struct msg_unpacker *); + +static void +remove_client (app_context_t *app, client_t *client) +{ + // TODO: cancel any write requests? + // XXX: should we unref it? + uv_close ((uv_handle_t *) &client->handle, NULL); + LIST_UNLINK (app->clients, client); + msg_reader_free (&client->msg_reader); + free (client); +} + +static bool +on_client_hello (app_context_t *app, client_t *client, + struct msg_unpacker *unpacker) +{ + (void) app; - // TODO: process the data + uint8_t version; + if (!msg_unpacker_u8 (unpacker, &version) + || version != PROTOCOL_VERSION) + // Nope, I don't like you + return false; + + send_hello_response (client); + return true; +} + +static bool +on_client_get_bitmap (app_context_t *app, client_t *client, + struct msg_unpacker *unpacker) +{ + (void) unpacker; + + send_get_bitmap_response (client, app); + return true; +} + +static bool +on_client_put_point (app_context_t *app, client_t *client, + struct msg_unpacker *unpacker) +{ + (void) client; + + int32_t x, y; + uint8_t color; + if (!msg_unpacker_i32 (unpacker, &x) + || !msg_unpacker_i32 (unpacker, &y) + || !msg_unpacker_u8 (unpacker, &color)) + return false; + + // The function takes care of broadcasting to all the other clients, + // as well as back to the original sender + draw_point (app, x, y, color); + return true; } static void on_client_data (uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) { - (void) buf; - app_context_t *app = stream->loop->data; client_t *client = stream->data; if (nread == UV_EOF || nread < 0) + goto disconnect; // Connection closed or error + + msg_reader_feed (&client->msg_reader, buf->base, nread); + + static const client_handler_fn handlers[MESSAGE_COUNT] = { - remove_client (app, client); - return; + [MESSAGE_HELLO] = on_client_hello, + [MESSAGE_GET_BITMAP] = on_client_get_bitmap, + [MESSAGE_PUT_POINT] = on_client_put_point, + }; + + void *msg; + size_t len; + while ((msg = msg_reader_get (&client->msg_reader, &len))) + { + struct msg_unpacker unpacker; + msg_unpacker_init (&unpacker, msg, len); + + uint8_t type; + if (!msg_unpacker_u8 (&unpacker, &type)) + goto disconnect; // Invalid message + if (type >= MESSAGE_COUNT) + goto disconnect; // Unknown message + + client_handler_fn handler = handlers[type]; + if (!handler) + goto disconnect; // Unknown message + if (!handler (app, client, &unpacker)) + goto disconnect; // Invalid message + if (msg_unpacker_get_available (&unpacker) > 0) + goto disconnect; // Overlong message data } + return; - // TODO: process the data +disconnect: + remove_client (app, client); } static void @@ -708,6 +1059,7 @@ on_new_client (uv_stream_t *server, int status) goto free_handle; client->handle.data = client; + msg_reader_init (&client->msg_reader); LIST_PREPEND (app->clients, client); return; @@ -907,6 +1259,9 @@ initialize_client (app_context_t *app, struct addrinfo *address) "error", "initialization failed", uv_strerror (err)); exit (EXIT_FAILURE); } + + send_hello_request (app); + send_get_bitmap_request (app); } static void diff --git a/utils.c b/utils.c index e1580e0..f7c8fad 100644 --- a/utils.c +++ b/utils.c @@ -216,6 +216,20 @@ str_append_printf (struct str *self, const char *fmt, ...) return size; } +static void +str_remove_slice (struct str *self, size_t start, size_t length) +{ + size_t end = start + length; + if (end > self->len) + end = self->len; + memmove (self->str + start, self->str + end, self->len - end); + self->str[self->len -= length] = '\0'; + + // Shrink the string if the allocation becomes way too large + if (self->alloc >= STR_SHRINK_THRESHOLD && self->len < (self->alloc >> 2)) + self->str = xrealloc (self->str, self->alloc >>= 2); +} + // --- Utilities --------------------------------------------------------------- static bool @@ -270,6 +284,208 @@ xstrtoul (unsigned long *out, const char *s, int base) return errno == 0 && !*end && end != s; } +// --- Message reader ---------------------------------------------------------- + +struct msg_reader +{ + struct str buf; ///< Input buffer + uint64_t offset; ///< Current offset in the buffer +}; + +static void +msg_reader_init (struct msg_reader *self) +{ + str_init (&self->buf); + self->offset = 0; +} + +static void +msg_reader_free (struct msg_reader *self) +{ + str_free (&self->buf); +} + +static void +msg_reader_compact (struct msg_reader *self) +{ + str_remove_slice (&self->buf, 0, self->offset); + self->offset = 0; +} + +static void +msg_reader_feed (struct msg_reader *self, const void *data, size_t len) +{ + // TODO: have some mechanism to prevent flooding + msg_reader_compact (self); + str_append_data (&self->buf, data, len); +} + +static void * +msg_reader_get (struct msg_reader *self, size_t *len) +{ + // Try to read in the length of the message + if (self->offset + sizeof (uint64_t) > self->buf.len) + return NULL; + + uint8_t *x = (uint8_t *) self->buf.str + self->offset; + uint64_t msg_len + = (uint64_t) x[0] << 56 | (uint64_t) x[1] << 48 + | (uint64_t) x[2] << 40 | (uint64_t) x[3] << 32 + | (uint64_t) x[4] << 24 | (uint64_t) x[5] << 16 + | (uint64_t) x[6] << 8 | (uint64_t) x[7]; + + if (msg_len < sizeof msg_len) + { + // The message is shorter than its header + // TODO: have some mechanism to report errors + return NULL; + } + + if (self->offset + msg_len < self->offset) + { + // Trying to read an insane amount of data but whatever + msg_reader_compact (self); + return NULL; + } + + // Check if we've got the full message in the buffer and return it + if (self->offset + msg_len > self->buf.len) + return NULL; + + // We have to subtract the header from the reported length + void *data = self->buf.str + self->offset + sizeof msg_len; + self->offset += msg_len; + *len = msg_len - sizeof msg_len; + return data; +} + +// --- Message unpacker -------------------------------------------------------- + +struct msg_unpacker +{ + const char *data; + size_t offset; + size_t len; +}; + +static void +msg_unpacker_init (struct msg_unpacker *self, const void *data, size_t len) +{ + self->data = data; + self->len = len; + self->offset = 0; +} + +static size_t +msg_unpacker_get_available (struct msg_unpacker *self) +{ + return self->len - self->offset; +} + +static bool +msg_unpacker_blob (struct msg_unpacker *self, const void **data, size_t len) +{ + if (self->len - self->offset < len) + return false; + *data = self->data + self->offset; + self->offset += len; + return true; +} + +#define UNPACKER_INT_BEGIN \ + if (self->len - self->offset < sizeof *value) \ + return false; \ + uint8_t *x = (uint8_t *) self->data + self->offset; \ + self->offset += sizeof *value; + +static bool +msg_unpacker_u8 (struct msg_unpacker *self, uint8_t *value) +{ + UNPACKER_INT_BEGIN + *value = x[0]; + return true; +} + +static bool +msg_unpacker_i32 (struct msg_unpacker *self, int32_t *value) +{ + UNPACKER_INT_BEGIN + *value + = (uint32_t) x[0] << 24 | (uint32_t) x[1] << 16 + | (uint32_t) x[2] << 8 | (uint32_t) x[3]; + return true; +} + +static bool +msg_unpacker_u64 (struct msg_unpacker *self, uint64_t *value) +{ + UNPACKER_INT_BEGIN + *value + = (uint64_t) x[0] << 56 | (uint64_t) x[1] << 48 + | (uint64_t) x[2] << 40 | (uint64_t) x[3] << 32 + | (uint64_t) x[4] << 24 | (uint64_t) x[5] << 16 + | (uint64_t) x[6] << 8 | (uint64_t) x[7]; + return true; +} + +#undef UNPACKER_INT_BEGIN + +// --- Message packer and writer ----------------------------------------------- + +struct msg_writer +{ + struct str buf; ///< Holds the message data +}; + +static void +msg_writer_init (struct msg_writer *self) +{ + str_init (&self->buf); + // Placeholder for message length + str_append_data (&self->buf, "\x00\x00\x00\x00" "\x00\x00\x00\x00", 8); +} + +static void +msg_writer_blob (struct msg_writer *self, const void *data, size_t len) +{ + str_append_data (&self->buf, data, len); +} + +static void +msg_writer_u8 (struct msg_writer *self, uint8_t x) +{ + str_append_data (&self->buf, &x, 1); +} + +static void +msg_writer_i32 (struct msg_writer *self, int32_t x) +{ + uint32_t u = x; + uint8_t tmp[4] = { u >> 24, u >> 16, u >> 8, u }; + str_append_data (&self->buf, tmp, sizeof tmp); +} + +static void +msg_writer_u64 (struct msg_writer *self, uint64_t x) +{ + uint8_t tmp[8] = + { x >> 56, x >> 48, x >> 40, x >> 32, x >> 24, x >> 16, x >> 8, x }; + str_append_data (&self->buf, tmp, sizeof tmp); +} + +static void * +msg_writer_flush (struct msg_writer *self, size_t *len) +{ + // Update the message length + uint64_t x = self->buf.len; + uint8_t tmp[8] = + { x >> 56, x >> 48, x >> 40, x >> 32, x >> 24, x >> 16, x >> 8, x }; + memcpy (self->buf.str, tmp, sizeof tmp); + + *len = x; + return str_steal (&self->buf); +} + // --- Option handler ---------------------------------------------------------- // Simple wrapper for the getopt_long API to make it easier to use and maintain. -- cgit v1.2.3-70-g09d2