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