protoverse

A metaverse protocol
git clone git://jb55.com/protoverse
Log | Files | Refs | README | LICENSE

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+
MMakefile | 10+++++++++-
Mclient.c | 9++++++---
Mcursor.c | 96++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mcursor.h | 10+++++++++-
Anet.c | 224+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Anet.h | 42++++++++++++++++++++++++++++++++++++++++++
Atest.c | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Avarint.c | 75+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Avarint.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 */