diff --git a/makefile b/makefile index 3da9d52..c8a14b2 100644 --- a/makefile +++ b/makefile @@ -2,7 +2,7 @@ CC = gcc CFLAGS = -Wall -g LDFLAGS = -lncurses -OBJS = main.o wsock.o +OBJS = main.o wsock.o ctx.o DEPS = main.c all: clii diff --git a/src/ctx.c b/src/ctx.c new file mode 100644 index 0000000..8991633 --- /dev/null +++ b/src/ctx.c @@ -0,0 +1,179 @@ +#include "ctx.h" + +#define DEF_CTX_LENS 8 +struct { + int self; + user_t* users; + channel_t* channels; + int users_length, + channels_length; +} _ctx; + +static void _users_init(user_t* users, int n) { + for(int i = 0; i < n; ++i) + users[i].id = -1; +} + +static void _channels_init(channel_t* channels, int n) { + for(int i = 0; i < n; ++i) + channels[i].name[0] = '\0'; +} + +void ctx_init() { + _ctx.self = -1; + _ctx.users = + malloc(sizeof(user_t) * DEF_CTX_LENS); + _ctx.channels = + malloc(sizeof(channel_t) * DEF_CTX_LENS); + _ctx.users_length + = _ctx.channels_length + = DEF_CTX_LENS; + _users_init(_ctx.users, _ctx.users_length); + _channels_init(_ctx.channels, _ctx.channels_length); +} + +void ctx_free() { + free(_ctx.users); + free(_ctx.channels); +} + +int parse_color(const char* color) { + return 0; +} + +uint64_t parse_flags(const char* flags) { + int length = strlen(flags); + uint64_t out = 0; + + for(int i = 0; i < length; --i) + if(flags[length - i - 1] == '1') + out |= 1ull << i; + return out; +} + +void parse_perms + (const char* perms, int n, int* out) +{ + const char* ptr = perms; + for(int i = 0; i < n; ++i) + out[i] = (int)strtol(ptr, (char**)&ptr, 10); +} + + +/** USER FUNCTIONS **/ +/**********************/ + +static user_t* _get_user(int id) { + for(int i = 0; i < _ctx.users_length; ++i) + if(_ctx.users[i].id == id) + return _ctx.users + i; + return NULL; +} + +static inline user_t* _get_empty_user() { + return _get_user(-1); +} + +void add_user(const user_t* user) { + if(user->id == -1) + return; + + user_t* to = _get_user(user->id); + if(to == NULL) + to = _get_empty_user(); + + if(to == NULL) { + _ctx.users = realloc(_ctx.users, + sizeof(user_t) * _ctx.users_length * 2); + _users_init( + _ctx.users + _ctx.users_length, + _ctx.users_length + ); + to = _ctx.users + _ctx.users_length; + _ctx.users_length *= 2; + } + + memcpy(to, user, sizeof(user_t)); +} + +const user_t* get_user(int id) { + if(id == -1) + return NULL; + + return _get_user(id); +} + +void rm_user(int id) { + if(id == -1) + return; + + user_t* user = _get_user(id); + if(user != NULL) + user->id = -1; +} + +void set_self(int id) { + _ctx.self = id; +} + +const user_t* get_self() { + return get_user(_ctx.self); +} + +int get_self_id() { + return _ctx.self; +} + + +/** CHANNEL FUNCTIONS **/ +/*************************/ + +static channel_t* _get_channel(const char* name) { + for(int i = 0; i < _ctx.channels_length; ++i) + if(strcmp(_ctx.channels[i].name, name) == 0) + return _ctx.channels + i; + return NULL; +} + +static channel_t* _get_empty_channel() { + return _get_channel(""); +} + +void add_channel(const channel_t* channel) { + if(channel->name[0] == '\0') + return; + + channel_t* to = _get_channel(channel->name); + if(to == NULL) + to = _get_empty_channel(); + + if(to == NULL) { + _ctx.channels = realloc(_ctx.channels, + sizeof(channel_t) * _ctx.channels_length * 2); + _channels_init( + _ctx.channels + _ctx.channels_length, + _ctx.channels_length + ); + to = _ctx.channels + _ctx.channels_length; + _ctx.channels_length *= 2; + } + + memcpy(to, channel, sizeof(channel_t)); +} + +const channel_t* get_channel(const char* name) { + if(name[0] == '\0') + return NULL; + + return _get_channel(name); +} + +void rm_channel(const char* name) { + if(name[0] == '\0') + return; + + channel_t* channel = _get_channel(name); + if(channel != NULL) + channel->name[0] = '\0'; +} + diff --git a/src/ctx.h b/src/ctx.h new file mode 100644 index 0000000..fcdadd6 --- /dev/null +++ b/src/ctx.h @@ -0,0 +1,46 @@ +#include +#include +#include +#include + +#define USER_PERMS 4 +#define USER_MOD 0 +#define USER_LOGS 1 +#define USER_NICK 2 +#define USER_CHAN 3 + +typedef struct { + int id; + char name[64]; + int perms[USER_PERMS]; + int color; +} user_t; + +#define CHAN_PARAMS 2 +#define CHAN_PWD 0 +#define CHAN_TMP 1 + +typedef struct { + char name[64]; + int params[CHAN_PARAMS]; +} channel_t; + +void ctx_init(); +void ctx_free(); + +int parse_color(const char*); +uint64_t parse_flags(const char*); +void parse_perms(const char*, int, int*); + +void add_user(const user_t*); +const user_t* get_user(int); +void rm_user(int); + +void set_self(int); +const user_t* get_self(); +int get_self_id(); + +void add_channel(const channel_t*); +const channel_t* get_channel(const char*); +void rm_channel(const char*); + diff --git a/src/main.c b/src/main.c index d07473b..e3b9c50 100644 --- a/src/main.c +++ b/src/main.c @@ -2,8 +2,10 @@ #include #include #include +#include #include "wsock.h" +#include "ctx.h" /** CONSTS & UTILITIES **/ /**************************/ @@ -19,19 +21,27 @@ const char USAGE[] = "on any web browser that is logged in."; const char* _home(const char*); +void _ping(); /** GLOBALS **/ /***************/ struct { + int running, exit_poll; char session[256]; + wsock_t* sock; + WINDOW *wchat, *wentry; + user_t bot; } _G; /** MAIN **/ /************/ +void print_msg(const user_t*, const char*); +void parse(char*); + int main(int argc, char** argv) { srand(time(NULL)); FILE* fp; @@ -67,62 +77,152 @@ int main(int argc, char** argv) { return -1; } - char buf[2048], *get; + int in_at = 0; + char buf[2048], input[256], *get; printf("Connecting to Flashii ...\n"); - wsock_t* sock = wsock_open(FII_ADDR, "/", 80); - printf("Authenticating ...\n"); - - sprintf(buf, "1\tMisuzu\t%s", _G.session); - wsock_send_str(sock, buf); + _G.sock = wsock_open(FII_ADDR, "/", 80); fd_set fds; - FD_ZERO(&fds); - FD_SET(0, &fds); - FD_SET(sock->sock, &fds); - struct timeval tout; - tout.tv_sec = 5; - tout.tv_usec = 0; - - int buf_at = 0; - - for(;;) { - printf("**** pinged ****\n"); - wsock_ping(sock, NULL); - int sel = select(sock->sock + 1, &fds, NULL, NULL, &tout); - if(sel > 0) { - if(FD_ISSET(0, &fds)) { - buf_at += fread(buf + buf_at, 1, sizeof(buf) - buf_at, 0); - buf[buf_at] = '\0'; - printf("%s\n", buf); - } - - if(FD_ISSET(sock->sock, &fds)) { - wsock_recv(sock, &get); - printf("%s\n", get); - free(get); - } - } - } initscr(); - raw(); noecho(); + start_color(); + raw(); noecho(); keypad(stdscr, TRUE); - /*for(;;) { + _G.wchat = newwin(LINES - 2, COLS, 0, 0); + scrollok(_G.wchat, TRUE); + idlok(_G.wchat, TRUE); - }*/ - - printw("hi mom"); + _G.wentry = newwin(1, COLS, LINES - 1, 0); + scrollok(_G.wentry, TRUE); + idlok(_G.wentry, TRUE); + + mvhline(LINES - 2, 0, ACS_HLINE, COLS - 1); refresh(); - halfdelay(1); - while(getch() == ERR); - //getch(); + wprintw(_G.wchat, "Authenticating ...\n"); + sprintf(buf, "1\tMisuzu\t%s", _G.session); + wsock_send_str(_G.sock, buf); + + _G.bot.id = -1; + _G.bot.name = "SERVER"; + memset(_G.bot.perms, 0, sizeof(_G.bot.perms)); + _G.bot.color = 0; // todo: change to gray + + ctx_init(); + _G.exit_poll = 0; + _G.running = 1; + while(_G.running) { + _ping(); + + int fds_max; + FD_ZERO(&fds); + FD_SET(fileno(stdin), &fds); + if(wsock_is_open(_G.sock)) { + FD_SET(_G.sock->sock, &fds); + fds_max = _G.sock->sock; + } else + fds_max = fileno(stdin); + + tout.tv_sec = 5; + tout.tv_usec = 0; + + int sel = select(fds_max + 1, &fds, NULL, NULL, &tout); + if(sel > 0) { + if(FD_ISSET(fileno(stdin), &fds)) { + //if(_G.exit_poll) + // break; + int ch = getch(); + if(ch == 27) + break; + if(ch == '\n') { + input[in_at] = '\0'; + sprintf(buf, "2\t%i\t%s", get_self_id(), input); + + in_at = 0; + wclear(_G.wentry); + } + + input[in_at++] = ch; + waddch(_G.wentry, ch); + //printf("%s\n", buf); + } + + if(FD_ISSET(_G.sock->sock, &fds)) { + int got = wsock_recv(_G.sock, &get); + if(got > 0) { + parse(get); + free(get); + } + } + } + + wrefresh(_G.wchat); + wrefresh(_G.wentry); + } + + wprintw(_G.wchat, "\nQuitting ..."); + wrefresh(_G.wchat); + + wsock_free(_G.sock); + ctx_free(); + delwin(_G.wentry); + delwin(_G.wchat); endwin(); return 0; } +void chat_msg(const user_t* user, const char* msg) { + static int last_user = -1; + wprintw(_G.wchat, "\n%s: %s", user->name, msg); +} + +void parse(char* msg) { + if(strlen(msg) == 0) + return; + + wprintw(_G.wchat, "\n"); + wprintw(_G.wchat, msg); + + char *ptr = msg, count; + for(count = 1; (ptr = strchr(ptr + 1, '\t')) != NULL; ++count); + + char** parts = malloc(sizeof(char*) * count); + parts[0] = ptr = msg; + for(int i = 1; (ptr = strchr(ptr + 1, '\t')) != NULL; ++i) { + *ptr = '\0'; + parts[i] = ptr + 1; + } + + user_t user, *puser; + channel_t chan, *pchan; + + int id = strtol(parts[0], NULL, 10); + switch(id) { + case 1: + if(parts[1][0] == 'y') { + user.id = (int)strtol(parts[2], NULL, 10); + strncpy(user.name, parts[3], sizeof(user.name)); + user.color = parse_color(parts[4]); + parse_perms(parts[5], USER_PERMS, user.perms); + add_user(&user); + set_self(user.id); + } else { + wprintw(_G.wchat, "Your authentication was rejected: "); + wprintw(_G.wchat, parts[2]); + wprintw(_G.wchat, "\nPress ESC to quit.\n"); + _G.exit_poll = 1; + } + break; + case 2: + + break; + } + + free(parts); +} + const char* _home(const char* path) { static char full_path[4096]; @@ -131,3 +231,19 @@ const char* _home(const char* path) { strcat(full_path, path); return full_path; } + +void _ping() { + static char buffer[32]; + static time_t last_ping = 0; + time_t now; + + int self = get_self_id(); + if(self == -1) + return; + sprintf(buffer, "0\t%i", self); + + if(difftime(time(&now), last_ping) >= 15) { + wsock_send_str(_G.sock, buffer); + last_ping = now; + } +} diff --git a/src/wsock.c b/src/wsock.c index 4055794..0be0aa1 100644 --- a/src/wsock.c +++ b/src/wsock.c @@ -5,7 +5,7 @@ #define WS_REORDER_BUFLEN (sizeof(long double)) -int _sys_is_net_order() { +static int _sys_is_net_order() { const uint16_t test = 0xB00B; return ((char*)&test)[0] == 0xB0; } @@ -67,7 +67,7 @@ void buffer_append_str(buffer_t* buf, const char* data) { buffer_append(buf, data, strlen(data)); } -int _buffer_read(buffer_t* buf, char* out, int n) { +static int _buffer_read(buffer_t* buf, char* out, int n) { if(buffer_length(buf) == 0) return 0; @@ -132,7 +132,7 @@ int buffer_flush_str(buffer_t* buf, char** out) { return length; } -void _buffer_clear(buffer_t* buf, int root) { +static void _buffer_clear(buffer_t* buf, int root) { if(buf == NULL) return; _buffer_clear(buf->next, 0); @@ -160,14 +160,16 @@ void buffer_free(buffer_t* buf) { /** WEB SOCKET **/ /******************/ -int _wsock_header_length(char* head) { +static int _wsock_header_length(char* head) { return 2 + ((head[1] & 0x80) == 0 ? 0 : 4) + ((head[1] & 0x7F) < 0x7E ? 0 : ((head[1] & 0x7F) == 0x7E ? 2 : 8)); } -int _wsock_read_header(char* head, _ws_head_t* out) { +static int _wsock_read_header + (char* head, _ws_head_t* out) +{ out->head_length = _wsock_header_length(head); out->fin = 0x80 & head[0]; out->opcode = 0x0F & head[0]; @@ -207,12 +209,12 @@ int _wsock_read_header(char* head, _ws_head_t* out) { return 1; } -void _wsock_mask(char* mask, char* msg, int length) { +static void _wsock_mask(char* mask, char* msg, int length) { for(int i = 0; i < length; ++i) msg[i] = msg[i] ^ mask[i % 4]; } -void _wsock_gen_mask(char* mask) { +static void _wsock_gen_mask(char* mask) { for(int i = 0; i < 4; ++i) mask[i] = rand() % 0xFF; } @@ -308,7 +310,9 @@ int wsock_mode_get(wsock_t* ws) { return ws->mode; } -int _srecv(wsock_t* ws, char* out, int len, int flags) { +static int _srecv + (wsock_t* ws, char* out, int len, int flags) +{ int got = recv(ws->sock, out, len, flags); if(got == EAGAIN || got == EWOULDBLOCK) return 0; @@ -410,29 +414,24 @@ int wsock_recv(wsock_t* ws, char** out) { case WS_PING: wsock_send_raw(ws, WS_PONG, body, body_length); break; + case WS_PONG: + break; } - if(WS_OP_IS_CTRL(ws->head_buf.opcode)) + if(WS_OP_IS_CTRL(ws->head_buf.opcode)) { + got = 0; free(body); + } - int opcode_was = ws->head_buf.opcode; ws->head_buf.opcode = WS_OP_UNDEF; - if(WS_OP_IS_CTRL(opcode_was) || !ws->head_buf.fin) { + if(!ws->head_buf.fin) { got = 0; continue; } else break; } while(ws->mode & WS_FULL_RECV); - // catches a weird potential case where the server sends - // empty data frames which will exit the main loop with - // the same condition as a nonblocking call that would - // block. to prevent confusion and the proper functioning - // of the WS_FULL_RECV flag, call recursively in this case - if(ws->mode & WS_FULL_RECV && got == 0) - return wsock_recv(ws, out); - else - return got; + return got; } // todo: support sending fragments at some point diff --git a/src/wsock.h b/src/wsock.h index 68f39fb..69ed383 100644 --- a/src/wsock.h +++ b/src/wsock.h @@ -94,6 +94,14 @@ typedef enum { // has an EXTREME THIRST for partial sends, they can // implement this behavior themselves +// !!! VERY IMPORTANT NOTE !!! +// if using WS_FULL_RECV, wsock_recv() can STILL RETURN 0 +// this happens when a control frame is received from the +// server and is handled by this library. in order for +// select() or poll() to function correctly when used +// outside of the library this is a necessary behavior. +// please check for a 0 return and handle appropriately + typedef struct { uint8_t fin, opcode, first_opcode, masked, mask[4], head_length;