/* * This file is part of the Snippets project. * Copyright 2024 Edward V. Emelianov . * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include // unix socket #include #include #include "usefull_macros.h" // max clients amount static int maxclients = 32; // too much clients handler; it is running for client connected with number>maxclients (before closing its fd) static sl_sock_maxclh_t toomuch_handler = NULL; // new client connected handler; it will be run each new connection static sl_sock_connh_t newconnect_handler = NULL; // client disconnected handler static sl_sock_disch_t disconnect_handler = NULL; /** * @brief sl_sock_changemaxclients - change amount of max simultaneously connected clients * SHOULD BE run BEFORE running of server * @param val - maximal clients number */ void sl_sock_changemaxclients(int val){ maxclients = val; } int sl_sock_getmaxclients(){ return maxclients; } // in each next function changed default handlers you can set h to NULL to remove your handler // setter of "too much clients" handler void sl_sock_maxclhandler(sl_sock_maxclh_t h){ toomuch_handler = h; } // setter of "new client connected" handler void sl_sock_connhandler(sl_sock_connh_t h){ newconnect_handler = h; } // setter of "client disconnected" handler void sl_sock_dischandler(sl_sock_disch_t h){ disconnect_handler = h; } // text messages for `hresult` static const char *resmessages[RESULT_AMOUNT] = { [RESULT_OK] = "OK\n", [RESULT_FAIL] = "FAIL\n", [RESULT_BADKEY] = "BADKEY\n", [RESULT_BADVAL] = "BADVAL\n", [RESULT_SILENCE] = "", }; /** * @brief sl_sock_hresult2str - get string message by hresult value * @param r - handler result * @return string with message */ const char *sl_sock_hresult2str(sl_sock_hresult_e r){ if(r >= RESULT_AMOUNT) return "BADRESULT"; return resmessages[r]; } // convert UNIX socket name for unaddr; result should be free'd static char *convunsname(const char *path){ char *apath = MALLOC(char, 106); if(*path == 0){ DBG("convert name starting from 0"); apath[0] = 0; strncpy(apath+1, path+1, 104); }else if(strncmp("\\0", path, 2) == 0){ DBG("convert name starting from \\0"); apath[0] = 0; strncpy(apath+1, path+2, 104); }else strncpy(apath, path, 105); return apath; } /** * @brief sl_sock_delete - close socket and delete descriptor * @param sock - pointer to socket descriptor */ void sl_sock_delete(sl_sock_t **sock){ if(!sock || !*sock) return; sl_sock_t *ptr = *sock; ptr->connected = 0; if(ptr->rthread){ DBG("Cancel thread"); pthread_cancel(ptr->rthread); } DBG("close fd=%d", ptr->fd); if(ptr->fd > -1) close(ptr->fd); DBG("delete ring buffer"); sl_RB_delete(&ptr->buffer); DBG("free addrinfo"); if(ptr->addrinfo) freeaddrinfo(ptr->addrinfo); DBG("free other"); FREE(ptr->node); FREE(ptr->service); FREE(ptr->data); DBG("free sock"); FREE(*sock); } /** * @brief clientrbthread - thread to fill client's ringbuffer with incoming data * If s->handlers is not NULL, process all incoming data HERE, you shouldn't use ringbuffer by hands! * @param d - socket descriptor * @return NULL */ static void *clientrbthread(void *d){ sl_sock_t *s = (sl_sock_t*) d; size_t buflen = s->buffer->length; char *buf = MALLOC(char, buflen); DBG("Start client read buffer thread"); while(s && s->connected){ pthread_mutex_lock(&s->mutex); if(1 != sl_canread(s->fd)){ pthread_mutex_unlock(&s->mutex); usleep(1000); continue; } ssize_t n = read(s->fd, buf, buflen); //DBG("read %zd from fd=%d, unlock", n, s->fd); //DBG("buf=%s", buf); pthread_mutex_unlock(&s->mutex); if(n < 1){ WARNX(_("Server disconnected")); goto errex; } ssize_t got = 0; do{ ssize_t written = sl_RB_write(s->buffer, (uint8_t*)buf + got, n-got); //DBG("Put %zd to buffer, got=%zd, n=%zd", written, got, n); if(got > n) return NULL; if(written > 0) got += written; }while(got != n); //DBG("All messsages done"); } errex: s->rthread = 0; s->connected = FALSE; return NULL; } // common for server thread and `sendall` static sl_sock_t **clients = NULL; /** * @brief sl_sock_sendall - send data to all clients connected (works only for server) * @param data - message * @param len - its length * @return N of sends or -1 if no server process running */ int sl_sock_sendall(uint8_t *data, size_t len){ if(!clients) return -1; int nsent = 0; for(int i = maxclients; i > 0; --i){ if(!clients[i]) continue; if(clients[i]->fd < 0 || !clients[i]->connected) continue; if((ssize_t)len == sl_sock_sendbinmessage(clients[i], data, len)) ++nsent; } return nsent; } // parser of client's message static sl_sock_hresult_e msgparser(sl_sock_t *client, char *str){ char key[SL_KEY_LEN], val[SL_VAL_LEN], *valptr; if(!str || !*str) return RESULT_BADKEY; int N = sl_get_keyval(str, key, val); DBG("getval=%d, key=%s, val=%s", N, key, val); if(N == 0) return RESULT_BADKEY; if(N == 1) valptr = NULL; else valptr = val; if(0 == strcmp(key, "help")){ sl_sock_sendstrmessage(client, "\nHelp:\n"); for(sl_sock_hitem_t *h = client->handlers; h->handler; ++h){ if(h->help){ sl_sock_sendstrmessage(client, h->key); sl_sock_sendstrmessage(client, ": "); sl_sock_sendstrmessage(client, h->help); sl_sock_sendbyte(client, '\n'); } } sl_sock_sendbyte(client, '\n'); return RESULT_SILENCE; } for(sl_sock_hitem_t *h = client->handlers; h->handler; ++h){ if(strcmp(h->key, key)) continue; return h->handler(client, h, valptr); } return RESULT_BADKEY; } /** * @brief serverrbthread - thread for standard server procedure (when user give non-NULL `handlers`) * @param d - socket descriptor * @return NULL */ static void *serverthread(void _U_ *d){ sl_sock_t *s = (sl_sock_t*) d; if(!s || !s->handlers){ WARNX(_("Can't start server handlers thread")); goto errex; } int sockfd = s->fd; if(listen(sockfd, maxclients) == -1){ WARN("listen"); goto errex; } DBG("Start server handlers thread"); int nfd = 1; // only one socket @start struct pollfd *poll_set = MALLOC(struct pollfd, maxclients+1); clients = MALLOC(sl_sock_t*, maxclients+1); // init default clients records for(int i = maxclients; i > 0; --i){ DBG("fill %dth client info", i); clients[i] = MALLOC(sl_sock_t, 1); sl_sock_t *c = clients[i]; c->type = s->type; if(s->node) c->node = strdup(s->node); if(s->service) c->service = strdup(s->service); c->handlers = s->handlers; // fill addrinfo c->addrinfo = MALLOC(struct addrinfo, 1); c->addrinfo->ai_addr = MALLOC(struct sockaddr, 1); } // ZERO - listening server socket poll_set[0].fd = sockfd; poll_set[0].events = POLLIN; // disconnect client void disconnect_(sl_sock_t *c, int N){ DBG("client \"%s\" (fd=%d) disconnected", c->IP, c->fd); if(disconnect_handler) disconnect_handler(c); pthread_mutex_lock(&c->mutex); DBG("close fd %d", c->fd); c->connected = 0; close(c->fd); sl_RB_clearbuf(c->buffer); // now move all data of last client to disconnected if(nfd > 2 && N != nfd - 1){ // don't move the only or the last DBG("lock fd=%d", clients[nfd-1]->fd); pthread_mutex_lock(&clients[nfd-1]->mutex); clients[N] = clients[nfd-1]; clients[nfd-1] = c; DBG("unlock fd=%d", clients[N]->fd); pthread_mutex_unlock(&clients[N]->mutex); // now N is nfd-1 poll_set[N] = poll_set[nfd - 1]; } DBG("unlock fd=%d", c->fd); pthread_mutex_unlock(&c->mutex); --nfd; } // allocate buffer with size not less than RB size size_t bufsize = s->buffer->length; // as RB should be 1 byte less, this is OK uint8_t *buf = MALLOC(uint8_t, bufsize); while(s && s->connected){ poll(poll_set, nfd, 1); if(poll_set[0].revents & POLLIN){ // check main for accept() struct sockaddr a; socklen_t len = sizeof(struct sockaddr); int client = accept(sockfd, &a, &len); DBG("New connection, nfd=%d, len=%d", nfd, len); if(nfd == maxclients + 1){ WARNX(_("Limit of connections reached")); if(toomuch_handler) toomuch_handler(client); close(client); }else{ memset(&poll_set[nfd], 0, sizeof(struct pollfd)); poll_set[nfd].fd = client; poll_set[nfd].events = POLLIN; DBG("got client[%d], fd=%d", nfd, client); sl_sock_t *c = clients[nfd]; c->fd = client; DBG("memcpy"); memcpy(c->addrinfo->ai_addr, &a, len); DBG("set conn flag"); c->connected = 1; struct sockaddr_in* inaddr = (struct sockaddr_in*)&a; if(!inet_ntop(AF_INET, &inaddr->sin_addr, c->IP, INET_ADDRSTRLEN)){ WARN("inet_ntop()"); *c->IP = 0; } DBG("got IP:%s", c->IP); if(newconnect_handler) newconnect_handler(c); if(!c->buffer){ // allocate memory for client's ringbuffer DBG("allocate ringbuffer"); c->buffer = sl_RB_new(s->buffer->length); // the same size as for master } ++nfd; } } // scan connections for(int fdidx = 1; fdidx < nfd; ++fdidx){ if((poll_set[fdidx].revents & POLLIN) == 0) continue; int fd = poll_set[fdidx].fd; sl_sock_t *c = clients[fdidx]; pthread_mutex_lock(&c->mutex); size_t nread = sl_RB_freesize(c->buffer); if(nread > bufsize) nread = bufsize; else if(nread < 1){ // no space in ringbuffer pthread_mutex_unlock(&c->mutex); // check for RB overflow if(sl_RB_hasbyte(c->buffer, '\n') < 0){ // -1 - buffer empty (can't be), -2 - buffer overflow WARNX(_("Server thread: ring buffer overflow for fd=%d"), fd); disconnect_(c, fdidx); --fdidx; } continue; } ssize_t got = read(fd, buf, nread); DBG("can read %zd bytes, got %zd bytes, buf=_%s_", nread, got, buf); pthread_mutex_unlock(&c->mutex); if(got <= 0){ // client disconnected disconnect_(c, fdidx); --fdidx; }else{ if(sl_RB_write(c->buffer, buf, got) < (size_t) got){ WARNX(_("Server thread: can't write data to ringbuffer: overflow from fd=%d"), fd); disconnect_(c, fdidx); --fdidx; } } } // and now check all incoming buffers for(int fdidx = 1; fdidx < nfd; ++fdidx){ sl_sock_t *c = clients[fdidx]; if(!c->connected) continue; ssize_t got = sl_RB_readline(c->buffer, (char*)buf, bufsize); if(got < 0){ // buffer overflow WARNX(_("Server thread: buffer overflow from fd=%d"), c->fd); disconnect_(c, fdidx); --fdidx; continue; }else if(got == 0) continue; sl_sock_hresult_e r = msgparser(c, (char*)buf); if(r != RESULT_SILENCE) sl_sock_sendstrmessage(c, sl_sock_hresult2str(r)); } } // clear memory FREE(buf); FREE(poll_set); for(int i = maxclients; i > 0; --i){ DBG("Clear %dth client data", i); sl_sock_t *c = clients[i]; if(c->fd > -1) close(c->fd); if(c->buffer) sl_RB_delete(&c->buffer); FREE(c->addrinfo->ai_addr); FREE(c->addrinfo); FREE(c->node); FREE(c->service); FREE(c); } FREE(clients); errex: s->rthread = 0; return NULL; } /** * @brief sl_sock_open - open socket (client or server) * @param type - socket type * @param path - path (for UNIX socket); for NET: ":port" for server, "server:port" for client * @param handlers - standard handlers when read data (or NULL) * @param bufsiz - input ring buffer size * @param isserver - 1 for server, 0 for client * @return socket descriptor or NULL if failed * to create anonymous UNIX-socket you can start "path" from 0 or from string "\0" */ static sl_sock_t *sl_sock_open(sl_socktype_e type, const char *path, sl_sock_hitem_t *handlers, int bufsiz, int isserver){ if(!path || type >= SOCKT_AMOUNT) return NULL; if(bufsiz < 256) bufsiz = 256; int sock = -1; struct addrinfo ai = {0}, *res = &ai; struct sockaddr_un unaddr = {0}; char *str = NULL; ai.ai_socktype = SOCK_STREAM; switch(type){ case SOCKT_UNIX: str = convunsname(path); if(!str) return NULL; unaddr.sun_family = AF_UNIX; ai.ai_addr = (struct sockaddr*) &unaddr; ai.ai_addrlen = sizeof(unaddr); memcpy(unaddr.sun_path, str, 106); ai.ai_family = AF_UNIX; //ai.ai_socktype = SOCK_SEQPACKET; break; case SOCKT_NET: case SOCKT_NETLOCAL: //ai.ai_socktype = SOCK_DGRAM; // = SOCK_STREAM; ai.ai_family = AF_INET; if(isserver) ai.ai_flags = AI_PASSIVE; break; default: // never reached WARNX(_("Unsupported socket type %d"), type); return NULL; break; } sl_sock_t *s = MALLOC(sl_sock_t, 1); s->type = type; s->fd = -1; s->handlers = handlers; s->buffer = sl_RB_new(bufsiz); if(!s->buffer) sl_sock_delete(&s); else{ // fill node/service if(type == SOCKT_UNIX) s->node = str; // str now is converted path else{ char *delim = strchr(path, ':'); if(!delim) s->service = strdup(path); // only port else{ if(delim == path) s->service = strdup(path+1); else{ size_t l = delim - path; s->node = MALLOC(char, l + 1); strncpy(s->node, path, l); s->service = strdup(delim + 1); } } DBG("socket->service=%s", s->service); } DBG("socket->node=%s", s->node); } // now try to open socket if(type != SOCKT_UNIX){ DBG("try to get addrinfo for node '%s' and service '%s'", s->node, s->service); char *node = s->node; if(isserver){ if(s->type == SOCKT_NET) node = NULL; // common net server -> node==NULL else node = "127.0.0.1"; // localhost } DBG("---> node '%s', service '%s'", node, s->service); int e = getaddrinfo(node, s->service, &ai, &res); if(e){ WARNX("getaddrinfo(): %s", gai_strerror(e)); sl_sock_delete(&s); return NULL; } } for(struct addrinfo *p = res; p; p = p->ai_next){ if((sock = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) continue; DBG("Try proto %d, type %d", p->ai_protocol, p->ai_socktype); if(isserver){ int reuseaddr = 1; if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(int)) == -1){ WARN("setsockopt()"); close(sock); sock = -1; continue; } if(bind(sock, p->ai_addr, p->ai_addrlen) == -1){ WARN("bind()"); close(sock); sock = -1; continue; } int enable = 1; if(ioctl(sock, FIONBIO, (void *)&enable) < 0){ // make socket nonblocking WARN("Can't make socket non-blocked"); } }else{ if(connect(sock, p->ai_addr, p->ai_addrlen) == -1){ WARN("connect()"); close(sock); sock = -1; } } break; } if(sock < 0) sl_sock_delete(&s); else{ s->fd = sock; pthread_mutex_init(&s->mutex, NULL); DBG("s->fd=%d, node=%s, service=%s", s->fd, s->node, s->service); int r = -1; if(isserver){ if(s->handlers) r = pthread_create(&s->rthread, NULL, serverthread, (void*)s); else r = 0; }else{ r = pthread_create(&s->rthread, NULL, clientrbthread, (void*)s); } if(r){ WARN("pthread_create()"); sl_sock_delete(&s); }else{ s->connected = TRUE; DBG("fd=%d CONNECTED", s->fd); } } return s; } /** * @brief sl_sock_run_client - try to connect to server and, socket read thread and process optional handlers * @param type - server type * @param path - path or address:port * @param handlers - array with handlers * @param bufsiz - input ring buffer size * @return socket descriptor or NULL if failed */ sl_sock_t *sl_sock_run_client(sl_socktype_e type, const char *path, int bufsiz){ sl_sock_t *s = sl_sock_open(type, path, NULL, bufsiz, 0); return s; } /** * @brief sl_sock_run_server - run built-in server parser * @param type - server type * @param path - path or port * @param handlers - array with handlers * @param bufsiz - input ring buffer size * @return socket descriptor or NULL if failed */ sl_sock_t *sl_sock_run_server(sl_socktype_e type, const char *path, int bufsiz, sl_sock_hitem_t *handlers){ sl_sock_t *s = sl_sock_open(type, path, handlers, bufsiz, 1); return s; } /** * @brief sl_sock_sendbinmessage - send binary data * @param socket - socket * @param msg - data * @param l - data length * @return amount of bytes sent or -1 in case of error */ ssize_t sl_sock_sendbinmessage(sl_sock_t *socket, const uint8_t *msg, size_t l){ if(!msg || l < 1) return -1; DBG("send to fd=%d message with len=%zd (%s)", socket->fd, l, msg); while(socket && socket->connected && !sl_canwrite(socket->fd)); if(!socket || !socket->connected) return -1; DBG("lock"); pthread_mutex_lock(&socket->mutex); DBG("SEND"); ssize_t sent = 0; do{ ssize_t r = send(socket->fd, msg+sent, l, MSG_NOSIGNAL); if(r < 0){ sent = -1; break; }else sent += r; DBG("sent %zd bytes", r); } while((size_t)sent != l); DBG("unlock"); pthread_mutex_unlock(&socket->mutex); return sent; } ssize_t sl_sock_sendstrmessage(sl_sock_t *socket, const char *msg){ if(!msg) return -1; size_t l = strlen(msg); return sl_sock_sendbinmessage(socket, (const uint8_t*)msg, l); } /** * @brief sl_sock_sendbyte - send one byte over socket * @param socket - socket * @param byte - byte to send * @return -1 in case of error, 1 if all OK */ ssize_t sl_sock_sendbyte(sl_sock_t *socket, uint8_t byte){ while(socket && socket->connected && !sl_canwrite(socket->fd)); if(!socket || !socket->connected) return -1; DBG("lock"); pthread_mutex_lock(&socket->mutex); ssize_t r = send(socket->fd, &byte, 1, MSG_NOSIGNAL); DBG("unlock"); pthread_mutex_unlock(&socket->mutex); return r; } /** * @brief sl_sock_readline - read string line from incoming ringbuffer * @param sock - socket * @param str (o) - buffer to copy * @param len - length of str * @return amount of bytes read */ ssize_t sl_sock_readline(sl_sock_t *sock, char *str, size_t len){ if(!sock || !sock->buffer || !str || !len) return -1; return sl_RB_readline(sock->buffer, str, len); } // default handlers - setters/getters of int64, double and string sl_sock_hresult_e sl_sock_inthandler(sl_sock_t *client, sl_sock_hitem_t *hitem, const char *str){ char buf[128]; sl_sock_int_t *i = (sl_sock_int_t *)hitem->data; if(!str){ // getter snprintf(buf, 127, "%s=%" PRId64 "\n", hitem->key, i->val); sl_sock_sendstrmessage(client, buf); return RESULT_SILENCE; } long long x; if(!sl_str2ll(&x, str)) return RESULT_BADVAL; if(x < INT64_MIN || x > INT64_MAX) return RESULT_BADVAL; i->val = (int64_t)x; i->timestamp = sl_dtime(); return RESULT_OK; } sl_sock_hresult_e sl_sock_dblhandler(sl_sock_t *client, sl_sock_hitem_t *hitem, const char *str){ char buf[128]; sl_sock_double_t *d = (sl_sock_double_t *)hitem->data; if(!str){ // getter snprintf(buf, 127, "%s=%g\n", hitem->key, d->val); sl_sock_sendstrmessage(client, buf); return RESULT_SILENCE; } double dv; if(!sl_str2d(&dv, str)) return RESULT_BADVAL; d->val = dv; d->timestamp = sl_dtime(); return RESULT_OK; } sl_sock_hresult_e sl_sock_strhandler(sl_sock_t *client, sl_sock_hitem_t *hitem, const char *str){ char buf[SL_VAL_LEN + SL_KEY_LEN + 3]; sl_sock_string_t *s = (sl_sock_string_t*) hitem->data; if(!str){ // getter snprintf(buf, SL_VAL_LEN + SL_KEY_LEN + 2, "%s=%s\n", hitem->key, s->val); sl_sock_sendstrmessage(client, buf); return RESULT_SILENCE; } int l = strlen(str); if(l > SL_VAL_LEN - 1) return RESULT_BADVAL; s->len = l; s->timestamp = sl_dtime(); memcpy(s->val, str, l); s->val[l] = 0; return RESULT_OK; }