├── .gitignore ├── LICENSE ├── README.md ├── examples ├── 01_plain_echo_server.c ├── 02_plain_async_echo_server.c ├── 11_plain_echo_server.c3 └── TODO ├── fuzzingclient.json ├── nob.c ├── src ├── arena.h ├── b64.h ├── coroutine.c ├── coroutine.c3 ├── coroutine.h ├── cws.c ├── cws.c3 ├── cws.h ├── nob.h └── teenysha1.h └── tools └── send_client.html /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | nob 3 | nob.old 4 | reports/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Alexey Kutepov 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # C WebSockets 2 | 3 | > [!WARNING] 4 | > The library is not in production ready state yet 5 | 6 | Custom WebSocket implementation in C for educational and recreational purposes. 7 | 8 | ## Quick Start 9 | 10 | ```console 11 | $ cc -o nob nob.c 12 | $ ./nob 13 | $ ./build/02_plain_async_echo_server 14 | $ firefox ./tools/send_client.html 15 | ``` 16 | 17 | The Echo Servers in the examples are also testable with [Autobahn Test Suite](https://github.com/crossbario/autobahn-testsuite). 18 | 19 | ``` 20 | $ ./build/02_plain_async_echo_server 21 | $ wstest --mode fuzzingclient 22 | ``` 23 | 24 | ## References 25 | 26 | - https://tools.ietf.org/html/rfc6455 27 | - https://www.websocket.org/echo.html 28 | -------------------------------------------------------------------------------- /examples/01_plain_echo_server.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "cws.h" 11 | 12 | #define HOST_IP "127.0.0.1" 13 | #define PORT 9001 14 | 15 | int cws_socket_read(void *data, void *buffer, size_t len) 16 | { 17 | int n = read((long int)data, buffer, len); 18 | if (n < 0) return CWS_ERROR_ERRNO; 19 | if (n == 0) return CWS_ERROR_CONNECTION_CLOSED; 20 | return n; 21 | } 22 | 23 | // peek: like read, but does not remove data from the buffer 24 | // Usually implemented via MSG_PEEK flag of recv 25 | int cws_socket_peek(void *data, void *buffer, size_t len) 26 | { 27 | int n = recv((long int)data, buffer, len, MSG_PEEK); 28 | if (n < 0) return CWS_ERROR_ERRNO; 29 | if (n == 0) return CWS_ERROR_CONNECTION_CLOSED; 30 | return n; 31 | } 32 | 33 | int cws_socket_write(void *data, const void *buffer, size_t len) 34 | { 35 | int n = write((long int)data, buffer, len); 36 | if (n < 0) return CWS_ERROR_ERRNO; 37 | if (n == 0) return CWS_ERROR_CONNECTION_CLOSED; 38 | return n; 39 | } 40 | 41 | int cws_socket_shutdown(void *data, Cws_Shutdown_How how) 42 | { 43 | if (shutdown((long int)data, how) < 0) return CWS_ERROR_ERRNO; 44 | return 0; 45 | } 46 | 47 | int cws_socket_close(void *data) 48 | { 49 | if (close((long int)data) < 0) return CWS_ERROR_ERRNO; 50 | return 0; 51 | } 52 | 53 | Cws_Socket cws_socket_from_fd(int fd) 54 | { 55 | return (Cws_Socket) { 56 | .data = (void*)(long int)fd, 57 | .read = cws_socket_read, 58 | .peek = cws_socket_peek, 59 | .write = cws_socket_write, 60 | .shutdown = cws_socket_shutdown, 61 | .close = cws_socket_close, 62 | }; 63 | } 64 | 65 | int main(void) 66 | { 67 | int server_fd = socket(AF_INET, SOCK_STREAM, 0); 68 | if (server_fd < 0) { 69 | fprintf(stderr, "ERROR: could not create server socket: %s\n", strerror(errno)); 70 | return 1; 71 | } 72 | 73 | int yes = 1; 74 | if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) { 75 | fprintf(stderr, "ERROR: could not configure server socket: %s\n", strerror(errno)); 76 | return 1; 77 | } 78 | 79 | struct sockaddr_in server_addr = {0}; 80 | server_addr.sin_family = AF_INET; 81 | server_addr.sin_port = htons(PORT); 82 | server_addr.sin_addr.s_addr = inet_addr(HOST_IP); 83 | if (bind(server_fd, (void*)&server_addr, sizeof(server_addr)) < 0) { 84 | fprintf(stderr, "ERROR: could not bind server socket: %s\n", strerror(errno)); 85 | return 1; 86 | } 87 | 88 | if (listen(server_fd, 69) < 0) { 89 | fprintf(stderr, "ERROR: could not listen to server socket: %s\n", strerror(errno)); 90 | return 1; 91 | } 92 | 93 | printf("INFO: listening to %s:%d\n", HOST_IP, PORT); 94 | 95 | while (true) { 96 | struct sockaddr_in client_addr = {0}; 97 | socklen_t client_addr_len = sizeof(client_addr); 98 | int client_fd = accept(server_fd, (void*)&client_addr, &client_addr_len); 99 | if (client_fd < 0) { 100 | fprintf(stderr, "ERROR: could not accept connection from client: %s\n", strerror(errno)); 101 | return 1; 102 | } 103 | 104 | Cws cws = { 105 | .socket = cws_socket_from_fd(client_fd), 106 | .debug = true, 107 | }; 108 | 109 | int err = cws_server_handshake(&cws); 110 | if (err < 0) { 111 | fprintf(stderr, "ERROR: %s\n", cws_error_message(&cws, err)); 112 | return 1; 113 | } 114 | 115 | printf("INFO: client connected\n"); 116 | for (int i = 0; ; ++i) { 117 | Cws_Message message = {0}; 118 | err = cws_read_message(&cws, &message); 119 | if (err < 0) { 120 | if (err == CWS_ERROR_FRAME_CLOSE_SENT) { 121 | printf("INFO: client closed connection\n"); 122 | } else { 123 | printf("ERROR: client connection failed: %s\n", cws_error_message(&cws, err)); 124 | } 125 | 126 | cws_close(&cws); 127 | break; 128 | } 129 | printf("INFO: %d: client sent %zu bytes of %s message\n", i, message.payload_len, cws_message_kind_name(&cws, message.kind)); 130 | cws_send_message(&cws, message.kind, message.payload, message.payload_len); 131 | arena_reset(&cws.arena); 132 | } 133 | 134 | arena_free(&cws.arena); 135 | } 136 | 137 | return 0; 138 | } 139 | -------------------------------------------------------------------------------- /examples/02_plain_async_echo_server.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "cws.h" 12 | #include "coroutine.h" 13 | 14 | #define HOST_IP "127.0.0.1" 15 | #define PORT 9001 16 | 17 | int cws_async_socket_read(void *data, void *buffer, size_t len) 18 | { 19 | int sockfd = (long int)data; 20 | coroutine_sleep_read(sockfd); 21 | int n = read(sockfd, buffer, len); 22 | if (n < 0) return CWS_ERROR_ERRNO; 23 | if (n == 0) return CWS_ERROR_CONNECTION_CLOSED; 24 | return n; 25 | } 26 | 27 | // peek: like read, but does not remove data from the buffer 28 | // Usually implemented via MSG_PEEK flag of recv 29 | int cws_async_socket_peek(void *data, void *buffer, size_t len) 30 | { 31 | int sockfd = (long int)data; 32 | coroutine_sleep_read(sockfd); 33 | int n = recv(sockfd, buffer, len, MSG_PEEK); 34 | if (n < 0) return CWS_ERROR_ERRNO; 35 | if (n == 0) return CWS_ERROR_CONNECTION_CLOSED; 36 | return n; 37 | } 38 | 39 | int cws_async_socket_write(void *data, const void *buffer, size_t len) 40 | { 41 | int sockfd = (long int)data; 42 | coroutine_sleep_write(sockfd); 43 | int n = write((long int)data, buffer, len); 44 | if (n < 0) return CWS_ERROR_ERRNO; 45 | if (n == 0) return CWS_ERROR_CONNECTION_CLOSED; 46 | return n; 47 | } 48 | 49 | int cws_async_socket_shutdown(void *data, Cws_Shutdown_How how) 50 | { 51 | if (shutdown((long int)data, how) < 0) return CWS_ERROR_ERRNO; 52 | return 0; 53 | } 54 | 55 | int cws_async_socket_close(void *data) 56 | { 57 | if (close((long int)data) < 0) return CWS_ERROR_ERRNO; 58 | return 0; 59 | } 60 | 61 | Cws_Socket cws_async_socket_from_fd(int fd) 62 | { 63 | return (Cws_Socket) { 64 | .data = (void*)(long int)fd, 65 | .read = cws_async_socket_read, 66 | .peek = cws_async_socket_peek, 67 | .write = cws_async_socket_write, 68 | .shutdown = cws_async_socket_shutdown, 69 | .close = cws_async_socket_close, 70 | }; 71 | } 72 | 73 | int set_non_blocking(int sockfd) 74 | { 75 | int flags = fcntl(sockfd, F_GETFL, 0); 76 | if (flags < 0) return -1; 77 | if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) < 0) return -1; 78 | return 0; 79 | } 80 | 81 | void echo_client(void *data) 82 | { 83 | int client_fd = (long int) data; 84 | 85 | Cws cws = { 86 | .socket = cws_async_socket_from_fd(client_fd), 87 | .debug = true, 88 | }; 89 | 90 | int err = cws_server_handshake(&cws); 91 | if (err < 0) { 92 | fprintf(stderr, "ERROR: %s\n", cws_error_message(&cws, err)); 93 | abort(); 94 | } 95 | 96 | printf("INFO: client connected\n"); 97 | for (int i = 0; ; ++i) { 98 | Cws_Message message = {0}; 99 | err = cws_read_message(&cws, &message); 100 | if (err < 0) { 101 | if (err == CWS_ERROR_FRAME_CLOSE_SENT) { 102 | printf("INFO: client closed connection\n"); 103 | } else { 104 | printf("ERROR: client connection failed: %s\n", cws_error_message(&cws, err)); 105 | } 106 | 107 | cws_close(&cws); 108 | break; 109 | } 110 | printf("INFO: %d: client sent %zu bytes of %s message\n", i, message.payload_len, cws_message_kind_name(&cws, message.kind)); 111 | cws_send_message(&cws, message.kind, message.payload, message.payload_len); 112 | arena_reset(&cws.arena); 113 | } 114 | 115 | arena_free(&cws.arena); 116 | } 117 | 118 | int main(void) 119 | { 120 | coroutine_init(); 121 | 122 | int server_fd = socket(AF_INET, SOCK_STREAM, 0); 123 | if (server_fd < 0) { 124 | fprintf(stderr, "ERROR: could not create server socket: %s\n", strerror(errno)); 125 | return 1; 126 | } 127 | 128 | int yes = 1; 129 | if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) { 130 | fprintf(stderr, "ERROR: could not configure server socket: %s\n", strerror(errno)); 131 | return 1; 132 | } 133 | 134 | struct sockaddr_in server_addr = {0}; 135 | server_addr.sin_family = AF_INET; 136 | server_addr.sin_port = htons(PORT); 137 | server_addr.sin_addr.s_addr = inet_addr(HOST_IP); 138 | if (bind(server_fd, (void*)&server_addr, sizeof(server_addr)) < 0) { 139 | fprintf(stderr, "ERROR: could not bind server socket: %s\n", strerror(errno)); 140 | return 1; 141 | } 142 | 143 | if (listen(server_fd, 69) < 0) { 144 | fprintf(stderr, "ERROR: could not listen to server socket: %s\n", strerror(errno)); 145 | return 1; 146 | } 147 | 148 | if (set_non_blocking(server_fd) < 0) { 149 | fprintf(stderr, "ERROR: could not set server socket non-blocking: %s\n", strerror(errno)); 150 | return 1; 151 | } 152 | 153 | printf("INFO: listening to %s:%d\n", HOST_IP, PORT); 154 | 155 | while (true) { 156 | struct sockaddr_in client_addr = {0}; 157 | socklen_t client_addr_len = sizeof(client_addr); 158 | coroutine_sleep_read(server_fd); 159 | int client_fd = accept(server_fd, (void*)&client_addr, &client_addr_len); 160 | if (client_fd < 0) { 161 | fprintf(stderr, "ERROR: could not accept connection from client: %s\n", strerror(errno)); 162 | return 1; 163 | } 164 | if (set_non_blocking(client_fd) < 0) { 165 | fprintf(stderr, "ERROR: could not set client socket non-blocking: %s\n", strerror(errno)); 166 | return 1; 167 | } 168 | 169 | coroutine_go(echo_client, (void*)(long int)client_fd); 170 | } 171 | 172 | return 0; 173 | } 174 | -------------------------------------------------------------------------------- /examples/11_plain_echo_server.c3: -------------------------------------------------------------------------------- 1 | module main; 2 | 3 | import std::io; 4 | import std::net::tcp, std::net::os; 5 | import libc; 6 | import cws; 7 | import arena; 8 | 9 | fn int cws_socket_read(void* data, void* buffer, usz len) 10 | { 11 | isz n = libc::read((int)(iptr)data, buffer, len); 12 | if (n < 0) return (int)cws::ERROR_ERRNO; 13 | if (n == 0) return (int)cws::ERROR_CONNECTION_CLOSED; 14 | return (int)n; 15 | } 16 | 17 | // peek: like read, but does not remove data from the buffer 18 | // Usually implemented via MSG_PEEK flag of recv 19 | fn int cws_socket_peek(void* data, void* buffer, usz len) 20 | { 21 | const int MSG_PEEK = 2; 22 | isz n = libc::recv((int)(iptr)data, buffer, len, MSG_PEEK); 23 | if (n < 0) return (int)cws::ERROR_ERRNO; 24 | if (n == 0) return (int)cws::ERROR_CONNECTION_CLOSED; 25 | return (int)n; 26 | } 27 | 28 | fn int cws_socket_write(void* data, void* buffer, usz len) 29 | { 30 | isz n = libc::write((int)(iptr)data, buffer, len); 31 | if (n < 0) return (int)cws::ERROR_ERRNO; 32 | if (n == 0) return (int)cws::ERROR_CONNECTION_CLOSED; 33 | return (int)n; 34 | } 35 | 36 | fn int cws_socket_shutdown(void* data, CwsShutdownHow how) 37 | { 38 | if (libc::shutdown((int)(iptr)data, (int)how) < 0) return (int)cws::ERROR_ERRNO; 39 | return 0; 40 | } 41 | 42 | fn int cws_socket_close(void *data) 43 | { 44 | if (libc::close((int)(iptr)data) < 0) return (int)cws::ERROR_ERRNO; 45 | return 0; 46 | } 47 | 48 | fn CwsSocket cws_socket_from_fd(int fd) 49 | { 50 | return CwsSocket { 51 | .data = (void*)(iptr)fd, 52 | .read = &cws_socket_read, 53 | .peek = &cws_socket_write, 54 | .write = &cws_socket_peek, 55 | .shutdown = &cws_socket_shutdown, 56 | .close = &cws_socket_close, 57 | }; 58 | } 59 | 60 | fn int main() { 61 | const String HOST = "127.0.0.1"; 62 | const ushort PORT = 9001; 63 | TcpServerSocket server = tcp::listen(HOST, PORT, 10, REUSEADDR)!!; 64 | io::printfn("Listening to %s:%d", HOST, PORT); 65 | while ACCEPT: (true) { 66 | TcpSocket client = tcp::accept(&server)!!; 67 | 68 | Cws cws = { 69 | .socket = cws_socket_from_fd(client.sock), 70 | .debug = true, 71 | }; 72 | defer arena::free(&cws.arena); 73 | 74 | int err = cws::server_handshake(&cws); 75 | if (err < 0) { 76 | io::eprintf("ERROR: server_handshake: %s\n", cws::error_message(&cws, (CwsError)err)); 77 | return 1; 78 | } 79 | 80 | io::eprintf("INFO: client connected\n"); 81 | for (int i = 0; ; ++i) { 82 | CwsMessage message; 83 | err = cws::read_message(&cws, &message); 84 | if (err < 0) { 85 | if (err == (int)cws::ERROR_FRAME_CLOSE_SENT) { 86 | io::printf("INFO: client closed connection\n"); 87 | } else { 88 | io::eprintf("ERROR: client connection failed: %s\n", cws::error_message(&cws, (CwsError)err)); 89 | } 90 | 91 | cws::close(&cws); 92 | break; 93 | } 94 | io::printf("INFO: %d: client sent %zu bytes of %s message\n", i, message.payload_len, cws::message_kind_name(&cws, message.kind)); 95 | cws::send_message(&cws, message.kind, message.payload, message.payload_len); 96 | arena::reset(&cws.arena); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /examples/TODO: -------------------------------------------------------------------------------- 1 | - Blocking SSL echo server example 2 | - Coroutines Async SSL Echo Server example 3 | - Client examples 4 | - Use send/recv in all the examples with send using MSG_NOSIGNAL flag that suppresses SIGPIPE 5 | - Apply SO_LINGER to the client sockets -------------------------------------------------------------------------------- /fuzzingclient.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "outdir": "./reports/servers", 4 | "servers": [ 5 | { 6 | "url": "ws://127.0.0.1:9001" 7 | } 8 | ], 9 | "cases": ["*"], 10 | "exclude-cases": ["9.7.*", "9.8.*"], 11 | "exclude-agent-cases": {} 12 | } 13 | -------------------------------------------------------------------------------- /nob.c: -------------------------------------------------------------------------------- 1 | #define NOB_IMPLEMENTATION 2 | #define NOB_STRIP_PREFIX 3 | #include "src/nob.h" 4 | 5 | #define BUILD_FOLDER "build/" 6 | #define EXAMPLES_FOLDER "examples/" 7 | #define SRC_FOLDER "src/" 8 | 9 | #define cc_with_cflags(cmd) cmd_append(cmd, "clang", "-Wall", "-Wextra", "-ggdb", "-I.", "-I"SRC_FOLDER) 10 | #define cc_output(cmd, output_path) cmd_append(cmd, "-o", output_path) 11 | #define cc_no_link(cmd) cmd_append(cmd, "-c") 12 | #define cc_input(cmd, ...) cmd_append(cmd, __VA_ARGS__) 13 | 14 | int main(int argc, char **argv) 15 | { 16 | NOB_GO_REBUILD_URSELF_PLUS(argc, argv, "src/nob.h"); 17 | Cmd cmd = {0}; 18 | Nob_Procs procs = {0}; 19 | 20 | if (!nob_mkdir_if_not_exists(BUILD_FOLDER)) return 1; 21 | 22 | cc_with_cflags(&cmd); 23 | cc_no_link(&cmd); 24 | cc_output(&cmd, BUILD_FOLDER"cws.o"); 25 | cc_input(&cmd, SRC_FOLDER"cws.c"); 26 | da_append(&procs, nob_cmd_run_async_and_reset(&cmd)); 27 | 28 | cc_with_cflags(&cmd); 29 | cc_no_link(&cmd); 30 | cc_output(&cmd, BUILD_FOLDER"coroutine.o"); 31 | cc_input(&cmd, SRC_FOLDER"coroutine.c"); 32 | da_append(&procs, nob_cmd_run_async_and_reset(&cmd)); 33 | 34 | if (!nob_procs_wait_and_reset(&procs)) return 1; 35 | 36 | cmd_append(&cmd, "ar", "-rcs", BUILD_FOLDER"libcws.a", BUILD_FOLDER"coroutine.o", BUILD_FOLDER"cws.o"); 37 | da_append(&procs, nob_cmd_run_async_and_reset(&cmd)); 38 | 39 | if (!nob_procs_wait_and_reset(&procs)) return 1; 40 | 41 | cc_with_cflags(&cmd); 42 | cc_output(&cmd, BUILD_FOLDER"01_plain_echo_server"); 43 | cc_input(&cmd, EXAMPLES_FOLDER"01_plain_echo_server.c", BUILD_FOLDER"libcws.a"); 44 | da_append(&procs, nob_cmd_run_async_and_reset(&cmd)); 45 | 46 | cc_with_cflags(&cmd); 47 | cc_output(&cmd, BUILD_FOLDER"02_plain_async_echo_server"); 48 | cc_input(&cmd, EXAMPLES_FOLDER"02_plain_async_echo_server.c", BUILD_FOLDER"libcws.a"); 49 | da_append(&procs, nob_cmd_run_async_and_reset(&cmd)); 50 | 51 | // TODO: detect the presense of c3c and if it's missing don't try to build the C3 example 52 | cmd_append(&cmd, 53 | "c3c", "compile", 54 | "-g", 55 | "-l", BUILD_FOLDER"libcws.a", 56 | "-o", BUILD_FOLDER"11_plain_echo_server", 57 | EXAMPLES_FOLDER"11_plain_echo_server.c3", 58 | SRC_FOLDER"cws.c3", 59 | SRC_FOLDER"coroutine.c3"); 60 | da_append(&procs, nob_cmd_run_async_and_reset(&cmd)); 61 | 62 | if (!nob_procs_wait_and_reset(&procs)) return 1; 63 | 64 | return 0; 65 | } 66 | -------------------------------------------------------------------------------- /src/arena.h: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Alexey Kutepov 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining 4 | // a copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to 8 | // permit persons to whom the Software is furnished to do so, subject to 9 | // the following conditions: 10 | 11 | // The above copyright notice and this permission notice shall be 12 | // included in all copies or substantial portions of the Software. 13 | 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | #ifndef ARENA_H_ 23 | #define ARENA_H_ 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | #ifndef ARENA_NOSTDIO 30 | #include 31 | #include 32 | #endif // ARENA_NOSTDIO 33 | 34 | #ifndef ARENA_ASSERT 35 | #include 36 | #define ARENA_ASSERT assert 37 | #endif 38 | 39 | #define ARENA_BACKEND_LIBC_MALLOC 0 40 | #define ARENA_BACKEND_LINUX_MMAP 1 41 | #define ARENA_BACKEND_WIN32_VIRTUALALLOC 2 42 | #define ARENA_BACKEND_WASM_HEAPBASE 3 43 | 44 | #ifndef ARENA_BACKEND 45 | #define ARENA_BACKEND ARENA_BACKEND_LIBC_MALLOC 46 | #endif // ARENA_BACKEND 47 | 48 | typedef struct Region Region; 49 | 50 | struct Region { 51 | Region *next; 52 | size_t count; 53 | size_t capacity; 54 | uintptr_t data[]; 55 | }; 56 | 57 | typedef struct { 58 | Region *begin, *end; 59 | } Arena; 60 | 61 | typedef struct { 62 | Region *region; 63 | size_t count; 64 | } Arena_Mark; 65 | 66 | #define REGION_DEFAULT_CAPACITY (8*1024) 67 | 68 | Region *new_region(size_t capacity); 69 | void free_region(Region *r); 70 | 71 | void *arena_alloc(Arena *a, size_t size_bytes); 72 | void *arena_realloc(Arena *a, void *oldptr, size_t oldsz, size_t newsz); 73 | char *arena_strdup(Arena *a, const char *cstr); 74 | void *arena_memdup(Arena *a, void *data, size_t size); 75 | #ifndef ARENA_NOSTDIO 76 | char *arena_sprintf(Arena *a, const char *format, ...); 77 | #endif // ARENA_NOSTDIO 78 | 79 | Arena_Mark arena_snapshot(Arena *a); 80 | void arena_reset(Arena *a); 81 | void arena_rewind(Arena *a, Arena_Mark m); 82 | void arena_free(Arena *a); 83 | void arena_trim(Arena *a); 84 | 85 | #define ARENA_DA_INIT_CAP 256 86 | 87 | #ifdef __cplusplus 88 | #define cast_ptr(ptr) (decltype(ptr)) 89 | #else 90 | #define cast_ptr(...) 91 | #endif 92 | 93 | #define arena_da_append(a, da, item) \ 94 | do { \ 95 | if ((da)->count >= (da)->capacity) { \ 96 | size_t new_capacity = (da)->capacity == 0 ? ARENA_DA_INIT_CAP : (da)->capacity*2; \ 97 | (da)->items = cast_ptr((da)->items)arena_realloc( \ 98 | (a), (da)->items, \ 99 | (da)->capacity*sizeof(*(da)->items), \ 100 | new_capacity*sizeof(*(da)->items)); \ 101 | (da)->capacity = new_capacity; \ 102 | } \ 103 | \ 104 | (da)->items[(da)->count++] = (item); \ 105 | } while (0) 106 | 107 | // Append several items to a dynamic array 108 | #define arena_da_append_many(a, da, new_items, new_items_count) \ 109 | do { \ 110 | if ((da)->count + (new_items_count) > (da)->capacity) { \ 111 | size_t new_capacity = (da)->capacity; \ 112 | if (new_capacity == 0) new_capacity = ARENA_DA_INIT_CAP; \ 113 | while ((da)->count + (new_items_count) > new_capacity) new_capacity *= 2; \ 114 | (da)->items = cast_ptr((da)->items)arena_realloc( \ 115 | (a), (da)->items, \ 116 | (da)->capacity*sizeof(*(da)->items), \ 117 | new_capacity*sizeof(*(da)->items)); \ 118 | (da)->capacity = new_capacity; \ 119 | } \ 120 | memcpy((da)->items + (da)->count, (new_items), (new_items_count)*sizeof(*(da)->items)); \ 121 | (da)->count += (new_items_count); \ 122 | } while (0) 123 | 124 | // Append a sized buffer to a string builder 125 | #define arena_sb_append_buf arena_da_append_many 126 | 127 | // Append a NULL-terminated string to a string builder 128 | #define arena_sb_append_cstr(a, sb, cstr) \ 129 | do { \ 130 | const char *s = (cstr); \ 131 | size_t n = strlen(s); \ 132 | arena_da_append_many(a, sb, s, n); \ 133 | } while (0) 134 | 135 | // Append a single NULL character at the end of a string builder. So then you can 136 | // use it a NULL-terminated C string 137 | #define arena_sb_append_null(a, sb) arena_da_append(a, sb, 0) 138 | 139 | #endif // ARENA_H_ 140 | 141 | #ifdef ARENA_IMPLEMENTATION 142 | 143 | #if ARENA_BACKEND == ARENA_BACKEND_LIBC_MALLOC 144 | #include 145 | 146 | // TODO: instead of accepting specific capacity new_region() should accept the size of the object we want to fit into the region 147 | // It should be up to new_region() to decide the actual capacity to allocate 148 | Region *new_region(size_t capacity) 149 | { 150 | size_t size_bytes = sizeof(Region) + sizeof(uintptr_t)*capacity; 151 | // TODO: it would be nice if we could guarantee that the regions are allocated by ARENA_BACKEND_LIBC_MALLOC are page aligned 152 | Region *r = (Region*)malloc(size_bytes); 153 | ARENA_ASSERT(r); 154 | r->next = NULL; 155 | r->count = 0; 156 | r->capacity = capacity; 157 | return r; 158 | } 159 | 160 | void free_region(Region *r) 161 | { 162 | free(r); 163 | } 164 | #elif ARENA_BACKEND == ARENA_BACKEND_LINUX_MMAP 165 | #include 166 | #include 167 | 168 | Region *new_region(size_t capacity) 169 | { 170 | size_t size_bytes = sizeof(Region) + sizeof(uintptr_t) * capacity; 171 | Region *r = mmap(NULL, size_bytes, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); 172 | ARENA_ASSERT(r != MAP_FAILED); 173 | r->next = NULL; 174 | r->count = 0; 175 | r->capacity = capacity; 176 | return r; 177 | } 178 | 179 | void free_region(Region *r) 180 | { 181 | size_t size_bytes = sizeof(Region) + sizeof(uintptr_t) * r->capacity; 182 | int ret = munmap(r, size_bytes); 183 | ARENA_ASSERT(ret == 0); 184 | } 185 | 186 | #elif ARENA_BACKEND == ARENA_BACKEND_WIN32_VIRTUALALLOC 187 | 188 | #if !defined(_WIN32) 189 | # error "Current platform is not Windows" 190 | #endif 191 | 192 | #define WIN32_LEAN_AND_MEAN 193 | #include 194 | 195 | #define INV_HANDLE(x) (((x) == NULL) || ((x) == INVALID_HANDLE_VALUE)) 196 | 197 | Region *new_region(size_t capacity) 198 | { 199 | SIZE_T size_bytes = sizeof(Region) + sizeof(uintptr_t) * capacity; 200 | Region *r = VirtualAllocEx( 201 | GetCurrentProcess(), /* Allocate in current process address space */ 202 | NULL, /* Unknown position */ 203 | size_bytes, /* Bytes to allocate */ 204 | MEM_COMMIT | MEM_RESERVE, /* Reserve and commit allocated page */ 205 | PAGE_READWRITE /* Permissions ( Read/Write )*/ 206 | ); 207 | if (INV_HANDLE(r)) 208 | ARENA_ASSERT(0 && "VirtualAllocEx() failed."); 209 | 210 | r->next = NULL; 211 | r->count = 0; 212 | r->capacity = capacity; 213 | return r; 214 | } 215 | 216 | void free_region(Region *r) 217 | { 218 | if (INV_HANDLE(r)) 219 | return; 220 | 221 | BOOL free_result = VirtualFreeEx( 222 | GetCurrentProcess(), /* Deallocate from current process address space */ 223 | (LPVOID)r, /* Address to deallocate */ 224 | 0, /* Bytes to deallocate ( Unknown, deallocate entire page ) */ 225 | MEM_RELEASE /* Release the page ( And implicitly decommit it ) */ 226 | ); 227 | 228 | if (FALSE == free_result) 229 | ARENA_ASSERT(0 && "VirtualFreeEx() failed."); 230 | } 231 | 232 | #elif ARENA_BACKEND == ARENA_BACKEND_WASM_HEAPBASE 233 | # error "TODO: WASM __heap_base backend is not implemented yet" 234 | #else 235 | # error "Unknown Arena backend" 236 | #endif 237 | 238 | // TODO: add debug statistic collection mode for arena 239 | // Should collect things like: 240 | // - How many times new_region was called 241 | // - How many times existing region was skipped 242 | // - How many times allocation exceeded REGION_DEFAULT_CAPACITY 243 | 244 | void *arena_alloc(Arena *a, size_t size_bytes) 245 | { 246 | size_t size = (size_bytes + sizeof(uintptr_t) - 1)/sizeof(uintptr_t); 247 | 248 | if (a->end == NULL) { 249 | ARENA_ASSERT(a->begin == NULL); 250 | size_t capacity = REGION_DEFAULT_CAPACITY; 251 | if (capacity < size) capacity = size; 252 | a->end = new_region(capacity); 253 | a->begin = a->end; 254 | } 255 | 256 | while (a->end->count + size > a->end->capacity && a->end->next != NULL) { 257 | a->end = a->end->next; 258 | } 259 | 260 | if (a->end->count + size > a->end->capacity) { 261 | ARENA_ASSERT(a->end->next == NULL); 262 | size_t capacity = REGION_DEFAULT_CAPACITY; 263 | if (capacity < size) capacity = size; 264 | a->end->next = new_region(capacity); 265 | a->end = a->end->next; 266 | } 267 | 268 | void *result = &a->end->data[a->end->count]; 269 | a->end->count += size; 270 | return result; 271 | } 272 | 273 | void *arena_realloc(Arena *a, void *oldptr, size_t oldsz, size_t newsz) 274 | { 275 | if (newsz <= oldsz) return oldptr; 276 | void *newptr = arena_alloc(a, newsz); 277 | char *newptr_char = (char*)newptr; 278 | char *oldptr_char = (char*)oldptr; 279 | for (size_t i = 0; i < oldsz; ++i) { 280 | newptr_char[i] = oldptr_char[i]; 281 | } 282 | return newptr; 283 | } 284 | 285 | char *arena_strdup(Arena *a, const char *cstr) 286 | { 287 | size_t n = strlen(cstr); 288 | char *dup = (char*)arena_alloc(a, n + 1); 289 | memcpy(dup, cstr, n); 290 | dup[n] = '\0'; 291 | return dup; 292 | } 293 | 294 | void *arena_memdup(Arena *a, void *data, size_t size) 295 | { 296 | return memcpy(arena_alloc(a, size), data, size); 297 | } 298 | 299 | #ifndef ARENA_NOSTDIO 300 | char *arena_sprintf(Arena *a, const char *format, ...) 301 | { 302 | va_list args; 303 | va_start(args, format); 304 | int n = vsnprintf(NULL, 0, format, args); 305 | va_end(args); 306 | 307 | ARENA_ASSERT(n >= 0); 308 | char *result = (char*)arena_alloc(a, n + 1); 309 | va_start(args, format); 310 | vsnprintf(result, n + 1, format, args); 311 | va_end(args); 312 | 313 | return result; 314 | } 315 | #endif // ARENA_NOSTDIO 316 | 317 | Arena_Mark arena_snapshot(Arena *a) 318 | { 319 | Arena_Mark m; 320 | if(a->end == NULL){ //snapshot of uninitialized arena 321 | ARENA_ASSERT(a->begin == NULL); 322 | m.region = a->end; 323 | m.count = 0; 324 | }else{ 325 | m.region = a->end; 326 | m.count = a->end->count; 327 | } 328 | 329 | return m; 330 | } 331 | 332 | void arena_reset(Arena *a) 333 | { 334 | for (Region *r = a->begin; r != NULL; r = r->next) { 335 | r->count = 0; 336 | } 337 | 338 | a->end = a->begin; 339 | } 340 | 341 | void arena_rewind(Arena *a, Arena_Mark m) 342 | { 343 | if(m.region == NULL){ //snapshot of uninitialized arena 344 | arena_reset(a); //leave allocation 345 | return; 346 | } 347 | 348 | m.region->count = m.count; 349 | for (Region *r = m.region->next; r != NULL; r = r->next) { 350 | r->count = 0; 351 | } 352 | 353 | a->end = m.region; 354 | } 355 | 356 | void arena_free(Arena *a) 357 | { 358 | Region *r = a->begin; 359 | while (r) { 360 | Region *r0 = r; 361 | r = r->next; 362 | free_region(r0); 363 | } 364 | a->begin = NULL; 365 | a->end = NULL; 366 | } 367 | 368 | void arena_trim(Arena *a){ 369 | Region *r = a->end->next; 370 | while (r) { 371 | Region *r0 = r; 372 | r = r->next; 373 | free_region(r0); 374 | } 375 | a->end->next = NULL; 376 | } 377 | 378 | #endif // ARENA_IMPLEMENTATION 379 | -------------------------------------------------------------------------------- /src/b64.h: -------------------------------------------------------------------------------- 1 | #ifndef B64_H_ 2 | #define B64_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #define B64_STD_ALPHA "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" 10 | #define B64_URL_ALPHA "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" 11 | #define B64_DEFAULT_PAD '=' 12 | 13 | #define b64_encode_out_len(in_len) (((in_len) + 2)/3*4) 14 | size_t b64_encode(const unsigned char *in, size_t in_len, char *out, size_t out_cap, const char *alpha, char padding); 15 | // TODO: implement b64_decode and publish as a separate single-header repo 16 | 17 | #endif // B64_H_ 18 | 19 | #ifdef B64_IMPLEMENTATION 20 | 21 | size_t b64_encode(const unsigned char *in, size_t in_len, char *out, size_t out_cap, const char *alpha, char padding) 22 | { 23 | assert(strlen(alpha) == 64); 24 | assert(b64_encode_out_len(in_len) <= out_cap); 25 | size_t out_len = 0; 26 | size_t in_cur = 0; 27 | uint32_t group = 0; 28 | while (in_cur + 3 <= in_len) { 29 | group = 0; 30 | group |= ((uint32_t)(in[in_cur++]))<<(2*8); 31 | group |= ((uint32_t)(in[in_cur++]))<<(1*8); 32 | group |= ((uint32_t)(in[in_cur++]))<<(0*8); 33 | out[out_len++] = alpha[(group>>(3*6))&0x3F]; 34 | out[out_len++] = alpha[(group>>(2*6))&0x3F]; 35 | out[out_len++] = alpha[(group>>(1*6))&0x3F]; 36 | out[out_len++] = alpha[(group>>(0*6))&0x3F]; 37 | } 38 | 39 | switch (in_len - in_cur) { 40 | case 0: break; 41 | case 1: { 42 | group = 0; 43 | group |= ((uint32_t)in[in_cur++])<<(2*8); 44 | out[out_len++] = alpha[(group>>(3*6))&0x3F]; 45 | out[out_len++] = alpha[(group>>(2*6))&0x3F]; 46 | out[out_len++] = padding; 47 | out[out_len++] = padding; 48 | } break; 49 | case 2: { 50 | group = 0; 51 | group |= ((uint32_t)in[in_cur++])<<(2*8); 52 | group |= ((uint32_t)in[in_cur++])<<(1*8); 53 | out[out_len++] = alpha[(group>>(3*6))&0x3F]; 54 | out[out_len++] = alpha[(group>>(2*6))&0x3F]; 55 | out[out_len++] = alpha[(group>>(1*6))&0x3F]; 56 | out[out_len++] = padding; 57 | } break; 58 | default: assert(0 && "UNREACHABLE"); 59 | } 60 | 61 | return out_len; 62 | } 63 | 64 | #endif // B64_IMPLEMENTATION 65 | -------------------------------------------------------------------------------- /src/coroutine.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "coroutine.h" 12 | 13 | // TODO: make the STACK_CAPACITY customizable by the user 14 | //#define STACK_CAPACITY (4*1024) 15 | #define STACK_CAPACITY (1024*getpagesize()) 16 | 17 | // Initial capacity of a dynamic array 18 | #ifndef DA_INIT_CAP 19 | #define DA_INIT_CAP 256 20 | #endif 21 | 22 | // Append an item to a dynamic array 23 | #define da_append(da, item) \ 24 | do { \ 25 | if ((da)->count >= (da)->capacity) { \ 26 | (da)->capacity = (da)->capacity == 0 ? DA_INIT_CAP : (da)->capacity*2; \ 27 | (da)->items = realloc((da)->items, (da)->capacity*sizeof(*(da)->items)); \ 28 | assert((da)->items != NULL && "Buy more RAM lol"); \ 29 | } \ 30 | \ 31 | (da)->items[(da)->count++] = (item); \ 32 | } while (0) 33 | 34 | #define da_remove_unordered(da, i) \ 35 | do { \ 36 | size_t j = (i); \ 37 | assert(j < (da)->count); \ 38 | (da)->items[j] = (da)->items[--(da)->count]; \ 39 | } while(0) 40 | 41 | #define UNUSED(x) (void)(x) 42 | #define TODO(message) do { fprintf(stderr, "%s:%d: TODO: %s\n", __FILE__, __LINE__, message); abort(); } while(0) 43 | #define UNREACHABLE(message) do { fprintf(stderr, "%s:%d: UNREACHABLE: %s\n", __FILE__, __LINE__, message); abort(); } while(0) 44 | 45 | typedef struct { 46 | void *rsp; 47 | void *stack_base; 48 | } Context; 49 | 50 | typedef struct { 51 | Context *items; 52 | size_t count; 53 | size_t capacity; 54 | } Contexts; 55 | 56 | typedef struct { 57 | size_t *items; 58 | size_t count; 59 | size_t capacity; 60 | } Indices; 61 | 62 | typedef struct { 63 | struct pollfd *items; 64 | size_t count; 65 | size_t capacity; 66 | } Polls; 67 | 68 | // TODO: coroutines library probably does not work well in multithreaded environment 69 | static size_t current = 0; 70 | static Indices active = {0}; 71 | static Indices dead = {0}; 72 | static Contexts contexts = {0}; 73 | static Indices asleep = {0}; 74 | static Polls polls = {0}; 75 | 76 | // TODO: ARM support 77 | // Requires modifications in all the @arch places 78 | 79 | typedef enum { 80 | SM_NONE = 0, 81 | SM_READ, 82 | SM_WRITE, 83 | } Sleep_Mode; 84 | 85 | // Linux x86_64 call convention 86 | // %rdi, %rsi, %rdx, %rcx, %r8, and %r9 87 | 88 | void __attribute__((naked)) coroutine_yield(void) 89 | { 90 | // @arch 91 | asm( 92 | " pushq %rdi\n" 93 | " pushq %rbp\n" 94 | " pushq %rbx\n" 95 | " pushq %r12\n" 96 | " pushq %r13\n" 97 | " pushq %r14\n" 98 | " pushq %r15\n" 99 | " movq %rsp, %rdi\n" // rsp 100 | " movq $0, %rsi\n" // sm = SM_NONE 101 | " jmp coroutine_switch_context\n"); 102 | } 103 | 104 | void __attribute__((naked)) coroutine_sleep_read(int fd __attribute__((unused))) 105 | { 106 | // @arch 107 | asm( 108 | " pushq %rdi\n" 109 | " pushq %rbp\n" 110 | " pushq %rbx\n" 111 | " pushq %r12\n" 112 | " pushq %r13\n" 113 | " pushq %r14\n" 114 | " pushq %r15\n" 115 | " movq %rdi, %rdx\n" // fd 116 | " movq %rsp, %rdi\n" // rsp 117 | " movq $1, %rsi\n" // sm = SM_READ 118 | " jmp coroutine_switch_context\n"); 119 | } 120 | 121 | void __attribute__((naked)) coroutine_sleep_write(int fd __attribute__((unused))) 122 | { 123 | // @arch 124 | asm( 125 | " pushq %rdi\n" 126 | " pushq %rbp\n" 127 | " pushq %rbx\n" 128 | " pushq %r12\n" 129 | " pushq %r13\n" 130 | " pushq %r14\n" 131 | " pushq %r15\n" 132 | " movq %rdi, %rdx\n" // fd 133 | " movq %rsp, %rdi\n" // rsp 134 | " movq $2, %rsi\n" // sm = SM_WRITE 135 | " jmp coroutine_switch_context\n"); 136 | } 137 | 138 | void __attribute__((naked)) coroutine_restore_context(void *rsp __attribute__((unused))) 139 | { 140 | // @arch 141 | asm( 142 | " movq %rdi, %rsp\n" 143 | " popq %r15\n" 144 | " popq %r14\n" 145 | " popq %r13\n" 146 | " popq %r12\n" 147 | " popq %rbx\n" 148 | " popq %rbp\n" 149 | " popq %rdi\n" 150 | " ret\n"); 151 | } 152 | 153 | void coroutine_switch_context(void *rsp, Sleep_Mode sm, int fd) 154 | { 155 | contexts.items[active.items[current]].rsp = rsp; 156 | 157 | switch (sm) { 158 | case SM_NONE: current += 1; break; 159 | case SM_READ: { 160 | da_append(&asleep, active.items[current]); 161 | struct pollfd pfd = {.fd = fd, .events = POLLRDNORM,}; 162 | da_append(&polls, pfd); 163 | da_remove_unordered(&active, current); 164 | } break; 165 | 166 | case SM_WRITE: { 167 | da_append(&asleep, active.items[current]); 168 | struct pollfd pfd = {.fd = fd, .events = POLLWRNORM,}; 169 | da_append(&polls, pfd); 170 | da_remove_unordered(&active, current); 171 | } break; 172 | 173 | default: UNREACHABLE("coroutine_switch_context"); 174 | } 175 | 176 | if (polls.count > 0) { 177 | int timeout = active.count == 0 ? -1 : 0; 178 | int result = poll(polls.items, polls.count, timeout); 179 | if (result < 0) TODO("poll"); 180 | 181 | for (size_t i = 0; i < polls.count;) { 182 | if (polls.items[i].revents) { 183 | size_t id = asleep.items[i]; 184 | da_remove_unordered(&polls, i); 185 | da_remove_unordered(&asleep, i); 186 | da_append(&active, id); 187 | } else { 188 | ++i; 189 | } 190 | } 191 | } 192 | 193 | assert(active.count > 0); 194 | current %= active.count; 195 | coroutine_restore_context(contexts.items[active.items[current]].rsp); 196 | } 197 | 198 | // TODO: think how to get rid of coroutine_init() call at all 199 | void coroutine_init(void) 200 | { 201 | if (contexts.count != 0) return; 202 | da_append(&contexts, (Context){0}); 203 | da_append(&active, 0); 204 | } 205 | 206 | void coroutine__finish_current(void) 207 | { 208 | if (active.items[current] == 0) { 209 | UNREACHABLE("Main Coroutine with id == 0 should never reach this place"); 210 | } 211 | 212 | da_append(&dead, active.items[current]); 213 | da_remove_unordered(&active, current); 214 | 215 | if (polls.count > 0) { 216 | int timeout = active.count == 0 ? -1 : 0; 217 | int result = poll(polls.items, polls.count, timeout); 218 | if (result < 0) TODO("poll"); 219 | 220 | for (size_t i = 0; i < polls.count;) { 221 | if (polls.items[i].revents) { 222 | size_t id = asleep.items[i]; 223 | da_remove_unordered(&polls, i); 224 | da_remove_unordered(&asleep, i); 225 | da_append(&active, id); 226 | } else { 227 | ++i; 228 | } 229 | } 230 | } 231 | 232 | assert(active.count > 0); 233 | current %= active.count; 234 | coroutine_restore_context(contexts.items[active.items[current]].rsp); 235 | } 236 | 237 | void coroutine_go(void (*f)(void*), void *arg) 238 | { 239 | size_t id; 240 | if (dead.count > 0) { 241 | id = dead.items[--dead.count]; 242 | } else { 243 | da_append(&contexts, ((Context){0})); 244 | id = contexts.count-1; 245 | contexts.items[id].stack_base = mmap(NULL, STACK_CAPACITY, PROT_WRITE|PROT_READ, MAP_PRIVATE|MAP_STACK|MAP_ANONYMOUS|MAP_GROWSDOWN, -1, 0); 246 | assert(contexts.items[id].stack_base != MAP_FAILED); 247 | } 248 | 249 | void **rsp = (void**)((char*)contexts.items[id].stack_base + STACK_CAPACITY); 250 | // @arch 251 | *(--rsp) = coroutine__finish_current; 252 | *(--rsp) = f; 253 | *(--rsp) = arg; // push rdi 254 | *(--rsp) = 0; // push rbx 255 | *(--rsp) = 0; // push rbp 256 | *(--rsp) = 0; // push r12 257 | *(--rsp) = 0; // push r13 258 | *(--rsp) = 0; // push r14 259 | *(--rsp) = 0; // push r15 260 | contexts.items[id].rsp = rsp; 261 | 262 | da_append(&active, id); 263 | } 264 | 265 | size_t coroutine_id(void) 266 | { 267 | return active.items[current]; 268 | } 269 | 270 | size_t coroutine_alive(void) 271 | { 272 | return active.count; 273 | } 274 | 275 | void coroutine_wake_up(size_t id) 276 | { 277 | // @speed coroutine_wake_up is linear 278 | for (size_t i = 0; i < asleep.count; ++i) { 279 | if (asleep.items[i] == id) { 280 | da_remove_unordered(&asleep, id); 281 | da_remove_unordered(&polls, id); 282 | da_append(&active, id); 283 | return; 284 | } 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /src/coroutine.c3: -------------------------------------------------------------------------------- 1 | // coroutine bindings for C3 (see coroutine.h for the API documentation) 2 | module coroutine; 3 | import std::net; 4 | 5 | def CoroutineFn = fn void(void*); 6 | 7 | extern fn void sleep_read(int fd) @extern("coroutine_sleep_read"); 8 | extern fn void sleep_write(int fd) @extern("coroutine_sleep_write"); 9 | extern fn void wake_up(usz id) @extern("coroutine_wake_up"); 10 | extern fn void init() @extern("coroutine_init"); 11 | extern fn void finish() @extern("coroutine_finish"); 12 | extern fn void yield() @extern("coroutine_yield"); 13 | extern fn void go(CoroutineFn f, void* arg = null) @extern("coroutine_go"); 14 | extern fn usz id() @extern("coroutine_id"); 15 | extern fn usz alive() @extern("coroutine_alive"); 16 | -------------------------------------------------------------------------------- /src/coroutine.h: -------------------------------------------------------------------------------- 1 | #ifndef COROUTINE_H_ 2 | #define COROUTINE_H_ 3 | 4 | // # What is a Coroutine? 5 | // 6 | // Coroutine is a lightweight user space thread with its own stack that can 7 | // suspend its execution and switch to another coroutine (see coroutine_yield() 8 | // function). Coroutines do not run in parallel but rather cooperatively switch 9 | // between each other whenever they feel like it. 10 | // 11 | // Coroutines are useful in cases when all your program does majority of the 12 | // time is waiting on IO. So with coroutines you have an opportunity to do 13 | // coroutine_yield() and go do something else. It is not useful to split up 14 | // heavy CPU computations because they all going to be executed on a single 15 | // thread. Use proper threads for that (pthreads on POSIX). 16 | // 17 | // Good use cases for coroutines are usually Network Applications and UI. 18 | // Anything with a slow Async IO. 19 | // 20 | // # How does it work? 21 | // 22 | // Each coroutine has its own separate call stack. Every time a new coroutine is 23 | // created with coroutine_go() a new call stack is allocated in dynamic memory. 24 | // The library manages a global array of coroutine stacks and switches between 25 | // them (on x86_64 literally swaps out the value of the RSP register) on every 26 | // coroutine_yield(), coroutine_sleep_read(), or coroutine_sleep_write(). 27 | 28 | #ifdef __cplusplus 29 | extern "C" { 30 | #endif // __cplusplus 31 | 32 | // TODO: consider making coroutine.h an stb-style single header library 33 | 34 | // Initialize the coroutine runtime. Must be called before using any other 35 | // functions of this API. After the initialization the currently running code is 36 | // considered the main coroutine with the id = 0. Should not be called twice. 37 | // TODO: Allow calling it twice, 'cause why not?! 38 | void coroutine_init(void); 39 | 40 | // Switch to the next coroutine. The execution will continue starting from 41 | // coroutine_yield() (or any other flavor of it like coroutine_sleep_read() or 42 | // coroutine_sleep_write) call of another coroutine. 43 | void coroutine_yield(void); 44 | 45 | // Create a new coroutine. This function does not automatically switch to the 46 | // new coroutine, but continues executing in the current coroutine. The 47 | // execution of the new coroutine will start as soon as the scheduler gets to it 48 | // handling the chains of coroutine_yield()-s. 49 | void coroutine_go(void (*f)(void*), void *arg); 50 | 51 | // The id of the current coroutine. 52 | size_t coroutine_id(void); 53 | 54 | // How many coroutines are currently alive. Could be used by the main coroutine 55 | // to wait until all the "child" coroutines have died. It may also continue from 56 | // the call of coroutine_sleep_read() and coroutine_sleep_write() if the 57 | // corresponding coroutine was woken up. 58 | size_t coroutine_alive(void); 59 | 60 | // Put the current coroutine to sleep until the non-blocking socket `fd` has 61 | // avaliable data to read. Trying to read from fd after coroutine_sleep_read() 62 | // may still cause EAGAIN, if the coroutine was woken up by coroutine_wake_up 63 | // before the socket became available for reading. You may treat this function 64 | // as a flavor of coroutine_yield(). 65 | void coroutine_sleep_read(int fd); 66 | 67 | // Put the current coroutine to sleep until the non-blocking socket `fd` is 68 | // ready to accept data to write. Trying to write to fd after 69 | // coroutine_sleep_write() may still cause EAGAIN, if the coroutine was woken up 70 | // by coroutine_wake_up before the socket became available for writing. You may 71 | // treat this function as a flavor of coroutine_yield(). 72 | void coroutine_sleep_write(int fd); 73 | 74 | // Wake up coroutine by id if it is currently sleeping due to 75 | // coroutine_sleep_read() or coroutine_sleep_write() calls. 76 | void coroutine_wake_up(size_t id); 77 | 78 | // TODO: implement sleeping by timeout 79 | // TODO: add timeouts to coroutine_sleep_read() and coroutine_sleep_write() 80 | 81 | #ifdef __cplusplus 82 | } 83 | #endif // __cplusplus 84 | 85 | #endif // COROUTINE_H_ 86 | -------------------------------------------------------------------------------- /src/cws.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "cws.h" 3 | #define NOB_STRIP_PREFIX 4 | #include "nob.h" 5 | #undef rename // stupid prefix bug in nob.h 6 | #include "teenysha1.h" 7 | #include "b64.h" 8 | 9 | typedef struct { 10 | unsigned char *items; 11 | size_t count; 12 | size_t capacity; 13 | } Cws_Payload_Buffer; 14 | 15 | typedef enum { 16 | CWS_OPCODE_CONT = 0x0, 17 | CWS_OPCODE_TEXT = 0x1, 18 | CWS_OPCODE_BIN = 0x2, 19 | CWS_OPCODE_CLOSE = 0x8, 20 | CWS_OPCODE_PING = 0x9, 21 | CWS_OPCODE_PONG = 0xA, 22 | } Cws_Opcode; 23 | 24 | static_assert((int)CWS_OPCODE_TEXT == (int)CWS_MESSAGE_TEXT, "Discrepancy between Cws_Opcode and Cws_Message_Kind"); 25 | static_assert((int)CWS_OPCODE_BIN == (int)CWS_MESSAGE_BIN, "Discrepancy between Cws_Opcode and Cws_Message_Kind"); 26 | 27 | typedef struct { 28 | bool fin, rsv1, rsv2, rsv3; 29 | Cws_Opcode opcode; 30 | bool masked; 31 | size_t payload_len; 32 | uint8_t mask[4]; 33 | } Cws_Frame_Header; 34 | 35 | // TODO: Make the CHUNK_SIZE customizable somehow 36 | // Maybe make it a runtime parameter of Cws, like the client flag. 37 | #define CHUNK_SIZE 1024 38 | 39 | #define CWS_FIN(header) (((header)[0] >> 7)&0x1); 40 | #define CWS_RSV1(header) (((header)[0] >> 6)&0x1); 41 | #define CWS_RSV2(header) (((header)[0] >> 5)&0x1); 42 | #define CWS_RSV3(header) (((header)[0] >> 4)&0x1); 43 | #define CWS_OPCODE(header) ((header)[0] & 0xF); 44 | #define CWS_MASK(header) ((header)[1] >> 7); 45 | #define CWS_PAYLOAD_LEN(header) ((header)[1] & 0x7F); 46 | 47 | // `cws__` with double underscore means that the function is private 48 | static int cws__socket_read_entire_buffer_raw(Cws_Socket socket, void *buffer, size_t len); 49 | static int cws__socket_write_entire_buffer_raw(Cws_Socket socket, const void *buffer, size_t len); 50 | static int cws__parse_sec_websocket_key_from_request(String_View *request, String_View *sec_websocket_key); 51 | static int cws__parse_sec_websocket_accept_from_response(String_View *response, String_View *sec_websocket_accept); 52 | static const char *cws__compute_sec_websocket_accept(Cws *cws, String_View sec_websocket_key); 53 | static int32_t cws__utf8_to_char32_fixed(unsigned char* ptr, size_t* size); 54 | static void cws__extend_unfinished_utf8(Cws *cws, Cws_Payload_Buffer *payload, size_t pos); 55 | static int cws__read_frame_header(Cws *cws, Cws_Frame_Header *frame_header); 56 | static int cws__read_frame_payload_chunk(Cws *cws, Cws_Frame_Header frame_header, unsigned char *payload, size_t payload_capacity, size_t payload_size); 57 | static int cws__read_frame_entire_payload(Cws *cws, Cws_Frame_Header frame_header, unsigned char **payload, size_t *payload_len); 58 | static int cws__send_frame(Cws *cws, bool fin, Cws_Opcode opcode, unsigned char *payload, size_t payload_len); 59 | static const char *cws__opcode_name(Cws *cws, Cws_Opcode opcode); 60 | static bool cws__opcode_is_control(Cws_Opcode opcode); 61 | 62 | void cws_close(Cws *cws) 63 | { 64 | // Ignoring any errors of socket operations because we are closing the connection anyway 65 | 66 | // TODO: The sender may give a reason of the close via the status code 67 | // See RFC6466, Section 7.4 68 | cws__send_frame(cws, true, CWS_OPCODE_CLOSE, NULL, 0); 69 | 70 | // Base on the ideas from https://blog.netherlabs.nl/articles/2009/01/18/the-ultimate-so_linger-page-or-why-is-my-tcp-not-reliable 71 | // Informing the OS that we are not planning to send anything anymore 72 | cws->socket.shutdown(cws->socket.data, CWS_SHUTDOWN_WRITE); 73 | // Depleting input before closing socket, so the OS does not send RST just because we have some input pending on close 74 | unsigned char buffer[1024]; 75 | while (true) { 76 | int n = cws->socket.read(cws->socket.data, buffer, sizeof(buffer)); 77 | if (n < 0) break; 78 | } 79 | 80 | // TODO: consider depleting the send buffer on Linux with ioctl(fd, SIOCOUTQ, &outstanding) 81 | // See https://blog.netherlabs.nl/articles/2009/01/18/the-ultimate-so_linger-page-or-why-is-my-tcp-not-reliable 82 | // for more info 83 | 84 | // Actually destroying the socket 85 | cws->socket.close(cws->socket.data); 86 | arena_free(&cws->arena); 87 | } 88 | 89 | static int cws__socket_read_entire_buffer_raw(Cws_Socket socket, void *buffer, size_t len) { 90 | char *buf = buffer; 91 | while (len > 0) { 92 | int n = socket.read(socket.data, buf, len); 93 | if (n < 0) return n; 94 | buf += n; 95 | len -= n; 96 | } 97 | return 0; 98 | } 99 | 100 | static int cws__socket_write_entire_buffer_raw(Cws_Socket socket, const void *buffer, size_t len) { 101 | const char *buf = buffer; 102 | while (len > 0) { 103 | int n = socket.write(socket.data, buf, len); 104 | if (n < 0) return n; 105 | buf += n; 106 | len -= n; 107 | } 108 | return 0; 109 | } 110 | 111 | int cws_server_handshake(Cws *cws) 112 | { 113 | // TODO: cws_server_handshake assumes that request fits into 1024 bytes 114 | char buffer[1024]; 115 | int ret = cws->socket.peek(cws->socket.data, buffer, ARRAY_LEN(buffer)); 116 | if (ret < 0) return ret; 117 | size_t buffer_size = ret; 118 | String_View request = nob_sv_from_parts(buffer, buffer_size); 119 | 120 | String_View sec_websocket_key = {0}; 121 | ret = cws__parse_sec_websocket_key_from_request(&request, &sec_websocket_key); 122 | if (ret < 0) return ret; 123 | 124 | ret = cws__socket_read_entire_buffer_raw(cws->socket, buffer, buffer_size - request.count); 125 | if (ret < 0) return ret; 126 | 127 | const char *sec_websocket_accept = cws__compute_sec_websocket_accept(cws, sec_websocket_key); 128 | 129 | char *response = arena_sprintf(&cws->arena, 130 | "HTTP/1.1 101 Switching Protocols\r\n" 131 | "Upgrade: websocket\r\n" 132 | "Connection: Upgrade\r\n" 133 | "Sec-WebSocket-Accept: %s\r\n" 134 | "\r\n", sec_websocket_accept); 135 | 136 | ret = cws__socket_write_entire_buffer_raw(cws->socket, response, strlen(response)); 137 | if (ret < 0) return ret; 138 | return 0; 139 | } 140 | 141 | // https://datatracker.ietf.org/doc/html/rfc6455#section-1.3 142 | // TODO: Ws.client_handshake should just accept a ws/wss URL 143 | int cws_client_handshake(Cws *cws, const char *host, const char *endpoint) 144 | { 145 | const char *handshake = arena_sprintf(&cws->arena, 146 | // TODO: customizable resource path 147 | "GET %s HTTP/1.1\r\n" 148 | "Host: %s\r\n" 149 | "Upgrade: websocket\r\n" 150 | "Connection: Upgrade\r\n" 151 | // TODO: Custom WebSocket key 152 | // Maybe even hardcode something that identifies cws? 153 | "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" 154 | "Sec-WebSocket-Version: 13\r\n" 155 | "\r\n", endpoint, host); 156 | 157 | int ret = cws__socket_write_entire_buffer_raw(cws->socket, handshake, strlen(handshake)); 158 | if (ret < 0) return ret; 159 | 160 | // TODO: Ws.client_handshake assumes that response fits into 1024 bytes 161 | char buffer[1024]; 162 | ret = cws->socket.peek(cws->socket.data, buffer, ARRAY_LEN(buffer)); 163 | if (ret < 0) return ret; 164 | size_t buffer_size = ret; 165 | String_View response = sv_from_parts(buffer, buffer_size); 166 | String_View sec_websocket_accept = {0}; 167 | ret = cws__parse_sec_websocket_accept_from_response(&response, &sec_websocket_accept); 168 | if (ret < 0) return ret; 169 | ret = cws->socket.read(cws->socket.data, buffer, buffer_size - response.count); 170 | if (ret < 0) return ret; 171 | if (!sv_eq(sec_websocket_accept, sv_from_cstr("s3pPLMBiTxaQ9kYGzzhZRbK+xOo="))) return CWS_ERROR_CLIENT_HANDSHAKE_BAD_ACCEPT; 172 | return 0; 173 | } 174 | 175 | static int cws__parse_sec_websocket_accept_from_response(String_View *response, String_View *sec_websocket_accept) 176 | { 177 | bool found_sec_websocket_accept = false; 178 | 179 | // TODO: verify the response status line 180 | // If the status code is an error one, log the message 181 | sv_chop_by_delim(response, '\n'); 182 | 183 | // TODO: verify the rest of the headers of the response 184 | // Right now we are only looking for Sec-WebSocket-Accept 185 | while (response->count > 0) { 186 | String_View header = sv_trim_left(sv_chop_by_delim(response, '\n')); 187 | if (header.count == 0) break; 188 | 189 | String_View key = sv_trim(sv_chop_by_delim(&header, ':')); 190 | String_View value = sv_trim(header); 191 | 192 | if (sv_eq(key, sv_from_cstr("Sec-WebSocket-Accept"))) { 193 | if (found_sec_websocket_accept) return CWS_ERROR_CLIENT_HANDSHAKE_DUPLICATE_ACCEPT; 194 | *sec_websocket_accept = value; 195 | found_sec_websocket_accept = true; 196 | } 197 | } 198 | if (!found_sec_websocket_accept) return CWS_ERROR_CLIENT_HANDSHAKE_NO_ACCEPT; 199 | return 0; 200 | } 201 | 202 | static int cws__send_frame(Cws *cws, bool fin, Cws_Opcode opcode, unsigned char *payload, size_t payload_len) 203 | { 204 | int ret; 205 | 206 | if (cws->debug) { 207 | printf("CWS DEBUG: TX FRAME: FIN(%d), OPCODE(%s), RSV(000), PAYLOAD_LEN: %zu\n", 208 | fin, 209 | cws__opcode_name(cws, opcode), 210 | payload_len); 211 | } 212 | 213 | // Send FIN and OPCODE 214 | { 215 | // NOTE: FIN is always set 216 | unsigned char data = (unsigned char) opcode; 217 | if (fin) data |= (1 << 7); 218 | ret = cws__socket_write_entire_buffer_raw(cws->socket, &data, 1); 219 | if (ret < 0) return ret; 220 | } 221 | 222 | // Send masked and payload length 223 | { 224 | // TODO: do we need to reverse the bytes on a machine with a different endianess than x86? 225 | // NOTE: client frames are always masked 226 | if (payload_len < 126) { 227 | unsigned char data = cws->client ? (1 << 7) : 0; 228 | data |= (unsigned char) payload_len; 229 | ret = cws__socket_write_entire_buffer_raw(cws->socket, &data, 1); 230 | if (ret < 0) return ret; 231 | } else if (payload_len <= UINT16_MAX) { 232 | unsigned char data = cws->client ? (1 << 7) : 0; 233 | data |= 126; 234 | ret = cws__socket_write_entire_buffer_raw(cws->socket, &data, 1); 235 | if (ret < 0) return ret; 236 | 237 | unsigned char len[2] = { 238 | (unsigned char)(payload_len >> (8 * 1)) & 0xFF, 239 | (unsigned char)(payload_len >> (8 * 0)) & 0xFF 240 | }; 241 | 242 | ret = cws__socket_write_entire_buffer_raw(cws->socket, len, ARRAY_LEN(len)); 243 | if (ret < 0) return ret; 244 | } else if (payload_len > UINT16_MAX) { 245 | unsigned char data = cws->client ? (1 << 7) : 0; 246 | data |= 127; 247 | unsigned char len[8] = { 248 | (unsigned char) (payload_len >> (8 * 7)) & 0xFF, 249 | (unsigned char) (payload_len >> (8 * 6)) & 0xFF, 250 | (unsigned char) (payload_len >> (8 * 5)) & 0xFF, 251 | (unsigned char) (payload_len >> (8 * 4)) & 0xFF, 252 | (unsigned char) (payload_len >> (8 * 3)) & 0xFF, 253 | (unsigned char) (payload_len >> (8 * 2)) & 0xFF, 254 | (unsigned char) (payload_len >> (8 * 1)) & 0xFF, 255 | (unsigned char) (payload_len >> (8 * 0)) & 0xFF 256 | }; 257 | 258 | ret = cws__socket_write_entire_buffer_raw(cws->socket, &data, 1); 259 | if (ret < 0) return ret; 260 | ret = cws__socket_write_entire_buffer_raw(cws->socket, len, ARRAY_LEN(len)); 261 | if (ret < 0) return ret; 262 | } 263 | } 264 | 265 | if (cws->client) { 266 | unsigned char mask[4] = {0}; 267 | 268 | // Generate and send mask 269 | { 270 | for (size_t i = 0; i < ARRAY_LEN(mask); ++i) { 271 | mask[i] = (unsigned char)(rand() % 0x100); 272 | } 273 | ret = cws__socket_write_entire_buffer_raw(cws->socket, mask, ARRAY_LEN(mask)); 274 | if (ret < 0) return ret; 275 | } 276 | 277 | // Mask the payload and send it 278 | for (size_t i = 0; i < payload_len; ) { 279 | unsigned char chunk[1024]; 280 | size_t chunk_size = 0; 281 | while (i < payload_len && chunk_size < ARRAY_LEN(chunk)) { 282 | chunk[chunk_size] = payload[i] ^ mask[i % 4]; 283 | chunk_size += 1; 284 | i += 1; 285 | } 286 | ret = cws__socket_write_entire_buffer_raw(cws->socket, chunk, chunk_size); 287 | if (ret < 0) return ret; 288 | } 289 | } else { 290 | ret = cws__socket_write_entire_buffer_raw(cws->socket, payload, payload_len); 291 | if (ret < 0) return ret; 292 | } 293 | 294 | return 0; 295 | } 296 | 297 | int cws_send_message(Cws *cws, Cws_Message_Kind kind, unsigned char *payload, size_t payload_len) 298 | { 299 | bool first = true; 300 | do { 301 | uint len = payload_len; 302 | if (len > CHUNK_SIZE) len = CHUNK_SIZE; 303 | bool fin = payload_len - len == 0; 304 | Cws_Opcode opcode = first ? (Cws_Opcode) kind : CWS_OPCODE_CONT; 305 | 306 | int ret = cws__send_frame(cws, fin, opcode, payload, len); 307 | if (ret < 0) return ret; 308 | 309 | payload += len; 310 | payload_len -= len; 311 | first = false; 312 | } while(payload_len > 0); 313 | 314 | return 0; 315 | } 316 | 317 | const char *cws_message_kind_name(Cws *cws, Cws_Message_Kind kind) 318 | { 319 | return cws__opcode_name(cws, (Cws_Opcode) kind); 320 | } 321 | 322 | static const char *cws__opcode_name(Cws *cws, Cws_Opcode opcode) 323 | { 324 | switch (opcode) { 325 | case CWS_OPCODE_CONT: return "CONT"; 326 | case CWS_OPCODE_TEXT: return "TEXT"; 327 | case CWS_OPCODE_BIN: return "BIN"; 328 | case CWS_OPCODE_CLOSE: return "CLOSE"; 329 | case CWS_OPCODE_PING: return "PING"; 330 | case CWS_OPCODE_PONG: return "PONG"; 331 | default: 332 | if (0x3 <= opcode && opcode <= 0x7) { 333 | return arena_sprintf(&cws->arena, "NONCONTROL(0x%X)", opcode & 0xF); 334 | } else if (0xB <= opcode && opcode <= 0xF) { 335 | return arena_sprintf(&cws->arena, "CONTROL(0x%X)", opcode & 0xF); 336 | } else { 337 | return arena_sprintf(&cws->arena, "INVALID(0x%X)", opcode & 0xF); 338 | } 339 | } 340 | } 341 | 342 | static bool cws__opcode_is_control(Cws_Opcode opcode) 343 | { 344 | // TODO: cws__opcode_name uses range 0xB <= opcode && opcode <= 0xF. Is this a bug? 345 | // 0xB and 0x8 kind do look the similar. I need to check the specs on that 346 | return 0x8 <= opcode && opcode <= 0xF; 347 | } 348 | 349 | 350 | static int cws__read_frame_header(Cws *cws, Cws_Frame_Header *frame_header) 351 | { 352 | unsigned char header[2]; 353 | 354 | // Read the header 355 | int ret = cws__socket_read_entire_buffer_raw(cws->socket, header, ARRAY_LEN(header)); 356 | if (ret < 0) return ret; 357 | frame_header->fin = (bool) CWS_FIN(header); 358 | frame_header->rsv1 = (bool) CWS_RSV1(header); 359 | frame_header->rsv2 = (bool) CWS_RSV2(header); 360 | frame_header->rsv3 = (bool) CWS_RSV3(header); 361 | frame_header->opcode = (Cws_Opcode) CWS_OPCODE(header); 362 | frame_header->masked = (bool) CWS_MASK(header); 363 | 364 | // Parse the payload length 365 | { 366 | // TODO: do we need to reverse the bytes on a machine with a different endianess than x86? 367 | unsigned char len = CWS_PAYLOAD_LEN(header); 368 | switch (len) { 369 | case 126: { 370 | unsigned char ext_len[2] = {0}; 371 | ret = cws__socket_read_entire_buffer_raw(cws->socket, ext_len, ARRAY_LEN(ext_len)); 372 | if (ret < 0) return ret; 373 | 374 | for (size_t i = 0; i < ARRAY_LEN(ext_len); ++i) { 375 | frame_header->payload_len = (frame_header->payload_len << 8) | ext_len[i]; 376 | } 377 | } break; 378 | case 127: { 379 | unsigned char ext_len[8] = {0}; 380 | ret = cws__socket_read_entire_buffer_raw(cws->socket, ext_len, ARRAY_LEN(ext_len)); 381 | if (ret < 0) return ret; 382 | 383 | for (size_t i = 0; i < ARRAY_LEN(ext_len); ++i) { 384 | frame_header->payload_len = (frame_header->payload_len << 8) | ext_len[i]; 385 | } 386 | } break; 387 | default: 388 | frame_header->payload_len = len; 389 | } 390 | } 391 | 392 | if (cws->debug) { 393 | printf("CWS DEBUG: RX FRAME: FIN(%d), OPCODE(%s), MASKED(%d), RSV(%d%d%d), PAYLOAD_LEN: %zu\n", 394 | frame_header->fin, 395 | cws__opcode_name(cws, frame_header->opcode), 396 | frame_header->masked, 397 | frame_header->rsv1, frame_header->rsv2, frame_header->rsv3, 398 | frame_header->payload_len); 399 | } 400 | 401 | // RFC 6455 - Section 5.5: 402 | // > All control frames MUST have a payload length of 125 bytes or less 403 | // > and MUST NOT be fragmented. 404 | if (cws__opcode_is_control(frame_header->opcode) && (frame_header->payload_len > 125 || !frame_header->fin)) { 405 | return CWS_ERROR_FRAME_CONTROL_TOO_BIG; 406 | } 407 | 408 | // RFC 6455 - Section 5.2: 409 | // > RSV1, RSV2, RSV3: 1 bit each 410 | // > 411 | // > MUST be 0 unless an extension is negotiated that defines meanings 412 | // > for non-zero values. If a nonzero value is received and none of 413 | // > the negotiated extensions defines the meaning of such a nonzero 414 | // > value, the receiving endpoint MUST _Fail the WebSocket 415 | // > Connection_. 416 | if (frame_header->rsv1 || frame_header->rsv2 || frame_header->rsv3) { 417 | return CWS_ERROR_FRAME_RESERVED_BITS_NOT_NEGOTIATED; 418 | } 419 | 420 | // Read the mask if masked 421 | if (frame_header->masked) { 422 | ret = cws__socket_read_entire_buffer_raw(cws->socket, frame_header->mask, ARRAY_LEN(frame_header->mask)); 423 | if (ret < 0) return ret; 424 | } 425 | 426 | return 0; 427 | } 428 | 429 | int cws__read_frame_payload_chunk(Cws *cws, Cws_Frame_Header frame_header, unsigned char *payload, size_t payload_len, size_t finished_payload_len) 430 | { 431 | assert(frame_header.payload_len == payload_len); 432 | if (finished_payload_len >= payload_len) return 0; 433 | unsigned char *unfinished_payload = payload + finished_payload_len; 434 | size_t unfinished_payload_len = payload_len - finished_payload_len; 435 | int ret = cws->socket.read(cws->socket.data, unfinished_payload, unfinished_payload_len); 436 | if (ret < 0) return ret; 437 | size_t n = ret; 438 | if (frame_header.masked) { 439 | for (size_t i = 0; i < unfinished_payload_len; ++i) { 440 | unfinished_payload[i] ^= frame_header.mask[(finished_payload_len + i) % 4]; 441 | } 442 | } 443 | return n; 444 | } 445 | 446 | static int cws__read_frame_entire_payload(Cws *cws, Cws_Frame_Header frame_header, unsigned char **payload, size_t *payload_len) 447 | { 448 | *payload_len = frame_header.payload_len; 449 | *payload = arena_alloc(&cws->arena, *payload_len); 450 | size_t finished_payload_len = 0; 451 | while (finished_payload_len < *payload_len) { 452 | int ret = cws__read_frame_payload_chunk(cws, frame_header, *payload, *payload_len, finished_payload_len); 453 | if (ret < 0) return ret; 454 | size_t n = ret; 455 | finished_payload_len += n; 456 | } 457 | return 0; 458 | } 459 | 460 | int cws_read_message(Cws *cws, Cws_Message *message) 461 | { 462 | Cws_Payload_Buffer payload = {0}; 463 | bool cont = false; 464 | size_t verify_pos = 0; 465 | 466 | for (;;) { 467 | Cws_Frame_Header frame = {0}; 468 | int ret = cws__read_frame_header(cws, &frame); 469 | if (ret < 0) return ret; 470 | if (cws__opcode_is_control(frame.opcode)) { 471 | unsigned char *payload; 472 | size_t payload_len; 473 | switch (frame.opcode) { 474 | case CWS_OPCODE_CLOSE: 475 | return CWS_ERROR_FRAME_CLOSE_SENT; 476 | case CWS_OPCODE_PING: 477 | ret = cws__read_frame_entire_payload(cws, frame, &payload, &payload_len); 478 | if (ret < 0) return ret; 479 | ret = cws__send_frame(cws, true, CWS_OPCODE_PONG, payload, payload_len); 480 | if (ret < 0) return ret; 481 | break; 482 | case CWS_OPCODE_PONG: 483 | ret = cws__read_frame_entire_payload(cws, frame, &payload, &payload_len); 484 | if (ret < 0) return ret; 485 | // Unsolicited PONGs are just ignored 486 | break; 487 | default: 488 | return CWS_ERROR_FRAME_UNEXPECTED_OPCODE; 489 | } 490 | } else { 491 | if (!cont) { 492 | switch (frame.opcode) { 493 | case CWS_OPCODE_TEXT: 494 | case CWS_OPCODE_BIN: 495 | message->kind = (Cws_Message_Kind) frame.opcode; 496 | break; 497 | default: 498 | return CWS_ERROR_FRAME_UNEXPECTED_OPCODE; 499 | } 500 | cont = true; 501 | } else { 502 | if (frame.opcode != CWS_OPCODE_CONT) { 503 | return CWS_ERROR_FRAME_UNEXPECTED_OPCODE; 504 | } 505 | } 506 | size_t frame_payload_len = frame.payload_len; 507 | unsigned char* frame_payload = arena_alloc(&cws->arena, frame_payload_len); 508 | size_t frame_finished_payload_len = 0; 509 | while (frame_finished_payload_len < frame_payload_len) { 510 | int ret = cws__read_frame_payload_chunk(cws, frame, frame_payload, frame_payload_len, frame_finished_payload_len); 511 | if (ret < 0) return ret; 512 | size_t n = ret; 513 | arena_sb_append_buf(&cws->arena, &payload, frame_payload + frame_finished_payload_len, n); 514 | frame_finished_payload_len += n; 515 | 516 | if (message->kind == CWS_MESSAGE_TEXT) { 517 | // Verifying UTF-8 518 | while (verify_pos < payload.count) { 519 | size_t size = payload.count - verify_pos; 520 | ret = cws__utf8_to_char32_fixed(&payload.items[verify_pos], &size); 521 | if (ret < 0) { 522 | if (ret != CWS_ERROR_UTF8_SHORT) return ret; // Fail-fast on invalid UTF-8 that is not unfinished UTF-8 523 | if (frame.fin) return ret; // Not tolerating unfinished UTF-8 if the message is finished 524 | // Extending the finished UTF-8 to check if it fixes the problem 525 | size_t saved_len = payload.count; 526 | cws__extend_unfinished_utf8(cws, &payload, verify_pos); 527 | size = payload.count - verify_pos; 528 | ret = cws__utf8_to_char32_fixed(&payload.items[verify_pos], &size); 529 | if (ret < 0) return ret; 530 | payload.count = saved_len; 531 | break; // Tolerating the unfinished UTF-8 sequences if the message is unfinished 532 | } 533 | verify_pos += size; 534 | } 535 | } 536 | } 537 | if (frame.fin) break; 538 | } 539 | } 540 | 541 | message->payload = payload.items; 542 | message->payload_len = payload.count; 543 | 544 | return 0; 545 | } 546 | 547 | static int32_t cws__utf8_to_char32_fixed(unsigned char* ptr, size_t* size) 548 | { 549 | size_t max_size = *size; 550 | if (max_size < 1) return CWS_ERROR_UTF8_SHORT; 551 | unsigned char c = (ptr++)[0]; 552 | 553 | if ((c & 0x80) == 0) 554 | { 555 | *size = 1; 556 | return c; 557 | } 558 | if ((c & 0xE0) == 0xC0) 559 | { 560 | if (max_size < 2) return CWS_ERROR_UTF8_SHORT; 561 | *size = 2; 562 | uint32_t uc = (c & 0x1F) << 6; 563 | c = *ptr; 564 | // Overlong sequence or invalid second. 565 | if (!uc || (c & 0xC0) != 0x80) return CWS_ERROR_UTF8_INVALID; 566 | uc = uc + (c & 0x3F); 567 | // maximum overlong sequence 568 | if (uc <= 0x7F) return CWS_ERROR_UTF8_INVALID; 569 | // UTF-16 surrogate pairs 570 | if (0xD800 <= uc && uc <= 0xDFFF) return CWS_ERROR_UTF8_INVALID; 571 | return uc; 572 | } 573 | if ((c & 0xF0) == 0xE0) 574 | { 575 | if (max_size < 3) return CWS_ERROR_UTF8_SHORT; 576 | *size = 3; 577 | uint32_t uc = (c & 0x0F) << 12; 578 | c = ptr++[0]; 579 | if ((c & 0xC0) != 0x80) return CWS_ERROR_UTF8_INVALID; 580 | uc += (c & 0x3F) << 6; 581 | c = ptr++[0]; 582 | // Overlong sequence or invalid last 583 | if (!uc || (c & 0xC0) != 0x80) return CWS_ERROR_UTF8_INVALID; 584 | uc = uc + (c & 0x3F); 585 | // maximum overlong sequence 586 | if (uc <= 0x7FF) return CWS_ERROR_UTF8_INVALID; 587 | // UTF-16 surrogate pairs 588 | if (0xD800 <= uc && uc <= 0xDFFF) return CWS_ERROR_UTF8_INVALID; 589 | return uc; 590 | } 591 | if (max_size < 4) return CWS_ERROR_UTF8_SHORT; 592 | if ((c & 0xF8) != 0xF0) return CWS_ERROR_UTF8_INVALID; 593 | *size = 4; 594 | uint32_t uc = (c & 0x07) << 18; 595 | c = ptr++[0]; 596 | if ((c & 0xC0) != 0x80) return CWS_ERROR_UTF8_INVALID; 597 | uc += (c & 0x3F) << 12; 598 | c = ptr++[0]; 599 | if ((c & 0xC0) != 0x80) return CWS_ERROR_UTF8_INVALID; 600 | uc += (c & 0x3F) << 6; 601 | c = ptr++[0]; 602 | // Overlong sequence or invalid last 603 | if (!uc || (c & 0xC0) != 0x80) return CWS_ERROR_UTF8_INVALID; 604 | uc = uc + (c & 0x3F); 605 | // UTF-16 surrogate pairs 606 | if (0xD800 <= uc && uc <= 0xDFFF) return CWS_ERROR_UTF8_INVALID; 607 | // maximum overlong sequence 608 | if (uc <= 0xFFFF) return CWS_ERROR_UTF8_INVALID; 609 | // Maximum valid Unicode number 610 | if (uc > 0x10FFFF) return CWS_ERROR_UTF8_INVALID; 611 | return uc; 612 | } 613 | 614 | static void cws__extend_unfinished_utf8(Cws *cws, Cws_Payload_Buffer *payload, size_t pos) 615 | { 616 | unsigned char c = payload->items[pos]; 617 | size_t size = 0; 618 | if ((c & 0x80) == 0) { 619 | size = 1; 620 | } else if ((c & 0xE0) == 0xC0) { 621 | size = 2; 622 | } else if ((c & 0xF0) == 0xE0) { 623 | size = 3; 624 | } else { 625 | size = 4; 626 | } 627 | while (payload->count - pos < size) arena_da_append(&cws->arena, payload, 0x80); 628 | } 629 | 630 | static int cws__parse_sec_websocket_key_from_request(String_View *request, String_View *sec_websocket_key) 631 | { 632 | bool found_sec_websocket_key = false; 633 | 634 | // TODO: verify the request status line 635 | sv_chop_by_delim(request, '\n'); 636 | 637 | // TODO: verify the rest of the headers of the request 638 | // Right now we are only looking for Sec-WebSocket-Key 639 | while (request->count > 0) { 640 | String_View header = sv_trim_left(sv_chop_by_delim(request, '\n')); 641 | if (header.count == 0) break; 642 | 643 | String_View key = sv_trim(sv_chop_by_delim(&header, ':')); 644 | String_View value = sv_trim(header); 645 | 646 | if (sv_eq(key, sv_from_cstr("Sec-WebSocket-Key"))) { 647 | if (found_sec_websocket_key) return CWS_ERROR_SERVER_HANDSHAKE_DUPLICATE_KEY; 648 | *sec_websocket_key = value; 649 | found_sec_websocket_key = true; 650 | } 651 | } 652 | if (!found_sec_websocket_key) return CWS_ERROR_SERVER_HANDSHAKE_NO_KEY; 653 | return 0; 654 | } 655 | 656 | static const char *cws__compute_sec_websocket_accept(Cws *cws, String_View sec_websocket_key) 657 | { 658 | const char *src = arena_sprintf(&cws->arena, SV_Fmt"258EAFA5-E914-47DA-95CA-C5AB0DC85B11", SV_Arg(sec_websocket_key)); 659 | SHA1 sha1 = {0}; 660 | sha1_reset(&sha1); 661 | sha1_process_bytes(&sha1, src, strlen(src)); 662 | digest8_t digest; 663 | sha1_get_digest_bytes(&sha1, digest); 664 | size_t sec_websocket_accept_len = b64_encode_out_len(sizeof(digest)) + 1; 665 | char *sec_websocket_accept = arena_alloc(&cws->arena, sec_websocket_accept_len); 666 | b64_encode((void*)digest, sizeof(digest), sec_websocket_accept, sec_websocket_accept_len, B64_STD_ALPHA, B64_DEFAULT_PAD); 667 | sec_websocket_accept[sec_websocket_accept_len-1] = '\0'; 668 | return sec_websocket_accept; 669 | } 670 | 671 | const char *cws_error_message(Cws *cws, Cws_Error error) 672 | { 673 | switch (error) { 674 | case CWS_OK: return "OK"; 675 | case CWS_ERROR_ERRNO: return strerror(errno); 676 | case CWS_ERROR_CONNECTION_CLOSED: return "Connection closed"; 677 | case CWS_ERROR_FRAME_CONTROL_TOO_BIG: return "Control frame too big"; 678 | case CWS_ERROR_FRAME_RESERVED_BITS_NOT_NEGOTIATED: return "Unnegotiated reserved frame bits"; 679 | case CWS_ERROR_FRAME_CLOSE_SENT: return "Close frame was sent"; 680 | case CWS_ERROR_FRAME_UNEXPECTED_OPCODE: return "Unexpected opcode frame"; 681 | case CWS_ERROR_UTF8_SHORT: return "UTF-8 sequence is too short"; 682 | case CWS_ERROR_UTF8_INVALID: return "UTF-8 sequence is invalid"; 683 | case CWS_ERROR_SERVER_HANDSHAKE_DUPLICATE_KEY: return "Server Handshake: duplicate Sec-WebSocket-Key"; 684 | case CWS_ERROR_SERVER_HANDSHAKE_NO_KEY: return "Server Handshake: Sec-WebSocket-Key is missing"; 685 | case CWS_ERROR_CLIENT_HANDSHAKE_BAD_ACCEPT: return "Client Handshake: bad Sec-WebSocket-Accept"; 686 | case CWS_ERROR_CLIENT_HANDSHAKE_DUPLICATE_ACCEPT: return "Client Handshake: duplicate Sec-WebSocket-Accept"; 687 | case CWS_ERROR_CLIENT_HANDSHAKE_NO_ACCEPT: return "Client Handshake: no Sec-WebSocket-Accept"; 688 | default: if (error <= CWS_ERROR_CUSTOM) { 689 | return arena_sprintf(&cws->arena, "Custom error (%d)", error); 690 | } else if (CWS_ERROR_CUSTOM < error && error < CWS_OK) { 691 | return arena_sprintf(&cws->arena, "Unknown error (%d)", error); 692 | } else { 693 | return arena_sprintf(&cws->arena, "Not an error (%d)", error); 694 | } 695 | } 696 | } 697 | 698 | #define ARENA_IMPLEMENTATION 699 | #include "arena.h" 700 | #define NOB_IMPLEMENTATION 701 | #include "nob.h" 702 | #define TEENY_SHA1_IMPLEMENTATION 703 | #include "teenysha1.h" 704 | #define B64_IMPLEMENTATION 705 | #include "b64.h" 706 | -------------------------------------------------------------------------------- /src/cws.c3: -------------------------------------------------------------------------------- 1 | // C3 bindings for cws 2 | module cws; 3 | import arena; 4 | 5 | distinct CwsShutdownHow = int; 6 | const CwsShutdownHow SHUTDOWN_READ = 0; 7 | const CwsShutdownHow SHUTDOWN_WRITE = 1; 8 | const CwsShutdownHow SHUTDOWN_BOTH = 2; 9 | 10 | distinct CwsError = int; 11 | const CwsError OK = 0; 12 | const CwsError ERROR_ERRNO = -1; 13 | const CwsError ERROR_CONNECTION_CLOSED = -2; 14 | const CwsError ERROR_FRAME_CONTROL_TOO_BIG = -3; 15 | const CwsError ERROR_FRAME_RESERVED_BITS_NOT_NEGOTIATED = -4; 16 | const CwsError ERROR_FRAME_CLOSE_SENT = -5; 17 | const CwsError ERROR_FRAME_UNEXPECTED_OPCODE = -6; 18 | const CwsError ERROR_UTF8_SHORT = -7; 19 | const CwsError ERROR_UTF8_INVALID = -8; 20 | const CwsError ERROR_SERVER_HANDSHAKE_DUPLICATE_KEY = -9; 21 | const CwsError ERROR_SERVER_HANDSHAKE_NO_KEY = -10; 22 | const CwsError ERROR_CLIENT_HANDSHAKE_BAD_ACCEPT = -11; 23 | const CwsError ERROR_CLIENT_HANDSHAKE_DUPLICATE_ACCEPT = -12; 24 | const CwsError ERROR_CLIENT_HANDSHAKE_NO_ACCEPT = -13; 25 | const CwsError ERROR_CUSTOM = -100; 26 | 27 | def CwsSocketReadFn = fn int(void* data, void* buffer, usz len); 28 | def CwsSocketWriteFn = fn int(void* data, void* buffer, usz len); 29 | def CwsSocketPeekFn = fn int(void* data, void* buffer, usz len); 30 | def CwsSocketShutdownFn = fn int(void* data, CwsShutdownHow how); 31 | def CwsSocketCloseFn = fn int(void *data); 32 | 33 | struct CwsSocket { 34 | void* data; 35 | CwsSocketReadFn read; 36 | CwsSocketWriteFn write; 37 | CwsSocketPeekFn peek; 38 | CwsSocketShutdownFn shutdown; 39 | CwsSocketCloseFn close; 40 | } 41 | 42 | distinct CwsMessageKind = int; 43 | const CwsMessageKind MESSAGE_TEXT = 0x1; 44 | const CwsMessageKind MESSAGE_BIN = 0x2; 45 | 46 | struct CwsMessage { 47 | CwsMessageKind kind; 48 | char *payload; 49 | usz payload_len; 50 | } 51 | 52 | struct Cws { 53 | CwsSocket socket; 54 | Arena arena; 55 | bool debug; // Enable debug logging 56 | bool client; 57 | } 58 | 59 | extern fn ZString message_kind_name(Cws *cws, CwsMessageKind kind) @extern("cws_message_kind_name"); 60 | extern fn int server_handshake(Cws *cws) @extern("cws_server_handshake"); 61 | extern fn int client_handshake(Cws *cws, ZString host, ZString endpoint) @extern("cws_client_handshake"); 62 | extern fn int send_message(Cws *cws, CwsMessageKind kind, char *payload, usz payload_len) @extern("cws_send_message"); 63 | extern fn int read_message(Cws *cws, CwsMessage *message) @extern("cws_read_message"); 64 | extern fn void close(Cws *cws) @extern("cws_close"); 65 | extern fn ZString error_message(Cws *cws, CwsError error) @extern("cws_error_message"); 66 | 67 | module arena; 68 | 69 | struct Arena { 70 | void*[2] opaque; 71 | } 72 | 73 | extern fn void reset(Arena *a) @extern("arena_reset"); 74 | extern fn void free(Arena *a) @extern("arena_free"); 75 | -------------------------------------------------------------------------------- /src/cws.h: -------------------------------------------------------------------------------- 1 | #ifndef CWS_H_ 2 | #define CWS_H_ 3 | 4 | #include 5 | #include 6 | #include "arena.h" 7 | 8 | // TODO: run autobahn testsuit on CI and deploy reports to github pages 9 | 10 | typedef enum { 11 | CWS_SHUTDOWN_READ, 12 | CWS_SHUTDOWN_WRITE, 13 | CWS_SHUTDOWN_BOTH, 14 | } Cws_Shutdown_How; 15 | 16 | // The errors are returned as negative values from cws_* functions 17 | typedef enum { 18 | CWS_OK = 0, 19 | CWS_ERROR_ERRNO = -1, 20 | CWS_ERROR_CONNECTION_CLOSED = -2, 21 | CWS_ERROR_FRAME_CONTROL_TOO_BIG = -3, 22 | CWS_ERROR_FRAME_RESERVED_BITS_NOT_NEGOTIATED = -4, 23 | CWS_ERROR_FRAME_CLOSE_SENT = -5, 24 | CWS_ERROR_FRAME_UNEXPECTED_OPCODE = -6, 25 | CWS_ERROR_UTF8_SHORT = -7, 26 | CWS_ERROR_UTF8_INVALID = -8, 27 | CWS_ERROR_SERVER_HANDSHAKE_DUPLICATE_KEY = -9, 28 | CWS_ERROR_SERVER_HANDSHAKE_NO_KEY = -10, 29 | CWS_ERROR_CLIENT_HANDSHAKE_BAD_ACCEPT = -11, 30 | CWS_ERROR_CLIENT_HANDSHAKE_DUPLICATE_ACCEPT = -12, 31 | CWS_ERROR_CLIENT_HANDSHAKE_NO_ACCEPT = -13, 32 | CWS_ERROR_CUSTOM = -100, 33 | } Cws_Error; 34 | 35 | // TODO: Maybe cws should ship some stock implementations of Cws_Socket backends: 36 | // - Plain sync 37 | // - Plain async on coroutines 38 | // - TLS sync 39 | // - TLS async on coroutines (if coroutines even work with OpenSSL) 40 | // Some of them are already implemented in examples 41 | 42 | // NOTE: read, write, and peek must never return 0. On internally returning 0 they must return CWS_ERROR_CONNECTION_CLOSED 43 | typedef struct { 44 | void *data; 45 | int (*read)(void *data, void *buffer, size_t len); 46 | // peek: like read, but does not remove data from the buffer 47 | // Usually implemented via MSG_PEEK flag of recv 48 | int (*peek)(void *data, void *buffer, size_t len); 49 | int (*write)(void *data, const void *buffer, size_t len); 50 | int (*shutdown)(void *data, Cws_Shutdown_How how); 51 | int (*close)(void *data); 52 | } Cws_Socket; 53 | 54 | typedef struct { 55 | Cws_Socket socket; 56 | Arena arena; // All the dynamic memory allocations done by cws go into this arena 57 | bool debug; // Enable debug logging 58 | bool client; 59 | } Cws; 60 | 61 | typedef enum { 62 | CWS_MESSAGE_TEXT = 0x1, 63 | CWS_MESSAGE_BIN = 0x2, 64 | } Cws_Message_Kind; 65 | 66 | typedef struct { 67 | Cws_Message_Kind kind; 68 | unsigned char *payload; 69 | size_t payload_len; 70 | } Cws_Message; 71 | 72 | const char *cws_message_kind_name(Cws *cws, Cws_Message_Kind kind); 73 | const char *cws_error_message(Cws *cws, Cws_Error error); 74 | // TODO: cws_server_handshake should allow you to inspect endpoints requested by clients and reject them 75 | int cws_server_handshake(Cws *cws); 76 | int cws_client_handshake(Cws *cws, const char *host, const char *endpoint); 77 | int cws_send_message(Cws *cws, Cws_Message_Kind kind, unsigned char *payload, size_t payload_len); 78 | int cws_read_message(Cws *cws, Cws_Message *message); 79 | void cws_close(Cws *cws); 80 | 81 | #endif // CWS_H_ 82 | -------------------------------------------------------------------------------- /src/nob.h: -------------------------------------------------------------------------------- 1 | /* nob - v1.20.2 - Public Domain - https://github.com/tsoding/nob.h 2 | 3 | This library is the next generation of the [NoBuild](https://github.com/tsoding/nobuild) idea. 4 | 5 | # Quick Example 6 | 7 | ```c 8 | // nob.c 9 | #define NOB_IMPLEMENTATION 10 | #include "nob.h" 11 | 12 | int main(int argc, char **argv) 13 | { 14 | NOB_GO_REBUILD_URSELF(argc, argv); 15 | Nob_Cmd cmd = {0}; 16 | nob_cmd_append(&cmd, "cc", "-Wall", "-Wextra", "-o", "main", "main.c"); 17 | if (!nob_cmd_run_sync(cmd)) return 1; 18 | return 0; 19 | } 20 | ``` 21 | 22 | ```console 23 | $ cc -o nob nob.c 24 | $ ./nob 25 | ``` 26 | 27 | The `nob` automatically rebuilds itself if `nob.c` is modified thanks to 28 | the `NOB_GO_REBUILD_URSELF` macro (don't forget to check out how it works below) 29 | 30 | # The Zoo of `nob_cmd_run_*` Functions 31 | 32 | `Nob_Cmd` is just a dynamic array of strings which represents a command and its arguments. 33 | First you append the arguments 34 | 35 | ```c 36 | Nob_Cmd cmd = {0}; 37 | nob_cmd_append(&cmd, "cc", "-Wall", "-Wextra", "-o", "main", "main.c"); 38 | ``` 39 | 40 | Then you run it 41 | 42 | ```c 43 | if (!nob_cmd_run_sync(cmd)) return 1; 44 | ``` 45 | 46 | `*_sync` at the end indicates that the function blocks until the command finishes executing 47 | and returns `true` on success and `false` on failure. You can run the command asynchronously 48 | but you have to explitictly wait for it afterwards: 49 | 50 | ```c 51 | Nob_Proc p = nob_cmd_run_async(cmd); 52 | if (p == NOB_INVALID_PROC) return 1; 53 | if (!nob_proc_wait(p)) return 1; 54 | ``` 55 | 56 | One of the problems with running commands like that is that `Nob_Cmd` still contains the arguments 57 | from the previously run command. If you want to reuse the same `Nob_Cmd` you have to not forget to reset 58 | it 59 | 60 | ```c 61 | Nob_Cmd cmd = {0}; 62 | 63 | nob_cmd_append(&cmd, "cc", "-Wall", "-Wextra", "-o", "main", "main.c"); 64 | if (!nob_cmd_run_sync(cmd)) return 1; 65 | cmd.count = 0; 66 | 67 | nob_cmd_append(&cmd, "./main", "foo", "bar", "baz"); 68 | if (!nob_cmd_run_sync(cmd)) return 1; 69 | cmd.count = 0; 70 | ``` 71 | 72 | Which is a bit error prone. To make it a bit easier we have `nob_cmd_run_sync_and_reset()` which 73 | accepts `Nob_Cmd` by reference and resets it for you: 74 | 75 | ```c 76 | Nob_Cmd cmd = {0}; 77 | 78 | nob_cmd_append(&cmd, "cc", "-Wall", "-Wextra", "-o", "main", "main.c"); 79 | if (!nob_cmd_run_sync_and_reset(&cmd)) return 1; 80 | 81 | nob_cmd_append(&cmd, "./main", "foo", "bar", "baz"); 82 | if (!nob_cmd_run_sync_and_reset(&cmd)) return 1; 83 | ``` 84 | 85 | There is of course also `nob_cmd_run_async_and_reset()` to maintain the pattern. 86 | 87 | The stdin, stdout and stderr of any command can be redirected by using `Nob_Cmd_Redirect` structure 88 | along with `nob_cmd_run_sync_redirect()` or `nob_cmd_run_async_redirect()` 89 | 90 | ```c 91 | // Opening all the necessary files 92 | Nob_Fd fdin = nob_fd_open_for_read("input.txt"); 93 | if (fdin == NOB_INVALID_FD) return 1; 94 | Nob_Fd fdout = nob_fd_open_for_write("output.txt"); 95 | if (fdout == NOB_INVALID_FD) return 1; 96 | Nob_Fd fderr = nob_fd_open_for_write("error.txt"); 97 | if (fderr == NOB_INVALID_FD) return 1; 98 | 99 | // Preparing the command 100 | Nob_Cmd cmd = {0}; 101 | nob_cmd_append(&cmd, "./main", "foo", "bar", "baz"); 102 | 103 | // Running the command synchronously redirecting the standard streams 104 | bool ok = nob_cmd_run_sync_redirect(cmd, (Nob_Cmd_Redirect) { 105 | .fdin = fdin, 106 | .fdout = fdout, 107 | .fderr = fderr, 108 | }); 109 | if (!ok) return 1; 110 | 111 | // Closing all the files 112 | nob_fd_close(fdin); 113 | nob_fd_close(fdout); 114 | nob_fd_close(fderr); 115 | 116 | // Reseting the command 117 | cmd.count = 0; 118 | ``` 119 | 120 | And of course if you find closing the files and reseting the command annoying we have 121 | `nob_cmd_run_sync_redirect_and_reset()` and `nob_cmd_run_async_redirect_and_reset()` 122 | which do all of that for you automatically. 123 | 124 | All the Zoo of `nob_cmd_run_*` functions follows the same pattern: sync/async, 125 | redirect/no redirect, and_reset/no and_reset. They always come in that order. 126 | 127 | # Stripping off `nob_` Prefixes 128 | 129 | Since Pure C does not have any namespaces we prefix each name of the API with the `nob_` to avoid any 130 | potential conflicts with any other names in your code. But sometimes it is very annoying and makes 131 | the code noisy. If you know that none of the names from nob.h conflict with anything in your code 132 | you can enable NOB_STRIP_PREFIX macro and just drop all the prefixes: 133 | 134 | ```c 135 | // nob.c 136 | #define NOB_IMPLEMENTATION 137 | #define NOB_STRIP_PREFIX 138 | #include "nob.h" 139 | 140 | int main(int argc, char **argv) 141 | { 142 | NOB_GO_REBUILD_URSELF(argc, argv); 143 | Cmd cmd = {0}; 144 | cmd_append(&cmd, "cc", "-Wall", "-Wextra", "-o", "main", "main.c"); 145 | if (!cmd_run_sync(cmd)) return 1; 146 | return 0; 147 | } 148 | ``` 149 | 150 | Not all the names have strippable prefixes. All the redefinable names like `NOB_GO_REBUILD_URSELF` 151 | for instance will retain their prefix even if NOB_STRIP_PREFIX is enabled. Notable exception is the 152 | nob_log() function. Stripping away the prefix results in log() which was historically always referring 153 | to the natural logarithmic function that is already defined in math.h. So there is no reason to strip 154 | off the prefix for nob_log(). 155 | 156 | The prefixes are stripped off only on the level of preprocessor. The names of the functions in the 157 | compiled object file will still retain the `nob_` prefix. Keep that in mind when you FFI with nob.h 158 | from other languages (for whatever reason). 159 | 160 | If only few specific names create conflicts for you, you can just #undef those names after the 161 | `#include ` since they are macros anyway. 162 | */ 163 | 164 | #ifndef NOB_H_ 165 | #define NOB_H_ 166 | 167 | #ifndef NOB_ASSERT 168 | #include 169 | #define NOB_ASSERT assert 170 | #endif /* NOB_ASSERT */ 171 | 172 | #ifndef NOB_REALLOC 173 | #include 174 | #define NOB_REALLOC realloc 175 | #endif /* NOB_REALLOC */ 176 | 177 | #ifndef NOB_FREE 178 | #include 179 | #define NOB_FREE free 180 | #endif /* NOB_FREE */ 181 | 182 | #include 183 | #include 184 | #include 185 | #include 186 | #include 187 | #include 188 | #include 189 | #include 190 | 191 | #ifdef _WIN32 192 | # define WIN32_LEAN_AND_MEAN 193 | # define _WINUSER_ 194 | # define _WINGDI_ 195 | # define _IMM_ 196 | # define _WINCON_ 197 | # include 198 | # include 199 | # include 200 | #else 201 | # include 202 | # include 203 | # include 204 | # include 205 | # include 206 | #endif 207 | 208 | #ifdef _WIN32 209 | # define NOB_LINE_END "\r\n" 210 | #else 211 | # define NOB_LINE_END "\n" 212 | #endif 213 | 214 | #if defined(__GNUC__) || defined(__clang__) 215 | // https://gcc.gnu.org/onlinedocs/gcc-4.7.2/gcc/Function-Attributes.html 216 | #define NOB_PRINTF_FORMAT(STRING_INDEX, FIRST_TO_CHECK) __attribute__ ((format (printf, STRING_INDEX, FIRST_TO_CHECK))) 217 | #else 218 | // TODO: implement NOB_PRINTF_FORMAT for MSVC 219 | #define NOB_PRINTF_FORMAT(STRING_INDEX, FIRST_TO_CHECK) 220 | #endif 221 | 222 | #define NOB_UNUSED(value) (void)(value) 223 | #define NOB_TODO(message) do { fprintf(stderr, "%s:%d: TODO: %s\n", __FILE__, __LINE__, message); abort(); } while(0) 224 | #define NOB_UNREACHABLE(message) do { fprintf(stderr, "%s:%d: UNREACHABLE: %s\n", __FILE__, __LINE__, message); abort(); } while(0) 225 | 226 | #define NOB_ARRAY_LEN(array) (sizeof(array)/sizeof(array[0])) 227 | #define NOB_ARRAY_GET(array, index) \ 228 | (NOB_ASSERT((size_t)index < NOB_ARRAY_LEN(array)), array[(size_t)index]) 229 | 230 | typedef enum { 231 | NOB_INFO, 232 | NOB_WARNING, 233 | NOB_ERROR, 234 | NOB_NO_LOGS, 235 | } Nob_Log_Level; 236 | 237 | // Any messages with the level below nob_minimal_log_level are going to be suppressed. 238 | extern Nob_Log_Level nob_minimal_log_level; 239 | 240 | void nob_log(Nob_Log_Level level, const char *fmt, ...) NOB_PRINTF_FORMAT(2, 3); 241 | 242 | // It is an equivalent of shift command from bash. It basically pops an element from 243 | // the beginning of a sized array. 244 | #define nob_shift(xs, xs_sz) (NOB_ASSERT((xs_sz) > 0), (xs_sz)--, *(xs)++) 245 | // NOTE: nob_shift_args() is an alias for an old variant of nob_shift that only worked with 246 | // the command line arguments passed to the main() function. nob_shift() is more generic. 247 | // So nob_shift_args() is semi-deprecated, but I don't see much reason to urgently 248 | // remove it. This alias does not hurt anybody. 249 | #define nob_shift_args(argc, argv) nob_shift(*argv, *argc) 250 | 251 | typedef struct { 252 | const char **items; 253 | size_t count; 254 | size_t capacity; 255 | } Nob_File_Paths; 256 | 257 | typedef enum { 258 | NOB_FILE_REGULAR = 0, 259 | NOB_FILE_DIRECTORY, 260 | NOB_FILE_SYMLINK, 261 | NOB_FILE_OTHER, 262 | } Nob_File_Type; 263 | 264 | bool nob_mkdir_if_not_exists(const char *path); 265 | bool nob_copy_file(const char *src_path, const char *dst_path); 266 | bool nob_copy_directory_recursively(const char *src_path, const char *dst_path); 267 | bool nob_read_entire_dir(const char *parent, Nob_File_Paths *children); 268 | bool nob_write_entire_file(const char *path, const void *data, size_t size); 269 | Nob_File_Type nob_get_file_type(const char *path); 270 | bool nob_delete_file(const char *path); 271 | 272 | #define nob_return_defer(value) do { result = (value); goto defer; } while(0) 273 | 274 | // Initial capacity of a dynamic array 275 | #ifndef NOB_DA_INIT_CAP 276 | #define NOB_DA_INIT_CAP 256 277 | #endif 278 | 279 | #define nob_da_reserve(da, expected_capacity) \ 280 | do { \ 281 | if ((expected_capacity) > (da)->capacity) { \ 282 | if ((da)->capacity == 0) { \ 283 | (da)->capacity = NOB_DA_INIT_CAP; \ 284 | } \ 285 | while ((expected_capacity) > (da)->capacity) { \ 286 | (da)->capacity *= 2; \ 287 | } \ 288 | (da)->items = NOB_REALLOC((da)->items, (da)->capacity * sizeof(*(da)->items)); \ 289 | NOB_ASSERT((da)->items != NULL && "Buy more RAM lol"); \ 290 | } \ 291 | } while (0) 292 | 293 | // Append an item to a dynamic array 294 | #define nob_da_append(da, item) \ 295 | do { \ 296 | nob_da_reserve((da), (da)->count + 1); \ 297 | (da)->items[(da)->count++] = (item); \ 298 | } while (0) 299 | 300 | #define nob_da_free(da) NOB_FREE((da).items) 301 | 302 | // Append several items to a dynamic array 303 | #define nob_da_append_many(da, new_items, new_items_count) \ 304 | do { \ 305 | nob_da_reserve((da), (da)->count + (new_items_count)); \ 306 | memcpy((da)->items + (da)->count, (new_items), (new_items_count)*sizeof(*(da)->items)); \ 307 | (da)->count += (new_items_count); \ 308 | } while (0) 309 | 310 | #define nob_da_resize(da, new_size) \ 311 | do { \ 312 | nob_da_reserve((da), new_size); \ 313 | (da)->count = (new_size); \ 314 | } while (0) 315 | 316 | #define nob_da_last(da) (da)->items[(NOB_ASSERT((da)->count > 0), (da)->count-1)] 317 | #define nob_da_remove_unordered(da, i) \ 318 | do { \ 319 | size_t j = (i); \ 320 | NOB_ASSERT(j < (da)->count); \ 321 | (da)->items[j] = (da)->items[--(da)->count]; \ 322 | } while(0) 323 | 324 | // Foreach over Dynamic Arrays. Example: 325 | // ```c 326 | // typedef struct { 327 | // int *items; 328 | // size_t count; 329 | // size_t capacity; 330 | // } Numbers; 331 | // 332 | // Numbers xs = {0}; 333 | // 334 | // nob_da_append(&xs, 69); 335 | // nob_da_append(&xs, 420); 336 | // nob_da_append(&xs, 1337); 337 | // 338 | // nob_da_foreach(int, x, &xs) { 339 | // // `x` here is a pointer to the current element. You can get its index by taking a difference 340 | // // between `x` and the start of the array which is `x.items`. 341 | // size_t index = x - xs.items; 342 | // nob_log(INFO, "%zu: %d", index, *x); 343 | // } 344 | // ``` 345 | #define nob_da_foreach(Type, it, da) for (Type *it = (da)->items; it < (da)->items + (da)->count; ++it) 346 | 347 | typedef struct { 348 | char *items; 349 | size_t count; 350 | size_t capacity; 351 | } Nob_String_Builder; 352 | 353 | bool nob_read_entire_file(const char *path, Nob_String_Builder *sb); 354 | int nob_sb_appendf(Nob_String_Builder *sb, const char *fmt, ...) NOB_PRINTF_FORMAT(2, 3); 355 | 356 | // Append a sized buffer to a string builder 357 | #define nob_sb_append_buf(sb, buf, size) nob_da_append_many(sb, buf, size) 358 | 359 | // Append a NULL-terminated string to a string builder 360 | #define nob_sb_append_cstr(sb, cstr) \ 361 | do { \ 362 | const char *s = (cstr); \ 363 | size_t n = strlen(s); \ 364 | nob_da_append_many(sb, s, n); \ 365 | } while (0) 366 | 367 | // Append a single NULL character at the end of a string builder. So then you can 368 | // use it a NULL-terminated C string 369 | #define nob_sb_append_null(sb) nob_da_append_many(sb, "", 1) 370 | 371 | // Free the memory allocated by a string builder 372 | #define nob_sb_free(sb) NOB_FREE((sb).items) 373 | 374 | // Process handle 375 | #ifdef _WIN32 376 | typedef HANDLE Nob_Proc; 377 | #define NOB_INVALID_PROC INVALID_HANDLE_VALUE 378 | typedef HANDLE Nob_Fd; 379 | #define NOB_INVALID_FD INVALID_HANDLE_VALUE 380 | #else 381 | typedef int Nob_Proc; 382 | #define NOB_INVALID_PROC (-1) 383 | typedef int Nob_Fd; 384 | #define NOB_INVALID_FD (-1) 385 | #endif // _WIN32 386 | 387 | Nob_Fd nob_fd_open_for_read(const char *path); 388 | Nob_Fd nob_fd_open_for_write(const char *path); 389 | void nob_fd_close(Nob_Fd fd); 390 | 391 | typedef struct { 392 | Nob_Proc *items; 393 | size_t count; 394 | size_t capacity; 395 | } Nob_Procs; 396 | 397 | // Wait until the process has finished 398 | bool nob_proc_wait(Nob_Proc proc); 399 | // Wait until all the processes have finished 400 | bool nob_procs_wait(Nob_Procs procs); 401 | // Wait until all the processes have finished and empty the procs array 402 | bool nob_procs_wait_and_reset(Nob_Procs *procs); 403 | // Append a new process to procs array and if procs.count reaches max_procs_count call nob_procs_wait_and_reset() on it 404 | bool nob_procs_append_with_flush(Nob_Procs *procs, Nob_Proc proc, size_t max_procs_count); 405 | 406 | // A command - the main workhorse of Nob. Nob is all about building commands and running them 407 | typedef struct { 408 | const char **items; 409 | size_t count; 410 | size_t capacity; 411 | } Nob_Cmd; 412 | 413 | // Example: 414 | // ```c 415 | // Nob_Fd fdin = nob_fd_open_for_read("input.txt"); 416 | // if (fdin == NOB_INVALID_FD) fail(); 417 | // Nob_Fd fdout = nob_fd_open_for_write("output.txt"); 418 | // if (fdout == NOB_INVALID_FD) fail(); 419 | // Nob_Cmd cmd = {0}; 420 | // nob_cmd_append(&cmd, "cat"); 421 | // if (!nob_cmd_run_sync_redirect_and_reset(&cmd, (Nob_Cmd_Redirect) { 422 | // .fdin = &fdin, 423 | // .fdout = &fdout 424 | // })) fail(); 425 | // ``` 426 | typedef struct { 427 | Nob_Fd *fdin; 428 | Nob_Fd *fdout; 429 | Nob_Fd *fderr; 430 | } Nob_Cmd_Redirect; 431 | 432 | // Render a string representation of a command into a string builder. Keep in mind the the 433 | // string builder is not NULL-terminated by default. Use nob_sb_append_null if you plan to 434 | // use it as a C string. 435 | void nob_cmd_render(Nob_Cmd cmd, Nob_String_Builder *render); 436 | 437 | // TODO: implement C++ support for nob.h 438 | #define nob_cmd_append(cmd, ...) \ 439 | nob_da_append_many(cmd, \ 440 | ((const char*[]){__VA_ARGS__}), \ 441 | (sizeof((const char*[]){__VA_ARGS__})/sizeof(const char*))) 442 | 443 | #define nob_cmd_extend(cmd, other_cmd) \ 444 | nob_da_append_many(cmd, (other_cmd)->items, (other_cmd)->count) 445 | 446 | // Free all the memory allocated by command arguments 447 | #define nob_cmd_free(cmd) NOB_FREE(cmd.items) 448 | 449 | // Run command asynchronously 450 | #define nob_cmd_run_async(cmd) nob_cmd_run_async_redirect(cmd, (Nob_Cmd_Redirect) {0}) 451 | // NOTE: nob_cmd_run_async_and_reset() is just like nob_cmd_run_async() except it also resets cmd.count to 0 452 | // so the Nob_Cmd instance can be seamlessly used several times in a row 453 | Nob_Proc nob_cmd_run_async_and_reset(Nob_Cmd *cmd); 454 | // Run redirected command asynchronously 455 | Nob_Proc nob_cmd_run_async_redirect(Nob_Cmd cmd, Nob_Cmd_Redirect redirect); 456 | // Run redirected command asynchronously and set cmd.count to 0 and close all the opened files 457 | Nob_Proc nob_cmd_run_async_redirect_and_reset(Nob_Cmd *cmd, Nob_Cmd_Redirect redirect); 458 | 459 | // Run command synchronously 460 | bool nob_cmd_run_sync(Nob_Cmd cmd); 461 | // NOTE: nob_cmd_run_sync_and_reset() is just like nob_cmd_run_sync() except it also resets cmd.count to 0 462 | // so the Nob_Cmd instance can be seamlessly used several times in a row 463 | bool nob_cmd_run_sync_and_reset(Nob_Cmd *cmd); 464 | // Run redirected command synchronously 465 | bool nob_cmd_run_sync_redirect(Nob_Cmd cmd, Nob_Cmd_Redirect redirect); 466 | // Run redirected command synchronously and set cmd.count to 0 and close all the opened files 467 | bool nob_cmd_run_sync_redirect_and_reset(Nob_Cmd *cmd, Nob_Cmd_Redirect redirect); 468 | 469 | #ifndef NOB_TEMP_CAPACITY 470 | #define NOB_TEMP_CAPACITY (8*1024*1024) 471 | #endif // NOB_TEMP_CAPACITY 472 | char *nob_temp_strdup(const char *cstr); 473 | void *nob_temp_alloc(size_t size); 474 | char *nob_temp_sprintf(const char *format, ...) NOB_PRINTF_FORMAT(1, 2); 475 | void nob_temp_reset(void); 476 | size_t nob_temp_save(void); 477 | void nob_temp_rewind(size_t checkpoint); 478 | 479 | // Given any path returns the last part of that path. 480 | // "/path/to/a/file.c" -> "file.c"; "/path/to/a/directory" -> "directory" 481 | const char *nob_path_name(const char *path); 482 | bool nob_rename(const char *old_path, const char *new_path); 483 | int nob_needs_rebuild(const char *output_path, const char **input_paths, size_t input_paths_count); 484 | int nob_needs_rebuild1(const char *output_path, const char *input_path); 485 | int nob_file_exists(const char *file_path); 486 | const char *nob_get_current_dir_temp(void); 487 | bool nob_set_current_dir(const char *path); 488 | 489 | // TODO: we should probably document somewhere all the compiler we support 490 | 491 | // The nob_cc_* macros try to abstract away the specific compiler. 492 | // They are verify basic and not particularly flexible, but you can redefine them if you need to 493 | // or not use them at all and create your own abstraction on top of Nob_Cmd. 494 | 495 | #ifndef nob_cc 496 | # if _WIN32 497 | # if defined(__GNUC__) 498 | # define nob_cc(cmd) nob_cmd_append(cmd, "cc") 499 | # elif defined(__clang__) 500 | # define nob_cc(cmd) nob_cmd_append(cmd, "clang") 501 | # elif defined(_MSC_VER) 502 | # define nob_cc(cmd) nob_cmd_append(cmd, "cl.exe") 503 | # endif 504 | # else 505 | # define nob_cc(cmd) nob_cmd_append(cmd, "cc") 506 | # endif 507 | #endif // nob_cc 508 | 509 | #ifndef nob_cc_flags 510 | # if defined(_MSC_VER) 511 | # define nob_cc_flags(...) // TODO: Add some cool recommended flags for MSVC (I don't really know any) 512 | # else 513 | # define nob_cc_flags(cmd) nob_cmd_append(cmd, "-Wall", "-Wextra") 514 | # endif 515 | #endif // nob_cc_output 516 | 517 | #ifndef nob_cc_output 518 | # if defined(_MSC_VER) 519 | # define nob_cc_output(cmd, output_path) nob_cmd_append(cmd, nob_temp_sprintf("/Fe:%s", (output_path))) 520 | # else 521 | # define nob_cc_output(cmd, output_path) nob_cmd_append(cmd, "-o", (output_path)) 522 | # endif 523 | #endif // nob_cc_output 524 | 525 | #ifndef nob_cc_inputs 526 | # define nob_cc_inputs(cmd, ...) nob_cmd_append(cmd, __VA_ARGS__) 527 | #endif // nob_cc_inputs 528 | 529 | // TODO: add MinGW support for Go Rebuild Urself™ Technology and all the nob_cc_* macros above 530 | // Musializer contributors came up with a pretty interesting idea of an optional prefix macro which could be useful for 531 | // MinGW support: 532 | // https://github.com/tsoding/musializer/blob/b7578cc76b9ecb573d239acc9ccf5a04d3aba2c9/src_build/nob_win64_mingw.c#L3-L9 533 | // TODO: Maybe instead NOB_REBUILD_URSELF macro, the Go Rebuild Urself™ Technology should use the 534 | // user defined nob_cc_* macros instead? 535 | #ifndef NOB_REBUILD_URSELF 536 | # if defined(_WIN32) 537 | # if defined(__GNUC__) 538 | # define NOB_REBUILD_URSELF(binary_path, source_path) "gcc", "-o", binary_path, source_path 539 | # elif defined(__clang__) 540 | # define NOB_REBUILD_URSELF(binary_path, source_path) "clang", "-o", binary_path, source_path 541 | # elif defined(_MSC_VER) 542 | # define NOB_REBUILD_URSELF(binary_path, source_path) "cl.exe", nob_temp_sprintf("/Fe:%s", (binary_path)), source_path 543 | # endif 544 | # else 545 | # define NOB_REBUILD_URSELF(binary_path, source_path) "cc", "-o", binary_path, source_path 546 | # endif 547 | #endif 548 | 549 | // Go Rebuild Urself™ Technology 550 | // 551 | // How to use it: 552 | // int main(int argc, char** argv) { 553 | // NOB_GO_REBUILD_URSELF(argc, argv); 554 | // // actual work 555 | // return 0; 556 | // } 557 | // 558 | // After your added this macro every time you run ./nob it will detect 559 | // that you modified its original source code and will try to rebuild itself 560 | // before doing any actual work. So you only need to bootstrap your build system 561 | // once. 562 | // 563 | // The modification is detected by comparing the last modified times of the executable 564 | // and its source code. The same way the make utility usually does it. 565 | // 566 | // The rebuilding is done by using the NOB_REBUILD_URSELF macro which you can redefine 567 | // if you need a special way of bootstraping your build system. (which I personally 568 | // do not recommend since the whole idea of NoBuild is to keep the process of bootstrapping 569 | // as simple as possible and doing all of the actual work inside of ./nob) 570 | // 571 | void nob__go_rebuild_urself(int argc, char **argv, const char *source_path, ...); 572 | #define NOB_GO_REBUILD_URSELF(argc, argv) nob__go_rebuild_urself(argc, argv, __FILE__, NULL) 573 | // Sometimes your nob.c includes additional files, so you want the Go Rebuild Urself™ Technology to check 574 | // if they also were modified and rebuild nob.c accordingly. For that we have NOB_GO_REBUILD_URSELF_PLUS(): 575 | // ```c 576 | // #define NOB_IMPLEMENTATION 577 | // #include "nob.h" 578 | // 579 | // #include "foo.c" 580 | // #include "bar.c" 581 | // 582 | // int main(int argc, char **argv) 583 | // { 584 | // NOB_GO_REBUILD_URSELF_PLUS(argc, argv, "foo.c", "bar.c"); 585 | // // ... 586 | // return 0; 587 | // } 588 | #define NOB_GO_REBUILD_URSELF_PLUS(argc, argv, ...) nob__go_rebuild_urself(argc, argv, __FILE__, __VA_ARGS__, NULL); 589 | 590 | typedef struct { 591 | size_t count; 592 | const char *data; 593 | } Nob_String_View; 594 | 595 | const char *nob_temp_sv_to_cstr(Nob_String_View sv); 596 | 597 | Nob_String_View nob_sv_chop_by_delim(Nob_String_View *sv, char delim); 598 | Nob_String_View nob_sv_chop_left(Nob_String_View *sv, size_t n); 599 | Nob_String_View nob_sv_trim(Nob_String_View sv); 600 | Nob_String_View nob_sv_trim_left(Nob_String_View sv); 601 | Nob_String_View nob_sv_trim_right(Nob_String_View sv); 602 | bool nob_sv_eq(Nob_String_View a, Nob_String_View b); 603 | bool nob_sv_end_with(Nob_String_View sv, const char *cstr); 604 | bool nob_sv_starts_with(Nob_String_View sv, Nob_String_View expected_prefix); 605 | Nob_String_View nob_sv_from_cstr(const char *cstr); 606 | Nob_String_View nob_sv_from_parts(const char *data, size_t count); 607 | // nob_sb_to_sv() enables you to just view Nob_String_Builder as Nob_String_View 608 | #define nob_sb_to_sv(sb) nob_sv_from_parts((sb).items, (sb).count) 609 | 610 | // printf macros for String_View 611 | #ifndef SV_Fmt 612 | #define SV_Fmt "%.*s" 613 | #endif // SV_Fmt 614 | #ifndef SV_Arg 615 | #define SV_Arg(sv) (int) (sv).count, (sv).data 616 | #endif // SV_Arg 617 | // USAGE: 618 | // String_View name = ...; 619 | // printf("Name: "SV_Fmt"\n", SV_Arg(name)); 620 | 621 | 622 | // minirent.h HEADER BEGIN //////////////////////////////////////// 623 | // Copyright 2021 Alexey Kutepov 624 | // 625 | // Permission is hereby granted, free of charge, to any person obtaining 626 | // a copy of this software and associated documentation files (the 627 | // "Software"), to deal in the Software without restriction, including 628 | // without limitation the rights to use, copy, modify, merge, publish, 629 | // distribute, sublicense, and/or sell copies of the Software, and to 630 | // permit persons to whom the Software is furnished to do so, subject to 631 | // the following conditions: 632 | // 633 | // The above copyright notice and this permission notice shall be 634 | // included in all copies or substantial portions of the Software. 635 | // 636 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 637 | // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 638 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 639 | // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 640 | // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 641 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 642 | // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 643 | // 644 | // ============================================================ 645 | // 646 | // minirent — 0.0.1 — A subset of dirent interface for Windows. 647 | // 648 | // https://github.com/tsoding/minirent 649 | // 650 | // ============================================================ 651 | // 652 | // ChangeLog (https://semver.org/ is implied) 653 | // 654 | // 0.0.2 Automatically include dirent.h on non-Windows 655 | // platforms 656 | // 0.0.1 First Official Release 657 | 658 | #ifndef _WIN32 659 | #include 660 | #else // _WIN32 661 | 662 | #define WIN32_LEAN_AND_MEAN 663 | #include "windows.h" 664 | 665 | struct dirent 666 | { 667 | char d_name[MAX_PATH+1]; 668 | }; 669 | 670 | typedef struct DIR DIR; 671 | 672 | static DIR *opendir(const char *dirpath); 673 | static struct dirent *readdir(DIR *dirp); 674 | static int closedir(DIR *dirp); 675 | 676 | #endif // _WIN32 677 | // minirent.h HEADER END //////////////////////////////////////// 678 | 679 | #ifdef _WIN32 680 | 681 | char *nob_win32_error_message(DWORD err); 682 | 683 | #endif // _WIN32 684 | 685 | #endif // NOB_H_ 686 | 687 | #ifdef NOB_IMPLEMENTATION 688 | 689 | // Any messages with the level below nob_minimal_log_level are going to be suppressed. 690 | Nob_Log_Level nob_minimal_log_level = NOB_INFO; 691 | 692 | #ifdef _WIN32 693 | 694 | // Base on https://stackoverflow.com/a/75644008 695 | // > .NET Core uses 4096 * sizeof(WCHAR) buffer on stack for FormatMessageW call. And...thats it. 696 | // > 697 | // > https://github.com/dotnet/runtime/blob/3b63eb1346f1ddbc921374a5108d025662fb5ffd/src/coreclr/utilcode/posterror.cpp#L264-L265 698 | #ifndef NOB_WIN32_ERR_MSG_SIZE 699 | #define NOB_WIN32_ERR_MSG_SIZE (4 * 1024) 700 | #endif // NOB_WIN32_ERR_MSG_SIZE 701 | 702 | char *nob_win32_error_message(DWORD err) { 703 | static char win32ErrMsg[NOB_WIN32_ERR_MSG_SIZE] = {0}; 704 | DWORD errMsgSize = FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, LANG_USER_DEFAULT, win32ErrMsg, 705 | NOB_WIN32_ERR_MSG_SIZE, NULL); 706 | 707 | if (errMsgSize == 0) { 708 | if (GetLastError() != ERROR_MR_MID_NOT_FOUND) { 709 | if (sprintf(win32ErrMsg, "Could not get error message for 0x%lX", err) > 0) { 710 | return (char *)&win32ErrMsg; 711 | } else { 712 | return NULL; 713 | } 714 | } else { 715 | if (sprintf(win32ErrMsg, "Invalid Windows Error code (0x%lX)", err) > 0) { 716 | return (char *)&win32ErrMsg; 717 | } else { 718 | return NULL; 719 | } 720 | } 721 | } 722 | 723 | while (errMsgSize > 1 && isspace(win32ErrMsg[errMsgSize - 1])) { 724 | win32ErrMsg[--errMsgSize] = '\0'; 725 | } 726 | 727 | return win32ErrMsg; 728 | } 729 | 730 | #endif // _WIN32 731 | 732 | // The implementation idea is stolen from https://github.com/zhiayang/nabs 733 | void nob__go_rebuild_urself(int argc, char **argv, const char *source_path, ...) 734 | { 735 | const char *binary_path = nob_shift(argv, argc); 736 | #ifdef _WIN32 737 | // On Windows executables almost always invoked without extension, so 738 | // it's ./nob, not ./nob.exe. For renaming the extension is a must. 739 | if (!nob_sv_end_with(nob_sv_from_cstr(binary_path), ".exe")) { 740 | binary_path = nob_temp_sprintf("%s.exe", binary_path); 741 | } 742 | #endif 743 | 744 | Nob_File_Paths source_paths = {0}; 745 | nob_da_append(&source_paths, source_path); 746 | va_list args; 747 | va_start(args, source_path); 748 | for (;;) { 749 | const char *path = va_arg(args, const char*); 750 | if (path == NULL) break; 751 | nob_da_append(&source_paths, path); 752 | } 753 | va_end(args); 754 | 755 | int rebuild_is_needed = nob_needs_rebuild(binary_path, source_paths.items, source_paths.count); 756 | if (rebuild_is_needed < 0) exit(1); // error 757 | if (!rebuild_is_needed) { // no rebuild is needed 758 | NOB_FREE(source_paths.items); 759 | return; 760 | } 761 | 762 | Nob_Cmd cmd = {0}; 763 | 764 | const char *old_binary_path = nob_temp_sprintf("%s.old", binary_path); 765 | 766 | if (!nob_rename(binary_path, old_binary_path)) exit(1); 767 | nob_cmd_append(&cmd, NOB_REBUILD_URSELF(binary_path, source_path)); 768 | if (!nob_cmd_run_sync_and_reset(&cmd)) { 769 | nob_rename(old_binary_path, binary_path); 770 | exit(1); 771 | } 772 | #ifdef NOB_EXPERIMENTAL_DELETE_OLD 773 | // TODO: this is an experimental behavior behind a compilation flag. 774 | // Once it is confirmed that it does not cause much problems on both POSIX and Windows 775 | // we may turn it on by default. 776 | nob_delete_file(old_binary_path); 777 | #endif // NOB_EXPERIMENTAL_DELETE_OLD 778 | 779 | nob_cmd_append(&cmd, binary_path); 780 | nob_da_append_many(&cmd, argv, argc); 781 | if (!nob_cmd_run_sync_and_reset(&cmd)) exit(1); 782 | exit(0); 783 | } 784 | 785 | static size_t nob_temp_size = 0; 786 | static char nob_temp[NOB_TEMP_CAPACITY] = {0}; 787 | 788 | bool nob_mkdir_if_not_exists(const char *path) 789 | { 790 | #ifdef _WIN32 791 | int result = mkdir(path); 792 | #else 793 | int result = mkdir(path, 0755); 794 | #endif 795 | if (result < 0) { 796 | if (errno == EEXIST) { 797 | nob_log(NOB_INFO, "directory `%s` already exists", path); 798 | return true; 799 | } 800 | nob_log(NOB_ERROR, "could not create directory `%s`: %s", path, strerror(errno)); 801 | return false; 802 | } 803 | 804 | nob_log(NOB_INFO, "created directory `%s`", path); 805 | return true; 806 | } 807 | 808 | bool nob_copy_file(const char *src_path, const char *dst_path) 809 | { 810 | nob_log(NOB_INFO, "copying %s -> %s", src_path, dst_path); 811 | #ifdef _WIN32 812 | if (!CopyFile(src_path, dst_path, FALSE)) { 813 | nob_log(NOB_ERROR, "Could not copy file: %s", nob_win32_error_message(GetLastError())); 814 | return false; 815 | } 816 | return true; 817 | #else 818 | int src_fd = -1; 819 | int dst_fd = -1; 820 | size_t buf_size = 32*1024; 821 | char *buf = NOB_REALLOC(NULL, buf_size); 822 | NOB_ASSERT(buf != NULL && "Buy more RAM lol!!"); 823 | bool result = true; 824 | 825 | src_fd = open(src_path, O_RDONLY); 826 | if (src_fd < 0) { 827 | nob_log(NOB_ERROR, "Could not open file %s: %s", src_path, strerror(errno)); 828 | nob_return_defer(false); 829 | } 830 | 831 | struct stat src_stat; 832 | if (fstat(src_fd, &src_stat) < 0) { 833 | nob_log(NOB_ERROR, "Could not get mode of file %s: %s", src_path, strerror(errno)); 834 | nob_return_defer(false); 835 | } 836 | 837 | dst_fd = open(dst_path, O_CREAT | O_TRUNC | O_WRONLY, src_stat.st_mode); 838 | if (dst_fd < 0) { 839 | nob_log(NOB_ERROR, "Could not create file %s: %s", dst_path, strerror(errno)); 840 | nob_return_defer(false); 841 | } 842 | 843 | for (;;) { 844 | ssize_t n = read(src_fd, buf, buf_size); 845 | if (n == 0) break; 846 | if (n < 0) { 847 | nob_log(NOB_ERROR, "Could not read from file %s: %s", src_path, strerror(errno)); 848 | nob_return_defer(false); 849 | } 850 | char *buf2 = buf; 851 | while (n > 0) { 852 | ssize_t m = write(dst_fd, buf2, n); 853 | if (m < 0) { 854 | nob_log(NOB_ERROR, "Could not write to file %s: %s", dst_path, strerror(errno)); 855 | nob_return_defer(false); 856 | } 857 | n -= m; 858 | buf2 += m; 859 | } 860 | } 861 | 862 | defer: 863 | NOB_FREE(buf); 864 | close(src_fd); 865 | close(dst_fd); 866 | return result; 867 | #endif 868 | } 869 | 870 | void nob_cmd_render(Nob_Cmd cmd, Nob_String_Builder *render) 871 | { 872 | for (size_t i = 0; i < cmd.count; ++i) { 873 | const char *arg = cmd.items[i]; 874 | if (arg == NULL) break; 875 | if (i > 0) nob_sb_append_cstr(render, " "); 876 | if (!strchr(arg, ' ')) { 877 | nob_sb_append_cstr(render, arg); 878 | } else { 879 | nob_da_append(render, '\''); 880 | nob_sb_append_cstr(render, arg); 881 | nob_da_append(render, '\''); 882 | } 883 | } 884 | } 885 | 886 | Nob_Proc nob_cmd_run_async_redirect(Nob_Cmd cmd, Nob_Cmd_Redirect redirect) 887 | { 888 | if (cmd.count < 1) { 889 | nob_log(NOB_ERROR, "Could not run empty command"); 890 | return NOB_INVALID_PROC; 891 | } 892 | 893 | Nob_String_Builder sb = {0}; 894 | nob_cmd_render(cmd, &sb); 895 | nob_sb_append_null(&sb); 896 | nob_log(NOB_INFO, "CMD: %s", sb.items); 897 | nob_sb_free(sb); 898 | memset(&sb, 0, sizeof(sb)); 899 | 900 | #ifdef _WIN32 901 | // https://docs.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output 902 | 903 | STARTUPINFO siStartInfo; 904 | ZeroMemory(&siStartInfo, sizeof(siStartInfo)); 905 | siStartInfo.cb = sizeof(STARTUPINFO); 906 | // NOTE: theoretically setting NULL to std handles should not be a problem 907 | // https://docs.microsoft.com/en-us/windows/console/getstdhandle?redirectedfrom=MSDN#attachdetach-behavior 908 | // TODO: check for errors in GetStdHandle 909 | siStartInfo.hStdError = redirect.fderr ? *redirect.fderr : GetStdHandle(STD_ERROR_HANDLE); 910 | siStartInfo.hStdOutput = redirect.fdout ? *redirect.fdout : GetStdHandle(STD_OUTPUT_HANDLE); 911 | siStartInfo.hStdInput = redirect.fdin ? *redirect.fdin : GetStdHandle(STD_INPUT_HANDLE); 912 | siStartInfo.dwFlags |= STARTF_USESTDHANDLES; 913 | 914 | PROCESS_INFORMATION piProcInfo; 915 | ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION)); 916 | 917 | // TODO: use a more reliable rendering of the command instead of cmd_render 918 | // cmd_render is for logging primarily 919 | nob_cmd_render(cmd, &sb); 920 | nob_sb_append_null(&sb); 921 | BOOL bSuccess = CreateProcessA(NULL, sb.items, NULL, NULL, TRUE, 0, NULL, NULL, &siStartInfo, &piProcInfo); 922 | nob_sb_free(sb); 923 | 924 | if (!bSuccess) { 925 | nob_log(NOB_ERROR, "Could not create child process for %s: %s", cmd.items[0], nob_win32_error_message(GetLastError())); 926 | return NOB_INVALID_PROC; 927 | } 928 | 929 | CloseHandle(piProcInfo.hThread); 930 | 931 | return piProcInfo.hProcess; 932 | #else 933 | pid_t cpid = fork(); 934 | if (cpid < 0) { 935 | nob_log(NOB_ERROR, "Could not fork child process: %s", strerror(errno)); 936 | return NOB_INVALID_PROC; 937 | } 938 | 939 | if (cpid == 0) { 940 | if (redirect.fdin) { 941 | if (dup2(*redirect.fdin, STDIN_FILENO) < 0) { 942 | nob_log(NOB_ERROR, "Could not setup stdin for child process: %s", strerror(errno)); 943 | exit(1); 944 | } 945 | } 946 | 947 | if (redirect.fdout) { 948 | if (dup2(*redirect.fdout, STDOUT_FILENO) < 0) { 949 | nob_log(NOB_ERROR, "Could not setup stdout for child process: %s", strerror(errno)); 950 | exit(1); 951 | } 952 | } 953 | 954 | if (redirect.fderr) { 955 | if (dup2(*redirect.fderr, STDERR_FILENO) < 0) { 956 | nob_log(NOB_ERROR, "Could not setup stderr for child process: %s", strerror(errno)); 957 | exit(1); 958 | } 959 | } 960 | 961 | // NOTE: This leaks a bit of memory in the child process. 962 | // But do we actually care? It's a one off leak anyway... 963 | Nob_Cmd cmd_null = {0}; 964 | nob_da_append_many(&cmd_null, cmd.items, cmd.count); 965 | nob_cmd_append(&cmd_null, NULL); 966 | 967 | if (execvp(cmd.items[0], (char * const*) cmd_null.items) < 0) { 968 | nob_log(NOB_ERROR, "Could not exec child process for %s: %s", cmd.items[0], strerror(errno)); 969 | exit(1); 970 | } 971 | NOB_UNREACHABLE("nob_cmd_run_async_redirect"); 972 | } 973 | 974 | return cpid; 975 | #endif 976 | } 977 | 978 | Nob_Proc nob_cmd_run_async_and_reset(Nob_Cmd *cmd) 979 | { 980 | Nob_Proc proc = nob_cmd_run_async(*cmd); 981 | cmd->count = 0; 982 | return proc; 983 | } 984 | 985 | Nob_Proc nob_cmd_run_async_redirect_and_reset(Nob_Cmd *cmd, Nob_Cmd_Redirect redirect) 986 | { 987 | Nob_Proc proc = nob_cmd_run_async_redirect(*cmd, redirect); 988 | cmd->count = 0; 989 | if (redirect.fdin) { 990 | nob_fd_close(*redirect.fdin); 991 | *redirect.fdin = NOB_INVALID_FD; 992 | } 993 | if (redirect.fdout) { 994 | nob_fd_close(*redirect.fdout); 995 | *redirect.fdout = NOB_INVALID_FD; 996 | } 997 | if (redirect.fderr) { 998 | nob_fd_close(*redirect.fderr); 999 | *redirect.fderr = NOB_INVALID_FD; 1000 | } 1001 | return proc; 1002 | } 1003 | 1004 | Nob_Fd nob_fd_open_for_read(const char *path) 1005 | { 1006 | #ifndef _WIN32 1007 | Nob_Fd result = open(path, O_RDONLY); 1008 | if (result < 0) { 1009 | nob_log(NOB_ERROR, "Could not open file %s: %s", path, strerror(errno)); 1010 | return NOB_INVALID_FD; 1011 | } 1012 | return result; 1013 | #else 1014 | // https://docs.microsoft.com/en-us/windows/win32/fileio/opening-a-file-for-reading-or-writing 1015 | SECURITY_ATTRIBUTES saAttr = {0}; 1016 | saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 1017 | saAttr.bInheritHandle = TRUE; 1018 | 1019 | Nob_Fd result = CreateFile( 1020 | path, 1021 | GENERIC_READ, 1022 | 0, 1023 | &saAttr, 1024 | OPEN_EXISTING, 1025 | FILE_ATTRIBUTE_READONLY, 1026 | NULL); 1027 | 1028 | if (result == INVALID_HANDLE_VALUE) { 1029 | nob_log(NOB_ERROR, "Could not open file %s: %s", path, nob_win32_error_message(GetLastError())); 1030 | return NOB_INVALID_FD; 1031 | } 1032 | 1033 | return result; 1034 | #endif // _WIN32 1035 | } 1036 | 1037 | Nob_Fd nob_fd_open_for_write(const char *path) 1038 | { 1039 | #ifndef _WIN32 1040 | Nob_Fd result = open(path, 1041 | O_WRONLY | O_CREAT | O_TRUNC, 1042 | S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); 1043 | if (result < 0) { 1044 | nob_log(NOB_ERROR, "could not open file %s: %s", path, strerror(errno)); 1045 | return NOB_INVALID_FD; 1046 | } 1047 | return result; 1048 | #else 1049 | SECURITY_ATTRIBUTES saAttr = {0}; 1050 | saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 1051 | saAttr.bInheritHandle = TRUE; 1052 | 1053 | Nob_Fd result = CreateFile( 1054 | path, // name of the write 1055 | GENERIC_WRITE, // open for writing 1056 | 0, // do not share 1057 | &saAttr, // default security 1058 | CREATE_ALWAYS, // create always 1059 | FILE_ATTRIBUTE_NORMAL, // normal file 1060 | NULL // no attr. template 1061 | ); 1062 | 1063 | if (result == INVALID_HANDLE_VALUE) { 1064 | nob_log(NOB_ERROR, "Could not open file %s: %s", path, nob_win32_error_message(GetLastError())); 1065 | return NOB_INVALID_FD; 1066 | } 1067 | 1068 | return result; 1069 | #endif // _WIN32 1070 | } 1071 | 1072 | void nob_fd_close(Nob_Fd fd) 1073 | { 1074 | #ifdef _WIN32 1075 | CloseHandle(fd); 1076 | #else 1077 | close(fd); 1078 | #endif // _WIN32 1079 | } 1080 | 1081 | bool nob_procs_wait(Nob_Procs procs) 1082 | { 1083 | bool success = true; 1084 | for (size_t i = 0; i < procs.count; ++i) { 1085 | success = nob_proc_wait(procs.items[i]) && success; 1086 | } 1087 | return success; 1088 | } 1089 | 1090 | bool nob_procs_wait_and_reset(Nob_Procs *procs) 1091 | { 1092 | bool success = nob_procs_wait(*procs); 1093 | procs->count = 0; 1094 | return success; 1095 | } 1096 | 1097 | bool nob_proc_wait(Nob_Proc proc) 1098 | { 1099 | if (proc == NOB_INVALID_PROC) return false; 1100 | 1101 | #ifdef _WIN32 1102 | DWORD result = WaitForSingleObject( 1103 | proc, // HANDLE hHandle, 1104 | INFINITE // DWORD dwMilliseconds 1105 | ); 1106 | 1107 | if (result == WAIT_FAILED) { 1108 | nob_log(NOB_ERROR, "could not wait on child process: %s", nob_win32_error_message(GetLastError())); 1109 | return false; 1110 | } 1111 | 1112 | DWORD exit_status; 1113 | if (!GetExitCodeProcess(proc, &exit_status)) { 1114 | nob_log(NOB_ERROR, "could not get process exit code: %s", nob_win32_error_message(GetLastError())); 1115 | return false; 1116 | } 1117 | 1118 | if (exit_status != 0) { 1119 | nob_log(NOB_ERROR, "command exited with exit code %lu", exit_status); 1120 | return false; 1121 | } 1122 | 1123 | CloseHandle(proc); 1124 | 1125 | return true; 1126 | #else 1127 | for (;;) { 1128 | int wstatus = 0; 1129 | if (waitpid(proc, &wstatus, 0) < 0) { 1130 | nob_log(NOB_ERROR, "could not wait on command (pid %d): %s", proc, strerror(errno)); 1131 | return false; 1132 | } 1133 | 1134 | if (WIFEXITED(wstatus)) { 1135 | int exit_status = WEXITSTATUS(wstatus); 1136 | if (exit_status != 0) { 1137 | nob_log(NOB_ERROR, "command exited with exit code %d", exit_status); 1138 | return false; 1139 | } 1140 | 1141 | break; 1142 | } 1143 | 1144 | if (WIFSIGNALED(wstatus)) { 1145 | nob_log(NOB_ERROR, "command process was terminated by signal %d", WTERMSIG(wstatus)); 1146 | return false; 1147 | } 1148 | } 1149 | 1150 | return true; 1151 | #endif 1152 | } 1153 | 1154 | bool nob_procs_append_with_flush(Nob_Procs *procs, Nob_Proc proc, size_t max_procs_count) 1155 | { 1156 | nob_da_append(procs, proc); 1157 | 1158 | if (procs->count >= max_procs_count) { 1159 | if (!nob_procs_wait_and_reset(procs)) return false; 1160 | } 1161 | 1162 | return true; 1163 | } 1164 | 1165 | bool nob_cmd_run_sync_redirect(Nob_Cmd cmd, Nob_Cmd_Redirect redirect) 1166 | { 1167 | Nob_Proc p = nob_cmd_run_async_redirect(cmd, redirect); 1168 | if (p == NOB_INVALID_PROC) return false; 1169 | return nob_proc_wait(p); 1170 | } 1171 | 1172 | bool nob_cmd_run_sync(Nob_Cmd cmd) 1173 | { 1174 | Nob_Proc p = nob_cmd_run_async(cmd); 1175 | if (p == NOB_INVALID_PROC) return false; 1176 | return nob_proc_wait(p); 1177 | } 1178 | 1179 | bool nob_cmd_run_sync_and_reset(Nob_Cmd *cmd) 1180 | { 1181 | bool p = nob_cmd_run_sync(*cmd); 1182 | cmd->count = 0; 1183 | return p; 1184 | } 1185 | 1186 | bool nob_cmd_run_sync_redirect_and_reset(Nob_Cmd *cmd, Nob_Cmd_Redirect redirect) 1187 | { 1188 | bool p = nob_cmd_run_sync_redirect(*cmd, redirect); 1189 | cmd->count = 0; 1190 | if (redirect.fdin) { 1191 | nob_fd_close(*redirect.fdin); 1192 | *redirect.fdin = NOB_INVALID_FD; 1193 | } 1194 | if (redirect.fdout) { 1195 | nob_fd_close(*redirect.fdout); 1196 | *redirect.fdout = NOB_INVALID_FD; 1197 | } 1198 | if (redirect.fderr) { 1199 | nob_fd_close(*redirect.fderr); 1200 | *redirect.fderr = NOB_INVALID_FD; 1201 | } 1202 | return p; 1203 | } 1204 | 1205 | void nob_log(Nob_Log_Level level, const char *fmt, ...) 1206 | { 1207 | if (level < nob_minimal_log_level) return; 1208 | 1209 | switch (level) { 1210 | case NOB_INFO: 1211 | fprintf(stderr, "[INFO] "); 1212 | break; 1213 | case NOB_WARNING: 1214 | fprintf(stderr, "[WARNING] "); 1215 | break; 1216 | case NOB_ERROR: 1217 | fprintf(stderr, "[ERROR] "); 1218 | break; 1219 | case NOB_NO_LOGS: return; 1220 | default: 1221 | NOB_UNREACHABLE("nob_log"); 1222 | } 1223 | 1224 | va_list args; 1225 | va_start(args, fmt); 1226 | vfprintf(stderr, fmt, args); 1227 | va_end(args); 1228 | fprintf(stderr, "\n"); 1229 | } 1230 | 1231 | bool nob_read_entire_dir(const char *parent, Nob_File_Paths *children) 1232 | { 1233 | bool result = true; 1234 | DIR *dir = NULL; 1235 | 1236 | dir = opendir(parent); 1237 | if (dir == NULL) { 1238 | #ifdef _WIN32 1239 | nob_log(NOB_ERROR, "Could not open directory %s: %s", parent, nob_win32_error_message(GetLastError())); 1240 | #else 1241 | nob_log(NOB_ERROR, "Could not open directory %s: %s", parent, strerror(errno)); 1242 | #endif // _WIN32 1243 | nob_return_defer(false); 1244 | } 1245 | 1246 | errno = 0; 1247 | struct dirent *ent = readdir(dir); 1248 | while (ent != NULL) { 1249 | nob_da_append(children, nob_temp_strdup(ent->d_name)); 1250 | ent = readdir(dir); 1251 | } 1252 | 1253 | if (errno != 0) { 1254 | #ifdef _WIN32 1255 | nob_log(NOB_ERROR, "Could not read directory %s: %s", parent, nob_win32_error_message(GetLastError())); 1256 | #else 1257 | nob_log(NOB_ERROR, "Could not read directory %s: %s", parent, strerror(errno)); 1258 | #endif // _WIN32 1259 | nob_return_defer(false); 1260 | } 1261 | 1262 | defer: 1263 | if (dir) closedir(dir); 1264 | return result; 1265 | } 1266 | 1267 | bool nob_write_entire_file(const char *path, const void *data, size_t size) 1268 | { 1269 | bool result = true; 1270 | 1271 | FILE *f = fopen(path, "wb"); 1272 | if (f == NULL) { 1273 | nob_log(NOB_ERROR, "Could not open file %s for writing: %s\n", path, strerror(errno)); 1274 | nob_return_defer(false); 1275 | } 1276 | 1277 | // len 1278 | // v 1279 | // aaaaaaaaaa 1280 | // ^ 1281 | // data 1282 | 1283 | const char *buf = data; 1284 | while (size > 0) { 1285 | size_t n = fwrite(buf, 1, size, f); 1286 | if (ferror(f)) { 1287 | nob_log(NOB_ERROR, "Could not write into file %s: %s\n", path, strerror(errno)); 1288 | nob_return_defer(false); 1289 | } 1290 | size -= n; 1291 | buf += n; 1292 | } 1293 | 1294 | defer: 1295 | if (f) fclose(f); 1296 | return result; 1297 | } 1298 | 1299 | Nob_File_Type nob_get_file_type(const char *path) 1300 | { 1301 | #ifdef _WIN32 1302 | DWORD attr = GetFileAttributesA(path); 1303 | if (attr == INVALID_FILE_ATTRIBUTES) { 1304 | nob_log(NOB_ERROR, "Could not get file attributes of %s: %s", path, nob_win32_error_message(GetLastError())); 1305 | return -1; 1306 | } 1307 | 1308 | if (attr & FILE_ATTRIBUTE_DIRECTORY) return NOB_FILE_DIRECTORY; 1309 | // TODO: detect symlinks on Windows (whatever that means on Windows anyway) 1310 | return NOB_FILE_REGULAR; 1311 | #else // _WIN32 1312 | struct stat statbuf; 1313 | if (stat(path, &statbuf) < 0) { 1314 | nob_log(NOB_ERROR, "Could not get stat of %s: %s", path, strerror(errno)); 1315 | return -1; 1316 | } 1317 | 1318 | if (S_ISREG(statbuf.st_mode)) return NOB_FILE_REGULAR; 1319 | if (S_ISDIR(statbuf.st_mode)) return NOB_FILE_DIRECTORY; 1320 | if (S_ISLNK(statbuf.st_mode)) return NOB_FILE_SYMLINK; 1321 | return NOB_FILE_OTHER; 1322 | #endif // _WIN32 1323 | } 1324 | 1325 | bool nob_delete_file(const char *path) 1326 | { 1327 | nob_log(NOB_INFO, "deleting %s", path); 1328 | #ifdef _WIN32 1329 | if (!DeleteFileA(path)) { 1330 | nob_log(NOB_ERROR, "Could not delete file %s: %s", path, nob_win32_error_message(GetLastError())); 1331 | return false; 1332 | } 1333 | return true; 1334 | #else 1335 | if (remove(path) < 0) { 1336 | nob_log(NOB_ERROR, "Could not delete file %s: %s", path, strerror(errno)); 1337 | return false; 1338 | } 1339 | return true; 1340 | #endif // _WIN32 1341 | } 1342 | 1343 | bool nob_copy_directory_recursively(const char *src_path, const char *dst_path) 1344 | { 1345 | bool result = true; 1346 | Nob_File_Paths children = {0}; 1347 | Nob_String_Builder src_sb = {0}; 1348 | Nob_String_Builder dst_sb = {0}; 1349 | size_t temp_checkpoint = nob_temp_save(); 1350 | 1351 | Nob_File_Type type = nob_get_file_type(src_path); 1352 | if (type < 0) return false; 1353 | 1354 | switch (type) { 1355 | case NOB_FILE_DIRECTORY: { 1356 | if (!nob_mkdir_if_not_exists(dst_path)) nob_return_defer(false); 1357 | if (!nob_read_entire_dir(src_path, &children)) nob_return_defer(false); 1358 | 1359 | for (size_t i = 0; i < children.count; ++i) { 1360 | if (strcmp(children.items[i], ".") == 0) continue; 1361 | if (strcmp(children.items[i], "..") == 0) continue; 1362 | 1363 | src_sb.count = 0; 1364 | nob_sb_append_cstr(&src_sb, src_path); 1365 | nob_sb_append_cstr(&src_sb, "/"); 1366 | nob_sb_append_cstr(&src_sb, children.items[i]); 1367 | nob_sb_append_null(&src_sb); 1368 | 1369 | dst_sb.count = 0; 1370 | nob_sb_append_cstr(&dst_sb, dst_path); 1371 | nob_sb_append_cstr(&dst_sb, "/"); 1372 | nob_sb_append_cstr(&dst_sb, children.items[i]); 1373 | nob_sb_append_null(&dst_sb); 1374 | 1375 | if (!nob_copy_directory_recursively(src_sb.items, dst_sb.items)) { 1376 | nob_return_defer(false); 1377 | } 1378 | } 1379 | } break; 1380 | 1381 | case NOB_FILE_REGULAR: { 1382 | if (!nob_copy_file(src_path, dst_path)) { 1383 | nob_return_defer(false); 1384 | } 1385 | } break; 1386 | 1387 | case NOB_FILE_SYMLINK: { 1388 | nob_log(NOB_WARNING, "TODO: Copying symlinks is not supported yet"); 1389 | } break; 1390 | 1391 | case NOB_FILE_OTHER: { 1392 | nob_log(NOB_ERROR, "Unsupported type of file %s", src_path); 1393 | nob_return_defer(false); 1394 | } break; 1395 | 1396 | default: NOB_UNREACHABLE("nob_copy_directory_recursively"); 1397 | } 1398 | 1399 | defer: 1400 | nob_temp_rewind(temp_checkpoint); 1401 | nob_da_free(src_sb); 1402 | nob_da_free(dst_sb); 1403 | nob_da_free(children); 1404 | return result; 1405 | } 1406 | 1407 | char *nob_temp_strdup(const char *cstr) 1408 | { 1409 | size_t n = strlen(cstr); 1410 | char *result = nob_temp_alloc(n + 1); 1411 | NOB_ASSERT(result != NULL && "Increase NOB_TEMP_CAPACITY"); 1412 | memcpy(result, cstr, n); 1413 | result[n] = '\0'; 1414 | return result; 1415 | } 1416 | 1417 | void *nob_temp_alloc(size_t size) 1418 | { 1419 | if (nob_temp_size + size > NOB_TEMP_CAPACITY) return NULL; 1420 | void *result = &nob_temp[nob_temp_size]; 1421 | nob_temp_size += size; 1422 | return result; 1423 | } 1424 | 1425 | char *nob_temp_sprintf(const char *format, ...) 1426 | { 1427 | va_list args; 1428 | va_start(args, format); 1429 | int n = vsnprintf(NULL, 0, format, args); 1430 | va_end(args); 1431 | 1432 | NOB_ASSERT(n >= 0); 1433 | char *result = nob_temp_alloc(n + 1); 1434 | NOB_ASSERT(result != NULL && "Extend the size of the temporary allocator"); 1435 | // TODO: use proper arenas for the temporary allocator; 1436 | va_start(args, format); 1437 | vsnprintf(result, n + 1, format, args); 1438 | va_end(args); 1439 | 1440 | return result; 1441 | } 1442 | 1443 | void nob_temp_reset(void) 1444 | { 1445 | nob_temp_size = 0; 1446 | } 1447 | 1448 | size_t nob_temp_save(void) 1449 | { 1450 | return nob_temp_size; 1451 | } 1452 | 1453 | void nob_temp_rewind(size_t checkpoint) 1454 | { 1455 | nob_temp_size = checkpoint; 1456 | } 1457 | 1458 | const char *nob_temp_sv_to_cstr(Nob_String_View sv) 1459 | { 1460 | char *result = nob_temp_alloc(sv.count + 1); 1461 | NOB_ASSERT(result != NULL && "Extend the size of the temporary allocator"); 1462 | memcpy(result, sv.data, sv.count); 1463 | result[sv.count] = '\0'; 1464 | return result; 1465 | } 1466 | 1467 | int nob_needs_rebuild(const char *output_path, const char **input_paths, size_t input_paths_count) 1468 | { 1469 | #ifdef _WIN32 1470 | BOOL bSuccess; 1471 | 1472 | HANDLE output_path_fd = CreateFile(output_path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL); 1473 | if (output_path_fd == INVALID_HANDLE_VALUE) { 1474 | // NOTE: if output does not exist it 100% must be rebuilt 1475 | if (GetLastError() == ERROR_FILE_NOT_FOUND) return 1; 1476 | nob_log(NOB_ERROR, "Could not open file %s: %s", output_path, nob_win32_error_message(GetLastError())); 1477 | return -1; 1478 | } 1479 | FILETIME output_path_time; 1480 | bSuccess = GetFileTime(output_path_fd, NULL, NULL, &output_path_time); 1481 | CloseHandle(output_path_fd); 1482 | if (!bSuccess) { 1483 | nob_log(NOB_ERROR, "Could not get time of %s: %s", output_path, nob_win32_error_message(GetLastError())); 1484 | return -1; 1485 | } 1486 | 1487 | for (size_t i = 0; i < input_paths_count; ++i) { 1488 | const char *input_path = input_paths[i]; 1489 | HANDLE input_path_fd = CreateFile(input_path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL); 1490 | if (input_path_fd == INVALID_HANDLE_VALUE) { 1491 | // NOTE: non-existing input is an error cause it is needed for building in the first place 1492 | nob_log(NOB_ERROR, "Could not open file %s: %s", input_path, nob_win32_error_message(GetLastError())); 1493 | return -1; 1494 | } 1495 | FILETIME input_path_time; 1496 | bSuccess = GetFileTime(input_path_fd, NULL, NULL, &input_path_time); 1497 | CloseHandle(input_path_fd); 1498 | if (!bSuccess) { 1499 | nob_log(NOB_ERROR, "Could not get time of %s: %s", input_path, nob_win32_error_message(GetLastError())); 1500 | return -1; 1501 | } 1502 | 1503 | // NOTE: if even a single input_path is fresher than output_path that's 100% rebuild 1504 | if (CompareFileTime(&input_path_time, &output_path_time) == 1) return 1; 1505 | } 1506 | 1507 | return 0; 1508 | #else 1509 | struct stat statbuf = {0}; 1510 | 1511 | if (stat(output_path, &statbuf) < 0) { 1512 | // NOTE: if output does not exist it 100% must be rebuilt 1513 | if (errno == ENOENT) return 1; 1514 | nob_log(NOB_ERROR, "could not stat %s: %s", output_path, strerror(errno)); 1515 | return -1; 1516 | } 1517 | int output_path_time = statbuf.st_mtime; 1518 | 1519 | for (size_t i = 0; i < input_paths_count; ++i) { 1520 | const char *input_path = input_paths[i]; 1521 | if (stat(input_path, &statbuf) < 0) { 1522 | // NOTE: non-existing input is an error cause it is needed for building in the first place 1523 | nob_log(NOB_ERROR, "could not stat %s: %s", input_path, strerror(errno)); 1524 | return -1; 1525 | } 1526 | int input_path_time = statbuf.st_mtime; 1527 | // NOTE: if even a single input_path is fresher than output_path that's 100% rebuild 1528 | if (input_path_time > output_path_time) return 1; 1529 | } 1530 | 1531 | return 0; 1532 | #endif 1533 | } 1534 | 1535 | int nob_needs_rebuild1(const char *output_path, const char *input_path) 1536 | { 1537 | return nob_needs_rebuild(output_path, &input_path, 1); 1538 | } 1539 | 1540 | const char *nob_path_name(const char *path) 1541 | { 1542 | #ifdef _WIN32 1543 | const char *p1 = strrchr(path, '/'); 1544 | const char *p2 = strrchr(path, '\\'); 1545 | const char *p = (p1 > p2)? p1 : p2; // NULL is ignored if the other search is successful 1546 | return p ? p + 1 : path; 1547 | #else 1548 | const char *p = strrchr(path, '/'); 1549 | return p ? p + 1 : path; 1550 | #endif // _WIN32 1551 | } 1552 | 1553 | bool nob_rename(const char *old_path, const char *new_path) 1554 | { 1555 | nob_log(NOB_INFO, "renaming %s -> %s", old_path, new_path); 1556 | #ifdef _WIN32 1557 | if (!MoveFileEx(old_path, new_path, MOVEFILE_REPLACE_EXISTING)) { 1558 | nob_log(NOB_ERROR, "could not rename %s to %s: %s", old_path, new_path, nob_win32_error_message(GetLastError())); 1559 | return false; 1560 | } 1561 | #else 1562 | if (rename(old_path, new_path) < 0) { 1563 | nob_log(NOB_ERROR, "could not rename %s to %s: %s", old_path, new_path, strerror(errno)); 1564 | return false; 1565 | } 1566 | #endif // _WIN32 1567 | return true; 1568 | } 1569 | 1570 | bool nob_read_entire_file(const char *path, Nob_String_Builder *sb) 1571 | { 1572 | bool result = true; 1573 | 1574 | FILE *f = fopen(path, "rb"); 1575 | if (f == NULL) nob_return_defer(false); 1576 | if (fseek(f, 0, SEEK_END) < 0) nob_return_defer(false); 1577 | #ifndef _WIN32 1578 | long m = ftell(f); 1579 | #else 1580 | long long m = _ftelli64(f); 1581 | #endif 1582 | if (m < 0) nob_return_defer(false); 1583 | if (fseek(f, 0, SEEK_SET) < 0) nob_return_defer(false); 1584 | 1585 | size_t new_count = sb->count + m; 1586 | if (new_count > sb->capacity) { 1587 | sb->items = NOB_REALLOC(sb->items, new_count); 1588 | NOB_ASSERT(sb->items != NULL && "Buy more RAM lool!!"); 1589 | sb->capacity = new_count; 1590 | } 1591 | 1592 | fread(sb->items + sb->count, m, 1, f); 1593 | if (ferror(f)) { 1594 | // TODO: Afaik, ferror does not set errno. So the error reporting in defer is not correct in this case. 1595 | nob_return_defer(false); 1596 | } 1597 | sb->count = new_count; 1598 | 1599 | defer: 1600 | if (!result) nob_log(NOB_ERROR, "Could not read file %s: %s", path, strerror(errno)); 1601 | if (f) fclose(f); 1602 | return result; 1603 | } 1604 | 1605 | int nob_sb_appendf(Nob_String_Builder *sb, const char *fmt, ...) 1606 | { 1607 | va_list args; 1608 | 1609 | va_start(args, fmt); 1610 | int n = vsnprintf(NULL, 0, fmt, args); 1611 | va_end(args); 1612 | 1613 | // NOTE: the new_capacity needs to be +1 because of the null terminator. 1614 | // However, further below we increase sb->count by n, not n + 1. 1615 | // This is because we don't want the sb to include the null terminator. The user can always sb_append_null() if they want it 1616 | nob_da_reserve(sb, sb->count + n + 1); 1617 | char *dest = sb->items + sb->count; 1618 | va_start(args, fmt); 1619 | vsnprintf(dest, n+1, fmt, args); 1620 | va_end(args); 1621 | 1622 | sb->count += n; 1623 | 1624 | return n; 1625 | } 1626 | 1627 | Nob_String_View nob_sv_chop_by_delim(Nob_String_View *sv, char delim) 1628 | { 1629 | size_t i = 0; 1630 | while (i < sv->count && sv->data[i] != delim) { 1631 | i += 1; 1632 | } 1633 | 1634 | Nob_String_View result = nob_sv_from_parts(sv->data, i); 1635 | 1636 | if (i < sv->count) { 1637 | sv->count -= i + 1; 1638 | sv->data += i + 1; 1639 | } else { 1640 | sv->count -= i; 1641 | sv->data += i; 1642 | } 1643 | 1644 | return result; 1645 | } 1646 | 1647 | Nob_String_View nob_sv_chop_left(Nob_String_View *sv, size_t n) 1648 | { 1649 | if (n > sv->count) { 1650 | n = sv->count; 1651 | } 1652 | 1653 | Nob_String_View result = nob_sv_from_parts(sv->data, n); 1654 | 1655 | sv->data += n; 1656 | sv->count -= n; 1657 | 1658 | return result; 1659 | } 1660 | 1661 | Nob_String_View nob_sv_from_parts(const char *data, size_t count) 1662 | { 1663 | Nob_String_View sv; 1664 | sv.count = count; 1665 | sv.data = data; 1666 | return sv; 1667 | } 1668 | 1669 | Nob_String_View nob_sv_trim_left(Nob_String_View sv) 1670 | { 1671 | size_t i = 0; 1672 | while (i < sv.count && isspace(sv.data[i])) { 1673 | i += 1; 1674 | } 1675 | 1676 | return nob_sv_from_parts(sv.data + i, sv.count - i); 1677 | } 1678 | 1679 | Nob_String_View nob_sv_trim_right(Nob_String_View sv) 1680 | { 1681 | size_t i = 0; 1682 | while (i < sv.count && isspace(sv.data[sv.count - 1 - i])) { 1683 | i += 1; 1684 | } 1685 | 1686 | return nob_sv_from_parts(sv.data, sv.count - i); 1687 | } 1688 | 1689 | Nob_String_View nob_sv_trim(Nob_String_View sv) 1690 | { 1691 | return nob_sv_trim_right(nob_sv_trim_left(sv)); 1692 | } 1693 | 1694 | Nob_String_View nob_sv_from_cstr(const char *cstr) 1695 | { 1696 | return nob_sv_from_parts(cstr, strlen(cstr)); 1697 | } 1698 | 1699 | bool nob_sv_eq(Nob_String_View a, Nob_String_View b) 1700 | { 1701 | if (a.count != b.count) { 1702 | return false; 1703 | } else { 1704 | return memcmp(a.data, b.data, a.count) == 0; 1705 | } 1706 | } 1707 | 1708 | bool nob_sv_end_with(Nob_String_View sv, const char *cstr) 1709 | { 1710 | size_t cstr_count = strlen(cstr); 1711 | if (sv.count >= cstr_count) { 1712 | size_t ending_start = sv.count - cstr_count; 1713 | Nob_String_View sv_ending = nob_sv_from_parts(sv.data + ending_start, cstr_count); 1714 | return nob_sv_eq(sv_ending, nob_sv_from_cstr(cstr)); 1715 | } 1716 | return false; 1717 | } 1718 | 1719 | 1720 | bool nob_sv_starts_with(Nob_String_View sv, Nob_String_View expected_prefix) 1721 | { 1722 | if (expected_prefix.count <= sv.count) { 1723 | Nob_String_View actual_prefix = nob_sv_from_parts(sv.data, expected_prefix.count); 1724 | return nob_sv_eq(expected_prefix, actual_prefix); 1725 | } 1726 | 1727 | return false; 1728 | } 1729 | 1730 | // RETURNS: 1731 | // 0 - file does not exists 1732 | // 1 - file exists 1733 | // -1 - error while checking if file exists. The error is logged 1734 | int nob_file_exists(const char *file_path) 1735 | { 1736 | #if _WIN32 1737 | // TODO: distinguish between "does not exists" and other errors 1738 | DWORD dwAttrib = GetFileAttributesA(file_path); 1739 | return dwAttrib != INVALID_FILE_ATTRIBUTES; 1740 | #else 1741 | struct stat statbuf; 1742 | if (stat(file_path, &statbuf) < 0) { 1743 | if (errno == ENOENT) return 0; 1744 | nob_log(NOB_ERROR, "Could not check if file %s exists: %s", file_path, strerror(errno)); 1745 | return -1; 1746 | } 1747 | return 1; 1748 | #endif 1749 | } 1750 | 1751 | const char *nob_get_current_dir_temp(void) 1752 | { 1753 | #ifdef _WIN32 1754 | DWORD nBufferLength = GetCurrentDirectory(0, NULL); 1755 | if (nBufferLength == 0) { 1756 | nob_log(NOB_ERROR, "could not get current directory: %s", nob_win32_error_message(GetLastError())); 1757 | return NULL; 1758 | } 1759 | 1760 | char *buffer = (char*) nob_temp_alloc(nBufferLength); 1761 | if (GetCurrentDirectory(nBufferLength, buffer) == 0) { 1762 | nob_log(NOB_ERROR, "could not get current directory: %s", nob_win32_error_message(GetLastError())); 1763 | return NULL; 1764 | } 1765 | 1766 | return buffer; 1767 | #else 1768 | char *buffer = (char*) nob_temp_alloc(PATH_MAX); 1769 | if (getcwd(buffer, PATH_MAX) == NULL) { 1770 | nob_log(NOB_ERROR, "could not get current directory: %s", strerror(errno)); 1771 | return NULL; 1772 | } 1773 | 1774 | return buffer; 1775 | #endif // _WIN32 1776 | } 1777 | 1778 | bool nob_set_current_dir(const char *path) 1779 | { 1780 | #ifdef _WIN32 1781 | if (!SetCurrentDirectory(path)) { 1782 | nob_log(NOB_ERROR, "could not set current directory to %s: %s", path, nob_win32_error_message(GetLastError())); 1783 | return false; 1784 | } 1785 | return true; 1786 | #else 1787 | if (chdir(path) < 0) { 1788 | nob_log(NOB_ERROR, "could not set current directory to %s: %s", path, strerror(errno)); 1789 | return false; 1790 | } 1791 | return true; 1792 | #endif // _WIN32 1793 | } 1794 | 1795 | // minirent.h SOURCE BEGIN //////////////////////////////////////// 1796 | #ifdef _WIN32 1797 | struct DIR 1798 | { 1799 | HANDLE hFind; 1800 | WIN32_FIND_DATA data; 1801 | struct dirent *dirent; 1802 | }; 1803 | 1804 | DIR *opendir(const char *dirpath) 1805 | { 1806 | NOB_ASSERT(dirpath); 1807 | 1808 | char buffer[MAX_PATH]; 1809 | snprintf(buffer, MAX_PATH, "%s\\*", dirpath); 1810 | 1811 | DIR *dir = (DIR*)NOB_REALLOC(NULL, sizeof(DIR)); 1812 | memset(dir, 0, sizeof(DIR)); 1813 | 1814 | dir->hFind = FindFirstFile(buffer, &dir->data); 1815 | if (dir->hFind == INVALID_HANDLE_VALUE) { 1816 | // TODO: opendir should set errno accordingly on FindFirstFile fail 1817 | // https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror 1818 | errno = ENOSYS; 1819 | goto fail; 1820 | } 1821 | 1822 | return dir; 1823 | 1824 | fail: 1825 | if (dir) { 1826 | NOB_FREE(dir); 1827 | } 1828 | 1829 | return NULL; 1830 | } 1831 | 1832 | struct dirent *readdir(DIR *dirp) 1833 | { 1834 | NOB_ASSERT(dirp); 1835 | 1836 | if (dirp->dirent == NULL) { 1837 | dirp->dirent = (struct dirent*)NOB_REALLOC(NULL, sizeof(struct dirent)); 1838 | memset(dirp->dirent, 0, sizeof(struct dirent)); 1839 | } else { 1840 | if(!FindNextFile(dirp->hFind, &dirp->data)) { 1841 | if (GetLastError() != ERROR_NO_MORE_FILES) { 1842 | // TODO: readdir should set errno accordingly on FindNextFile fail 1843 | // https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror 1844 | errno = ENOSYS; 1845 | } 1846 | 1847 | return NULL; 1848 | } 1849 | } 1850 | 1851 | memset(dirp->dirent->d_name, 0, sizeof(dirp->dirent->d_name)); 1852 | 1853 | strncpy( 1854 | dirp->dirent->d_name, 1855 | dirp->data.cFileName, 1856 | sizeof(dirp->dirent->d_name) - 1); 1857 | 1858 | return dirp->dirent; 1859 | } 1860 | 1861 | int closedir(DIR *dirp) 1862 | { 1863 | NOB_ASSERT(dirp); 1864 | 1865 | if(!FindClose(dirp->hFind)) { 1866 | // TODO: closedir should set errno accordingly on FindClose fail 1867 | // https://docs.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-getlasterror 1868 | errno = ENOSYS; 1869 | return -1; 1870 | } 1871 | 1872 | if (dirp->dirent) { 1873 | NOB_FREE(dirp->dirent); 1874 | } 1875 | NOB_FREE(dirp); 1876 | 1877 | return 0; 1878 | } 1879 | #endif // _WIN32 1880 | // minirent.h SOURCE END //////////////////////////////////////// 1881 | 1882 | #endif // NOB_IMPLEMENTATION 1883 | 1884 | #ifndef NOB_STRIP_PREFIX_GUARD_ 1885 | #define NOB_STRIP_PREFIX_GUARD_ 1886 | // NOTE: The name stripping should be part of the header so it's not accidentally included 1887 | // several times. At the same time, it should be at the end of the file so to not create any 1888 | // potential conflicts in the NOB_IMPLEMENTATION. The header obviously cannot be at the end 1889 | // of the file because NOB_IMPLEMENTATION needs the forward declarations from there. So the 1890 | // solution is to split the header into two parts where the name stripping part is at the 1891 | // end of the file after the NOB_IMPLEMENTATION. 1892 | #ifdef NOB_STRIP_PREFIX 1893 | #define TODO NOB_TODO 1894 | #define UNREACHABLE NOB_UNREACHABLE 1895 | #define UNUSED NOB_UNUSED 1896 | #define ARRAY_LEN NOB_ARRAY_LEN 1897 | #define ARRAY_GET NOB_ARRAY_GET 1898 | #define INFO NOB_INFO 1899 | #define WARNING NOB_WARNING 1900 | #define ERROR NOB_ERROR 1901 | #define NO_LOGS NOB_NO_LOGS 1902 | #define Log_Level Nob_Log_Level 1903 | #define minimal_log_level nob_minimal_log_level 1904 | // NOTE: Name log is already defined in math.h and historically always was the natural logarithmic function. 1905 | // So there should be no reason to strip the `nob_` prefix in this specific case. 1906 | // #define log nob_log 1907 | #define shift nob_shift 1908 | #define shift_args nob_shift_args 1909 | #define File_Paths Nob_File_Paths 1910 | #define FILE_REGULAR NOB_FILE_REGULAR 1911 | #define FILE_DIRECTORY NOB_FILE_DIRECTORY 1912 | #define FILE_SYMLINK NOB_FILE_SYMLINK 1913 | #define FILE_OTHER NOB_FILE_OTHER 1914 | #define File_Type Nob_File_Type 1915 | #define mkdir_if_not_exists nob_mkdir_if_not_exists 1916 | #define copy_file nob_copy_file 1917 | #define copy_directory_recursively nob_copy_directory_recursively 1918 | #define read_entire_dir nob_read_entire_dir 1919 | #define write_entire_file nob_write_entire_file 1920 | #define get_file_type nob_get_file_type 1921 | #define delete_file nob_delete_file 1922 | #define return_defer nob_return_defer 1923 | #define da_append nob_da_append 1924 | #define da_free nob_da_free 1925 | #define da_append_many nob_da_append_many 1926 | #define da_resize nob_da_resize 1927 | #define da_reserve nob_da_reserve 1928 | #define da_last nob_da_last 1929 | #define da_remove_unordered nob_da_remove_unordered 1930 | #define da_foreach nob_da_foreach 1931 | #define String_Builder Nob_String_Builder 1932 | #define read_entire_file nob_read_entire_file 1933 | #define sb_appendf nob_sb_appendf 1934 | #define sb_append_buf nob_sb_append_buf 1935 | #define sb_append_cstr nob_sb_append_cstr 1936 | #define sb_append_null nob_sb_append_null 1937 | #define sb_free nob_sb_free 1938 | #define Proc Nob_Proc 1939 | #define INVALID_PROC NOB_INVALID_PROC 1940 | #define Fd Nob_Fd 1941 | #define INVALID_FD NOB_INVALID_FD 1942 | #define fd_open_for_read nob_fd_open_for_read 1943 | #define fd_open_for_write nob_fd_open_for_write 1944 | #define fd_close nob_fd_close 1945 | #define Procs Nob_Procs 1946 | #define proc_wait nob_proc_wait 1947 | #define procs_wait nob_procs_wait 1948 | #define procs_wait_and_reset nob_procs_wait_and_reset 1949 | #define procs_append_with_flush nob_procs_append_with_flush 1950 | #define Cmd Nob_Cmd 1951 | #define Cmd_Redirect Nob_Cmd_Redirect 1952 | #define cmd_render nob_cmd_render 1953 | #define cmd_append nob_cmd_append 1954 | #define cmd_extend nob_cmd_extend 1955 | #define cmd_free nob_cmd_free 1956 | #define cmd_run_async nob_cmd_run_async 1957 | #define cmd_run_async_and_reset nob_cmd_run_async_and_reset 1958 | #define cmd_run_async_redirect nob_cmd_run_async_redirect 1959 | #define cmd_run_async_redirect_and_reset nob_cmd_run_async_redirect_and_reset 1960 | #define cmd_run_sync nob_cmd_run_sync 1961 | #define cmd_run_sync_and_reset nob_cmd_run_sync_and_reset 1962 | #define cmd_run_sync_redirect nob_cmd_run_sync_redirect 1963 | #define cmd_run_sync_redirect_and_reset nob_cmd_run_sync_redirect_and_reset 1964 | #define temp_strdup nob_temp_strdup 1965 | #define temp_alloc nob_temp_alloc 1966 | #define temp_sprintf nob_temp_sprintf 1967 | #define temp_reset nob_temp_reset 1968 | #define temp_save nob_temp_save 1969 | #define temp_rewind nob_temp_rewind 1970 | #define path_name nob_path_name 1971 | #define rename nob_rename 1972 | #define needs_rebuild nob_needs_rebuild 1973 | #define needs_rebuild1 nob_needs_rebuild1 1974 | #define file_exists nob_file_exists 1975 | #define get_current_dir_temp nob_get_current_dir_temp 1976 | #define set_current_dir nob_set_current_dir 1977 | #define String_View Nob_String_View 1978 | #define temp_sv_to_cstr nob_temp_sv_to_cstr 1979 | #define sv_chop_by_delim nob_sv_chop_by_delim 1980 | #define sv_chop_left nob_sv_chop_left 1981 | #define sv_trim nob_sv_trim 1982 | #define sv_trim_left nob_sv_trim_left 1983 | #define sv_trim_right nob_sv_trim_right 1984 | #define sv_eq nob_sv_eq 1985 | #define sv_starts_with nob_sv_starts_with 1986 | #define sv_end_with nob_sv_end_with 1987 | #define sv_from_cstr nob_sv_from_cstr 1988 | #define sv_from_parts nob_sv_from_parts 1989 | #define sb_to_sv nob_sb_to_sv 1990 | #define win32_error_message nob_win32_error_message 1991 | #endif // NOB_STRIP_PREFIX 1992 | #endif // NOB_STRIP_PREFIX_GUARD_ 1993 | 1994 | /* 1995 | Revision history: 1996 | 1997 | 1.20.2 (2025-04-24) Report the program name that failed to start up in nob_cmd_run_async_redirect() (By @rexim) 1998 | 1.20.1 (2025-04-16) Use vsnprintf() in nob_sb_appendf() instead of vsprintf() (By @LainLayer) 1999 | 1.20.0 (2025-04-16) Introduce nob_cc(), nob_cc_flags(), nob_cc_inputs(), nob_cc_output() macros (By @rexim) 2000 | 1.19.0 (2025-03-25) Add nob_procs_append_with_flush() (By @rexim and @anion155) 2001 | 1.18.0 (2025-03-24) Add nob_da_foreach() (By @rexim) 2002 | Allow file sizes greater than 2GB to be read on windows (By @satchelfrost and @KillerxDBr) 2003 | Fix nob_fd_open_for_write behaviour on windows so it truncates the opened files (By @twixuss) 2004 | 1.17.0 (2025-03-16) Factor out nob_da_reserve() (By @rexim) 2005 | Add nob_sb_appendf() (By @angelcaru) 2006 | 1.16.1 (2025-03-16) Make nob_da_resize() exponentially grow capacity similar to no_da_append_many() 2007 | 1.16.0 (2025-03-16) Introduce NOB_PRINTF_FORMAT 2008 | 1.15.1 (2025-03-16) Make nob.h compilable in gcc/clang with -std=c99 on POSIX. This includes: 2009 | not using strsignal() 2010 | using S_IS* stat macros instead of S_IF* flags 2011 | 1.15.0 (2025-03-03) Add nob_sv_chop_left() 2012 | 1.14.1 (2025-03-02) Add NOB_EXPERIMENTAL_DELETE_OLD flag that enables deletion of nob.old in Go Rebuild Urself™ Technology 2013 | 1.14.0 (2025-02-17) Add nob_da_last() 2014 | Add nob_da_remove_unordered() 2015 | 1.13.1 (2025-02-17) Fix segfault in nob_delete_file() (By @SileNce5k) 2016 | 1.13.0 (2025-02-11) Add nob_da_resize() (By @satchelfrost) 2017 | 1.12.0 (2025-02-04) Add nob_delete_file() 2018 | Add nob_sv_start_with() 2019 | 1.11.0 (2025-02-04) Add NOB_GO_REBUILD_URSELF_PLUS() (By @rexim) 2020 | 1.10.0 (2025-02-04) Make NOB_ASSERT, NOB_REALLOC, and NOB_FREE redefinable (By @OleksiiBulba) 2021 | 1.9.1 (2025-02-04) Fix signature of nob_get_current_dir_temp() (By @julianstoerig) 2022 | 1.9.0 (2024-11-06) Add Nob_Cmd_Redirect mechanism (By @rexim) 2023 | Add nob_path_name() (By @0dminnimda) 2024 | 1.8.0 (2024-11-03) Add nob_cmd_extend() (By @0dminnimda) 2025 | 1.7.0 (2024-11-03) Add nob_win32_error_message and NOB_WIN32_ERR_MSG_SIZE (By @KillerxDBr) 2026 | 1.6.0 (2024-10-27) Add nob_cmd_run_sync_and_reset() 2027 | Add nob_sb_to_sv() 2028 | Add nob_procs_wait_and_reset() 2029 | 1.5.1 (2024-10-25) Include limits.h for Linux musl libc (by @pgalkin) 2030 | 1.5.0 (2024-10-23) Add nob_get_current_dir_temp() 2031 | Add nob_set_current_dir() 2032 | 1.4.0 (2024-10-21) Fix UX issues with NOB_GO_REBUILD_URSELF on Windows when you call nob without the .exe extension (By @pgalkin) 2033 | Add nob_sv_end_with (By @pgalkin) 2034 | 1.3.2 (2024-10-21) Fix unreachable error in nob_log on passing NOB_NO_LOGS 2035 | 1.3.1 (2024-10-21) Fix redeclaration error for minimal_log_level (By @KillerxDBr) 2036 | 1.3.0 (2024-10-17) Add NOB_UNREACHABLE 2037 | 1.2.2 (2024-10-16) Fix compilation of nob_cmd_run_sync_and_reset on Windows (By @KillerxDBr) 2038 | 1.2.1 (2024-10-16) Add a separate include guard for NOB_STRIP_PREFIX. 2039 | 1.2.0 (2024-10-15) Make NOB_DA_INIT_CAP redefinable 2040 | Add NOB_STRIP_PREFIX which strips off nob_* prefix from all the user facing names 2041 | Add NOB_UNUSED macro 2042 | Add NOB_TODO macro 2043 | Add nob_sv_trim_left and nob_sv_trim_right declarations to the header part 2044 | 1.1.1 (2024-10-15) Remove forward declaration for is_path1_modified_after_path2 2045 | 1.1.0 (2024-10-15) nob_minimal_log_level 2046 | nob_cmd_run_sync_and_reset 2047 | 1.0.0 (2024-10-15) first release based on https://github.com/tsoding/musializer/blob/4ac7cce9874bc19e02d8c160c8c6229de8919401/nob.h 2048 | */ 2049 | 2050 | /* 2051 | Version Conventions: 2052 | 2053 | We are following https://semver.org/ so the version has a format MAJOR.MINOR.PATCH: 2054 | - Modifying comments does not update the version. 2055 | - PATCH is incremented in case of a bug fix or refactoring without touching the API. 2056 | - MINOR is incremented when new functions and/or types are added in a way that does 2057 | not break any existing user code. We want to do this in the majority of the situation. 2058 | If we want to delete a certain function or type in favor of another one we should 2059 | just add the new function/type and deprecate the old one in a backward compatible way 2060 | and let them co-exist for a while. 2061 | - MAJOR update should be just a periodic cleanup of the deprecated functions and types 2062 | without really modifying any existing functionality. 2063 | 2064 | Naming Conventions: 2065 | 2066 | - All the user facing names should be prefixed with `nob_` or `NOB_` depending on the case. 2067 | - The prefixes of non-redefinable names should be strippable with NOB_STRIP_PREFIX (unless 2068 | explicitly stated otherwise like in case of nob_log). 2069 | - Internal functions should be prefixed with `nob__` (double underscore). 2070 | */ 2071 | 2072 | /* 2073 | ------------------------------------------------------------------------------ 2074 | This software is available under 2 licenses -- choose whichever you prefer. 2075 | ------------------------------------------------------------------------------ 2076 | ALTERNATIVE A - MIT License 2077 | Copyright (c) 2024 Alexey Kutepov 2078 | Permission is hereby granted, free of charge, to any person obtaining a copy of 2079 | this software and associated documentation files (the "Software"), to deal in 2080 | the Software without restriction, including without limitation the rights to 2081 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 2082 | of the Software, and to permit persons to whom the Software is furnished to do 2083 | so, subject to the following conditions: 2084 | The above copyright notice and this permission notice shall be included in all 2085 | copies or substantial portions of the Software. 2086 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 2087 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 2088 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 2089 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 2090 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 2091 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 2092 | SOFTWARE. 2093 | ------------------------------------------------------------------------------ 2094 | ALTERNATIVE B - Public Domain (www.unlicense.org) 2095 | This is free and unencumbered software released into the public domain. 2096 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute this 2097 | software, either in source code form or as a compiled binary, for any purpose, 2098 | commercial or non-commercial, and by any means. 2099 | In jurisdictions that recognize copyright laws, the author or authors of this 2100 | software dedicate any and all copyright interest in the software to the public 2101 | domain. We make this dedication for the benefit of the public at large and to 2102 | the detriment of our heirs and successors. We intend this dedication to be an 2103 | overt act of relinquishment in perpetuity of all present and future rights to 2104 | this software under copyright law. 2105 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 2106 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 2107 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 2108 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 2109 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 2110 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 2111 | ------------------------------------------------------------------------------ 2112 | */ 2113 | -------------------------------------------------------------------------------- /src/teenysha1.h: -------------------------------------------------------------------------------- 1 | /* 2 | * TeenySHA1 - a header only implementation of the SHA1 algorithm in C. Based 3 | * on the implementation in boost::uuid::details. Translated to C from 4 | * https://github.com/mohaps/TinySHA1 5 | * 6 | * SHA1 Wikipedia Page: http://en.wikipedia.org/wiki/SHA-1 7 | * 8 | * Copyright (c) 2012-25 SAURAV MOHAPATRA 9 | * Copyright (c) 2025 ALEXEY KUTEPOV 10 | * 11 | * Permission to use, copy, modify, and distribute this software for any 12 | * purpose with or without fee is hereby granted, provided that the above 13 | * copyright notice and this permission notice appear in all copies. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 16 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 17 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 18 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 19 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 20 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 21 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 22 | */ 23 | #ifndef _TEENY_SHA1_HPP_ 24 | #define _TEENY_SHA1_HPP_ 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | typedef uint32_t digest32_t[5]; 31 | typedef uint8_t digest8_t[20]; 32 | 33 | typedef struct { 34 | digest32_t digest; 35 | uint8_t block[64]; 36 | size_t block_byte_index; 37 | size_t byte_count; 38 | } SHA1; 39 | 40 | void sha1_reset(SHA1 *sha1); 41 | void sha1_process_block(SHA1 *sha1, const void* const start, const void* const end); 42 | void sha1_process_byte(SHA1 *sha1, uint8_t octet); 43 | void sha1_process_bytes(SHA1 *sha1, const void* const data, size_t len); 44 | // WARNING! On little-endian machine (like x86_64) `sha1_get_digest` will return the digest uint32_t chunks 45 | // in a byte order suitable for human readable printing 46 | // ```c 47 | // printf("%08x%08x%08x%08x%08x\n", digest[0], digest[1], digest[2], digest[3], digest[4]) 48 | // ``` 49 | // If you need actual digest bytes in a correct order use `sha1_get_digest_bytes`. 50 | const uint32_t* sha1_get_digest(SHA1 *sha1, digest32_t digest); 51 | const uint8_t* sha1_get_digest_bytes(SHA1 *sha1, digest8_t digest); 52 | 53 | #endif // _TEENY_SHA1_HPP_ 54 | 55 | #ifdef TEENY_SHA1_IMPLEMENTATION 56 | 57 | static inline uint32_t sha1__left_rotate(uint32_t value, size_t count) 58 | { 59 | return (value << count) ^ (value >> (32-count)); 60 | } 61 | 62 | void sha1__process_block(SHA1 *sha1) 63 | { 64 | uint32_t w[80]; 65 | for (size_t i = 0; i < 16; i++) { 66 | w[i] = (sha1->block[i*4 + 0] << 24); 67 | w[i] |= (sha1->block[i*4 + 1] << 16); 68 | w[i] |= (sha1->block[i*4 + 2] << 8); 69 | w[i] |= (sha1->block[i*4 + 3]); 70 | } 71 | for (size_t i = 16; i < 80; i++) { 72 | w[i] = sha1__left_rotate((w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16]), 1); 73 | } 74 | 75 | uint32_t a = sha1->digest[0]; 76 | uint32_t b = sha1->digest[1]; 77 | uint32_t c = sha1->digest[2]; 78 | uint32_t d = sha1->digest[3]; 79 | uint32_t e = sha1->digest[4]; 80 | 81 | for (size_t i=0; i<80; ++i) { 82 | uint32_t f = 0; 83 | uint32_t k = 0; 84 | 85 | if (i<20) { 86 | f = (b & c) | (~b & d); 87 | k = 0x5A827999; 88 | } else if (i<40) { 89 | f = b ^ c ^ d; 90 | k = 0x6ED9EBA1; 91 | } else if (i<60) { 92 | f = (b & c) | (b & d) | (c & d); 93 | k = 0x8F1BBCDC; 94 | } else { 95 | f = b ^ c ^ d; 96 | k = 0xCA62C1D6; 97 | } 98 | uint32_t temp = sha1__left_rotate(a, 5) + f + e + k + w[i]; 99 | e = d; 100 | d = c; 101 | c = sha1__left_rotate(b, 30); 102 | b = a; 103 | a = temp; 104 | } 105 | 106 | sha1->digest[0] += a; 107 | sha1->digest[1] += b; 108 | sha1->digest[2] += c; 109 | sha1->digest[3] += d; 110 | sha1->digest[4] += e; 111 | } 112 | 113 | void sha1_reset(SHA1 *sha1) 114 | { 115 | sha1->digest[0] = 0x67452301; 116 | sha1->digest[1] = 0xEFCDAB89; 117 | sha1->digest[2] = 0x98BADCFE; 118 | sha1->digest[3] = 0x10325476; 119 | sha1->digest[4] = 0xC3D2E1F0; 120 | sha1->block_byte_index = 0; 121 | sha1->byte_count = 0; 122 | } 123 | 124 | void sha1_process_byte(SHA1 *sha1, uint8_t octet) 125 | { 126 | sha1->block[sha1->block_byte_index++] = octet; 127 | ++sha1->byte_count; 128 | if(sha1->block_byte_index == 64) { 129 | sha1->block_byte_index = 0; 130 | sha1__process_block(sha1); 131 | } 132 | } 133 | 134 | void sha1_process_block(SHA1 *sha1, const void* const start, const void* const end) 135 | { 136 | const uint8_t* begin = (const uint8_t*)(start); 137 | const uint8_t* finish = (const uint8_t*)(end); 138 | while(begin != finish) { 139 | sha1_process_byte(sha1, *begin); 140 | begin++; 141 | } 142 | } 143 | 144 | void sha1_process_bytes(SHA1 *sha1, const void* const data, size_t len) 145 | { 146 | const uint8_t* block = (const uint8_t*)(data); 147 | sha1_process_block(sha1, block, block + len); 148 | } 149 | 150 | const uint32_t* sha1_get_digest(SHA1 *sha1, digest32_t digest) 151 | { 152 | size_t bitCount = sha1->byte_count * 8; 153 | sha1_process_byte(sha1, 0x80); 154 | if (sha1->block_byte_index > 56) { 155 | while (sha1->block_byte_index != 0) { 156 | sha1_process_byte(sha1, 0); 157 | } 158 | while (sha1->block_byte_index < 56) { 159 | sha1_process_byte(sha1, 0); 160 | } 161 | } else { 162 | while (sha1->block_byte_index < 56) { 163 | sha1_process_byte(sha1, 0); 164 | } 165 | } 166 | sha1_process_byte(sha1, 0); 167 | sha1_process_byte(sha1, 0); 168 | sha1_process_byte(sha1, 0); 169 | sha1_process_byte(sha1, 0); 170 | sha1_process_byte(sha1, (unsigned char)((bitCount>>24) & 0xFF)); 171 | sha1_process_byte(sha1, (unsigned char)((bitCount>>16) & 0xFF)); 172 | sha1_process_byte(sha1, (unsigned char)((bitCount>>8 ) & 0xFF)); 173 | sha1_process_byte(sha1, (unsigned char)((bitCount) & 0xFF)); 174 | 175 | memcpy(digest, sha1->digest, 5 * sizeof(uint32_t)); 176 | return digest; 177 | } 178 | 179 | const uint8_t* sha1_get_digest_bytes(SHA1 *sha1, digest8_t digest) 180 | { 181 | digest32_t d32; 182 | sha1_get_digest(sha1, d32); 183 | size_t di = 0; 184 | digest[di++] = ((d32[0] >> 24) & 0xFF); 185 | digest[di++] = ((d32[0] >> 16) & 0xFF); 186 | digest[di++] = ((d32[0] >> 8) & 0xFF); 187 | digest[di++] = ((d32[0]) & 0xFF); 188 | 189 | digest[di++] = ((d32[1] >> 24) & 0xFF); 190 | digest[di++] = ((d32[1] >> 16) & 0xFF); 191 | digest[di++] = ((d32[1] >> 8) & 0xFF); 192 | digest[di++] = ((d32[1]) & 0xFF); 193 | 194 | digest[di++] = ((d32[2] >> 24) & 0xFF); 195 | digest[di++] = ((d32[2] >> 16) & 0xFF); 196 | digest[di++] = ((d32[2] >> 8) & 0xFF); 197 | digest[di++] = ((d32[2]) & 0xFF); 198 | 199 | digest[di++] = ((d32[3] >> 24) & 0xFF); 200 | digest[di++] = ((d32[3] >> 16) & 0xFF); 201 | digest[di++] = ((d32[3] >> 8) & 0xFF); 202 | digest[di++] = ((d32[3]) & 0xFF); 203 | 204 | digest[di++] = ((d32[4] >> 24) & 0xFF); 205 | digest[di++] = ((d32[4] >> 16) & 0xFF); 206 | digest[di++] = ((d32[4] >> 8) & 0xFF); 207 | digest[di++] = ((d32[4]) & 0xFF); 208 | return digest; 209 | } 210 | 211 | #endif // TEENY_SHA1_IMPLEMENTATION 212 | -------------------------------------------------------------------------------- /tools/send_client.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | C3 WebSocket Library 5 | 6 | 7 |

Start the example_server and send some text to it via this field:

8 | 9 | 10 |

11 | 42 | 43 | 44 | --------------------------------------------------------------------------------