commit 1b2b24fa43d98378b15c09d947ddb470e7d3af04
parent 7223cbab79fd158dd605c69aebed990e8a3e5a83
Author: William Casarin <jb55@jb55.com>
Date: Sun, 2 Aug 2020 15:05:48 -0700
Initial network packet serialization
Signed-off-by: William Casarin <jb55@jb55.com>
Diffstat:
M | .gitignore | | | 1 | + |
M | Makefile | | | 10 | +++++++++- |
M | client.c | | | 9 | ++++++--- |
M | cursor.c | | | 96 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- |
M | cursor.h | | | 10 | +++++++++- |
A | net.c | | | 224 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | net.h | | | 42 | ++++++++++++++++++++++++++++++++++++++++++ |
A | test.c | | | 61 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | varint.c | | | 75 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | varint.h | | | 14 | ++++++++++++++ |
10 files changed, 536 insertions(+), 6 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -2,3 +2,4 @@
/protoverse
/libprotoverse.a
/TAGS
+/test
diff --git a/Makefile b/Makefile
@@ -1,7 +1,9 @@
CFLAGS = -Wno-error=unused-function -O1 -g -std=c89 -Wall -Wextra -Werror -Wstrict-prototypes -Wold-style-definition -Wmissing-prototypes -Wmissing-declarations -Wdeclaration-after-statement
-OBJS = io.o parse.o cursor.o describe.o serve.o client.o
+OBJS = io.o parse.o cursor.o describe.o serve.o client.o net.o varint.o
+
+all: protoverse libprotoverse.a
protoverse: protoverse.c $(OBJS)
$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@
@@ -12,6 +14,12 @@ libprotoverse.a: $(OBJS)
clean:
rm -f protoverse *.o
+test: test.c $(OBJS)
+ $(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@
+
+check: test
+ ./test
+
TAGS: fake
etags *.c *.h > $@
diff --git a/client.c b/client.c
@@ -9,18 +9,23 @@
#include <errno.h>
#include "client.h"
+#include "cursor.h"
int inet_aton(const char *cp, struct in_addr *inp);
int protoverse_connect(const char *server_ip_str, int port)
{
+ static unsigned char buf[0xFFFF];
+
int sockfd;
struct in_addr server_in_addr;
struct sockaddr_in server_addr;
- static char buf[2048];
+ struct cursor cursor;
ssize_t sent;
const char msg[] = "hello, world";
+ make_cursor(buf, buf + sizeof(buf), &cursor);
+
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
printf("socket creation failed: %s\n", strerror(errno));
return 0;
@@ -35,8 +40,6 @@ int protoverse_connect(const char *server_ip_str, int port)
server_addr.sin_port = port == 0 || port == -1 ? 1988 : port;
server_addr.sin_addr = server_in_addr;
- strncpy(buf, msg, sizeof(buf));
-
printf("sending '%s' to %s\n", msg, server_ip_str);
sent = sendto(sockfd, buf, sizeof(msg), MSG_CONFIRM,
(struct sockaddr*)&server_addr,
diff --git a/cursor.c b/cursor.c
@@ -1,6 +1,8 @@
#include "cursor.h"
#include "typedefs.h"
+#include "varint.h"
+
#include <stdio.h>
#include <string.h>
@@ -63,7 +65,7 @@ int pull_data(struct cursor *cursor, u8 *data, int len)
int push_data(struct cursor *cursor, u8 *data, int len)
{
- if (cursor->p + len >= cursor->end) {
+ if (cursor->p + len > cursor->end) {
printf("push_data oob\n");
return 0;
}
@@ -79,6 +81,61 @@ int push_int(struct cursor *cursor, int i)
return push_data(cursor, (u8*)&i, sizeof(i));
}
+/* TODO: push_varint */
+int push_varint(struct cursor *cursor, int n)
+{
+ int ok, len;
+ unsigned char b;
+ len = 0;
+
+ while (1) {
+ b = (n & 0xFF) | 0x80;
+ n >>= 7;
+ if (n == 0) {
+ b &= 0x7F;
+ ok = push_byte(cursor, b);
+ len++;
+ if (!ok) return 0;
+ break;
+ }
+
+ ok = push_byte(cursor, b);
+ len++;
+ if (!ok) return 0;
+ }
+
+ return len;
+}
+
+/* TODO: pull_varint */
+int pull_varint(struct cursor *cursor, int *n)
+{
+ int ok, i;
+ unsigned char b;
+ *n = 0;
+
+ for (i = 0;; i++) {
+ ok = pull_byte(cursor, &b);
+ if (!ok) return 0;
+
+ *n |= ((int)b & 0x7F) << (i * 7);
+
+ /* is_last */
+ if ((b & 0x80) == 0) {
+ return 1;
+ }
+
+ if (i == 4) return 0;
+ }
+
+ return 0;
+}
+
+int pull_int(struct cursor *cursor, int *i)
+{
+ return pull_data(cursor, (u8*)i, sizeof(*i));
+}
+
int push_u16(struct cursor *cursor, u16 i)
{
return push_data(cursor, (u8*)&i, sizeof(i));
@@ -105,3 +162,40 @@ int push_str(struct cursor *cursor, const char *str)
{
return push_data(cursor, (u8*)str, strlen(str));
}
+
+/* TODO: push varint size */
+int push_prefixed_str(struct cursor *cursor, const char *str)
+{
+ int ok, len;
+ len = strlen(str);
+ ok = push_varint(cursor, len);
+ if (!ok) return 0;
+ return push_sized_str(cursor, str, len);
+}
+
+int pull_prefixed_str(struct cursor *cursor, struct cursor *dest_buf, const char **str)
+{
+ int len, ok;
+
+ ok = pull_varint(cursor, &len);
+ if (!ok) return 0;
+
+ if (dest_buf->p + len > dest_buf->end) {
+ return 0;
+ }
+
+ ok = pull_data(cursor, dest_buf->p, len);
+ if (!ok) return 0;
+
+ *str = (char*)dest_buf->p;
+ dest_buf->p += len;
+
+ ok = push_byte(dest_buf, 0);
+
+ return 1;
+}
+
+int cursor_remaining_capacity(struct cursor *cursor)
+{
+ return cursor->end - cursor->p;
+}
diff --git a/cursor.h b/cursor.h
@@ -12,16 +12,24 @@ struct cursor {
void copy_cursor(struct cursor *src, struct cursor *dest);
int cursor_index(struct cursor *cursor, int elem_size);
void make_cursor(unsigned char *start, unsigned char *end, struct cursor *cursor);
+int cursor_remaining_capacity(struct cursor *cursor);
void *index_cursor(struct cursor *cursor, unsigned short index, int elem_size);
int push_u16(struct cursor *cursor, unsigned short i);
int push_int(struct cursor *cursor, int i);
+int push_varint(struct cursor *cursor, int i);
int push_data(struct cursor *cursor, unsigned char *data, int len);
+int push_byte(struct cursor *cursor, unsigned char c);
+
int pull_data(struct cursor *cursor, unsigned char *data, int len);
int pull_byte(struct cursor *cursor, unsigned char *c);
-int push_byte(struct cursor *cursor, unsigned char c);
+int pull_int(struct cursor *cursor, int *i);
+int pull_varint(struct cursor *cursor, int *i);
+int push_prefixed_str(struct cursor *cursor, const char *str);
int push_str(struct cursor *cursor, const char *str);
int push_sized_str(struct cursor *cursor, const char *str, int len);
+int pull_prefixed_str(struct cursor *cursor, struct cursor *dest_buf, const char **str);
+
#endif
diff --git a/net.c b/net.c
@@ -0,0 +1,224 @@
+
+#include "net.h"
+#include "cursor.h"
+#include "varint.h"
+
+#include <sys/types.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdio.h>
+#include <string.h>
+
+static int push_fetch_packet(struct cursor *c, struct fetch_data_packet *fetch)
+{
+ return push_prefixed_str(c, fetch->path);
+}
+
+/* TODO: CPU-independent encoding */
+static int push_chat_packet(struct cursor *c, struct chat_packet *chat)
+{
+ int ok;
+ ok = push_varint(c, chat->sender);
+ if (!ok) return 0;
+
+ return push_prefixed_str(c, chat->message);
+}
+
+static int pull_chat_packet(struct cursor *c, struct cursor *buf, struct chat_packet *chat)
+{
+ int ok;
+
+ ok = pull_varint(c, &chat->sender);
+ if (!ok) return 0;
+
+ return pull_prefixed_str(c, buf, &chat->message);
+}
+
+static int pull_fetch_packet(struct cursor *c, struct cursor *buf, struct fetch_data_packet *fetch)
+{
+ return pull_prefixed_str(c, buf, &fetch->path);
+}
+
+int send_packet(int sockfd, struct sockaddr *to_addr, int to_addr_len, struct packet *packet)
+{
+ static unsigned char buf[0xFFFF];
+ int ok, len;
+
+ len = push_packet(buf, sizeof(buf), packet);
+ if (!len) return 0;
+
+ ok = sendto(sockfd, buf, len, MSG_CONFIRM, to_addr, to_addr_len);
+
+ if (ok != len) {
+ printf("sendto: sent %d != packet_len %d\n", ok, len);
+ return 0;
+ }
+
+ return 1;
+}
+
+static int push_envelope(struct cursor *cursor, enum packet_type type, int len)
+{
+ int ok;
+ int env_len;
+
+ ok = push_varint(cursor, (int)type);
+ if (!ok) return 0;
+
+ env_len = ok;
+
+ ok = push_varint(cursor, len);
+
+ env_len += ok;
+
+ return env_len;
+}
+
+static int pull_envelope(struct cursor *cursor, enum packet_type *type, int *len)
+{
+ int ok, env_len;
+ ok = pull_varint(cursor, (int*)type);
+ if (!ok) return 0;
+
+ if (*type >= PKT_NUM_TYPES)
+ return 0;
+
+ env_len = ok;
+
+ ok = pull_varint(cursor, len);
+ if (!ok) return 0;
+
+ env_len += ok;
+
+ return env_len;
+}
+
+static int push_packet_data(struct cursor *c, struct packet *packet)
+{
+ switch (packet->type) {
+ case PKT_FETCH_DATA:
+ return push_fetch_packet(c, &packet->data.fetch);
+ break;
+ case PKT_CHAT:
+ return push_chat_packet(c, &packet->data.chat);
+ break;
+ case PKT_NUM_TYPES:
+ return 0;
+ }
+
+ return 0;
+}
+
+int push_packet(unsigned char *buf, int bufsize, struct packet *packet)
+{
+ struct cursor cursor;
+ struct cursor envelope_cursor;
+ int len, ok, envelope_size;
+ envelope_size = VARINT_MAX_LEN * 2;
+
+ make_cursor(buf, buf + envelope_size, &envelope_cursor);
+ make_cursor(buf + envelope_size, buf + bufsize, &cursor);
+
+ ok = push_packet_data(&cursor, packet);
+ if (!ok) return 0;
+
+ len = cursor.p - cursor.start;
+
+ ok = push_envelope(&envelope_cursor, packet->type, len);
+ if (!ok) return 0;
+
+ memmove(buf + ok, cursor.start, len);
+
+ return len + ok;
+}
+
+
+static int pull_packet_data(struct cursor *c, struct cursor *buf,
+ struct packet *packet, int len)
+{
+ (void)c;
+ (void)len;
+ switch (packet->type) {
+ case PKT_FETCH_DATA:
+ return pull_fetch_packet(c, buf, &packet->data.fetch);
+ case PKT_CHAT:
+ return pull_chat_packet(c, buf, &packet->data.chat);
+ case PKT_NUM_TYPES:
+ break;
+ }
+
+ return 0;
+}
+
+int pull_packet(struct cursor *c, struct cursor *buf, struct packet *packet)
+{
+ int ok, env_size, len, capacity_left;
+
+ env_size = pull_envelope(c, &packet->type, &len);
+ if (!env_size) return 0;
+
+ capacity_left = cursor_remaining_capacity(c) - 1;
+ if (len > capacity_left) {
+ printf("sanity: packet larger (%d) than remaining buffer size: %d", len, capacity_left);
+ return 0;
+ }
+
+ ok = pull_packet_data(c, buf, packet, len);
+ if (!ok) return 0;
+
+ return len + env_size;
+}
+
+static int packet_chat_eq(struct chat_packet *a, struct chat_packet *b)
+{
+ /* fail if either is null but not both 0 or both not 0 */
+ if ((a->message == 0) ^ (b->message == 0))
+ return 0;
+
+ return a->sender == b->sender && !strcmp(a->message, b->message);
+}
+
+static int packet_fetch_eq(struct fetch_data_packet *a,
+ struct fetch_data_packet *b)
+{
+ if ((a->path == 0) ^ (b->path == 0))
+ return 0;
+
+ return !strcmp(a->path, b->path);
+}
+
+int packet_eq(struct packet *a, struct packet *b)
+{
+ if (a->type != b->type)
+ return 0;
+
+ switch (a->type) {
+ case PKT_CHAT:
+ return packet_chat_eq(&a->data.chat, &b->data.chat);
+ case PKT_FETCH_DATA:
+ return packet_fetch_eq(&a->data.fetch, &b->data.fetch);
+ case PKT_NUM_TYPES:
+ return 0;
+ }
+
+ return 0;
+}
+
+
+void print_packet(struct packet *packet)
+{
+ switch (packet->type) {
+ case PKT_CHAT:
+ printf("(chat (sender %d) (message \"%s\"))\n",
+ packet->data.chat.sender,
+ packet->data.chat.message);
+ return;
+ case PKT_FETCH_DATA:
+ printf("(fetch)\n");
+ return;
+ case PKT_NUM_TYPES:
+ break;
+ }
+
+ printf("(unknown)\n");
+}
diff --git a/net.h b/net.h
@@ -0,0 +1,42 @@
+
+#ifndef PROTOVERSE_NET_H
+#define PROTOVERSE_NET_H
+
+#include <sys/socket.h>
+#include "cursor.h"
+
+enum packet_type {
+ PKT_FETCH_DATA,
+ PKT_CHAT,
+ PKT_NUM_TYPES,
+};
+
+struct fetch_data_packet {
+ const char *path;
+};
+
+struct chat_packet {
+ int sender;
+ const char *message;
+};
+
+union packet_data {
+ struct fetch_data_packet fetch;
+ struct chat_packet chat;
+};
+
+struct packet {
+ enum packet_type type;
+ union packet_data data;
+};
+
+int send_packet(int fd, struct sockaddr *to_addr, int to_addr_len, struct packet *packet);
+int recv_packet(int fd, struct packet *packet);
+
+int push_packet(unsigned char *buf, int bufsize, struct packet *packet);
+int pull_packet(struct cursor *c, struct cursor *buf, struct packet *packet);
+
+int packet_eq(struct packet *a, struct packet *b);
+void print_packet(struct packet *packet);
+
+#endif /* PROTOVERSE_NET_H */
diff --git a/test.c b/test.c
@@ -0,0 +1,61 @@
+
+#include "net.h"
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+
+static void print_mem(unsigned char *a, int len)
+{
+ int i;
+ for (i = 0; i < len; i++) {
+ printf("%02x ", a[i]);
+ }
+ printf("\n");
+
+}
+
+int main(int argc, char *argv[])
+{
+ static unsigned char bufs[3][1024];
+
+ struct cursor cursors[3];
+ struct packet packet;
+ struct packet packet_out;
+
+ int pushed[2], pulled, i;
+
+ (void)argc;
+ (void)argv;
+
+ for (i = 0; i < 3; i++) {
+ make_cursor(bufs[i], bufs[i] + sizeof(bufs[i]), &cursors[i]);
+ }
+
+ packet.type = PKT_CHAT;
+ packet.data.chat.sender = 1;
+ packet.data.chat.message = "hello there";
+
+ pushed[0] = push_packet(bufs[0], sizeof(bufs[0]), &packet);
+ assert(pushed[0]);
+
+ pulled = pull_packet(&cursors[0], &cursors[1], &packet_out);
+ assert(pulled);
+
+ pushed[1] = push_packet(bufs[2], sizeof(bufs[2]), &packet_out);
+ assert(pushed[1]);
+
+ printf("chat packet\n");
+ print_mem(bufs[0], pushed[0]);
+
+ /* printf("pushed %d,%d pulled %d\n", pushed[0], pushed[1], pulled); */
+ assert(pushed[0] == pulled && pushed[1] == pushed[0]);
+
+
+ assert(!memcmp(bufs[0], bufs[2], pulled));
+ assert(packet_eq(&packet, &packet_out));
+
+ print_packet(&packet);
+
+ return 0;
+}
diff --git a/varint.c b/varint.c
@@ -0,0 +1,75 @@
+
+#include "varint.h"
+
+size_t varint_size(uint64_t v)
+{
+ if (v < 0xfd)
+ return 1;
+ if (v <= 0xffff)
+ return 3;
+ if (v <= 0xffffffff)
+ return 5;
+ return 9;
+}
+
+size_t varint_put(unsigned char buf[VARINT_MAX_LEN], uint64_t v)
+{
+ unsigned char *p = buf;
+
+ if (v < 0xfd) {
+ *(p++) = v;
+ } else if (v <= 0xffff) {
+ (*p++) = 0xfd;
+ (*p++) = v;
+ (*p++) = v >> 8;
+ } else if (v <= 0xffffffff) {
+ (*p++) = 0xfe;
+ (*p++) = v;
+ (*p++) = v >> 8;
+ (*p++) = v >> 16;
+ (*p++) = v >> 24;
+ } else {
+ (*p++) = 0xff;
+ (*p++) = v;
+ (*p++) = v >> 8;
+ (*p++) = v >> 16;
+ (*p++) = v >> 24;
+ (*p++) = v >> 32;
+ (*p++) = v >> 40;
+ (*p++) = v >> 48;
+ (*p++) = v >> 56;
+ }
+ return p - buf;
+}
+
+size_t varint_get(const unsigned char *p, size_t max, int64_t *val)
+{
+ if (max < 1)
+ return 0;
+
+ switch (*p) {
+ case 0xfd:
+ if (max < 3)
+ return 0;
+ *val = ((uint64_t)p[2] << 8) + p[1];
+ return 3;
+ case 0xfe:
+ if (max < 5)
+ return 0;
+ *val = ((uint64_t)p[4] << 24) + ((uint64_t)p[3] << 16)
+ + ((uint64_t)p[2] << 8) + p[1];
+ return 5;
+ case 0xff:
+ if (max < 9)
+ return 0;
+ *val = ((uint64_t)p[8] << 56) + ((uint64_t)p[7] << 48)
+ + ((uint64_t)p[6] << 40) + ((uint64_t)p[5] << 32)
+ + ((uint64_t)p[4] << 24) + ((uint64_t)p[3] << 16)
+ + ((uint64_t)p[2] << 8) + p[1];
+ return 9;
+ default:
+ *val = *p;
+ return 1;
+ }
+}
+
diff --git a/varint.h b/varint.h
@@ -0,0 +1,14 @@
+
+#ifndef PROTOVERSE_VARINT_H
+#define PROTOVERSE_VARINT_H
+
+#define VARINT_MAX_LEN 9
+
+#include <stddef.h>
+#include <stdint.h>
+
+size_t varint_put(unsigned char buf[VARINT_MAX_LEN], uint64_t v);
+size_t varint_size(uint64_t v);
+size_t varint_get(const unsigned char *p, size_t max, int64_t *val);
+
+#endif /* PROTOVERSE_VARINT_H */