├── .gitignore ├── .gitmodules ├── examples ├── http │ ├── main.c │ ├── http_parser.h │ ├── xhttp.h │ └── http_parser.c └── echo.c ├── LICENSE ├── src ├── utils.h ├── endian.h ├── icmp.h ├── defs.h ├── endian.c ├── tcp_timer.h ├── ip.h ├── utils.c ├── microtcp.h ├── icmp.c ├── arp.h ├── tcp_timer.c ├── tcp.h ├── ip.c ├── tinycthread.h ├── arp.c ├── tinycthread.c └── microtcp.c ├── makefile └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | out/* 2 | obj/* -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "3p/libtuntap"] 2 | path = 3p/libtuntap 3 | url = https://github.com/cozis/libtuntap.git 4 | [submodule "3p/tinycthread"] 5 | path = 3p/tinycthread 6 | url = https://github.com/tinycthread/tinycthread.git 7 | [submodule "3p/packetdrill"] 8 | path = 3p/packetdrill 9 | url = https://github.com/google/packetdrill.git 10 | -------------------------------------------------------------------------------- /examples/http/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "xhttp.h" 7 | 8 | static void callback(xh_request *req, xh_response *res, void *userp) 9 | { 10 | (void) req; 11 | (void) userp; 12 | 13 | res->status = 200; 14 | res->body.str = "Hello, world!"; 15 | xh_header_add(res, "Content-Type", "text/plain"); 16 | } 17 | 18 | int main(void) 19 | { 20 | const char *error = xhttp(80, callback, NULL); 21 | if(error != NULL) { 22 | fprintf(stderr, "Error: %s\n", error); 23 | return 1; 24 | } 25 | return 0; 26 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Francesco Cozzuto 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /examples/echo.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(void) 5 | { 6 | microtcp_t *mtcp = microtcp_create("10.0.0.5", "10.0.0.4", NULL, NULL); 7 | if (mtcp == NULL) { 8 | fprintf(stderr, "Error: Couldn't create MicroTCP instance\n"); 9 | return -1; 10 | } 11 | 12 | uint16_t port = 8081; 13 | microtcp_socket_t *server = microtcp_open(mtcp, port); 14 | if (server == NULL) { 15 | fprintf(stderr, "Error: %s\n", microtcp_strerror(microtcp_get_error(mtcp))); 16 | microtcp_destroy(mtcp); 17 | return -1; 18 | } 19 | 20 | while (1) { 21 | 22 | microtcp_socket_t *client = microtcp_accept(server); 23 | if (client == NULL) { 24 | fprintf(stderr, "Error: %s\n", microtcp_strerror(microtcp_get_socket_error(server))); 25 | break; 26 | } 27 | 28 | char buffer[1024]; 29 | int num = microtcp_recv(client, buffer, sizeof(buffer)); 30 | if (num > 0) { 31 | microtcp_send(client, "echo: ", 6); 32 | microtcp_send(client, buffer, num); 33 | } 34 | microtcp_close(client); 35 | } 36 | 37 | microtcp_close(server); 38 | microtcp_destroy(mtcp); 39 | return 0; 40 | } 41 | -------------------------------------------------------------------------------- /src/utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2024 Francesco Cozzuto 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | 26 | #include 27 | #include "defs.h" 28 | 29 | bool parse_mac(const char *src, size_t len, mac_address_t *mac); 30 | bool parse_ip(const char *ip, ip_address_t *parsed_ip); 31 | mac_address_t generate_random_mac(); 32 | -------------------------------------------------------------------------------- /src/endian.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * MIT License 4 | * 5 | * Copyright (c) 2024 Francesco Cozzuto 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | * 25 | */ 26 | 27 | #include 28 | #include 29 | 30 | bool cpu_is_little_endian(void); 31 | uint16_t net_to_cpu_u16(uint16_t n); 32 | uint32_t net_to_cpu_u32(uint32_t n); 33 | uint16_t cpu_to_net_u16(uint16_t n); 34 | uint32_t cpu_to_net_u32(uint32_t n); 35 | -------------------------------------------------------------------------------- /examples/http/http_parser.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define HP_MAX_HEADERS 8 6 | 7 | typedef struct { 8 | const char *str; 9 | size_t len; 10 | } hp_string_t; 11 | 12 | typedef struct { 13 | bool occurred; 14 | char msg[256]; 15 | } hp_error_t; 16 | 17 | typedef enum { 18 | HP_METHOD_GET, 19 | HP_METHOD_PUT, 20 | HP_METHOD_POST, 21 | HP_METHOD_HEAD, 22 | HP_METHOD_PATCH, 23 | HP_METHOD_TRACE, 24 | HP_METHOD_DELETE, 25 | } hp_method_t; 26 | 27 | typedef enum { 28 | HP_HOSTMODE_NAME, 29 | HP_HOSTMODE_IPV4, 30 | HP_HOSTMODE_IPV6, 31 | } hp_hostmode_t; 32 | 33 | typedef struct { 34 | hp_hostmode_t mode; 35 | union { 36 | uint32_t ipv4; 37 | uint16_t ipv6[8]; 38 | hp_string_t name; 39 | }; 40 | bool no_port; 41 | uint16_t port; 42 | } hp_host_t; 43 | 44 | typedef struct { 45 | hp_host_t host; 46 | hp_string_t path; 47 | hp_string_t query; 48 | hp_string_t schema; 49 | hp_string_t fragment; 50 | hp_string_t username; 51 | hp_string_t password; 52 | } hp_url_t; 53 | 54 | typedef struct { 55 | hp_string_t name; 56 | hp_string_t body; 57 | } hp_header_t; 58 | 59 | typedef struct { 60 | int major, minor; 61 | hp_url_t url; 62 | hp_method_t method; 63 | hp_header_t headers[HP_MAX_HEADERS]; 64 | size_t num_headers; 65 | } hp_request_t; 66 | 67 | bool hp_parse(const char *src, size_t len, hp_request_t *out, hp_error_t *err); 68 | bool hp_parse_url(const char *src, size_t len, hp_url_t *url, hp_error_t *err); 69 | hp_header_t *hp_get_header(hp_request_t req, const char *name); 70 | bool hp_get_content_length(hp_request_t req, size_t *out, hp_error_t *error); -------------------------------------------------------------------------------- /src/icmp.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2024 Francesco Cozzuto 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | 26 | #include 27 | #include "defs.h" 28 | 29 | typedef struct { 30 | void *output_ptr; 31 | size_t output_len; 32 | void *send_data; 33 | void (*send)(void *send_data, ip_address_t ip, size_t len); 34 | } icmp_state_t; 35 | 36 | void icmp_init(icmp_state_t *state, void *send_data, void (*send)(void*, ip_address_t, size_t)); 37 | void icmp_free(icmp_state_t *state); 38 | void icmp_process_packet(icmp_state_t *state, ip_address_t ip, const void *src, size_t len); 39 | void icmp_change_output_buffer(icmp_state_t *state, void *ptr, size_t len); 40 | -------------------------------------------------------------------------------- /examples/http/xhttp.h: -------------------------------------------------------------------------------- 1 | #ifndef XHTTP_H 2 | #define XHTTP_H 3 | 4 | #define MICROHTTP_MAX_CLIENTS 64 5 | 6 | typedef struct { 7 | void *userp; 8 | const char *ip; 9 | const char *mac; 10 | int (*send_frame)(void *userp, const void *src, size_t len); 11 | int (*recv_frame)(void *userp, void *dst, size_t len); 12 | } microhttp_config_t; 13 | 14 | typedef void *xh_handle; 15 | 16 | typedef struct { 17 | char *str; int len; 18 | } xh_string; 19 | 20 | typedef struct { 21 | xh_string key, val; 22 | } xh_pair; 23 | 24 | typedef struct { 25 | xh_pair *list; 26 | int count; 27 | } xh_table; 28 | 29 | typedef enum { 30 | XH_GET = 1, 31 | XH_HEAD = 2, 32 | XH_POST = 4, 33 | XH_PUT = 8, 34 | XH_DELETE = 16, 35 | XH_CONNECT = 32, 36 | XH_OPTIONS = 64, 37 | XH_TRACE = 128, 38 | XH_PATCH = 256, 39 | } xh_method; 40 | 41 | typedef struct { 42 | xh_method method_id; 43 | xh_string method; 44 | xh_string params; 45 | xh_string URL; 46 | unsigned int version_minor; 47 | unsigned int version_major; 48 | xh_table headers; 49 | xh_string body; 50 | } xh_request; 51 | 52 | typedef struct { 53 | int status; 54 | xh_table headers; 55 | xh_string body; 56 | _Bool close; 57 | } xh_response; 58 | 59 | typedef void (*xh_callback)(xh_request*, xh_response*, void*); 60 | 61 | const char *xhttp(unsigned short port, xh_callback callback, xh_handle *handle); 62 | void xh_quit(xh_handle handle); 63 | 64 | void xh_header_add(xh_response *res, const char *name, const char *valfmt, ...); 65 | void xh_header_rem(xh_response *res, const char *name); 66 | const char *xh_header_get(void *req_or_res, const char *name); 67 | _Bool xh_header_cmp(const char *a, const char *b); 68 | 69 | int xh_urlcmp(const char *URL, const char *fmt, ...); 70 | int xh_vurlcmp(const char *URL, const char *fmt, va_list va); 71 | 72 | #define xh_string_new(s, l) \ 73 | ((xh_string) { (s), ((int) (l)) < 0 ? (int) strlen(s) : (int) (l) }) 74 | 75 | #define xh_string_from_literal(s) \ 76 | ((xh_string) { (s), sizeof(s)-1 }) 77 | 78 | #endif // #ifndef XHTTP_H -------------------------------------------------------------------------------- /src/defs.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2024 Francesco Cozzuto 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | 26 | #ifndef MICROTCP_DEFS_H 27 | #define MICROTCP_DEFS_H 28 | #include 29 | #include // static_assert 30 | 31 | typedef struct { 32 | uint8_t data[6]; 33 | } mac_address_t; 34 | 35 | typedef uint32_t ip_address_t; 36 | 37 | static_assert(sizeof(mac_address_t) == 6); 38 | static_assert(sizeof(ip_address_t) == 4); 39 | 40 | #define MAC_ZERO (mac_address_t) {.data = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}} 41 | #define MAC_BROADCAST (mac_address_t) {.data = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff}} 42 | 43 | typedef struct { 44 | void *ptr; 45 | size_t len; 46 | } slice_t; 47 | 48 | #define MIN(X, Y) ((X) < (Y) ? (X) : (Y)) 49 | #define MAX(X, Y) ((X) > (Y) ? (X) : (Y)) 50 | #define ABS(X) ((X) < 0 ? -(X) : (X)) 51 | #define COUNT(X) ((int) (sizeof(X) / sizeof((X)[0]))) 52 | #define SLICE(X) ((slice_t) {.ptr=&(X), .len=sizeof(X)}) 53 | 54 | #define UNPACK_IP(IP) \ 55 | ((IP) >> 0 & 0xff), \ 56 | ((IP) >> 8 & 0xff), \ 57 | ((IP) >> 16 & 0xff), \ 58 | ((IP) >> 24 & 0xff) 59 | 60 | #endif /* MICROTCP_DEFS_H */ 61 | -------------------------------------------------------------------------------- /src/endian.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2024 Francesco Cozzuto 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | 26 | #include "endian.h" 27 | 28 | bool cpu_is_little_endian(void) 29 | { 30 | uint16_t x = 1; 31 | return *((uint8_t*) &x); 32 | } 33 | 34 | static uint16_t invert_byte_order_u16(uint32_t n) 35 | { 36 | return (n >> 8) 37 | | (n << 8); 38 | } 39 | 40 | static uint32_t invert_byte_order_u32(uint32_t n) 41 | { 42 | return ((n >> 24) & 0x000000FF) 43 | | ((n >> 8) & 0x0000FF00) 44 | | ((n << 8) & 0x00FF0000) 45 | | ((n << 24) & 0xFF000000); 46 | } 47 | 48 | uint16_t net_to_cpu_u16(uint16_t n) 49 | { 50 | if (cpu_is_little_endian()) 51 | return invert_byte_order_u16(n); 52 | return n; 53 | } 54 | 55 | uint32_t net_to_cpu_u32(uint32_t n) 56 | { 57 | if (cpu_is_little_endian()) 58 | return invert_byte_order_u32(n); 59 | return n; 60 | } 61 | 62 | uint16_t cpu_to_net_u16(uint16_t n) 63 | { 64 | if (cpu_is_little_endian()) 65 | return invert_byte_order_u16(n); 66 | return n; 67 | } 68 | 69 | uint32_t cpu_to_net_u32(uint32_t n) 70 | { 71 | if (cpu_is_little_endian()) 72 | return invert_byte_order_u32(n); 73 | return n; 74 | } -------------------------------------------------------------------------------- /src/tcp_timer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2024 Francesco Cozzuto 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | 26 | #include 27 | #include 28 | 29 | #ifndef TCP_MAX_TIMERS 30 | #define TCP_MAX_TIMERS 1024 31 | #endif 32 | 33 | #define TCP_MAX_TIMER_NAME 15 34 | 35 | typedef struct tcp_timerset_t tcp_timerset_t; 36 | typedef struct tcp_timer_t tcp_timer_t; 37 | 38 | struct tcp_timer_t { 39 | tcp_timerset_t *set; 40 | tcp_timer_t *prev; 41 | tcp_timer_t *next; 42 | uint64_t set_time; 43 | uint64_t trg_time; 44 | uint64_t deadline; 45 | void (*callback)(void *data); 46 | void *data; 47 | char name[TCP_MAX_TIMER_NAME+1]; 48 | }; 49 | 50 | struct tcp_timerset_t { 51 | uint64_t current_time_ms; 52 | tcp_timer_t *used_list; 53 | tcp_timer_t *free_list; 54 | tcp_timer_t pool[TCP_MAX_TIMERS]; 55 | }; 56 | 57 | void tcp_timerset_step(tcp_timerset_t *set, size_t ms); 58 | void tcp_timerset_init(tcp_timerset_t *set); 59 | void tcp_timerset_free(tcp_timerset_t *set); 60 | void tcp_timer_disable(tcp_timer_t *timer); 61 | tcp_timer_t *tcp_timer_create(tcp_timerset_t *set, size_t ms, 62 | const char *name, 63 | void (*callback)(void*), void *data); 64 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | 2 | ifeq ($(OS),Windows_NT) 3 | OSTAG = WIN 4 | EXT = .exe 5 | CMAKE_GENERATOR = "MinGW Makefiles" 6 | else 7 | UNAME_S := $(shell uname -s) 8 | ifeq ($(UNAME_S),Linux) 9 | OSTAG = LIN 10 | EXT = 11 | CMAKE_GENERATOR = "Unix Makefiles" 12 | else ifeq ($(UNAME_S),Darwin) 13 | $(error OSX isn't supported) 14 | OSNAME = osx 15 | endif 16 | endif 17 | 18 | AR = ar$(EXT) 19 | CC = gcc$(EXT) 20 | CXX = g++$(EXT) 21 | 22 | SRCDIR = src 23 | OBJDIR = obj 24 | OUTDIR = out 25 | 26 | CFILES = $(wildcard $(SRCDIR)/*.c) 27 | HFILES = $(wildcard $(SRCDIR)/*.h) 28 | OFILES = $(patsubst $(SRCDIR)/%.c, $(OBJDIR)/%.o, $(CFILES)) 29 | 30 | CFLAGS_WIN = 31 | CFLAGS_LIN = -pthread -fsanitize=address,undefined 32 | CFLAGS = -Wall -Wextra -g $(CFLAGS_$(OSTAG)) -DTCP_DEBUG #-DMICROTCP_DEBUG 33 | 34 | LIBFILE = $(OUTDIR)/libmicrotcp.a 35 | 36 | TAP_AFILES = $(OUTDIR)/libtuntap.a 37 | TAP_HFILES = $(OUTDIR)/tuntap.h $(OUTDIR)/tuntap-export.h 38 | 39 | EXAMPLE_0 = $(OUTDIR)/http$(EXT) 40 | EXAMPLE_1 = $(OUTDIR)/echo$(EXT) 41 | 42 | EXAMPLE_CFILES_0 = $(wildcard examples/http/*.c) 43 | EXAMPLE_HFILES_0 = $(wildcard examples/http/*.h) 44 | 45 | EXAMPLE_CFILES_1 = examples/echo.c 46 | EXAMPLE_HFILES_1 = 47 | 48 | EXAMPLE_LFLAGS_WIN = -lws2_32 49 | EXAMPLE_LFLAGS_LIN = 50 | EXAMPLE_LFLAGS = -lmicrotcp -ltuntap $(EXAMPLE_LFLAGS_$(OSTAG)) 51 | 52 | .PHONY: all exm lib clean 53 | 54 | all: lib exm 55 | 56 | exm: $(EXAMPLE_0) $(EXAMPLE_1) 57 | 58 | lib: $(LIBFILE) $(TAP_HFILES) $(TAP_AFILES) 59 | cp $(SRCDIR)/microtcp.h $(OUTDIR)/microtcp.h 60 | 61 | $(OBJDIR)/%.o: $(SRCDIR)/%.c $(HFILES) $(TAP_HFILES) 62 | $(CC) -c -o $@ $< $(CFLAGS) -I$(OUTDIR) 63 | 64 | $(LIBFILE): $(OFILES) 65 | $(AR) rcs $@ $^ 66 | 67 | $(TAP_AFILES) $(TAP_HFILES): 68 | cd 3p/libtuntap/ && \ 69 | mkdir -p build && \ 70 | cd build && \ 71 | cmake .. -G $(CMAKE_GENERATOR) -DBUILD_TESTING=OFF -DCMAKE_C_COMPILER=$(CC) -DCMAKE_CXX_COMPILER=$(CXX) -DCMAKE_BUILD_TYPE=Debug && \ 72 | make 73 | cp 3p/libtuntap/build/lib/libtuntap.a $(OUTDIR)/libtuntap.a 74 | cp 3p/libtuntap/tuntap.h $(OUTDIR)/tuntap.h 75 | cp 3p/libtuntap/build/tuntap-export.h $(OUTDIR)/tuntap-export.h 76 | 77 | $(EXAMPLE_0): lib $(EXAMPLE_CFILES_0) $(EXAMPLE_HFILES_0) 78 | $(CC) -o $@ $(EXAMPLE_CFILES_0) $(CFLAGS) $(EXAMPLE_LFLAGS) -I$(OUTDIR) -L$(OUTDIR) 79 | 80 | $(EXAMPLE_1): lib $(EXAMPLE_CFILES_1) $(EXAMPLE_HFILES_1) 81 | $(CC) -o $@ $(EXAMPLE_CFILES_1) $(CFLAGS) $(EXAMPLE_LFLAGS) -I$(OUTDIR) -L$(OUTDIR) 82 | 83 | clean: 84 | rm -fr $(OBJDIR) $(OUTDIR) 3p/libtuntap/build 85 | mkdir obj out 86 | -------------------------------------------------------------------------------- /src/ip.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2024 Francesco Cozzuto 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include "defs.h" 30 | #include "icmp.h" 31 | 32 | #define IP_PLUGGED_PROTOCOLS_MAX 4 33 | 34 | typedef struct { 35 | uint8_t header_length_or_version1: 4; // Header length when little endian 36 | uint8_t header_length_or_version2: 4; // Header length when big endian 37 | uint8_t type_of_service; 38 | uint16_t total_length; 39 | uint16_t id; 40 | uint16_t fragment_offset; 41 | uint8_t time_to_live; 42 | uint8_t protocol; 43 | uint16_t checksum; 44 | uint32_t src_ip; 45 | uint32_t dst_ip; 46 | char payload[]; 47 | } __attribute__((packed)) ip_packet_t; 48 | static_assert(sizeof(ip_packet_t) == 20); 49 | 50 | typedef enum { 51 | IP_PROTOCOL_ICMP = 1, 52 | IP_PROTOCOL_TCP = 6, 53 | IP_PROTOCOL_UDP = 17, 54 | } ip_protocol_t; 55 | 56 | typedef struct { 57 | uint8_t protocol; 58 | void *data; 59 | void (*process_packet)(void*, ip_address_t, const void*, size_t); 60 | } ip_plugged_protocol_t; 61 | 62 | typedef struct { 63 | ip_address_t ip; 64 | 65 | uint32_t next_id; 66 | 67 | void *output_ptr; 68 | size_t output_max; 69 | 70 | icmp_state_t icmp_state; 71 | 72 | void *send_data; 73 | void (*send)(void*, ip_address_t, size_t); 74 | 75 | size_t plugged_protocols_count; 76 | ip_plugged_protocol_t plugged_protocols[IP_PLUGGED_PROTOCOLS_MAX]; 77 | } ip_state_t; 78 | 79 | void ip_ms_passed(ip_state_t *state, size_t ms); 80 | void ip_change_output_buffer(ip_state_t *state, void *ptr, size_t max); 81 | void ip_init(ip_state_t *state, ip_address_t ip, void *send_data, void (*send)(void*, ip_address_t, size_t)); 82 | void ip_free(ip_state_t *state); 83 | int ip_send(ip_state_t *state, ip_protocol_t protocol, ip_address_t dst, bool no_fragm, const void *src, size_t len); 84 | int ip_send_2(ip_state_t *state, ip_protocol_t protocol, ip_address_t dst, bool no_fragm, const slice_t *slices, size_t num_slices); 85 | void ip_process_packet(ip_state_t *state, const void *packet, size_t len); 86 | bool ip_plug_protocol(ip_state_t *ip_state, uint8_t protocol, void *data, void (*process_packet)(void *data, ip_address_t sender, const void *packet, size_t len)); 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MicroTCP 2 | MicroTCP is a TCP/IP network stack I started building as a learning exercise while attending the Computer Networking course at the Università degli Studi di Napoli Federico II. It's just a hobby project and is intended to just be a minimal, yet complete, implementation. 3 | 4 | At this moment MicroTCP implements ARP (RFC 826, complete), IPv4 (no fragmentation), ICMP (minimum necessary to reply to pings) and TCP (complete but not stress-tested). Note that "complete" should not be intended as "fully compliant" but just as a measure of progress on all of the major features. For instance, it's complete enough to handle HTTP traffic on a local network (Look into examples/microhttp to know more). 5 | 6 | ## Where does it run? 7 | MicroTCP can run on Windows and Linux alongside the OS's network stack. To route the network traffic to MicroTCP, the process running it behaves as a virtual host with its own IP address. This is done using a TAP device, which comes built-in on Linux and needs to be installed on Windows. It should be very easy to adapt MicroTCP to run on microcontrollers but haven't tried yet. The dream is to serve my [blog](https://cozis.github.io/) from an STM32 board! 8 | 9 | ## Build and Install 10 | If you are on Windows, you need to install the TAP driver provided by OpenVPN and instanciate a virtual NIC so that MicroTCP can connect to it when started. To build the project from source, make sure you cloned the repository with submodules 11 | ```sh 12 | git clone https://github.com/cozis/microtcp.git --recursive 13 | ``` 14 | and then run 15 | ```sh 16 | make 17 | ``` 18 | You'll need both `make` and `cmake` for it to work. If all goes well, you'll find the library files `libtuntap.a`, `libmicrotcp.a` and header files `tuntap.h`, `tuntap-export.h`, `microtcp.h` in `out/`. 19 | 20 | ## Usage 21 | MicroTCP's uses the usual socket interface any network programmer is familiar with, the main difference being you need to explicitly instanciate the network stack and pass its handle around. 22 | 23 | Here's a simple echo server that shows the basic usage: 24 | 25 | ```c 26 | #include 27 | 28 | int main(void) 29 | { 30 | microtcp_t *mtcp = microtcp_create("10.0.0.5", "10.0.0.4", NULL, NULL); 31 | if (mtcp == NULL) 32 | return -1; // Couldn't create MicroTCP instance 33 | 34 | uint16_t port = 8081; 35 | microtcp_socket_t *server = microtcp_open(mtcp, port); 36 | if (server == NULL) { 37 | microtcp_destroy(mtcp); 38 | return -1; 39 | } 40 | 41 | while (1) { 42 | 43 | microtcp_socket_t *client = microtcp_accept(server); 44 | if (client == NULL) 45 | break; 46 | 47 | char buffer[1024]; 48 | int num = microtcp_recv(client, buffer, sizeof(buffer)); 49 | if (num > 0) { 50 | microtcp_send(client, "echo: ", 6); 51 | microtcp_send(client, buffer, num); 52 | } 53 | microtcp_close(client); 54 | } 55 | 56 | microtcp_close(server); 57 | microtcp_destroy(mtcp); 58 | return 0; 59 | } 60 | ``` 61 | This should be pretty straight forward to understand. One thing may be worth noting is that `microtcp_open` behaves as the BSD's `socket+bind+listen` all at once to setup a listening TCP server. 62 | 63 | There is more than one way to set up the stack, the main way being `microtcp_create` which creates a virtual network inferface on the host OS with IP 10.0.0.5/24 and a virtual host for the MicroTCP process at 10.0.0.4/24. You can open Wireshark on the virtual NIC to inspect the traffic between the host and the process. 64 | 65 | It's also possible to configure the stack using the `microtcp_create_using_callbacks`, which lets you explicitly provide the input L2 frames to it and receive the frames in a buffer. This is how one would configure the stack to run on a microcontroller. 66 | 67 | Each instance of MicroTCP (without considering the callbacks) is completely isolated from the others, therefore, if your specific callback implementation allows it, you can have as many instances as you like! 68 | 69 | ## Testing 70 | There is still no testing infractructure. The way I'm testing it is by setting up an HTTP or echo server and stressing it until something breaks while capturing what happened using Wireshark. -------------------------------------------------------------------------------- /src/utils.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2024 Francesco Cozzuto 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | 26 | #include 27 | #include // rand 28 | #include 29 | #include "utils.h" 30 | #include "endian.h" 31 | 32 | static bool is_hex_digit(char c) 33 | { 34 | return (c >= '0' && c <= '9') 35 | || (c >= 'a' && c <= 'f') 36 | || (c >= 'A' && c <= 'F'); 37 | } 38 | 39 | static int int_from_hex_digit(char c) 40 | { 41 | assert(is_hex_digit(c)); 42 | if (c >= 'A' && c <= 'F') 43 | return c - 'A' + 10; 44 | if (c >= 'a' && c <= 'f') 45 | return c - 'a' + 10; 46 | return c - '0'; 47 | } 48 | 49 | bool parse_mac(const char *src, size_t len, mac_address_t *mac) 50 | { 51 | if (src == NULL || len != 17 52 | || !is_hex_digit(src[0]) 53 | || !is_hex_digit(src[1]) 54 | || src[2] != ':' 55 | || !is_hex_digit(src[3]) 56 | || !is_hex_digit(src[4]) 57 | || src[5] != ':' 58 | || !is_hex_digit(src[6]) 59 | || !is_hex_digit(src[7]) 60 | || src[8] != ':' 61 | || !is_hex_digit(src[9]) 62 | || !is_hex_digit(src[10]) 63 | || src[11] != ':' 64 | || !is_hex_digit(src[12]) 65 | || !is_hex_digit(src[13]) 66 | || src[14] != ':' 67 | || !is_hex_digit(src[15]) 68 | || !is_hex_digit(src[16])) 69 | return false; 70 | 71 | static const char max_char_map[] = "0123456789ABCDEF"; 72 | 73 | if (mac) 74 | for (int i = 0; i < 6; i++) { 75 | int u = int_from_hex_digit(src[i * 3 + 0]); 76 | int v = int_from_hex_digit(src[i * 3 + 1]); 77 | mac->data[i] = (max_char_map[u] << 4) | max_char_map[v]; 78 | } 79 | 80 | return true; 81 | } 82 | 83 | mac_address_t generate_random_mac() 84 | { 85 | mac_address_t mac = { 86 | .data = { 87 | rand() & 0xff, 88 | rand() & 0xff, 89 | rand() & 0xff, 90 | rand() & 0xff, 91 | rand() & 0xff, 92 | rand() & 0xff, 93 | }, 94 | }; 95 | return mac; 96 | } 97 | 98 | 99 | bool parse_ip(const char *ip, ip_address_t *parsed_ip) 100 | { 101 | size_t len = strlen(ip); 102 | size_t i = 0; 103 | 104 | uint32_t value = 0; 105 | 106 | for (size_t k = 0; k < 4; k++) { 107 | if (i == len || !isdigit(ip[i])) 108 | return false; 109 | int n = 0; // Used to represent a byte, but it's larger 110 | // to detect overflows. 111 | do { 112 | // Convert character to number 113 | int digit = ip[i] - '0'; 114 | if (n > (UINT8_MAX - digit)/10) 115 | // Adding this digit would make the 116 | // byte overflow, so it can't be part 117 | // of the octet. 118 | break; 119 | n = n * 10 + digit; 120 | i++; 121 | } while (i < len && isdigit(ip[i])); 122 | 123 | assert(n >= 0 && n <= UINT8_MAX); 124 | value = (value << 8) | (uint8_t) n; 125 | 126 | // If this isn't the last octet and there is no 127 | // dot following it, the address is invalid. 128 | if (k < 3) { 129 | if (i == len || ip[i] != '.') 130 | return false; 131 | i++; // Consume the dot. 132 | } 133 | } 134 | if (i < len) 135 | // source string contains something 136 | // other than the address in it. 137 | return false; 138 | 139 | *parsed_ip = cpu_to_net_u32(value); 140 | return true; 141 | } -------------------------------------------------------------------------------- /src/microtcp.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2024 Francesco Cozzuto 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | 26 | #ifndef MICROTCP_H 27 | #define MICROTCP_H 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | typedef struct microtcp_t microtcp_t; 35 | typedef struct microtcp_socket_t microtcp_socket_t; 36 | 37 | #define MICROTCP_MAX_BUFFERS 8 38 | #define MICROTCP_MAX_SOCKETS 32 39 | #define MICROTCP_MAX_MUX_ENTRIES 32 40 | 41 | typedef enum { 42 | 43 | MICROTCP_ERRCODE_NONE = 0, 44 | 45 | MICROTCP_ERRCODE_NOCLEAR, 46 | 47 | // Returned by microtcp_open and microtcp_accept 48 | MICROTCP_ERRCODE_SOCKETLIMIT, 49 | 50 | // Returned by microtcp_open 51 | MICROTCP_ERRCODE_TCPERROR, 52 | MICROTCP_ERRCODE_BADCONDVAR, 53 | 54 | // Returned by microtcp_accept 55 | MICROTCP_ERRCODE_NOTLISTENER, 56 | MICROTCP_ERRCODE_CANTBLOCK, 57 | 58 | // Returned by microtcp_recv and microtcp_send 59 | MICROTCP_ERRCODE_NOTCONNECTION, 60 | 61 | // Returned by microtcp_accept, microtcp_recv and microtcp_send 62 | MICROTCP_ERRCODE_WOULDBLOCK, 63 | 64 | } microtcp_errcode_t; 65 | 66 | typedef struct { 67 | void *data; 68 | void (*free)(void *data); 69 | int (*send)(void *data, const void *src, size_t len); 70 | int (*recv)(void *data, void *dst, size_t len); 71 | } microtcp_callbacks_t; 72 | 73 | bool microtcp_callbacks_create_for_tap(const char *ip, const char *mac, microtcp_callbacks_t *callbacks); 74 | microtcp_t *microtcp_create(const char *tap_ip, const char *stack_ip, const char *tap_mac, const char *stack_mac); 75 | microtcp_t *microtcp_create_using_callbacks(const char *ip, const char *mac, microtcp_callbacks_t callbacks); 76 | void microtcp_destroy(microtcp_t *mtcp); 77 | microtcp_errcode_t microtcp_get_error(microtcp_t *mtcp); 78 | void microtcp_clear_error(microtcp_t *mtcp); 79 | 80 | microtcp_errcode_t microtcp_get_socket_error(microtcp_socket_t *sock); 81 | void microtcp_clear_socket_error(microtcp_socket_t *sock); 82 | 83 | const char *microtcp_strerror(microtcp_errcode_t errcode); 84 | microtcp_socket_t *microtcp_open(microtcp_t *mtcp, uint16_t port); 85 | microtcp_socket_t *microtcp_accept(microtcp_socket_t *socket); 86 | void microtcp_close(microtcp_socket_t *socket); 87 | 88 | int microtcp_send(microtcp_socket_t *socket, const void *src, size_t len); 89 | int microtcp_recv(microtcp_socket_t *socket, void *dst, size_t len); 90 | 91 | bool microtcp_step(microtcp_t *mtcp); 92 | void microtcp_set_blocking(microtcp_socket_t *socket, bool block); 93 | bool microtcp_process_packet(microtcp_t *mtcp, const void *packet, size_t len); 94 | 95 | typedef enum { 96 | MICROTCP_MUX_ACCEPT = 1 << 0, 97 | MICROTCP_MUX_RECV = 1 << 1, 98 | MICROTCP_MUX_SEND = 1 << 2, 99 | } microtcp_muxeventid_t; 100 | 101 | typedef struct { 102 | void *userp; 103 | int events; 104 | microtcp_socket_t *socket; 105 | } microtcp_muxevent_t; 106 | 107 | typedef struct microtcp_mux_t microtcp_mux_t; 108 | microtcp_mux_t *microtcp_mux_create(microtcp_t *mtcp); 109 | void microtcp_mux_destroy(microtcp_mux_t *mux); 110 | bool microtcp_mux_register(microtcp_mux_t *mux, microtcp_socket_t *sock, int events, void *userp); 111 | bool microtcp_mux_unregister(microtcp_mux_t *mux, microtcp_socket_t *sock, int events); 112 | bool microtcp_mux_wait(microtcp_mux_t *mux, microtcp_muxevent_t *ev); 113 | 114 | #endif -------------------------------------------------------------------------------- /src/icmp.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * MIT License 4 | * 5 | * Copyright (c) 2024 Francesco Cozzuto 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in all 15 | * copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | * SOFTWARE. 24 | * 25 | */ 26 | 27 | #include 28 | #include "endian.h" 29 | #include "icmp.h" 30 | 31 | #ifdef ICMP_DEBUG 32 | #include 33 | #define ICMP_DEBUG_LOG(fmt, ...) fprintf(stderr, "ICMP :: " fmt "\n", ## __VA_ARGS__) 34 | #else 35 | #define ICMP_DEBUG_LOG(...) 36 | #endif 37 | 38 | typedef enum { 39 | ICMP_TYPE_ECHO_REPLY = 0, 40 | ICMP_TYPE_ECHO_REQUEST = 8, 41 | } icmp_type_t; 42 | 43 | typedef struct { 44 | uint8_t type; 45 | uint8_t code; 46 | uint16_t checksum; 47 | uint16_t id_no; 48 | uint16_t seq_no; 49 | uint8_t data[]; 50 | } icmp_message_echo_t; 51 | 52 | typedef struct { 53 | uint8_t type; 54 | uint8_t code; 55 | uint16_t checksum; 56 | uint8_t data[]; 57 | } icmp_message_generic_t; 58 | 59 | void icmp_change_output_buffer(icmp_state_t *state, void *ptr, size_t len) 60 | { 61 | state->output_ptr = ptr; 62 | state->output_len = len; 63 | } 64 | 65 | void icmp_init(icmp_state_t *state, void *send_data, void (*send)(void*, ip_address_t, size_t)) 66 | { 67 | state->output_ptr = NULL; 68 | state->output_len = 0; 69 | state->send_data = send_data; 70 | state->send = send; 71 | } 72 | 73 | void icmp_free(icmp_state_t *state) 74 | { 75 | (void) state; 76 | } 77 | 78 | static uint16_t calculate_checksum_icmp(const void *src, size_t len) 79 | { 80 | assert((len & 1) == 0); 81 | 82 | const uint16_t *src2 = src; 83 | 84 | uint32_t sum = 0xffff; 85 | for (size_t i = 0; i < len/2; i++) { 86 | sum += net_to_cpu_u16(src2[i]); 87 | if (sum > 0xffff) 88 | sum -= 0xffff; 89 | } 90 | 91 | return cpu_to_net_u16(~sum); 92 | } 93 | 94 | void icmp_process_packet(icmp_state_t *state, ip_address_t ip, const void *src, size_t len) 95 | { 96 | if (len < sizeof(icmp_message_generic_t)) 97 | return; 98 | 99 | const icmp_message_generic_t *packet = src; 100 | 101 | switch (packet->type) { 102 | case ICMP_TYPE_ECHO_REQUEST: 103 | { 104 | if (len < sizeof(icmp_message_echo_t)) 105 | return; 106 | 107 | const icmp_message_echo_t *echo_request = (icmp_message_echo_t*) packet; 108 | 109 | if (calculate_checksum_icmp(echo_request, len)) { 110 | ICMP_DEBUG_LOG("Dropping ICMP message with invalid checksum"); 111 | return; 112 | } 113 | 114 | if (state->output_ptr == NULL || state->output_len < len) { 115 | ICMP_DEBUG_LOG("Ignoring ECHO REQUEST because the output buffer " 116 | "is too small for an ECHO REPLY (have %d, need %d)", 117 | (int) state->output_len, (int) len); 118 | return; 119 | } 120 | 121 | icmp_message_echo_t *echo_reply = state->output_ptr; 122 | 123 | echo_reply->type = ICMP_TYPE_ECHO_REPLY; 124 | echo_reply->code = 0; 125 | echo_reply->checksum = 0; 126 | echo_reply->id_no = echo_request->id_no; 127 | echo_reply->seq_no = echo_request->seq_no; 128 | memcpy(echo_reply->data, echo_request->data, len - sizeof(icmp_message_echo_t)); 129 | 130 | echo_reply->checksum = calculate_checksum_icmp(echo_reply, len); 131 | 132 | ICMP_DEBUG_LOG("Replying to echo request"); 133 | state->send(state->send_data, ip, len); 134 | } 135 | break; 136 | 137 | default: 138 | // Unsupported ICMP message 139 | break; 140 | } 141 | } -------------------------------------------------------------------------------- /src/arp.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2024 Francesco Cozzuto 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include "defs.h" 30 | 31 | #define ARP_MAX_PENDING_REQUESTS 32 32 | #define ARP_TRANSLATION_TABLE_SIZE 128 33 | 34 | typedef enum { 35 | ARP_RESOLUTION_OK, 36 | ARP_RESOLUTION_FAILED, 37 | ARP_RESOLUTION_TIMEOUT, 38 | } arp_resolution_status_t; 39 | 40 | typedef struct arp_translation_table_entry_t arp_translation_table_entry_t; 41 | struct arp_translation_table_entry_t { 42 | arp_translation_table_entry_t *prev; 43 | arp_translation_table_entry_t *next; 44 | mac_address_t mac; 45 | ip_address_t ip; 46 | uint64_t timeout; 47 | }; 48 | 49 | typedef struct { 50 | uint64_t time; 51 | arp_translation_table_entry_t *used_list_head; 52 | arp_translation_table_entry_t *used_list_tail; 53 | arp_translation_table_entry_t *free_list; 54 | arp_translation_table_entry_t entries[ARP_TRANSLATION_TABLE_SIZE]; 55 | } arp_translation_table_t; 56 | 57 | typedef struct arp_pending_request_t arp_pending_request_t; 58 | struct arp_pending_request_t { 59 | arp_pending_request_t *prev; 60 | arp_pending_request_t *next; 61 | ip_address_t ip; 62 | uint64_t timeout; 63 | void *callback_data; 64 | void (*callback)(void*, arp_resolution_status_t status, mac_address_t); 65 | }; 66 | 67 | typedef enum { 68 | ARP_HARDWARE_ETHERNET = 1, 69 | } arp_hardware_type; 70 | 71 | typedef enum { 72 | ARP_PROTOCOL_IP = 0x800, 73 | } arp_protocol_type; 74 | 75 | typedef enum { 76 | ARP_OPERATION_REQUEST = 1, 77 | ARP_OPERATION_REPLY = 2, 78 | } arp_operation_t; 79 | 80 | typedef struct __attribute__((__packed__)) { 81 | uint16_t hardware_type; 82 | uint16_t protocol_type; 83 | uint8_t hardware_len; 84 | uint8_t protocol_len; 85 | uint16_t operation_type; 86 | mac_address_t sender_hardware_address; 87 | ip_address_t sender_protocol_address; 88 | mac_address_t target_hardware_address; 89 | ip_address_t target_protocol_address; 90 | } arp_packet_t; 91 | 92 | static_assert(offsetof(arp_packet_t, hardware_type) == 0); 93 | static_assert(offsetof(arp_packet_t, protocol_type) == 2); 94 | static_assert(offsetof(arp_packet_t, hardware_len) == 4); 95 | static_assert(offsetof(arp_packet_t, protocol_len) == 5); 96 | static_assert(offsetof(arp_packet_t, operation_type) == 6); 97 | static_assert(offsetof(arp_packet_t, sender_hardware_address) == 8); 98 | static_assert(offsetof(arp_packet_t, sender_protocol_address) == 14); 99 | static_assert(offsetof(arp_packet_t, target_hardware_address) == 18); 100 | static_assert(offsetof(arp_packet_t, target_protocol_address) == 24); 101 | static_assert(sizeof(arp_packet_t) == 28); 102 | 103 | typedef struct { 104 | 105 | uint64_t time; 106 | uint64_t cache_timeout; 107 | uint64_t request_timeout; 108 | 109 | arp_packet_t *output; 110 | 111 | void *send_data; 112 | void (*send)(void *send_data, mac_address_t dest_mac); 113 | 114 | ip_address_t self_ip; 115 | mac_address_t self_mac; 116 | arp_translation_table_t table; 117 | 118 | arp_pending_request_t *pending_request_free_list; 119 | arp_pending_request_t *pending_request_used_list; 120 | arp_pending_request_t *pending_request_used_tail; 121 | arp_pending_request_t pending_request_pool[ARP_MAX_PENDING_REQUESTS]; 122 | } arp_state_t; 123 | 124 | typedef enum { 125 | ARP_PROCESS_RESULT_HWARENOTSUPP, 126 | ARP_PROCESS_RESULT_PROTONOTSUPP, 127 | ARP_PROCESS_RESULT_INVALID, 128 | ARP_PROCESS_RESULT_OK, 129 | } arp_process_result_t; 130 | 131 | void arp_init(arp_state_t *state, 132 | ip_address_t ip, 133 | mac_address_t mac, 134 | void *send_data, 135 | void (*send)(void*, mac_address_t)); 136 | 137 | void arp_free(arp_state_t *state); 138 | 139 | arp_process_result_t 140 | arp_process_packet(arp_state_t *state, 141 | const void *packet, 142 | size_t len); 143 | 144 | void arp_resolve_mac(arp_state_t *state, 145 | ip_address_t ip, 146 | void *userp, 147 | void (*callback)(void*, arp_resolution_status_t, mac_address_t)); 148 | 149 | void arp_ms_passed(arp_state_t *state, size_t ms); 150 | void arp_change_output_buffer(arp_state_t *state, void *ptr, size_t max); 151 | -------------------------------------------------------------------------------- /src/tcp_timer.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2024 Francesco Cozzuto 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | 26 | #include 27 | #include 28 | #include "tcp_timer.h" 29 | 30 | #ifdef TCP_DEBUG 31 | #include 32 | #define TCP_DEBUG_LOG(fmt, ...) fprintf(stderr, "TCP-TIMERS :: " fmt "\n", ## __VA_ARGS__) 33 | #else 34 | #define TCP_DEBUG_LOG(...) {} 35 | #endif 36 | 37 | void tcp_timerset_init(tcp_timerset_t *set) 38 | { 39 | if (TCP_MAX_TIMERS == 0) 40 | set->free_list = NULL; 41 | else { 42 | for (size_t i = 0; i < TCP_MAX_TIMERS-1; i++) 43 | set->pool[i].next = set->pool + i+1; 44 | set->pool[TCP_MAX_TIMERS-1].next = NULL; 45 | set->free_list = set->pool; 46 | } 47 | set->used_list = NULL; 48 | set->current_time_ms = 0; 49 | } 50 | 51 | void tcp_timerset_free(tcp_timerset_t *set) 52 | { 53 | (void) set; 54 | } 55 | 56 | void tcp_timer_disable(tcp_timer_t *timer) 57 | { 58 | tcp_timerset_t *set = timer->set; 59 | 60 | TCP_DEBUG_LOG("Timer %d disabled", (int) (timer - set->pool)); 61 | 62 | // Pop the timer from the used list 63 | if (timer->prev) 64 | timer->prev->next = timer->next; 65 | else 66 | set->used_list = timer->next; 67 | if (timer->next) 68 | timer->next->prev = timer->prev; 69 | 70 | // Push it into the free list 71 | timer->prev = NULL; 72 | timer->next = set->free_list; 73 | set->free_list = timer; 74 | } 75 | 76 | tcp_timer_t *tcp_timer_create(tcp_timerset_t *set, size_t ms, 77 | const char *name, 78 | void (*callback)(void*), void *data) 79 | { 80 | assert(callback); 81 | 82 | tcp_timer_t *timer = set->free_list; 83 | if (timer == NULL) 84 | // Out of timers! This is really bad. 85 | // What can be done to mitigate this? 86 | return NULL; 87 | 88 | set->free_list = timer->next; 89 | // NOTE: Since the free list is singly linked, there's 90 | // no need to change the prev member of the new 91 | // first item of the free list. 92 | 93 | timer->set = set; 94 | timer->set_time = set->current_time_ms; 95 | timer->trg_time = ms; 96 | timer->deadline = set->current_time_ms + ms; 97 | timer->data = data; 98 | timer->callback = callback; 99 | strncpy(timer->name, name, sizeof(timer->name)); 100 | timer->name[TCP_MAX_TIMER_NAME] = '\0'; 101 | 102 | // Insert the timer structure into the timer list 103 | // in an orderly fashon 104 | if (set->used_list == NULL) { 105 | // This is the first timer of the list 106 | set->used_list = timer; 107 | timer->prev = NULL; 108 | timer->next = NULL; 109 | } else if (timer->deadline < set->used_list->deadline) { 110 | // This timer should be the first of the list 111 | timer->prev = NULL; 112 | timer->next = set->used_list; 113 | set->used_list->prev = timer; 114 | set->used_list = timer; 115 | } else { 116 | // The timer isn't the first of the list. We need to 117 | // determine at which position it should be inserted 118 | // 119 | // Scan the list until the timer that needs to come 120 | // after the inserted one is reached 121 | tcp_timer_t *cursor = set->used_list; 122 | while (cursor->next && cursor->next->deadline <= timer->deadline) 123 | cursor = cursor->next; 124 | tcp_timer_t *prev = cursor; 125 | cursor = cursor->next; 126 | 127 | if (cursor) { 128 | // The cursor points to the element that needs to 129 | // come after. Since we know the inserted item won't 130 | // be the first, then this one isn't the first element 131 | // of the list either, so its "prev" isn't NULL. 132 | assert(cursor != set->used_list); 133 | assert(cursor->prev); 134 | timer->prev = cursor->prev; 135 | timer->next = cursor; 136 | cursor->prev->next = timer; 137 | cursor->prev = timer; 138 | 139 | } else { 140 | // No element that needs to come after was found, 141 | // so its position should be the last. 142 | prev->next = timer; 143 | timer->prev = prev; 144 | timer->next = NULL; 145 | } 146 | } 147 | TCP_DEBUG_LOG("Timer %d created (%s)", (int) (timer - set->pool), name); 148 | return timer; 149 | } 150 | 151 | void tcp_timerset_step(tcp_timerset_t *set, size_t ms) 152 | { 153 | set->current_time_ms += ms; 154 | 155 | if (set->used_list == NULL || set->used_list->deadline > set->current_time_ms) 156 | // No timeouts triggered 157 | return; 158 | 159 | tcp_timer_t *timedout_head = set->used_list; // We know that at least one timeout triggered 160 | tcp_timer_t *timedout_tail; // This has to be determined by the following loop 161 | 162 | // Scan through all of the timeouts that just triggered 163 | tcp_timer_t *timeout = set->used_list; 164 | do { 165 | 166 | if (timeout->deadline > set->current_time_ms) 167 | // This timeout didn't trigger, so the last 168 | // timed out timeout was the previous one. 169 | break; 170 | 171 | TCP_DEBUG_LOG("Timer %d triggered (deadline %d, current %d, set_time=%d, trg_time=%d)", (int) (timeout - set->pool), (int) timeout->deadline, (int) set->current_time_ms, (int) timeout->set_time, (int) timeout->trg_time); 172 | 173 | timeout->callback(timeout->data); 174 | 175 | timedout_tail = timeout; 176 | timeout = timeout->next; 177 | } while (timeout); 178 | 179 | // Now put the list of timed out timeouts back 180 | // into the free list 181 | if (timedout_tail->next) 182 | timedout_tail->next->prev = NULL; 183 | 184 | set->used_list = timedout_tail->next; 185 | 186 | timedout_tail->prev = NULL; 187 | timedout_tail->next = set->free_list; 188 | 189 | set->free_list = timedout_head; 190 | } -------------------------------------------------------------------------------- /src/tcp.h: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2024 Francesco Cozzuto 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | 26 | #include 27 | #include 28 | #include "defs.h" 29 | #include "tcp_timer.h" 30 | 31 | #define TCP_TIMEOUT_TIME_WAIT 1000 32 | //240000 33 | 34 | #define TCP_MAX_TIMEOUTS 1024 35 | #define TCP_MAX_LISTENERS 32 36 | #define TCP_MAX_SOCKETS 1024 37 | #define TCP_IBUFFER_SIZE 1024 38 | #define TCP_OBUFFER_SIZE 1024 39 | 40 | typedef struct tcp_state_t tcp_state_t; // Predeclare for cyclic references 41 | 42 | #define TCP_FLAG_FIN 0x01 43 | #define TCP_FLAG_SYN 0x02 44 | #define TCP_FLAG_RST 0x04 45 | #define TCP_FLAG_PUSH 0x08 46 | #define TCP_FLAG_ACK 0x10 47 | #define TCP_FLAG_URG 0x20 48 | 49 | typedef struct { 50 | uint16_t src_port; 51 | uint16_t dst_port; 52 | uint32_t seq_no; 53 | uint32_t ack_no; 54 | uint8_t offset1: 4; // When CPU is big endian 55 | uint8_t offset2: 4; // When CPU is little endian 56 | uint8_t flags; 57 | uint16_t window; 58 | uint16_t checksum; 59 | uint16_t urgent_pointer; 60 | char payload[]; 61 | } __attribute__((packed)) tcp_segment_t; 62 | static_assert(sizeof(tcp_segment_t) == 20); 63 | 64 | typedef struct tcp_connection_t tcp_connection_t; 65 | typedef struct tcp_listener_t tcp_listener_t; 66 | 67 | // After receiving a reset or close event, no 68 | // methods shall be called on the connection 69 | // object, not even to close it. 70 | typedef enum { 71 | TCP_CONNEVENT_CLOSE, 72 | TCP_CONNEVENT_RESET, 73 | TCP_CONNEVENT_RECV, 74 | TCP_CONNEVENT_SEND, 75 | } tcp_connevent_t; 76 | 77 | typedef enum { 78 | TCP_LISTENEVENT_ACCEPT, 79 | } tcp_listenevent_t; 80 | 81 | typedef void (*tcp_conneventcb_t)(void *data, tcp_connevent_t); 82 | typedef void (*tcp_listeneventcb_t)(void *data, tcp_listenevent_t); 83 | 84 | struct tcp_listener_t { 85 | tcp_state_t *state; 86 | tcp_listener_t *prev; 87 | tcp_listener_t *next; 88 | bool closed; // When a listener is closed while one or more connections that is 89 | // previously accepted are open, the structure isn't deallocated 90 | // but just marked lazily as "closed". A listener in the "closed-but-not-deallocated" 91 | // state will not accept new connections but will serve the ones 92 | // its still holding. 93 | // In this state the listener can be reopened by setting the "closed" 94 | // flag to true (and keeping the old connections intact). 95 | 96 | // Port the listener is listening onto 97 | uint16_t port; 98 | 99 | // Number of connection 100 | int count; 101 | 102 | // List of established and accepted connections 103 | tcp_connection_t *accepted; 104 | 105 | // List of connections which aren't in an established 106 | // state yet. 107 | tcp_connection_t *noestab; 108 | 109 | // Queue of connections ready to be accepted 110 | // (established but not accepted) 111 | tcp_connection_t *qhead; 112 | tcp_connection_t *qtail; 113 | 114 | tcp_listeneventcb_t cb_event; 115 | void *cb_data; 116 | }; 117 | 118 | typedef enum { 119 | TCP_STATE_CLOSED = 0, 120 | TCP_STATE_LISTEN, 121 | TCP_STATE_SYN_SENT, 122 | TCP_STATE_SYN_RCVD, 123 | TCP_STATE_ESTABLISHED, 124 | TCP_STATE_FIN_WAIT_1, 125 | TCP_STATE_FIN_WAIT_2, 126 | TCP_STATE_CLOSE_WAIT, 127 | TCP_STATE_LAST_ACK, 128 | TCP_STATE_TIME_WAIT, 129 | TCP_STATE_CLOSING, 130 | } tcp_connstate_t; 131 | 132 | struct tcp_connection_t { 133 | 134 | tcp_listener_t *listener; // Listener that accepted this connection 135 | tcp_connection_t *next; 136 | tcp_connection_t *prev; 137 | 138 | tcp_conneventcb_t cb_event; 139 | void *cb_data; 140 | 141 | tcp_connstate_t state; 142 | 143 | ip_address_t peer_ip; // Network byte order 144 | uint16_t peer_port; // CPU byte order 145 | 146 | // From RFC 6298, Section 2 147 | // To compute the current RTO, a TCP sender maintains two state 148 | // variables, SRTT (smoothed round-trip time) and RTTVAR (round-trip 149 | // time variation). In addition, we assume a clock granularity of G 150 | // seconds. 151 | float srtt; 152 | float rttvar; 153 | 154 | tcp_timer_t *retr_timer; 155 | tcp_timer_t *wait_timer; 156 | 157 | #ifdef TCP_DEBUG 158 | uint32_t init_peer_seq; // First requence number of the peer, in cpu byte order 159 | #endif 160 | 161 | // Send Sequence Space 162 | // 163 | // 1 2 3 4 164 | // ----------|----------|----------|---------- 165 | // SND.UNA SND.NXT SND.UNA 166 | // +SND.WND 167 | // 168 | // 1 - old sequence numbers which have been acknowledged 169 | // 2 - sequence numbers of unacknowledged data 170 | // 3 - sequence numbers allowed for new data transmission 171 | // 4 - future sequence numbers which are not yet allowed 172 | // 173 | // Receive Sequence Space 174 | // 175 | // 1 2 3 176 | // ----------|----------|---------- 177 | // RCV.NXT RCV.NXT 178 | // +RCV.WND 179 | // 180 | // (From RFC 793 section 3.2, // https://www.ietf.org/rfc/rfc793.txt) 181 | 182 | uint32_t rcv_unread; // It's the sequence number of the first 183 | // byte stored in the input buffer, such 184 | // that [rcv_next - rcv_unread] is the 185 | // number of bytes that the parent application 186 | // can read from the socket. 187 | 188 | uint32_t rcv_nxt; // RCV.NXT from RFC 793 189 | // It's the sequence number of the next 190 | // byte waiting to be received. 191 | 192 | uint32_t rcv_wnd; // RCV.WND from RFC 793 193 | // It's the size of the portion of input 194 | // buffer that's currently free. 195 | 196 | uint32_t snd_wnd; // SND.WND from RFC 793 197 | // It's the number of bytes stored in 198 | // the [out_buffer] output buffer, both 199 | // sent but not acknowledged and not sent. 200 | 201 | uint32_t snd_nxt; // SND.NXT from RFC 793 202 | // It's the sequence number of the first 203 | // not yet sent byte in the output buffer. 204 | // By subtracting [snd_una] from this value, 205 | // you get the amount of bytes sent out but 206 | // not yet acknowledged. 207 | 208 | uint32_t snd_una; // SND.UNA from RFC 793 209 | // It's the sequence number of the last 210 | // byte sent and acknowledged by the peer. 211 | 212 | uint32_t snd_wl1; // SND.WL1 from RFC 9293 213 | // segment sequence number used for last 214 | // window update. 215 | 216 | uint32_t snd_wl2; // SND.WL2 from RFC 9293 217 | // segment acknowledgment number used for 218 | // last window update. 219 | 220 | // If true, the next segment which will empty the 221 | // output buffer will contain a FIN. 222 | bool send_fin_when_fully_flushed; 223 | bool waiting_ack_for_syn; 224 | bool waiting_ack_for_fin; 225 | size_t oused; 226 | char idata[TCP_IBUFFER_SIZE]; 227 | char odata[TCP_OBUFFER_SIZE]; 228 | }; 229 | 230 | typedef struct { 231 | void *data; 232 | int (*send)(void *data, ip_address_t ip, const slice_t *slices, size_t num_slices); 233 | } tcp_callbacks_t; 234 | 235 | struct tcp_state_t { 236 | 237 | ip_address_t ip; 238 | tcp_callbacks_t callbacks; 239 | 240 | tcp_timerset_t timers; 241 | 242 | tcp_connection_t *free_connection_list; 243 | 244 | tcp_listener_t *used_listener_list; 245 | tcp_listener_t *free_listener_list; 246 | 247 | tcp_listener_t listener_pool[TCP_MAX_LISTENERS]; 248 | tcp_connection_t connection_pool[TCP_MAX_SOCKETS]; 249 | }; 250 | 251 | void tcp_init(tcp_state_t *tcp_state, ip_address_t ip, tcp_callbacks_t callbacks); 252 | void tcp_free(tcp_state_t *tcp_state); 253 | void tcp_ms_passed(tcp_state_t *state, size_t ms); 254 | void tcp_process_segment(tcp_state_t *state, ip_address_t sender, tcp_segment_t *segment, size_t len); 255 | tcp_listener_t *tcp_listener_create(tcp_state_t *state, uint16_t port, bool reuse, void *cb_data, tcp_listeneventcb_t func); 256 | void tcp_listener_destroy(tcp_listener_t *listener); 257 | tcp_connection_t *tcp_listener_accept(tcp_listener_t *listener, void *cb_data, tcp_conneventcb_t func); 258 | void tcp_connection_destroy(tcp_connection_t *connection); 259 | size_t tcp_connection_recv(tcp_connection_t *connection, void *dst, size_t len); 260 | size_t tcp_connection_send(tcp_connection_t *connection, const void *src, size_t len); 261 | -------------------------------------------------------------------------------- /src/ip.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2024 Francesco Cozzuto 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | 26 | #include 27 | #include "endian.h" 28 | #include "ip.h" 29 | 30 | #ifdef IP_DEBUG 31 | #include 32 | #define IP_DEBUG_LOG(fmt, ...) do { fprintf(stderr, "IP :: " fmt "\n", ## __VA_ARGS__); } while (0); 33 | #else 34 | #define IP_DEBUG_LOG(...) do { } while (0); 35 | #endif 36 | 37 | 38 | static uint16_t calculate_checksum_ip(const void *src, size_t len) 39 | { 40 | assert((len & 1) == 0); 41 | 42 | const uint16_t *src2 = src; 43 | 44 | uint32_t sum = 0xffff; 45 | for (size_t i = 0; i < len/2; i++) { 46 | sum += net_to_cpu_u16(src2[i]); 47 | if (sum > 0xffff) 48 | sum -= 0xffff; 49 | } 50 | 51 | return cpu_to_net_u16(~sum); 52 | } 53 | 54 | static ip_plugged_protocol_t * 55 | find_protocol_with_id(ip_state_t *ip_state, uint8_t protocol) 56 | { 57 | for (size_t i = 0; i < ip_state->plugged_protocols_count; i++) 58 | if (protocol == ip_state->plugged_protocols[i].protocol) 59 | return ip_state->plugged_protocols + i; 60 | return NULL; 61 | } 62 | 63 | bool ip_plug_protocol(ip_state_t *ip_state, uint8_t protocol, 64 | void *data, void (*process_packet)(void *data, ip_address_t sender, const void *packet, size_t len)) 65 | { 66 | if (protocol == IP_PROTOCOL_ICMP) 67 | return false; // Can't override default ICMP module 68 | 69 | ip_plugged_protocol_t *p = find_protocol_with_id(ip_state, protocol); 70 | 71 | if (!p) { 72 | if (ip_state->plugged_protocols_count == IP_PLUGGED_PROTOCOLS_MAX) 73 | return false; 74 | p = ip_state->plugged_protocols + ip_state->plugged_protocols_count++; 75 | } 76 | 77 | p->protocol = protocol; 78 | p->data = data; 79 | p->process_packet = process_packet; 80 | 81 | return true; 82 | } 83 | 84 | static bool is_packet_one_of_more_fragments(const ip_packet_t *packet) 85 | { 86 | size_t offset = net_to_cpu_u16(packet->fragment_offset) & 0x1FFF; 87 | bool more_fragments = net_to_cpu_u16(packet->fragment_offset) & 0x2000; 88 | return more_fragments || offset; 89 | } 90 | 91 | static void send_icmp_packet(void *data, ip_address_t ip, size_t len) 92 | { 93 | ip_state_t *ip_state = data; 94 | 95 | // The data was written in the output buffer 96 | ip_packet_t *packet = ip_state->output_ptr; // This changes every iteration 97 | 98 | int version = 4; 99 | int header_length = 5; 100 | if (cpu_is_little_endian()) { 101 | packet->header_length_or_version1 = header_length; 102 | packet->header_length_or_version2 = version; 103 | } else { 104 | packet->header_length_or_version1 = version; 105 | packet->header_length_or_version2 = header_length; 106 | } 107 | packet->type_of_service = 0; // ??? 108 | packet->total_length = cpu_to_net_u16(sizeof(ip_packet_t) + len); 109 | packet->id = ip_state->next_id++; 110 | packet->fragment_offset = 0; // ??? 111 | packet->time_to_live = 32; // ??? 112 | packet->protocol = IP_PROTOCOL_ICMP; 113 | packet->checksum = 0; // Temporary value 114 | packet->src_ip = ip_state->ip; 115 | packet->dst_ip = ip; 116 | 117 | packet->checksum = calculate_checksum_ip((uint16_t*) packet, 4 * header_length); 118 | 119 | ip_state->send(ip_state->send_data, ip, sizeof(ip_packet_t) + len); 120 | } 121 | 122 | void ip_init(ip_state_t *state, 123 | ip_address_t ip, 124 | void *send_data, 125 | void (*send)(void*, ip_address_t, size_t)) 126 | { 127 | state->ip = ip; 128 | state->next_id = 0; 129 | state->send_data = send_data; 130 | state->send = send; 131 | state->output_ptr = NULL; 132 | state->output_max = 0; 133 | state->plugged_protocols_count = 0; 134 | icmp_init(&state->icmp_state, state, send_icmp_packet); 135 | } 136 | 137 | void ip_free(ip_state_t *ip_state) 138 | { 139 | icmp_free(&ip_state->icmp_state); 140 | } 141 | 142 | void ip_change_output_buffer(ip_state_t *state, void *ptr, size_t max) 143 | { 144 | state->output_ptr = ptr; 145 | state->output_max = max; 146 | icmp_change_output_buffer(&state->icmp_state, (ip_packet_t*) ptr + 1, max - sizeof(ip_packet_t)); 147 | } 148 | 149 | void ip_ms_passed(ip_state_t *state, size_t ms) 150 | { 151 | (void) state; 152 | (void) ms; 153 | } 154 | 155 | int ip_send(ip_state_t *state, ip_protocol_t protocol, 156 | ip_address_t dst, bool no_fragm, 157 | const void *src, size_t len) 158 | { 159 | const slice_t slices[] = {{src, len}}; 160 | return ip_send_2(state, protocol, dst, no_fragm, slices, COUNT(slices)); 161 | } 162 | 163 | int ip_send_2(ip_state_t *state, ip_protocol_t protocol, ip_address_t dst, 164 | bool no_fragm, const slice_t *slices, size_t num_slices) 165 | { 166 | size_t total_len = 0; 167 | for (size_t i = 0; i < num_slices; i++) 168 | total_len += slices[i].len; 169 | 170 | size_t managed_payload = 0; 171 | 172 | while (managed_payload < total_len && (managed_payload == 0 || !no_fragm)) { 173 | 174 | if (state->output_ptr == NULL) { 175 | // Lower layers of the network stack didn't specify an output 176 | // buffer region. This may be because no memory is available. 177 | 178 | // If at least one byte was sent, return gracefully. 179 | // If no byte was sent return an error to the caller. 180 | if (managed_payload > 0) 181 | break; 182 | else 183 | return -1; 184 | } 185 | 186 | if (state->output_max <= sizeof(ip_packet_t)) 187 | // Output buffer provided by the lower layers of the stack 188 | // isn't big enough for an IP packet containing a single byte. 189 | return -1; 190 | 191 | size_t current_payload_limit = state->output_max - sizeof(ip_packet_t); 192 | size_t remaining_payload = total_len - managed_payload; 193 | size_t considered_payload = MIN(current_payload_limit, remaining_payload); 194 | 195 | ip_packet_t *packet = state->output_ptr; // This changes every iteration 196 | 197 | int version = 4; 198 | int header_length = 5; 199 | if (cpu_is_little_endian()) { 200 | packet->header_length_or_version1 = header_length; 201 | packet->header_length_or_version2 = version; 202 | } else { 203 | packet->header_length_or_version1 = version; 204 | packet->header_length_or_version2 = header_length; 205 | } 206 | packet->type_of_service = 0; // ??? 207 | packet->total_length = cpu_to_net_u16(sizeof(ip_packet_t) + considered_payload); 208 | packet->id = state->next_id++; 209 | packet->fragment_offset = 0; // ??? 210 | packet->time_to_live = 32; // ??? 211 | packet->protocol = protocol; 212 | packet->checksum = 0; // Temporary value 213 | packet->src_ip = state->ip; 214 | packet->dst_ip = dst; 215 | 216 | size_t copied_bytes = 0; 217 | size_t copied_slices = 0; 218 | while (copied_bytes < considered_payload) { 219 | size_t copying = MIN(slices[copied_slices].len, considered_payload - copied_bytes); 220 | memcpy(packet->payload + copied_bytes, slices[copied_slices].ptr, copying); 221 | copied_bytes += copying; 222 | copied_slices++; 223 | } 224 | 225 | packet->checksum = calculate_checksum_ip((uint16_t*) packet, 4 * header_length); 226 | 227 | // Sending updates the [state->output_ptr] and [state->output_len] 228 | state->send(state->send_data, dst, sizeof(ip_packet_t) + considered_payload); 229 | 230 | managed_payload += considered_payload; 231 | } 232 | 233 | return managed_payload; 234 | } 235 | 236 | void ip_process_packet(ip_state_t *ip_state, const void *packet, size_t len) 237 | { 238 | if (len < sizeof(ip_packet_t)) 239 | return; 240 | 241 | const ip_packet_t *packet2 = packet; 242 | 243 | int version; 244 | int header_length; 245 | 246 | if (cpu_is_little_endian()) { 247 | header_length = packet2->header_length_or_version1; 248 | version = packet2->header_length_or_version2; 249 | } else { 250 | version = packet2->header_length_or_version1; 251 | header_length = packet2->header_length_or_version2; 252 | } 253 | 254 | if (version != 4 || header_length < 5) { 255 | IP_DEBUG_LOG("Only supported IPv4 (received %d) with no options", version); 256 | return; 257 | } 258 | 259 | size_t option_count = header_length - sizeof(ip_packet_t)/4; 260 | if (option_count > 0) { 261 | // TODO: Handle IP options 262 | return; 263 | } 264 | 265 | if (is_packet_one_of_more_fragments(packet2)) { 266 | IP_DEBUG_LOG("Not supporting IP fragmentation"); 267 | return; 268 | } 269 | 270 | if (calculate_checksum_ip((uint16_t*) packet2, 4 * header_length)) { 271 | IP_DEBUG_LOG("Dropping IP packet with invalid checksum"); 272 | return; 273 | } 274 | /* 275 | IP_DEBUG_LOG("Received packet for %d.%d.%d.%d (I'm %d.%d.%d.%d)", 276 | ((uint8_t*) &packet2->dst_ip)[0], 277 | ((uint8_t*) &packet2->dst_ip)[1], 278 | ((uint8_t*) &packet2->dst_ip)[2], 279 | ((uint8_t*) &packet2->dst_ip)[3], 280 | ((uint8_t*) &ip_state->ip)[0], 281 | ((uint8_t*) &ip_state->ip)[1], 282 | ((uint8_t*) &ip_state->ip)[2], 283 | ((uint8_t*) &ip_state->ip)[3]); 284 | */ 285 | if (packet2->dst_ip != ip_state->ip) { 286 | // IP_DEBUG_LOG("Packet not for me"); 287 | return; 288 | } 289 | 290 | ip_plugged_protocol_t *handler = find_protocol_with_id(ip_state, packet2->protocol); 291 | 292 | const void *packet3_ptr = packet2+1; 293 | size_t packet3_len = net_to_cpu_u16(packet2->total_length) - sizeof(ip_packet_t); 294 | 295 | if (handler) 296 | handler->process_packet(handler->data, packet2->src_ip, packet3_ptr, packet3_len); 297 | else if (packet2->protocol == IP_PROTOCOL_ICMP) 298 | icmp_process_packet(&ip_state->icmp_state, packet2->src_ip, packet3_ptr, packet3_len); 299 | else 300 | IP_DEBUG_LOG("Unsupported protocol %d", packet2->protocol); 301 | } 302 | -------------------------------------------------------------------------------- /src/tinycthread.h: -------------------------------------------------------------------------------- 1 | /* -*- mode: c; tab-width: 2; indent-tabs-mode: nil; -*- 2 | Copyright (c) 2012 Marcus Geelnard 3 | Copyright (c) 2013-2016 Evan Nemerson 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 18 | 2. Altered source versions must be plainly marked as such, and must not be 19 | misrepresented as being the original software. 20 | 21 | 3. This notice may not be removed or altered from any source 22 | distribution. 23 | */ 24 | 25 | #ifndef _TINYCTHREAD_H_ 26 | #define _TINYCTHREAD_H_ 27 | 28 | #ifdef __cplusplus 29 | extern "C" { 30 | #endif 31 | 32 | /** 33 | * @file 34 | * @mainpage TinyCThread API Reference 35 | * 36 | * @section intro_sec Introduction 37 | * TinyCThread is a minimal, portable implementation of basic threading 38 | * classes for C. 39 | * 40 | * They closely mimic the functionality and naming of the C11 standard, and 41 | * should be easily replaceable with the corresponding standard variants. 42 | * 43 | * @section port_sec Portability 44 | * The Win32 variant uses the native Win32 API for implementing the thread 45 | * classes, while for other systems, the POSIX threads API (pthread) is used. 46 | * 47 | * @section misc_sec Miscellaneous 48 | * The following special keywords are available: #_Thread_local. 49 | * 50 | * For more detailed information, browse the different sections of this 51 | * documentation. A good place to start is: 52 | * tinycthread.h. 53 | */ 54 | 55 | /* Which platform are we on? */ 56 | #if !defined(_TTHREAD_PLATFORM_DEFINED_) 57 | #if defined(_WIN32) || defined(__WIN32__) || defined(__WINDOWS__) 58 | #define _TTHREAD_WIN32_ 59 | #else 60 | #define _TTHREAD_POSIX_ 61 | #endif 62 | #define _TTHREAD_PLATFORM_DEFINED_ 63 | #endif 64 | 65 | /* Activate some POSIX functionality (e.g. clock_gettime and recursive mutexes) */ 66 | #if defined(_TTHREAD_POSIX_) 67 | #undef _FEATURES_H 68 | #if !defined(_GNU_SOURCE) 69 | #define _GNU_SOURCE 70 | #endif 71 | #if !defined(_POSIX_C_SOURCE) || ((_POSIX_C_SOURCE - 0) < 199309L) 72 | #undef _POSIX_C_SOURCE 73 | #define _POSIX_C_SOURCE 199309L 74 | #endif 75 | #if !defined(_XOPEN_SOURCE) || ((_XOPEN_SOURCE - 0) < 500) 76 | #undef _XOPEN_SOURCE 77 | #define _XOPEN_SOURCE 500 78 | #endif 79 | #define _XPG6 80 | #endif 81 | 82 | /* Generic includes */ 83 | #include 84 | 85 | /* Platform specific includes */ 86 | #if defined(_TTHREAD_POSIX_) 87 | #include 88 | #elif defined(_TTHREAD_WIN32_) 89 | #ifndef WIN32_LEAN_AND_MEAN 90 | #define WIN32_LEAN_AND_MEAN 91 | #define __UNDEF_LEAN_AND_MEAN 92 | #endif 93 | #include 94 | #ifdef __UNDEF_LEAN_AND_MEAN 95 | #undef WIN32_LEAN_AND_MEAN 96 | #undef __UNDEF_LEAN_AND_MEAN 97 | #endif 98 | #endif 99 | 100 | /* Compiler-specific information */ 101 | #if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L 102 | #define TTHREAD_NORETURN _Noreturn 103 | #elif defined(__GNUC__) 104 | #define TTHREAD_NORETURN __attribute__((__noreturn__)) 105 | #else 106 | #define TTHREAD_NORETURN 107 | #endif 108 | 109 | /* If TIME_UTC is missing, provide it and provide a wrapper for 110 | timespec_get. */ 111 | #ifndef TIME_UTC 112 | #define TIME_UTC 1 113 | #define _TTHREAD_EMULATE_TIMESPEC_GET_ 114 | 115 | #if defined(_TTHREAD_WIN32_) 116 | struct _tthread_timespec { 117 | time_t tv_sec; 118 | long tv_nsec; 119 | }; 120 | #define timespec _tthread_timespec 121 | #endif 122 | 123 | int _tthread_timespec_get(struct timespec *ts, int base); 124 | #define timespec_get _tthread_timespec_get 125 | #endif 126 | 127 | /** TinyCThread version (major number). */ 128 | #define TINYCTHREAD_VERSION_MAJOR 1 129 | /** TinyCThread version (minor number). */ 130 | #define TINYCTHREAD_VERSION_MINOR 2 131 | /** TinyCThread version (full version). */ 132 | #define TINYCTHREAD_VERSION (TINYCTHREAD_VERSION_MAJOR * 100 + TINYCTHREAD_VERSION_MINOR) 133 | 134 | /** 135 | * @def _Thread_local 136 | * Thread local storage keyword. 137 | * A variable that is declared with the @c _Thread_local keyword makes the 138 | * value of the variable local to each thread (known as thread-local storage, 139 | * or TLS). Example usage: 140 | * @code 141 | * // This variable is local to each thread. 142 | * _Thread_local int variable; 143 | * @endcode 144 | * @note The @c _Thread_local keyword is a macro that maps to the corresponding 145 | * compiler directive (e.g. @c __declspec(thread)). 146 | * @note This directive is currently not supported on Mac OS X (it will give 147 | * a compiler error), since compile-time TLS is not supported in the Mac OS X 148 | * executable format. Also, some older versions of MinGW (before GCC 4.x) do 149 | * not support this directive, nor does the Tiny C Compiler. 150 | * @hideinitializer 151 | */ 152 | 153 | #if !(defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201102L)) && !defined(_Thread_local) 154 | #if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__SUNPRO_CC) || defined(__IBMCPP__) 155 | #define _Thread_local __thread 156 | #else 157 | #define _Thread_local __declspec(thread) 158 | #endif 159 | #elif defined(__GNUC__) && defined(__GNUC_MINOR__) && (((__GNUC__ << 8) | __GNUC_MINOR__) < ((4 << 8) | 9)) 160 | #define _Thread_local __thread 161 | #endif 162 | 163 | /* Macros */ 164 | #if defined(_TTHREAD_WIN32_) 165 | #define TSS_DTOR_ITERATIONS (4) 166 | #else 167 | #define TSS_DTOR_ITERATIONS PTHREAD_DESTRUCTOR_ITERATIONS 168 | #endif 169 | 170 | /* Function return values */ 171 | #define thrd_error 0 /**< The requested operation failed */ 172 | #define thrd_success 1 /**< The requested operation succeeded */ 173 | #define thrd_timedout 2 /**< The time specified in the call was reached without acquiring the requested resource */ 174 | #define thrd_busy 3 /**< The requested operation failed because a tesource requested by a test and return function is already in use */ 175 | #define thrd_nomem 4 /**< The requested operation failed because it was unable to allocate memory */ 176 | 177 | /* Mutex types */ 178 | #define mtx_plain 0 179 | #define mtx_timed 1 180 | #define mtx_recursive 2 181 | 182 | /* Mutex */ 183 | #if defined(_TTHREAD_WIN32_) 184 | typedef struct { 185 | union { 186 | CRITICAL_SECTION cs; /* Critical section handle (used for non-timed mutexes) */ 187 | HANDLE mut; /* Mutex handle (used for timed mutex) */ 188 | } mHandle; /* Mutex handle */ 189 | int mAlreadyLocked; /* TRUE if the mutex is already locked */ 190 | int mRecursive; /* TRUE if the mutex is recursive */ 191 | int mTimed; /* TRUE if the mutex is timed */ 192 | } mtx_t; 193 | #else 194 | typedef pthread_mutex_t mtx_t; 195 | #endif 196 | 197 | /** Create a mutex object. 198 | * @param mtx A mutex object. 199 | * @param type Bit-mask that must have one of the following six values: 200 | * @li @c mtx_plain for a simple non-recursive mutex 201 | * @li @c mtx_timed for a non-recursive mutex that supports timeout 202 | * @li @c mtx_plain | @c mtx_recursive (same as @c mtx_plain, but recursive) 203 | * @li @c mtx_timed | @c mtx_recursive (same as @c mtx_timed, but recursive) 204 | * @return @ref thrd_success on success, or @ref thrd_error if the request could 205 | * not be honored. 206 | */ 207 | int mtx_init(mtx_t *mtx, int type); 208 | 209 | /** Release any resources used by the given mutex. 210 | * @param mtx A mutex object. 211 | */ 212 | void mtx_destroy(mtx_t *mtx); 213 | 214 | /** Lock the given mutex. 215 | * Blocks until the given mutex can be locked. If the mutex is non-recursive, and 216 | * the calling thread already has a lock on the mutex, this call will block 217 | * forever. 218 | * @param mtx A mutex object. 219 | * @return @ref thrd_success on success, or @ref thrd_error if the request could 220 | * not be honored. 221 | */ 222 | int mtx_lock(mtx_t *mtx); 223 | 224 | /** Lock the given mutex, or block until a specific point in time. 225 | * Blocks until either the given mutex can be locked, or the specified TIME_UTC 226 | * based time. 227 | * @param mtx A mutex object. 228 | * @param ts A UTC based calendar time 229 | * @return @ref The mtx_timedlock function returns thrd_success on success, or 230 | * thrd_timedout if the time specified was reached without acquiring the 231 | * requested resource, or thrd_error if the request could not be honored. 232 | */ 233 | int mtx_timedlock(mtx_t *mtx, const struct timespec *ts); 234 | 235 | /** Try to lock the given mutex. 236 | * The specified mutex shall support either test and return or timeout. If the 237 | * mutex is already locked, the function returns without blocking. 238 | * @param mtx A mutex object. 239 | * @return @ref thrd_success on success, or @ref thrd_busy if the resource 240 | * requested is already in use, or @ref thrd_error if the request could not be 241 | * honored. 242 | */ 243 | int mtx_trylock(mtx_t *mtx); 244 | 245 | /** Unlock the given mutex. 246 | * @param mtx A mutex object. 247 | * @return @ref thrd_success on success, or @ref thrd_error if the request could 248 | * not be honored. 249 | */ 250 | int mtx_unlock(mtx_t *mtx); 251 | 252 | /* Condition variable */ 253 | #if defined(_TTHREAD_WIN32_) 254 | typedef struct { 255 | HANDLE mEvents[2]; /* Signal and broadcast event HANDLEs. */ 256 | unsigned int mWaitersCount; /* Count of the number of waiters. */ 257 | CRITICAL_SECTION mWaitersCountLock; /* Serialize access to mWaitersCount. */ 258 | } cnd_t; 259 | #else 260 | typedef pthread_cond_t cnd_t; 261 | #endif 262 | 263 | /** Create a condition variable object. 264 | * @param cond A condition variable object. 265 | * @return @ref thrd_success on success, or @ref thrd_error if the request could 266 | * not be honored. 267 | */ 268 | int cnd_init(cnd_t *cond); 269 | 270 | /** Release any resources used by the given condition variable. 271 | * @param cond A condition variable object. 272 | */ 273 | void cnd_destroy(cnd_t *cond); 274 | 275 | /** Signal a condition variable. 276 | * Unblocks one of the threads that are blocked on the given condition variable 277 | * at the time of the call. If no threads are blocked on the condition variable 278 | * at the time of the call, the function does nothing and return success. 279 | * @param cond A condition variable object. 280 | * @return @ref thrd_success on success, or @ref thrd_error if the request could 281 | * not be honored. 282 | */ 283 | int cnd_signal(cnd_t *cond); 284 | 285 | /** Broadcast a condition variable. 286 | * Unblocks all of the threads that are blocked on the given condition variable 287 | * at the time of the call. If no threads are blocked on the condition variable 288 | * at the time of the call, the function does nothing and return success. 289 | * @param cond A condition variable object. 290 | * @return @ref thrd_success on success, or @ref thrd_error if the request could 291 | * not be honored. 292 | */ 293 | int cnd_broadcast(cnd_t *cond); 294 | 295 | /** Wait for a condition variable to become signaled. 296 | * The function atomically unlocks the given mutex and endeavors to block until 297 | * the given condition variable is signaled by a call to cnd_signal or to 298 | * cnd_broadcast. When the calling thread becomes unblocked it locks the mutex 299 | * before it returns. 300 | * @param cond A condition variable object. 301 | * @param mtx A mutex object. 302 | * @return @ref thrd_success on success, or @ref thrd_error if the request could 303 | * not be honored. 304 | */ 305 | int cnd_wait(cnd_t *cond, mtx_t *mtx); 306 | 307 | /** Wait for a condition variable to become signaled. 308 | * The function atomically unlocks the given mutex and endeavors to block until 309 | * the given condition variable is signaled by a call to cnd_signal or to 310 | * cnd_broadcast, or until after the specified time. When the calling thread 311 | * becomes unblocked it locks the mutex before it returns. 312 | * @param cond A condition variable object. 313 | * @param mtx A mutex object. 314 | * @param xt A point in time at which the request will time out (absolute time). 315 | * @return @ref thrd_success upon success, or @ref thrd_timeout if the time 316 | * specified in the call was reached without acquiring the requested resource, or 317 | * @ref thrd_error if the request could not be honored. 318 | */ 319 | int cnd_timedwait(cnd_t *cond, mtx_t *mtx, const struct timespec *ts); 320 | 321 | /* Thread */ 322 | #if defined(_TTHREAD_WIN32_) 323 | typedef HANDLE thrd_t; 324 | #else 325 | typedef pthread_t thrd_t; 326 | #endif 327 | 328 | /** Thread start function. 329 | * Any thread that is started with the @ref thrd_create() function must be 330 | * started through a function of this type. 331 | * @param arg The thread argument (the @c arg argument of the corresponding 332 | * @ref thrd_create() call). 333 | * @return The thread return value, which can be obtained by another thread 334 | * by using the @ref thrd_join() function. 335 | */ 336 | typedef int (*thrd_start_t)(void *arg); 337 | 338 | /** Create a new thread. 339 | * @param thr Identifier of the newly created thread. 340 | * @param func A function pointer to the function that will be executed in 341 | * the new thread. 342 | * @param arg An argument to the thread function. 343 | * @return @ref thrd_success on success, or @ref thrd_nomem if no memory could 344 | * be allocated for the thread requested, or @ref thrd_error if the request 345 | * could not be honored. 346 | * @note A thread’s identifier may be reused for a different thread once the 347 | * original thread has exited and either been detached or joined to another 348 | * thread. 349 | */ 350 | int thrd_create(thrd_t *thr, thrd_start_t func, void *arg); 351 | 352 | /** Identify the calling thread. 353 | * @return The identifier of the calling thread. 354 | */ 355 | thrd_t thrd_current(void); 356 | 357 | /** Dispose of any resources allocated to the thread when that thread exits. 358 | * @return thrd_success, or thrd_error on error 359 | */ 360 | int thrd_detach(thrd_t thr); 361 | 362 | /** Compare two thread identifiers. 363 | * The function determines if two thread identifiers refer to the same thread. 364 | * @return Zero if the two thread identifiers refer to different threads. 365 | * Otherwise a nonzero value is returned. 366 | */ 367 | int thrd_equal(thrd_t thr0, thrd_t thr1); 368 | 369 | /** Terminate execution of the calling thread. 370 | * @param res Result code of the calling thread. 371 | */ 372 | TTHREAD_NORETURN void thrd_exit(int res); 373 | 374 | /** Wait for a thread to terminate. 375 | * The function joins the given thread with the current thread by blocking 376 | * until the other thread has terminated. 377 | * @param thr The thread to join with. 378 | * @param res If this pointer is not NULL, the function will store the result 379 | * code of the given thread in the integer pointed to by @c res. 380 | * @return @ref thrd_success on success, or @ref thrd_error if the request could 381 | * not be honored. 382 | */ 383 | int thrd_join(thrd_t thr, int *res); 384 | 385 | /** Put the calling thread to sleep. 386 | * Suspend execution of the calling thread. 387 | * @param duration Interval to sleep for 388 | * @param remaining If non-NULL, this parameter will hold the remaining 389 | * time until time_point upon return. This will 390 | * typically be zero, but if the thread was woken up 391 | * by a signal that is not ignored before duration was 392 | * reached @c remaining will hold a positive time. 393 | * @return 0 (zero) on successful sleep, -1 if an interrupt occurred, 394 | * or a negative value if the operation fails. 395 | */ 396 | int thrd_sleep(const struct timespec *duration, struct timespec *remaining); 397 | 398 | /** Yield execution to another thread. 399 | * Permit other threads to run, even if the current thread would ordinarily 400 | * continue to run. 401 | */ 402 | void thrd_yield(void); 403 | 404 | /* Thread local storage */ 405 | #if defined(_TTHREAD_WIN32_) 406 | typedef DWORD tss_t; 407 | #else 408 | typedef pthread_key_t tss_t; 409 | #endif 410 | 411 | /** Destructor function for a thread-specific storage. 412 | * @param val The value of the destructed thread-specific storage. 413 | */ 414 | typedef void (*tss_dtor_t)(void *val); 415 | 416 | /** Create a thread-specific storage. 417 | * @param key The unique key identifier that will be set if the function is 418 | * successful. 419 | * @param dtor Destructor function. This can be NULL. 420 | * @return @ref thrd_success on success, or @ref thrd_error if the request could 421 | * not be honored. 422 | * @note On Windows, the @c dtor will definitely be called when 423 | * appropriate for threads created with @ref thrd_create. It will be 424 | * called for other threads in most cases, the possible exception being 425 | * for DLLs loaded with LoadLibraryEx. In order to be certain, you 426 | * should use @ref thrd_create whenever possible. 427 | */ 428 | int tss_create(tss_t *key, tss_dtor_t dtor); 429 | 430 | /** Delete a thread-specific storage. 431 | * The function releases any resources used by the given thread-specific 432 | * storage. 433 | * @param key The key that shall be deleted. 434 | */ 435 | void tss_delete(tss_t key); 436 | 437 | /** Get the value for a thread-specific storage. 438 | * @param key The thread-specific storage identifier. 439 | * @return The value for the current thread held in the given thread-specific 440 | * storage. 441 | */ 442 | void *tss_get(tss_t key); 443 | 444 | /** Set the value for a thread-specific storage. 445 | * @param key The thread-specific storage identifier. 446 | * @param val The value of the thread-specific storage to set for the current 447 | * thread. 448 | * @return @ref thrd_success on success, or @ref thrd_error if the request could 449 | * not be honored. 450 | */ 451 | int tss_set(tss_t key, void *val); 452 | 453 | #if defined(_TTHREAD_WIN32_) 454 | typedef struct { 455 | LONG volatile status; 456 | CRITICAL_SECTION lock; 457 | } once_flag; 458 | #define ONCE_FLAG_INIT {0,} 459 | #else 460 | #define once_flag pthread_once_t 461 | #define ONCE_FLAG_INIT PTHREAD_ONCE_INIT 462 | #endif 463 | 464 | /** Invoke a callback exactly once 465 | * @param flag Flag used to ensure the callback is invoked exactly 466 | * once. 467 | * @param func Callback to invoke. 468 | */ 469 | #if defined(_TTHREAD_WIN32_) 470 | void call_once(once_flag *flag, void (*func)(void)); 471 | #else 472 | #define call_once(flag,func) pthread_once(flag,func) 473 | #endif 474 | 475 | #ifdef __cplusplus 476 | } 477 | #endif 478 | 479 | #endif /* _TINYTHREAD_H_ */ 480 | -------------------------------------------------------------------------------- /src/arp.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2024 Francesco Cozzuto 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | 26 | #include 27 | #include "endian.h" 28 | #include "arp.h" 29 | 30 | #ifdef ARP_DEBUG 31 | #include 32 | #define ARP_DEBUG_LOG(fmt, ...) fprintf(stderr, "ARP :: " fmt "\n", ## __VA_ARGS__) 33 | #else 34 | #define ARP_DEBUG_LOG(...) 35 | #endif 36 | 37 | void arp_change_output_buffer(arp_state_t *state, void *ptr, size_t max) 38 | { 39 | if (max < sizeof(arp_packet_t)) 40 | state->output = NULL; 41 | else 42 | state->output = ptr; 43 | } 44 | 45 | static void arp_translation_table_seconds_passed(arp_translation_table_t *table, size_t seconds) 46 | { 47 | table->time += seconds; 48 | 49 | // Determine all of the elements of the table that have just 50 | // timed out. 51 | // 52 | // The [used_list] contains all of the active table entries 53 | // in a doubly linked list. The first element is referred by 54 | // [table->used_list_head], and the last by [table->used_list_tail]. 55 | // The entries are ordered in descending [entry->timeout] 56 | // attribute. The [timeout] attribute indicates the absolute 57 | // time at which the entry will be considered invalid, 58 | // relative to [table->time]. 59 | // 60 | // Since the list goes from high to low timeout, if an entry 61 | // at a given point in time isn't timed-out, all of the 62 | // entries that come before it also aren't timed-out. 63 | // Analogously, is an entry in a given point in time is 64 | // timed-out, all of the entries after it are also timed-out. 65 | // 66 | // In general, at any given point in time, the list is made 67 | // of a first half of non-timed-out entries and a second half 68 | // of timed-out entries. 69 | // 70 | // This function needs to remove the timed-out tail of the 71 | // used entries list and add it to the free entry list. 72 | // 73 | 74 | // Find from the end of the list the first non-timed-out 75 | // entry. The timed-out elements will be all of the ones 76 | // that come after it. 77 | // 78 | // NOTE: If all of the entries are timed-out or the list is 79 | // empty, the loop will exit with the NULL entry. 80 | 81 | arp_translation_table_entry_t *entry = table->used_list_tail; 82 | while (entry && entry->timeout < table->time) 83 | entry = entry->prev; 84 | 85 | // First and last element of the timed-out list. We need 86 | // to determine these. 87 | arp_translation_table_entry_t *timeout_list; 88 | arp_translation_table_entry_t *timeout_tail; 89 | 90 | if (entry) { 91 | 92 | // The iteration didn't end with a NULL cursor, so either 93 | // there are no timed-out elements (in which case the cursor 94 | // is the tail of the list) or there are both timed-out and 95 | // non-timed-out entries. 96 | // 97 | // Either way, the start of the list is [entry->next]. 98 | timeout_list = entry->next; 99 | // 100 | // If there are no timed-out entries, the tail of the timed-out 101 | // list must be NULL, else it's the tail of the used list. 102 | timeout_tail = entry->next ? table->used_list_tail : NULL; 103 | // 104 | // The entry becomes the new tail 105 | entry->next = NULL; 106 | table->used_list_tail = entry; 107 | 108 | } else { 109 | 110 | // If the iteration ended with a NULL cursor, there 111 | // are no valid entries in the list. Either the list 112 | // is all timed-out, or it's empty. 113 | // 114 | // Either way we take the list pointers and make them 115 | // out timed-out list. 116 | timeout_list = table->used_list_head; 117 | timeout_tail = table->used_list_tail; 118 | // 119 | // If the list wasn't empty, we make it so. 120 | table->used_list_head = NULL; 121 | table->used_list_tail = NULL; 122 | } 123 | 124 | // Append the timed-out list to the free list 125 | if (timeout_list) { 126 | timeout_list->prev = NULL; 127 | timeout_tail->next = table->free_list; 128 | table->free_list = timeout_list; 129 | } 130 | } 131 | 132 | void arp_ms_passed(arp_state_t *state, size_t ms) 133 | { 134 | size_t seconds = ms / 1000; 135 | 136 | state->time += seconds; 137 | 138 | // Scan through all of the timed-out entries 139 | // in the pending request list from the tail 140 | arp_pending_request_t *cursor = state->pending_request_used_tail; 141 | while (cursor && cursor->timeout < state->time) 142 | cursor = cursor->prev; 143 | 144 | 145 | // Chop off the list of timed out entries 146 | arp_pending_request_t *timeout_list; 147 | arp_pending_request_t *timeout_tail; 148 | 149 | if (cursor) { 150 | // Cursor holds the first request that's not timed out, 151 | // therefore all of the entries that come after it are 152 | // now invalid 153 | timeout_list = cursor->next; 154 | timeout_tail = cursor->next ? state->pending_request_used_tail : NULL; 155 | 156 | // Now chop off the list 157 | cursor->next = NULL; 158 | state->pending_request_used_tail = cursor; 159 | } else { 160 | // Either the list is empty or all of the requests are 161 | // now invalid. 162 | timeout_list = state->pending_request_used_list; 163 | timeout_tail = state->pending_request_used_tail; 164 | state->pending_request_used_list = NULL; 165 | state->pending_request_used_tail = NULL; 166 | } 167 | 168 | // Now walk through the timed out entries and 169 | // run the callback with the timeout status code 170 | arp_pending_request_t *timeout_cursor = timeout_list; 171 | while (timeout_cursor) { 172 | timeout_cursor->callback(timeout_cursor->callback_data, ARP_RESOLUTION_TIMEOUT, MAC_ZERO); 173 | timeout_cursor = timeout_cursor->next; 174 | } 175 | 176 | // Now put the timed out entries back in the free 177 | // list (if there are any) 178 | if (timeout_list) { 179 | timeout_list->prev = NULL; 180 | timeout_tail->next = state->pending_request_free_list; 181 | state->pending_request_free_list = timeout_list; 182 | } 183 | 184 | arp_translation_table_seconds_passed(&state->table, seconds); 185 | } 186 | 187 | static void 188 | arp_translation_table_init(arp_translation_table_t *table) 189 | { 190 | table->time = 0; 191 | table->used_list_head = NULL; 192 | table->used_list_tail = NULL; 193 | table->free_list = table->entries; 194 | for (size_t i = 0; i < ARP_TRANSLATION_TABLE_SIZE-1; i++) { 195 | table->entries[i].prev = NULL; 196 | table->entries[i].next = table->entries + i+1; 197 | } 198 | table->entries[ARP_TRANSLATION_TABLE_SIZE-1].prev = NULL; 199 | table->entries[ARP_TRANSLATION_TABLE_SIZE-1].next = NULL; 200 | } 201 | 202 | static void 203 | arp_translation_table_free(arp_translation_table_t *table) 204 | { 205 | (void) table; 206 | } 207 | 208 | 209 | #ifdef ARP_DEBUG 210 | static bool 211 | arp_translation_table_entry_is_used(arp_translation_table_t *table, 212 | arp_translation_table_entry_t *entry) 213 | { 214 | arp_translation_table_entry_t *cursor = table->used_list_head; 215 | while (cursor) { 216 | if (cursor == entry) 217 | return true; 218 | cursor = cursor->next; 219 | } 220 | return false; 221 | } 222 | 223 | static bool 224 | arp_translation_table_entry_is_unlinked(arp_translation_table_t *table, 225 | arp_translation_table_entry_t *entry) 226 | { 227 | return entry->prev == NULL 228 | && entry->next == NULL 229 | && table->free_list != entry 230 | && table->used_list_head != entry 231 | && table->used_list_tail != entry; 232 | } 233 | #endif 234 | 235 | static void 236 | arp_translation_table_unlink_used_entry(arp_translation_table_t *table, 237 | arp_translation_table_entry_t *entry) 238 | { 239 | 240 | #ifdef ARP_DEBUG 241 | assert(!arp_translation_table_entry_is_unlinked(table, entry)); 242 | #endif 243 | 244 | if (entry->prev) 245 | entry->prev->next = entry->next; 246 | else 247 | table->used_list_head = entry->next; 248 | 249 | if (entry->next) 250 | entry->next->prev = entry->prev; 251 | else 252 | table->used_list_tail = entry->prev; 253 | 254 | entry->prev = NULL; 255 | entry->next = NULL; 256 | 257 | #ifdef ARP_DEBUG 258 | assert(arp_translation_table_entry_is_unlinked(table, entry)); 259 | #endif 260 | 261 | } 262 | 263 | static void 264 | arp_translation_table_insert_unlinked_entry_into_used_list(arp_translation_table_t *table, 265 | arp_translation_table_entry_t *entry) 266 | { 267 | 268 | #ifdef ARP_DEBUG 269 | assert(arp_translation_table_entry_is_unlinked(table, entry)); 270 | assert(!arp_translation_table_entry_is_used(table, entry)); 271 | #endif 272 | 273 | // Find the first entry with the lower timeout 274 | arp_translation_table_entry_t *cursor = table->used_list_head; 275 | while (cursor && cursor->timeout < entry->timeout) 276 | cursor = cursor->next; 277 | 278 | if (cursor) { 279 | // Insert the entry before the cursor position. 280 | entry->prev = cursor->prev; 281 | entry->next = cursor; 282 | 283 | if (cursor->prev) 284 | cursor->prev->next = entry; 285 | else 286 | table->used_list_head = entry; 287 | 288 | cursor->prev = entry; 289 | 290 | } else { 291 | 292 | // Either the list is empty or the entry must 293 | // be inserted last. 294 | 295 | entry->prev = table->used_list_tail; 296 | entry->next = NULL; 297 | 298 | if (table->used_list_tail) 299 | table->used_list_tail->next = entry; 300 | else 301 | table->used_list_head = entry; 302 | table->used_list_tail = entry; 303 | } 304 | 305 | #ifdef ARP_DEBUG 306 | assert(!arp_translation_table_entry_is_unlinked(table, entry)); 307 | assert(arp_translation_table_entry_is_used(table, entry)); 308 | #endif 309 | } 310 | 311 | static void 312 | arp_translation_table_free_least_recently_used_entry(arp_translation_table_t *table) 313 | { 314 | arp_translation_table_entry_t *entry = table->used_list_tail; 315 | if (entry) { 316 | 317 | #ifdef ARP_DEBUG 318 | assert(!arp_translation_table_entry_is_unlinked(table, entry)); 319 | #endif 320 | 321 | arp_translation_table_unlink_used_entry(table, entry); 322 | 323 | #ifdef ARP_DEBUG 324 | assert(arp_translation_table_entry_is_unlinked(table, entry)); 325 | #endif 326 | 327 | // Push the entry to the free list 328 | entry->next = table->free_list; 329 | table->free_list = entry; 330 | 331 | #ifdef ARP_DEBUG 332 | assert(!arp_translation_table_entry_is_unlinked(table, entry)); 333 | #endif 334 | 335 | } 336 | } 337 | 338 | static arp_translation_table_entry_t* 339 | arp_translation_table_find_entry_by_ip(arp_translation_table_t *table, 340 | ip_address_t ip) 341 | { 342 | arp_translation_table_entry_t *entry = table->used_list_head; 343 | while (entry) { 344 | if (entry->ip == ip) 345 | return entry; 346 | entry = entry->next; 347 | } 348 | return NULL; 349 | } 350 | 351 | static bool arp_translation_table_find_mac_by_ip(arp_translation_table_t *table, 352 | ip_address_t ip, mac_address_t *mac) 353 | { 354 | arp_translation_table_entry_t *entry = 355 | arp_translation_table_find_entry_by_ip(table, ip); 356 | 357 | if (entry) 358 | *mac = entry->mac; 359 | return !!entry; 360 | } 361 | 362 | static arp_translation_table_entry_t* 363 | arp_translation_table_pop_free_entry(arp_translation_table_t *table) 364 | { 365 | arp_translation_table_entry_t *entry = table->free_list; 366 | if (entry) 367 | table->free_list = entry->next; 368 | return entry; 369 | } 370 | 371 | static void 372 | arp_translation_table_initialize_entry(arp_translation_table_entry_t *entry, 373 | mac_address_t mac, ip_address_t ip, 374 | uint64_t timeout) 375 | { 376 | entry->mac = mac; 377 | entry->ip = ip; 378 | entry->timeout = timeout; 379 | entry->prev = NULL; 380 | entry->next = NULL; 381 | } 382 | 383 | static void 384 | arp_translation_table_insert_or_update(arp_translation_table_t *table, 385 | mac_address_t mac, ip_address_t ip, 386 | uint64_t timeout) 387 | { 388 | arp_translation_table_entry_t *entry = 389 | arp_translation_table_find_entry_by_ip(table, ip); 390 | 391 | if (entry) { 392 | entry->timeout = table->time + timeout; // Refresh timeout 393 | arp_translation_table_unlink_used_entry(table, entry); 394 | } else { 395 | entry = arp_translation_table_pop_free_entry(table); 396 | if (!entry) { 397 | arp_translation_table_free_least_recently_used_entry(table); 398 | entry = arp_translation_table_pop_free_entry(table); 399 | } 400 | assert(entry); 401 | 402 | arp_translation_table_initialize_entry(entry, mac, ip, table->time + timeout); 403 | } 404 | arp_translation_table_insert_unlinked_entry_into_used_list(table, entry); 405 | } 406 | 407 | static bool 408 | arp_translation_table_update(arp_translation_table_t *table, 409 | mac_address_t mac, ip_address_t ip, 410 | uint64_t timeout) 411 | { 412 | arp_translation_table_entry_t *entry = 413 | arp_translation_table_find_entry_by_ip(table, ip); 414 | 415 | if (entry) { 416 | arp_translation_table_unlink_used_entry(table, entry); 417 | arp_translation_table_initialize_entry(entry, mac, ip, table->time + timeout); 418 | arp_translation_table_insert_unlinked_entry_into_used_list(table, entry); 419 | } 420 | return !!entry; 421 | } 422 | 423 | void arp_init(arp_state_t *state, ip_address_t ip, mac_address_t mac, 424 | void *send_data, void (*send)(void*, mac_address_t)) 425 | { 426 | state->time = 0; 427 | state->request_timeout = 1; 428 | state->cache_timeout = 10; 429 | state->output = NULL; 430 | state->send_data = send_data; 431 | state->send = send; 432 | 433 | state->self_ip = ip; 434 | state->self_mac = mac; 435 | arp_translation_table_init(&state->table); 436 | 437 | state->pending_request_used_list = NULL; 438 | state->pending_request_used_tail = NULL; 439 | state->pending_request_free_list = state->pending_request_pool; 440 | for (size_t i = 0; i < ARP_MAX_PENDING_REQUESTS; i++) 441 | state->pending_request_pool[i].next = state->pending_request_pool + i+1; 442 | state->pending_request_pool[ARP_MAX_PENDING_REQUESTS-1].next = NULL; 443 | } 444 | 445 | void arp_free(arp_state_t *state) 446 | { 447 | arp_translation_table_free(&state->table); 448 | } 449 | 450 | static void append_pending_request_to_used_list(arp_state_t *state, arp_pending_request_t *pending_request) 451 | { 452 | arp_pending_request_t *cursor = state->pending_request_used_list; 453 | 454 | // Find the first pending request in the list 455 | // with a lower timeout and insert the request 456 | // before it. 457 | while (cursor && cursor->timeout > pending_request->timeout) 458 | cursor = cursor->next; 459 | 460 | if (cursor) { 461 | pending_request->prev = cursor->prev; 462 | pending_request->next = cursor; 463 | 464 | if (cursor->prev) 465 | cursor->prev->next = pending_request; 466 | else 467 | state->pending_request_used_list = pending_request; 468 | cursor->prev = pending_request; 469 | } else { 470 | // Insert the request in the tail of the list 471 | pending_request->prev = state->pending_request_used_tail; 472 | pending_request->next = NULL; 473 | 474 | if (state->pending_request_used_tail) 475 | state->pending_request_used_tail->next = pending_request; 476 | else 477 | state->pending_request_used_list = pending_request; 478 | state->pending_request_used_tail = pending_request; 479 | } 480 | } 481 | 482 | void arp_resolve_mac(arp_state_t *state, ip_address_t ip, void *userp, void (*callback)(void*, arp_resolution_status_t, mac_address_t)) 483 | { 484 | bool found_mac_locally; 485 | mac_address_t mac; 486 | 487 | if (state->self_ip == ip) { 488 | mac = state->self_mac; 489 | found_mac_locally = true; 490 | } else 491 | found_mac_locally = arp_translation_table_find_mac_by_ip(&state->table, ip, &mac); 492 | 493 | if (found_mac_locally) 494 | callback(userp, ARP_RESOLUTION_OK, mac); 495 | else { 496 | 497 | // MAC isn't in the translation table. 498 | // We need to make an ARP REQUEST 499 | 500 | arp_pending_request_t *pending_request = state->pending_request_free_list; 501 | if (pending_request == NULL) { 502 | callback(userp, ARP_RESOLUTION_FAILED, MAC_ZERO); 503 | return; 504 | } 505 | state->pending_request_free_list = pending_request->next; 506 | 507 | pending_request->ip = ip; 508 | pending_request->timeout = state->time + state->request_timeout; 509 | pending_request->callback = callback; 510 | pending_request->callback_data = userp; 511 | pending_request->prev = NULL; 512 | pending_request->next = NULL; 513 | 514 | append_pending_request_to_used_list(state, pending_request); 515 | 516 | arp_packet_t *packet = state->output; 517 | if (packet != NULL) { 518 | packet->hardware_type = cpu_to_net_u16(ARP_HARDWARE_ETHERNET); 519 | packet->protocol_type = cpu_to_net_u16(ARP_PROTOCOL_IP); 520 | packet->hardware_len = 6; 521 | packet->protocol_len = 4; 522 | packet->operation_type = cpu_to_net_u16(ARP_OPERATION_REQUEST); 523 | packet->sender_hardware_address = state->self_mac; 524 | packet->sender_protocol_address = state->self_ip; 525 | packet->target_hardware_address = MAC_ZERO; // This is what we're trying to find 526 | packet->target_protocol_address = ip; 527 | 528 | ARP_DEBUG_LOG("Sending out ARP request to resolve MAC"); 529 | 530 | state->send(state->send_data, MAC_BROADCAST); 531 | } else { 532 | ARP_DEBUG_LOG("Couldn't send ARP request because no output buffer was provided"); 533 | } 534 | } 535 | } 536 | 537 | static void 538 | try_resolving_pending_requests(arp_state_t *state, ip_address_t ip, mac_address_t mac) 539 | { 540 | // NOTE: Could try resolving pending requests from 541 | // the tail of the list instead of the head 542 | // since the tail entries have been waiting 543 | // longer. I think we can assume the older 544 | // entries have higher chances of being resolved. 545 | 546 | arp_pending_request_t *pending_request = state->pending_request_used_list; 547 | arp_pending_request_t *prev = NULL; 548 | while (pending_request) { 549 | 550 | arp_pending_request_t *next = pending_request->next; 551 | 552 | if (pending_request->ip == ip) { 553 | pending_request->callback(pending_request->callback_data, ARP_RESOLUTION_OK, mac); 554 | 555 | pending_request->next = state->pending_request_free_list; 556 | state->pending_request_free_list = pending_request; 557 | 558 | if (prev) 559 | prev->next = next; 560 | else 561 | state->pending_request_used_list = next; 562 | 563 | if (next) 564 | next->prev = prev; 565 | else 566 | state->pending_request_used_tail = prev; 567 | 568 | } else 569 | prev = pending_request; 570 | 571 | pending_request = next; 572 | } 573 | } 574 | 575 | arp_process_result_t arp_process_packet(arp_state_t *state, const void *packet, size_t len) 576 | { 577 | if (len != sizeof(arp_packet_t)) 578 | return ARP_PROCESS_RESULT_INVALID; 579 | 580 | const arp_packet_t *packet2 = packet; 581 | 582 | if (packet2->hardware_type != cpu_to_net_u16(ARP_HARDWARE_ETHERNET)) { 583 | /* Level 2 protocol not supported */ 584 | ARP_DEBUG_LOG("Hardware type %d not supported", packet2->hardware_type); 585 | return ARP_PROCESS_RESULT_HWARENOTSUPP; 586 | } 587 | 588 | if (packet2->protocol_type != cpu_to_net_u16(ARP_PROTOCOL_IP)) { 589 | /* Level 3 protocol not supported */ 590 | ARP_DEBUG_LOG("Protocol type %d not supported", packet2->protocol_type); 591 | return ARP_PROCESS_RESULT_PROTONOTSUPP; 592 | } 593 | 594 | if (packet2->hardware_len != 6 || packet2->protocol_len != 4) { 595 | /* Invalid fields */ 596 | ARP_DEBUG_LOG("Invalid hardware or protocol address size %d or %d (expected %d and %d)", packet2->hardware_len, packet2->protocol_len, 6, 4); 597 | return ARP_PROCESS_RESULT_INVALID; 598 | } 599 | 600 | bool merge = arp_translation_table_update(&state->table, packet2->sender_hardware_address, 601 | packet2->sender_protocol_address, state->cache_timeout); 602 | 603 | if (packet2->target_protocol_address == state->self_ip) { 604 | 605 | if (!merge) { 606 | arp_translation_table_insert_or_update(&state->table, packet2->sender_hardware_address, 607 | packet2->sender_protocol_address, state->cache_timeout); 608 | try_resolving_pending_requests(state, packet2->sender_protocol_address, 609 | packet2->sender_hardware_address); 610 | } 611 | 612 | if (packet2->operation_type == cpu_to_net_u16(ARP_OPERATION_REQUEST)) { 613 | 614 | // Generate the ARP REPLY 615 | 616 | arp_packet_t *response = state->output; 617 | if (state->output) { 618 | response->hardware_type = packet2->hardware_type; 619 | response->protocol_type = packet2->protocol_type; 620 | response->hardware_len = packet2->hardware_len; 621 | response->protocol_len = packet2->protocol_len; 622 | response->operation_type = cpu_to_net_u16(ARP_OPERATION_REPLY); 623 | response->sender_hardware_address = state->self_mac; 624 | response->sender_protocol_address = state->self_ip; 625 | response->target_hardware_address = packet2->sender_hardware_address; 626 | response->target_protocol_address = packet2->sender_protocol_address; 627 | 628 | ARP_DEBUG_LOG("Sending reply"); 629 | 630 | state->send(state->send_data, packet2->sender_hardware_address); 631 | } 632 | } 633 | } else { 634 | //ARP_DEBUG_LOG("Request not for me"); 635 | } 636 | 637 | return ARP_PROCESS_RESULT_OK; 638 | } 639 | -------------------------------------------------------------------------------- /src/tinycthread.c: -------------------------------------------------------------------------------- 1 | /* -*- mode: c; tab-width: 2; indent-tabs-mode: nil; -*- 2 | Copyright (c) 2012 Marcus Geelnard 3 | Copyright (c) 2013-2016 Evan Nemerson 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgment in the product documentation would be 16 | appreciated but is not required. 17 | 18 | 2. Altered source versions must be plainly marked as such, and must not be 19 | misrepresented as being the original software. 20 | 21 | 3. This notice may not be removed or altered from any source 22 | distribution. 23 | */ 24 | 25 | #include "tinycthread.h" 26 | #include 27 | 28 | /* Platform specific includes */ 29 | #if defined(_TTHREAD_POSIX_) 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #elif defined(_TTHREAD_WIN32_) 36 | #include 37 | #include 38 | #endif 39 | 40 | /* Standard, good-to-have defines */ 41 | #ifndef NULL 42 | #define NULL (void*)0 43 | #endif 44 | #ifndef TRUE 45 | #define TRUE 1 46 | #endif 47 | #ifndef FALSE 48 | #define FALSE 0 49 | #endif 50 | 51 | #ifdef __cplusplus 52 | extern "C" { 53 | #endif 54 | 55 | 56 | int mtx_init(mtx_t *mtx, int type) 57 | { 58 | #if defined(_TTHREAD_WIN32_) 59 | mtx->mAlreadyLocked = FALSE; 60 | mtx->mRecursive = type & mtx_recursive; 61 | mtx->mTimed = type & mtx_timed; 62 | if (!mtx->mTimed) 63 | { 64 | InitializeCriticalSection(&(mtx->mHandle.cs)); 65 | } 66 | else 67 | { 68 | mtx->mHandle.mut = CreateMutex(NULL, FALSE, NULL); 69 | if (mtx->mHandle.mut == NULL) 70 | { 71 | return thrd_error; 72 | } 73 | } 74 | return thrd_success; 75 | #else 76 | int ret; 77 | pthread_mutexattr_t attr; 78 | pthread_mutexattr_init(&attr); 79 | if (type & mtx_recursive) 80 | { 81 | pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); 82 | } 83 | ret = pthread_mutex_init(mtx, &attr); 84 | pthread_mutexattr_destroy(&attr); 85 | return ret == 0 ? thrd_success : thrd_error; 86 | #endif 87 | } 88 | 89 | void mtx_destroy(mtx_t *mtx) 90 | { 91 | #if defined(_TTHREAD_WIN32_) 92 | if (!mtx->mTimed) 93 | { 94 | DeleteCriticalSection(&(mtx->mHandle.cs)); 95 | } 96 | else 97 | { 98 | CloseHandle(mtx->mHandle.mut); 99 | } 100 | #else 101 | pthread_mutex_destroy(mtx); 102 | #endif 103 | } 104 | 105 | int mtx_lock(mtx_t *mtx) 106 | { 107 | #if defined(_TTHREAD_WIN32_) 108 | if (!mtx->mTimed) 109 | { 110 | EnterCriticalSection(&(mtx->mHandle.cs)); 111 | } 112 | else 113 | { 114 | switch (WaitForSingleObject(mtx->mHandle.mut, INFINITE)) 115 | { 116 | case WAIT_OBJECT_0: 117 | break; 118 | case WAIT_ABANDONED: 119 | default: 120 | return thrd_error; 121 | } 122 | } 123 | 124 | if (!mtx->mRecursive) 125 | { 126 | while(mtx->mAlreadyLocked) Sleep(1); /* Simulate deadlock... */ 127 | mtx->mAlreadyLocked = TRUE; 128 | } 129 | return thrd_success; 130 | #else 131 | return pthread_mutex_lock(mtx) == 0 ? thrd_success : thrd_error; 132 | #endif 133 | } 134 | 135 | int mtx_timedlock(mtx_t *mtx, const struct timespec *ts) 136 | { 137 | #if defined(_TTHREAD_WIN32_) 138 | struct timespec current_ts; 139 | DWORD timeoutMs; 140 | 141 | if (!mtx->mTimed) 142 | { 143 | return thrd_error; 144 | } 145 | 146 | timespec_get(¤t_ts, TIME_UTC); 147 | 148 | if ((current_ts.tv_sec > ts->tv_sec) || ((current_ts.tv_sec == ts->tv_sec) && (current_ts.tv_nsec >= ts->tv_nsec))) 149 | { 150 | timeoutMs = 0; 151 | } 152 | else 153 | { 154 | timeoutMs = (DWORD)(ts->tv_sec - current_ts.tv_sec) * 1000; 155 | timeoutMs += (ts->tv_nsec - current_ts.tv_nsec) / 1000000; 156 | timeoutMs += 1; 157 | } 158 | 159 | /* TODO: the timeout for WaitForSingleObject doesn't include time 160 | while the computer is asleep. */ 161 | switch (WaitForSingleObject(mtx->mHandle.mut, timeoutMs)) 162 | { 163 | case WAIT_OBJECT_0: 164 | break; 165 | case WAIT_TIMEOUT: 166 | return thrd_timedout; 167 | case WAIT_ABANDONED: 168 | default: 169 | return thrd_error; 170 | } 171 | 172 | if (!mtx->mRecursive) 173 | { 174 | while(mtx->mAlreadyLocked) Sleep(1); /* Simulate deadlock... */ 175 | mtx->mAlreadyLocked = TRUE; 176 | } 177 | 178 | return thrd_success; 179 | #elif defined(_POSIX_TIMEOUTS) && (_POSIX_TIMEOUTS >= 200112L) && defined(_POSIX_THREADS) && (_POSIX_THREADS >= 200112L) 180 | switch (pthread_mutex_timedlock(mtx, ts)) { 181 | case 0: 182 | return thrd_success; 183 | case ETIMEDOUT: 184 | return thrd_timedout; 185 | default: 186 | return thrd_error; 187 | } 188 | #else 189 | int rc; 190 | struct timespec cur, dur; 191 | 192 | /* Try to acquire the lock and, if we fail, sleep for 5ms. */ 193 | while ((rc = pthread_mutex_trylock (mtx)) == EBUSY) { 194 | timespec_get(&cur, TIME_UTC); 195 | 196 | if ((cur.tv_sec > ts->tv_sec) || ((cur.tv_sec == ts->tv_sec) && (cur.tv_nsec >= ts->tv_nsec))) 197 | { 198 | break; 199 | } 200 | 201 | dur.tv_sec = ts->tv_sec - cur.tv_sec; 202 | dur.tv_nsec = ts->tv_nsec - cur.tv_nsec; 203 | if (dur.tv_nsec < 0) 204 | { 205 | dur.tv_sec--; 206 | dur.tv_nsec += 1000000000; 207 | } 208 | 209 | if ((dur.tv_sec != 0) || (dur.tv_nsec > 5000000)) 210 | { 211 | dur.tv_sec = 0; 212 | dur.tv_nsec = 5000000; 213 | } 214 | 215 | nanosleep(&dur, NULL); 216 | } 217 | 218 | switch (rc) { 219 | case 0: 220 | return thrd_success; 221 | case ETIMEDOUT: 222 | case EBUSY: 223 | return thrd_timedout; 224 | default: 225 | return thrd_error; 226 | } 227 | #endif 228 | } 229 | 230 | int mtx_trylock(mtx_t *mtx) 231 | { 232 | #if defined(_TTHREAD_WIN32_) 233 | int ret; 234 | 235 | if (!mtx->mTimed) 236 | { 237 | ret = TryEnterCriticalSection(&(mtx->mHandle.cs)) ? thrd_success : thrd_busy; 238 | } 239 | else 240 | { 241 | ret = (WaitForSingleObject(mtx->mHandle.mut, 0) == WAIT_OBJECT_0) ? thrd_success : thrd_busy; 242 | } 243 | 244 | if ((!mtx->mRecursive) && (ret == thrd_success)) 245 | { 246 | if (mtx->mAlreadyLocked) 247 | { 248 | LeaveCriticalSection(&(mtx->mHandle.cs)); 249 | ret = thrd_busy; 250 | } 251 | else 252 | { 253 | mtx->mAlreadyLocked = TRUE; 254 | } 255 | } 256 | return ret; 257 | #else 258 | return (pthread_mutex_trylock(mtx) == 0) ? thrd_success : thrd_busy; 259 | #endif 260 | } 261 | 262 | int mtx_unlock(mtx_t *mtx) 263 | { 264 | #if defined(_TTHREAD_WIN32_) 265 | mtx->mAlreadyLocked = FALSE; 266 | if (!mtx->mTimed) 267 | { 268 | LeaveCriticalSection(&(mtx->mHandle.cs)); 269 | } 270 | else 271 | { 272 | if (!ReleaseMutex(mtx->mHandle.mut)) 273 | { 274 | return thrd_error; 275 | } 276 | } 277 | return thrd_success; 278 | #else 279 | return pthread_mutex_unlock(mtx) == 0 ? thrd_success : thrd_error;; 280 | #endif 281 | } 282 | 283 | #if defined(_TTHREAD_WIN32_) 284 | #define _CONDITION_EVENT_ONE 0 285 | #define _CONDITION_EVENT_ALL 1 286 | #endif 287 | 288 | int cnd_init(cnd_t *cond) 289 | { 290 | #if defined(_TTHREAD_WIN32_) 291 | cond->mWaitersCount = 0; 292 | 293 | /* Init critical section */ 294 | InitializeCriticalSection(&cond->mWaitersCountLock); 295 | 296 | /* Init events */ 297 | cond->mEvents[_CONDITION_EVENT_ONE] = CreateEvent(NULL, FALSE, FALSE, NULL); 298 | if (cond->mEvents[_CONDITION_EVENT_ONE] == NULL) 299 | { 300 | cond->mEvents[_CONDITION_EVENT_ALL] = NULL; 301 | return thrd_error; 302 | } 303 | cond->mEvents[_CONDITION_EVENT_ALL] = CreateEvent(NULL, TRUE, FALSE, NULL); 304 | if (cond->mEvents[_CONDITION_EVENT_ALL] == NULL) 305 | { 306 | CloseHandle(cond->mEvents[_CONDITION_EVENT_ONE]); 307 | cond->mEvents[_CONDITION_EVENT_ONE] = NULL; 308 | return thrd_error; 309 | } 310 | 311 | return thrd_success; 312 | #else 313 | return pthread_cond_init(cond, NULL) == 0 ? thrd_success : thrd_error; 314 | #endif 315 | } 316 | 317 | void cnd_destroy(cnd_t *cond) 318 | { 319 | #if defined(_TTHREAD_WIN32_) 320 | if (cond->mEvents[_CONDITION_EVENT_ONE] != NULL) 321 | { 322 | CloseHandle(cond->mEvents[_CONDITION_EVENT_ONE]); 323 | } 324 | if (cond->mEvents[_CONDITION_EVENT_ALL] != NULL) 325 | { 326 | CloseHandle(cond->mEvents[_CONDITION_EVENT_ALL]); 327 | } 328 | DeleteCriticalSection(&cond->mWaitersCountLock); 329 | #else 330 | pthread_cond_destroy(cond); 331 | #endif 332 | } 333 | 334 | int cnd_signal(cnd_t *cond) 335 | { 336 | #if defined(_TTHREAD_WIN32_) 337 | int haveWaiters; 338 | 339 | /* Are there any waiters? */ 340 | EnterCriticalSection(&cond->mWaitersCountLock); 341 | haveWaiters = (cond->mWaitersCount > 0); 342 | LeaveCriticalSection(&cond->mWaitersCountLock); 343 | 344 | /* If we have any waiting threads, send them a signal */ 345 | if(haveWaiters) 346 | { 347 | if (SetEvent(cond->mEvents[_CONDITION_EVENT_ONE]) == 0) 348 | { 349 | return thrd_error; 350 | } 351 | } 352 | 353 | return thrd_success; 354 | #else 355 | return pthread_cond_signal(cond) == 0 ? thrd_success : thrd_error; 356 | #endif 357 | } 358 | 359 | int cnd_broadcast(cnd_t *cond) 360 | { 361 | #if defined(_TTHREAD_WIN32_) 362 | int haveWaiters; 363 | 364 | /* Are there any waiters? */ 365 | EnterCriticalSection(&cond->mWaitersCountLock); 366 | haveWaiters = (cond->mWaitersCount > 0); 367 | LeaveCriticalSection(&cond->mWaitersCountLock); 368 | 369 | /* If we have any waiting threads, send them a signal */ 370 | if(haveWaiters) 371 | { 372 | if (SetEvent(cond->mEvents[_CONDITION_EVENT_ALL]) == 0) 373 | { 374 | return thrd_error; 375 | } 376 | } 377 | 378 | return thrd_success; 379 | #else 380 | return pthread_cond_broadcast(cond) == 0 ? thrd_success : thrd_error; 381 | #endif 382 | } 383 | 384 | #if defined(_TTHREAD_WIN32_) 385 | static int _cnd_timedwait_win32(cnd_t *cond, mtx_t *mtx, DWORD timeout) 386 | { 387 | DWORD result; 388 | int lastWaiter; 389 | 390 | /* Increment number of waiters */ 391 | EnterCriticalSection(&cond->mWaitersCountLock); 392 | ++ cond->mWaitersCount; 393 | LeaveCriticalSection(&cond->mWaitersCountLock); 394 | 395 | /* Release the mutex while waiting for the condition (will decrease 396 | the number of waiters when done)... */ 397 | mtx_unlock(mtx); 398 | 399 | /* Wait for either event to become signaled due to cnd_signal() or 400 | cnd_broadcast() being called */ 401 | result = WaitForMultipleObjects(2, cond->mEvents, FALSE, timeout); 402 | if (result == WAIT_TIMEOUT) 403 | { 404 | /* The mutex is locked again before the function returns, even if an error occurred */ 405 | mtx_lock(mtx); 406 | return thrd_timedout; 407 | } 408 | else if (result == WAIT_FAILED) 409 | { 410 | /* The mutex is locked again before the function returns, even if an error occurred */ 411 | mtx_lock(mtx); 412 | return thrd_error; 413 | } 414 | 415 | /* Check if we are the last waiter */ 416 | EnterCriticalSection(&cond->mWaitersCountLock); 417 | -- cond->mWaitersCount; 418 | lastWaiter = (result == (WAIT_OBJECT_0 + _CONDITION_EVENT_ALL)) && 419 | (cond->mWaitersCount == 0); 420 | LeaveCriticalSection(&cond->mWaitersCountLock); 421 | 422 | /* If we are the last waiter to be notified to stop waiting, reset the event */ 423 | if (lastWaiter) 424 | { 425 | if (ResetEvent(cond->mEvents[_CONDITION_EVENT_ALL]) == 0) 426 | { 427 | /* The mutex is locked again before the function returns, even if an error occurred */ 428 | mtx_lock(mtx); 429 | return thrd_error; 430 | } 431 | } 432 | 433 | /* Re-acquire the mutex */ 434 | mtx_lock(mtx); 435 | 436 | return thrd_success; 437 | } 438 | #endif 439 | 440 | int cnd_wait(cnd_t *cond, mtx_t *mtx) 441 | { 442 | #if defined(_TTHREAD_WIN32_) 443 | return _cnd_timedwait_win32(cond, mtx, INFINITE); 444 | #else 445 | return pthread_cond_wait(cond, mtx) == 0 ? thrd_success : thrd_error; 446 | #endif 447 | } 448 | 449 | int cnd_timedwait(cnd_t *cond, mtx_t *mtx, const struct timespec *ts) 450 | { 451 | #if defined(_TTHREAD_WIN32_) 452 | struct timespec now; 453 | if (timespec_get(&now, TIME_UTC) == TIME_UTC) 454 | { 455 | unsigned long long nowInMilliseconds = now.tv_sec * 1000 + now.tv_nsec / 1000000; 456 | unsigned long long tsInMilliseconds = ts->tv_sec * 1000 + ts->tv_nsec / 1000000; 457 | DWORD delta = (tsInMilliseconds > nowInMilliseconds) ? 458 | (DWORD)(tsInMilliseconds - nowInMilliseconds) : 0; 459 | return _cnd_timedwait_win32(cond, mtx, delta); 460 | } 461 | else 462 | return thrd_error; 463 | #else 464 | int ret; 465 | ret = pthread_cond_timedwait(cond, mtx, ts); 466 | if (ret == ETIMEDOUT) 467 | { 468 | return thrd_timedout; 469 | } 470 | return ret == 0 ? thrd_success : thrd_error; 471 | #endif 472 | } 473 | 474 | #if defined(_TTHREAD_WIN32_) 475 | struct TinyCThreadTSSData { 476 | void* value; 477 | tss_t key; 478 | struct TinyCThreadTSSData* next; 479 | }; 480 | 481 | static tss_dtor_t _tinycthread_tss_dtors[1088] = { NULL, }; 482 | 483 | static _Thread_local struct TinyCThreadTSSData* _tinycthread_tss_head = NULL; 484 | static _Thread_local struct TinyCThreadTSSData* _tinycthread_tss_tail = NULL; 485 | 486 | static void _tinycthread_tss_cleanup (void); 487 | 488 | static void _tinycthread_tss_cleanup (void) { 489 | struct TinyCThreadTSSData* data; 490 | int iteration; 491 | unsigned int again = 1; 492 | void* value; 493 | 494 | for (iteration = 0 ; iteration < TSS_DTOR_ITERATIONS && again > 0 ; iteration++) 495 | { 496 | again = 0; 497 | for (data = _tinycthread_tss_head ; data != NULL ; data = data->next) 498 | { 499 | if (data->value != NULL) 500 | { 501 | value = data->value; 502 | data->value = NULL; 503 | 504 | if (_tinycthread_tss_dtors[data->key] != NULL) 505 | { 506 | again = 1; 507 | _tinycthread_tss_dtors[data->key](value); 508 | } 509 | } 510 | } 511 | } 512 | 513 | while (_tinycthread_tss_head != NULL) { 514 | data = _tinycthread_tss_head->next; 515 | free (_tinycthread_tss_head); 516 | _tinycthread_tss_head = data; 517 | } 518 | _tinycthread_tss_head = NULL; 519 | _tinycthread_tss_tail = NULL; 520 | } 521 | 522 | static void NTAPI _tinycthread_tss_callback(PVOID h, DWORD dwReason, PVOID pv) 523 | { 524 | (void)h; 525 | (void)pv; 526 | 527 | if (_tinycthread_tss_head != NULL && (dwReason == DLL_THREAD_DETACH || dwReason == DLL_PROCESS_DETACH)) 528 | { 529 | _tinycthread_tss_cleanup(); 530 | } 531 | } 532 | 533 | #if defined(_MSC_VER) 534 | #ifdef _M_X64 535 | #pragma const_seg(".CRT$XLB") 536 | #else 537 | #pragma data_seg(".CRT$XLB") 538 | #endif 539 | PIMAGE_TLS_CALLBACK p_thread_callback = _tinycthread_tss_callback; 540 | #ifdef _M_X64 541 | #pragma data_seg() 542 | #else 543 | #pragma const_seg() 544 | #endif 545 | #else 546 | PIMAGE_TLS_CALLBACK p_thread_callback __attribute__((section(".CRT$XLB"))) = _tinycthread_tss_callback; 547 | #endif 548 | 549 | #endif /* defined(_TTHREAD_WIN32_) */ 550 | 551 | /** Information to pass to the new thread (what to run). */ 552 | typedef struct { 553 | thrd_start_t mFunction; /**< Pointer to the function to be executed. */ 554 | void * mArg; /**< Function argument for the thread function. */ 555 | } _thread_start_info; 556 | 557 | /* Thread wrapper function. */ 558 | #if defined(_TTHREAD_WIN32_) 559 | static DWORD WINAPI _thrd_wrapper_function(LPVOID aArg) 560 | #elif defined(_TTHREAD_POSIX_) 561 | static void * _thrd_wrapper_function(void * aArg) 562 | #endif 563 | { 564 | thrd_start_t fun; 565 | void *arg; 566 | int res; 567 | 568 | /* Get thread startup information */ 569 | _thread_start_info *ti = (_thread_start_info *) aArg; 570 | fun = ti->mFunction; 571 | arg = ti->mArg; 572 | 573 | /* The thread is responsible for freeing the startup information */ 574 | free((void *)ti); 575 | 576 | /* Call the actual client thread function */ 577 | res = fun(arg); 578 | 579 | #if defined(_TTHREAD_WIN32_) 580 | if (_tinycthread_tss_head != NULL) 581 | { 582 | _tinycthread_tss_cleanup(); 583 | } 584 | 585 | return (DWORD)res; 586 | #else 587 | return (void*)(intptr_t)res; 588 | #endif 589 | } 590 | 591 | int thrd_create(thrd_t *thr, thrd_start_t func, void *arg) 592 | { 593 | /* Fill out the thread startup information (passed to the thread wrapper, 594 | which will eventually free it) */ 595 | _thread_start_info* ti = (_thread_start_info*)malloc(sizeof(_thread_start_info)); 596 | if (ti == NULL) 597 | { 598 | return thrd_nomem; 599 | } 600 | ti->mFunction = func; 601 | ti->mArg = arg; 602 | 603 | /* Create the thread */ 604 | #if defined(_TTHREAD_WIN32_) 605 | *thr = CreateThread(NULL, 0, _thrd_wrapper_function, (LPVOID) ti, 0, NULL); 606 | #elif defined(_TTHREAD_POSIX_) 607 | if(pthread_create(thr, NULL, _thrd_wrapper_function, (void *)ti) != 0) 608 | { 609 | *thr = 0; 610 | } 611 | #endif 612 | 613 | /* Did we fail to create the thread? */ 614 | if(!*thr) 615 | { 616 | free(ti); 617 | return thrd_error; 618 | } 619 | 620 | return thrd_success; 621 | } 622 | 623 | thrd_t thrd_current(void) 624 | { 625 | #if defined(_TTHREAD_WIN32_) 626 | return GetCurrentThread(); 627 | #else 628 | return pthread_self(); 629 | #endif 630 | } 631 | 632 | int thrd_detach(thrd_t thr) 633 | { 634 | #if defined(_TTHREAD_WIN32_) 635 | /* https://stackoverflow.com/questions/12744324/how-to-detach-a-thread-on-windows-c#answer-12746081 */ 636 | return CloseHandle(thr) != 0 ? thrd_success : thrd_error; 637 | #else 638 | return pthread_detach(thr) == 0 ? thrd_success : thrd_error; 639 | #endif 640 | } 641 | 642 | int thrd_equal(thrd_t thr0, thrd_t thr1) 643 | { 644 | #if defined(_TTHREAD_WIN32_) 645 | return GetThreadId(thr0) == GetThreadId(thr1); 646 | #else 647 | return pthread_equal(thr0, thr1); 648 | #endif 649 | } 650 | 651 | void thrd_exit(int res) 652 | { 653 | #if defined(_TTHREAD_WIN32_) 654 | if (_tinycthread_tss_head != NULL) 655 | { 656 | _tinycthread_tss_cleanup(); 657 | } 658 | 659 | ExitThread((DWORD)res); 660 | #else 661 | pthread_exit((void*)(intptr_t)res); 662 | #endif 663 | } 664 | 665 | int thrd_join(thrd_t thr, int *res) 666 | { 667 | #if defined(_TTHREAD_WIN32_) 668 | DWORD dwRes; 669 | 670 | if (WaitForSingleObject(thr, INFINITE) == WAIT_FAILED) 671 | { 672 | return thrd_error; 673 | } 674 | if (res != NULL) 675 | { 676 | if (GetExitCodeThread(thr, &dwRes) != 0) 677 | { 678 | *res = (int) dwRes; 679 | } 680 | else 681 | { 682 | return thrd_error; 683 | } 684 | } 685 | CloseHandle(thr); 686 | #elif defined(_TTHREAD_POSIX_) 687 | void *pres; 688 | if (pthread_join(thr, &pres) != 0) 689 | { 690 | return thrd_error; 691 | } 692 | if (res != NULL) 693 | { 694 | *res = (int)(intptr_t)pres; 695 | } 696 | #endif 697 | return thrd_success; 698 | } 699 | 700 | int thrd_sleep(const struct timespec *duration, struct timespec *remaining) 701 | { 702 | #if !defined(_TTHREAD_WIN32_) 703 | int res = nanosleep(duration, remaining); 704 | if (res == 0) { 705 | return 0; 706 | } else if (errno == EINTR) { 707 | return -1; 708 | } else { 709 | return -2; 710 | } 711 | #else 712 | struct timespec start; 713 | DWORD t; 714 | 715 | timespec_get(&start, TIME_UTC); 716 | 717 | t = SleepEx((DWORD)(duration->tv_sec * 1000 + 718 | duration->tv_nsec / 1000000 + 719 | (((duration->tv_nsec % 1000000) == 0) ? 0 : 1)), 720 | TRUE); 721 | 722 | if (t == 0) { 723 | return 0; 724 | } else { 725 | if (remaining != NULL) { 726 | timespec_get(remaining, TIME_UTC); 727 | remaining->tv_sec -= start.tv_sec; 728 | remaining->tv_nsec -= start.tv_nsec; 729 | if (remaining->tv_nsec < 0) 730 | { 731 | remaining->tv_nsec += 1000000000; 732 | remaining->tv_sec -= 1; 733 | } 734 | } 735 | 736 | return (t == WAIT_IO_COMPLETION) ? -1 : -2; 737 | } 738 | #endif 739 | } 740 | 741 | void thrd_yield(void) 742 | { 743 | #if defined(_TTHREAD_WIN32_) 744 | Sleep(0); 745 | #else 746 | sched_yield(); 747 | #endif 748 | } 749 | 750 | int tss_create(tss_t *key, tss_dtor_t dtor) 751 | { 752 | #if defined(_TTHREAD_WIN32_) 753 | *key = TlsAlloc(); 754 | if (*key == TLS_OUT_OF_INDEXES) 755 | { 756 | return thrd_error; 757 | } 758 | _tinycthread_tss_dtors[*key] = dtor; 759 | #else 760 | if (pthread_key_create(key, dtor) != 0) 761 | { 762 | return thrd_error; 763 | } 764 | #endif 765 | return thrd_success; 766 | } 767 | 768 | void tss_delete(tss_t key) 769 | { 770 | #if defined(_TTHREAD_WIN32_) 771 | struct TinyCThreadTSSData* data = (struct TinyCThreadTSSData*) TlsGetValue (key); 772 | struct TinyCThreadTSSData* prev = NULL; 773 | if (data != NULL) 774 | { 775 | if (data == _tinycthread_tss_head) 776 | { 777 | _tinycthread_tss_head = data->next; 778 | } 779 | else 780 | { 781 | prev = _tinycthread_tss_head; 782 | if (prev != NULL) 783 | { 784 | while (prev->next != data) 785 | { 786 | prev = prev->next; 787 | } 788 | } 789 | } 790 | 791 | if (data == _tinycthread_tss_tail) 792 | { 793 | _tinycthread_tss_tail = prev; 794 | } 795 | 796 | free (data); 797 | } 798 | _tinycthread_tss_dtors[key] = NULL; 799 | TlsFree(key); 800 | #else 801 | pthread_key_delete(key); 802 | #endif 803 | } 804 | 805 | void *tss_get(tss_t key) 806 | { 807 | #if defined(_TTHREAD_WIN32_) 808 | struct TinyCThreadTSSData* data = (struct TinyCThreadTSSData*)TlsGetValue(key); 809 | if (data == NULL) 810 | { 811 | return NULL; 812 | } 813 | return data->value; 814 | #else 815 | return pthread_getspecific(key); 816 | #endif 817 | } 818 | 819 | int tss_set(tss_t key, void *val) 820 | { 821 | #if defined(_TTHREAD_WIN32_) 822 | struct TinyCThreadTSSData* data = (struct TinyCThreadTSSData*)TlsGetValue(key); 823 | if (data == NULL) 824 | { 825 | data = (struct TinyCThreadTSSData*)malloc(sizeof(struct TinyCThreadTSSData)); 826 | if (data == NULL) 827 | { 828 | return thrd_error; 829 | } 830 | 831 | data->value = NULL; 832 | data->key = key; 833 | data->next = NULL; 834 | 835 | if (_tinycthread_tss_tail != NULL) 836 | { 837 | _tinycthread_tss_tail->next = data; 838 | } 839 | else 840 | { 841 | _tinycthread_tss_tail = data; 842 | } 843 | 844 | if (_tinycthread_tss_head == NULL) 845 | { 846 | _tinycthread_tss_head = data; 847 | } 848 | 849 | if (!TlsSetValue(key, data)) 850 | { 851 | free (data); 852 | return thrd_error; 853 | } 854 | } 855 | data->value = val; 856 | #else 857 | if (pthread_setspecific(key, val) != 0) 858 | { 859 | return thrd_error; 860 | } 861 | #endif 862 | return thrd_success; 863 | } 864 | 865 | #if defined(_TTHREAD_EMULATE_TIMESPEC_GET_) 866 | int _tthread_timespec_get(struct timespec *ts, int base) 867 | { 868 | #if defined(_TTHREAD_WIN32_) 869 | struct _timeb tb; 870 | #elif !defined(CLOCK_REALTIME) 871 | struct timeval tv; 872 | #endif 873 | 874 | if (base != TIME_UTC) 875 | { 876 | return 0; 877 | } 878 | 879 | #if defined(_TTHREAD_WIN32_) 880 | _ftime_s(&tb); 881 | ts->tv_sec = (time_t)tb.time; 882 | ts->tv_nsec = 1000000L * (long)tb.millitm; 883 | #elif defined(CLOCK_REALTIME) 884 | base = (clock_gettime(CLOCK_REALTIME, ts) == 0) ? base : 0; 885 | #else 886 | gettimeofday(&tv, NULL); 887 | ts->tv_sec = (time_t)tv.tv_sec; 888 | ts->tv_nsec = 1000L * (long)tv.tv_usec; 889 | #endif 890 | 891 | return base; 892 | } 893 | #endif /* _TTHREAD_EMULATE_TIMESPEC_GET_ */ 894 | 895 | #if defined(_TTHREAD_WIN32_) 896 | void call_once(once_flag *flag, void (*func)(void)) 897 | { 898 | /* The idea here is that we use a spin lock (via the 899 | InterlockedCompareExchange function) to restrict access to the 900 | critical section until we have initialized it, then we use the 901 | critical section to block until the callback has completed 902 | execution. */ 903 | while (flag->status < 3) 904 | { 905 | switch (flag->status) 906 | { 907 | case 0: 908 | if (InterlockedCompareExchange (&(flag->status), 1, 0) == 0) { 909 | InitializeCriticalSection(&(flag->lock)); 910 | EnterCriticalSection(&(flag->lock)); 911 | flag->status = 2; 912 | func(); 913 | flag->status = 3; 914 | LeaveCriticalSection(&(flag->lock)); 915 | return; 916 | } 917 | break; 918 | case 1: 919 | break; 920 | case 2: 921 | EnterCriticalSection(&(flag->lock)); 922 | LeaveCriticalSection(&(flag->lock)); 923 | break; 924 | } 925 | } 926 | } 927 | #endif /* defined(_TTHREAD_WIN32_) */ 928 | 929 | #ifdef __cplusplus 930 | } 931 | #endif 932 | -------------------------------------------------------------------------------- /examples/http/http_parser.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "http_parser.h" 6 | 7 | typedef struct { 8 | size_t offset; 9 | size_t length; 10 | } slice_t; 11 | 12 | typedef struct { 13 | const unsigned char *src; 14 | size_t len, cur; 15 | } scanner_t; 16 | 17 | #define EMPTY_SLICE ((slice_t) {0, 0}) 18 | #define EMPTY_STRING ((hp_string_t) {NULL, 0}) 19 | 20 | static bool is_lower_alpha(unsigned char c) 21 | { 22 | return c >= 'a' && c <= 'z'; 23 | } 24 | 25 | static bool is_upper_alpha(unsigned char c) 26 | { 27 | return c >= 'A' && c <= 'Z'; 28 | } 29 | 30 | static bool is_alpha(unsigned char c) 31 | { 32 | return is_upper_alpha(c) 33 | || is_lower_alpha(c); 34 | } 35 | 36 | static bool is_digit(unsigned char c) 37 | { 38 | return c >= '0' && c <= '9'; 39 | } 40 | 41 | static bool is_hex_digit(unsigned char c) 42 | { 43 | return is_digit(c) 44 | || (c >= 'a' && c <= 'f') 45 | || (c >= 'A' && c <= 'F'); 46 | } 47 | 48 | static bool is_unreserved(unsigned char c) 49 | { 50 | return is_alpha(c) || is_digit(c) 51 | || c == '-' || c == '.' 52 | || c == '_' || c == '~'; 53 | } 54 | 55 | static bool is_subdelim(unsigned char c) 56 | { 57 | return c == '!' || c == '$' 58 | || c == '&' || c == '\'' 59 | || c == '(' || c == ')' 60 | || c == '*' || c == '+' 61 | || c == ',' || c == ';' 62 | || c == '='; 63 | } 64 | 65 | static bool is_pchar(unsigned char c) 66 | { 67 | return is_unreserved(c) 68 | || is_subdelim(c) 69 | || c == ':' || c == '@'; 70 | } 71 | 72 | static void report(hp_error_t *err, const char *fmt, ...) 73 | { 74 | if (err != NULL && !err->occurred) { 75 | va_list args; 76 | va_start(args, fmt); 77 | vsnprintf(err->msg, sizeof(err->msg), fmt, args); 78 | va_end(args); 79 | 80 | err->occurred = true; 81 | } 82 | } 83 | 84 | static slice_t slice_up(scanner_t *scanner, 85 | bool (*is_head)(unsigned char), 86 | bool (*is_body)(unsigned char)) 87 | { 88 | size_t offset = scanner->cur; 89 | if (scanner->cur < scanner->len && is_head(scanner->src[scanner->cur])) 90 | do 91 | scanner->cur++; 92 | while (scanner->cur < scanner->len && is_body(scanner->src[scanner->cur])); 93 | return (slice_t) {offset, scanner->cur-offset}; 94 | } 95 | 96 | static bool follows_char(scanner_t scanner, unsigned char c) 97 | { 98 | return scanner.cur < scanner.len && scanner.src[scanner.cur] == c; 99 | } 100 | 101 | static bool follows_pchar(scanner_t scanner) 102 | { 103 | return scanner.cur < scanner.len && is_pchar(scanner.src[scanner.cur]); 104 | } 105 | 106 | static bool follows_pair(scanner_t scanner, char pair[2]) 107 | { 108 | return scanner.cur+1 < scanner.len 109 | && scanner.src[scanner.cur+0] == (unsigned char) pair[0] 110 | && scanner.src[scanner.cur+1] == (unsigned char) pair[1]; 111 | } 112 | 113 | static bool follows_digit(scanner_t scanner) 114 | { 115 | return scanner.cur < scanner.len 116 | && is_digit(scanner.src[scanner.cur]); 117 | } 118 | 119 | static bool follows_hex_digit(scanner_t scanner) 120 | { 121 | return scanner.cur < scanner.len 122 | && is_hex_digit(scanner.src[scanner.cur]); 123 | } 124 | 125 | static bool consume_char(scanner_t *scanner, unsigned char c) 126 | { 127 | if (follows_char(*scanner, c)) { 128 | scanner->cur++; 129 | return true; 130 | } else 131 | return false; 132 | } 133 | 134 | static void unconsume_char(scanner_t *scanner) 135 | { 136 | assert(scanner->cur > 0); 137 | scanner->cur--; 138 | } 139 | 140 | static bool consume_pchar(scanner_t *scanner) 141 | { 142 | if (follows_pchar(*scanner)) { 143 | scanner->cur++; 144 | return true; 145 | } else 146 | return false; 147 | } 148 | 149 | static bool consume_pair(scanner_t *scanner, char pair[static 2]) 150 | { 151 | if (follows_pair(*scanner, pair)) { 152 | scanner->cur += 2; 153 | return true; 154 | } else 155 | return false; 156 | } 157 | 158 | static bool consume_digit(scanner_t *scanner, unsigned char *digit) 159 | { 160 | if (follows_digit(*scanner)) { 161 | *digit = scanner->src[scanner->cur]; 162 | scanner->cur++; 163 | return true; 164 | } else 165 | return false; 166 | } 167 | 168 | static bool consume_hex_digit(scanner_t *scanner, unsigned char *digit) 169 | { 170 | if (follows_hex_digit(*scanner)) { 171 | *digit = scanner->src[scanner->cur]; 172 | scanner->cur++; 173 | return true; 174 | } else 175 | return false; 176 | } 177 | 178 | static void consume_spaces(scanner_t *scanner) 179 | { 180 | while (consume_char(scanner, ' ')); 181 | } 182 | 183 | static bool consume_u64_base_10(scanner_t *scanner, uint64_t max, uint64_t *out) 184 | { 185 | if (!follows_digit(*scanner)) 186 | return false; 187 | 188 | uint64_t num = 0; 189 | unsigned char digit; 190 | while (consume_digit(scanner, &digit)) { 191 | int u = digit - '0'; 192 | if (num > (max - u) / 10) { 193 | unconsume_char(scanner); 194 | break; 195 | } 196 | num = num * 10 + u; 197 | } 198 | 199 | *out = num; 200 | return true; 201 | } 202 | 203 | static int hex_digit_to_int(char c) 204 | { 205 | assert(is_hex_digit(c)); 206 | 207 | if (is_lower_alpha(c)) 208 | return c - 'a' + 10; 209 | 210 | if (is_upper_alpha(c)) 211 | return c - 'A' + 10; 212 | 213 | assert(is_digit(c)); 214 | return c - '0'; 215 | } 216 | 217 | static bool consume_u64_base_16(scanner_t *scanner, uint64_t max, uint64_t *out) 218 | { 219 | if (!follows_hex_digit(*scanner)) 220 | return false; 221 | 222 | uint64_t num = 0; 223 | unsigned char digit; 224 | while (consume_hex_digit(scanner, &digit)) { 225 | int u = hex_digit_to_int(digit); 226 | if (num > (max - u) / 16) { 227 | unconsume_char(scanner); 228 | break; 229 | } 230 | num = num * 16 + u; 231 | } 232 | 233 | *out = num; 234 | return true; 235 | } 236 | 237 | static bool consume_u8_base_10(scanner_t *scanner, uint8_t *out) 238 | { 239 | uint64_t buffer; 240 | bool ok = consume_u64_base_10(scanner, UINT8_MAX, &buffer); 241 | assert(!ok || buffer <= UINT8_MAX); 242 | *out = buffer; 243 | return ok; 244 | } 245 | 246 | static bool consume_u16_base_16(scanner_t *scanner, uint16_t *out) 247 | { 248 | uint64_t buffer; 249 | bool ok = consume_u64_base_16(scanner, UINT16_MAX, &buffer); 250 | assert(!ok || buffer <= UINT16_MAX); 251 | *out = buffer; 252 | return ok; 253 | } 254 | 255 | static bool consume_u16_base_10(scanner_t *scanner, uint16_t *out) 256 | { 257 | uint64_t buffer; 258 | bool ok = consume_u64_base_10(scanner, UINT16_MAX, &buffer); 259 | assert(!ok || buffer <= UINT16_MAX); 260 | *out = buffer; 261 | return ok; 262 | } 263 | 264 | // [ : ] // [ [ : ] @ ] { | | "[" "]" } [ : ] [ ] [ ? ] [ # ] 265 | 266 | static bool is_schema_first(unsigned char c) 267 | { 268 | return is_alpha(c); 269 | } 270 | 271 | static bool is_schema(unsigned char c) 272 | { 273 | return is_alpha(c) 274 | || is_digit(c) 275 | || c == '+' 276 | || c == '-' 277 | || c == '.'; 278 | } 279 | 280 | static slice_t parse_schema(scanner_t *scanner) 281 | { 282 | size_t start = scanner->cur; 283 | 284 | slice_t schema = slice_up(scanner, is_schema_first, is_schema); 285 | if (schema.length > 0) 286 | if (!consume_char(scanner, ':')) { 287 | scanner->cur = start; 288 | return EMPTY_SLICE; 289 | } 290 | return schema; 291 | } 292 | 293 | static bool is_username(unsigned char c) 294 | { 295 | return is_unreserved(c) || is_subdelim(c); 296 | } 297 | 298 | static bool is_username_first(unsigned char c) 299 | { 300 | return is_username(c); 301 | } 302 | 303 | static bool is_password(unsigned char c) 304 | { 305 | return is_username(c); 306 | } 307 | 308 | static bool is_password_first(unsigned char c) 309 | { 310 | return is_password(c); 311 | } 312 | 313 | static hp_string_t string_from_slice(const unsigned char *src, slice_t slice) 314 | { 315 | assert(src != NULL); 316 | 317 | if (slice.length == 0) 318 | return EMPTY_STRING; 319 | else 320 | return (hp_string_t) {(char*) src + slice.offset, slice.length}; 321 | } 322 | 323 | static void parse_userinfo(scanner_t *scanner, slice_t *username, slice_t *password) 324 | { 325 | size_t start = scanner->cur; 326 | 327 | *password = EMPTY_SLICE; 328 | 329 | *username = slice_up(scanner, is_username_first, is_username); 330 | if (username->length > 0) { 331 | 332 | if (consume_char(scanner, ':')) 333 | *password = slice_up(scanner, is_password_first, is_password); 334 | 335 | if (!consume_char(scanner, '@')) { 336 | *username = EMPTY_SLICE; 337 | *password = EMPTY_SLICE; 338 | scanner->cur = start; // Rollback changes 339 | } 340 | } 341 | } 342 | 343 | static bool parse_ipv4(scanner_t *scanner, uint32_t *out, hp_error_t *err) 344 | { 345 | uint8_t byte; 346 | uint32_t ipv4 = 0; 347 | 348 | for (int u = 0; u < 3; u++) { 349 | 350 | if (!consume_u8_base_10(scanner, &byte)) { 351 | if (u == 0) 352 | report(err, "Missing IPv4"); 353 | else 354 | report(err, "Missing IPv4 byte"); 355 | return false; 356 | } 357 | ipv4 = (ipv4 << 8) + byte; 358 | 359 | if (!consume_char(scanner, '.')) 360 | return false; 361 | } 362 | 363 | if (!consume_u8_base_10(scanner, &byte)) { 364 | report(err, "Missing IPv4 byte"); 365 | return false; 366 | } 367 | ipv4 = (ipv4 << 8) + byte; 368 | 369 | *out = ipv4; 370 | return true; 371 | } 372 | 373 | static bool parse_ipv6(scanner_t *scanner, uint16_t ipv6[static 8], hp_error_t *err) 374 | { 375 | uint16_t tail[8]; 376 | size_t head_count = 0; 377 | size_t tail_count = 0; 378 | 379 | if (!consume_pair(scanner, "::")) { 380 | 381 | do { 382 | uint16_t word; 383 | if (!consume_u16_base_16(scanner, &word)) { 384 | if (scanner->cur == scanner->len) { 385 | if (head_count == 0) 386 | report(err, "Missing IPv6"); 387 | else 388 | report(err, "Missing IPv6 hex value"); 389 | } else 390 | report(err, "Invalid IPv6"); 391 | return false; 392 | } 393 | 394 | ipv6[head_count++] = word; 395 | 396 | if (head_count == 8) 397 | break; 398 | 399 | if (!consume_char(scanner, ':')) { 400 | report(err, "Missing ':' after IPv6 hex value"); 401 | return false; 402 | } 403 | 404 | } while (!consume_char(scanner, ':')); 405 | } 406 | 407 | if (head_count + tail_count < 8) { 408 | while (follows_hex_digit(*scanner)) { 409 | 410 | // We know the current character is a 411 | // hex digit, therefore [parse_ipv6_word] 412 | // won't fail. 413 | uint16_t word; 414 | (void) consume_u16_base_16(scanner, &word); 415 | 416 | tail[tail_count++] = word; 417 | 418 | if (head_count + tail_count == 8) 419 | break; 420 | 421 | if (!consume_char(scanner, ':')) 422 | break; 423 | } 424 | } 425 | 426 | assert(head_count + tail_count <= 8); 427 | 428 | for (size_t p = 0; p < 8 - head_count - tail_count; p++) 429 | ipv6[head_count + p] = 0; 430 | 431 | for (size_t p = 0; p < tail_count; p++) 432 | ipv6[8 - tail_count + p] = tail[p]; 433 | 434 | return true; 435 | } 436 | 437 | static bool is_hostname(unsigned char c) 438 | { 439 | return is_unreserved(c) || is_subdelim(c); 440 | } 441 | 442 | static bool is_hostname_first(unsigned char c) 443 | { 444 | return is_hostname(c); 445 | } 446 | 447 | static bool parse_host(scanner_t *scanner, hp_host_t *host, hp_error_t *err) 448 | { 449 | if (consume_char(scanner, '[')) { 450 | if (!parse_ipv6(scanner, host->ipv6, err)) 451 | return false; 452 | if (!consume_char(scanner, ']')) { 453 | report(err, "Missing ']' after IPv6"); 454 | return false; 455 | } 456 | host->mode = HP_HOSTMODE_IPV6; 457 | } else { 458 | 459 | uint32_t ipv4; 460 | bool is_ipv4; 461 | 462 | if (follows_digit(*scanner)) { 463 | size_t start = scanner->cur; 464 | is_ipv4 = parse_ipv4(scanner, &ipv4, NULL); 465 | if (!is_ipv4) 466 | scanner->cur = start; 467 | } else 468 | is_ipv4 = false; 469 | 470 | if (is_ipv4) { 471 | host->ipv4 = ipv4; 472 | host->mode = HP_HOSTMODE_IPV4; 473 | } else { 474 | 475 | slice_t hostname = slice_up(scanner, is_hostname_first, is_hostname); 476 | if (hostname.length == 0) { 477 | report(err, "Missing host"); 478 | return false; 479 | } 480 | 481 | host->mode = HP_HOSTMODE_NAME; 482 | host->name = string_from_slice(scanner->src, hostname); 483 | } 484 | } 485 | 486 | host->no_port = !consume_u16_base_10(scanner, &host->port); 487 | return true; 488 | } 489 | 490 | static bool parse_path(scanner_t *scanner, slice_t *out, hp_error_t *err) 491 | { 492 | out->offset = scanner->cur; 493 | 494 | if (!consume_char(scanner, '/')) 495 | if (!follows_pchar(*scanner)) { 496 | report(err, "Missing path"); 497 | return false; 498 | } 499 | 500 | while (consume_pchar(scanner)) { 501 | while (consume_pchar(scanner)); 502 | if (!consume_char(scanner, '/')) 503 | break; 504 | } 505 | 506 | out->length = scanner->cur - out->offset; 507 | return true; 508 | } 509 | 510 | static bool is_query(unsigned char c) 511 | { 512 | return is_pchar(c) || c == '/' || c == '?'; 513 | } 514 | 515 | static bool is_fragment(unsigned char c) 516 | { 517 | return is_pchar(c) || c == '/'; 518 | } 519 | 520 | static bool parse_url(scanner_t *scanner, hp_url_t *url, hp_error_t *err) 521 | { 522 | url->schema = string_from_slice(scanner->src, parse_schema(scanner)); 523 | 524 | if (consume_pair(scanner, "//")) { 525 | 526 | slice_t username, password; 527 | parse_userinfo(scanner, &username, &password); 528 | url->username = string_from_slice(scanner->src, username); 529 | url->password = string_from_slice(scanner->src, password); 530 | 531 | if (!parse_host(scanner, &url->host, err)) 532 | return false; 533 | 534 | if (follows_char(*scanner, '/')) { 535 | /* absolute path */ 536 | // The parsing of the path can't fail 537 | // because we already know there's at 538 | // leat a '/' for it. 539 | slice_t path; 540 | (void) parse_path(scanner, &path, err); 541 | url->path = string_from_slice(scanner->src, path); 542 | } else 543 | url->path = EMPTY_STRING; 544 | 545 | } else { 546 | 547 | url->host.mode = HP_HOSTMODE_NAME; 548 | url->host.name = EMPTY_STRING; 549 | url->host.no_port = true; 550 | url->host.port = 0; 551 | 552 | url->username = EMPTY_STRING; 553 | url->password = EMPTY_STRING; 554 | 555 | // TODO: Since there was no authority, 556 | // the path is non optional. 557 | 558 | if (follows_char(*scanner, '?')) { 559 | report(err, "Missing path before query"); 560 | return false; 561 | } 562 | if (follows_char(*scanner, '#')) { 563 | report(err, "Missing path before fragment"); 564 | return false; 565 | } 566 | 567 | slice_t path; 568 | if (!parse_path(scanner, &path, err)) 569 | return false; 570 | url->path = string_from_slice(scanner->src, path); 571 | } 572 | 573 | url->query = consume_char(scanner, '?') 574 | ? string_from_slice(scanner->src, slice_up(scanner, is_query, is_query)) 575 | : EMPTY_STRING; 576 | 577 | url->fragment = consume_char(scanner, '#') 578 | ? string_from_slice(scanner->src, slice_up(scanner, is_fragment, is_fragment)) 579 | : EMPTY_STRING; 580 | return true; 581 | } 582 | 583 | static bool is_header_name_body(unsigned char c) 584 | { 585 | return is_alpha(c) || is_digit(c) || c == '-'; 586 | } 587 | 588 | static bool is_header_name_head(unsigned char c) 589 | { 590 | return is_header_name_body(c); 591 | } 592 | 593 | static bool is_header_body_body(unsigned char c) 594 | { 595 | return c != '\r'; 596 | } 597 | 598 | static bool is_header_body_head(unsigned char c) 599 | { 600 | return is_header_body_body(c); 601 | } 602 | 603 | static bool parse_header(scanner_t *scanner, hp_header_t *header, hp_error_t *err) 604 | { 605 | slice_t name, body; 606 | 607 | name = slice_up(scanner, is_header_name_head, is_header_name_body); 608 | if (name.length == 0) { 609 | report(err, "Missing header name"); 610 | return false; 611 | } 612 | 613 | if (!consume_char(scanner, ':')) { 614 | report(err, "Missing ':' after header name"); 615 | return false; 616 | } 617 | 618 | body = slice_up(scanner, is_header_body_head, is_header_body_body); 619 | 620 | if (!consume_pair(scanner, "\r\n")) { 621 | report(err, "Missing CRLF after header"); 622 | return false; 623 | } 624 | 625 | header->name = string_from_slice(scanner->src, name); 626 | header->body = string_from_slice(scanner->src, body); 627 | return true; 628 | } 629 | 630 | static bool parse_version(scanner_t *scanner, int *major, int *minor, hp_error_t *err) 631 | { 632 | unsigned char char_major = '0'; 633 | unsigned char char_minor = '0'; 634 | 635 | if (!consume_char(scanner, 'H') || 636 | !consume_char(scanner, 'T') || 637 | !consume_char(scanner, 'T') || 638 | !consume_char(scanner, 'P') || 639 | !consume_char(scanner, '/') || 640 | !consume_digit(scanner, &char_major)) { 641 | report(err, "Invalid version token"); 642 | return false; 643 | } 644 | if (consume_char(scanner, '.')) 645 | if (!consume_digit(scanner, &char_minor)) { 646 | report(err, "Invalid version token"); 647 | return false; 648 | } 649 | *major = char_major - '0'; 650 | *minor = char_minor - '0'; 651 | return true; 652 | } 653 | 654 | static bool get_method_id(hp_string_t str, hp_method_t *method) 655 | { 656 | // CONNECT OPTIONS TRACE PATCH 657 | switch (str.len) { 658 | case 3: 659 | if (str.str[0] == 'G' && 660 | str.str[1] == 'E' && 661 | str.str[2] == 'T') { 662 | *method = HP_METHOD_GET; 663 | return true; 664 | } 665 | if (str.str[0] == 'P' && 666 | str.str[1] == 'U' && 667 | str.str[2] == 'T') { 668 | *method = HP_METHOD_PUT; 669 | return true; 670 | } 671 | break; 672 | 673 | case 4: 674 | if (str.str[0] == 'P' && 675 | str.str[1] == 'O' && 676 | str.str[2] == 'S' && 677 | str.str[3] == 'T') { 678 | *method = HP_METHOD_POST; 679 | return true; 680 | } 681 | if (str.str[0] == 'H' && 682 | str.str[1] == 'E' && 683 | str.str[2] == 'A' && 684 | str.str[3] == 'D') { 685 | *method = HP_METHOD_HEAD; 686 | return true; 687 | } 688 | break; 689 | 690 | case 5: 691 | if (str.str[0] == 'T' && 692 | str.str[1] == 'R' && 693 | str.str[2] == 'A' && 694 | str.str[3] == 'C' && 695 | str.str[4] == 'E') { 696 | *method = HP_METHOD_TRACE; 697 | return true; 698 | } 699 | if (str.str[0] == 'P' && 700 | str.str[1] == 'A' && 701 | str.str[2] == 'T' && 702 | str.str[3] == 'C' && 703 | str.str[4] == 'H') { 704 | *method = HP_METHOD_PATCH; 705 | return true; 706 | } 707 | break; 708 | 709 | case 6: 710 | if (str.str[0] == 'D' && 711 | str.str[1] == 'E' && 712 | str.str[2] == 'L' && 713 | str.str[3] == 'E' && 714 | str.str[4] == 'T' && 715 | str.str[5] == 'E') { 716 | *method = HP_METHOD_DELETE; 717 | return true; 718 | } 719 | break; 720 | } 721 | return false; 722 | } 723 | 724 | static bool is_method_body(unsigned char c) 725 | { 726 | return is_upper_alpha(c); 727 | } 728 | 729 | static bool is_method_head(unsigned char c) 730 | { 731 | return is_method_body(c); 732 | } 733 | 734 | static bool parse_method(scanner_t *scanner, hp_method_t *method, hp_error_t *err) 735 | { 736 | slice_t method_slice = slice_up(scanner, is_method_head, is_method_body); 737 | if (method_slice.length == 0) { 738 | report(err, "Missing method"); 739 | return false; 740 | } 741 | hp_string_t method_string = string_from_slice(scanner->src, method_slice); 742 | if (!get_method_id(method_string, method)) { 743 | report(err, "Invalid method %.*s", (int) method_string.len, method_string.str); 744 | return false; 745 | } 746 | return true; 747 | } 748 | 749 | static bool parse_status_line(scanner_t *scanner, 750 | hp_method_t *method, 751 | hp_url_t *url, 752 | int *major, int *minor, 753 | hp_error_t *err) 754 | { 755 | if (!parse_method(scanner, method, err)) 756 | return false; 757 | 758 | if (!consume_char(scanner, ' ')) { 759 | report(err, "Missing space after method"); 760 | return false; 761 | } 762 | 763 | if (!parse_url(scanner, url, err)) 764 | return false; 765 | 766 | if (!consume_char(scanner, ' ')) { 767 | report(err, "Missing space after URL"); 768 | return false; 769 | } 770 | 771 | if (!parse_version(scanner, major, minor, err)) 772 | return false; 773 | 774 | if (!consume_pair(scanner, "\r\n")) { 775 | report(err, "Missing CRLF after version token"); 776 | return false; 777 | } 778 | 779 | return true; 780 | } 781 | 782 | static void append_header(hp_request_t *req, 783 | hp_header_t header) 784 | { 785 | if (req->num_headers < HP_MAX_HEADERS) 786 | req->headers[req->num_headers++] = header; 787 | } 788 | 789 | bool hp_parse(const char *src, size_t len, 790 | hp_request_t *out, hp_error_t *err) 791 | { 792 | scanner_t scanner = {(unsigned char*) src, len, 0}; 793 | 794 | if (!parse_status_line(&scanner, &out->method, 795 | &out->url, &out->major, 796 | &out->minor, err)) 797 | return false; 798 | 799 | out->num_headers = 0; 800 | while (!consume_pair(&scanner, "\r\n")) { 801 | 802 | hp_header_t header; 803 | if (!parse_header(&scanner, &header, err)) 804 | return false; 805 | 806 | append_header(out, header); 807 | } 808 | 809 | return true; 810 | } 811 | 812 | bool hp_parse_url(const char *src, size_t len, 813 | hp_url_t *url, hp_error_t *err) 814 | { 815 | scanner_t scanner = {(unsigned char*) src, len, 0}; 816 | return parse_url(&scanner, url, err); 817 | } 818 | 819 | static char to_lower(char c) 820 | { 821 | if (is_upper_alpha(c)) 822 | return c - 'A' + 'a'; 823 | else 824 | return c; 825 | } 826 | 827 | static bool case_insensitive_string_compare(hp_string_t s1, hp_string_t s2) 828 | { 829 | if (s1.len != s2.len) 830 | return false; 831 | 832 | for (size_t i = 0; i < s1.len; i++) 833 | if (to_lower(s1.str[i]) != to_lower(s2.str[i])) 834 | return false; 835 | return true; 836 | } 837 | 838 | hp_header_t *hp_get_header(hp_request_t req, const char *name) 839 | { 840 | hp_string_t name2 = {name, strlen(name)}; 841 | for (size_t i = 0; i < req.num_headers; i++) { 842 | hp_header_t *header = req.headers + i; 843 | if (case_insensitive_string_compare(name2, header->name)) 844 | return header; 845 | } 846 | return NULL; 847 | } 848 | 849 | static bool parse_content_length(const char *src, size_t len, size_t *out, hp_error_t *error) 850 | { 851 | scanner_t scanner = {(unsigned char*) src, len, 0}; 852 | consume_spaces(&scanner); 853 | 854 | if (!follows_digit(scanner)) { 855 | report(error, "Non-digit character in Content-Length header"); 856 | return false; 857 | } 858 | 859 | *out = 0; 860 | unsigned char digit; 861 | while (consume_digit(&scanner, &digit)) { 862 | int k = digit - '0'; 863 | if (*out > (SIZE_MAX - k) / 10) { 864 | report(error, "Unsigned integer is too big"); 865 | return false; 866 | } 867 | *out = *out * 10 + k; 868 | } 869 | 870 | if (scanner.cur < scanner.len) { 871 | report(error, "Invalid character '%c'", scanner.src[scanner.cur]); 872 | return false; 873 | } 874 | return true; 875 | } 876 | 877 | bool hp_get_content_length(hp_request_t req, size_t *out, hp_error_t *error) 878 | { 879 | hp_header_t *header = hp_get_header(req, "Content-Length"); 880 | if (header == NULL) 881 | return 0; 882 | return parse_content_length(header->body.str, header->body.len, out, error); 883 | } -------------------------------------------------------------------------------- /src/microtcp.c: -------------------------------------------------------------------------------- 1 | /* 2 | * MIT License 3 | * 4 | * Copyright (c) 2024 Francesco Cozzuto 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | * 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include "ip.h" 35 | #include "arp.h" 36 | #include "tcp.h" 37 | #include "utils.h" 38 | #include "endian.h" 39 | #include "microtcp.h" 40 | #include "tinycthread.h" 41 | 42 | #ifdef MICROTCP_DEBUG 43 | #include 44 | #define MICROTCP_DEBUG_LOG(fmt, ...) do { fprintf(stderr, "MICROTCP :: " fmt "\n", ## __VA_ARGS__); } while (0); 45 | #else 46 | #define MICROTCP_DEBUG_LOG(...) do {} while (0); 47 | #endif 48 | 49 | typedef struct mux_entry_t mux_entry_t; 50 | struct mux_entry_t { 51 | mux_entry_t **mux_prev; 52 | mux_entry_t *mux_next; 53 | mux_entry_t **sock_prev; 54 | mux_entry_t *sock_next; 55 | microtcp_mux_t *mux; // This is set on initialization 56 | // of the parent microtcp_mux_t 57 | // and never changed. 58 | microtcp_socket_t *sock; 59 | void *userp; 60 | int triggered_events; 61 | int events_of_interest; 62 | }; 63 | 64 | struct microtcp_mux_t { 65 | microtcp_t *mtcp; 66 | cnd_t queue_not_empty; 67 | mux_entry_t *free_list; 68 | mux_entry_t *idle_list; 69 | mux_entry_t *ready_queue_head; 70 | mux_entry_t *ready_queue_tail; 71 | mux_entry_t entries[MICROTCP_MAX_MUX_ENTRIES]; 72 | }; 73 | 74 | typedef struct buffer_t buffer_t; 75 | struct buffer_t { 76 | microtcp_t *mtcp; 77 | buffer_t *prev; 78 | buffer_t *next; 79 | size_t used; 80 | char data[1218]; 81 | }; 82 | 83 | typedef enum { 84 | SOCKET_LISTENER, 85 | SOCKET_CONNECTION, 86 | } socket_type_t; 87 | 88 | struct microtcp_socket_t { 89 | microtcp_t *mtcp; 90 | microtcp_socket_t *prev; 91 | microtcp_socket_t *next; 92 | microtcp_errcode_t errcode; 93 | socket_type_t type; 94 | bool block; // If true, operations on this socket will block execution, else they wont 95 | union { 96 | tcp_listener_t *listener; 97 | tcp_connection_t *conn; 98 | }; 99 | 100 | union { 101 | cnd_t something_to_accept; 102 | struct { 103 | cnd_t something_to_recv; 104 | cnd_t something_to_send; 105 | }; 106 | }; 107 | 108 | mux_entry_t *mux_list; 109 | }; 110 | 111 | struct microtcp_t { 112 | 113 | uint64_t last_update_time_ms; 114 | 115 | microtcp_errcode_t errcode; 116 | 117 | bool thread_should_stop; 118 | thrd_t thread_id; 119 | mtx_t lock; 120 | 121 | microtcp_callbacks_t callbacks; 122 | 123 | ip_address_t ip; 124 | mac_address_t mac; 125 | 126 | ip_state_t ip_state; 127 | arp_state_t arp_state; 128 | tcp_state_t tcp_state; 129 | 130 | buffer_t *used_buffer; 131 | buffer_t *wait_buffer_list; 132 | buffer_t *free_buffer_list; 133 | buffer_t buffer_pool[MICROTCP_MAX_BUFFERS]; 134 | 135 | microtcp_socket_t *used_socket_list; 136 | microtcp_socket_t *free_socket_list; 137 | microtcp_socket_t socket_pool[MICROTCP_MAX_SOCKETS]; 138 | }; 139 | 140 | typedef enum { 141 | ETHERNET_PROTOCOL_ARP = 0x0806, 142 | ETHERNET_PROTOCOL_IP = 0x0800, 143 | } ethernet_protocol_t; 144 | 145 | typedef struct { 146 | mac_address_t dst; 147 | mac_address_t src; 148 | uint16_t proto; 149 | } __attribute__((packed)) ethernet_frame_t; 150 | 151 | microtcp_errcode_t microtcp_get_error(microtcp_t *mtcp) 152 | { 153 | return mtcp->errcode; 154 | } 155 | 156 | void microtcp_clear_error(microtcp_t *mtcp) 157 | { 158 | mtcp->errcode = MICROTCP_ERRCODE_NONE; 159 | } 160 | 161 | microtcp_errcode_t microtcp_get_socket_error(microtcp_socket_t *sock) 162 | { 163 | return sock->errcode; 164 | } 165 | 166 | void microtcp_clear_socket_error(microtcp_socket_t *sock) 167 | { 168 | sock->errcode = MICROTCP_ERRCODE_NONE; 169 | } 170 | 171 | const char *microtcp_strerror(microtcp_errcode_t errcode) 172 | { 173 | switch (errcode) { 174 | case MICROTCP_ERRCODE_NONE: return "No error occurred"; 175 | case MICROTCP_ERRCODE_NOCLEAR: return "Uncleared error"; 176 | case MICROTCP_ERRCODE_SOCKETLIMIT: return "Can't create a socket because the socket limit per microtcp instance was reached"; 177 | case MICROTCP_ERRCODE_TCPERROR: return "An error occurred at the TCP layer"; 178 | case MICROTCP_ERRCODE_BADCONDVAR: return "Condition variable error"; 179 | case MICROTCP_ERRCODE_NOTLISTENER: return "Invalid operation on a non-listener socket"; 180 | case MICROTCP_ERRCODE_CANTBLOCK: return "Can't execute a blocking call for this function"; 181 | case MICROTCP_ERRCODE_WOULDBLOCK: return "Can't executa e non-blocking call for this function"; 182 | case MICROTCP_ERRCODE_NOTCONNECTION: return "Invalid operation on a non-connection socket"; 183 | } 184 | return "???"; 185 | } 186 | 187 | static void send_arp_packet(void *data, mac_address_t dst) 188 | { 189 | microtcp_t *mtcp = data; 190 | buffer_t *buffer = mtcp->used_buffer; 191 | 192 | buffer->used = sizeof(ethernet_frame_t) + sizeof(arp_packet_t); 193 | 194 | ethernet_frame_t *frame = (ethernet_frame_t*) buffer->data; 195 | frame->dst = dst; 196 | frame->src = mtcp->mac; 197 | frame->proto = cpu_to_net_u16(ETHERNET_PROTOCOL_ARP); 198 | 199 | int n = mtcp->callbacks.send(mtcp->callbacks.data, buffer->data, buffer->used); 200 | if (n < 0) 201 | MICROTCP_DEBUG_LOG("Couldn't send (%s)", strerror(errno)); 202 | 203 | // Now reset the used buffer 204 | mtcp->used_buffer->used = 0; 205 | } 206 | 207 | static int send_tcp_segment(void *data, ip_address_t ip, 208 | const slice_t *slices, 209 | size_t num_slices) 210 | { 211 | microtcp_t *mtcp = data; 212 | return ip_send_2(&mtcp->ip_state, IP_PROTOCOL_TCP, ip, true, slices, num_slices); 213 | } 214 | 215 | static void move_wait_buffer_to_free_list(buffer_t *buffer) 216 | { 217 | microtcp_t *mtcp = buffer->mtcp; 218 | 219 | if (buffer->prev) 220 | buffer->prev->next = buffer->next; 221 | else 222 | mtcp->wait_buffer_list = buffer->next; 223 | 224 | if (buffer->next) 225 | buffer->next->prev = buffer->prev; 226 | 227 | buffer->prev = NULL; 228 | buffer->next = mtcp->free_buffer_list; 229 | mtcp->free_buffer_list = buffer; 230 | } 231 | 232 | static void mac_resolved(void *data, arp_resolution_status_t status, mac_address_t mac) 233 | { 234 | buffer_t *buffer = data; 235 | microtcp_t *mtcp = buffer->mtcp; 236 | 237 | switch (status) { 238 | 239 | case ARP_RESOLUTION_OK: 240 | { 241 | ethernet_frame_t *frame = (ethernet_frame_t*) buffer->data; 242 | frame->dst = mac; 243 | 244 | int n = mtcp->callbacks.send(mtcp->callbacks.data, buffer->data, buffer->used); 245 | if (n < 0) 246 | MICROTCP_DEBUG_LOG("Couldn't send (%s)", strerror(errno)); 247 | } 248 | break; 249 | 250 | case ARP_RESOLUTION_FAILED: MICROTCP_DEBUG_LOG("MAC resolution failed"); break; 251 | case ARP_RESOLUTION_TIMEOUT: MICROTCP_DEBUG_LOG("MAC resolution timeout"); break; 252 | } 253 | 254 | move_wait_buffer_to_free_list(buffer); 255 | } 256 | 257 | static void move_used_buffer_to_wait_list(microtcp_t *mtcp) 258 | { 259 | buffer_t *buffer = mtcp->used_buffer; 260 | mtcp->used_buffer = NULL; 261 | 262 | buffer->next = mtcp->wait_buffer_list; 263 | if (mtcp->wait_buffer_list) 264 | mtcp->wait_buffer_list->prev = buffer; 265 | mtcp->wait_buffer_list = buffer; 266 | 267 | ip_change_output_buffer(&mtcp->ip_state, NULL, 0); 268 | arp_change_output_buffer(&mtcp->arp_state, NULL, 0); 269 | } 270 | 271 | static void use_a_buffer(microtcp_t *mtcp) 272 | { 273 | // At this moment the network stack has no allocated 274 | // output buffer but wants to allocate one (by calling 275 | // this function). 276 | // It's assumed there is no output buffer, hence: 277 | // 278 | assert(mtcp->used_buffer == NULL); 279 | // 280 | // To allocate a buffer, we need to pop it from the 281 | // buffer free list, which is a singly-linked list of 282 | // unused buffers. Once it's been popped off the list, 283 | // we need to tell the upper layers of the stack that 284 | // this is the new output buffer. 285 | // 286 | // If the free list is empty, no buffer is allocated. 287 | // 288 | if (!mtcp->free_buffer_list) 289 | return; // No free buffers available in the free list. 290 | // 291 | // Pop a buffer from the free list 292 | buffer_t *buffer = mtcp->free_buffer_list; 293 | mtcp->free_buffer_list = buffer->next; 294 | // 295 | // Initialize the buffer 296 | buffer->mtcp = mtcp; 297 | buffer->used = 0; 298 | buffer->prev = NULL; 299 | buffer->next = NULL; 300 | // 301 | // Set it as the output buffer 302 | mtcp->used_buffer = buffer; 303 | // 304 | // Now tell the upper layers where they'll output 305 | // the data, but reserve the first bytes of the buffer 306 | // for the ethernet header. 307 | // 308 | void *output_ptr = buffer->data + sizeof(ethernet_frame_t); 309 | size_t output_max = sizeof(buffer->data) - sizeof(ethernet_frame_t); 310 | ip_change_output_buffer(&mtcp->ip_state, output_ptr, output_max); 311 | arp_change_output_buffer(&mtcp->arp_state, output_ptr, output_max); 312 | } 313 | 314 | static void send_ip_packet(void *data, ip_address_t ip, size_t len) 315 | { 316 | microtcp_t *mtcp = data; 317 | 318 | buffer_t *buffer = mtcp->used_buffer; 319 | if (buffer == NULL) 320 | // The IP layer wants to send something, but no output 321 | // buffer was associated to it. This function should not 322 | // have been called by the IP layer without a buffer. 323 | return; 324 | 325 | buffer->used = sizeof(ethernet_frame_t) + len; 326 | 327 | move_used_buffer_to_wait_list(mtcp); 328 | use_a_buffer(mtcp); 329 | 330 | ethernet_frame_t *frame = (ethernet_frame_t*) buffer->data; 331 | frame->src = mtcp->mac; 332 | frame->dst = MAC_ZERO; // We need to determine it 333 | frame->proto = cpu_to_net_u16(ETHERNET_PROTOCOL_IP); 334 | 335 | arp_resolve_mac(&mtcp->arp_state, ip, buffer, mac_resolved); 336 | } 337 | 338 | static void 339 | tcp_process_segment_wrapper(void *data, ip_address_t ip, const void *packet, size_t len) 340 | { 341 | if (len >= sizeof(tcp_segment_t)) 342 | tcp_process_segment((tcp_state_t*) data, ip, (tcp_segment_t*) packet, len); 343 | } 344 | 345 | static void 346 | process_packet(microtcp_t *mtcp, const void *packet, size_t len) 347 | { 348 | if (len < sizeof(ethernet_frame_t)) 349 | return; 350 | 351 | const ethernet_frame_t *frame = packet; 352 | 353 | switch (net_to_cpu_u16(frame->proto)) { 354 | 355 | case ETHERNET_PROTOCOL_ARP: arp_process_packet(&mtcp->arp_state, frame+1, len - sizeof(ethernet_frame_t)); break; 356 | case ETHERNET_PROTOCOL_IP : ip_process_packet(&mtcp->ip_state, frame+1, len - sizeof(ethernet_frame_t)); break; 357 | 358 | default: 359 | // Unsupported ethertype 360 | //MICROTCP_DEBUG_LOG("Ignoring packet with ethertype %4x", frame->proto); 361 | break; 362 | } 363 | } 364 | 365 | bool microtcp_process_packet(microtcp_t *mtcp, const void *packet, size_t len) 366 | { 367 | if (mtcp->errcode != MICROTCP_ERRCODE_NONE) { 368 | mtcp->errcode = MICROTCP_ERRCODE_NOCLEAR; 369 | return false; 370 | } 371 | 372 | mtx_lock(&mtcp->lock); 373 | process_packet(mtcp, packet, len); 374 | mtx_unlock(&mtcp->lock); 375 | return true; 376 | } 377 | 378 | static uint64_t get_time_in_ms(void) 379 | { 380 | struct timespec t; 381 | clock_gettime(CLOCK_MONOTONIC, &t); 382 | return t.tv_sec * 1000 + t.tv_nsec / 1000000; 383 | } 384 | 385 | bool microtcp_step(microtcp_t *mtcp) 386 | { 387 | if (mtcp->errcode != MICROTCP_ERRCODE_NONE) { 388 | mtcp->errcode = MICROTCP_ERRCODE_NOCLEAR; 389 | return false; 390 | } 391 | 392 | char packet[1024]; // This buffer is the bottleneck for the 393 | // maximum packet size that can be processed. 394 | 395 | // The call to [recv] (which is assumed to be blocking) 396 | // needs to be out of the critical section to give other 397 | // threads the ability to progress in the mean time. 398 | int size = mtcp->callbacks.recv(mtcp->callbacks.data, packet, sizeof(packet)); 399 | if (size < 0) 400 | return true; 401 | 402 | uint64_t current_time_ms = get_time_in_ms(); 403 | 404 | mtx_lock(&mtcp->lock); 405 | { 406 | process_packet(mtcp, packet, size); 407 | 408 | uint64_t ms = (current_time_ms - mtcp->last_update_time_ms); 409 | 410 | if (ms > 0) { 411 | ip_ms_passed(&mtcp->ip_state, ms); 412 | arp_ms_passed(&mtcp->arp_state, ms); 413 | tcp_ms_passed(&mtcp->tcp_state, ms); 414 | mtcp->last_update_time_ms = current_time_ms; 415 | } 416 | } 417 | mtx_unlock(&mtcp->lock); 418 | return true; 419 | } 420 | 421 | static int loop(void *data) 422 | { 423 | microtcp_t *mtcp = data; 424 | while (!mtcp->thread_should_stop) 425 | microtcp_step(mtcp); 426 | return 0; 427 | } 428 | 429 | microtcp_t *microtcp_create_using_callbacks(const char *ip, const char *mac, 430 | microtcp_callbacks_t callbacks) 431 | { 432 | mac_address_t parsed_mac; 433 | if (mac == NULL) { 434 | // Generate a random MAC 435 | parsed_mac = generate_random_mac(); 436 | } else { 437 | if (!parse_mac(mac, mac ? strlen(mac) : 0, &parsed_mac)) 438 | return NULL; 439 | } 440 | 441 | ip_address_t parsed_ip; 442 | if (!parse_ip(ip, &parsed_ip)) 443 | return NULL; 444 | 445 | microtcp_t *mtcp = malloc(sizeof(microtcp_t)); 446 | if (mtcp == NULL) 447 | return NULL; 448 | 449 | mtcp->errcode = MICROTCP_ERRCODE_NONE; 450 | mtcp->ip = parsed_ip; 451 | mtcp->mac = parsed_mac; 452 | mtcp->callbacks = callbacks; 453 | mtcp->last_update_time_ms = get_time_in_ms(); 454 | 455 | mtcp->used_buffer = NULL; 456 | mtcp->wait_buffer_list = NULL; 457 | mtcp->free_buffer_list = mtcp->buffer_pool; 458 | for (size_t i = 0; i < MICROTCP_MAX_BUFFERS-1; i++) { 459 | mtcp->buffer_pool[i].mtcp = NULL; 460 | mtcp->buffer_pool[i].prev = NULL; 461 | mtcp->buffer_pool[i].next = mtcp->buffer_pool + i+1; 462 | } 463 | mtcp->buffer_pool[MICROTCP_MAX_BUFFERS-1].mtcp = NULL; 464 | mtcp->buffer_pool[MICROTCP_MAX_BUFFERS-1].prev = NULL; 465 | mtcp->buffer_pool[MICROTCP_MAX_BUFFERS-1].next = NULL; 466 | 467 | mtcp->used_socket_list = NULL; 468 | mtcp->free_socket_list = mtcp->socket_pool; 469 | for (size_t i = 0; i < MICROTCP_MAX_SOCKETS-1; i++) { 470 | mtcp->socket_pool[i].mtcp = NULL; 471 | mtcp->socket_pool[i].prev = NULL; 472 | mtcp->socket_pool[i].next = mtcp->socket_pool + i + 1; 473 | } 474 | mtcp->socket_pool[MICROTCP_MAX_SOCKETS-1].mtcp = NULL; 475 | mtcp->socket_pool[MICROTCP_MAX_SOCKETS-1].prev = NULL; 476 | mtcp->socket_pool[MICROTCP_MAX_SOCKETS-1].next = NULL; 477 | 478 | ip_init(&mtcp->ip_state, parsed_ip, mtcp, send_ip_packet); 479 | if (!ip_plug_protocol(&mtcp->ip_state, IP_PROTOCOL_TCP, &mtcp->tcp_state, tcp_process_segment_wrapper)) { 480 | free(mtcp); 481 | return NULL; 482 | } 483 | 484 | arp_init(&mtcp->arp_state, parsed_ip, parsed_mac, mtcp, send_arp_packet); 485 | 486 | tcp_init(&mtcp->tcp_state, parsed_ip, (tcp_callbacks_t) { 487 | .data = mtcp, 488 | .send = send_tcp_segment, 489 | }); 490 | 491 | use_a_buffer(mtcp); 492 | 493 | { 494 | if (mtx_init(&mtcp->lock, mtx_recursive) != thrd_success) { 495 | ip_free(&mtcp->ip_state); 496 | arp_free(&mtcp->arp_state); 497 | tcp_free(&mtcp->tcp_state); 498 | free(mtcp); 499 | return NULL; 500 | } 501 | mtcp->thread_should_stop = false; 502 | if (thrd_create(&mtcp->thread_id, loop, mtcp) != thrd_success) { 503 | ip_free(&mtcp->ip_state); 504 | arp_free(&mtcp->arp_state); 505 | tcp_free(&mtcp->tcp_state); 506 | mtx_destroy(&mtcp->lock); 507 | free(mtcp); 508 | return NULL; 509 | } 510 | } 511 | 512 | MICROTCP_DEBUG_LOG("Instanciated (" 513 | "debug=" 514 | #ifdef MICROTCP_DEBUG 515 | "yes" 516 | #else 517 | "no" 518 | #endif 519 | ")"); 520 | 521 | return mtcp; 522 | } 523 | 524 | static void log_callback_for_tuntap_library(int level, const char *errmsg) 525 | { 526 | const char *name; 527 | 528 | switch(level) { 529 | case TUNTAP_LOG_DEBUG : name = "Debug"; break; 530 | case TUNTAP_LOG_INFO : name = "Info"; break; 531 | case TUNTAP_LOG_NOTICE: name = "Notice"; break; 532 | case TUNTAP_LOG_WARN : name = "Warning"; break; 533 | case TUNTAP_LOG_ERR : name = "Error"; break; 534 | case TUNTAP_LOG_NONE: 535 | default: 536 | name = NULL; 537 | break; 538 | } 539 | if (name == NULL) { 540 | MICROTCP_DEBUG_LOG("%s (from the tap library)", errmsg); 541 | } else { 542 | MICROTCP_DEBUG_LOG("[%s] %s (from the tap library)", name, errmsg); 543 | } 544 | } 545 | 546 | bool microtcp_callbacks_create_for_tap(const char *ip, const char *mac, 547 | microtcp_callbacks_t *callbacks) 548 | { 549 | assert(ip); 550 | 551 | struct device *dev = tuntap_init(); 552 | if (!dev) 553 | return false; 554 | 555 | // This must be set AFTER tuntap_init because 556 | // it sets the callback function to the default 557 | // callback which writes to stderr. 558 | tuntap_log_set_cb(log_callback_for_tuntap_library); 559 | 560 | int netmask = 24; // TODO: Make this configurable 561 | 562 | if (tuntap_start(dev, TUNTAP_MODE_ETHERNET, TUNTAP_ID_ANY)) 563 | goto cleanup; 564 | 565 | tuntap_set_ip(dev, ip, netmask); 566 | tuntap_set_hwaddr(dev, mac ? mac : "random"); 567 | 568 | if (tuntap_up(dev)) 569 | goto cleanup; 570 | 571 | *callbacks = (microtcp_callbacks_t) { 572 | .data = dev, 573 | .free = (void(*)(void*)) tuntap_release, 574 | .recv = (int(*)(void*, void*, size_t)) tuntap_read, 575 | .send = (int(*)(void*, const void*, size_t)) tuntap_write, 576 | }; 577 | 578 | return true; 579 | 580 | cleanup: 581 | tuntap_release(dev); 582 | return false; 583 | } 584 | 585 | microtcp_t *microtcp_create(const char *tap_ip, const char *stack_ip, 586 | const char *tap_mac, const char *stack_mac) 587 | { 588 | microtcp_callbacks_t callbacks; 589 | if (!microtcp_callbacks_create_for_tap(tap_ip, tap_mac, &callbacks)) 590 | return NULL; 591 | microtcp_t *mtcp = microtcp_create_using_callbacks(stack_ip, stack_mac, callbacks); 592 | if (!mtcp) 593 | callbacks.free(callbacks.data); 594 | return mtcp; 595 | } 596 | 597 | void microtcp_destroy(microtcp_t *mtcp) 598 | { 599 | MICROTCP_DEBUG_LOG("Stopping thread"); 600 | mtcp->thread_should_stop = true; 601 | thrd_join(mtcp->thread_id, NULL); 602 | mtx_destroy(&mtcp->lock); 603 | MICROTCP_DEBUG_LOG("Thread stopped"); 604 | 605 | ip_free(&mtcp->ip_state); 606 | arp_free(&mtcp->arp_state); 607 | tcp_free(&mtcp->tcp_state); 608 | 609 | if (mtcp->callbacks.free) 610 | mtcp->callbacks.free(mtcp->callbacks.data); 611 | } 612 | 613 | static microtcp_socket_t* 614 | pop_socket_struct_from_free_list(microtcp_t *mtcp) 615 | { 616 | microtcp_socket_t *socket = mtcp->free_socket_list; 617 | if (socket) 618 | mtcp->free_socket_list = socket->next; 619 | return socket; 620 | } 621 | 622 | static void 623 | push_unlinked_socket_into_used_list(microtcp_socket_t *socket) 624 | { 625 | microtcp_t *mtcp = socket->mtcp; 626 | 627 | socket->next = mtcp->used_socket_list; 628 | if (mtcp->used_socket_list) 629 | mtcp->used_socket_list->prev = socket; 630 | mtcp->used_socket_list = socket; 631 | } 632 | 633 | static void 634 | unlink_socket_from_used_socket_list(microtcp_socket_t *socket) 635 | { 636 | microtcp_t *mtcp = socket->mtcp; 637 | 638 | if (socket->prev) 639 | socket->prev->next = socket->next; 640 | else 641 | mtcp->used_socket_list = socket->next; 642 | 643 | if (socket->next) 644 | socket->next->prev = socket->prev; 645 | 646 | socket->prev = NULL; 647 | socket->next = NULL; 648 | } 649 | 650 | static void 651 | push_unlinked_socket_into_free_list(microtcp_t *mtcp, microtcp_socket_t *socket) 652 | { 653 | socket->prev = NULL; 654 | socket->next = mtcp->free_socket_list; 655 | mtcp->free_socket_list = socket; 656 | } 657 | 658 | static void 659 | signal_events_to_muxes_associated_to_socket(microtcp_socket_t *socket, int events); 660 | 661 | static void listener_event_callback(void *data, tcp_listenevent_t event) 662 | { 663 | microtcp_socket_t *socket = data; 664 | 665 | int flags = 0; 666 | switch (event) { 667 | case TCP_LISTENEVENT_ACCEPT: 668 | cnd_signal(&socket->something_to_accept); 669 | flags = MICROTCP_MUX_ACCEPT; 670 | MICROTCP_DEBUG_LOG("Signaling ACCEPT to muxes"); 671 | break; 672 | } 673 | if (flags) 674 | signal_events_to_muxes_associated_to_socket(socket, flags); 675 | } 676 | 677 | microtcp_socket_t *microtcp_open(microtcp_t *mtcp, uint16_t port) 678 | { 679 | if (mtcp->errcode != MICROTCP_ERRCODE_NONE) { 680 | mtcp->errcode = MICROTCP_ERRCODE_NOCLEAR; 681 | return NULL; 682 | } 683 | 684 | microtcp_socket_t *socket = NULL; 685 | mtx_lock(&mtcp->lock); 686 | 687 | socket = pop_socket_struct_from_free_list(mtcp); 688 | if (!socket) { 689 | mtcp->errcode = MICROTCP_ERRCODE_SOCKETLIMIT; 690 | goto unlock_and_exit; // Socket limit reached 691 | } 692 | 693 | tcp_listener_t *listener = tcp_listener_create(&mtcp->tcp_state, port, false, socket, listener_event_callback); 694 | if (listener == NULL) { 695 | // FIXME: This error code should be more specific, 696 | // but the TCP module isn't stable yet 697 | mtcp->errcode = MICROTCP_ERRCODE_TCPERROR; 698 | push_unlinked_socket_into_free_list(mtcp, socket); 699 | goto unlock_and_exit; 700 | } 701 | 702 | socket->mtcp = mtcp; 703 | socket->prev = NULL; 704 | socket->next = NULL; 705 | socket->type = SOCKET_LISTENER; 706 | socket->block = true; 707 | socket->errcode = MICROTCP_ERRCODE_NONE; 708 | socket->listener = listener; 709 | socket->mux_list = NULL; 710 | 711 | if (cnd_init(&socket->something_to_accept) != thrd_success) { 712 | mtcp->errcode = MICROTCP_ERRCODE_BADCONDVAR; 713 | push_unlinked_socket_into_free_list(mtcp, socket); 714 | tcp_listener_destroy(listener); 715 | goto unlock_and_exit; 716 | } 717 | 718 | push_unlinked_socket_into_used_list(socket); 719 | 720 | unlock_and_exit: 721 | mtx_unlock(&mtcp->lock); 722 | return socket; 723 | } 724 | 725 | void microtcp_close(microtcp_socket_t *socket) 726 | { 727 | if (!socket) 728 | return; 729 | 730 | microtcp_t *mtcp = socket->mtcp; 731 | 732 | mtx_lock(&mtcp->lock); 733 | { 734 | // Unregister from all multiplexers 735 | while (socket->mux_list) { 736 | // The unregister operation only has 737 | // an effect when all of the triggered 738 | // events of the socket are consumed, 739 | // so to unregister immediately we need 740 | // to untrigger the events 741 | socket->mux_list->triggered_events = 0; 742 | microtcp_mux_unregister(socket->mux_list->mux, socket, ~0); 743 | } 744 | 745 | switch (socket->type) { 746 | 747 | case SOCKET_LISTENER: 748 | cnd_destroy(&socket->something_to_accept); 749 | tcp_listener_destroy(socket->listener); 750 | break; 751 | 752 | case SOCKET_CONNECTION: 753 | if (socket->conn) // Only need to close the connection 754 | // if the peer didn't already. 755 | tcp_connection_destroy(socket->conn); 756 | break; 757 | } 758 | 759 | unlink_socket_from_used_socket_list(socket); 760 | push_unlinked_socket_into_free_list(mtcp, socket); 761 | } 762 | mtx_unlock(&mtcp->lock); 763 | } 764 | 765 | static void conn_event_callback(void *data, tcp_connevent_t event) 766 | { 767 | microtcp_socket_t *socket = data; 768 | 769 | int flags = 0; 770 | switch (event) { 771 | 772 | case TCP_CONNEVENT_RECV: 773 | MICROTCP_DEBUG_LOG("Signal RECV"); 774 | cnd_signal(&socket->something_to_recv); 775 | flags = MICROTCP_MUX_RECV; 776 | break; 777 | 778 | case TCP_CONNEVENT_SEND: 779 | MICROTCP_DEBUG_LOG("Signal SEND"); 780 | cnd_signal(&socket->something_to_send); 781 | flags = MICROTCP_MUX_SEND; 782 | break; 783 | 784 | case TCP_CONNEVENT_RESET: 785 | MICROTCP_DEBUG_LOG("Signal RESET"); 786 | socket->conn = NULL; 787 | break; 788 | 789 | case TCP_CONNEVENT_CLOSE: 790 | MICROTCP_DEBUG_LOG("Signal CLOSE"); 791 | socket->conn = NULL; 792 | break; 793 | } 794 | if (flags) 795 | signal_events_to_muxes_associated_to_socket(socket, flags); 796 | } 797 | 798 | void microtcp_set_blocking(microtcp_socket_t *socket, bool block) 799 | { 800 | socket->block = block; 801 | } 802 | 803 | static bool init_conn_socket(microtcp_socket_t *socket, 804 | microtcp_socket_t *parent, 805 | tcp_connection_t *conn) 806 | { 807 | socket->mtcp = parent->mtcp; 808 | socket->prev = NULL; 809 | socket->next = NULL; 810 | socket->type = SOCKET_CONNECTION; 811 | socket->block = true; 812 | socket->conn = conn; 813 | socket->errcode = MICROTCP_ERRCODE_NONE; 814 | socket->mux_list = NULL; 815 | 816 | if (cnd_init(&socket->something_to_recv) != thrd_success) { 817 | socket->errcode = MICROTCP_ERRCODE_BADCONDVAR; 818 | return false; 819 | } 820 | 821 | if (cnd_init(&socket->something_to_send) != thrd_success) { 822 | socket->errcode = MICROTCP_ERRCODE_BADCONDVAR; 823 | cnd_destroy(&socket->something_to_recv); 824 | return false; 825 | } 826 | 827 | return true; 828 | } 829 | 830 | static microtcp_socket_t *accept_inner(microtcp_socket_t *socket) 831 | { 832 | microtcp_t *mtcp = socket->mtcp; 833 | 834 | if (socket->errcode != MICROTCP_ERRCODE_NONE) { 835 | socket->errcode = MICROTCP_ERRCODE_NOCLEAR; 836 | return NULL; 837 | } 838 | 839 | if (socket->type != SOCKET_LISTENER) { 840 | socket->errcode = MICROTCP_ERRCODE_NOTLISTENER; 841 | return NULL; // Can't accept from a non-listening socket 842 | } 843 | 844 | microtcp_socket_t *accepted = pop_socket_struct_from_free_list(mtcp); 845 | if (!accepted) { 846 | socket->errcode = MICROTCP_ERRCODE_SOCKETLIMIT; 847 | return NULL; // Socket limit reached 848 | } 849 | 850 | tcp_connection_t *conn = tcp_listener_accept(socket->listener, accepted, conn_event_callback); 851 | if (conn == NULL) { 852 | 853 | if (!socket->block) { 854 | socket->errcode = MICROTCP_ERRCODE_WOULDBLOCK; 855 | return NULL; 856 | } 857 | 858 | do { 859 | if (cnd_wait(&socket->something_to_accept, &mtcp->lock) != thrd_success) { 860 | socket->errcode = MICROTCP_ERRCODE_BADCONDVAR; 861 | push_unlinked_socket_into_free_list(mtcp, accepted); 862 | return NULL; 863 | } 864 | conn = tcp_listener_accept(socket->listener, accepted, conn_event_callback); 865 | } while (!conn); 866 | } 867 | 868 | if (!init_conn_socket(accepted, socket, conn)) { 869 | push_unlinked_socket_into_free_list(mtcp, accepted); 870 | return NULL; 871 | } 872 | 873 | push_unlinked_socket_into_used_list(accepted); 874 | return accepted; 875 | } 876 | 877 | microtcp_socket_t *microtcp_accept(microtcp_socket_t *socket) 878 | { 879 | if (!socket) 880 | return NULL; 881 | microtcp_socket_t *accepted; 882 | microtcp_t *mtcp = socket->mtcp; 883 | mtx_lock(&mtcp->lock); 884 | accepted = accept_inner(socket); 885 | mtx_unlock(&mtcp->lock); 886 | return accepted; 887 | } 888 | 889 | static int recv_inner(microtcp_socket_t *socket, 890 | void *dst, size_t len) 891 | { 892 | microtcp_t *mtcp = socket->mtcp; 893 | 894 | if (socket->errcode != MICROTCP_ERRCODE_NONE) { 895 | socket->errcode = MICROTCP_ERRCODE_NOCLEAR; 896 | return -1; 897 | } 898 | 899 | if (socket->type != SOCKET_CONNECTION) { 900 | socket->errcode = MICROTCP_ERRCODE_NOTCONNECTION; 901 | return -1; 902 | } 903 | 904 | if (socket->conn == NULL) 905 | return 0; 906 | 907 | // Don't read more bytes than the maximum number 908 | // representable by an "int" to not overflow the 909 | // return value. 910 | if (len > (size_t) INT_MAX) len = INT_MAX; 911 | 912 | size_t num = tcp_connection_recv(socket->conn, dst, len); 913 | 914 | if (num == 0) { 915 | 916 | if (!socket->block) { 917 | socket->errcode = MICROTCP_ERRCODE_WOULDBLOCK; 918 | return -1; 919 | } 920 | 921 | do { 922 | if (cnd_wait(&socket->something_to_recv, &mtcp->lock) != thrd_success) { 923 | socket->errcode = MICROTCP_ERRCODE_BADCONDVAR; 924 | return -1; 925 | } 926 | num = tcp_connection_recv(socket->conn, dst, len); 927 | } while (num == 0); 928 | } 929 | 930 | assert(num <= INT_MAX); 931 | return (int) num; 932 | } 933 | 934 | int microtcp_recv(microtcp_socket_t *socket, 935 | void *dst, size_t len) 936 | { 937 | if (!socket) 938 | return -1; 939 | int res; 940 | microtcp_t *mtcp = socket->mtcp; 941 | mtx_lock(&mtcp->lock); 942 | res = recv_inner(socket, dst, len); 943 | mtx_unlock(&mtcp->lock); 944 | return res; 945 | } 946 | 947 | static int send_inner(microtcp_socket_t *socket, 948 | const void *src, size_t len) 949 | { 950 | microtcp_t *mtcp = socket->mtcp; 951 | 952 | if (socket->type != SOCKET_CONNECTION) { 953 | socket->errcode = MICROTCP_ERRCODE_NOTCONNECTION; 954 | return -1; 955 | } 956 | 957 | if (socket->conn == NULL) 958 | return 0; 959 | 960 | // As for "recv", never send a number of bytes 961 | // bigger than what can be represented by the 962 | // return value. 963 | if (len > INT_MAX) len = INT_MAX; 964 | 965 | size_t num = tcp_connection_send(socket->conn, src, len); 966 | if (num == 0) { 967 | 968 | if (!socket->block) { 969 | socket->errcode = MICROTCP_ERRCODE_WOULDBLOCK; 970 | return -1; 971 | } 972 | 973 | do { 974 | if (cnd_wait(&socket->something_to_send, &mtcp->lock) != thrd_success) { 975 | socket->errcode = MICROTCP_ERRCODE_BADCONDVAR; 976 | return -1; 977 | } 978 | num = tcp_connection_send(socket->conn, src, len); 979 | } while (num == 0); 980 | } 981 | 982 | assert(num <= INT_MAX); 983 | return (int) num; 984 | } 985 | 986 | int microtcp_send(microtcp_socket_t *socket, 987 | const void *src, size_t len) 988 | { 989 | if (!socket) 990 | return -1; 991 | int res; 992 | microtcp_t *mtcp = socket->mtcp; 993 | mtx_lock(&mtcp->lock); 994 | res = send_inner(socket, src, len); 995 | mtx_unlock(&mtcp->lock); 996 | return res; 997 | } 998 | 999 | microtcp_mux_t *microtcp_mux_create(microtcp_t *mtcp) 1000 | { 1001 | microtcp_mux_t *mux = malloc(sizeof(microtcp_mux_t)); 1002 | if (!mux) 1003 | return NULL; 1004 | 1005 | mux->mtcp = mtcp; 1006 | 1007 | // Build the free list 1008 | static_assert(MICROTCP_MAX_MUX_ENTRIES > 1); 1009 | const int max = MICROTCP_MAX_MUX_ENTRIES; 1010 | for (int i = 1; i < max-1; i++) { 1011 | mux->entries[i].mux = mux; // This will be never changed 1012 | mux->entries[i].mux_prev = &mux->entries[i-1].mux_next; 1013 | mux->entries[i].mux_next = &mux->entries[i+1]; 1014 | } 1015 | mux->entries[0].mux = mux; // Never changed 1016 | mux->entries[0].mux_prev = &mux->free_list; 1017 | mux->entries[0].mux_next = &mux->entries[1]; 1018 | mux->entries[max-1].mux = mux; // Never changed 1019 | mux->entries[max-1].mux_prev = &mux->entries[max-2].mux_next; 1020 | mux->entries[max-1].mux_next = NULL; 1021 | 1022 | mux->idle_list = NULL; 1023 | mux->free_list = mux->entries; 1024 | mux->ready_queue_head = NULL; 1025 | mux->ready_queue_tail = NULL; 1026 | 1027 | if (cnd_init(&mux->queue_not_empty) != thrd_success) { 1028 | free(mux); 1029 | return NULL; 1030 | } 1031 | 1032 | return mux; 1033 | } 1034 | 1035 | static bool mux_poll(microtcp_mux_t *mux, microtcp_muxevent_t *ev); 1036 | 1037 | void microtcp_mux_destroy(microtcp_mux_t *mux) 1038 | { 1039 | // Unregister all idle sockets 1040 | // Idle entries don't have pending events 1041 | // to deliver so by unregistering them the 1042 | // entry is unlinked. 1043 | while (mux->idle_list) 1044 | microtcp_mux_unregister(mux, mux->idle_list->sock, ~0); 1045 | 1046 | // Consume all previously reported events 1047 | // to make sure that when unregistering 1048 | // the entries are actually removed 1049 | while (mux_poll(mux, NULL)); 1050 | 1051 | // Unreagister all sockets that have events 1052 | while (mux->ready_queue_head) { 1053 | mux_entry_t *entry = mux->ready_queue_head; 1054 | microtcp_mux_unregister(mux, entry->sock, ~0); 1055 | 1056 | // Since all events were consumed beforehand 1057 | // we're sure the entry was removed. 1058 | assert(entry != mux->ready_queue_head); 1059 | } 1060 | 1061 | cnd_destroy(&mux->queue_not_empty); 1062 | free(mux); 1063 | } 1064 | 1065 | static mux_entry_t* 1066 | find_socket_and_mux_entry(microtcp_mux_t *mux, microtcp_socket_t *sock) 1067 | { 1068 | mux_entry_t *entry = sock->mux_list; 1069 | while (entry) { 1070 | if (entry->mux == mux) 1071 | break; 1072 | entry = entry->sock_next; 1073 | } 1074 | return entry; 1075 | } 1076 | 1077 | static void 1078 | move_mux_entry_to_free_list(mux_entry_t *entry) 1079 | { 1080 | microtcp_mux_t *mux = entry->mux; 1081 | 1082 | // If the entry is in a list, unlink it 1083 | if (mux->ready_queue_tail == entry) 1084 | mux->ready_queue_tail = entry->mux_next; 1085 | if (entry->mux_prev) 1086 | *entry->mux_prev = entry->mux_next; 1087 | if (entry->sock_prev) 1088 | *entry->sock_prev = entry->sock_next; 1089 | 1090 | // Put the structure into the free list 1091 | entry->mux_prev = &mux->free_list; 1092 | entry->mux_next = mux->free_list; 1093 | if (mux->free_list) 1094 | mux->free_list->mux_prev = &entry->mux_next; 1095 | mux->free_list = entry; 1096 | } 1097 | 1098 | static void 1099 | move_mux_entry_to_idle_list(mux_entry_t *entry) 1100 | { 1101 | microtcp_mux_t *mux = entry->mux; 1102 | 1103 | // To be moved to the idle list the entry 1104 | // must be associated to a socket so it 1105 | // must be in a socket mux list, therefore 1106 | // it must be true that 1107 | assert(entry->sock_prev); // not null iff the entry is in a mux list 1108 | 1109 | // Make sure the entry is unlinked relative 1110 | // to the lists in the mux 1111 | if (mux->ready_queue_tail == entry) 1112 | mux->ready_queue_tail = entry->mux_next; 1113 | if (entry->mux_prev) 1114 | *entry->mux_prev = entry->mux_next; 1115 | 1116 | // Now actually insert it into the idle list 1117 | entry->mux_prev = &mux->idle_list; 1118 | entry->mux_next = mux->idle_list; 1119 | if (mux->idle_list) 1120 | mux->idle_list->mux_prev = &entry->mux_next; 1121 | mux->idle_list = entry; 1122 | } 1123 | 1124 | bool microtcp_mux_unregister(microtcp_mux_t *mux, microtcp_socket_t *sock, int events) 1125 | { 1126 | mtx_lock(&mux->mtcp->lock); 1127 | 1128 | // There's no need to check that mux 1129 | // and socket have the same mtcp because 1130 | // if it's different it will result that 1131 | // the socket isn't registered into the 1132 | // mux. 1133 | 1134 | mux_entry_t *entry = find_socket_and_mux_entry(mux, sock); 1135 | if (!entry) { 1136 | // This socket wasn't registered into the mux 1137 | mtx_unlock(&mux->mtcp->lock); 1138 | return false; 1139 | } 1140 | 1141 | // Unset the events of interest 1142 | entry->events_of_interest &= ~events; 1143 | 1144 | if (entry->triggered_events) { 1145 | // NOTE: Since we modified "events_of_interest" 1146 | // but not "triggered_events", any previously 1147 | // triggered events that were now unregistered 1148 | // will still be delivered to the user. 1149 | // 1150 | // Though when events are delivered, if all 1151 | // events registered were all unregistered, 1152 | // the socket is removed from the mux. 1153 | } else 1154 | // No events were previously reported so we can 1155 | // move the entry to the free list. 1156 | move_mux_entry_to_free_list(entry); 1157 | 1158 | mtx_unlock(&mux->mtcp->lock); 1159 | return true; 1160 | } 1161 | 1162 | bool microtcp_mux_register(microtcp_mux_t *mux, microtcp_socket_t *sock, int events, void *userp) 1163 | { 1164 | mtx_lock(&mux->mtcp->lock); 1165 | 1166 | if (mux->mtcp != sock->mtcp) { 1167 | mtx_unlock(&mux->mtcp->lock); 1168 | return false; // mux and socket are associated to different microtcp stacks 1169 | } 1170 | 1171 | if (events == 0) { 1172 | mtx_unlock(&mux->mtcp->lock); 1173 | return true; // Nothing to be done 1174 | } 1175 | 1176 | mux_entry_t *entry = find_socket_and_mux_entry(mux, sock); 1177 | if (!entry) { 1178 | // This is the first time that the socket is registered. 1179 | // Create an entry for it 1180 | if (mux->free_list == NULL) { 1181 | // The entry limit was reached. 1182 | // It's impossible to register the socket at this time 1183 | mtx_unlock(&mux->mtcp->lock); 1184 | return false; 1185 | } 1186 | 1187 | // Pop from the free list 1188 | entry = mux->free_list; 1189 | *entry->mux_prev = entry->mux_next; 1190 | 1191 | // Push it into the idle list of the mux 1192 | entry->mux_prev = &mux->idle_list; 1193 | entry->mux_next = mux->idle_list; 1194 | if (mux->idle_list) 1195 | mux->idle_list->mux_prev = &entry->mux_next; 1196 | mux->idle_list = entry; 1197 | 1198 | // Push it into the socket mux list 1199 | entry->sock_prev = &sock->mux_list; 1200 | entry->sock_next = sock->mux_list; 1201 | if (sock->mux_list) 1202 | sock->mux_list->sock_prev = &entry->sock_next; 1203 | sock->mux_list = entry; 1204 | 1205 | // Initialize the entry 1206 | entry->sock = sock; 1207 | entry->userp = userp; 1208 | entry->triggered_events = 0; 1209 | entry->events_of_interest = 0; 1210 | // entry->mux = mux; This isn't necessary because the mux field 1211 | // is initialized once with the mux and never 1212 | // changed. 1213 | } 1214 | 1215 | entry->events_of_interest |= events; 1216 | 1217 | mtx_unlock(&mux->mtcp->lock); 1218 | return true; 1219 | } 1220 | 1221 | static bool mux_poll(microtcp_mux_t *mux, microtcp_muxevent_t *ev) 1222 | { 1223 | 1224 | if (!mux->ready_queue_head) 1225 | return false; // No events occurred 1226 | 1227 | // Get the tail of the queue (without popping it) 1228 | mux_entry_t *entry = mux->ready_queue_head; 1229 | 1230 | // If this socket was in the ready queue 1231 | // it must have triggered events 1232 | assert(entry->triggered_events); 1233 | 1234 | if (ev) { 1235 | ev->userp = entry->userp; 1236 | ev->events = entry->triggered_events; 1237 | ev->socket = entry->sock; 1238 | } 1239 | 1240 | // Unmark events as triggered 1241 | entry->triggered_events = 0; 1242 | 1243 | if (entry->events_of_interest == 0) 1244 | // All events were unregistered. 1245 | // We can remove the socket from the mux. 1246 | move_mux_entry_to_free_list(entry); 1247 | else 1248 | // The socket wasn't unregistered or 1249 | // wasn't unregistered completely so 1250 | // we put the entry into the idle list 1251 | move_mux_entry_to_idle_list(entry); 1252 | 1253 | return true; 1254 | } 1255 | 1256 | bool microtcp_mux_wait(microtcp_mux_t *mux, microtcp_muxevent_t *ev) 1257 | { 1258 | mtx_lock(&mux->mtcp->lock); 1259 | while (!mux_poll(mux, ev)) { 1260 | MICROTCP_DEBUG_LOG("Multiplexer waiting for an event"); 1261 | if (cnd_wait(&mux->queue_not_empty, &mux->mtcp->lock) != thrd_success) 1262 | abort(); // FIXME: Shouldn't just abort like this 1263 | MICROTCP_DEBUG_LOG("Multiplexer woke up for an event"); 1264 | } 1265 | mtx_unlock(&mux->mtcp->lock); 1266 | return true; 1267 | } 1268 | 1269 | static void 1270 | signal_events_to_muxes_associated_to_socket(microtcp_socket_t *socket, int events) 1271 | { 1272 | // (This function is called by the socket and not the mux) 1273 | 1274 | assert(events); // If no events need to be signaled then 1275 | // this function has no reason to be called. 1276 | 1277 | MICROTCP_DEBUG_LOG("Socket about to signal to multiplexers"); 1278 | 1279 | mux_entry_t *entry = socket->mux_list; 1280 | while (entry) { 1281 | 1282 | microtcp_mux_t *mux = entry->mux; 1283 | 1284 | // Mask the bitmask of triggered events [events] with 1285 | // the bitmask of events that this multiplexer is 1286 | // interested in. 1287 | int newly_triggered_events = events & entry->events_of_interest; 1288 | 1289 | if (!newly_triggered_events) 1290 | MICROTCP_DEBUG_LOG("MUX not interested in these events"); 1291 | 1292 | // If there are no previously triggered events by this 1293 | // socket and the socket just generated some events the 1294 | // mux is interested in, then we need to move the socket-mux 1295 | // structure from the idle list to the ready queue of the mux. 1296 | bool first_event_of_socket_in_mux = (entry->triggered_events == 0) && newly_triggered_events; 1297 | entry->triggered_events |= newly_triggered_events; 1298 | 1299 | if (first_event_of_socket_in_mux) { 1300 | 1301 | // Is this the first socket structure of the muxes 1302 | // ready queue? If it is, we'll need to wake it up 1303 | bool queue_was_empty = (mux->ready_queue_head == NULL); 1304 | 1305 | // Unlink it from the idle list 1306 | *entry->mux_prev = entry->mux_next; 1307 | if (entry->mux_next) 1308 | entry->mux_next->mux_prev = entry->mux_prev; 1309 | 1310 | // Add it to the queue 1311 | if (mux->ready_queue_tail) 1312 | entry->mux_prev = &mux->ready_queue_tail->mux_next; 1313 | else { 1314 | entry->mux_prev = &mux->ready_queue_head; 1315 | mux->ready_queue_head = entry; 1316 | } 1317 | entry->mux_next = NULL; 1318 | mux->ready_queue_tail = entry; 1319 | 1320 | MICROTCP_DEBUG_LOG("Signaling event to multiplexer"); 1321 | if (queue_was_empty) 1322 | cnd_signal(&mux->queue_not_empty); 1323 | MICROTCP_DEBUG_LOG("Signaled event to multiplexer"); 1324 | } 1325 | entry = entry->sock_next; 1326 | } 1327 | MICROTCP_DEBUG_LOG("Socket signaled to multiplexers"); 1328 | } 1329 | --------------------------------------------------------------------------------