diff --git a/README.md b/README.md index 45d7f4e..55123e1 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,11 @@ libp2p: https://github.com/libp2p/specs

Prerequisites: To compile the C version you will need:
lmdb https://github.com/jmjatlanta/lmdb
-c-protobuf https://github.com/kenCode/c-protobuf
-c-multihash https://github.com/kenCode/c-multihash
-c-multiaddr https://github.com/kenCode/c-multiaddr
-c-libp2p https://github.com/kenCode/c-libp2p
+c-protobuf https://github.com/kenCode-de/c-protobuf
+c-multihash https://github.com/kenCode-de/c-multihash
+c-multiaddr https://github.com/kenCode-de/c-multiaddr
+c-libp2p https://github.com/kenCode-de/c-libp2p

-And of course this project at https://github.com/kenCode/c-ipfs
+And of course this project at https://github.com/kenCode-de/c-ipfs

The compilation at this point is simple, but not very flexible. Place all of these projects in a directory. Compile all (the order above is recommended) by going into each one and running "make all". diff --git a/core/api.c b/core/api.c new file mode 100644 index 0000000..dde67fd --- /dev/null +++ b/core/api.c @@ -0,0 +1,270 @@ +/** + * Methods for lightweight/specific HTTP for API communication. + */ +#include +#include +#include +#include +#include +#include + +#include "libp2p/net/p2pnet.h" +#include "libp2p/utils/logger.h" +#include "ipfs/core/api.h" + +pthread_mutex_t conns_lock; +int conns_count; + +pthread_t listen_thread = 0; + +struct s_list api_list; + +/** + * Pthread to take care of each client connection. + * @param ptr is the connection index in api_list, integer not pointer, cast required. + * @returns nothing + */ +void *api_connection_thread (void *ptr) +{ + int timeout, s, r; + const INT_TYPE i = (INT_TYPE) ptr; + char buf[MAX_READ+1], *p; + char client[INET_ADDRSTRLEN]; + struct s_request req; + + req.buf = NULL; // sanity. + + buf[MAX_READ] = '\0'; + + s = api_list.conns[i]->socket; + timeout = api_list.timeout; + + if (socket_read_select4(s, timeout) <= 0) { + libp2p_logger_error("api", "Client connection timeout.\n"); + goto quit; + } + r = read(s, buf, sizeof buf); + if (r <= 0) { + libp2p_logger_error("api", "Read from client fail.\n"); + goto quit; + } + buf[r] = '\0'; + + p = strstr(buf, "\r\n\r\n"); + + if (p) { + req.size = p - buf + 1; + req.buf = malloc(req.size); + if (!req.buf) { + // memory allocation fail. + libp2p_logger_error("api", "malloc fail.\n"); + write_cstr (s, HTTP_500); + goto quit; + } + memcpy(req.buf, buf, req.size - 1); + req.buf[req.size-1] = '\0'; + + req.method = req.buf; + p = strchr(req.method, ' '); + if (!p) { + write_cstr (s, HTTP_400); + goto quit; + } + *p++ = '\0'; // End of method. + req.path = p; + p = strchr(req.path, ' '); + if (!p) { + write_cstr (s, HTTP_400); + goto quit; + } + *p++ = '\0'; // End of path. + req.http_ver = p; + p = strchr(req.http_ver, '\r'); + if (!p) { + write_cstr (s, HTTP_400); + goto quit; + } + *p++ = '\0'; // End of http version. + while (*p == '\r' || *p == '\n') p++; + req.header = p; + req.body = req.buf + req.size; + req.body_size = 0; + + libp2p_logger_error("api", "method = '%s'\n" + "path = '%s'\n" + "http_ver = '%s'\n" + "header {\n%s\n}\n" + "body_size = %d\n", + req.method, req.path, req.http_ver, req.header, req.body_size); + + if (strcmp(req.method, "GET")==0) { + // just an error message, because it's not used. + write_cstr (s, HTTP_404); + //} else if (cstrstart(buf, "POST ")) { + // TODO: Handle chunked/gzip/form-data/json POST requests. + } + } else { + write_cstr (s, HTTP_400); + } + +quit: + if (req.buf) + free(req.buf); + if (inet_ntop(AF_INET, &(api_list.conns[i]->ipv4), client, INET_ADDRSTRLEN) == NULL) + strcpy(client, "UNKNOW"); + libp2p_logger_error("api", "Closing client connection %s:%d (%d).\n", client, api_list.conns[i]->port, i+1); + pthread_mutex_lock(&conns_lock); + close(s); + free (api_list.conns[i]); + api_list.conns[i] = NULL; + conns_count--; + pthread_mutex_unlock(&conns_lock); + + return NULL; +} + +/** + * Close all connections stopping respectives pthreads and free allocated memory. + */ +void api_connections_cleanup (void) +{ + int i; + + pthread_mutex_lock(&conns_lock); + if (conns_count > 0 && api_list.conns) { + for (i = 0 ; i < api_list.max_conns ; i++) { + if (api_list.conns[i]->pthread) { + pthread_cancel (api_list.conns[i]->pthread); + close (api_list.conns[i]->socket); + free (api_list.conns[i]); + api_list.conns[i] = NULL; + } + } + conns_count = 0; + } + if (api_list.conns) { + free (api_list.conns); + api_list.conns = NULL; + } + pthread_mutex_unlock(&conns_lock); +} + +/** + * Pthread to keep in background dealing with client connections. + * @param ptr is not used. + * @returns nothing + */ +void *api_listen_thread (void *ptr) +{ + int s; + INT_TYPE i; + uint32_t ipv4; + uint16_t port; + char client[INET_ADDRSTRLEN]; + + conns_count = 0; + + for (;;) { + s = socket_accept4(api_list.socket, &ipv4, &port); + if (s <= 0) { + break; + } + if (conns_count >= api_list.max_conns) { // limit reached. + libp2p_logger_error("api", "Limit of connections reached (%d).\n", api_list.max_conns); + close (s); + continue; + } + + pthread_mutex_lock(&conns_lock); + for (i = 0 ; i < api_list.max_conns && api_list.conns[i] ; i++); + api_list.conns[i] = malloc (sizeof (struct s_conns)); + if (!api_list.conns[i]) { + libp2p_logger_error("api", "Fail to allocate memory to accept connection.\n"); + close (s); + continue; + } + if (inet_ntop(AF_INET, &ipv4, client, INET_ADDRSTRLEN) == NULL) + strcpy(client, "UNKNOW"); + api_list.conns[i]->socket = s; + api_list.conns[i]->ipv4 = ipv4; + api_list.conns[i]->port = port; + if (pthread_create(&(api_list.conns[i]->pthread), NULL, api_connection_thread, (void*)i)) { + libp2p_logger_error("api", "Create pthread fail.\n"); + free (api_list.conns[i]); + api_list.conns[i] = NULL; + conns_count--; + close(s); + } else { + conns_count++; + } + libp2p_logger_error("api", "Accept connection %s:%d (%d/%d), pthread %d.\n", client, port, conns_count, api_list.max_conns, i+1); + pthread_mutex_unlock(&conns_lock); + } + api_connections_cleanup (); + return NULL; +} + +/** + * Start API interface daemon. + * @param port. + * @param max_conns. + * @param timeout time out of client connection. + * @returns 0 when failure or 1 if success. + */ +int api_start (uint16_t port, int max_conns, int timeout) +{ + int s; + size_t alloc_size = sizeof(void*) * max_conns; + + api_list.ipv4 = hostname_to_ip("127.0.0.1"); // api is listening only on loopback. + api_list.port = port; + + if (listen_thread != 0) { + libp2p_logger_error("api", "API already running.\n"); + return 0; + } + + if ((s = socket_listen(socket_tcp4(), &(api_list.ipv4), &(api_list.port))) <= 0) { + libp2p_logger_error("api", "Failed to init API. port: %d\n", port); + return 0; + } + + api_list.socket = s; + api_list.max_conns = max_conns; + api_list.timeout = timeout; + + api_list.conns = malloc (alloc_size); + if (!api_list.conns) { + close (s); + libp2p_logger_error("api", "Error allocating memory.\n"); + return 0; + } + memset(api_list.conns, 0, alloc_size); + + if (pthread_create(&listen_thread, NULL, api_listen_thread, NULL)) { + close (s); + free (api_list.conns); + api_list.conns = NULL; + listen_thread = 0; + libp2p_logger_error("api", "Error creating thread for API.\n"); + return 0; + } + + return 1; +} + +/** + * Stop API. + * @returns 0 when failure or 1 if success. + */ +int api_stop (void) +{ + if (!listen_thread) return 0; + pthread_cancel(listen_thread); + + api_connections_cleanup (); + + listen_thread = 0; + + return 1; +} diff --git a/include/ipfs/core/api.h b/include/ipfs/core/api.h new file mode 100644 index 0000000..663f797 --- /dev/null +++ b/include/ipfs/core/api.h @@ -0,0 +1,64 @@ +#pragma once + +#ifdef __x86_64__ + #define INT_TYPE uint64_t +#else + #define INT_TYPE uint32_t +#endif + +#define MAX_READ (32*1024) // 32k + +struct s_list { + int socket; + uint32_t ipv4; + uint16_t port; + int max_conns; + int timeout; + struct s_conns { + int socket; + uint32_t ipv4; + uint16_t port; + pthread_t pthread; + } **conns; +}; + +struct s_request { + char *buf; + size_t size; + + char *method; + char *path; + char *http_ver; + char *header; + char *body; + size_t body_size; +}; + +#define HTTP_400 "HTTP/1.1 400 Bad Request\r\n" \ + "Content-Type: text/plain\r\n" \ + "Connection: close\r\n\r\n" \ + "400 Bad Request" + + +#define HTTP_404 "HTTP/1.1 404 Not Found\r\n" \ + "Content-Type: text/plain; charset=utf-8\r\n" \ + "X-Content-Type-Options: nosniff\r\n" \ + "Content-Length: 19\r\n\r\n" \ + "404 page not found\n" + +#define HTTP_500 "HTTP/1.1 500 Internal server error\r\n" \ + "Content-Type: text/plain\r\n" \ + "Connection: close\r\n\r\n" \ + "500 Internal server error" + +#define write_cstr(f,s) write(f,s,sizeof(s)-1) +#define write_str(f,s) write(f,s,strlen(s)) + +#define cstrstart(a,b) (memcmp(a,b,sizeof(b)-1)==0) +#define strstart(a,b) (memcmp(a,b,strlen(b))==0) + +void *api_connection_thread (void *ptr); +void api_connections_cleanup (void); +void *api_listen_thread (void *ptr); +int api_start (uint16_t port, int max_conns, int timeout); +int api_stop (void);