From 13d9f40dfb924364583ae9858cd18acf35f59c9c Mon Sep 17 00:00:00 2001 From: Evan Lloyd New-Schmidt Date: Sun, 2 Aug 2020 11:39:07 -0700 Subject: [PATCH 01/15] Add version negotiation to network protocol Before making more breaking changes to the networking protocol (like sending user coordinates back and forth for cursor support), it seemed best to add a method of negotiating a protocol version so things can fail or downgrade more gracefully. As implemented here, previous versions of servers and clients will not work with these updated versions. This would be a pretty big problem, except that as far as I know nobody uses this project regularly, especially in a networked/remote capacity. Because previously the client expected canvas data to be sent over immediately, the only way that I can think of to support older clients while not requiring that canvas send is to add a timeout on the server that waits for a version number and then sends the canvas if nothing is sent, which seems pretty unideal. --- src/network.c | 20 ++++++++++++++++++++ src/server.c | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/src/network.c b/src/network.c index 154da44..4c2b6c9 100644 --- a/src/network.c +++ b/src/network.c @@ -29,6 +29,8 @@ struct hostent *hostinfo; struct sockaddr_in address; struct addrinfo hints, *servinfo; +const char* PROTOCOL_VERSION = "1.0"; + /* Connects to server and returns its canvas * */ @@ -61,6 +63,24 @@ Canvas *net_init(char *in_hostname, char *in_port) { sockstream = fdopen(sockfd, "r+"); + // "negotiate" protocol version + char version_request_msg[16]; + snprintf(version_request_msg, 16, "v %s\n", PROTOCOL_VERSION); + if (write(sockfd, version_request_msg, strlen(version_request_msg)) < 0) { + eprintf("version negotiation: write error\n"); + exit(1); + } + + if (getline(&msg_buf, &msg_size, sockstream) == -1) { + eprintf("version negotiation: read error: the cow says 'moo'\n"); + exit(1); + } + if (!(msg_buf[0] == 'v' && msg_buf[1] == 'o' && msg_buf[2] == 'k')) { + eprintf("Failed to negotiate protocol version: the server says '%s'\n", msg_buf); + exit(1); + } + + // receive canvas from server getline(&msg_buf, &msg_size, sockstream); char *command = strtok(msg_buf, " "); if (!strcmp(command, "cs")) { diff --git a/src/server.c b/src/server.c index ce10c42..fd8ba5c 100644 --- a/src/server.c +++ b/src/server.c @@ -25,6 +25,8 @@ static _Atomic unsigned int cli_count = 0; static int uid = 10; +const char* PROTOCOL_VERSION = "1.0"; + #define MAX_CLIENTS 100 #define BUFFER_SZ 2048 @@ -154,6 +156,35 @@ void *handle_client(void *arg) { print_client_addr(cli->addr); printf(" referenced by %d\n", cli->uid); + // protocol negotiation + if ((rlen = read(cli->connfd, buff_in, sizeof(buff_in) - 1)) < 0) { + printf("version negotation: error reading from socket\n"); + goto CLIENT_CLOSE; + } + buff_in[rlen] = '\0'; + strip_newline(buff_in); + char* cmd = strtok(buff_in, " "); + if (cmd == NULL || cmd[0] != 'v') { + printf("version negotiation: client command not 'v'\n"); + + goto CLIENT_CLOSE; + } + char* client_version = strtok(NULL, " "); + if (client_version == NULL) { + printf("version negotiation: unable to parse client version\n"); + send_message_self("can't parse version\n", cli->connfd); + goto CLIENT_CLOSE; + } + if (strcmp(client_version, PROTOCOL_VERSION) != 0) { + printf("version negotiation: unknown client protocol version: '%s'\n", client_version); + send_message_self("unknown protocol - supported protocol versions: ", cli->connfd); + send_message_self(PROTOCOL_VERSION, cli->connfd); + send_message_self("\n", cli->connfd); + goto CLIENT_CLOSE; + } + send_message_self("vok\n", cli->connfd); + + // send canvas sprintf(buff_out, "cs %d %d\n", canvas->num_rows, canvas->num_cols); send_message_self(buff_out, cli->connfd); printf("sent canvas size\n"); @@ -199,6 +230,7 @@ void *handle_client(void *arg) { send_message_self(canvas_buf, cli->connfd); } } + CLIENT_CLOSE: /* Close connection */ close(cli->connfd); From 40ce43be43cdf551a58bffbba2bb6bccd26c40c5 Mon Sep 17 00:00:00 2001 From: Evan Lloyd New-Schmidt Date: Sun, 9 Aug 2020 17:01:51 -0700 Subject: [PATCH 02/15] Add errno messages for protocol negotiation --- src/network.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/network.c b/src/network.c index 4c2b6c9..abfa1fb 100644 --- a/src/network.c +++ b/src/network.c @@ -29,7 +29,7 @@ struct hostent *hostinfo; struct sockaddr_in address; struct addrinfo hints, *servinfo; -const char* PROTOCOL_VERSION = "1.0"; +const char *PROTOCOL_VERSION = "1.0"; /* Connects to server and returns its canvas * @@ -67,16 +67,17 @@ Canvas *net_init(char *in_hostname, char *in_port) { char version_request_msg[16]; snprintf(version_request_msg, 16, "v %s\n", PROTOCOL_VERSION); if (write(sockfd, version_request_msg, strlen(version_request_msg)) < 0) { - eprintf("version negotiation: write error\n"); + perror("version negotiation: write error"); exit(1); } if (getline(&msg_buf, &msg_size, sockstream) == -1) { - eprintf("version negotiation: read error: the cow says 'moo'\n"); + perror("version negotiation: read error"); exit(1); } if (!(msg_buf[0] == 'v' && msg_buf[1] == 'o' && msg_buf[2] == 'k')) { - eprintf("Failed to negotiate protocol version: the server says '%s'\n", msg_buf); + eprintf("Failed to negotiate protocol version: the server says '%s'\n", + msg_buf); exit(1); } From 1d58b8d56e03ddafc12a7235bc1b3dbc9d1650aa Mon Sep 17 00:00:00 2001 From: Evan New-Schmidt Date: Sat, 25 May 2019 19:23:15 -0400 Subject: [PATCH 03/15] Rework server.c --- src/server.c | 112 +++++++++++++++++++++++++++++++++------------------ src/util.h | 19 +++++++-- 2 files changed, 87 insertions(+), 44 deletions(-) diff --git a/src/server.c b/src/server.c index fd8ba5c..663bab2 100644 --- a/src/server.c +++ b/src/server.c @@ -21,6 +21,7 @@ #include #include "canvas.h" +#include "util.h" static _Atomic unsigned int cli_count = 0; static int uid = 10; @@ -30,6 +31,8 @@ const char* PROTOCOL_VERSION = "1.0"; #define MAX_CLIENTS 100 #define BUFFER_SZ 2048 +#define LOG_TRAFFIC + /* Client structure */ typedef struct { struct sockaddr_in addr; /* Client remote address */ @@ -45,42 +48,73 @@ pthread_mutex_t clients_mutex = PTHREAD_MUTEX_INITIALIZER; Canvas *canvas; char *canvas_buf; +bool client_eq(client_t *a, client_t *b) { + return a != NULL && b != NULL && a->uid == b->uid; +} + /* Add client to queue */ void queue_add(client_t *cl) { pthread_mutex_lock(&clients_mutex); - for (int i = 0; i < MAX_CLIENTS; ++i) { + int i; + for (i = 0; i < MAX_CLIENTS; ++i) { if (!clients[i]) { clients[i] = cl; + logd("Stored client %d (%s) at index %d\n", cl->uid, cl->name, i); break; } } + if (i == MAX_CLIENTS) { + logd("No room for additional clients!"); + } pthread_mutex_unlock(&clients_mutex); } +int write_fd(int fd, const char *s) { + const int len = strlen(s); + const int res = write(fd, s, strlen(s)); +#ifdef LOG_TRAFFIC + const int errnum = errno; + logd("Wrote %d of %d bytes to descriptor %d: '%s'\n", res, len, fd, s); + errno = errnum; +#endif + return res; +} + +int write_client(client_t *client, const char *s) { + int res = write_fd(client->connfd, s); + if (res < 0) { + perrorf("Write to client %i (%s) failed", client->uid, client->name); + } + return res; +} + /* Delete client from queue */ -void queue_delete(int uid) { +void queue_delete(client_t *client) { pthread_mutex_lock(&clients_mutex); - for (int i = 0; i < MAX_CLIENTS; ++i) { + int i; + for (i = 0; i < MAX_CLIENTS; ++i) { if (clients[i]) { - if (clients[i]->uid == uid) { + if (client_eq(clients[i], client)) { + logd("queue_delete: removed client %d (%s) from queue\n", + clients[i]->uid, clients[i]->name); clients[i] = NULL; break; } } } + if (i == MAX_CLIENTS) { + logd("queue_delete: couldn't find client %i in queue\n", uid); + } pthread_mutex_unlock(&clients_mutex); } /* Send message to all clients but the sender */ -void send_message(char *s, int uid) { +void send_message(char *s, client_t *client) { pthread_mutex_lock(&clients_mutex); for (int i = 0; i < MAX_CLIENTS; ++i) { if (clients[i]) { - if (clients[i]->uid != uid) { - if (write(clients[i]->connfd, s, strlen(s)) < 0) { - perror("Write to descriptor failed"); - break; - } + if (!client_eq(clients[i], client)) { + write_client(clients[i], s); } } } @@ -92,33 +126,25 @@ void broadcast_message(char *s) { pthread_mutex_lock(&clients_mutex); for (int i = 0; i < MAX_CLIENTS; ++i) { if (clients[i]) { - if (write(clients[i]->connfd, s, strlen(s)) < 0) { - perror("Write to descriptor failed"); - break; - } + write_client(clients[i], s); } } pthread_mutex_unlock(&clients_mutex); } /* Send message to sender */ -void send_message_self(const char *s, int connfd) { - if (write(connfd, s, strlen(s)) < 0) { - perror("Write to descriptor failed"); - exit(-1); - } +void send_message_self(const char *s, client_t *client) { + write_client(client, s); } /* Send message to client */ -void send_message_client(char *s, int uid) { +void send_message_client(char *s, client_t *client) { pthread_mutex_lock(&clients_mutex); for (int i = 0; i < MAX_CLIENTS; ++i) { - if (clients[i]) { - if (clients[i]->uid == uid) { - if (write(clients[i]->connfd, s, strlen(s)) < 0) { - perror("Write to descriptor failed"); - break; - } + if (client_eq(clients[i], client)) { + if (write(clients[i]->connfd, s, strlen(s)) < 0) { + perror("Write to descriptor failed"); + break; } } } @@ -172,26 +198,27 @@ void *handle_client(void *arg) { char* client_version = strtok(NULL, " "); if (client_version == NULL) { printf("version negotiation: unable to parse client version\n"); - send_message_self("can't parse version\n", cli->connfd); + send_message_self("can't parse version\n", cli); goto CLIENT_CLOSE; } if (strcmp(client_version, PROTOCOL_VERSION) != 0) { - printf("version negotiation: unknown client protocol version: '%s'\n", client_version); - send_message_self("unknown protocol - supported protocol versions: ", cli->connfd); - send_message_self(PROTOCOL_VERSION, cli->connfd); - send_message_self("\n", cli->connfd); + printf("version negotiation: unknown client protocol version: '%s'\n", + client_version); + send_message_self("unknown protocol - supported protocol versions: ", cli); + send_message_self(PROTOCOL_VERSION, cli); + send_message_self("\n", cli); goto CLIENT_CLOSE; } - send_message_self("vok\n", cli->connfd); - + send_message_self("vok\n", cli); + // send canvas sprintf(buff_out, "cs %d %d\n", canvas->num_rows, canvas->num_cols); - send_message_self(buff_out, cli->connfd); + send_message_self(buff_out, cli); printf("sent canvas size\n"); canvas_serialize(canvas, canvas_buf); - send_message_self(canvas_buf, cli->connfd); + send_message_self(canvas_buf, cli); sprintf(buff_out, "\n"); - send_message_self(buff_out, cli->connfd); + send_message_self(buff_out, cli); printf("sent serialized canvas\n"); /* Receive input from client */ @@ -205,6 +232,11 @@ void *handle_client(void *arg) { continue; } +#ifdef LOG_TRAFFIC + logd("Read %d bytes from client %d (%s): '%s'\n", rlen, cli->uid, cli->name, + buff_in); +#endif + /* Process Command */ char c = buff_in[strlen(buff_in) - 1]; char *command; @@ -223,11 +255,11 @@ void *handle_client(void *arg) { canvas_scharyx(canvas, y, x, c); sprintf(buff_out, "s %d %d %c\n", y, x, c); - send_message(buff_out, cli->uid); + send_message(buff_out, cli); } } else if (!strcmp(command, "c")) { canvas_serialize(canvas, canvas_buf); - send_message_self(canvas_buf, cli->connfd); + send_message_self(canvas_buf, cli); } } CLIENT_CLOSE: @@ -236,7 +268,7 @@ void *handle_client(void *arg) { close(cli->connfd); /* Delete client from queue and yield thread */ - queue_delete(cli->uid); + queue_delete(cli); printf("<< quit "); print_client_addr(cli->addr); printf(" referenced by %d\n", cli->uid); @@ -307,7 +339,7 @@ int main(int argc, char *argv[]) { perror("Socket binding failed"); serv_addr.sin_port = htons(++port); } - printf("connected to port %d", port); + printf("Connected to port %d\n", port); /* Listen */ if (listen(listenfd, 10) < 0) { diff --git a/src/util.h b/src/util.h index bc64d1e..b6452d8 100644 --- a/src/util.h +++ b/src/util.h @@ -1,4 +1,8 @@ #ifndef util_h +/* Utility macros and functions. + * + * TODO: use fmt argument in macros instead of anything + */ #define util_h #include @@ -8,16 +12,23 @@ // printf to stderr #define eprintf(...) fprintf(stderr, __VA_ARGS__) +// print an error message with formatting that correctly gets the errno +#define perrorf(...) \ + do { \ + const int errnum = errno; \ + eprintf(__VA_ARGS__); \ + eprintf(": %s\n", strerror(errnum)); \ + } while (0) + // LOGGING -// techniques for preventing unused variables/function warnings based on zf_log: -// https://github.com/wonder-mice/zf_log/ +// techniques for preventing unused variables/function warnings based on +// zf_log: https://github.com/wonder-mice/zf_log/ /* Dummy function that does nothing with variadic args. * * Static b/c it shouldn't be used directly anywhere outside of this header. * Inline b/c it fixes the "defined but not used" warning, and it will be called - * many times to do nothing. - * https://stackoverflow.com/a/2765211 + * many times to do nothing. https://stackoverflow.com/a/2765211 * https://stackoverflow.com/q/2845748 * https://stackoverflow.com/a/1932371 * https://stackoverflow.com/q/7762731 From 3bffd1978a50fc46a97478b4cc8e6dea87035aac Mon Sep 17 00:00:00 2001 From: Evan New-Schmidt Date: Sat, 25 May 2019 22:37:36 -0400 Subject: [PATCH 04/15] Tweak server.c --- src/server.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/server.c b/src/server.c index 663bab2..9a94771 100644 --- a/src/server.c +++ b/src/server.c @@ -241,10 +241,11 @@ void *handle_client(void *arg) { char c = buff_in[strlen(buff_in) - 1]; char *command; command = strtok(buff_in, " "); - if (!strcmp(command, "q")) { + if (strcmp(command, "q") == 0) { break; } - if (!strcmp(command, "s")) { + + if (strcmp(command, "s") == 0) { int y = atoi(strtok(NULL, " ")); int x = atoi(strtok(NULL, " ")); @@ -257,9 +258,11 @@ void *handle_client(void *arg) { sprintf(buff_out, "s %d %d %c\n", y, x, c); send_message(buff_out, cli); } - } else if (!strcmp(command, "c")) { + } else if (strcmp(command, "c") == 0) { canvas_serialize(canvas, canvas_buf); send_message_self(canvas_buf, cli); + } else if (strcmp(command, "p") == 0) { + } } CLIENT_CLOSE: From 0c1c350a1de4d61e1eb0ae399929db29c373124d Mon Sep 17 00:00:00 2001 From: Evan New-Schmidt Date: Sat, 25 May 2019 22:39:54 -0400 Subject: [PATCH 05/15] Tweak network.c --- src/frontend.c | 2 +- src/network.c | 55 +++++++++++++++++++++++++++++++++++++++++--------- src/network.h | 3 ++- 3 files changed, 48 insertions(+), 12 deletions(-) diff --git a/src/frontend.c b/src/frontend.c index 52b5bfb..b410f6a 100644 --- a/src/frontend.c +++ b/src/frontend.c @@ -442,7 +442,7 @@ int main(int argc, char *argv[]) { fd == net_cfg->sockfd) { // Accept data from open socket logd("recv network\n"); // If server disconnects - if (net_handler(view) != 0) { + if (net_handler(state) != 0) { networked = false; print_msg_win("Server Disconnect!"); }; diff --git a/src/network.c b/src/network.c index abfa1fb..45755cd 100644 --- a/src/network.c +++ b/src/network.c @@ -12,9 +12,12 @@ #include "canvas.h" #include "network.h" +#include "state.h" #include "util.h" #include "view.h" +#define LOG_TRAFFIC + /* Network Client Variables */ fd_set testfds, clientfds; char *msg_buf; @@ -31,6 +34,31 @@ struct addrinfo hints, *servinfo; const char *PROTOCOL_VERSION = "1.0"; +int write_fd(int fd, const char *s) { + const int len = strlen(s); + const int res = write(fd, s, strlen(s)); +#ifdef LOG_TRAFFIC + const int errnum = errno; + logd("Wrote %d of %d bytes to descriptor %d: '%s'\n", res, len, fd, s); + errno = errnum; +#endif + return res; +} + +int build_set_msg(char *buff, int buff_len, const int y, const int x, + const char val) { + return snprintf(buff, buff_len, "s %i %i %c\n", y, x, val); +} + +int build_pos_msg(char *buff, int buff_len, const int y, const int x, + const int uid) { + return snprintf(buff, buff_len, "p %i %i %i\n", y, x, uid); +} + +int parse_pos_msg(char *buff, int *y, int *x, int *uid) { + return sscanf(buff, "p %i %i %i", y, x, uid) == 3; +} + /* Connects to server and returns its canvas * */ @@ -84,7 +112,7 @@ Canvas *net_init(char *in_hostname, char *in_port) { // receive canvas from server getline(&msg_buf, &msg_size, sockstream); char *command = strtok(msg_buf, " "); - if (!strcmp(command, "cs")) { + if (strcmp(command, "cs") == 0) { int row = atoi(strtok(NULL, " ")); int col = atoi(strtok(NULL, " ")); @@ -119,16 +147,24 @@ Net_cfg *net_getcfg() { /* Reads incoming packets and updates canvas. * Need to run redraw_canvas_win() after calling! */ -int net_handler(View *view) { - logd("receiving: "); +int net_handler(State *state) { + View *view = state->view; + getline(&msg_buf, &msg_size, sockstream); - logd("[%li]", msg_size); - logd("rec buffer: '%s'", msg_buf); +#ifdef LOG_TRAFFIC + logd("received %li bytes: '%s'\n", msg_size, msg_buf); +#endif char ch = msg_buf[strlen(msg_buf) - 2]; // -2 for '\n' char *command = strtok(msg_buf, " \n"); logd("\"%s\"", command); - if (!strcmp(command, "s")) { + if (strcmp(command, "q") == 0) { + logd("closing socket\n"); + close(sockfd); + return 1; + } + + if (strcmp(command, "s") == 0) { int y = atoi(strtok(NULL, " ")); int x = atoi(strtok(NULL, " ")); @@ -149,13 +185,12 @@ int net_handler(View *view) { int net_send_char(int y, int x, char ch) { char send_buf[50]; snprintf(send_buf, 50, "s %d %d %c\n", y, x, ch); - logd("send buffer: '%s'\n", send_buf); - if (write(sockfd, send_buf, strlen(send_buf)) < 0) { - logd("write error"); + + if (write_fd(sockfd, send_buf) < 0) { + perrorf("net_send_char: write_fd"); return -1; } - logd("sending: s %d %d %c\n", y, x, ch); // DON"T TRUST FPRINTF!!! It has failed me! // fprintf(sockstream, "s %d %d %c\n", y, x, ch); diff --git a/src/network.h b/src/network.h index f793a5f..67e5c19 100644 --- a/src/network.h +++ b/src/network.h @@ -7,6 +7,7 @@ #include #include "canvas.h" +#include "state.h" #include "view.h" typedef struct NET_CFG { @@ -16,7 +17,7 @@ typedef struct NET_CFG { Canvas *net_init(char *hostname, char *port); Net_cfg *net_getcfg(); -int net_handler(View *view); +int net_handler(State *state); int net_send_char(int y, int x, char ch); #endif From 488c5d53d23fdeb58dfb7cfd16a3279884d02864 Mon Sep 17 00:00:00 2001 From: Evan New-Schmidt Date: Sun, 26 May 2019 14:16:41 -0700 Subject: [PATCH 06/15] Working position sending --- src/Makefile | 2 +- src/frontend.c | 21 ++++++ src/frontend.h | 2 + src/network.c | 173 ++++++++++++++++++++++++++++++++++++++++++------- src/network.h | 13 ++++ src/server.c | 38 +++++++---- src/state.h | 16 +++++ 7 files changed, 226 insertions(+), 39 deletions(-) diff --git a/src/Makefile b/src/Makefile index 20fbe49..1913765 100644 --- a/src/Makefile +++ b/src/Makefile @@ -34,7 +34,7 @@ frontend.out: LDLIBS +=-lncurses -lm frontend.out: cursor.o fe_modes.o canvas.o view.o network.o lib/argtable3.o server.out: LDLIBS +=-lpthread -server.out: canvas.o +server.out: canvas.o network.o ## PATTERNS diff --git a/src/frontend.c b/src/frontend.c index b410f6a..f9ff667 100644 --- a/src/frontend.c +++ b/src/frontend.c @@ -307,6 +307,7 @@ void init_state(State *state, const arguments_t *const arguments) { .view = view, .last_cursor = cursor_newyx(arguments->y, arguments->x), .filepath = arguments->filename, + .collab_list = collab_list_create(NUM_COLLAB), }; *state = new_state; } @@ -450,6 +451,8 @@ int main(int argc, char *argv[]) { refresh_screen(); } else if (fd == 0) { // process keyboard activity master_handler(state, canvas_win, status_interface->info_win); + draw_collab_cursors(state); + net_update_pos(state); refresh_screen(); } } @@ -531,6 +534,24 @@ void redraw_canvas_win() { } } +void draw_collab_cursors(State *state) { + collab_t *c = NULL; + const int min_x = view->x; + const int min_y = view->y; + const int max_x = min(view_max_x, view->canvas->num_cols - view->x); + const int max_y = min(view_max_y, view->canvas->num_rows - view->y); + for (int i = 0; i < state->collab_list->len; i++) { + c = state->collab_list->list[i]; + if (c != NULL && (c->x >= min_x && c->y <= max_x) && + (c->y >= min_y && c->y <= max_y)) { + logd("Drawing collab %i\n", c->uid); + // TODO: set reverse video and color at this point + if (has_colors()) { + } + } + } +} + void refresh_screen() { update_screen_size(); wmove(canvas_win, cursor_y_to_canvas(cursor), cursor_x_to_canvas(cursor)); diff --git a/src/frontend.h b/src/frontend.h index c61a46f..e0962cc 100644 --- a/src/frontend.h +++ b/src/frontend.h @@ -4,6 +4,7 @@ #include #include "cursor.h" #include "mode_id.h" +#include "state.h" #include "view.h" #define KEY_TAB '\t' @@ -34,4 +35,5 @@ void highlight_mode_text(int x, int num_ch); int print_mode_win(char *format, ...); void update_info_win(const Mode_ID current_mode, const int x, const int y, const int w, const int h); +void draw_collab_cursors(State *state); #endif diff --git a/src/network.c b/src/network.c index 45755cd..a65e12b 100644 --- a/src/network.c +++ b/src/network.c @@ -55,10 +55,148 @@ int build_pos_msg(char *buff, int buff_len, const int y, const int x, return snprintf(buff, buff_len, "p %i %i %i\n", y, x, uid); } -int parse_pos_msg(char *buff, int *y, int *x, int *uid) { +int parse_pos_msg(const char *buff, int *y, int *x, int *uid) { return sscanf(buff, "p %i %i %i", y, x, uid) == 3; } +int net_send_pos(int y, int x) { + char send_buf[32]; + snprintf(send_buf, 32, "p %d %d 0\n", y, x); + if (write_fd(sockfd, send_buf) < 0) { + perrorf("net_send_pos: write_fd"); + return -1; + } + return 0; +} + +int net_update_pos(State *state) { + logd("Hello\n"); + static int last_x = 0; + static int last_y = 0; + bool updated = false; + const int cur_x = state->view->x + state->cursor->x; + const int cur_y = state->view->y + state->cursor->y; + if (cur_x != last_x) { + last_x = cur_x; + updated = true; + } + if (cur_y != last_y) { + last_y = cur_y; + updated = true; + } + if (updated) { + logd("Sending updated pos\n"); + net_send_pos(cur_y, cur_x); + return 1; + } + return 0; +} + +collab_t *collab_create(int uid, int y, int x) { + collab_t *c = malloc(sizeof(collab_t)); + c->uid = uid; + c->y = y; + c->x = x; + return c; +} + +void collab_free(collab_t *collab) { + free(collab); +} + +/* Sends a set char command to the server + * + */ +int net_send_char(int y, int x, char ch) { + char send_buf[50]; + snprintf(send_buf, 50, "s %d %d %c\n", y, x, ch); + + if (write_fd(sockfd, send_buf) < 0) { + perrorf("net_send_char: write_fd"); + return -1; + } + + // DON"T TRUST FPRINTF!!! It has failed me! + // fprintf(sockstream, "s %d %d %c\n", y, x, ch); + + return 0; +} + +collab_list_t *collab_list_create(int len) { + collab_list_t *l = malloc(sizeof(collab_list_t)); + l->len = len; + l->list = malloc(sizeof(collab_t) * len); + l->num = 0; + for (int i = 0; i < len; i++) { + l->list[i] = NULL; + } + return l; +} + +void collab_list_free(collab_list_t *l) { + for (int i = 0; i < l->len; i++) { + collab_free(l->list[i]); + } + free(l->list); + free(l); +} + +int collab_list_add(collab_list_t *l, int uid, int y, int x) { + int i; + logd("Foo\n"); + logd("list: %p\n", l); + for (i = 0; i < l->len; i++) { + logd("Loop %i\n", i); + if (l->list[i] == NULL) { + l->list[i] = collab_create(uid, y, x); + l->num++; + logd("Added new collaborator %i at (%i, %i)\n", uid, y, x); + break; + } + } + if (i == l->len) { + logd("Collaborator list full\n"); + return -1; + } + return 0; +} + +int collab_list_del(collab_list_t *l, int uid) { + int i; + for (i = 0; i < l->len; i++) { + if (l->list[i]->uid == uid) { + collab_t *c = l->list[i]; + l->list[i] = NULL; + collab_free(c); + l->num--; + break; + } + } + if (i == l->len) { + logd("Couldn't find uid %i in list\n", uid); + return -1; + } + return 0; +} + +int collab_list_upd(collab_list_t *l, int uid, int y, int x) { + int i; + logd("foo\n"); + for (i = 0; i < l->len; i++) { + logd("upd Loop: %d\n", i); + if (l->list[i] != NULL && l->list[i]->uid == uid) { + l->list[i]->x = x; + l->list[i]->y = y; + logd("Updated collaborator %i to (%i, %i)\n", uid, x, y); + break; + } + } + if (i == l->len) { + return collab_list_add(l, uid, y, x); + } + return 0; +} + /* Connects to server and returns its canvas * */ @@ -155,6 +293,7 @@ int net_handler(State *state) { logd("received %li bytes: '%s'\n", msg_size, msg_buf); #endif char ch = msg_buf[strlen(msg_buf) - 2]; // -2 for '\n' + char *msg = strndup(msg_buf, msg_size); char *command = strtok(msg_buf, " \n"); logd("\"%s\"", command); @@ -169,30 +308,14 @@ int net_handler(State *state) { int x = atoi(strtok(NULL, " ")); canvas_scharyx(view->canvas, y, x, ch); + } else if (strcmp(command, "p") == 0) { + int y, x, uid; + // logd("msg_buf: '%s'", msg_buf); + if (!parse_pos_msg(msg, &y, &x, &uid)) { + perrorf("net_handler: parse_pos_msg"); + } + collab_list_upd(state->collab_list, uid, y, x); } - if (!strcmp(command, "q")) { - logd("closing socket\n"); - close(sockfd); - return 1; - } - - return 0; -} - -/* Sends a set char command to the server - * - */ -int net_send_char(int y, int x, char ch) { - char send_buf[50]; - snprintf(send_buf, 50, "s %d %d %c\n", y, x, ch); - - if (write_fd(sockfd, send_buf) < 0) { - perrorf("net_send_char: write_fd"); - return -1; - } - - // DON"T TRUST FPRINTF!!! It has failed me! - // fprintf(sockstream, "s %d %d %c\n", y, x, ch); - + free(msg); return 0; } diff --git a/src/network.h b/src/network.h index 67e5c19..dd4acf7 100644 --- a/src/network.h +++ b/src/network.h @@ -10,14 +10,27 @@ #include "state.h" #include "view.h" +#define NUM_COLLAB 20 + +const char *PROTOCOL_VERSION; + typedef struct NET_CFG { fd_set clientfds; int sockfd; } Net_cfg; +int write_fd(int fd, const char *s); + Canvas *net_init(char *hostname, char *port); Net_cfg *net_getcfg(); int net_handler(State *state); int net_send_char(int y, int x, char ch); +int net_update_pos(State *state); +int parse_pos_msg(const char *buff, int *y, int *x, int *uid); +int build_pos_msg(char *buff, int buff_len, const int y, const int x, + const int uid); + +collab_list_t *collab_list_create(int len); +void collab_list_free(collab_list_t *l); #endif diff --git a/src/server.c b/src/server.c index 9a94771..1aa930f 100644 --- a/src/server.c +++ b/src/server.c @@ -21,13 +21,12 @@ #include #include "canvas.h" +#include "network.h" #include "util.h" static _Atomic unsigned int cli_count = 0; static int uid = 10; -const char* PROTOCOL_VERSION = "1.0"; - #define MAX_CLIENTS 100 #define BUFFER_SZ 2048 @@ -69,16 +68,16 @@ void queue_add(client_t *cl) { pthread_mutex_unlock(&clients_mutex); } -int write_fd(int fd, const char *s) { - const int len = strlen(s); - const int res = write(fd, s, strlen(s)); -#ifdef LOG_TRAFFIC - const int errnum = errno; - logd("Wrote %d of %d bytes to descriptor %d: '%s'\n", res, len, fd, s); - errno = errnum; -#endif - return res; -} +// int write_fd(int fd, const char *s) { +// const int len = strlen(s); +// const int res = write(fd, s, strlen(s)); +// #ifdef LOG_TRAFFIC +// const int errnum = errno; +// logd("Wrote %d of %d bytes to descriptor %d: '%s'\n", res, len, fd, s); +// errno = errnum; +// #endif +// return res; +// } int write_client(client_t *client, const char *s) { int res = write_fd(client->connfd, s); @@ -240,6 +239,7 @@ void *handle_client(void *arg) { /* Process Command */ char c = buff_in[strlen(buff_in) - 1]; char *command; + char *msg = strndup(buff_in, BUFFER_SZ); command = strtok(buff_in, " "); if (strcmp(command, "q") == 0) { break; @@ -262,8 +262,20 @@ void *handle_client(void *arg) { canvas_serialize(canvas, canvas_buf); send_message_self(canvas_buf, cli); } else if (strcmp(command, "p") == 0) { - + // copy pos message and fill in client id + char send_buff[32]; + int y, x, uid; + if (!parse_pos_msg(msg, &y, &x, &uid)) { + logd("Warning: parse_pos_msg didn't get all of them\n"); + } + logd("Got (%i, %i) from '%s'\n", x, y, msg); + if (build_pos_msg(send_buff, 32, y, x, cli->uid) >= 32) { + logd("handle_client: build_pos_msg buffer too small\n"); + } + // send to other clients + send_message(send_buff, cli); } + free(msg); } CLIENT_CLOSE: diff --git a/src/state.h b/src/state.h index b4c9d25..cc8584f 100644 --- a/src/state.h +++ b/src/state.h @@ -5,6 +5,18 @@ #include "mode_id.h" #include "view.h" +typedef struct { + int uid; + int y; + int x; +} collab_t; + +typedef struct { + int num; + int len; + collab_t **list; +} collab_list_t; + /* State keeps track of changing variables for mode functions. * If you add something, don't forget to also add an init before the main * loop. @@ -22,6 +34,10 @@ typedef struct { int last_arrow_direction; Cursor *last_cursor; char *filepath; // path of savefile + + // network-related + collab_list_t *collab_list; + char *name; } State; #endif From 2631841558e5d62b67a1b5fd450dd0b4bd32f523 Mon Sep 17 00:00:00 2001 From: Evan New-Schmidt Date: Sun, 26 May 2019 14:45:26 -0700 Subject: [PATCH 07/15] Draw collaborator cursors on screen --- src/frontend.c | 22 ++++++++++++---------- src/network.c | 5 ----- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/frontend.c b/src/frontend.c index f9ff667..0be19d1 100644 --- a/src/frontend.c +++ b/src/frontend.c @@ -452,8 +452,8 @@ int main(int argc, char *argv[]) { } else if (fd == 0) { // process keyboard activity master_handler(state, canvas_win, status_interface->info_win); draw_collab_cursors(state); - net_update_pos(state); refresh_screen(); + net_update_pos(state); } } } @@ -478,12 +478,12 @@ void setup_colors() { // TODO: Use #define to get colors for standard uses // Assign color codes - init_pair(1, COLOR_RED, COLOR_BLACK); - init_pair(2, COLOR_GREEN, COLOR_BLACK); - init_pair(3, COLOR_BLUE, COLOR_BLACK); - init_pair(4, COLOR_CYAN, COLOR_BLACK); - init_pair(5, COLOR_MAGENTA, COLOR_BLACK); - init_pair(6, COLOR_YELLOW, COLOR_BLACK); + init_pair(1, COLOR_WHITE, COLOR_BLUE); + init_pair(2, COLOR_WHITE, COLOR_GREEN); + init_pair(3, COLOR_WHITE, COLOR_RED); + init_pair(4, COLOR_WHITE, COLOR_CYAN); + init_pair(5, COLOR_WHITE, COLOR_MAGENTA); + init_pair(6, COLOR_WHITE, COLOR_YELLOW); init_pair(7, COLOR_BLACK, COLOR_WHITE); } @@ -545,9 +545,11 @@ void draw_collab_cursors(State *state) { if (c != NULL && (c->x >= min_x && c->y <= max_x) && (c->y >= min_y && c->y <= max_y)) { logd("Drawing collab %i\n", c->uid); - // TODO: set reverse video and color at this point - if (has_colors()) { - } + const int color = has_colors() ? (i % 6) + 1 : 0; + // TODO: blink cursor with A_BLINK attribute (needs to pause between + // updates/only move on changes?) + mvwchgat(canvas_win, c->y - view->y + 1, c->x - view->x + 1, 1, 0, color, + NULL); } } } diff --git a/src/network.c b/src/network.c index a65e12b..063b720 100644 --- a/src/network.c +++ b/src/network.c @@ -143,10 +143,7 @@ void collab_list_free(collab_list_t *l) { int collab_list_add(collab_list_t *l, int uid, int y, int x) { int i; - logd("Foo\n"); - logd("list: %p\n", l); for (i = 0; i < l->len; i++) { - logd("Loop %i\n", i); if (l->list[i] == NULL) { l->list[i] = collab_create(uid, y, x); l->num++; @@ -181,9 +178,7 @@ int collab_list_del(collab_list_t *l, int uid) { int collab_list_upd(collab_list_t *l, int uid, int y, int x) { int i; - logd("foo\n"); for (i = 0; i < l->len; i++) { - logd("upd Loop: %d\n", i); if (l->list[i] != NULL && l->list[i]->uid == uid) { l->list[i]->x = x; l->list[i]->y = y; From 25499988320d397c97aad177881a23e894e3cdfb Mon Sep 17 00:00:00 2001 From: Evan New-Schmidt Date: Sun, 26 May 2019 16:24:17 -0700 Subject: [PATCH 08/15] Draw cursors on network update too --- src/frontend.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/frontend.c b/src/frontend.c index 0be19d1..27425a3 100644 --- a/src/frontend.c +++ b/src/frontend.c @@ -448,6 +448,7 @@ int main(int argc, char *argv[]) { print_msg_win("Server Disconnect!"); }; redraw_canvas_win(); // TODO: draw single char update + draw_collab_cursors(state); refresh_screen(); } else if (fd == 0) { // process keyboard activity master_handler(state, canvas_win, status_interface->info_win); From 196146cfbf6462b5f461c41ffb0dfd987dcaeac5 Mon Sep 17 00:00:00 2001 From: Evan New-Schmidt Date: Mon, 27 May 2019 15:59:54 -0700 Subject: [PATCH 09/15] Start uids at 1 --- src/server.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server.c b/src/server.c index 1aa930f..0516d2c 100644 --- a/src/server.c +++ b/src/server.c @@ -25,7 +25,7 @@ #include "util.h" static _Atomic unsigned int cli_count = 0; -static int uid = 10; +static int uid = 1; // start uids at 1 #define MAX_CLIENTS 100 #define BUFFER_SZ 2048 From f249c4776ab7b3adaf6124ab0d88e730a22d352d Mon Sep 17 00:00:00 2001 From: Evan New-Schmidt Date: Mon, 27 May 2019 16:33:37 -0700 Subject: [PATCH 10/15] Set collab colors based on uid Cursor colors are now consistent across clients --- src/frontend.c | 46 +++++++++++++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/src/frontend.c b/src/frontend.c index 27425a3..a985aa2 100644 --- a/src/frontend.c +++ b/src/frontend.c @@ -95,6 +95,12 @@ FILE *logfile = NULL; #define VERSION "unknown" #endif +// colors of collaborator cursors, drawn in order based on uid +// see `setup_colors` and `draw_collab_cursors` +const short cursor_colors[] = { + COLOR_CYAN, COLOR_YELLOW, COLOR_MAGENTA, COLOR_GREEN, COLOR_RED, COLOR_BLUE, +}; + // cli pieces const char *program_name = "collascii"; const char *program_version = VERSION; @@ -474,18 +480,22 @@ int main(int argc, char *argv[]) { finish(0); } +/* Initialize ncurses color configurations + * + * Before using color features elsewhere make sure to check has_colors() first. + */ void setup_colors() { start_color(); - - // TODO: Use #define to get colors for standard uses - // Assign color codes - init_pair(1, COLOR_WHITE, COLOR_BLUE); - init_pair(2, COLOR_WHITE, COLOR_GREEN); - init_pair(3, COLOR_WHITE, COLOR_RED); - init_pair(4, COLOR_WHITE, COLOR_CYAN); - init_pair(5, COLOR_WHITE, COLOR_MAGENTA); - init_pair(6, COLOR_WHITE, COLOR_YELLOW); - init_pair(7, COLOR_BLACK, COLOR_WHITE); + // Use the terminal's prefered color scheme if it supports it + // -1 can be used to refer to the prefered background/foreground + use_default_colors(); + + // initialize cursor colors - prefered foreground with colored background + // start at index 1 (0 is already default colors) + const int start = 1; + for (int i = 0; i < sizeof(cursor_colors) / sizeof(short); i++) { + init_pair(i + start, -1, cursor_colors[i]); + } } /* Update canvas with character at cursor current position. @@ -535,6 +545,8 @@ void redraw_canvas_win() { } } +/* Draw all visible collaborator cursors on the canvas. + */ void draw_collab_cursors(State *state) { collab_t *c = NULL; const int min_x = view->x; @@ -546,11 +558,19 @@ void draw_collab_cursors(State *state) { if (c != NULL && (c->x >= min_x && c->y <= max_x) && (c->y >= min_y && c->y <= max_y)) { logd("Drawing collab %i\n", c->uid); - const int color = has_colors() ? (i % 6) + 1 : 0; + + // pick color based on uid, which starts at 1 + // if color isn't supported, use normal terminal colors and reverse video + const int uid_start = 0; + const int color_start = 1; + const size_t colors_len = sizeof(cursor_colors) / sizeof(short); + const int color = + has_colors() ? ((c->uid - uid_start) % colors_len) + color_start : 0; + const int attr = has_colors() ? 0 : A_REVERSE; // TODO: blink cursor with A_BLINK attribute (needs to pause between // updates/only move on changes?) - mvwchgat(canvas_win, c->y - view->y + 1, c->x - view->x + 1, 1, 0, color, - NULL); + mvwchgat(canvas_win, c->y - view->y + 1, c->x - view->x + 1, 1, attr, + color, NULL); } } } From 4ec3fc665934898731bbfbdf7218d8f9933b1c5a Mon Sep 17 00:00:00 2001 From: Evan New-Schmidt Date: Mon, 27 May 2019 16:36:05 -0700 Subject: [PATCH 11/15] Pass collab_list directly to draw function --- src/frontend.c | 10 +++++----- src/frontend.h | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/frontend.c b/src/frontend.c index a985aa2..0718adc 100644 --- a/src/frontend.c +++ b/src/frontend.c @@ -454,11 +454,11 @@ int main(int argc, char *argv[]) { print_msg_win("Server Disconnect!"); }; redraw_canvas_win(); // TODO: draw single char update - draw_collab_cursors(state); + draw_collab_cursors(state->collab_list); refresh_screen(); } else if (fd == 0) { // process keyboard activity master_handler(state, canvas_win, status_interface->info_win); - draw_collab_cursors(state); + draw_collab_cursors(state->collab_list); refresh_screen(); net_update_pos(state); } @@ -547,14 +547,14 @@ void redraw_canvas_win() { /* Draw all visible collaborator cursors on the canvas. */ -void draw_collab_cursors(State *state) { +void draw_collab_cursors(collab_list_t *collab_list) { collab_t *c = NULL; const int min_x = view->x; const int min_y = view->y; const int max_x = min(view_max_x, view->canvas->num_cols - view->x); const int max_y = min(view_max_y, view->canvas->num_rows - view->y); - for (int i = 0; i < state->collab_list->len; i++) { - c = state->collab_list->list[i]; + for (int i = 0; i < collab_list->len; i++) { + c = collab_list->list[i]; if (c != NULL && (c->x >= min_x && c->y <= max_x) && (c->y >= min_y && c->y <= max_y)) { logd("Drawing collab %i\n", c->uid); diff --git a/src/frontend.h b/src/frontend.h index e0962cc..bbe0610 100644 --- a/src/frontend.h +++ b/src/frontend.h @@ -35,5 +35,5 @@ void highlight_mode_text(int x, int num_ch); int print_mode_win(char *format, ...); void update_info_win(const Mode_ID current_mode, const int x, const int y, const int w, const int h); -void draw_collab_cursors(State *state); +void draw_collab_cursors(collab_list_t *collab_list); #endif From 978b726be1c4464c130d9ec085af5a131eec1d26 Mon Sep 17 00:00:00 2001 From: Evan New-Schmidt Date: Mon, 27 May 2019 16:37:56 -0700 Subject: [PATCH 12/15] Update draw collab docstring --- src/frontend.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/frontend.c b/src/frontend.c index 0718adc..ccbf415 100644 --- a/src/frontend.c +++ b/src/frontend.c @@ -546,6 +546,8 @@ void redraw_canvas_win() { } /* Draw all visible collaborator cursors on the canvas. + * + * Collaborator cursor colors are from `cursors_colors` and set by uid. */ void draw_collab_cursors(collab_list_t *collab_list) { collab_t *c = NULL; @@ -555,6 +557,7 @@ void draw_collab_cursors(collab_list_t *collab_list) { const int max_y = min(view_max_y, view->canvas->num_rows - view->y); for (int i = 0; i < collab_list->len; i++) { c = collab_list->list[i]; + // only draw cursors that exist and are visible on the screen if (c != NULL && (c->x >= min_x && c->y <= max_x) && (c->y >= min_y && c->y <= max_y)) { logd("Drawing collab %i\n", c->uid); From 574f392615b9c377ef345158a68ee7b9485c60d7 Mon Sep 17 00:00:00 2001 From: Evan New-Schmidt Date: Mon, 27 May 2019 16:38:22 -0700 Subject: [PATCH 13/15] Fix bounds-checking bug --- src/frontend.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend.c b/src/frontend.c index ccbf415..7162973 100644 --- a/src/frontend.c +++ b/src/frontend.c @@ -558,7 +558,7 @@ void draw_collab_cursors(collab_list_t *collab_list) { for (int i = 0; i < collab_list->len; i++) { c = collab_list->list[i]; // only draw cursors that exist and are visible on the screen - if (c != NULL && (c->x >= min_x && c->y <= max_x) && + if (c != NULL && (c->x >= min_x && c->x <= max_x) && (c->y >= min_y && c->y <= max_y)) { logd("Drawing collab %i\n", c->uid); From b22248e84f0f655fc656783c74287564ef2195f8 Mon Sep 17 00:00:00 2001 From: Evan New-Schmidt Date: Mon, 27 May 2019 17:03:04 -0700 Subject: [PATCH 14/15] Fix another bounds-checking bug --- src/frontend.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/frontend.c b/src/frontend.c index 7162973..03c05eb 100644 --- a/src/frontend.c +++ b/src/frontend.c @@ -551,10 +551,11 @@ void redraw_canvas_win() { */ void draw_collab_cursors(collab_list_t *collab_list) { collab_t *c = NULL; + // calculate visible bounds (in canvas coordinates) const int min_x = view->x; const int min_y = view->y; - const int max_x = min(view_max_x, view->canvas->num_cols - view->x); - const int max_y = min(view_max_y, view->canvas->num_rows - view->y); + const int max_x = min(view->canvas->num_cols, view->x + view_max_x) - 1; + const int max_y = min(view->canvas->num_rows, view->y + view_max_y) - 1; for (int i = 0; i < collab_list->len; i++) { c = collab_list->list[i]; // only draw cursors that exist and are visible on the screen From 81ac50087eb46b551f4b63a496344431815216f5 Mon Sep 17 00:00:00 2001 From: Evan Lloyd New-Schmidt Date: Sun, 9 Aug 2020 16:59:50 -0700 Subject: [PATCH 15/15] Use getline to prevent incomplete messages being read --- src/server.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/server.c b/src/server.c index 0516d2c..ef2544a 100644 --- a/src/server.c +++ b/src/server.c @@ -171,7 +171,8 @@ void print_client_addr(struct sockaddr_in addr) { /* Handle all communication with the client */ void *handle_client(void *arg) { char buff_out[BUFFER_SZ]; - char buff_in[BUFFER_SZ / 2]; + char *buff_in; + size_t buff_in_size; int rlen; cli_count++; @@ -181,20 +182,21 @@ void *handle_client(void *arg) { print_client_addr(cli->addr); printf(" referenced by %d\n", cli->uid); + FILE *input_stream = fdopen(cli->connfd, "r"); + // protocol negotiation - if ((rlen = read(cli->connfd, buff_in, sizeof(buff_in) - 1)) < 0) { + if ((rlen = getline(&buff_in, &buff_in_size, input_stream)) < 0) { printf("version negotation: error reading from socket\n"); goto CLIENT_CLOSE; } - buff_in[rlen] = '\0'; strip_newline(buff_in); - char* cmd = strtok(buff_in, " "); + char *cmd = strtok(buff_in, " "); if (cmd == NULL || cmd[0] != 'v') { printf("version negotiation: client command not 'v'\n"); goto CLIENT_CLOSE; } - char* client_version = strtok(NULL, " "); + char *client_version = strtok(NULL, " "); if (client_version == NULL) { printf("version negotiation: unable to parse client version\n"); send_message_self("can't parse version\n", cli); @@ -221,8 +223,7 @@ void *handle_client(void *arg) { printf("sent serialized canvas\n"); /* Receive input from client */ - while ((rlen = read(cli->connfd, buff_in, sizeof(buff_in) - 1)) > 0) { - buff_in[rlen] = '\0'; + while ((rlen = getline(&buff_in, &buff_in_size, input_stream)) > 0) { buff_out[0] = '\0'; strip_newline(buff_in); @@ -277,7 +278,7 @@ void *handle_client(void *arg) { } free(msg); } - CLIENT_CLOSE: +CLIENT_CLOSE: /* Close connection */ close(cli->connfd);