diff options
| -rw-r--r-- | README.md | 4 | ||||
| -rw-r--r-- | src/client.c | 18 | ||||
| -rw-r--r-- | src/globals.h | 23 | ||||
| -rw-r--r-- | src/hash.c | 2 | ||||
| -rw-r--r-- | src/room.c | 71 | ||||
| -rw-r--r-- | src/room.h | 14 | ||||
| -rw-r--r-- | src/server.c | 10 | ||||
| -rw-r--r-- | src/server_reqs.c | 43 | ||||
| -rw-r--r-- | src/telnet.c | 13 | ||||
| -rw-r--r-- | src/userdb.c | 1 | ||||
| -rw-r--r-- | src/util.h | 2 | ||||
| -rw-r--r-- | worlds/test.c | 16 |
12 files changed, 156 insertions, 61 deletions
@@ -80,8 +80,12 @@ latency. ## Known Bugs * Telnet server implementation is not fully conforming + * Initial account login time is bogus +* Every subsequent connection allocates slightly more memory than the + last + ## Versioning Scheme Versions are numbered using the MAJOR.MINOR.BUGFIX scheme. diff --git a/src/client.c b/src/client.c index cbfe63b..fef7fab 100644 --- a/src/client.c +++ b/src/client.c @@ -71,7 +71,8 @@ void __attribute__((format(printf,1,2))) out(const char *fmt, ...) /* do some line wrapping */ - int pos = 0, last_space = 0; + static int pos = 0; + int last_space = 0; char *ptr = buf; uint16_t line_width = telnet_get_width() + 1; char *line_buf = malloc(line_width + 2); @@ -168,8 +169,6 @@ void send_master(unsigned char cmd, const void *data, size_t sz) while(!request_complete) poll_requests(); free(req); - - debugf("done with request\n"); } #define BUFSZ 128 @@ -362,7 +361,7 @@ void client_change_room(room_id id) void *dir_map = NULL; -void client_move(const char *dir) +bool client_move(const char *dir) { const struct dir_pair { const char *text; @@ -401,9 +400,16 @@ void client_move(const char *dir) if(pair) { send_master(REQ_MOVE, &pair->val, sizeof(pair->val)); + if(reqdata_type == TYPE_BOOLEAN && returned_reqdata.boolean) + return true; + else + return false; } else + { out("Unknown direction.\n"); + return false; + } } void client_look(void) @@ -695,8 +701,8 @@ auth: if(dir) { all_upper(dir); - client_move(dir); - client_look(); + if(client_move(dir)) + client_look(); } else out("Expected direction after GO.\n"); diff --git a/src/globals.h b/src/globals.h index 1daac8e..9944996 100644 --- a/src/globals.h +++ b/src/globals.h @@ -28,6 +28,7 @@ #include <arpa/inet.h> #include <arpa/telnet.h> #include <assert.h> +#include <bsd/stdlib.h> // for arc4random #include <ctype.h> #include <errno.h> #include <fcntl.h> @@ -46,6 +47,7 @@ #include <sys/ipc.h> #include <sys/mman.h> #include <sys/socket.h> +#include <sys/time.h> #include <sys/types.h> #include <sys/wait.h> #include <time.h> @@ -90,3 +92,24 @@ #else #define debugf(fmt,...) /* nop */ #endif + +#if 0 +extern bool are_child; + +static void *logged_malloc(const char *func, int line, size_t sz) +{ + if(are_child) + debugf("%s:%d mallocs %d bytes\n", func, line, sz); + return malloc(sz); +} + +static void *logged_calloc(const char *func, int line, size_t x, size_t y) +{ + if(are_child) + debugf("%s:%d callocs %dx%d bytes\n", func, line, x,y); + return calloc(x,y); +} + +#define malloc(x) logged_malloc(__func__, __LINE__, x) +#define calloc(x,y) logged_calloc(__func__, __LINE__, x,y) +#endif @@ -60,6 +60,8 @@ void *hash_init(size_t sz, unsigned (*hash_fn)(const void*), int (*compare_keys)(const void*, const void*)) { struct hash_map *ret = calloc(sizeof(struct hash_map), 1); + if(!sz) + sz = 1; ret->table = calloc(sz, sizeof(struct hash_node*)); ret->table_sz = sz; ret->hash = hash_fn; @@ -42,17 +42,23 @@ bool room_user_add(room_id id, struct child_data *child) if(!room) error("unknown room %d", id); - if(hash_insert(room->users, &child->pid, child)) - return false; + if(child->user) + { + /* hash_insert returns NULL on success */ + return !hash_insert(room->users, child->user, child); + } else - return true; + return false; } bool room_user_del(room_id id, struct child_data *child) { struct room_t *room = room_get(id); - return hash_remove(room->users, &child->pid); + if(child->user) + return hash_remove(room->users, child->user); + else + return false; } void write_roomid(int fd, room_id *id) @@ -116,6 +122,8 @@ static void room_free(struct room_t *room) { hash_free(room->users); room->users = NULL; + hash_free(room->objects); + room->objects = NULL; free(room->data.name); free(room->data.desc); } @@ -135,8 +143,52 @@ void world_free(void) } } -static SIMP_HASH(pid_t, pid_hash); -static SIMP_EQUAL(pid_t, pid_equal); +struct object_t *obj_new(void) +{ + struct object_t *new = calloc(1, sizeof(struct object_t)); + /* generate a unique 128-bit id for this object */ + /* 64 bits are used to store a nanosecond-resolution timestamp */ + /* 64 random bits are also used */ + uint64_t timestamp; + struct timeval tv; + gettimeofday(&tv, NULL); + timestamp = (obj_id)tv.tv_sec * (obj_id)1000000 + (obj_id)tv.tv_usec; + + uint64_t rand_bits; + arc4random_buf(&rand_bits, sizeof(rand_bits)); + + new->id = ((obj_id)timestamp << 64) | (obj_id)rand_bits; + + unsigned char bytes[16]; + memcpy(bytes, &new->id, sizeof(bytes)); + debugf("UUID: "); + for(unsigned i = 0; i < sizeof(bytes); ++i) + { + if(i == 4 || i == 6 || i == 8 || i == 10) + debugf("-"); + debugf("%02x", bytes[15 - i]); + } + debugf("\n"); + + return new; +} + +bool obj_add(room_id room, struct object_t *obj) +{ + return !hash_insert(room_get(room)->objects, &obj->id, obj); +} + +static SIMP_HASH(obj_id, obj_hash); +static SIMP_EQUAL(obj_id, obj_equal); + +#define OBJMAP_SIZE 8 + +/* initialize the room's hash tables */ +static void room_init_maps(struct room_t *room) +{ + room->users = hash_init((userdb_size() / 2) + 1, hash_djb, compare_strings); + room->objects = hash_init(OBJMAP_SIZE, obj_hash, obj_equal); +} /** * Loads a world using data on disk and in memory. @@ -175,7 +227,7 @@ bool world_load(const char *fname, const struct roomdata_t *data, size_t data_sz for(unsigned i = 0; i < world_sz; ++i) { - world[i].users = hash_init((userdb_size() / 2) + 1, pid_hash, pid_equal); + room_init_maps(world + i); world[i].id = read_roomid(fd); memcpy(&world[i].data, data + i, sizeof(struct roomdata_t)); @@ -200,7 +252,7 @@ void world_init(const struct roomdata_t *data, size_t sz, const char *name) world_sz = 0; world_name = strdup(name); - void *map = hash_init(1, hash_djb, compare_strings); + void *map = hash_init(sz / 2 + 1, hash_djb, compare_strings); for(size_t i = 0; i < sz; ++i) { @@ -226,8 +278,7 @@ void world_init(const struct roomdata_t *data, size_t sz, const char *name) world[i].adjacent[dir] = room->id; } } - - world[i].users = hash_init((userdb_size() / 2) + 1, pid_hash, pid_equal); + room_init_maps(world + i); world_sz = i + 1; } @@ -48,7 +48,7 @@ struct roomdata_t { }; struct object_t { - obj_id id; + obj_id id; // don't modify const char *name; /* no articles: "a", "an", "the" */ @@ -62,7 +62,7 @@ struct object_t { void (*hook_drop)(struct object_t*, user_t *user); void (*hook_use)(struct object_t*, user_t *user); void (*hook_destroy)(struct object_t*); - char* (*hook_desc)(struct object_t*, user_t*); + const char* (*hook_desc)(struct object_t*, user_t*); }; struct verb_t { @@ -79,9 +79,9 @@ struct room_t { room_id adjacent[NUM_DIRECTIONS]; /* hash maps */ - void *objects; + void *objects; /* obj_id -> object */ void *verbs; - void *users; /* PID -> user_t */ + void *users; /* username -> child_data */ }; /* room/world */ @@ -93,10 +93,10 @@ struct room_t *room_get(room_id id); bool room_user_add(room_id id, struct child_data *child); bool room_user_del(room_id id, struct child_data *child); -/* returns a new object */ +/* returns a new object with a unique id */ struct object_t *obj_new(void); -/* new should point to a statically allocated object */ -void obj_add(room_id room, struct object_t *new); +/* new should point to a new object allocated with obj_new */ +bool obj_add(room_id room, struct object_t *new); void world_free(void); diff --git a/src/server.c b/src/server.c index 6ce4de4..b8d5bb4 100644 --- a/src/server.c +++ b/src/server.c @@ -76,8 +76,11 @@ static void handle_disconnects(void) pid_t pid; while((pid = waitpid(-1, NULL, WNOHANG)) > 0) { + struct child_data *child = hash_lookup(child_map, &pid); + debugf("Client disconnect.\n"); - //struct child_data *child = hash_lookup(child_map, &pid); + + room_user_del(child->room, child); --num_clients; @@ -130,7 +133,7 @@ static void __attribute__((noreturn)) serv_cleanup(void) ev_default_destroy(); - _exit(0); + exit(0); } static void __attribute__((noreturn)) sigint_handler(int s) @@ -228,7 +231,7 @@ static void new_connection_cb(EV_P_ ev_io *w, int revents) if(pipe2(readpipe, O_DIRECT) < 0) error("error creating pipe, need linux kernel >= 3.4"); - if(pipe2(outpipe, O_NONBLOCK | O_DIRECT) < 0) + if(pipe2(outpipe, O_DIRECT) < 0) error("error creating pipe, need linux kernel >= 3.4"); pid_t pid = fork(); @@ -281,6 +284,7 @@ static void new_connection_cb(EV_P_ ev_io *w, int revents) ev_io *new_io_watcher = calloc(1, sizeof(ev_io)); ev_io_init(new_io_watcher, childreq_cb, new->readpipe[0], EV_READ); + ev_set_priority(new_io_watcher, EV_MINPRI); ev_io_start(EV_A_ new_io_watcher); new->io_watcher = new_io_watcher; diff --git a/src/server_reqs.c b/src/server_reqs.c index 5a15812..d10acde 100644 --- a/src/server_reqs.c +++ b/src/server_reqs.c @@ -29,6 +29,13 @@ static void send_packet(struct child_data *child, unsigned char cmd, assert(datalen < MSG_MAX); unsigned char pkt[MSG_MAX]; pkt[0] = cmd; + + //if((data?datalen:0) + 1 > MSG_MAX && cmd == REQ_BCASTMSG) + //{ + // /* TODO: split long messages */ + // ; + //} + if(data && datalen) memcpy(pkt + 1, data, datalen); tryagain: @@ -46,6 +53,8 @@ static void req_pass_msg(unsigned char *data, size_t datalen, (void) sender; send_packet(child, REQ_BCASTMSG, data, datalen); + if(child->pid != sender->pid) + send_packet(child, REQ_ALLDONE, NULL, 0); } static void req_send_clientinfo(unsigned char *data, size_t datalen, @@ -84,8 +93,6 @@ static void req_change_state(unsigned char *data, size_t datalen, (void) data; (void) datalen; (void) child; (void) sender; if(datalen == sizeof(sender->state)) sender->state = *((int*)data); - else - debugf("State data is of the wrong size %*s\n", datalen, data); } static void req_change_user(unsigned char *data, size_t datalen, @@ -112,7 +119,7 @@ static void req_kick_client(unsigned char *data, size_t datalen, { pid_t kicked_pid = *((pid_t*)data); if(kicked_pid == child->pid) - send_packet(child, REQ_BCASTMSG, data + sizeof(pid_t), datalen - sizeof(pid_t)); + send_packet(child, REQ_KICK, data + sizeof(pid_t), datalen - sizeof(pid_t)); } } @@ -128,6 +135,7 @@ static void req_send_desc(unsigned char *data, size_t datalen, struct child_data struct room_t *room = room_get(sender->room); send_packet(sender, REQ_BCASTMSG, room->data.desc, strlen(room->data.desc)); + send_packet(sender, REQ_PRINTNEWLINE, NULL, 0); } @@ -189,10 +197,7 @@ static void req_send_user(unsigned char *data, size_t datalen, struct child_data } debugf("looking up user %s failed\n", data); - debugf("failure 2\n"); } - - debugf("failure 1\n"); } static void req_del_user(unsigned char *data, size_t datalen, struct child_data *sender) @@ -298,20 +303,23 @@ void reqmap_free(void) * 7. Parent spins until the needed number of signals is reached. */ +static unsigned char packet[MSG_MAX + 1]; + bool handle_child_req(int in_fd) { - unsigned char packet[MSG_MAX + 1]; - ssize_t packet_len = read(in_fd, packet, MSG_MAX); - struct child_data *sender = NULL; + if((size_t)packet_len < sizeof(pid_t) + 1) + { + /* the pipe is probably broken (i.e. disconnect), so we don't + * try to send a reply */ + return false; + } - if(packet_len <= 0) - goto fail; + struct child_data *sender = NULL; pid_t sender_pid; memcpy(&sender_pid, packet, sizeof(pid_t)); - debugf("servreq: Got request from PID %d\n", sender_pid); sender = hash_lookup(child_map, &sender_pid); @@ -328,6 +336,8 @@ bool handle_child_req(int in_fd) struct child_request *req = hash_lookup(request_map, &cmd); + debugf("Child %d sends request %d\n", sender_pid, cmd); + if(!req) { debugf("Unknown request.\n"); @@ -360,8 +370,6 @@ bool handle_child_req(int in_fd) if(child->pid == sender->pid) continue; - debugf("iterating over child %d\n", child->pid); - switch(req->which) { case CHILD_ALL: @@ -375,19 +383,14 @@ bool handle_child_req(int in_fd) finish: - debugf("finalizing request\n"); - if(req && req->finalize) req->finalize(data, datalen, sender); /* fall through */ fail: + if(sender) - { send_packet(sender, REQ_ALLDONE, NULL, 0); - debugf("sending all done code\n"); - } - return true; } diff --git a/src/telnet.c b/src/telnet.c index a28752a..743eb53 100644 --- a/src/telnet.c +++ b/src/telnet.c @@ -42,8 +42,6 @@ enum telnet_status telnet_parse_data(const unsigned char *buf, size_t buflen) bool in_sb = false; bool line_done = false; - debugf("telnet: "); - for(unsigned i = 0; i < buflen; ++i) { unsigned char c = buf[i]; @@ -51,16 +49,12 @@ enum telnet_status telnet_parse_data(const unsigned char *buf, size_t buflen) if(c == IAC) iac = true; else if(c == '\n' || c == '\r') - { - debugf("found newline, done reading.\n"); line_done = true; - } if(iac) { if(TELCMD_OK(c)) { - debugf("%s ", TELCMD(c)); found_cmd = true; switch(c) { @@ -78,7 +72,7 @@ enum telnet_status telnet_parse_data(const unsigned char *buf, size_t buflen) while(j < 4 && i < buflen) { bytes[j++] = buf[++i]; - debugf("%d ", buf[j - 1]); + //debugf("%d ", buf[j - 1]); if(bytes[j - 1] == 255) /* 255 is doubled to distinguish from IAC */ { ++i; @@ -94,13 +88,8 @@ enum telnet_status telnet_parse_data(const unsigned char *buf, size_t buflen) continue; } } - debugf("%d ", c); } - debugf("\n"); - if(found_cmd) - debugf("telnet: is NOT data\n"); - return found_cmd ? TELNET_FOUNDCMD : (line_done ? TELNET_LINEOVER : TELNET_DATA); } diff --git a/src/userdb.c b/src/userdb.c index 2d7702c..3a7ebbc 100644 --- a/src/userdb.c +++ b/src/userdb.c @@ -156,7 +156,6 @@ struct userdata_t *userdb_request_lookup(const char *name) if(are_child) { send_master(REQ_GETUSERDATA, name, strlen(name) + 1); - debugf("returned reqdata is of type %d\n", reqdata_type); if(reqdata_type == TYPE_USERDATA) return &returned_reqdata.userdata; return NULL; @@ -21,6 +21,6 @@ /* utility functions */ void __attribute__((noreturn,format(printf,1,2))) error(const char *fmt, ...); -void debugf_real(const char*, int, const char*, const char *fmt, ...); +void __attribute__((format(printf,4,5))) debugf_real(const char*, int, const char*, const char *fmt, ...); void remove_cruft(char*); void all_upper(char*); diff --git a/worlds/test.c b/worlds/test.c index b93b795..d31dd11 100644 --- a/worlds/test.c +++ b/worlds/test.c @@ -1,12 +1,26 @@ #include <world_api.h> +const char *sword_desc(struct object_t *obj, user_t *user) +{ + return "It is very shiny."; +} + +static void portal_init(room_id id) +{ + debugf("portal room init.\n"); + struct object_t *new = obj_new(); + new->name = "sword"; + new->hook_desc = sword_desc; + obj_add(id, new); +} + const struct roomdata_t netcosm_world[] = { { "portal_room", "Portal Room", "You stand in the middle of a stone room. In to the east lies a portal to the world. Above it, there is a sign that reads `Alacron, 238 A.B.A.`.", { NONE_N, NONE_NE, "world_start", NONE_SE, NONE_S, NONE_SW, NONE_W, NONE_NW, NONE_UP, NONE_DN, "world_start", NONE_OT }, - NULL, + portal_init, NULL, NULL, }, |