diff options
| -rw-r--r-- | .gitignore | 3 | ||||
| -rwxr-xr-x | build/chatty | bin | 139016 -> 257376 bytes | |||
| -rwxr-xr-x | build/server | bin | 44688 -> 138056 bytes | |||
| -rwxr-xr-x | source/archived/v1/.gitignore | 6 | ||||
| -rwxr-xr-x | source/build.sh | 40 | ||||
| -rw-r--r-- | source/chatty.c | 528 | ||||
| -rw-r--r-- | source/chatty.h | 8 | ||||
| -rw-r--r-- | source/server.c | 197 |
8 files changed, 413 insertions, 369 deletions
@@ -1,2 +1,3 @@ -.gitignore chatty.log +.chatty_clients +.chatty_id diff --git a/build/chatty b/build/chatty Binary files differindex 06f4379..b11f571 100755 --- a/build/chatty +++ b/build/chatty diff --git a/build/server b/build/server Binary files differindex 90d2d33..98ed054 100755 --- a/build/server +++ b/build/server diff --git a/source/archived/v1/.gitignore b/source/archived/v1/.gitignore new file mode 100755 index 0000000..6ecdb26 --- /dev/null +++ b/source/archived/v1/.gitignore @@ -0,0 +1,6 @@ +chatty +server +recv +send +client +tags diff --git a/source/build.sh b/source/build.sh index 317e9a4..e8c89bd 100755 --- a/source/build.sh +++ b/source/build.sh @@ -4,11 +4,47 @@ ScriptDir="$(dirname "$(readlink -f "$0")")" cd "$ScriptDir" BuildDir="$ScriptDir"/../build +CompilerFlags=" +-DIMPORT_ID=1 +" + +WarningFlags=" +-Wall +-Wextra +-Wno-unused-variable +-Wno-unused-parameter +-Wno-unused-but-set-variable +-Wno-maybe-uninitialized +-Wno-sign-compare +" + +Mode="$1" +if [ "$Mode" != "release" ] +then + Mode="debug" +fi +printf '[Mode %s]\n' "$Mode" + +if [ "$Mode" = "debug" ] +then + CompilerFlags="$CompilerFlags + -DDEBUG=1 + -ggdb -g3 + " +elif [ "$Mode" = "release" ] +then + CompilerFlags="$CompilerFlags + -O3 + " +fi + mkdir -p "$BuildDir" + printf 'chatty.c\n' -gcc -DDEBUG -ggdb -Wall -pedantic -std=c11 -I external -o "$BuildDir"/chatty chatty.c +gcc $CompilerFlags $WarningFlags -I external -o "$BuildDir"/chatty chatty.c + printf 'server.c\n' -gcc -DDEBUG -ggdb -Wall -pedantic -std=c99 -o "$BuildDir"/server server.c +gcc $CompilerFlags $WarningFlags -o "$BuildDir"/server server.c # printf 'archived/input_box.c\n' # gcc -DDEBUG -ggdb -Wall -pedantic -std=c11 -I external -I . -o "$BuildDir"/input_box archived/input_box.c diff --git a/source/chatty.c b/source/chatty.c index 32a4431..01d450b 100644 --- a/source/chatty.c +++ b/source/chatty.c @@ -15,7 +15,7 @@ #define TIMEOUT_RECONNECT 1 #define MAX_INPUT_LEN 512 // Filepath where user ID is stored -#define ID_FILE "_id" +#define ID_FILE ".chatty_id" // Filepath where logged #define LOGFILE "chatty.log" // enable logging @@ -26,10 +26,10 @@ #ifndef Assert #ifdef DEBUG #define Assert(expr) if (!(expr)) \ - { \ - tb_shutdown(); \ - raise(SIGTRAP); \ - } +{ \ +tb_shutdown(); \ +raise(SIGTRAP); \ +} #else #define Assert(expr) ; #endif // DEBUG @@ -49,10 +49,10 @@ #include "ui.h" enum { FDS_BI = 0, // for one-way communication with the server (eg. TextMessage) - FDS_UNI, // For two-way communication with the server (eg. IDMessage) - FDS_TTY, - FDS_RESIZE, - FDS_MAX }; + FDS_UNI, // For two-way communication with the server (eg. IDMessage) + FDS_TTY, + FDS_RESIZE, + FDS_MAX }; typedef struct { u8 Author[AUTHOR_LEN]; @@ -96,7 +96,7 @@ get_user_by_id(Arena* clientsArena, ID id) { // User is not in the clientsArena if (id == user.ID) return &user; - + User* clients = clientsArena->addr; for (u64 i = 0; i < (clientsArena->pos / sizeof(*clients)); i++) { @@ -117,16 +117,16 @@ add_user_info(Arena* clientsArena, s32 fd, u64 id) IDMessage message = {id}; s32 nsend = sendAnyMessage(fd, header, &message); Assert(nsend != -1); - + // Wait for response IntroductionMessage introduction_message; recvAnyMessageType(fd, &header, &introduction_message, HEADER_TYPE_INTRODUCTION); - + // Add the information User* client = ArenaPush(clientsArena, sizeof(*client)); memcpy(client->Author, introduction_message.author, AUTHOR_LEN); client->ID = id; - + LoggingF("Got " USER_FMT "\n", USER_ARG((*client))); return client; } @@ -137,10 +137,10 @@ get_connection(struct sockaddr_in* address) { s32 fd = socket(AF_INET, SOCK_STREAM, 0); if (fd == -1) return -1; - + s32 err = connect(fd, (struct sockaddr*)address, sizeof(*address)); if (err) return -1; - + return fd; } @@ -157,14 +157,14 @@ authenticate(User* user, s32 fd) IDMessage message = {user->ID}; s32 nsend = sendAnyMessage(fd, header, &message); Assert(nsend != -1); - + ErrorMessage error_message; s32 nrecv = recvAnyMessageType(fd, &header, &error_message, HEADER_TYPE_ERROR); Assert(nrecv != -1); // TODO: handle not found if (nrecv == 0) return 0; - + if (error_message.type == ERROR_TYPE_SUCCESS) return 1; else @@ -178,7 +178,7 @@ authenticate(User* user, s32 fd) memcpy(message.author, user->Author, AUTHOR_LEN); s32 nsend = sendAnyMessage(fd, header, &message); Assert(nsend != -1); - + IDMessage id_message; s32 nrecv = recvAnyMessageType(fd, &header, &id_message, HEADER_TYPE_ID); Assert(nrecv != -1); @@ -205,7 +205,7 @@ thread_reconnect(void* fds_ptr) { // timeout nanosleep(&t, &t); - + bifd = get_connection(&address); if (bifd == -1) { @@ -219,27 +219,27 @@ thread_reconnect(void* fds_ptr) close(bifd); continue; } - + LoggingF("Reconnect succeeded (%d, %d), authenticating\n", unifd, bifd); - + if (authenticate(&user, bifd) && authenticate(&user, unifd)) { break; } - + close(bifd); close(unifd); - + LoggingF("Failed, retrying...\n"); } - + fds[FDS_BI].fd = bifd; fds[FDS_UNI].fd = unifd; - + // Redraw screen raise(SIGWINCH); - + return 0; } @@ -247,31 +247,31 @@ command_output run_command_get_output(char *Command, char *Argv[], u8 *OutputBuffer, int Len) { command_output Result = {0}; - + int CommandPipe[2]; int Error = pipe(CommandPipe); Assert(Error != -1); - + int Pid = fork(); Assert(Pid != -1); - + // Run command in child if (!Pid) { dup2(CommandPipe[1], STDOUT_FILENO); //redirect stdout to Pipe close(CommandPipe[0]); close(CommandPipe[1]); - + int fd = open("/dev/null", O_WRONLY); dup2(fd, STDERR_FILENO); - + execvp(Command, Argv); } - + // Wait for child int statval; waitpid(Pid, &statval, 0); - + if(WIFEXITED(statval)) { int ExitCode = WEXITSTATUS(statval); @@ -285,12 +285,12 @@ run_command_get_output(char *Command, char *Argv[], u8 *OutputBuffer, int Len) Result.Error = 1; return Result; } - + close(CommandPipe[1]); - + Result.NumRead = read(CommandPipe[0], OutputBuffer, Len); Assert(Result.NumRead != -1); - + return Result; } @@ -308,49 +308,49 @@ DisplayChat(Arena* ScratchArena, }; u32 FreeHeight = global.height - TextBox.H; TextBox.Y = FreeHeight; - + #define MIN_TEXT_WIDTH_FOR_WRAPPING 20 s32 MinBoxWidth = TEXTBOX_MIN_WIDTH; s32 InputBoxTextWidth = TextBox.W - MinBoxWidth + 2; bool ShouldIncreaseSize = ( - (s32)InputLen >= InputBoxTextWidth && - InputBoxTextWidth > MIN_TEXT_WIDTH_FOR_WRAPPING - ); + (s32)InputLen >= InputBoxTextWidth && + InputBoxTextWidth > MIN_TEXT_WIDTH_FOR_WRAPPING + ); if (ShouldIncreaseSize) { TextBox.H++; } #undef MIN_TEXT_WIDTH_FOR_WRAPPING - + rect TextR = { TextBox.X + 2, TextBox.Y + 1, TextBox.W - 2*TEXTBOX_PADDING_X - 2*TEXTBOX_BORDER_WIDTH, TextBox.H - 2*TEXTBOX_BORDER_WIDTH }; - + if (global.height < TextBox.H || global.width < TextBox.W) { tb_hide_cursor(); return; } - + bytebuf_puts(&global.out, global.caps[TB_CAP_SHOW_CURSOR]); global.cursor_x = TextR.X; global.cursor_y = TextR.Y; DrawBox(TextBox, 0); DrawTextBox(TextR, Input, InputLen); - + // Print vertical bar s32 VerticalBarOffset = TIMESTAMP_LEN + AUTHOR_LEN + 2; for (u32 Y = 0; Y < FreeHeight; Y++) tb_print(VerticalBarOffset, Y, 0, 0, "│"); - + // show error popup if server disconnected if (fds[FDS_UNI].fd == -1 || fds[FDS_BI].fd == -1) { popup(TB_RED, TB_BLACK, (u8*)"Server disconnected."); } - + // Print messages in msgsArena, if there are too many to display, start printing from an offset. // Looks like this: // 03:24:29 [1234567890ab] hello homes how are @@ -358,53 +358,53 @@ DisplayChat(Arena* ScratchArena, // 03:24:33 [TlasT] │ I am fine // 03:24:33 [Fin] │ I am too { - + // If there is not enough space to draw, do not draw if (FreeHeight <= 0) return; - + // Used to go to the next message in MessagesArena by incrementing with the messages' size. u8* MessageAddress = MessagesArena->addr; Assert(MessageAddress != 0); - + // Skip messages if there is not enough space to display them all u32 MessagesOffset = (MessagesNum > FreeHeight) ? MessagesNum - FreeHeight : 0; for (u32 MessageIndex = 0; MessageIndex < MessagesOffset; MessageIndex++) { HeaderMessage* header = (HeaderMessage*)MessageAddress; MessageAddress += sizeof(*header); - + switch (header->type) { - case HEADER_TYPE_TEXT: - { - TextMessage* message = (TextMessage*)MessageAddress; - MessageAddress += TEXTMESSAGE_SIZE; - MessageAddress += message->len * sizeof(*message->text); - break; - } - case HEADER_TYPE_PRESENCE: + case HEADER_TYPE_TEXT: + { + TextMessage* message = (TextMessage*)MessageAddress; + MessageAddress += TEXTMESSAGE_SIZE; + MessageAddress += message->len * sizeof(*message->text); + break; + } + case HEADER_TYPE_PRESENCE: MessageAddress += sizeof(PresenceMessage); break; - case HEADER_TYPE_HISTORY: + case HEADER_TYPE_HISTORY: MessageAddress += sizeof(HistoryMessage); break; - default: + default: // unhandled message type Assert(0); } } - + u32 MessageY = 0; - + for (u32 i = MessagesOffset; - i < MessagesNum; - i++) + i < MessagesNum; + i++) { if (MessageY >= FreeHeight) break; - + HeaderMessage* header = (HeaderMessage*)MessageAddress; MessageAddress += sizeof(*header); - + User* client = get_user_by_id(ClientsArena, header->id); if (!client) { @@ -412,84 +412,84 @@ DisplayChat(Arena* ScratchArena, client = add_user_info(ClientsArena, fds[FDS_BI].fd, header->id); } Assert(client); - + switch (header->type) { - case HEADER_TYPE_TEXT: - { - TextMessage* message = (TextMessage*)MessageAddress; - - - // Color own messages - u32 fg = 0; - if (user.ID == header->id) - { - fg = TB_CYAN; - } - else - { - fg = TB_MAGENTA; - } - - // prefix is of format "HH:MM:SS [<author>] ", create it - u8 timestamp[TIMESTAMP_LEN]; - formatTimestamp(timestamp, message->timestamp); - - tb_printf(0, MessageY, TB_WHITE, 0, "%s", timestamp); - tb_printf(TIMESTAMP_LEN, MessageY, fg, 0, "[%s]", client->Author); - - // Only display when there is enough space - if (global.width > VerticalBarOffset + 2) + case HEADER_TYPE_TEXT: { - raw_result RawText = markdown_to_raw(ScratchArena, (wchar_t*)&message->text, message->len); - markdown_formatoptions MDFormat = preprocess_markdown(ScratchArena, - (wchar_t*)&message->text, - message->len); - - u32 timesWrapped = tb_print_wrapped_with_markdown(VerticalBarOffset + 2, MessageY, fg, 0, - RawText.Text, RawText.Len, - global.width, global.height, MDFormat); - - // Free the memory - ScratchArena->pos = 0; - - MessageY += timesWrapped; - } - else + TextMessage* message = (TextMessage*)MessageAddress; + + + // Color own messages + u32 fg = 0; + if (user.ID == header->id) + { + fg = TB_CYAN; + } + else + { + fg = TB_MAGENTA; + } + + // prefix is of format "HH:MM:SS [<author>] ", create it + u8 timestamp[TIMESTAMP_LEN]; + formatTimestamp(timestamp, message->timestamp); + + tb_printf(0, MessageY, TB_WHITE, 0, "%s", timestamp); + tb_printf(TIMESTAMP_LEN, MessageY, fg, 0, "[%s]", client->Author); + + // Only display when there is enough space + if (global.width > VerticalBarOffset + 2) + { + raw_result RawText = markdown_to_raw(ScratchArena, (wchar_t*)&message->text, message->len); + markdown_formatoptions MDFormat = preprocess_markdown(ScratchArena, + (wchar_t*)&message->text, + message->len); + + u32 timesWrapped = tb_print_wrapped_with_markdown(VerticalBarOffset + 2, MessageY, fg, 0, + RawText.Text, RawText.Len, + global.width, global.height, MDFormat); + + // Free the memory + ScratchArena->pos = 0; + + MessageY += timesWrapped; + } + else + { + // We still displayed the timestamp so we need to increment the Y. + MessageY++; + } + + u32 message_size = TEXTMESSAGE_SIZE + message->len * sizeof(*message->text); + MessageAddress += message_size; + } break; + case HEADER_TYPE_PRESENCE: { - // We still displayed the timestamp so we need to increment the Y. + PresenceMessage* message = (PresenceMessage*)MessageAddress; + tb_printf(TIMESTAMP_LEN, MessageY, TB_MAGENTA, 0, "[%s]", client->Author); + + // Wrap Text in '*' + u8 *Text = presenceTypeString(message->type); + u32 Len = 0; + while(Text[Len]) Len++; + u32 FormattedText[Len+2]; + FormattedText[0] = '*'; + FormattedText[Len+1] = '*'; + for (u32 i = 1; i < Len + 1; i++) FormattedText[i] = Text[i-1]; + + tb_print_markdown(VerticalBarOffset + 2, MessageY, 0, 0, FormattedText, Len + 2); + MessageY++; - } - - u32 message_size = TEXTMESSAGE_SIZE + message->len * sizeof(*message->text); - MessageAddress += message_size; - } break; - case HEADER_TYPE_PRESENCE: - { - PresenceMessage* message = (PresenceMessage*)MessageAddress; - tb_printf(TIMESTAMP_LEN, MessageY, TB_MAGENTA, 0, "[%s]", client->Author); - - // Wrap Text in '*' - u8 *Text = presenceTypeString(message->type); - u32 Len = 0; - while(Text[Len]) Len++; - u32 FormattedText[Len+2]; - FormattedText[0] = '*'; - FormattedText[Len+1] = '*'; - for (u32 i = 1; i < Len + 1; i++) FormattedText[i] = Text[i-1]; - - tb_print_markdown(VerticalBarOffset + 2, MessageY, 0, 0, FormattedText, Len + 2); - - MessageY++; - MessageAddress += sizeof(*message); - } break; - case HEADER_TYPE_HISTORY: - { - HistoryMessage* message = (HistoryMessage*)MessageAddress; - MessageAddress += sizeof(*message); - // TODO: implement - } break; - default: + MessageAddress += sizeof(*message); + } break; + case HEADER_TYPE_HISTORY: + { + HistoryMessage* message = (HistoryMessage*)MessageAddress; + MessageAddress += sizeof(*message); + // TODO: implement + } break; + default: tb_printf(0, MessageY, 0, 0, "%s", headerTypeString(header->type)); MessageY++; break; @@ -507,40 +507,40 @@ main(int argc, char** argv) fprintf(stderr, "usage: chatty <username>\n"); return 1; } - + u32 arg_len = strlen(argv[1]); Assert(arg_len <= AUTHOR_LEN - 1); memcpy(user.Author, argv[1], arg_len); user.Author[arg_len] = '\0'; - + s32 err = 0; // error code for functions - + u32 MessagesNum = 0; // Number of messages in msgsArena s32 nrecv = 0; // number of bytes received - + wchar_t Input[MAX_INPUT_LEN] = {0}; // input buffer u32 InputIndex = 0; // number of characters in input - + Arena ScratchArena; Arena MessagesArena; Arena ClientsArena; ArenaAlloc(&MessagesArena, Megabytes(64)); // Messages received & sent ArenaAlloc(&ClientsArena, Megabytes(1)); // Arena for storing clients ArenaAlloc(&ScratchArena, Megabytes(1)); // Arena for storing clients - + struct tb_event ev; // event fork keypress & resize u8 quit = 0; // boolean to indicate if we want to quit the main loop u8* quitmsg = 0; // this string will be printed before returning from main - + pthread_t thr_rec; // thread for reconnecting to server when disconnected - + #ifdef LOGGING LogFD = open(LOGFILE, O_RDWR | O_CREAT | O_TRUNC, 0600); Assert(LogFD != -1); #else logfd = 2; // stderr #endif - + // poopoo C cannot infer type struct pollfd fds[FDS_MAX] = { {-1, POLLIN, 0}, // FDS_BI @@ -548,18 +548,18 @@ main(int argc, char** argv) {-1, POLLIN, 0}, // FDS_TTY {-1, POLLIN, 0}, // FDS_RESIZE }; - + address = (struct sockaddr_in){ AF_INET, htons(PORT), {0}, {0}, }; - + #ifdef IMPORT_ID // File for storing the user's ID. u32 idfile = open(ID_FILE, O_RDWR | O_CREAT, 0600); - s32 nread = read(idfile, &user.id, sizeof(user.id)); + s32 nread = read(idfile, &user.ID, sizeof(user.ID)); Assert(nread != -1); #endif /* Authentication */ @@ -591,43 +591,43 @@ main(int argc, char** argv) fds[FDS_BI].fd = bifd; fds[FDS_UNI].fd = unifd; } - + #ifdef IMPORT_ID // Save id - write(idfile, &user.id, sizeof(user.id)); + write(idfile, &user.ID, sizeof(user.ID)); #endif - + LoggingF("Got ID: %lu\n", user.ID); - + // for wide character printing Assert(setlocale(LC_ALL, "")); - + // init tb_init(); tb_get_fds(&fds[FDS_TTY].fd, &fds[FDS_RESIZE].fd); - + DisplayChat(&ScratchArena, &MessagesArena, MessagesNum, &ClientsArena, fds, Input, InputIndex); tb_present(); - + // main loop while (!quit) { err = poll(fds, FDS_MAX, TIMEOUT_POLL); // ignore resize events and use them to redraw the screen Assert(err != -1 || errno == EINTR); - + tb_clear(); - + if (fds[FDS_UNI].revents & POLLIN) { // got data from server HeaderMessage header; nrecv = recv(fds[FDS_UNI].fd, &header, sizeof(header), 0); Assert(nrecv != -1); - + // Server disconnects if (nrecv == 0) { @@ -646,39 +646,39 @@ main(int argc, char** argv) LoggingF("Header received does not match version\n"); continue; } - + void* addr = ArenaPush(&MessagesArena, sizeof(header)); memcpy(addr, &header, sizeof(header)); - + // Messages handled from server switch (header.type) { - case HEADER_TYPE_TEXT: + case HEADER_TYPE_TEXT: recvTextMessage(&MessagesArena, fds[FDS_UNI].fd); MessagesNum++; break; - case HEADER_TYPE_PRESENCE:; + case HEADER_TYPE_PRESENCE:; PresenceMessage* message = ArenaPush(&MessagesArena, sizeof(*message)); nrecv = recv(fds[FDS_UNI].fd, message, sizeof(*message), 0); Assert(nrecv != -1); Assert(nrecv == sizeof(*message)); MessagesNum++; break; - default: + default: LoggingF("Got unhandled message: %s\n", headerTypeString(header.type)); break; } } } - + if (fds[FDS_TTY].revents & POLLIN) { // got a key event tb_poll_event(&ev); - + switch (ev.key) { - case TB_KEY_CTRL_W: + case TB_KEY_CTRL_W: // delete consecutive whitespace while (InputIndex) { @@ -700,111 +700,111 @@ main(int argc, char** argv) InputIndex--; } break; - case TB_KEY_CTRL_Z: - { - pid_t pid = getpid(); - tb_shutdown(); - kill(pid, SIGSTOP); - tb_init(); - } break; - case TB_KEY_CTRL_Y: // Paste clipboard contents to input - { - u32 OutputBufferLen = MAX_INPUT_LEN - InputIndex; - if (OutputBufferLen <= 0) break; - - u8 OutputBuffer[OutputBufferLen]; - - char *PathName = "xclip"; - char *Argv[] = {PathName, "-o", "-sel", "c", 0}; - - command_output Output = run_command_get_output(PathName, Argv, OutputBuffer, OutputBufferLen - 1); - if (Output.Error) break; - - // Remove trailing whitespace - int BufferIndex = Output.NumRead - 1; - while (BufferIndex > 0 && - (OutputBuffer[BufferIndex] == '\n' || - OutputBuffer[BufferIndex] == '\t')) + case TB_KEY_CTRL_Z: { - OutputBuffer[BufferIndex] = 0; - BufferIndex--; - } - - // Append to output - for (s32 BufferIndex = 0; BufferIndex < Output.NumRead; BufferIndex++) + pid_t pid = getpid(); + tb_shutdown(); + kill(pid, SIGSTOP); + tb_init(); + } break; + case TB_KEY_CTRL_Y: // Paste clipboard contents to input { - // convert u8 to u32 - u32 ch = OutputBuffer[BufferIndex]; - Input[InputIndex] = ch; - InputIndex++; - } - - } break; - case TB_KEY_CTRL_I: - { - for (u32 i = 0; - i < TAB_WIDTH && InputIndex < MAX_INPUT_LEN - 1; - i++) + u32 OutputBufferLen = MAX_INPUT_LEN - InputIndex; + if (OutputBufferLen <= 0) break; + + u8 OutputBuffer[OutputBufferLen]; + + char *PathName = "xclip"; + char *Argv[] = {PathName, "-o", "-sel", "c", 0}; + + command_output Output = run_command_get_output(PathName, Argv, OutputBuffer, OutputBufferLen - 1); + if (Output.Error) break; + + // Remove trailing whitespace + int BufferIndex = Output.NumRead - 1; + while (BufferIndex > 0 && + (OutputBuffer[BufferIndex] == '\n' || + OutputBuffer[BufferIndex] == '\t')) + { + OutputBuffer[BufferIndex] = 0; + BufferIndex--; + } + + // Append to output + for (s32 BufferIndex = 0; BufferIndex < Output.NumRead; BufferIndex++) + { + // convert u8 to u32 + u32 ch = OutputBuffer[BufferIndex]; + Input[InputIndex] = ch; + InputIndex++; + } + + } break; + case TB_KEY_CTRL_I: { - Input[InputIndex] = L' '; - InputIndex++; - } - } break; - case TB_KEY_BACKSPACE2: + for (u32 i = 0; + i < TAB_WIDTH && InputIndex < MAX_INPUT_LEN - 1; + i++) + { + Input[InputIndex] = L' '; + InputIndex++; + } + } break; + case TB_KEY_BACKSPACE2: if (InputIndex) InputIndex--; Input[InputIndex] = 0; break; - case TB_KEY_CTRL_D: - case TB_KEY_CTRL_C: + case TB_KEY_CTRL_D: + case TB_KEY_CTRL_C: quit = 1; break; - case TB_KEY_CTRL_M: // send message - { - raw_result RawText = markdown_to_raw(0, Input, InputIndex); - - if (RawText.Len == 0) - // do not send empty message - break; - if (fds[FDS_UNI].fd == -1) - // do not send message to disconnected server - break; - - // null terminate - Input[InputIndex] = 0; - InputIndex++; - - // Save header - HeaderMessage* header = ArenaPush(&MessagesArena, sizeof(*header)); - header->version = PROTOCOL_VERSION; - header->type = HEADER_TYPE_TEXT; - header->id = user.ID; - - // Save message - TextMessage* sendmsg = ArenaPush(&MessagesArena, TEXTMESSAGE_SIZE); - sendmsg->timestamp = time(0); - sendmsg->len = InputIndex; - - u32 text_size = InputIndex * sizeof(*Input); - ArenaPush(&MessagesArena, text_size); - memcpy(&sendmsg->text, Input, text_size); - - sendAnyMessage(fds[FDS_UNI].fd, *header, sendmsg); - - MessagesNum++; - // also clear input - } // fallthrough - case TB_KEY_CTRL_U: // clear input + case TB_KEY_CTRL_M: // send message + { + raw_result RawText = markdown_to_raw(0, Input, InputIndex); + + if (RawText.Len == 0) + // do not send empty message + break; + if (fds[FDS_UNI].fd == -1) + // do not send message to disconnected server + break; + + // null terminate + Input[InputIndex] = 0; + InputIndex++; + + // Save header + HeaderMessage* header = ArenaPush(&MessagesArena, sizeof(*header)); + header->version = PROTOCOL_VERSION; + header->type = HEADER_TYPE_TEXT; + header->id = user.ID; + + // Save message + TextMessage* sendmsg = ArenaPush(&MessagesArena, TEXTMESSAGE_SIZE); + sendmsg->timestamp = time(0); + sendmsg->len = InputIndex; + + u32 text_size = InputIndex * sizeof(*Input); + ArenaPush(&MessagesArena, text_size); + memcpy(&sendmsg->text, Input, text_size); + + sendAnyMessage(fds[FDS_UNI].fd, *header, sendmsg); + + MessagesNum++; + // also clear input + } // fallthrough + case TB_KEY_CTRL_U: // clear input bzero(Input, InputIndex * sizeof(*Input)); InputIndex = 0; break; - default: + default: if (ev.ch == 0) break; - + // TODO: show error if (InputIndex == MAX_INPUT_LEN - 1) // last byte reserved for \0 break; - + // append key to input buffer Input[InputIndex] = ev.ch; InputIndex++; @@ -812,23 +812,23 @@ main(int argc, char** argv) if (quit) break; } - + // These are used to redraw the screen from threads if (fds[FDS_RESIZE].revents & POLLIN) { // ignore tb_poll_event(&ev); } - + DisplayChat(&ScratchArena, &MessagesArena, MessagesNum, &ClientsArena, fds, Input, InputIndex); - + tb_present(); } - + tb_shutdown(); - + if (quitmsg != 0) printf("%s\n", quitmsg); - + return 0; } diff --git a/source/chatty.h b/source/chatty.h index f7525fa..04e2a85 100644 --- a/source/chatty.h +++ b/source/chatty.h @@ -58,19 +58,19 @@ LoggingF(char* format, ...) char buf[LOGMESSAGE_MAX]; va_list args; va_start(args, format); - + vsnprintf(buf, sizeof(buf), format, args); va_end(args); - + int n = 0; while (*(buf + n) != 0) n++; - + u64 t = time(0); u8 timestamp[LOG_LEN]; struct tm* ltime = localtime((time_t*)&t); strftime((char*)timestamp, LOG_LEN, LOG_FMT, ltime); write(LogFD, timestamp, LOG_LEN - 1); - + write(LogFD, buf, n); } diff --git a/source/server.c b/source/server.c index a6613d6..689537e 100644 --- a/source/server.c +++ b/source/server.c @@ -13,11 +13,11 @@ #ifndef Assert #ifdef DEBUG #define Assert(expr) if (!(expr)) { \ - raise(SIGTRAP); \ +raise(SIGTRAP); \ } #else #define Assert(expr) if (!(expr)) { \ - raise(SIGTRAP); \ +raise(SIGTRAP); \ } #endif // DEBUG #endif // Assert @@ -42,8 +42,9 @@ #define FDS_SIZE (fdsArena.pos / sizeof(struct pollfd)) #define CLIENTS_SIZE (clientsArena.pos / sizeof(Client)) +#define IMPORT_ID 1 // Where to save clients -#define CLIENTS_FILE "_clients" +#define CLIENTS_FILE ".chatty_clients" // Where to write logs #define LOGFILE "server.log" // Log to LOGFILE instead of stderr @@ -51,8 +52,8 @@ // enum for indexing the fds array enum { FDS_STDIN = 0, - FDS_SERVER, - FDS_CLIENTS }; + FDS_SERVER, + FDS_CLIENTS }; // Client information typedef struct { @@ -80,7 +81,7 @@ Client* getClientByID(Client* clients, u32 nclients, ID id) { if (!id) return 0; - + for (u32 i = 0; i < nclients; i++) { if (clients[i].id == id) @@ -95,7 +96,7 @@ Client* getClientByFD(Client* clients, u32 nclients, s32 fd) { if (fd == -1) return 0; - + for (u32 i = 0; i < nclients; i++) { if ((clients[i].unifd && clients[i].unifd->fd == fd) || @@ -111,7 +112,7 @@ printTextMessage(TextMessage* message, Client* client, u8 wide) { u8 timestamp[TIMESTAMP_LEN] = {0}; formatTimestamp(timestamp, message->timestamp); - + if (wide) { setlocale(LC_ALL, ""); @@ -134,7 +135,7 @@ sendToOthers(Client* clients, u32 nclients, Client* client, ClientFD type, Heade for (u32 i = 0; i < nclients - 1; i ++) { if (clients + i == client) continue; - + if (type == UNIFD) { if (clients[i].unifd && clients[i].unifd->fd != -1) @@ -150,7 +151,7 @@ sendToOthers(Client* clients, u32 nclients, Client* client, ClientFD type, Heade continue; } nsend = sendAnyMessage(fd, *header, anyMessage); - + assert(nsend != -1); LoggingF("sendToOthers "CLIENT_FMT"|%d<-%s %d bytes\n", CLIENT_ARG((clients[i])), fd, headerTypeString(header->type), nsend); } @@ -183,8 +184,8 @@ sendToAll(Client* clients, u32 nclients, ClientFD type, HeaderMessage* header, v assert(0); assert(nsend != -1); LoggingF("sendToAll|[%s]->"CLIENT_FMT" %d bytes\n", headerTypeString(header->type), - CLIENT_ARG(clients[i]), - nsend); + CLIENT_ARG(clients[i]), + nsend); } } @@ -213,7 +214,7 @@ void disconnectAndNotify(Client* clients, u32 nclients, Client* client) { disconnect(client); - + local_persist HeaderMessage header = HEADER_INIT(HEADER_TYPE_PRESENCE); header.id = client->id; PresenceMessage message = {.type = PRESENCE_TYPE_DISCONNECTED}; @@ -231,16 +232,16 @@ authenticate(Arena* clientsArena, s32 clients_file, struct pollfd* pollfd, Heade { s32 nrecv = 0; Client* client = 0; - + LoggingF("authenticate (%d)|" HEADER_FMT "\n", pollfd->fd, HEADER_ARG(header)); - + /* Scenario 1: Search for existing client */ if (header.type == HEADER_TYPE_ID) { IDMessage message; s32 nrecv = recv(pollfd->fd, &message, sizeof(message), 0); assert(nrecv == sizeof(message)); - + client = getClientByID((Client*)clientsArena->addr, nclients, message.id); if (!client) { @@ -257,15 +258,15 @@ authenticate(Arena* clientsArena, s32 clients_file, struct pollfd* pollfd, Heade ErrorMessage error_message = ERROR_INIT(ERROR_TYPE_SUCCESS); sendAnyMessage(pollfd->fd, header, &error_message); } - + if (!client->bifd) client->bifd = pollfd; else if (!client->unifd) client->unifd = pollfd; else assert(0); - - + + return client; } /* Scenario 2: Create a new client */ @@ -278,40 +279,40 @@ authenticate(Arena* clientsArena, s32 clients_file, struct pollfd* pollfd, Heade LoggingF("authenticate (%d)|err: %d/%lu bytes\n", pollfd->fd, nrecv, sizeof(message)); return 0; } - + // Copy metadata from IntroductionMessage client = ArenaPush(clientsArena, sizeof(*client)); memcpy(client->author, message.author, AUTHOR_LEN); client->id = nclients; - + if (!client->bifd) client->bifd = pollfd; else if (!client->unifd) client->unifd = pollfd; else assert(0); - + nclients++; - + #ifdef IMPORT_ID write(clients_file, client, sizeof(*client)); #endif LoggingF("authenticate (%d)|Added [%s](%lu)\n", pollfd->fd, client->author, client->id); - + // Send ID to new client HeaderMessage header = HEADER_INIT(HEADER_TYPE_ID); IDMessage id_message; id_message.id = client->id; - + s32 nsend = sendAnyMessage(pollfd->fd, header, &id_message); assert(nsend != -1); - + return client; } - + LoggingF("authenticate (%d)|Wrong header expected %s or %s\n", pollfd->fd, - headerTypeString(HEADER_TYPE_INTRODUCTION), - headerTypeString(HEADER_TYPE_ID)); + headerTypeString(HEADER_TYPE_INTRODUCTION), + headerTypeString(HEADER_TYPE_ID)); return 0; } @@ -319,19 +320,19 @@ int main(int argc, char** argv) { signal(SIGPIPE, SIG_IGN); - + LogFD = 2; // optional logging if (argc > 1) { if (*argv[1] == '-') if (argv[1][1] == 'l') - { - LogFD = open(LOGFILE, O_RDWR | O_CREAT | O_TRUNC, 0600); - assert(LogFD != -1); - } + { + LogFD = open(LOGFILE, O_RDWR | O_CREAT | O_TRUNC, 0600); + assert(LogFD != -1); + } } - + s32 serverfd; // Start listening on the socket { @@ -339,25 +340,25 @@ main(int argc, char** argv) u32 on = 1; serverfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); assert(serverfd > 2); - + err = setsockopt(serverfd, SOL_SOCKET, SO_REUSEADDR, (u8*)&on, sizeof(on)); assert(!err); - + const struct sockaddr_in address = { AF_INET, htons(PORT), {0}, {0}, }; - + err = bind(serverfd, (const struct sockaddr*)&address, sizeof(address)); assert(!err); - + err = listen(serverfd, MAX_CONNECTIONS); assert(!err); LoggingF("Listening on :%d\n", PORT); } - + Arena clientsArena; Arena fdsArena; Arena msgsArena; @@ -366,7 +367,7 @@ main(int argc, char** argv) ArenaAlloc(&msgsArena, Megabytes(128)); // storing received messages struct pollfd* fds = fdsArena.addr; Client* clients = clientsArena.addr; - + // Initializing fds struct pollfd* fdsAddr; struct pollfd newpollfd = {-1, POLLIN, 0}; // for copying with events already set @@ -379,21 +380,21 @@ main(int argc, char** argv) fdsAddr = ArenaPush(&fdsArena, sizeof(*fds)); memcpy(fdsAddr, &newpollfd, sizeof(*fds)); newpollfd.fd = -1; - + s32 clients_file; #ifdef IMPORT_ID clients_file = open(CLIENTS_FILE, O_RDWR | O_CREAT | O_APPEND, 0600); assert(clients_file != -1); struct stat statbuf; assert(fstat(clients_file, &statbuf) != -1); - + read(clients_file, clients, statbuf.st_size); if (statbuf.st_size > 0) { ArenaPush(&clientsArena, statbuf.st_size); LoggingF("Imported %lu client(s)\n", statbuf.st_size / sizeof(*clients)); nclients += statbuf.st_size / sizeof(*clients); - + // Reset pointers on imported clients for (u32 i = 0; i < nclients - 1; i++) { @@ -406,16 +407,16 @@ main(int argc, char** argv) #else clients_file = 0; #endif - + // Initialize the rest of the fds array for (u32 i = FDS_CLIENTS; i < MAX_CONNECTIONS; i++) fds[i] = newpollfd; - + while (1) { s32 err = poll(fds, FDS_SIZE, TIMEOUT); assert(err != -1); - + if (fds[FDS_STDIN].revents & POLLIN) { u8 c; // exit on ctrl-d @@ -426,7 +427,7 @@ main(int argc, char** argv) { // TODO: what if we are not aligned by 2 anymore? s32 clientfd = accept(serverfd, 0, 0); - + if (clientfd == -1) { LoggingF("Error while accepting connection (%d)\n", clientfd); @@ -434,7 +435,7 @@ main(int argc, char** argv) } else LoggingF("New connection(%d)\n", clientfd); - + // TODO: find empty space in arena (fragmentation) if (nclients + 1 == MAX_CONNECTIONS) { @@ -453,13 +454,13 @@ main(int argc, char** argv) LoggingF("Added pollfd(%d)\n", clientfd); } } - + for (u32 conn = FDS_CLIENTS; conn < FDS_SIZE; conn++) { if (!(fds[conn].revents & POLLIN)) continue; if (fds[conn].fd == -1) continue; LoggingF("Message(%d)\n", fds[conn].fd); - + // We received a message, try to parse the header HeaderMessage header; s32 nrecv = recv(fds[conn].fd, &header, sizeof(header), 0); @@ -467,7 +468,7 @@ main(int argc, char** argv) { LoggingF("Received error from fd: %d, errno: %d\n", fds[conn].fd, errno); }; - + Client* client; if (nrecv != sizeof(header)) { @@ -486,14 +487,14 @@ main(int argc, char** argv) continue; } LoggingF("Received(%d): " HEADER_FMT "\n", fds[conn].fd, HEADER_ARG(header)); - + // Authentication if (!header.id) { LoggingF("No client for connection(%d)\n", fds[conn].fd); - + client = authenticate(&clientsArena, clients_file, fds + conn, header); - + if (!client) { LoggingF("Could not initialize client (%d)\n", fds[conn].fd); @@ -511,71 +512,71 @@ main(int argc, char** argv) } continue; } - + client = getClientByID(clients, nclients, header.id); if (!client) { LoggingF("No client for id %d\n", fds[conn].fd); - + header.type = HEADER_TYPE_ERROR; ErrorMessage message = ERROR_INIT(ERROR_TYPE_NOTFOUND); - + sendAnyMessage(fds[conn].fd, header, &message); - + // Reject connection fds[conn].fd = -1; close(fds[conn].fd); continue; } - + switch (header.type) { - /* Send text message to all other clients */ - case HEADER_TYPE_TEXT: - { - TextMessage* text_message = recvTextMessage(&msgsArena, fds[conn].fd); - LoggingF("Received(%d): ", fds[conn].fd); - printTextMessage(text_message, client, 0); - - sendToOthers(clients, nclients, client, UNIFD, &header, text_message); - } break; - /* Send back client information */ - case HEADER_TYPE_ID: - { - IDMessage id_message; - s32 nrecv = recv(fds[conn].fd, &id_message, sizeof(id_message), 0); - assert(nrecv == sizeof(id_message)); - - client = getClientByID(clients, nclients, id_message.id); - if (!client) + /* Send text message to all other clients */ + case HEADER_TYPE_TEXT: { - header.type = HEADER_TYPE_ERROR; - ErrorMessage message = ERROR_INIT(ERROR_TYPE_NOTFOUND); - s32 nsend = sendAnyMessage(fds[conn].fd, header, &message); - assert(nsend != -1); - break; - } - - HeaderMessage header = HEADER_INIT(HEADER_TYPE_INTRODUCTION); - IntroductionMessage introduction_message; - header.id = client->id; - memcpy(introduction_message.author, client->author, AUTHOR_LEN); - - nrecv = sendAnyMessage(fds[conn].fd, header, &introduction_message); - assert(nrecv != -1); - } break; - default: + TextMessage* text_message = recvTextMessage(&msgsArena, fds[conn].fd); + LoggingF("Received(%d): ", fds[conn].fd); + printTextMessage(text_message, client, 0); + + sendToOthers(clients, nclients, client, UNIFD, &header, text_message); + } break; + /* Send back client information */ + case HEADER_TYPE_ID: + { + IDMessage id_message; + s32 nrecv = recv(fds[conn].fd, &id_message, sizeof(id_message), 0); + assert(nrecv == sizeof(id_message)); + + client = getClientByID(clients, nclients, id_message.id); + if (!client) + { + header.type = HEADER_TYPE_ERROR; + ErrorMessage message = ERROR_INIT(ERROR_TYPE_NOTFOUND); + s32 nsend = sendAnyMessage(fds[conn].fd, header, &message); + assert(nsend != -1); + break; + } + + HeaderMessage header = HEADER_INIT(HEADER_TYPE_INTRODUCTION); + IntroductionMessage introduction_message; + header.id = client->id; + memcpy(introduction_message.author, client->author, AUTHOR_LEN); + + nrecv = sendAnyMessage(fds[conn].fd, header, &introduction_message); + assert(nrecv != -1); + } break; + default: LoggingF("Unhandled '%s' from "CLIENT_FMT"(%d)\n", headerTypeString(header.type), - CLIENT_ARG((*client)), - fds[conn].fd); + CLIENT_ARG((*client)), + fds[conn].fd); disconnectAndNotify(client, nclients, client); continue; } } } - + #ifdef IMPORT_ID close(clients_file); #endif - + return 0; } |
