├── examples └── echoback │ ├── bin │ └── .gitkeep │ ├── obj │ └── .gitkeep │ ├── src │ └── main.c │ └── Makefile ├── .dockerignore ├── shell ├── static_analysis.sh ├── loop_test.sh ├── check_ldd.sh ├── musl_build.sh ├── dump_kernel.sh └── run.sh ├── src ├── arch │ ├── linux │ │ ├── x86_64 │ │ │ ├── dummy.c │ │ │ ├── restore_rt.S │ │ │ ├── close.h │ │ │ ├── write.h │ │ │ ├── accept.h │ │ │ ├── recv.h │ │ │ ├── send.h │ │ │ ├── optimize_socket.h │ │ │ ├── listen.h │ │ │ ├── sigaction.h │ │ │ ├── epoll.h │ │ │ ├── memory.h │ │ │ ├── asm_syscall.S │ │ │ └── asm.h │ │ ├── sockaddr.h │ │ ├── epoll.h │ │ ├── fcntl.h │ │ ├── sigaction_def.h │ │ ├── sockoption.h │ │ └── errno.h │ ├── darwin │ │ ├── epoll.h │ │ ├── close.h │ │ ├── write.h │ │ ├── accept.h │ │ ├── sigaction.h │ │ ├── send.h │ │ ├── recv.h │ │ ├── listen.h │ │ ├── optimize_socket.h │ │ ├── memory.h │ │ └── kqueue.c │ ├── close.h │ ├── write.h │ ├── accept.h │ ├── recv.h │ ├── send.h │ ├── sigaction.h │ ├── epoll.h │ ├── optimize_socket.h │ ├── listen.h │ └── memory.h ├── util │ ├── signal.h │ ├── signal.c │ ├── allocator.h │ ├── types.h │ ├── log.c │ └── string.h ├── crypto │ ├── sha1.h │ ├── sha1_def.h │ ├── base64.h │ └── sha1.c ├── websocket │ ├── socket │ │ ├── close.c │ │ ├── send.c │ │ ├── accept.c │ │ ├── recv.c │ │ ├── listen.c │ │ ├── optimize_socket.h │ │ └── epoll.c │ ├── server │ │ ├── init.c │ │ ├── accept │ │ │ ├── epoll_accept.h │ │ │ └── accept_handle.h │ │ ├── receive │ │ │ ├── receive_handle.h │ │ │ ├── epoll_receive.h │ │ │ └── opcode_handle.h │ │ ├── loop.c │ │ └── handshake.c │ ├── internal_log.c │ ├── crypto.c │ ├── websocket_local.h │ ├── parser.c │ └── websocket.h └── http │ ├── http.h │ └── http.c ├── renovate.json ├── docker-compose.yml ├── .clang-format ├── .github └── workflows │ ├── lint.yml │ ├── coverity.yml │ └── test.yml ├── Justfile ├── Dockerfile ├── tests ├── Makefile ├── CMakeLists.txt ├── http │ └── request.cpp └── crypto │ ├── base64.cpp │ └── sha1.cpp ├── .gitignore ├── LICENSE ├── flake.lock ├── CMakeLists.txt ├── flake.nix └── README.md /examples/echoback/bin/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/echoback/obj/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | **/.git 2 | **/README.md 3 | **/LICENSE 4 | 5 | -------------------------------------------------------------------------------- /shell/static_analysis.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd $(dirname $0) 4 | 5 | clang-tidy $(find ../src -name "*.[ch]") 6 | -------------------------------------------------------------------------------- /src/arch/linux/x86_64/dummy.c: -------------------------------------------------------------------------------- 1 | void __cxa_finalize(void* ptr) 2 | { 3 | } 4 | void __stack_chk_fail(void* ptr) 5 | { 6 | } 7 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /shell/loop_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Need iperf3. 4 | # sudo apt install iperf3 5 | 6 | iperf3 -s & 7 | sleep 1 8 | iperf3 -c localhost 9 | pkill iperf3 10 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | websocket: 3 | build: 4 | context: . 5 | dockerfile: Dockerfile 6 | target: runtime 7 | ports: 8 | - "8080:8080" 9 | -------------------------------------------------------------------------------- /src/util/signal.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_SIGNAL_H_ 2 | #define NOSTR_SIGNAL_H_ 3 | 4 | #include "./types.h" 5 | 6 | bool is_rise_signal(); 7 | bool signal_init(); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /src/arch/darwin/epoll.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_DARWIN_EPOLL_H_ 2 | #define NOSTR_DARWIN_EPOLL_H_ 3 | 4 | #ifdef __APPLE__ 5 | #include 6 | typedef struct kevent WebSocketEpollEvent, *PWebSocketEpollEvent; 7 | #endif 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /src/crypto/sha1.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_SHA1_H_ 2 | #define NOSTR_SHA1_H_ 3 | 4 | #include "../util/types.h" 5 | 6 | #define SHA1_DIGEST_LENGTH 20 7 | 8 | void sha1(const char* input, const size_t input_len, uint8_t* output); 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /src/arch/darwin/close.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_DARWIN_CLOSE_H_ 2 | #define NOSTR_DARWIN_CLOSE_H_ 3 | 4 | #include 5 | 6 | #include "../../util/types.h" 7 | 8 | static inline int32_t darwin_close(int32_t fd) 9 | { 10 | return close(fd); 11 | } 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /src/arch/linux/x86_64/restore_rt.S: -------------------------------------------------------------------------------- 1 | .section .text 2 | .align 16 3 | .global linux_x8664_restore_rt 4 | .type linux_x8664_restore_rt, @function 5 | linux_x8664_restore_rt: 6 | .cfi_startproc 7 | .cfi_signal_frame 8 | mov $15, %rax 9 | syscall 10 | .cfi_endproc 11 | -------------------------------------------------------------------------------- /src/arch/darwin/write.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_DARWIN_WRITE_H_ 2 | #define NOSTR_DARWIN_WRITE_H_ 3 | 4 | #include 5 | 6 | #include "../../util/types.h" 7 | 8 | static inline ssize_t darwin_write(const int32_t fd, const void* buf, const size_t count) 9 | { 10 | return write(fd, buf, count); 11 | } 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /src/arch/darwin/accept.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_DARWIN_ACCEPT_H_ 2 | #define NOSTR_DARWIN_ACCEPT_H_ 3 | 4 | #include 5 | 6 | #include "../../util/types.h" 7 | 8 | static inline int32_t darwin_accept(const int32_t sock_fd, struct sockaddr* addr, socklen_t* addrlen, const int32_t flags) 9 | { 10 | return accept(sock_fd, addr, addrlen); 11 | } 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /src/arch/linux/x86_64/close.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_LINUX_X86_64_CLOSE_H_ 2 | #define NOSTR_LINUX_X86_64_CLOSE_H_ 3 | 4 | #include "../errno.h" 5 | #include "./asm.h" 6 | 7 | static inline int32_t linux_x8664_close(const int fd) 8 | { 9 | int32_t ret = linux_x8664_asm_syscall1( 10 | __NR_close, 11 | fd); 12 | 13 | SYSCALL_EARLY_RETURN(ret); 14 | return ret; 15 | } 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | AlignConsecutiveAssignments: true 3 | AlignConsecutiveDeclarations: true 4 | ColumnLimit: 0 5 | IndentWidth: 2 6 | TabWidth: 2 7 | ContinuationIndentWidth: 2 8 | AllowShortFunctionsOnASingleLine: None 9 | AllowShortLoopsOnASingleLine: false 10 | BreakBeforeBraces: Linux 11 | SortIncludes: true 12 | DerivePointerAlignment: false 13 | PointerAlignment: Left 14 | AlignOperands: true 15 | 16 | -------------------------------------------------------------------------------- /src/arch/close.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_INTERNAL_CLOSE_H_ 2 | #define NOSTR_INTERNAL_CLOSE_H_ 3 | 4 | #include "../util/types.h" 5 | #ifdef __APPLE__ 6 | #include "darwin/close.h" 7 | #else 8 | #include "linux/x86_64/close.h" 9 | #endif 10 | 11 | static inline int32_t internal_close(int32_t fd) 12 | { 13 | #ifdef __APPLE__ 14 | return darwin_close(fd); 15 | #else 16 | return linux_x8664_close(fd); 17 | #endif 18 | } 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /src/arch/write.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_INTERNAL_WRITE_H_ 2 | #define NOSTR_INTERNAL_WRITE_H_ 3 | 4 | #ifdef __APPLE__ 5 | #include "darwin/write.h" 6 | #else 7 | #include "linux/x86_64/write.h" 8 | #endif 9 | 10 | static inline ssize_t internal_write(const int32_t fd, const void* buf, const size_t count) 11 | { 12 | #ifdef __APPLE__ 13 | return darwin_write(fd, buf, count); 14 | #else 15 | return linux_x8664_write(fd, buf, count); 16 | #endif 17 | } 18 | #endif 19 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | on: 3 | push: 4 | pull_request: 5 | 6 | permissions: 7 | contents: read # to fetch code (actions/checkout) 8 | 9 | jobs: 10 | scan: 11 | runs-on: ubuntu-24.04 12 | 13 | env: 14 | CC: gcc 15 | DEBIAN_FRONTEND: noninteractive 16 | 17 | steps: 18 | - name: Checkout code 19 | uses: actions/checkout@v6 20 | 21 | - name: Lint markdown 22 | uses: akiomik/mado@v0.3.0 23 | -------------------------------------------------------------------------------- /src/arch/darwin/sigaction.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_DARWIN_SIGACTION_H_ 2 | #define NOSTR_DARWIN_SIGACTION_H_ 3 | 4 | #include 5 | 6 | #include "../../util/types.h" 7 | 8 | static inline int32_t darwin_sigaction(const int32_t signum, struct sigaction* act, struct sigaction* oldact) 9 | { 10 | return sigaction(signum, act, oldact); 11 | } 12 | 13 | static inline int32_t darwin_sigemptyset(sigset_t* set) 14 | { 15 | return sigemptyset(set); 16 | } 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /src/arch/darwin/send.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_DARWIN_SEND_H_ 2 | #define NOSTR_DARWIN_SEND_H_ 3 | 4 | #include 5 | 6 | #include "../../util/types.h" 7 | 8 | static inline ssize_t darwin_sendto( 9 | const int32_t sock_fd, 10 | const void* buf, 11 | const size_t len, 12 | const int32_t flags, 13 | struct sockaddr* dest_addr, 14 | const socklen_t addr_len) 15 | { 16 | return sendto(sock_fd, buf, len, flags, dest_addr, addr_len); 17 | } 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /src/arch/darwin/recv.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_DARWIN_RECV_H_ 2 | #define NOSTR_DARWIN_RECV_H_ 3 | 4 | #include 5 | 6 | #include "../../util/types.h" 7 | 8 | static inline ssize_t darwin_recvfrom( 9 | const int32_t sock_fd, 10 | void* buf, 11 | const size_t len, 12 | const int32_t flags, 13 | struct sockaddr* src_addr, 14 | socklen_t* addr_len) 15 | { 16 | return recvfrom(sock_fd, buf, len, flags, src_addr, addr_len); 17 | } 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /src/arch/linux/x86_64/write.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_LINUX_X86_64_WRITE_H_ 2 | #define NOSTR_LINUX_X86_64_WRITE_H_ 3 | 4 | #include "../../../util/types.h" 5 | #include "../errno.h" 6 | #include "./asm.h" 7 | 8 | static inline ssize_t linux_x8664_write(const int32_t fd, const void* buf, const size_t count) 9 | { 10 | int32_t ret = linux_x8664_asm_syscall3( 11 | __NR_write, 12 | fd, 13 | buf, 14 | count); 15 | 16 | SYSCALL_SIZE_EARLY_RETURN(ret); 17 | return (ssize_t)ret; 18 | } 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /src/websocket/socket/close.c: -------------------------------------------------------------------------------- 1 | #include "../../arch/close.h" 2 | 3 | #include "../websocket_local.h" 4 | 5 | int32_t websocket_close(const int32_t sock_fd) 6 | { 7 | var_info("WebSocket close...: ", sock_fd); 8 | if (sock_fd < 0) { 9 | return WEBSOCKET_ERRORCODE_NONE; 10 | } 11 | 12 | if (internal_close(sock_fd) == WEBSOCKET_SYSCALL_ERROR) { 13 | str_info("WebSocket close error: ", strerror(errno)); 14 | return WEBSOCKET_ERRORCODE_SOCKET_CLOSE_ERROR; 15 | } 16 | 17 | return WEBSOCKET_ERRORCODE_NONE; 18 | } 19 | -------------------------------------------------------------------------------- /Justfile: -------------------------------------------------------------------------------- 1 | release-build: 2 | rm -rf build 3 | cmake -S . -B build -DCMAKE_BUILD_TYPE=Release 4 | cmake --build build 5 | 6 | debug-build: 7 | rm -rf build 8 | cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug 9 | cmake --build build 10 | 11 | sample-debug-build: 12 | make clean -C examples/echoback 13 | make BUILD=debug -C examples/echoback 14 | 15 | sample-release-build: 16 | make clean -C examples/echoback 17 | make BUILD=release -C examples/echoback 18 | 19 | fmt: 20 | clang-format -i $(find ./src -name '*.[ch]') 21 | 22 | -------------------------------------------------------------------------------- /src/arch/linux/x86_64/accept.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_LINUX_X86_64_ACCEPT_H_ 2 | #define NOSTR_LINUX_X86_64_ACCEPT_H_ 3 | 4 | #include "../errno.h" 5 | #include "../sockaddr.h" 6 | #include "./asm.h" 7 | 8 | static inline int32_t linux_x8664_accept4(const int32_t sock_fd, const struct sockaddr* addr, const socklen_t* addrlen, const int32_t flags) 9 | { 10 | int32_t ret = linux_x8664_asm_syscall4( 11 | __NR_accept4, 12 | sock_fd, 13 | addr, 14 | addrlen, 15 | flags); 16 | 17 | SYSCALL_EARLY_RETURN(ret); 18 | return ret; 19 | } 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /src/arch/accept.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_INTERNAL_ACCEPT_H_ 2 | #define NOSTR_INTERNAL_ACCEPT_H_ 3 | 4 | #include "../util/types.h" 5 | #ifdef __APPLE__ 6 | #include "darwin/accept.h" 7 | #else 8 | #include "linux/x86_64/accept.h" 9 | #endif 10 | 11 | static inline int32_t internal_accept(const int32_t sock_fd, struct sockaddr* addr, socklen_t* addrlen, const int32_t flags) 12 | { 13 | #ifdef __APPLE__ 14 | return darwin_accept(sock_fd, addr, addrlen, flags); 15 | #else 16 | return linux_x8664_accept4(sock_fd, addr, addrlen, flags); 17 | #endif 18 | } 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /shell/check_ldd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd $(dirname $0) 4 | 5 | ECHOBACK_SERVER_PATH=../examples/echoback 6 | ECHOBACK_SERVER_BIN_PATH=${ECHOBACK_SERVER_PATH}/bin/wsserver 7 | 8 | wsDebugBuild() { 9 | rm -rf ../build 1>/dev/null 10 | cmake -S .. -B ../build -DCMAKE_BUILD_TYPE=Debug 1>/dev/null 11 | cmake --build ../build 1>/dev/null 12 | make clean -C ${ECHOBACK_SERVER_PATH} 1>/dev/null 13 | BUILD=debug make -C ${ECHOBACK_SERVER_PATH} 1>/dev/null 14 | } 15 | 16 | echo "check link (examples/echoback/bin/wsserver)..." 17 | wsDebugBuild 18 | ldd ../examples/echoback/bin/wsserver 19 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:bookworm-slim AS build-dev 2 | WORKDIR /opt/websocket 3 | COPY . /opt/websocket 4 | RUN \ 5 | apt update && \ 6 | apt install -y \ 7 | cmake \ 8 | make 9 | RUN \ 10 | cmake -S /opt/websocket -B /opt/websocket/build -DCMAKE_BUILD_TYPE=Release && \ 11 | cmake --build /opt/websocket/build && \ 12 | make BUILD=release -C /opt/websocket/examples/echoback 13 | 14 | FROM scratch AS runtime 15 | WORKDIR /opt/websocket 16 | 17 | COPY --from=build-dev /opt/websocket/examples/echoback/bin/wsserver /opt/websocket/examples/echoback/bin/ 18 | 19 | ENTRYPOINT ["/opt/websocket/examples/echoback/bin/wsserver"] 20 | -------------------------------------------------------------------------------- /src/websocket/server/init.c: -------------------------------------------------------------------------------- 1 | #include "../websocket_local.h" 2 | 3 | int32_t websocket_server_init(const PWebSocketInitArgs args) 4 | { 5 | log_info("websocket server init processing...\n"); 6 | var_info("port : ", args->port_num); 7 | var_info("backlog : ", args->backlog); 8 | 9 | if (!signal_init()) { 10 | return WEBSOCKET_ERRORCODE_FATAL_ERROR; 11 | } 12 | 13 | int32_t server_sock = websocket_listen(args->port_num, args->backlog); 14 | if (server_sock < 0) { 15 | return WEBSOCKET_ERRORCODE_FATAL_ERROR; 16 | } 17 | 18 | log_info("WebSocket server is listening...\n"); 19 | return server_sock; 20 | } 21 | -------------------------------------------------------------------------------- /src/arch/linux/sockaddr.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_LINUX_SOCKADDR_H_ 2 | #define NOSTR_LINUX_SOCKADDR_H_ 3 | 4 | #include "../../util/types.h" 5 | 6 | #define AF_INET 2 7 | #define SOCK_STREAM 1 8 | #define INADDR_ANY ((in_addr_t)0x00000000) 9 | 10 | typedef uint32_t in_addr_t; 11 | 12 | struct in_addr { 13 | in_addr_t s_addr; 14 | }; 15 | 16 | struct sockaddr_in { 17 | uint16_t sin_family; 18 | uint16_t sin_port; 19 | struct in_addr sin_addr; 20 | char sin_zero[8]; 21 | }; 22 | 23 | struct sockaddr { 24 | uint16_t sa_family; 25 | char sa_data[14]; 26 | }; 27 | 28 | typedef uint32_t socklen_t; 29 | #endif 30 | -------------------------------------------------------------------------------- /src/arch/recv.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_INTERNAL_RECV_H_ 2 | #define NOSTR_INTERNAL_RECV_H_ 3 | 4 | #ifdef __APPLE__ 5 | #include "darwin/recv.h" 6 | #else 7 | #include "linux/x86_64/recv.h" 8 | #endif 9 | 10 | static inline ssize_t internal_recvfrom( 11 | const int32_t sock_fd, 12 | void* buf, 13 | const size_t len, 14 | const int32_t flags, 15 | struct sockaddr* src_addr, 16 | socklen_t* addr_len) 17 | { 18 | #ifdef __APPLE__ 19 | return darwin_recvfrom(sock_fd, buf, len, flags, src_addr, addr_len); 20 | #else 21 | return linux_x8664_recvfrom(sock_fd, buf, len, flags, src_addr, addr_len); 22 | #endif 23 | } 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /src/arch/send.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_INTERNAL_SEND_H_ 2 | #define NOSTR_INTERNAL_SEND_H_ 3 | 4 | #ifdef __APPLE__ 5 | #include "darwin/send.h" 6 | #else 7 | #include "linux/x86_64/send.h" 8 | #endif 9 | 10 | static inline ssize_t internal_sendto( 11 | const int32_t sock_fd, 12 | const void* buf, 13 | const size_t len, 14 | const int32_t flags, 15 | struct sockaddr* dest_addr, 16 | const socklen_t addr_len) 17 | { 18 | #ifdef __APPLE__ 19 | return darwin_sendto(sock_fd, buf, len, flags, dest_addr, addr_len); 20 | #else 21 | return linux_x8664_sendto(sock_fd, buf, len, flags, dest_addr, addr_len); 22 | #endif 23 | } 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /src/arch/darwin/listen.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_DARWIN_LISTEN_H_ 2 | #define NOSTR_DARWIN_LISTEN_H_ 3 | 4 | #include 5 | 6 | #include "../../util/types.h" 7 | 8 | static inline int32_t darwin_socket(const int32_t domain, const int32_t type, const int32_t protocol) 9 | { 10 | return socket(domain, type, protocol); 11 | } 12 | 13 | static inline int32_t darwin_bind(const int32_t sockfd, const struct sockaddr* addr, const socklen_t addrlen) 14 | { 15 | return bind(sockfd, addr, addrlen); 16 | } 17 | 18 | static inline int32_t darwin_listen(const int32_t sockfd, const int32_t backlog) 19 | { 20 | return listen(sockfd, backlog); 21 | } 22 | 23 | #endif 24 | -------------------------------------------------------------------------------- /src/arch/darwin/optimize_socket.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_DARWIN_OPTIMIZE_SOCKET_H_ 2 | #define NOSTR_DARWIN_OPTIMIZE_SOCKET_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "../../util/types.h" 9 | 10 | static inline int32_t darwin_fcntl(const int32_t fd, const int32_t cmd, const int64_t arg) 11 | { 12 | return fcntl(fd, cmd, arg); 13 | } 14 | 15 | static inline int32_t darwin_setsockopt( 16 | const int32_t sock_fd, 17 | const int32_t level, 18 | const int32_t optname, 19 | const void* optval, 20 | const socklen_t optlen) 21 | { 22 | return setsockopt(sock_fd, level, optname, optval, optlen); 23 | } 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /src/arch/linux/x86_64/recv.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_LINUX_X86_64_RECV_H_ 2 | #define NOSTR_LINUX_X86_64_RECV_H_ 3 | 4 | #include "../../../util/types.h" 5 | #include "../errno.h" 6 | #include "../sockaddr.h" 7 | #include "./asm.h" 8 | 9 | static inline ssize_t linux_x8664_recvfrom( 10 | const int32_t sock_fd, 11 | void* buf, 12 | const size_t len, 13 | const int32_t flags, 14 | struct sockaddr* src_addr, 15 | socklen_t* addrlen) 16 | { 17 | int32_t ret = linux_x8664_asm_syscall6( 18 | __NR_recvfrom, 19 | sock_fd, 20 | buf, 21 | len, 22 | flags, 23 | src_addr, 24 | addrlen); 25 | 26 | SYSCALL_SIZE_EARLY_RETURN(ret); 27 | return ret; 28 | } 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /tests/Makefile: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # File : Malefile 3 | # Description : 4 | # Author : hakkadaikon 5 | ############################################################################### 6 | 7 | #------------------------------------------------------------------------------ 8 | # Build options 9 | #------------------------------------------------------------------------------ 10 | .PHONY: test clean format 11 | 12 | clean: 13 | rm -rf build 14 | 15 | # format (use clang) 16 | format: 17 | @clang-format -i $(shell find . -name '*.[ch]pp' -o -name '*.[ch]') 18 | 19 | test: clean 20 | cmake -S . -B build && cmake --build build && cd build && ctest 21 | -------------------------------------------------------------------------------- /src/arch/linux/x86_64/send.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_LINUX_X86_64_SEND_H_ 2 | #define NOSTR_LINUX_X86_64_SEND_H_ 3 | 4 | #include "../../../util/types.h" 5 | #include "../errno.h" 6 | #include "../sockaddr.h" 7 | #include "./asm.h" 8 | 9 | static inline ssize_t linux_x8664_sendto( 10 | const int32_t sock_fd, 11 | const void* buf, 12 | const size_t len, 13 | const int32_t flags, 14 | struct sockaddr* dest_addr, 15 | const socklen_t addrlen) 16 | { 17 | int32_t ret = linux_x8664_asm_syscall6( 18 | __NR_sendto, 19 | sock_fd, 20 | buf, 21 | len, 22 | flags, 23 | dest_addr, 24 | addrlen); 25 | 26 | SYSCALL_SIZE_EARLY_RETURN(ret); 27 | return (ssize_t)ret; 28 | } 29 | 30 | #endif 31 | -------------------------------------------------------------------------------- /src/arch/sigaction.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_INTERNAL_SIGACTION_H_ 2 | #define NOSTR_INTERNAL_SIGACTION_H_ 3 | 4 | #ifdef __APPLE__ 5 | #include "darwin/sigaction.h" 6 | #else 7 | #include "linux/x86_64/sigaction.h" 8 | #endif 9 | 10 | static inline int32_t internal_sigaction(const int32_t signum, struct sigaction* act, struct sigaction* oldact) 11 | { 12 | #ifdef __APPLE__ 13 | return darwin_sigaction(signum, act, oldact); 14 | #else 15 | return linux_x8664_sigaction(signum, act, oldact); 16 | #endif 17 | } 18 | 19 | static inline int32_t internal_sigemptyset(sigset_t* set) 20 | { 21 | #ifdef __APPLE__ 22 | return darwin_sigemptyset(set); 23 | #else 24 | return linux_x8664_sigemptyset(set); 25 | #endif 26 | } 27 | 28 | #endif 29 | -------------------------------------------------------------------------------- /src/arch/epoll.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_INTERNAL_SEND_H_ 2 | #define NOSTR_INTERNAL_SEND_H_ 3 | 4 | #include "../util/types.h" 5 | #include "./linux/epoll.h" 6 | #include "./linux/x86_64/epoll.h" 7 | 8 | static inline int32_t internal_epoll_ctl( 9 | const int32_t epoll_fd, 10 | const int32_t op, 11 | const int32_t fd, 12 | PWebSocketEpollEvent event) 13 | { 14 | return linux_x8664_epoll_ctl(epoll_fd, op, fd, event); 15 | } 16 | 17 | static inline int32_t internal_epoll_create1(const int32_t flags) 18 | { 19 | return linux_x8664_epoll_create1(flags); 20 | } 21 | 22 | static inline int32_t internal_epoll_wait( 23 | const int32_t epfd, 24 | PWebSocketEpollEvent events, 25 | const int32_t maxevents, 26 | const int32_t timeout) 27 | { 28 | return linux_x8664_epoll_wait(epfd, events, maxevents, timeout); 29 | } 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /src/util/signal.c: -------------------------------------------------------------------------------- 1 | #include "./signal.h" 2 | 3 | #include "../arch/sigaction.h" 4 | #include "allocator.h" 5 | 6 | static bool rise_signal = false; 7 | 8 | static void signal_handler(int32_t signum); 9 | bool is_rise_signal(); 10 | bool signal_init(); 11 | 12 | bool signal_init() 13 | { 14 | struct sigaction sa; 15 | websocket_memset_s(&sa, sizeof(sa), 0x00, sizeof(sa)); 16 | sa.sa_handler = signal_handler; 17 | internal_sigemptyset(&sa.sa_mask); 18 | 19 | int32_t signals[] = {SIGHUP, SIGINT, SIGTERM}; 20 | 21 | for (int32_t i = 0; i < (sizeof(signals) / sizeof(signals[0])); i++) { 22 | if (internal_sigaction(signals[i], &sa, (void*)0) == -1) { 23 | return false; 24 | } 25 | } 26 | 27 | return true; 28 | } 29 | 30 | static void signal_handler(int32_t signum) 31 | { 32 | rise_signal = true; 33 | } 34 | 35 | bool is_rise_signal() 36 | { 37 | return rise_signal; 38 | } 39 | -------------------------------------------------------------------------------- /src/arch/optimize_socket.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_INTERNAL_OPTIMIZE_SOCKET_H_ 2 | #define NOSTR_INTERNAL_OPTIMIZE_SOCKET_H_ 3 | 4 | #ifdef __APPLE__ 5 | #include "darwin/optimize_socket.h" 6 | #else 7 | #include "linux/x86_64/optimize_socket.h" 8 | #endif 9 | 10 | static inline int32_t internal_fcntl(const int32_t fd, const int32_t cmd, const int64_t arg) 11 | { 12 | #ifdef __APPLE__ 13 | return darwin_fcntl(fd, cmd, arg); 14 | #else 15 | return linux_x8664_fcntl(fd, cmd, arg); 16 | #endif 17 | } 18 | 19 | static inline int32_t internal_setsockopt( 20 | const int32_t sock_fd, 21 | const int32_t level, 22 | const int32_t optname, 23 | const void* optval, 24 | const socklen_t optlen) 25 | { 26 | #ifdef __APPLE__ 27 | return darwin_setsockopt(sock_fd, level, optname, optval, optlen); 28 | #else 29 | return linux_x8664_setsockopt(sock_fd, level, optname, optval, optlen); 30 | #endif 31 | } 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(websocket-test) 3 | 4 | set(CMAKE_CXX_STANDARD 20) 5 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 6 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 7 | 8 | include(FetchContent) 9 | include(GoogleTest) 10 | 11 | FetchContent_Declare( 12 | googletest 13 | DOWNLOAD_EXTRACT_TIMESTAMP ON 14 | URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip 15 | ) 16 | 17 | FetchContent_MakeAvailable(googletest) 18 | 19 | enable_testing() 20 | 21 | add_executable( 22 | websocket-test 23 | crypto/base64.cpp 24 | crypto/sha1.cpp 25 | http/request.cpp 26 | ../src/http/http.c 27 | ../src/crypto/sha1.c 28 | ) 29 | 30 | target_include_directories( 31 | websocket-test PRIVATE 32 | ${CMAKE_CURRENT_LIST_DIR}/../src 33 | ) 34 | 35 | target_link_libraries( 36 | websocket-test 37 | GTest::gtest_main 38 | ) 39 | 40 | gtest_discover_tests(websocket-test) 41 | -------------------------------------------------------------------------------- /src/arch/darwin/memory.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_DARWIN_MEMORY_H_ 2 | #define NOSTR_DARWIN_MEMORY_H_ 3 | 4 | #include 5 | 6 | #include "../../util/types.h" 7 | 8 | static inline void* darwin_memcpy(void* dest, const void* src, size_t size) 9 | { 10 | return memcpy(dest, src, size); 11 | } 12 | 13 | static inline void* darwin_memset(void* s, const int32_t c, const size_t size) 14 | { 15 | return memset(s, c, size); 16 | } 17 | 18 | /* 19 | * memset_s: Secure memory set function (wrapper for memset) 20 | * 21 | * @s: Pointer to the memory area to be set 22 | * @smax: Size of the memory area in bytes 23 | * @c: Value to set (passed as int, but treated as an unsigned char) 24 | * @n: Number of bytes to set 25 | * 26 | * Note: A compiler barrier is inserted to ensure that the memset call is not optimized away. 27 | */ 28 | static inline int32_t darwin_memset_s(void* s, const size_t smax, const int32_t c, const size_t n) 29 | { 30 | return memset_s(s, smax, c, n); 31 | } 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /src/arch/linux/x86_64/optimize_socket.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_LINUX_X86_64_OPTIMIZE_SOCKET_H_ 2 | #define NOSTR_LINUX_X86_64_OPTIMIZE_SOCKET_H_ 3 | 4 | #include "../errno.h" 5 | #include "../fcntl.h" 6 | #include "../sockaddr.h" 7 | #include "../sockoption.h" 8 | #include "./asm.h" 9 | 10 | static inline int32_t linux_x8664_fcntl(const int32_t fd, const int32_t cmd, const int64_t arg) 11 | { 12 | int32_t ret = linux_x8664_asm_syscall3( 13 | __NR_fcntl, 14 | fd, 15 | cmd, 16 | arg); 17 | 18 | SYSCALL_EARLY_RETURN(ret); 19 | return ret; 20 | } 21 | 22 | static inline int32_t linux_x8664_setsockopt( 23 | const int32_t sockfd, 24 | const int32_t level, 25 | const int32_t optname, 26 | const void* optval, 27 | const socklen_t optlen) 28 | { 29 | int32_t ret = linux_x8664_asm_syscall5( 30 | __NR_setsockopt, 31 | sockfd, 32 | level, 33 | optname, 34 | optval, 35 | optlen); 36 | 37 | SYSCALL_EARLY_RETURN(ret); 38 | return ret; 39 | } 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /src/websocket/socket/send.c: -------------------------------------------------------------------------------- 1 | #include "../../arch/send.h" 2 | 3 | #include "../websocket_local.h" 4 | 5 | static int32_t get_send_err(ssize_t bytes_send); 6 | 7 | int32_t websocket_send(const int32_t sock_fd, const size_t buffer_size, const char* restrict buffer) 8 | { 9 | log_debug("WebSocket server send\n"); 10 | 11 | int32_t errcode; 12 | ssize_t bytes_send = internal_sendto(sock_fd, buffer, buffer_size, 0, NULL, 0); 13 | if ((errcode = get_send_err(bytes_send)) != WEBSOCKET_ERRORCODE_NONE) { 14 | return errcode; 15 | } 16 | 17 | return WEBSOCKET_ERRORCODE_NONE; 18 | } 19 | 20 | static int32_t get_send_err(ssize_t bytes_send) 21 | { 22 | if (bytes_send == WEBSOCKET_SYSCALL_ERROR) { 23 | if (errno == EAGAIN || errno == EWOULDBLOCK) { 24 | return WEBSOCKET_ERRORCODE_CONTINUABLE_ERROR; 25 | } 26 | 27 | return WEBSOCKET_ERRORCODE_SOCKET_CLOSE_ERROR; 28 | str_error("Failed to send(). reason : ", strerror(errno)); 29 | } 30 | 31 | return WEBSOCKET_ERRORCODE_NONE; 32 | } 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | ./lib 21 | *.lib 22 | *.a 23 | *.la 24 | *.lo 25 | 26 | # Shared objects (inc. Windows DLLs) 27 | *.dll 28 | *.so 29 | *.so.* 30 | *.dylib 31 | 32 | # Executables 33 | *.exe 34 | *.out 35 | *.app 36 | *.i*86 37 | *.x86_64 38 | *.hex 39 | 40 | # Debug files 41 | *.dSYM/ 42 | *.su 43 | *.idb 44 | *.pdb 45 | 46 | # Kernel Module Compile Results 47 | *.mod* 48 | *.cmd 49 | .tmp_versions/ 50 | modules.order 51 | Module.symvers 52 | Mkfile.old 53 | dkms.conf 54 | 55 | # Perf 56 | perf.data 57 | perf.data.old 58 | 59 | # Examples 60 | examples/echoback/bin/* 61 | examples/echoback/obj/* 62 | 63 | # Log 64 | *.txt 65 | 66 | # Other 67 | tests/build 68 | build 69 | test 70 | ws-server 71 | result 72 | tmp/ 73 | .cache/ 74 | native/src 75 | native/lib 76 | native/obj 77 | 78 | !CMakeLists.txt 79 | !.gitignore 80 | -------------------------------------------------------------------------------- /src/arch/linux/x86_64/listen.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_LINUX_X86_64_LISTEN_H_ 2 | #define NOSTR_LINUX_X86_64_LISTEN_H_ 3 | 4 | #include "./optimize_socket.h" 5 | 6 | static inline int32_t linux_x8664_socket(const int32_t domain, const int32_t type, const int32_t protocol) 7 | { 8 | int32_t ret = linux_x8664_asm_syscall3( 9 | __NR_socket, 10 | domain, 11 | type, 12 | protocol); 13 | 14 | SYSCALL_EARLY_RETURN(ret); 15 | return ret; 16 | } 17 | 18 | static inline int32_t linux_x8664_bind(const int32_t sockfd, const struct sockaddr* addr, const socklen_t addrlen) 19 | { 20 | int32_t ret = linux_x8664_asm_syscall3( 21 | __NR_bind, 22 | sockfd, 23 | addr, 24 | addrlen); 25 | 26 | SYSCALL_EARLY_RETURN(ret); 27 | return ret; 28 | } 29 | 30 | static inline int32_t linux_x8664_listen(const int32_t sockfd, const int32_t backlog) 31 | { 32 | int32_t ret = linux_x8664_asm_syscall2( 33 | __NR_listen, 34 | sockfd, 35 | backlog); 36 | 37 | SYSCALL_EARLY_RETURN(ret); 38 | return ret; 39 | } 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /src/arch/linux/x86_64/sigaction.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_LINUX_X86_64_SIGACTION_H_ 2 | #define NOSTR_LINUX_X86_64_SIGACTION_H_ 3 | 4 | #include "../../../util/allocator.h" 5 | #include "../../../util/types.h" 6 | #include "../errno.h" 7 | #include "../sigaction_def.h" 8 | #include "asm.h" 9 | 10 | extern void linux_x8664_restore_rt(void); 11 | 12 | static inline int32_t linux_x8664_sigaction(const int32_t signum, struct sigaction* act, struct sigaction* oldact) 13 | { 14 | act->sa_flags |= SA_RESTORER; 15 | act->sa_restorer = &linux_x8664_restore_rt; 16 | 17 | int32_t ret = linux_x8664_asm_syscall4( 18 | __NR_rt_sigaction, 19 | signum, 20 | act, 21 | oldact, 22 | sizeof(sigset_t)); 23 | 24 | // var_dump_local(1, "sigaction ret:", ret); 25 | // var_dump_local(1, "signum:", signum); 26 | SYSCALL_EARLY_RETURN(ret); 27 | return ret; 28 | } 29 | 30 | static inline int32_t linux_x8664_sigemptyset(sigset_t* set) 31 | { 32 | websocket_memset_s(set, sizeof(sigset_t), 0x00, sizeof(sigset_t)); 33 | return 1; 34 | } 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /src/arch/listen.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_INTERNAL_LISTEN_H_ 2 | #define NOSTR_INTERNAL_LISTEN_H_ 3 | 4 | #include "../util/types.h" 5 | #ifdef __APPLE__ 6 | #include "darwin/listen.h" 7 | #else 8 | #include "linux/x86_64/listen.h" 9 | #endif 10 | 11 | static inline int32_t internal_socket(const int32_t domain, const int32_t type, const int32_t protocol) 12 | { 13 | #ifdef __APPLE__ 14 | return darwin_socket(domain, type, protocol); 15 | #else 16 | return linux_x8664_socket(domain, type, protocol); 17 | #endif 18 | } 19 | 20 | static inline int32_t internal_bind(const int32_t sockfd, const struct sockaddr* addr, const socklen_t addrlen) 21 | { 22 | #ifdef __APPLE__ 23 | return darwin_bind(sockfd, addr, addrlen); 24 | #else 25 | return linux_x8664_bind(sockfd, addr, addrlen); 26 | #endif 27 | } 28 | 29 | static inline int32_t internal_listen(const int32_t sockfd, const int32_t backlog) 30 | { 31 | #ifdef __APPLE__ 32 | return darwin_listen(sockfd, backlog); 33 | #else 34 | return linux_x8664_listen(sockfd, backlog); 35 | #endif 36 | } 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /src/http/http.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_HTTP_H_ 2 | #define NOSTR_HTTP_H_ 3 | 4 | #include "../util/allocator.h" 5 | #include "../util/types.h" 6 | 7 | enum { 8 | HTTP_METHOD_CAPACITY = 16, 9 | HTTP_TARGET_CAPACITY = 256, 10 | HTTP_VERSION_CAPACITY = 16, 11 | HTTP_HEADER_KEY_CAPACITY = 32, 12 | HTTP_HEADER_VALUE_CAPACITY = 256, 13 | HTTP_HEADER_CAPACITY = 32 14 | }; 15 | 16 | typedef struct { 17 | char method[HTTP_METHOD_CAPACITY]; 18 | char target[HTTP_TARGET_CAPACITY]; 19 | char http_version[HTTP_VERSION_CAPACITY]; 20 | } HTTPRequestLine, *PHTTPRequestLine; 21 | 22 | typedef struct { 23 | char key[HTTP_HEADER_KEY_CAPACITY]; 24 | char value[HTTP_HEADER_VALUE_CAPACITY]; 25 | } HTTPRequestHeaderLine, *PHTTPRequestHeaderLine; 26 | 27 | typedef struct { 28 | HTTPRequestLine line; 29 | HTTPRequestHeaderLine headers[HTTP_HEADER_CAPACITY]; 30 | size_t header_size; 31 | } HTTPRequest, *PHTTPRequest; 32 | 33 | bool extract_http_request( 34 | const char* buffer, 35 | const size_t buffer_size, 36 | PHTTPRequest request); 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /shell/musl_build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd $(dirname $0) 4 | CURRENT_PATH=$(realpath .) 5 | 6 | ECHOBACK_SERVER_PATH=../examples/echoback 7 | 8 | curl -LO https://musl.cc/x86_64-linux-musl-native.tgz 9 | tar xfz x86_64-linux-musl-native.tgz 10 | 11 | rm -rf ../build 12 | make clean -C ${ECHOBACK_SERVER_PATH} 1>/dev/null 13 | 14 | export LD=${CURRENT_PATH}/x86_64-linux-musl-native/bin/ld 15 | export LIBRARY_PATH=${CURRENT_PATH}/x86_64-linux-musl-native/lib:$LIBRARY_PATH 16 | export LD_LIBRARY_PATH=${CURRENT_PATH}/x86_64-linux-musl-native/lib:$LD_LIBRARY_PATH 17 | 18 | cmake -S .. -B ../build \ 19 | -DCMAKE_BUILD_TYPE=Release \ 20 | -DCMAKE_C_COMPILER=${CURRENT_PATH}/x86_64-linux-musl-native/bin/gcc \ 21 | -DCMAKE_CXX_COMPILER=${CURRENT_PATH}/x86_64-linux-musl-native/bin/g++ 22 | cmake --build ../build 23 | 24 | BUILD=release \ 25 | CC=${CURRENT_PATH}/x86_64-linux-musl-native/bin/gcc \ 26 | CXX=${CURRENT_PATH}/x86_64-linux-musl-native/bin/g++ \ 27 | LD=${CURRENT_PATH}/x86_64-linux-musl-native/bin/ld \ 28 | make -C ${ECHOBACK_SERVER_PATH} 1>/dev/null 29 | 30 | rm -f x86_64-linux-musl-native.tgz 31 | rm -rf x86_64-linux-musl-native/ 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Hakkadaikon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/websocket/server/accept/epoll_accept.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_SERVER_LOOP_EPOLL_ACCEPT_H_ 2 | #define NOSTR_SERVER_LOOP_EPOLL_ACCEPT_H_ 3 | 4 | #include "../../websocket_local.h" 5 | #include "accept_handle.h" 6 | 7 | static inline int32_t epoll_accept( 8 | const PWebSocketEpollLoopArgs epoll_args, 9 | const int32_t server_sock, 10 | PWebSocketRawBuffer buffer, 11 | const PWebSocketEpollEvent register_event, 12 | PWebSocketCallbacks callbacks) 13 | { 14 | log_debug("rise error check...\n"); 15 | int32_t code = websocket_epoll_rise_error(epoll_args->event); 16 | if (code != WEBSOCKET_ERRORCODE_NONE) { 17 | return code; 18 | } 19 | 20 | log_debug("rise input check...\n"); 21 | code = websocket_epoll_rise_input(epoll_args->event); 22 | if (code != WEBSOCKET_ERRORCODE_NONE) { 23 | return code; 24 | } 25 | 26 | log_debug("accept handle\n"); 27 | if (!accept_handle( 28 | epoll_args->epoll_fd, 29 | server_sock, 30 | buffer, 31 | register_event, 32 | callbacks)) { 33 | return WEBSOCKET_ERRORCODE_CONTINUABLE_ERROR; 34 | } 35 | 36 | return WEBSOCKET_ERRORCODE_NONE; 37 | } 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /src/arch/linux/epoll.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_LINUX_EPOLL_H_ 2 | #define NOSTR_LINUX_EPOLL_H_ 3 | 4 | #include "../../util/types.h" 5 | 6 | #define EPOLL_CTL_ADD 1 // Add a file descriptor to the interface. 7 | #define EPOLL_CTL_DEL 2 // Remove a file descriptor from the interface. 8 | #define EPOLL_CTL_MOD 3 // Change file descriptor epoll_event structure. 9 | 10 | enum EPOLL_EVENTS { 11 | EPOLLIN = 0x001, 12 | EPOLLPRI = 0x002, 13 | EPOLLOUT = 0x004, 14 | EPOLLRDNORM = 0x040, 15 | EPOLLRDBAND = 0x080, 16 | EPOLLWRNORM = 0x100, 17 | EPOLLWRBAND = 0x200, 18 | EPOLLMSG = 0x400, 19 | EPOLLERR = 0x008, 20 | EPOLLHUP = 0x010, 21 | EPOLLRDHUP = 0x2000, 22 | EPOLLEXCLUSIVE = 1u << 28, 23 | EPOLLWAKEUP = 1u << 29, 24 | EPOLLONESHOT = 1u << 30, 25 | EPOLLET = 1u << 31 26 | }; 27 | 28 | typedef union { 29 | void* ptr; 30 | int32_t fd; 31 | uint32_t u32; 32 | uint64_t u64; 33 | } WebSocketEpollData, *PWebSocketEpollData; 34 | 35 | typedef struct 36 | { 37 | uint32_t events; 38 | int32_t dummy; 39 | WebSocketEpollData data; 40 | } WebSocketEpollEvent, *PWebSocketEpollEvent; 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /src/arch/linux/x86_64/epoll.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_LINUX_X86_64_EPOLL_H_ 2 | #define NOSTR_LINUX_X86_64_EPOLL_H_ 3 | 4 | #include "../epoll.h" 5 | #include "../errno.h" 6 | #include "./asm.h" 7 | 8 | static inline int32_t linux_x8664_epoll_ctl( 9 | const int32_t epoll_fd, 10 | const int32_t op, 11 | const int32_t fd, 12 | PWebSocketEpollEvent event) 13 | { 14 | int32_t ret = linux_x8664_asm_syscall4( 15 | __NR_epoll_ctl, 16 | epoll_fd, 17 | op, 18 | fd, 19 | event); 20 | 21 | SYSCALL_EARLY_RETURN(ret); 22 | return ret; 23 | } 24 | 25 | static inline int32_t linux_x8664_epoll_create1(const int32_t flags) 26 | { 27 | int32_t ret = linux_x8664_asm_syscall1( 28 | __NR_epoll_create1, 29 | flags); 30 | 31 | SYSCALL_EARLY_RETURN(ret); 32 | return ret; 33 | } 34 | 35 | static inline int32_t linux_x8664_epoll_wait( 36 | const int32_t epfd, 37 | PWebSocketEpollEvent events, 38 | const int32_t maxevents, 39 | const int32_t timeout) 40 | { 41 | int32_t ret = linux_x8664_asm_syscall4( 42 | __NR_epoll_wait, 43 | epfd, 44 | events, 45 | maxevents, 46 | timeout); 47 | 48 | SYSCALL_EARLY_RETURN(ret); 49 | return ret; 50 | } 51 | 52 | #endif 53 | -------------------------------------------------------------------------------- /src/websocket/server/receive/receive_handle.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_SERVER_LOOP_RECEIVE_HANDLE_H_ 2 | #define NOSTR_SERVER_LOOP_RECEIVE_HANDLE_H_ 3 | 4 | #include "../../../util/allocator.h" 5 | #include "../../websocket_local.h" 6 | #include "opcode_handle.h" 7 | 8 | static inline int32_t receive_handle( 9 | const int32_t client_sock, 10 | const size_t read_size, 11 | PWebSocketRawBuffer buffer, 12 | PWebSocketCallbacks callbacks) 13 | { 14 | WebSocketFrame frame; 15 | websocket_memset(&frame, 0x00, sizeof(frame)); 16 | 17 | frame.payload = (char*)websocket_alloc(read_size); 18 | 19 | int32_t rtn = 0; 20 | 21 | if (!parse_websocket_frame(buffer->request, read_size, &frame)) { 22 | rtn = WEBSOCKET_ERRORCODE_CONTINUABLE_ERROR; 23 | goto FINALIZE; 24 | } 25 | 26 | websocket_frame_dump(&frame); 27 | 28 | rtn = opcode_handle(client_sock, buffer, callbacks, &frame); 29 | if (rtn != WEBSOCKET_ERRORCODE_NONE) { 30 | goto FINALIZE; 31 | } 32 | 33 | if (is_rise_signal()) { 34 | var_info("rise signal. sock : ", client_sock); 35 | rtn = WEBSOCKET_ERRORCODE_FATAL_ERROR; 36 | goto FINALIZE; 37 | } 38 | 39 | FINALIZE: 40 | websocket_free(frame.payload); 41 | return rtn; 42 | } 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /src/websocket/internal_log.c: -------------------------------------------------------------------------------- 1 | #ifndef __APPLE__ 2 | #include "../arch/epoll.h" 3 | #endif 4 | 5 | #include "websocket_local.h" 6 | 7 | void websocket_frame_dump(PWebSocketFrame restrict frame) 8 | { 9 | var_debug("fin : ", frame->fin); 10 | var_debug("rsv1 : ", frame->rsv1); 11 | var_debug("rsv2 : ", frame->rsv2); 12 | var_debug("rsv3 : ", frame->rsv3); 13 | var_debug("opcode : ", frame->opcode); 14 | var_debug("mask : ", frame->mask); 15 | var_debug("payload_len : ", frame->payload_len); 16 | var_debug("ext_payload_len : ", frame->ext_payload_len); 17 | str_debug("payload : ", frame->payload); 18 | } 19 | 20 | void websocket_epoll_event_dump(const int32_t events) 21 | { 22 | #ifndef __APPLE__ 23 | log_debug("epoll events: "); 24 | 25 | if (events & EPOLLIN) { 26 | log_debug("EPOLLIN "); 27 | } 28 | 29 | if (events & EPOLLERR) { 30 | log_debug("EPOLLIERR "); 31 | } 32 | 33 | if (events & EPOLLHUP) { 34 | log_debug("EPOLLHUP "); 35 | } 36 | 37 | if (events & EPOLLOUT) { 38 | log_debug("EPOLLOUT "); 39 | } 40 | 41 | if (events & EPOLLRDHUP) { 42 | log_debug("EPOLLRDHUP "); 43 | } 44 | 45 | log_debug("\n"); 46 | #endif 47 | } 48 | -------------------------------------------------------------------------------- /src/arch/memory.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_INTERNAL_MEMORY_H_ 2 | #define NOSTR_INTERNAL_MEMORY_H_ 3 | 4 | #ifdef __APPLE__ 5 | #include "darwin/memory.h" 6 | #else 7 | #include "linux/x86_64/memory.h" 8 | 9 | #endif 10 | 11 | static inline void* internal_memcpy(void* dest, const void* src, size_t size) 12 | { 13 | #ifdef __APPLE__ 14 | return darwin_memcpy(dest, src, size); 15 | #else 16 | return linux_x8664_memcpy(dest, src, size); 17 | #endif 18 | } 19 | 20 | static inline void* internal_memset(void* s, const int32_t c, const size_t size) 21 | { 22 | #ifdef __APPLE__ 23 | return darwin_memset(s, c, size); 24 | #else 25 | return linux_x8664_memset(s, c, size); 26 | #endif 27 | } 28 | 29 | /* 30 | * memset_s: Secure memory set function (wrapper for memset) 31 | * 32 | * @s: Pointer to the memory area to be set 33 | * @smax: Size of the memory area in bytes 34 | * @c: Value to set (passed as int, but treated as an unsigned char) 35 | * @n: Number of bytes to set 36 | * 37 | * Note: A compiler barrier is inserted to ensure that the memset call is not optimized away. 38 | */ 39 | static inline int32_t internal_memset_s(void* s, const size_t smax, const int32_t c, const size_t n) 40 | { 41 | #ifdef __APPLE__ 42 | return darwin_memset_s(s, smax, c, n); 43 | #else 44 | return linux_x8664_memset_s(s, smax, c, n); 45 | #endif 46 | } 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /src/websocket/server/receive/epoll_receive.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_SERVER_LOOP_EPOLL_RECEIVE_H_ 2 | #define NOSTR_SERVER_LOOP_EPOLL_RECEIVE_H_ 3 | 4 | #include "../../../util/allocator.h" 5 | #include "../../websocket_local.h" 6 | #include "receive_handle.h" 7 | 8 | static inline int32_t epoll_receive( 9 | const PWebSocketEpollLoopArgs epoll_args, 10 | PWebSocketRawBuffer buffer, 11 | PWebSocketCallbacks callbacks) 12 | { 13 | int32_t code = websocket_epoll_rise_error(epoll_args->event); 14 | if (code != WEBSOCKET_ERRORCODE_NONE) { 15 | return code; 16 | } 17 | 18 | code = websocket_epoll_rise_input(epoll_args->event); 19 | if (code != WEBSOCKET_ERRORCODE_NONE) { 20 | return code; 21 | } 22 | 23 | int32_t client_sock = websocket_epoll_getfd(epoll_args->event); 24 | 25 | ssize_t read_size = websocket_recv(client_sock, buffer->capacity, buffer->request); 26 | 27 | if (read_size <= 0) { 28 | if (read_size == WEBSOCKET_ERRORCODE_FATAL_ERROR || read_size == WEBSOCKET_ERRORCODE_SOCKET_CLOSE_ERROR) { 29 | return read_size; 30 | } 31 | 32 | return WEBSOCKET_ERRORCODE_CONTINUABLE_ERROR; 33 | } 34 | 35 | int32_t ret = receive_handle(client_sock, read_size, buffer, callbacks); 36 | 37 | if (ret == WEBSOCKET_ERRORCODE_FATAL_ERROR || ret == WEBSOCKET_ERRORCODE_SOCKET_CLOSE_ERROR) { 38 | return ret; 39 | } 40 | 41 | return WEBSOCKET_ERRORCODE_NONE; 42 | } 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /src/websocket/socket/accept.c: -------------------------------------------------------------------------------- 1 | #include "../../arch/accept.h" 2 | 3 | #include "../websocket_local.h" 4 | #include "./optimize_socket.h" 5 | 6 | static int32_t get_accept_err(const int32_t client_sock); 7 | 8 | int32_t websocket_accept(const int32_t sock_fd) 9 | { 10 | int32_t errcode; 11 | struct sockaddr_in client_addr; 12 | socklen_t addr_len = sizeof(client_addr); 13 | 14 | if (is_rise_signal()) { 15 | log_info("A signal was raised during accept(). The system will abort processing.\n"); 16 | return WEBSOCKET_ERRORCODE_FATAL_ERROR; 17 | } 18 | 19 | log_debug("accept...\n"); 20 | int client_sock = internal_accept(sock_fd, (struct sockaddr*)&client_addr, &addr_len, 0); 21 | if ((errcode = get_accept_err(client_sock)) != WEBSOCKET_ERRORCODE_NONE) { 22 | return errcode; 23 | } 24 | 25 | if (optimize_client_socket(client_sock) == WEBSOCKET_ERRORCODE_FATAL_ERROR) { 26 | websocket_close(client_sock); 27 | return WEBSOCKET_ERRORCODE_CONTINUABLE_ERROR; 28 | } 29 | 30 | return client_sock; 31 | } 32 | 33 | static int32_t get_accept_err(const int32_t client_sock) 34 | { 35 | if (client_sock < 0) { 36 | if (errno == EINTR || errno == EAGAIN) { 37 | return WEBSOCKET_ERRORCODE_CONTINUABLE_ERROR; 38 | } 39 | 40 | str_error("accept() failed. reason : ", strerror(errno)); 41 | return WEBSOCKET_ERRORCODE_FATAL_ERROR; 42 | } 43 | 44 | return WEBSOCKET_ERRORCODE_NONE; 45 | } 46 | -------------------------------------------------------------------------------- /shell/dump_kernel.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # General gigabit tuning 4 | # see: https://github.com/tranchiendang/linux-websocket-tuning/blob/7aae27c9a42b86b868a82b5de649f421f70480ea/README.md 5 | ################################################### 6 | # sysctl -w net.core.somaxconn=16384 7 | # sysctl -w net.core.rmem_max=16777216 8 | # sysctl -w net.core.wmem_max=16777216 9 | # sysctl -w net.ipv4.tcp_rmem="4096 87380 16777216" 10 | # sysctl -w net.ipv4.tcp_wmem="4096 65536 16777216" 11 | # sysctl -w net.ipv4.tcp_syncookies=1 12 | # sysctl -w net.ipv4.tcp_mem="50576 64768 98152" 13 | # sysctl -w net.core.netdev_max_backlog=2500 14 | # sysctl -w net.ipv4.tcp_fastopen=3 15 | ################################################### 16 | 17 | # Default by ubuntu 20.04 18 | ################################################### 19 | # sysctl -w net.core.somaxconn=4096 20 | # sysctl -w net.core.rmem_max=212992 21 | # sysctl -w net.core.wmem_max=212992 22 | # sysctl -w net.ipv4.tcp_rmem="4096 131072 6291456" 23 | # sysctl -w net.ipv4.tcp_wmem="4096 16384 4194304" 24 | # sysctl -w net.ipv4.tcp_syncookies=1 25 | # sysctl -w net.ipv4.tcp_mem="9645 12862 19290" 26 | # sysctl -w net.core.netdev_max_backlog=1000 27 | ################################################### 28 | 29 | sysctl net.core.somaxconn 30 | sysctl net.core.rmem_max 31 | sysctl net.core.wmem_max 32 | sysctl net.ipv4.tcp_rmem 33 | sysctl net.ipv4.tcp_wmem 34 | sysctl net.ipv4.tcp_syncookies 35 | sysctl net.ipv4.tcp_mem 36 | sysctl net.core.netdev_max_backlog 37 | -------------------------------------------------------------------------------- /src/util/allocator.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_ALLOCATOR_H_ 2 | #define NOSTR_ALLOCATOR_H_ 3 | 4 | #include "./types.h" 5 | #ifdef __APPLE__ 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include // for memset 11 | #else 12 | #include "../arch/linux/errno.h" 13 | // extern void* alloca(size_t __size); 14 | #undef alloca 15 | #undef __alloca 16 | #define alloca(size) __alloca(size) 17 | #define __alloca(size) __builtin_alloca(size) 18 | #endif 19 | #include "../arch/memory.h" 20 | 21 | #define ALLOCATE_STACK 22 | // #define ALLOCATE_HEAP 23 | 24 | #if defined(ALLOCATE_STACK) 25 | #define websocket_alloc(size) alloca(size) 26 | #define websocket_free(addr) 27 | #undef ALLOCATE_STACK 28 | #elif defined(ALLOCATE_HEAP) 29 | #define websocket_alloc(size) malloc(size) 30 | #define websocket_free(addr) free(addr) 31 | #undef ALLOCATE_HEAP 32 | #endif 33 | 34 | /** 35 | * websocket_memcpy 36 | */ 37 | static inline void* websocket_memcpy(void* dest, const void* src, size_t size) 38 | { 39 | return internal_memcpy(dest, src, size); 40 | } 41 | 42 | /* 43 | * websocket_memset 44 | */ 45 | static inline void* websocket_memset(void* s, const int32_t c, const size_t size) 46 | { 47 | return internal_memset(s, c, size); 48 | } 49 | 50 | /* 51 | * websocket_memset_s 52 | */ 53 | static inline int32_t websocket_memset_s(void* s, const size_t smax, const int32_t c, const size_t n) 54 | { 55 | return internal_memset_s(s, smax, c, n); 56 | } 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /src/websocket/socket/recv.c: -------------------------------------------------------------------------------- 1 | #include "../../arch/recv.h" 2 | 3 | #include "../websocket_local.h" 4 | 5 | #ifndef __APPLE__ 6 | #define MSG_DONTWAIT 0x40 7 | #endif 8 | 9 | static int32_t get_recv_err(const ssize_t bytes_read, const int32_t sock_fd); 10 | 11 | ssize_t websocket_recv(const int32_t sock_fd, const size_t capacity, char* restrict buffer) 12 | { 13 | if (is_rise_signal()) { 14 | log_info("A signal was raised during recv(). The system will abort processing.\n"); 15 | return WEBSOCKET_ERRORCODE_FATAL_ERROR; 16 | } 17 | 18 | int32_t errcode; 19 | ssize_t bytes_read = internal_recvfrom(sock_fd, buffer, capacity - 1, MSG_DONTWAIT, NULL, NULL); 20 | if ((errcode = get_recv_err(bytes_read, sock_fd)) != WEBSOCKET_ERRORCODE_NONE) { 21 | return errcode; 22 | } 23 | 24 | buffer[bytes_read] = '\0'; 25 | return bytes_read; 26 | } 27 | 28 | static int32_t get_recv_err(const ssize_t bytes_read, const int32_t sock_fd) 29 | { 30 | if (bytes_read == 0) { 31 | var_info("Socket was disconnected. socket : ", sock_fd); 32 | return WEBSOCKET_ERRORCODE_SOCKET_CLOSE_ERROR; 33 | } 34 | 35 | if (bytes_read == WEBSOCKET_SYSCALL_ERROR) { 36 | if (errno == EINTR || errno == EAGAIN) { 37 | return WEBSOCKET_ERRORCODE_CONTINUABLE_ERROR; 38 | } 39 | 40 | str_info("Failed to recv(). reason : ", strerror(errno)); 41 | var_info("socket : ", sock_fd); 42 | return WEBSOCKET_ERRORCODE_SOCKET_CLOSE_ERROR; 43 | } 44 | 45 | return WEBSOCKET_ERRORCODE_NONE; 46 | } 47 | -------------------------------------------------------------------------------- /src/websocket/crypto.c: -------------------------------------------------------------------------------- 1 | #include "../crypto/base64.h" 2 | #include "../crypto/sha1.h" 3 | #include "../util/allocator.h" 4 | #include "websocket_local.h" 5 | 6 | bool generate_websocket_acceptkey(const char* client_key, const size_t accept_key_size, char* accept_key) 7 | { 8 | if (is_null(client_key) || is_null(accept_key) || accept_key_size < 128) { 9 | log_error("Invalid WebSocket handshake request. Failed generate accept key\n"); 10 | return false; 11 | } 12 | 13 | const char* websocket_accept_guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; 14 | char concatenated[256]; 15 | bool has_error = false; 16 | 17 | size_t client_key_size = get_str_len(client_key); 18 | size_t guid_size = get_str_len(websocket_accept_guid); 19 | 20 | websocket_memcpy(concatenated, client_key, client_key_size); 21 | websocket_memcpy(concatenated + client_key_size, websocket_accept_guid, guid_size); 22 | concatenated[client_key_size + guid_size] = '\0'; 23 | 24 | uint8_t sha1_result[SHA1_DIGEST_LENGTH]; 25 | websocket_memset(sha1_result, 0x00, sizeof(sha1_result)); 26 | sha1(concatenated, get_str_nlen(concatenated, sizeof(concatenated)), sha1_result); 27 | 28 | if (!base64_encode(sha1_result, SHA1_DIGEST_LENGTH, accept_key, accept_key_size)) { 29 | has_error = true; 30 | goto FINALIZE; 31 | } 32 | 33 | FINALIZE: 34 | // Wipe variables 35 | websocket_memset_s(concatenated, sizeof(concatenated), 0x00, sizeof(concatenated)); 36 | websocket_memset_s(sha1_result, sizeof(sha1_result), 0x00, sizeof(sha1_result)); 37 | 38 | return !has_error; 39 | } 40 | -------------------------------------------------------------------------------- /src/arch/linux/fcntl.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_LINUX_FCNTL_H_ 2 | #define NOSTR_LINUX_FCNTL_H_ 3 | 4 | #define O_ACCMODE 0003 5 | #define O_RDONLY 00 6 | #define O_WRONLY 01 7 | #define O_RDWR 02 8 | #define O_CREAT 0100 // Not fcntl. 9 | #define O_EXCL 0200 // Not fcntl. 10 | #define O_NOCTTY 0400 // Not fcntl. 11 | #define O_TRUNC 01000 // Not fcntl. 12 | #define O_APPEND 02000 13 | #define O_NONBLOCK 04000 14 | #define O_NDELAY O_NONBLOCK 15 | #define O_SYNC 04010000 16 | #define O_FSYNC O_SYNC 17 | #define O_ASYNC 020000 18 | #define __O_LARGEFILE 0100000 19 | #define __O_DIRECTORY 0200000 20 | #define __O_NOFOLLOW 0400000 21 | #define __O_CLOEXEC 02000000 22 | #define __O_DIRECT 040000 23 | #define __O_NOATIME 01000000 24 | #define __O_PATH 010000000 25 | #define __O_DSYNC 010000 26 | #define __O_TMPFILE (020000000 | __O_DIRECTORY) 27 | 28 | #define F_GETLK 5 29 | #define F_SETLK 6 30 | #define F_SETLKW 7 31 | #define F_GETLK64 12 32 | #define F_SETLK64 13 33 | #define F_SETLKW64 14 34 | #define F_DUPFD 0 35 | #define F_GETFD 1 36 | #define F_SETFD 2 37 | #define F_GETFL 3 38 | #define F_SETFL 4 39 | #define __F_SETOWN 8 40 | #define __F_GETOWN 9 41 | 42 | #define __F_SETSIG 10 43 | #define __F_GETSIG 11 44 | #define __F_SETOWN_EX 45 | #define __F_GETOWN_EX 46 | #define FD_CLOEXEC 1 // Actually anything with low bit set goes 47 | 48 | #define F_RDLCK 0 // Read lock. 49 | #define F_WRLCK 1 // Write lock. 50 | #define F_UNLCK 2 // Remove lock. 51 | #define F_EXLCK 4 // or 3 52 | #define F_SHLCK 8 // or 4 53 | #define __POSIX_FADV_DONTNEED 4 54 | #define __POSIX_FADV_NOREUSE 5 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /src/websocket/server/receive/opcode_handle.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_SERVER_LOOP_OPCODE_HANDLE_H_ 2 | #define NOSTR_SERVER_LOOP_OPCODE_HANDLE_H_ 3 | 4 | #include "../../../util/allocator.h" 5 | #include "../../websocket_local.h" 6 | 7 | static inline int32_t opcode_handle( 8 | const int32_t client_sock, 9 | PWebSocketRawBuffer buffer, 10 | PWebSocketCallbacks callbacks, 11 | PWebSocketFrame frame) 12 | { 13 | switch (frame->opcode) { 14 | case WEBSOCKET_OP_CODE_TEXT: 15 | case WEBSOCKET_OP_CODE_BINARY: 16 | if (!is_null(callbacks->receive_callback)) { 17 | callbacks->receive_callback(client_sock, frame, buffer->capacity, buffer->response); 18 | } 19 | 20 | break; 21 | case WEBSOCKET_OP_CODE_CLOSE: 22 | return WEBSOCKET_ERRORCODE_SOCKET_CLOSE_ERROR; 23 | case WEBSOCKET_OP_CODE_PING: { 24 | frame->mask = 0; 25 | frame->opcode = WEBSOCKET_OP_CODE_PONG; 26 | 27 | size_t frame_size = create_websocket_frame(frame, buffer->capacity, buffer->response); 28 | if (frame_size == 0) { 29 | log_error("Failed to create pong frame.\n"); 30 | return WEBSOCKET_ERRORCODE_SOCKET_CLOSE_ERROR; 31 | } 32 | 33 | int32_t rtn = websocket_send(client_sock, frame_size, buffer->response); 34 | if (rtn != WEBSOCKET_ERRORCODE_NONE) { 35 | return rtn; 36 | } 37 | 38 | break; 39 | } 40 | case WEBSOCKET_OP_CODE_PONG: 41 | break; 42 | default: 43 | var_error("Unknown op code: ", frame->opcode); 44 | return WEBSOCKET_ERRORCODE_SOCKET_CLOSE_ERROR; 45 | } 46 | 47 | return WEBSOCKET_ERRORCODE_NONE; 48 | } 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /shell/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd $(dirname $0) 4 | 5 | ECHOBACK_SERVER_PATH=../examples/echoback 6 | ECHOBACK_SERVER_BIN_PATH=${ECHOBACK_SERVER_PATH}/bin/wsserver 7 | 8 | clean() { 9 | rm -rf ../build 1>/dev/null 10 | make clean -C ${ECHOBACK_SERVER_PATH} 1>/dev/null 11 | } 12 | 13 | wsDebugBuild() { 14 | rm -rf ../build 1>/dev/null 15 | make clean -C ${ECHOBACK_SERVER_PATH} 1>/dev/null 16 | cmake -S .. -B ../build -DCMAKE_BUILD_TYPE=Debug 1>/dev/null 17 | cmake --build ../build 18 | BUILD=debug make -C ${ECHOBACK_SERVER_PATH} 19 | } 20 | 21 | wsReleaseBuild() { 22 | rm -rf ../build 1>/dev/null 23 | make clean -C ${ECHOBACK_SERVER_PATH} 1>/dev/null 24 | cmake -S .. -B ../build -DCMAKE_BUILD_TYPE=Release 1>/dev/null 25 | cmake --build ../build 26 | BUILD=release make -C ${ECHOBACK_SERVER_PATH} 27 | } 28 | 29 | case "$1" in 30 | memcheck) 31 | wsDebugBuild 32 | echo "debug run (memcheck)" 33 | valgrind --tool=memcheck --leak-check=full --show-leak-kinds=all --track-origins=yes -s ${ECHOBACK_SERVER_BIN_PATH} 34 | ;; 35 | helgrind) 36 | wsDebugBuild 37 | echo "debug run (helgrind)" 38 | valgrind --tool=helgrind --history-level=approx -s ${ECHOBACK_SERVER_BIN_PATH} 39 | ;; 40 | debug) 41 | wsDebugBuild 42 | echo "debug run" 43 | #gdb --args ${ECHOBACK_SERVER_BIN_PATH} 44 | ${ECHOBACK_SERVER_BIN_PATH} 45 | ;; 46 | release) 47 | wsReleaseBuild 48 | echo "release run" 49 | ${ECHOBACK_SERVER_BIN_PATH} 50 | ;; 51 | clean) 52 | clean 53 | ;; 54 | *) 55 | wsReleaseBuild 56 | echo "release run" 57 | ${ECHOBACK_SERVER_BIN_PATH} 58 | ;; 59 | esac 60 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1731533236, 9 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "nixpkgs": { 22 | "locked": { 23 | "lastModified": 1740367490, 24 | "narHash": "sha256-WGaHVAjcrv+Cun7zPlI41SerRtfknGQap281+AakSAw=", 25 | "owner": "NixOS", 26 | "repo": "nixpkgs", 27 | "rev": "0196c0175e9191c474c26ab5548db27ef5d34b05", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "NixOS", 32 | "ref": "nixos-unstable", 33 | "repo": "nixpkgs", 34 | "type": "github" 35 | } 36 | }, 37 | "root": { 38 | "inputs": { 39 | "flake-utils": "flake-utils", 40 | "nixpkgs": "nixpkgs" 41 | } 42 | }, 43 | "systems": { 44 | "locked": { 45 | "lastModified": 1681028828, 46 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 47 | "owner": "nix-systems", 48 | "repo": "default", 49 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 50 | "type": "github" 51 | }, 52 | "original": { 53 | "owner": "nix-systems", 54 | "repo": "default", 55 | "type": "github" 56 | } 57 | } 58 | }, 59 | "root": "root", 60 | "version": 7 61 | } 62 | -------------------------------------------------------------------------------- /src/util/types.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_UTIL_TYPES_H_ 2 | #define NOSTR_UTIL_TYPES_H_ 3 | 4 | #ifdef __APPLE__ 5 | #include 6 | #include 7 | #include 8 | #else 9 | #ifndef __cplusplus 10 | #ifndef _INT8_T 11 | #define _INT8_T 12 | typedef char int8_t; 13 | #endif 14 | 15 | #ifndef _UINT8_T 16 | #define _UINT8_T 17 | typedef unsigned char uint8_t; 18 | #endif 19 | 20 | #ifndef _INT16_T 21 | #define _INT16_T 22 | typedef short int16_t; 23 | #endif 24 | 25 | #ifndef _UINT16_T 26 | #define _UINT16_T 27 | typedef unsigned short uint16_t; 28 | #endif 29 | 30 | #ifndef _INT32_T 31 | #define _INT32_T 32 | typedef int int32_t; 33 | #endif 34 | 35 | #ifndef _UINT32_T 36 | #define _UINT32_T 37 | typedef unsigned int uint32_t; 38 | #endif 39 | 40 | #ifndef _INT64_T 41 | #define _INT64_T 42 | typedef long long int64_t; 43 | #endif 44 | 45 | #ifndef _UINT64_T 46 | #define _UINT64_T 47 | typedef unsigned long long uint64_t; 48 | #endif 49 | 50 | #ifndef _SIZE_T 51 | #define _SIZE_T 52 | typedef uint64_t size_t; 53 | #endif 54 | 55 | #ifndef _SSIZE_T 56 | #define _SSIZE_T 57 | typedef int64_t ssize_t; 58 | #endif 59 | 60 | #ifndef bool 61 | #define bool int32_t 62 | #endif 63 | 64 | #ifndef true 65 | #define true 1 66 | #endif 67 | 68 | #ifndef false 69 | #define false 0 70 | #endif 71 | #endif 72 | 73 | #ifndef NULL 74 | #define NULL ((void*)0) 75 | #endif 76 | 77 | #endif 78 | 79 | #ifndef STDOUT_FILENO 80 | #define STDOUT_FILENO 1 // Standard output. 81 | #endif 82 | 83 | #ifndef STDERR_FILENO 84 | #define STDERR_FILENO 2 // Standard error output. 85 | #endif 86 | 87 | #ifndef thread_local 88 | #if __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_THREADS__) 89 | #define thread_local _Thread_local 90 | #else 91 | #define thread_local __thread 92 | #endif 93 | #endif 94 | 95 | #endif 96 | -------------------------------------------------------------------------------- /src/crypto/sha1_def.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_SHA1_DEF_H_ 2 | #define NOSTR_SHA1_DEF_H_ 3 | 4 | #include "../util/types.h" 5 | 6 | typedef struct 7 | { 8 | uint32_t state[5]; 9 | uint32_t count[2]; 10 | uint8_t buffer[64]; 11 | } Sha1Ctx; 12 | 13 | typedef union { 14 | uint8_t c[64]; 15 | uint32_t l[16]; 16 | } Char64Long16; 17 | 18 | #define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) 19 | 20 | /* blk0() and blk() perform the initial expand. */ 21 | /* I got the idea of expanding during the round function from SSLeay */ 22 | #if BYTE_ORDER == LITTLE_ENDIAN 23 | #define blk0(i) (block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) | (rol(block->l[i], 8) & 0x00FF00FF)) 24 | #elif BYTE_ORDER == BIG_ENDIAN 25 | #define blk0(i) block->l[i] 26 | #else 27 | #error "Endianness not defined!" 28 | #endif 29 | #define blk(i) \ 30 | (block->l[i & 15] = rol(block->l[(i + 13) & 15] ^ block->l[(i + 8) & 15] ^ block->l[(i + 2) & 15] ^ block->l[i & 15], 1)) 31 | 32 | /* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ 33 | #define R0(v, w, x, y, z, i) \ 34 | z += ((w & (x ^ y)) ^ y) + blk0(i) + 0x5A827999 + rol(v, 5); \ 35 | w = rol(w, 30); 36 | #define R1(v, w, x, y, z, i) \ 37 | z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \ 38 | w = rol(w, 30); 39 | #define R2(v, w, x, y, z, i) \ 40 | z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); \ 41 | w = rol(w, 30); 42 | #define R3(v, w, x, y, z, i) \ 43 | z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v, 5); \ 44 | w = rol(w, 30); 45 | #define R4(v, w, x, y, z, i) \ 46 | z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \ 47 | w = rol(w, 30); 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /examples/echoback/src/main.c: -------------------------------------------------------------------------------- 1 | //#include 2 | //#include 3 | #include "../../../src/websocket/websocket.h" 4 | 5 | void websocket_receive_callback( 6 | const int client_sock, 7 | PWebSocketFrame frame, 8 | const size_t buffer_capacity, 9 | char* response_buffer) 10 | { 11 | switch (frame->opcode) { 12 | case WEBSOCKET_OP_CODE_TEXT: { 13 | frame->mask = 0; 14 | size_t frame_size = create_websocket_frame(frame, buffer_capacity, response_buffer); 15 | if (frame_size == 0) { 16 | log_error("Failed to create websocket frame.\n"); 17 | return; 18 | } 19 | 20 | websocket_send(client_sock, frame_size, response_buffer); 21 | } break; 22 | default: 23 | break; 24 | } 25 | } 26 | 27 | void websocket_connect_callback(int client_sock) 28 | { 29 | //printf("[user] connect. socket fd : %d\n", client_sock); 30 | //fflush(stdout); 31 | } 32 | 33 | void websocket_disconnect_callback(int client_sock) 34 | { 35 | //printf("[user] disconnect. socket fd : %d\n", client_sock); 36 | //fflush(stdout); 37 | } 38 | 39 | int main() 40 | { 41 | WebSocketInitArgs init_args; 42 | init_args.port_num = 8080; 43 | init_args.backlog = 5; 44 | 45 | int server_sock = websocket_server_init(&init_args); 46 | if (server_sock < WEBSOCKET_ERRORCODE_NONE) { 47 | log_error("websocket server init error.\n"); 48 | return 1; 49 | } 50 | 51 | WebSocketLoopArgs loop_args; 52 | loop_args.server_sock = server_sock; 53 | loop_args.callbacks.receive_callback = websocket_receive_callback; 54 | loop_args.callbacks.connect_callback = websocket_connect_callback; 55 | loop_args.callbacks.disconnect_callback = websocket_disconnect_callback; 56 | loop_args.buffer_capacity = 1024; 57 | 58 | websocket_server_loop(&loop_args); 59 | websocket_close(server_sock); 60 | 61 | log_error("websocket server end.\n"); 62 | return 0; 63 | } 64 | -------------------------------------------------------------------------------- /src/arch/linux/x86_64/memory.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_LINUX_X86_64_MEMORY_H_ 2 | #define NOSTR_LINUX_X86_64_MEMORY_H_ 3 | 4 | #include "../../../util/types.h" 5 | #include "../errno.h" 6 | 7 | static inline void* linux_x8664_memcpy(void* dest, const void* src, size_t size) 8 | { 9 | if (dest == NULL || src == NULL || size == 0) { 10 | return NULL; 11 | } 12 | 13 | unsigned char* d = (unsigned char*)dest; 14 | const unsigned char* s = (const unsigned char*)src; 15 | size_t n = size; 16 | 17 | while (n--) { 18 | *d++ = *s++; 19 | } 20 | return dest; 21 | } 22 | 23 | static inline void* linux_x8664_memset(void* s, const int c, const size_t size) 24 | { 25 | if (s == NULL && size != 0) { 26 | return NULL; 27 | } 28 | 29 | unsigned char* p = (unsigned char*)s; 30 | size_t n = size; 31 | while (n--) { 32 | *p++ = (unsigned char)c; 33 | } 34 | 35 | return s; 36 | } 37 | 38 | /* 39 | * memset_s: Secure memory set function (wrapper for memset) 40 | * 41 | * @s: Pointer to the memory area to be set 42 | * @smax: Size of the memory area in bytes 43 | * @c: Value to set (passed as int, but treated as an unsigned char) 44 | * @n: Number of bytes to set 45 | * 46 | * Note: A compiler barrier is inserted to ensure that the memset call is not optimized away. 47 | */ 48 | static inline int32_t linux_x8664_memset_s(void* s, const size_t smax, const int32_t c, const size_t n) 49 | { 50 | if (s == NULL && n != 0) { 51 | return EINVAL; 52 | } 53 | 54 | // If n is greater than smax, clear the entire buffer (if possible) and return an error 55 | if (n > smax) { 56 | if (s != NULL && smax > 0) { 57 | linux_x8664_memset(s, c, smax); 58 | // Compiler barrier to prevent optimization removal 59 | __asm__ volatile("" :: 60 | : "memory"); 61 | } 62 | return EINVAL; 63 | } 64 | 65 | // Set n bytes of memory to the value c 66 | linux_x8664_memset(s, c, n); 67 | // Compiler barrier to prevent optimization removal 68 | __asm__ volatile("" :: 69 | : "memory"); 70 | 71 | return 0; 72 | } 73 | 74 | #endif 75 | -------------------------------------------------------------------------------- /src/websocket/server/accept/accept_handle.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_SERVER_LOOP_ACCEPT_H_ 2 | #define NOSTR_SERVER_LOOP_ACCEPT_H_ 3 | 4 | #include "../../../util/allocator.h" 5 | #include "../../websocket_local.h" 6 | 7 | static inline bool accept_handle( 8 | const int32_t epoll_fd, 9 | const int32_t server_sock, 10 | PWebSocketRawBuffer buffer, 11 | PWebSocketEpollEvent event, 12 | PWebSocketCallbacks callbacks) 13 | { 14 | HTTPRequest request; 15 | ssize_t bytes_read; 16 | 17 | bool err = false; 18 | 19 | log_debug("accept...\n"); 20 | int32_t client_sock = websocket_accept(server_sock); 21 | if (client_sock < 0) { 22 | if (client_sock == WEBSOCKET_ERRORCODE_FATAL_ERROR) { 23 | err = true; 24 | } 25 | 26 | goto FINALIZE; 27 | } 28 | 29 | log_debug("epoll add(client sock)...\n"); 30 | if (!websocket_epoll_add(epoll_fd, client_sock, event)) { 31 | err = true; 32 | goto FINALIZE; 33 | } 34 | 35 | log_debug("Read to handshake message...\n"); 36 | while (1) { 37 | bytes_read = websocket_recv(client_sock, buffer->capacity, buffer->request); 38 | if (bytes_read < 0) { 39 | if (bytes_read == WEBSOCKET_ERRORCODE_CONTINUABLE_ERROR) { 40 | continue; 41 | } 42 | 43 | err = true; 44 | var_info("Failed to handshake message read.\n", client_sock); 45 | websocket_epoll_del(epoll_fd, client_sock); 46 | goto FINALIZE; 47 | } 48 | 49 | break; 50 | } 51 | 52 | log_debug("Analyze to handshake message...\n"); 53 | if (!client_handshake(client_sock, bytes_read, buffer, &request)) { 54 | websocket_epoll_del(epoll_fd, client_sock); 55 | err = true; 56 | goto FINALIZE; 57 | } 58 | 59 | FINALIZE: 60 | if (err) { 61 | log_debug("websocket_accept error. finalize...\n"); 62 | if (client_sock >= 0) { 63 | websocket_close(client_sock); 64 | } 65 | 66 | return false; 67 | } 68 | 69 | if (!is_null(callbacks->connect_callback)) { 70 | callbacks->connect_callback(client_sock); 71 | } 72 | 73 | var_debug("accept done. client_sock : ", client_sock); 74 | return true; 75 | } 76 | 77 | #endif 78 | -------------------------------------------------------------------------------- /.github/workflows/coverity.yml: -------------------------------------------------------------------------------- 1 | name: Coverity 2 | on: 3 | schedule: 4 | - cron: '42 0 * * *' # Run once per day, to avoid Coverity's submission limits 5 | workflow_dispatch: 6 | 7 | permissions: 8 | contents: read # to fetch code (actions/checkout) 9 | 10 | jobs: 11 | scan: 12 | runs-on: ubuntu-24.04 13 | 14 | env: 15 | CC: gcc 16 | DEBIAN_FRONTEND: noninteractive 17 | TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }} 18 | 19 | steps: 20 | - name: Checkout code 21 | if: env.TOKEN 22 | uses: actions/checkout@v6 23 | 24 | - name: Install dependencies on ubuntu 25 | run: | 26 | sudo apt-get update 27 | sudo apt-get --reinstall install -y build-essential gcc cmake 28 | sudo apt-get install -y gzip 29 | 30 | - name: Download coverity 31 | if: env.TOKEN 32 | run: | 33 | wget -q https://scan.coverity.com/download/linux64 --post-data "token=$TOKEN&project=Hakkadaikon%2Fwebsocket" -O coverity_tool.tgz 34 | mkdir cov-scan 35 | tar ax -f coverity_tool.tgz --strip-components=1 -C cov-scan 36 | 37 | - name: Install Nix 38 | uses: DeterminateSystems/nix-installer-action@main 39 | with: 40 | github-token: ${{ secrets.GH_TOKEN }} 41 | 42 | - name: Add coverity path 43 | if: env.TOKEN 44 | run: | 45 | echo "$(pwd)/cov-scan/bin" >> $GITHUB_PATH 46 | 47 | - name: Build/scan websocket 48 | if: env.TOKEN 49 | run: | 50 | cov-build --dir cov-int cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug && cmake --build build 51 | 52 | - name: Submit results 53 | if: env.TOKEN 54 | run: | 55 | tar zcf cov-scan.tgz cov-int 56 | curl --form token=$TOKEN \ 57 | --form email=$EMAIL \ 58 | --form file=@cov-scan.tgz \ 59 | --form version="$(git rev-parse HEAD)" \ 60 | --form description="Automatic GHA scan" \ 61 | 'https://scan.coverity.com/builds?project=Hakkadaikon%2Fwebsocket' 62 | env: 63 | EMAIL: ${{ secrets.COVERITY_SCAN_EMAIL }} 64 | -------------------------------------------------------------------------------- /src/arch/linux/x86_64/asm_syscall.S: -------------------------------------------------------------------------------- 1 | .text 2 | .global __linux_x8664_asm_syscall6 3 | .global __linux_x8664_asm_syscall5 4 | .global __linux_x8664_asm_syscall4 5 | .global __linux_x8664_asm_syscall3 6 | .global __linux_x8664_asm_syscall2 7 | .global __linux_x8664_asm_syscall1 8 | .global __linux_x8664_asm_syscall0 9 | 10 | .type __linux_x8664_asm_syscall6, @function 11 | .type __linux_x8664_asm_syscall5, @function 12 | .type __linux_x8664_asm_syscall4, @function 13 | .type __linux_x8664_asm_syscall3, @function 14 | .type __linux_x8664_asm_syscall2, @function 15 | .type __linux_x8664_asm_syscall1, @function 16 | .type __linux_x8664_asm_syscall0, @function 17 | 18 | __linux_x8664_asm_syscall6: 19 | // @param rdi: syscall num 20 | // @param rsi: arg1 21 | // @param rdx: arg2 22 | // @param rcx: arg3 23 | // @param r8 : arg4 24 | // @param r9 : arg5 25 | // @param rsp: arg6 26 | movq %rdi, %rax 27 | movq %rsi, %rdi 28 | movq %rdx, %rsi 29 | movq %rcx, %rdx 30 | movq %r8, %r10 31 | movq %r9, %r8 32 | movq 16(%rsp), %r9 33 | syscall 34 | ret 35 | 36 | __linux_x8664_asm_syscall5: 37 | movq %rdi, %rax 38 | movq %rsi, %rdi 39 | movq %rdx, %rsi 40 | movq %rcx, %rdx 41 | movq %r8, %r10 42 | movq %r9, %r8 43 | syscall 44 | ret 45 | 46 | __linux_x8664_asm_syscall4: 47 | movq %rdi, %rax 48 | movq %rsi, %rdi 49 | movq %rdx, %rsi 50 | movq %rcx, %rdx 51 | movq %r8, %r10 52 | syscall 53 | ret 54 | 55 | __linux_x8664_asm_syscall3: 56 | movq %rdi, %rax 57 | movq %rsi, %rdi 58 | movq %rdx, %rsi 59 | movq %rcx, %rdx 60 | syscall 61 | ret 62 | 63 | __linux_x8664_asm_syscall2: 64 | movq %rdi, %rax 65 | movq %rsi, %rdi 66 | movq %rdx, %rsi 67 | syscall 68 | ret 69 | 70 | __linux_x8664_asm_syscall1: 71 | movq %rdi, %rax 72 | movq %rsi, %rdi 73 | syscall 74 | ret 75 | 76 | __linux_x8664_asm_syscall0: 77 | movq %rdi, %rax 78 | syscall 79 | ret 80 | -------------------------------------------------------------------------------- /src/websocket/socket/listen.c: -------------------------------------------------------------------------------- 1 | #include "../../arch/listen.h" 2 | 3 | #include "../websocket_local.h" 4 | #include "./optimize_socket.h" 5 | 6 | static void set_sockaddr(const int port_num, struct sockaddr_in* server_addr); 7 | 8 | int32_t websocket_listen(const int32_t port_num, const int32_t backlog) 9 | { 10 | int32_t server_sock; 11 | struct sockaddr_in server_addr; 12 | 13 | server_sock = internal_socket(AF_INET, SOCK_STREAM, 0); 14 | if (server_sock < 0) { 15 | str_error("Failed to socket(). reason : ", strerror(errno)); 16 | return WEBSOCKET_ERRORCODE_FATAL_ERROR; 17 | } 18 | 19 | set_sockaddr(port_num, &server_addr); 20 | 21 | bool err = false; 22 | 23 | if (internal_bind(server_sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { 24 | str_error("Failed to bind(). reason : ", strerror(errno)); 25 | err = true; 26 | goto FINALIZE; 27 | } 28 | 29 | if (internal_listen(server_sock, backlog) < 0) { 30 | str_error("Failed to listen(). reason : ", strerror(errno)); 31 | err = true; 32 | goto FINALIZE; 33 | } 34 | 35 | if (set_nonblocking(server_sock) == WEBSOCKET_SYSCALL_ERROR) { 36 | log_error("Failed to set server socket non-blocking\n"); 37 | err = true; 38 | goto FINALIZE; 39 | } 40 | 41 | if (optimize_server_socket(server_sock) == WEBSOCKET_ERRORCODE_FATAL_ERROR) { 42 | err = true; 43 | goto FINALIZE; 44 | } 45 | 46 | FINALIZE: 47 | if (err) { 48 | websocket_close(server_sock); 49 | return WEBSOCKET_ERRORCODE_FATAL_ERROR; 50 | } 51 | 52 | return server_sock; 53 | } 54 | 55 | static void set_sockaddr(const int port_num, struct sockaddr_in* server_addr) 56 | { 57 | #ifndef __APPLE__ 58 | #if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ 59 | #define htons(x) ((((x) & 0x00ff) << 8) | (((x) & 0xff00) >> 8)) 60 | #elif defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ 61 | #define htons(x) (x) 62 | #else 63 | #error "Unknown endianness" 64 | #endif 65 | #endif 66 | 67 | websocket_memset(server_addr, 0, sizeof(struct sockaddr_in)); 68 | server_addr->sin_family = AF_INET; 69 | server_addr->sin_addr.s_addr = INADDR_ANY; 70 | server_addr->sin_port = htons(port_num); 71 | } 72 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | project(wsserver C) 3 | enable_language(ASM) 4 | 5 | # Set a default build type if not specified (for single-config generators) 6 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 7 | set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE) 8 | endif() 9 | 10 | if (APPLE) 11 | file(GLOB_RECURSE SRC_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/*.c") 12 | set(DEBUG_COMPILE_OPTIONS "-O0" "-g") 13 | set(RELEASE_COMPILE_OPTIONS "-O3" "-mtune=native" "-ffast-math" "-fno-math-errno" "-falign-functions") 14 | else() 15 | file(GLOB_RECURSE SRC_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/*.c") 16 | file(GLOB_RECURSE ASM_FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/*.S") 17 | set( 18 | DEBUG_COMPILE_OPTIONS 19 | "-O0" 20 | "-static-libasan" 21 | "-fno-builtin" 22 | "-g" 23 | ) 24 | set( 25 | RELEASE_COMPILE_OPTIONS 26 | "-O3" 27 | "-mtune=native" 28 | "-ffast-math" 29 | "-fno-math-errno" 30 | "-fno-builtin" 31 | "-falign-functions" 32 | ) 33 | endif() 34 | 35 | # Create the static library target including C and assembly files 36 | add_library(wsserver STATIC ${SRC_FILES} ${ASM_FILES}) 37 | add_library(wsserver_shared SHARED ${SRC_FILES} ${ASM_FILES}) 38 | 39 | # Set the output directory for the static library to the "lib" folder in the build directory 40 | set_target_properties(wsserver PROPERTIES 41 | ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" 42 | ) 43 | 44 | # Add the src directory to the include path 45 | target_include_directories(wsserver PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/src") 46 | 47 | target_compile_options( 48 | wsserver PRIVATE 49 | $<$:${DEBUG_COMPILE_OPTIONS}> 50 | $<$:${RELEASE_COMPILE_OPTIONS}> 51 | ) 52 | 53 | target_compile_definitions( 54 | wsserver PRIVATE 55 | $<$:LOG_LEVEL_DEBUG> 56 | $<$:LOG_LEVEL_ERROR> 57 | ) 58 | 59 | # Add link options (applied to both Debug and Release configurations) 60 | #target_link_options(wsserver PRIVATE -flto) 61 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static") 62 | set(BUILD_SHARED_LIBS OFF) 63 | 64 | install(TARGETS wsserver 65 | ARCHIVE DESTINATION lib 66 | LIBRARY DESTINATION lib 67 | RUNTIME DESTINATION bin) 68 | 69 | install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/src/websocket/websocket.h" 70 | DESTINATION include) 71 | 72 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "wsserver flake: build a WebSocket server library with CMake"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 6 | flake-utils.url = "github:numtide/flake-utils"; 7 | }; 8 | 9 | outputs = { self, nixpkgs, flake-utils }: 10 | flake-utils.lib.eachDefaultSystem (system: 11 | let 12 | pkgs = import nixpkgs { inherit system; }; 13 | 14 | wsserverRelease = pkgs.stdenv.mkDerivation { 15 | pname = "wsserver-release"; 16 | version = "2.1.6"; 17 | src = self; 18 | nativeBuildInputs = [ pkgs.cmake pkgs.gnumake ]; 19 | 20 | buildPhase = '' 21 | cd $TMPDIR 22 | cmake -S $src -B build -DCMAKE_BUILD_TYPE=Release 23 | cmake --build build 24 | ''; 25 | 26 | installPhase = '' 27 | mkdir -p $out/lib/ $out/include/ 28 | cp $TMPDIR/build/lib/libwsserver.a $out/lib/ 29 | cp $src/src/websocket/websocket.h $out/include/ 30 | ''; 31 | 32 | meta = with pkgs.lib; { 33 | description = "WebSocket server library"; 34 | license = licenses.mit; 35 | platforms = platforms.linux; 36 | }; 37 | }; 38 | 39 | wsserverDebug = pkgs.stdenv.mkDerivation { 40 | pname = "wsserver-debug"; 41 | version = "2.1.6"; 42 | src = self; 43 | nativeBuildInputs = [ pkgs.cmake pkgs.gnumake ]; 44 | 45 | buildPhase = '' 46 | cd $TMPDIR 47 | cmake -S $src -B build -DCMAKE_BUILD_TYPE=Debug 48 | cmake --build build 49 | ''; 50 | 51 | installPhase = '' 52 | mkdir -p $out/lib/ $out/include/ 53 | cp $TMPDIR/build/lib/libwsserver.a $out/lib/ 54 | cp $src/src/websocket/websocket.h $out/include/ 55 | ''; 56 | 57 | meta = with pkgs.lib; { 58 | description = "WebSocket server library (debug)"; 59 | license = licenses.mit; 60 | platforms = platforms.linux; 61 | }; 62 | }; 63 | in { 64 | packages.release = wsserverRelease; 65 | packages.debug = wsserverDebug; 66 | packages.default = wsserverRelease; 67 | 68 | devShell = pkgs.mkShell { 69 | buildInputs = [ pkgs.cmake pkgs.gnumake ]; 70 | }; 71 | } 72 | ); 73 | } 74 | 75 | -------------------------------------------------------------------------------- /tests/http/request.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | extern "C" { 6 | #include "http/http.h" 7 | } 8 | 9 | namespace 10 | { 11 | 12 | TEST(HttpRequestParseTest, ValidWebSocketUpgradeRequest) 13 | { 14 | const char* request_text = 15 | "GET /chat HTTP/1.1\r\n" 16 | "Host: server.example.com\r\n" 17 | "Upgrade: websocket\r\n" 18 | "Connection: Upgrade\r\n" 19 | "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" 20 | "Origin: http://example.com\r\n" 21 | "Sec-WebSocket-Protocol: chat, superchat\r\n" 22 | "Sec-WebSocket-Version: 13\r\n"; 23 | 24 | const size_t request_len = static_cast(strlen(request_text)); 25 | 26 | HTTPRequest request{}; 27 | 28 | const bool ok = extract_http_request(request_text, request_len, &request); 29 | ASSERT_TRUE(ok); 30 | 31 | EXPECT_STREQ(request.line.method, "GET"); 32 | EXPECT_STREQ(request.line.target, "/chat"); 33 | EXPECT_STREQ(request.line.http_version, "HTTP/1.1"); 34 | 35 | ASSERT_EQ(request.header_size, static_cast(7)); 36 | 37 | ASSERT_STREQ(request.headers[0].key, "Host"); 38 | ASSERT_STREQ(request.headers[0].value, "server.example.com"); 39 | 40 | ASSERT_STREQ(request.headers[1].key, "Upgrade"); 41 | ASSERT_STREQ(request.headers[1].value, "websocket"); 42 | 43 | ASSERT_STREQ(request.headers[2].key, "Connection"); 44 | ASSERT_STREQ(request.headers[2].value, "Upgrade"); 45 | 46 | ASSERT_STREQ(request.headers[3].key, "Sec-WebSocket-Key"); 47 | ASSERT_STREQ(request.headers[3].value, "dGhlIHNhbXBsZSBub25jZQ=="); 48 | 49 | ASSERT_STREQ(request.headers[4].key, "Origin"); 50 | ASSERT_STREQ(request.headers[4].value, "http://example.com"); 51 | 52 | ASSERT_STREQ(request.headers[5].key, "Sec-WebSocket-Protocol"); 53 | ASSERT_STREQ(request.headers[5].value, "chat, superchat"); 54 | 55 | ASSERT_STREQ(request.headers[6].key, "Sec-WebSocket-Version"); 56 | ASSERT_STREQ(request.headers[6].value, "13"); 57 | } 58 | 59 | TEST(HttpRequestParseTest, MissingColonInHeaderFails) 60 | { 61 | const char* request_text = 62 | "GET / HTTP/1.1\r\n" 63 | "Host server.example.com\r\n"; 64 | 65 | const size_t request_len = static_cast(strlen(request_text)); 66 | 67 | HTTPRequest request{}; 68 | const bool ok = extract_http_request(request_text, request_len, &request); 69 | EXPECT_FALSE(ok); 70 | } 71 | 72 | TEST(HttpRequestParseTest, OnlyRequestLineFails) 73 | { 74 | const char* request_text = "GET / HTTP/1.1"; 75 | const size_t request_len = static_cast(strlen(request_text)); 76 | 77 | HTTPRequest request{}; 78 | const bool ok = extract_http_request(request_text, request_len, &request); 79 | EXPECT_FALSE(ok); 80 | } 81 | 82 | } // namespace 83 | -------------------------------------------------------------------------------- /tests/crypto/base64.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | extern "C" { 7 | #include "crypto/base64.h" 8 | } 9 | 10 | static size_t RequiredBase64Capacity(size_t input_len) 11 | { 12 | size_t blocks = (input_len + 2) / 3; 13 | return blocks * 4 + 1; 14 | } 15 | 16 | TEST(Base64Test, EncodeKnownVectors) 17 | { 18 | struct Case { 19 | std::string input; 20 | std::string expected; 21 | }; 22 | 23 | std::vector cases = { 24 | {"f", "Zg=="}, 25 | {"hello", "aGVsbG8="}, 26 | {"websocket", "d2Vic29ja2V0"}, 27 | }; 28 | 29 | for (const auto& tc : cases) { 30 | const size_t cap = RequiredBase64Capacity(tc.input.size()); 31 | std::string out; 32 | out.resize(cap); 33 | 34 | bool ok = base64_encode( 35 | reinterpret_cast(tc.input.data()), 36 | static_cast(tc.input.size()), 37 | &out[0], 38 | cap); 39 | ASSERT_TRUE(ok) << "encode failed for input: " << tc.input; 40 | 41 | // Ensure null-terminated 42 | ASSERT_EQ(out[tc.expected.size()], '\0'); 43 | 44 | // Compare only the meaningful part (excluding null) 45 | out.resize(tc.expected.size()); 46 | EXPECT_EQ(out, tc.expected) << "mismatch for input: " << tc.input; 47 | } 48 | } 49 | 50 | TEST(Base64Test, EncodeEmptyInputFails) 51 | { 52 | char out[8] = {0}; 53 | bool ok = base64_encode(nullptr, 0, out, sizeof(out)); 54 | EXPECT_FALSE(ok); 55 | } 56 | 57 | TEST(Base64Test, EncodeNullOutputFails) 58 | { 59 | const uint8_t data[1] = {0x61}; 60 | bool ok = base64_encode(data, 1, nullptr, 0); 61 | EXPECT_FALSE(ok); 62 | } 63 | 64 | TEST(Base64Test, EncodeInsufficientCapacityFailsForMin) 65 | { 66 | const uint8_t data[1] = {0x61}; // 'a' -> YQ== 67 | // Capacity <= 4 should fail per implementation precondition 68 | char out4[4] = {0}; 69 | EXPECT_FALSE(base64_encode(data, 1, out4, sizeof(out4))); 70 | 71 | char out5[5] = {0}; 72 | EXPECT_TRUE(base64_encode(data, 1, out5, sizeof(out5))); 73 | EXPECT_STREQ(out5, "YQ=="); 74 | } 75 | 76 | TEST(Base64Test, IsBase64Validation) 77 | { 78 | std::vector> cases = { 79 | {"Zg==", true}, 80 | {"Zm9v", true}, 81 | {"aGVsbG8=", true}, 82 | // invalid: too short 83 | {"A", false}, 84 | {"==", false}, 85 | // invalid: contains non-base64 char 86 | {"Zm9v*", false}, 87 | // invalid: padding in the wrong place or too many 88 | {"A===", false}, 89 | {"====", false}, 90 | }; 91 | 92 | for (const auto& [s, expect] : cases) { 93 | EXPECT_EQ(is_base64(s.c_str()), expect) << s; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: WebSocket Tests 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | build-and-test: 9 | runs-on: ${{ matrix.os }}-${{ matrix.version }} 10 | strategy: 11 | matrix: 12 | include: 13 | - os: ubuntu 14 | version: 22.04 15 | gcc: 9 16 | c_std: 11 17 | cpp_std: 17 18 | - os: ubuntu 19 | version: 24.04 20 | gcc: 14 21 | c_std: 23 22 | cpp_std: 23 23 | - os: macos 24 | version: 14 25 | c_std: 11 26 | cpp_std: 17 27 | - os: macos 28 | version: 15 29 | c_std: 23 30 | cpp_std: 23 31 | 32 | steps: 33 | - name: Checkout code 34 | uses: actions/checkout@v6 35 | 36 | - name: Install dependencies on ubuntu (CC ${{ matrix.gcc }}) 37 | if: matrix.os == 'ubuntu' 38 | run: | 39 | sudo apt-get update 40 | sudo apt-get install -y cmake build-essential gcc-${{ matrix.gcc }} g++-${{ matrix.gcc }} 41 | echo "CC=gcc-${{ matrix.gcc }}" >> $GITHUB_ENV 42 | echo "CXX=g++-${{ matrix.gcc }}" >> $GITHUB_ENV 43 | echo "C_STD=${{ matrix.c_std }}" >> $GITHUB_ENV 44 | echo "CPP_STD=${{ matrix.cpp_std }}" >> $GITHUB_ENV 45 | 46 | - name: Install dependencies on macos 47 | if: matrix.os == 'macos' 48 | run: | 49 | brew update 50 | brew reinstall gcc cmake 51 | echo "C_STD=${{ matrix.c_std }}" >> $GITHUB_ENV 52 | echo "CPP_STD=${{ matrix.cpp_std }}" >> $GITHUB_ENV 53 | 54 | - name: Build project 55 | shell: bash 56 | run: | 57 | if [ -n "${CC:-}" ]; then 58 | cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_STANDARD="${C_STD}" -DCMAKE_C_COMPILER="${CC}" 59 | else 60 | cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_STANDARD="${C_STD}" 61 | fi 62 | 63 | cmake --build build 64 | 65 | if [ -n "${CC:-}" ]; then 66 | make BUILD=release -C examples/echoback CC="${CC}" 67 | else 68 | make BUILD=release -C examples/echoback 69 | fi 70 | 71 | - name: Check link 72 | if: matrix.os == 'ubuntu' 73 | shell: bash 74 | run: | 75 | echo "Check ldd" 76 | ldd examples/echoback/bin/wsserver 77 | echo "Check readelf" 78 | readelf --dyn-syms -W examples/echoback/bin/wsserver 79 | 80 | - name: Run tests 81 | shell: bash 82 | run: | 83 | if [ -n "${CXX:-}" ]; then 84 | cmake -S tests -B tests/build -DCMAKE_CXX_STANDARD="${CPP_STD}" -DCMAKE_CXX_COMPILER="${CXX}" 85 | else 86 | cmake -S tests -B tests/build -DCMAKE_CXX_STANDARD="${CPP_STD}" 87 | fi 88 | cmake --build tests/build 89 | ctest --test-dir tests/build 90 | -------------------------------------------------------------------------------- /src/util/log.c: -------------------------------------------------------------------------------- 1 | #include "../arch/write.h" 2 | #include "../websocket/websocket.h" 3 | #include "./string.h" 4 | 5 | static inline int32_t calc_digit(int32_t value) 6 | { 7 | int32_t is_negative = (value < 0); 8 | if (is_negative) { 9 | value = -value; 10 | } 11 | 12 | for (int32_t i = 10, j = 1; i < 1000000000; i *= 10, j++) { 13 | if (value < i) { 14 | return j; 15 | } 16 | } 17 | 18 | return 10 + is_negative; 19 | } 20 | 21 | static inline size_t safe_itoa(int32_t value, char* restrict buffer, size_t buffer_capacity) 22 | { 23 | int32_t digit = calc_digit(value); 24 | char* end = buffer + digit; 25 | char* current = end; 26 | int32_t is_negative = (value < 0); 27 | 28 | if (is_negative) { 29 | value = -value; 30 | } 31 | 32 | *current-- = '\0'; 33 | do { 34 | *current-- = '0' + (value % 10); 35 | value /= 10; 36 | } while (value > 0 && current >= buffer); 37 | 38 | if (is_negative && current >= buffer) { 39 | *current-- = '-'; 40 | } 41 | return end - (current + 1); 42 | } 43 | 44 | static inline void hex_dump_char(char c) 45 | { 46 | unsigned char uc = (unsigned char)c; 47 | char hex_digits[] = "0123456789ABCDEF"; 48 | char buf[3]; 49 | 50 | buf[0] = hex_digits[uc / 16]; 51 | buf[1] = hex_digits[uc % 16]; 52 | buf[2] = ' '; 53 | 54 | internal_write(STDOUT_FILENO, buf, 3); 55 | } 56 | 57 | void hex_dump_local(const void* restrict data, size_t size) 58 | { 59 | const char* byte_data = (const char*)data; 60 | 61 | for (size_t i = 0; i < size; i++) { 62 | hex_dump_char(byte_data[i]); 63 | 64 | if ((i + 1) % 16 == 0) { 65 | internal_write(STDOUT_FILENO, "\n", 1); 66 | } 67 | } 68 | 69 | if (size % 16 != 0) { 70 | internal_write(STDOUT_FILENO, "\n", 1); 71 | } 72 | } 73 | 74 | void log_dump_local(const int32_t fd, const char* restrict str) 75 | { 76 | if (is_null(str) || fd <= 0) { 77 | return; 78 | } 79 | 80 | size_t len = get_str_len(str); 81 | if (len == 0) { 82 | return; 83 | } 84 | 85 | (void)internal_write(fd, str, len); 86 | } 87 | 88 | void var_dump_local(const int32_t fd, const char* restrict str, const int32_t value) 89 | { 90 | if (is_null(str) || fd <= 0) { 91 | return; 92 | } 93 | 94 | size_t len = get_str_len(str); 95 | if (len == 0) { 96 | return; 97 | } 98 | 99 | (void)internal_write(fd, str, len); 100 | 101 | char buffer[32]; 102 | size_t buffer_size = safe_itoa(value, buffer, sizeof(buffer)); 103 | buffer[buffer_size] = '\n'; 104 | buffer[buffer_size + 1] = '\0'; 105 | 106 | (void)internal_write(fd, buffer, buffer_size + 1); 107 | } 108 | 109 | void str_dump_local(const int32_t fd, const char* restrict str, const char* restrict value) 110 | { 111 | if (is_null(str) || is_null(value) || fd <= 0) { 112 | return; 113 | } 114 | 115 | log_dump(fd, str); 116 | log_dump(fd, value); 117 | log_dump(fd, "\n"); 118 | } 119 | -------------------------------------------------------------------------------- /src/crypto/base64.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_BASE64_H_ 2 | #define NOSTR_BASE64_H_ 3 | 4 | #include "../util/string.h" 5 | 6 | #define base64_padding_char ('=') 7 | #define base64_input_block_size 3 8 | #define base64_output_block_size 4 9 | #define base64_num_of_char_bit 6 10 | #define is_base64_char(c) \ 11 | (is_lower_char(c) || is_upper_char(c) || is_number_char(c) || c == '+' || c == '/') 12 | #define base64_get_array_value(array, offset, capacity) \ 13 | ((offset < capacity) ? array[offset] : 0) 14 | #define base64_char_mask \ 15 | ((1 << base64_num_of_char_bit) - 1) 16 | #define base64_out_char_index(marge, input_index) \ 17 | ((marge >> ((base64_output_block_size - input_index - 1) * base64_num_of_char_bit)) & base64_char_mask) 18 | 19 | static inline bool base64_encode(const uint8_t* input, const size_t input_length, char* output, const size_t output_capacity) 20 | { 21 | const char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 22 | size_t input_offset; 23 | size_t output_offset; 24 | 25 | // minimum input length : 1byte 26 | // minimum output capacity: 5byte(4byte + \0) 27 | if (is_null(input) || is_null(output) || input_length <= 0 || output_capacity <= base64_output_block_size) { 28 | return false; 29 | } 30 | 31 | for ( 32 | input_offset = 0, output_offset = 0; 33 | (input_offset < input_length) && (output_offset < output_capacity - base64_output_block_size); 34 | input_offset += base64_input_block_size, output_offset += base64_output_block_size) { 35 | uint32_t marge = 36 | (base64_get_array_value(input, input_offset + 0, input_length) << 16) | 37 | (base64_get_array_value(input, input_offset + 1, input_length) << 8) | 38 | (base64_get_array_value(input, input_offset + 2, input_length) << 0); 39 | 40 | output[output_offset + 0] = base64_chars[base64_out_char_index(marge, 0)]; 41 | output[output_offset + 1] = base64_chars[base64_out_char_index(marge, 1)]; 42 | output[output_offset + 2] = base64_chars[base64_out_char_index(marge, 2)]; 43 | output[output_offset + 3] = base64_chars[base64_out_char_index(marge, 3)]; 44 | } 45 | 46 | int32_t input_length_mod = input_length % base64_input_block_size; 47 | int32_t padding_count = (input_length_mod) ? (base64_input_block_size - input_length_mod) : 0; 48 | for (size_t i = 1; i <= padding_count; i++) { 49 | output[output_offset - i] = base64_padding_char; 50 | } 51 | 52 | output[output_offset] = '\0'; 53 | return true; 54 | } 55 | 56 | static inline bool is_base64(const char* str) 57 | { 58 | size_t len = get_str_len(str); 59 | if (len < base64_output_block_size) { 60 | return false; 61 | } 62 | 63 | size_t padding_count = 0; 64 | for (size_t i = 1; i <= 2; i++) { 65 | if (str[len - i] == base64_padding_char) { 66 | padding_count++; 67 | } 68 | } 69 | 70 | size_t base64_length = len - padding_count; 71 | 72 | // base64 char check 73 | for (size_t i = 0; i < base64_length; i++) { 74 | if (!is_base64_char(str[i])) { 75 | return false; 76 | } 77 | } 78 | 79 | return true; 80 | } 81 | 82 | #endif 83 | -------------------------------------------------------------------------------- /src/websocket/socket/optimize_socket.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_OPTIMIZE_SOCKET_H_ 2 | #define NOSTR_OPTIMIZE_SOCKET_H_ 3 | 4 | #include "../../arch/optimize_socket.h" 5 | #include "../websocket_local.h" 6 | 7 | static inline int32_t set_nonblocking(int32_t fd) 8 | { 9 | int32_t flags = internal_fcntl(fd, F_GETFL, 0); 10 | if (flags == WEBSOCKET_SYSCALL_ERROR) { 11 | return WEBSOCKET_SYSCALL_ERROR; // Failed to get flags 12 | } 13 | 14 | return internal_fcntl(fd, F_SETFL, flags | O_NONBLOCK); 15 | } 16 | 17 | static inline int32_t optimize_client_socket(int32_t client_sock) 18 | { 19 | int32_t rtn = WEBSOCKET_ERRORCODE_NONE; 20 | int32_t param; 21 | 22 | param = 1; 23 | if (internal_setsockopt(client_sock, IPPROTO_TCP, TCP_NODELAY, ¶m, sizeof(param)) == WEBSOCKET_SYSCALL_ERROR) { 24 | str_error("Failed to setsockopt[IPPROTO_TCP/TCP_NODELAY] reason : ", strerror(errno)); 25 | rtn = WEBSOCKET_ERRORCODE_FATAL_ERROR; 26 | goto FINALIZE; 27 | } 28 | 29 | param = 1; 30 | if (internal_setsockopt(client_sock, SOL_SOCKET, SO_REUSEADDR, ¶m, sizeof(param)) == WEBSOCKET_SYSCALL_ERROR) { 31 | str_error("Failed to setsockopt[SOL_SOCKET/SO_REUSEADDR] reason : ", strerror(errno)); 32 | rtn = WEBSOCKET_ERRORCODE_FATAL_ERROR; 33 | goto FINALIZE; 34 | } 35 | 36 | #if defined(SO_REUSEPORT) 37 | param = 1; 38 | if (internal_setsockopt(client_sock, SOL_SOCKET, SO_REUSEPORT, ¶m, sizeof(param)) == WEBSOCKET_SYSCALL_ERROR) { 39 | str_error("Failed to setsockopt[SOL_SOCKET/SO_REUSEPORT] reason : ", strerror(errno)); 40 | rtn = WEBSOCKET_ERRORCODE_FATAL_ERROR; 41 | goto FINALIZE; 42 | } 43 | #endif 44 | 45 | FINALIZE: 46 | return rtn; 47 | } 48 | 49 | static inline int32_t optimize_server_socket(int32_t server_sock) 50 | { 51 | int32_t rtn = WEBSOCKET_ERRORCODE_NONE; 52 | int32_t param = 1; 53 | 54 | param = 1; 55 | if (internal_setsockopt(server_sock, IPPROTO_TCP, TCP_NODELAY, ¶m, sizeof(param)) == WEBSOCKET_SYSCALL_ERROR) { 56 | str_error("Failed to setsockopt[IPPROTO_TCP/TCP_NODELAY] reason : ", strerror(errno)); 57 | rtn = WEBSOCKET_ERRORCODE_FATAL_ERROR; 58 | goto FINALIZE; 59 | } 60 | 61 | param = 1; 62 | if (internal_setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, ¶m, sizeof(param)) == WEBSOCKET_SYSCALL_ERROR) { 63 | str_error("Failed to setsockopt[SOL_SOCKET/SO_REUSEADDR] reason : ", strerror(errno)); 64 | rtn = WEBSOCKET_ERRORCODE_FATAL_ERROR; 65 | goto FINALIZE; 66 | } 67 | 68 | #if defined(SO_REUSEPORT) 69 | param = 1; 70 | if (internal_setsockopt(server_sock, SOL_SOCKET, SO_REUSEPORT, ¶m, sizeof(param)) == WEBSOCKET_SYSCALL_ERROR) { 71 | str_error("Failed to setsockopt[SOL_SOCKET/SO_REUSEPORT] reason : ", strerror(errno)); 72 | rtn = WEBSOCKET_ERRORCODE_FATAL_ERROR; 73 | goto FINALIZE; 74 | } 75 | #endif 76 | 77 | #ifndef __APPLE__ 78 | param = 5; 79 | if (internal_setsockopt(server_sock, IPPROTO_TCP, TCP_FASTOPEN, ¶m, sizeof(param)) == WEBSOCKET_SYSCALL_ERROR) { 80 | str_error("Failed to setsockopt[IPPROTO_TCP/TCP_FASTOPEN] reason : ", strerror(errno)); 81 | rtn = WEBSOCKET_ERRORCODE_FATAL_ERROR; 82 | goto FINALIZE; 83 | } 84 | #endif 85 | 86 | FINALIZE: 87 | return rtn; 88 | } 89 | 90 | #endif 91 | -------------------------------------------------------------------------------- /src/arch/darwin/kqueue.c: -------------------------------------------------------------------------------- 1 | #ifdef __APPLE__ 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "../../util/signal.h" 13 | #include "../../util/types.h" 14 | #include "../../websocket/websocket_local.h" 15 | #include "./epoll.h" 16 | 17 | bool websocket_epoll_add(const int32_t epoll_fd, const int32_t sock_fd, PWebSocketEpollEvent event) 18 | { 19 | EV_SET(event, sock_fd, EVFILT_READ, EV_ADD, 0, 0, NULL); 20 | 21 | while (1) { 22 | if (is_rise_signal()) { 23 | log_info("A signal was raised during kevent(). The system will abort processing.\n"); 24 | return false; 25 | } 26 | 27 | if (kevent(epoll_fd, event, 1, NULL, 0, NULL) != WEBSOCKET_SYSCALL_ERROR) { 28 | break; 29 | } 30 | 31 | if (errno != EINTR) { 32 | str_error("Failed to kevent(EV_ADD | EV_CLEAR). reason : ", strerror(errno)); 33 | return false; 34 | } 35 | } 36 | 37 | return true; 38 | } 39 | 40 | bool websocket_epoll_del(const int32_t epoll_fd, const int32_t sock_fd) 41 | { 42 | struct kevent event; 43 | EV_SET(&event, sock_fd, EVFILT_READ, EV_DELETE, 0, 0, NULL); 44 | 45 | if (kevent(epoll_fd, &event, 1, NULL, 0, NULL) < 0) { 46 | str_error("Failed to kevent(EV_DELETE). reason : ", strerror(errno)); 47 | return false; 48 | } 49 | 50 | return true; 51 | } 52 | 53 | int32_t websocket_epoll_create() 54 | { 55 | int32_t epoll_fd = kqueue(); 56 | if (epoll_fd == -1) { 57 | str_error("Failed to kqueue(). reason : ", strerror(errno)); 58 | return WEBSOCKET_ERRORCODE_FATAL_ERROR; 59 | } 60 | 61 | return epoll_fd; 62 | } 63 | 64 | int32_t websocket_epoll_wait(const int32_t epoll_fd, PWebSocketEpollEvent events, const int32_t max_events) 65 | { 66 | if (is_rise_signal()) { 67 | log_info("A signal was raised during kevent(). The system will abort processing.\n"); 68 | return WEBSOCKET_ERRORCODE_FATAL_ERROR; 69 | } 70 | 71 | struct timespec timeout = {0, 0}; // Non-blocking 72 | int32_t num_of_event = kevent(epoll_fd, NULL, 0, events, max_events, &timeout); 73 | 74 | if (num_of_event < 0) { 75 | if (errno == EINTR || errno == EAGAIN) { 76 | return WEBSOCKET_ERRORCODE_CONTINUABLE_ERROR; 77 | } 78 | 79 | str_error("Failed to kevent(). reason : ", strerror(errno)); 80 | log_error("The system will abort processing.\n"); 81 | return WEBSOCKET_ERRORCODE_FATAL_ERROR; 82 | } 83 | 84 | return num_of_event; 85 | } 86 | 87 | int32_t websocket_epoll_getfd(PWebSocketEpollEvent event) 88 | { 89 | return event->ident; 90 | } 91 | 92 | int32_t websocket_epoll_rise_error(PWebSocketEpollEvent event) 93 | { 94 | if (event->flags & EV_ERROR) { 95 | str_error("EV_ERROR : ", strerror((int)event->data)); 96 | return WEBSOCKET_ERRORCODE_SOCKET_CLOSE_ERROR; 97 | } 98 | 99 | return WEBSOCKET_ERRORCODE_NONE; 100 | } 101 | 102 | int32_t websocket_epoll_rise_input(PWebSocketEpollEvent event) 103 | { 104 | if (!(event->filter | EVFILT_READ)) { 105 | return WEBSOCKET_ERRORCODE_CONTINUABLE_ERROR; 106 | } 107 | 108 | return WEBSOCKET_ERRORCODE_NONE; 109 | } 110 | 111 | #endif 112 | -------------------------------------------------------------------------------- /examples/echoback/Makefile: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # File : Malefile 3 | # Description : Template of makefile. 4 | # Author : hakkadaikon 5 | ############################################################################### 6 | 7 | #------------------------------------------------------------------------------ 8 | # Program name 9 | #------------------------------------------------------------------------------ 10 | PROGRAM := wsserver 11 | BUILD := release 12 | 13 | #------------------------------------------------------------------------------ 14 | # Compiler name 15 | #------------------------------------------------------------------------------ 16 | OS := $(shell uname -s) 17 | ifeq ($(OS), Darwin) 18 | CC := clang 19 | else 20 | CC := gcc 21 | endif 22 | 23 | #------------------------------------------------------------------------------ 24 | # Directories 25 | #------------------------------------------------------------------------------ 26 | SRCROOT := . 27 | OBJROOT := ./obj 28 | BINROOT := ./bin 29 | LDLIBS := -L../../build/lib 30 | 31 | #------------------------------------------------------------------------------ 32 | # Files 33 | #------------------------------------------------------------------------------ 34 | SRCFILES := $(shell find $(SRCROOT) -name "*.c") 35 | OBJFILES := $(addprefix $(OBJROOT)/, $(SRCFILES:.c=.o)) 36 | DEPENDFILES := $(OBJFILES:.o=.d) 37 | 38 | #------------------------------------------------------------------------------ 39 | # Flags 40 | #------------------------------------------------------------------------------ 41 | ifeq ($(BUILD), debug) 42 | ifeq ($(OS), Darwin) 43 | CFLAGS := \ 44 | -I../../src \ 45 | -O0 \ 46 | -g \ 47 | -DLOG_LEVEL_DEBUG 48 | 49 | LDFLAGS := -lwsserver 50 | else 51 | CFLAGS := \ 52 | -I../../src \ 53 | -O0 \ 54 | -static-libasan \ 55 | -g \ 56 | -fPIC \ 57 | -DLOG_LEVEL_DEBUG 58 | 59 | LDFLAGS := -lwsserver 60 | endif 61 | 62 | else 63 | ifeq ($(BUILD), release) 64 | ifeq ($(OS), Darwin) 65 | CFLAGS := \ 66 | -I../../src \ 67 | -O3 \ 68 | -flto=auto \ 69 | -mtune=native \ 70 | -ffast-math \ 71 | -fno-math-errno \ 72 | -falign-functions \ 73 | -DLOG_LEVEL_ERROR 74 | 75 | LDFLAGS := -lwsserver 76 | else 77 | CFLAGS := \ 78 | -I../../src \ 79 | -O3 \ 80 | -fno-lto \ 81 | -mtune=native \ 82 | -ffast-math \ 83 | -fno-math-errno \ 84 | -falign-functions \ 85 | -fPIC \ 86 | -DLOG_LEVEL_ERROR 87 | endif 88 | 89 | LDFLAGS := -lwsserver 90 | endif 91 | endif 92 | 93 | #------------------------------------------------------------------------------ 94 | # Make rules 95 | #------------------------------------------------------------------------------ 96 | $(BINROOT)/$(PROGRAM): $(OBJFILES) 97 | $(CC) $(CFLAGS) -o $@ $^ $(LDLIBS) $(LDFLAGS) 98 | 99 | $(OBJROOT)/%.o: $(SRCROOT)/%.c 100 | if [ ! -e `dirname $@` ]; then mkdir -p `dirname $@`; fi 101 | $(CC) $(CFLAGS) $(INCLUDE) -o $@ -c $< 102 | 103 | #------------------------------------------------------------------------------ 104 | # Build options 105 | #------------------------------------------------------------------------------ 106 | .PHONY: all clean 107 | 108 | all: 109 | $(OBJROOT)/$(PROGRAM) 110 | 111 | clean: 112 | rm -f $(BINROOT)/$(PROGRAM) 113 | rm -rf $(DEPENDFILES) 114 | 115 | ifneq "$(MAKECMDGOALS)" "clean" 116 | -include $(DEPENDFILES) 117 | endif 118 | -------------------------------------------------------------------------------- /src/websocket/socket/epoll.c: -------------------------------------------------------------------------------- 1 | #ifndef __APPLE__ 2 | 3 | #include "../../arch/epoll.h" 4 | 5 | #include "../websocket_local.h" 6 | #include "./optimize_socket.h" 7 | 8 | static int32_t get_epoll_wait_err(const int32_t num_of_event); 9 | 10 | bool websocket_epoll_add(const int32_t epoll_fd, const int32_t sock_fd, PWebSocketEpollEvent event) 11 | { 12 | event->data.fd = sock_fd; 13 | event->events = EPOLLIN | EPOLLHUP | EPOLLERR | EPOLLRDHUP | EPOLLET; 14 | 15 | if (is_rise_signal()) { 16 | log_info("A signal was raised during epoll_wait(). The system will abort processing.\n"); 17 | return false; 18 | } 19 | 20 | if (internal_epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sock_fd, event) == WEBSOCKET_SYSCALL_ERROR) { 21 | str_error("Failed to epoll_ctl(CTL_ADD). reason : ", strerror(errno)); 22 | return false; 23 | } 24 | 25 | return true; 26 | } 27 | 28 | bool websocket_epoll_del(const int32_t epoll_fd, const int32_t sock_fd) 29 | { 30 | if (internal_epoll_ctl(epoll_fd, EPOLL_CTL_DEL, sock_fd, NULL) == WEBSOCKET_SYSCALL_ERROR) { 31 | str_error("Failed to epoll_ctl(CTL_DEL). reason : ", strerror(errno)); 32 | return false; 33 | } 34 | 35 | return true; 36 | } 37 | 38 | int32_t websocket_epoll_create() 39 | { 40 | int32_t epoll_fd = internal_epoll_create1(0); 41 | if (epoll_fd == WEBSOCKET_SYSCALL_ERROR) { 42 | str_error("Failed to epoll_create1(). reason : ", strerror(errno)); 43 | return WEBSOCKET_ERRORCODE_FATAL_ERROR; 44 | } 45 | 46 | if (set_nonblocking(epoll_fd) == WEBSOCKET_SYSCALL_ERROR) { 47 | str_error("Failed to fnctl - epoll_create1(). reason : ", strerror(errno)); 48 | return WEBSOCKET_ERRORCODE_FATAL_ERROR; 49 | } 50 | 51 | return epoll_fd; 52 | } 53 | 54 | int32_t websocket_epoll_wait(const int32_t epoll_fd, PWebSocketEpollEvent events, const int32_t max_events) 55 | { 56 | if (is_rise_signal()) { 57 | log_info("A signal was raised during epoll_wait(). The system will abort processing.\n"); 58 | return WEBSOCKET_ERRORCODE_FATAL_ERROR; 59 | } 60 | 61 | int32_t errcode; 62 | int32_t num_of_event = internal_epoll_wait(epoll_fd, events, max_events, 0); 63 | if ((errcode = get_epoll_wait_err(num_of_event)) != WEBSOCKET_ERRORCODE_NONE) { 64 | return errcode; 65 | } 66 | 67 | return num_of_event; 68 | } 69 | 70 | static int32_t get_epoll_wait_err(const int32_t num_of_event) 71 | { 72 | if (num_of_event < 0) { 73 | if (errno == EINTR || errno == EAGAIN) { 74 | return WEBSOCKET_ERRORCODE_CONTINUABLE_ERROR; 75 | } 76 | 77 | str_error("Failed to epoll_wait(). reason : ", strerror(errno)); 78 | log_error("The system will abort processing.\n"); 79 | return WEBSOCKET_ERRORCODE_FATAL_ERROR; 80 | } 81 | 82 | return WEBSOCKET_ERRORCODE_NONE; 83 | } 84 | 85 | int32_t websocket_epoll_getfd(PWebSocketEpollEvent event) 86 | { 87 | return event->data.fd; 88 | } 89 | 90 | int32_t websocket_epoll_rise_error(PWebSocketEpollEvent event) 91 | { 92 | if (event->events & (EPOLLHUP | EPOLLERR | EPOLLRDHUP)) { 93 | return WEBSOCKET_ERRORCODE_SOCKET_CLOSE_ERROR; 94 | } 95 | 96 | return WEBSOCKET_ERRORCODE_NONE; 97 | } 98 | 99 | int32_t websocket_epoll_rise_input(PWebSocketEpollEvent event) 100 | { 101 | if (!(event->events & EPOLLIN)) { 102 | return WEBSOCKET_ERRORCODE_CONTINUABLE_ERROR; 103 | } 104 | 105 | return WEBSOCKET_ERRORCODE_NONE; 106 | } 107 | 108 | #endif 109 | -------------------------------------------------------------------------------- /src/websocket/websocket_local.h: -------------------------------------------------------------------------------- 1 | #ifndef _NOSTR_WEBSOCKET_LOCAL_H_ 2 | #define _NOSTR_WEBSOCKET_LOCAL_H_ 3 | 4 | /** 5 | * @file websocket_local.h 6 | * 7 | * @brief Parses each parameter of a websocket frame stored in network byte order. 8 | * @see RFC6455 (https://datatracker.ietf.org/doc/html/rfc6455) 9 | */ 10 | 11 | #ifdef __APPLE__ 12 | #include "../arch/darwin/epoll.h" 13 | #else 14 | #include "../arch/linux/epoll.h" 15 | #endif 16 | #include "../http/http.h" 17 | #include "../util/signal.h" 18 | #include "../util/string.h" 19 | #include "websocket.h" 20 | 21 | typedef struct { 22 | int32_t epoll_fd; 23 | uint8_t dummy[4]; 24 | PWebSocketEpollEvent event; 25 | } WebSocketEpollLoopArgs, *PWebSocketEpollLoopArgs; 26 | 27 | typedef struct { 28 | size_t capacity; 29 | char* request; 30 | char* response; 31 | uint8_t dummy[6]; 32 | } WebSocketRawBuffer, *PWebSocketRawBuffer; 33 | 34 | typedef enum { 35 | WEBSOCKET_SYSCALL_ERROR = -1 36 | } WebSocketSysCallErrorCode; 37 | 38 | /*----------------------------------------------------------------------------*/ 39 | /* websocket/server/handshake.c */ 40 | /*----------------------------------------------------------------------------*/ 41 | 42 | bool client_handshake( 43 | const int32_t client_sock, 44 | const size_t bytes_read, 45 | PWebSocketRawBuffer buffer, 46 | PHTTPRequest request); 47 | 48 | /*----------------------------------------------------------------------------*/ 49 | /* websocket/crypto.c */ 50 | /*----------------------------------------------------------------------------*/ 51 | 52 | bool generate_websocket_acceptkey(const char* client_key, const size_t accept_key_size, char* accept_key); 53 | 54 | /*----------------------------------------------------------------------------*/ 55 | /* websocket/socket/accept.c */ 56 | /*----------------------------------------------------------------------------*/ 57 | 58 | int32_t websocket_accept(const int32_t sock_fd); 59 | 60 | /*----------------------------------------------------------------------------*/ 61 | /* websocket/socket/listen.c */ 62 | /*----------------------------------------------------------------------------*/ 63 | 64 | int32_t websocket_listen(const int32_t port_num, const int32_t backlog); 65 | 66 | /*----------------------------------------------------------------------------*/ 67 | /* websocket/socket/epoll.c or websocket/socket/kqueue.c */ 68 | /*----------------------------------------------------------------------------*/ 69 | 70 | bool websocket_epoll_add(const int32_t epoll_fd, const int32_t sock_fd, PWebSocketEpollEvent event); 71 | bool websocket_epoll_del(const int32_t epoll_fd, const int32_t sock_fd); 72 | int32_t websocket_epoll_create(); 73 | int32_t websocket_epoll_wait(const int32_t epoll_fd, PWebSocketEpollEvent events, const int32_t max_events); 74 | int32_t websocket_epoll_getfd(PWebSocketEpollEvent event); 75 | int32_t websocket_epoll_rise_error(PWebSocketEpollEvent event); 76 | int32_t websocket_epoll_rise_input(PWebSocketEpollEvent event); 77 | 78 | /*----------------------------------------------------------------------------*/ 79 | /* websocket/internal_log.c */ 80 | /*----------------------------------------------------------------------------*/ 81 | 82 | void websocket_frame_dump(PWebSocketFrame frame); 83 | void websocket_epoll_event_dump(const int32_t events); 84 | 85 | #endif 86 | -------------------------------------------------------------------------------- /src/arch/linux/sigaction_def.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_LINUX_SIGACTION_DEF_H_ 2 | #define NOSTR_LINUX_SIGACTION_DEF_H_ 3 | 4 | #include "../../util/types.h" 5 | 6 | typedef uint64_t sigset_t; 7 | typedef void signalfunc_t(int32_t); 8 | typedef void restorefunc_t(void); 9 | typedef signalfunc_t* sighandler_t; 10 | typedef restorefunc_t* sigrestore_t; 11 | 12 | struct sigaction { 13 | sighandler_t sa_handler; 14 | uint64_t sa_flags; 15 | sigrestore_t sa_restorer; 16 | sigset_t sa_mask; 17 | }; 18 | 19 | #ifndef SA_RESTORER 20 | #define SA_RESTORER 0x04000000 21 | #endif 22 | 23 | #ifndef SIGINT 24 | #define SIGINT 2 // Interactive attention signal. 25 | #endif 26 | 27 | #ifndef SIGILL 28 | #define SIGILL 4 // Illegal instruction. 29 | #endif 30 | 31 | #ifndef SIGABRT 32 | #define SIGABRT 6 // Abnormal termination. 33 | #endif 34 | 35 | #ifndef SIGFPE 36 | #define SIGFPE 8 // Erroneous arithmetic operation. 37 | #endif 38 | 39 | #ifndef SIGSEGV 40 | #define SIGSEGV 11 // Invalid access to storage. 41 | #endif 42 | 43 | #ifndef SIGTERM 44 | #define SIGTERM 15 // Termination request. 45 | #endif 46 | 47 | // Historical signals specified by POSIX. 48 | #ifndef SIGHUP 49 | #define SIGHUP 1 // Hangup. 50 | #endif 51 | 52 | #ifndef SIGQUIT 53 | #define SIGQUIT 3 // Quit. 54 | #endif 55 | 56 | #ifndef SIGTRAP 57 | #define SIGTRAP 5 // Trace/breakpoint trap. 58 | #endif 59 | 60 | #ifndef SIGKILL 61 | #define SIGKILL 9 // Killed. 62 | #endif 63 | 64 | #ifndef SIGBUS 65 | #define SIGBUS 10 // Bus error. 66 | #endif 67 | 68 | #ifndef SIGSYS 69 | #define SIGSYS 12 // Bad system call. 70 | #endif 71 | 72 | #ifndef SIGPIPE 73 | #define SIGPIPE 13 // Broken pipe. 74 | #endif 75 | 76 | #ifndef SIGALRM 77 | #define SIGALRM 14 // Alarm clock. 78 | #endif 79 | 80 | // New(er) POSIX signals (1003.1-2008, 1003.1-2013). 81 | #ifndef SIGURG 82 | #define SIGURG 16 // Urgent data is available at a socket. 83 | #endif 84 | 85 | #ifndef SIGSTOP 86 | #define SIGSTOP 17 // Stop, unblockable. 87 | #endif 88 | 89 | #ifndef SIGTSTP 90 | #define SIGTSTP 18 // Keyboard stop. 91 | #endif 92 | 93 | #ifndef SIGCONT 94 | #define SIGCONT 19 // Continue. 95 | #endif 96 | 97 | #ifndef SIGCHLD 98 | #define SIGCHLD 20 // Child terminated or stopped. 99 | #endif 100 | 101 | #ifndef SIGTTIN 102 | #define SIGTTIN 21 // Background read from control terminal. 103 | #endif 104 | 105 | #ifndef SIGTTOU 106 | #define SIGTTOU 22 // Background write to control terminal. 107 | #endif 108 | 109 | #ifndef SIGPOLL 110 | #define SIGPOLL 23 // Pollable event occurred (System V). 111 | #endif 112 | 113 | #ifndef SIGXCPU 114 | #define SIGXCPU 24 // CPU time limit exceeded. 115 | #endif 116 | 117 | #ifndef SIGXFSZ 118 | #define SIGXFSZ 25 // File size limit exceeded. 119 | #endif 120 | 121 | #ifndef SIGVTALRM 122 | #define SIGVTALRM 26 // Virtual timer expired. 123 | #endif 124 | 125 | #ifndef SIGPROF 126 | #define SIGPROF 27 // Profiling timer expired. 127 | #endif 128 | 129 | #ifndef SIGUSR1 130 | #define SIGUSR1 30 // User-defined signal 1. 131 | #endif 132 | 133 | #ifndef SIGUSR2 134 | #define SIGUSR2 31 // User-defined signal 2. 135 | #endif 136 | 137 | // Nonstandard signals found in all modern POSIX systems (including both BSD and Linux). 138 | #ifndef SIGWINCH 139 | #define SIGWINCH 28 // Window size change (4.3 BSD, Sun). 140 | #endif 141 | 142 | // Archaic names for compatibility. 143 | #ifndef SIGIO 144 | #define SIGIO SIGPOLL // I/O now possible (4.2 BSD). 145 | #endif 146 | 147 | #ifndef SIGIOT 148 | #define SIGIOT SIGABRT // IOT instruction, abort() on a PDP-11. 149 | #endif 150 | 151 | #ifndef SIGCLD 152 | #define SIGCLD SIGCHLD // Old System V name 153 | #endif 154 | 155 | #endif 156 | -------------------------------------------------------------------------------- /src/websocket/server/loop.c: -------------------------------------------------------------------------------- 1 | #include "../../util/allocator.h" 2 | #include "../websocket_local.h" 3 | #include "accept/epoll_accept.h" 4 | #include "receive/epoll_receive.h" 5 | 6 | bool websocket_server_loop(PWebSocketLoopArgs args) 7 | { 8 | int32_t epoll_fd = websocket_epoll_create(); 9 | if (epoll_fd < 0) { 10 | return false; 11 | } 12 | 13 | const size_t MAX_EVENTS = 16384; 14 | WebSocketEpollEvent register_event; 15 | WebSocketEpollEvent epoll_events[MAX_EVENTS]; 16 | WebSocketRawBuffer buffer; 17 | 18 | websocket_memset(®ister_event, 0x00, sizeof(register_event)); 19 | websocket_memset(epoll_events, 0x00, sizeof(epoll_events)); 20 | 21 | if (!websocket_epoll_add(epoll_fd, args->server_sock, ®ister_event)) { 22 | websocket_close(epoll_fd); 23 | return false; 24 | } 25 | 26 | var_debug("websocket server fd : ", args->server_sock); 27 | var_debug("websocket epoll fd : ", epoll_fd); 28 | var_debug("buffer capacity : ", args->buffer_capacity); 29 | 30 | buffer.capacity = args->buffer_capacity; 31 | buffer.request = websocket_alloc(buffer.capacity); 32 | buffer.response = websocket_alloc(buffer.capacity); 33 | 34 | if (!buffer.request || !buffer.response) { 35 | log_error("Failed to allocate buffers\n"); 36 | websocket_close(epoll_fd); 37 | return false; 38 | } 39 | 40 | websocket_memset(buffer.request, 0x00, buffer.capacity); 41 | websocket_memset(buffer.response, 0x00, buffer.capacity); 42 | 43 | while (1) { 44 | int32_t num_of_events = websocket_epoll_wait(epoll_fd, epoll_events, MAX_EVENTS); 45 | if (num_of_events <= 0) { 46 | if (num_of_events != WEBSOCKET_ERRORCODE_FATAL_ERROR) { 47 | continue; 48 | } 49 | 50 | log_debug("epoll_wait error. go to finalize...\n"); 51 | goto FINALIZE; 52 | } 53 | 54 | var_debug("Escaped from epoll wait. num of events: ", num_of_events); 55 | 56 | for (int32_t i = 0; i < num_of_events; ++i) { 57 | WebSocketEpollLoopArgs epoll_args; 58 | epoll_args.event = &epoll_events[i]; 59 | epoll_args.epoll_fd = epoll_fd; 60 | 61 | int32_t fd = websocket_epoll_getfd(epoll_args.event); 62 | 63 | if (fd == args->server_sock) { 64 | if (epoll_accept( 65 | &epoll_args, 66 | args->server_sock, 67 | &buffer, 68 | ®ister_event, 69 | &args->callbacks) == WEBSOCKET_ERRORCODE_FATAL_ERROR) { 70 | log_debug("accept error. go to finalize...\n"); 71 | goto FINALIZE; 72 | } 73 | 74 | continue; 75 | } 76 | 77 | int32_t client_sock = fd; 78 | 79 | int32_t ret = epoll_receive(&epoll_args, &buffer, &args->callbacks); 80 | 81 | if (ret == WEBSOCKET_ERRORCODE_SOCKET_CLOSE_ERROR) { 82 | log_debug("socket close...\n"); 83 | 84 | websocket_epoll_del(epoll_fd, client_sock); 85 | websocket_close(client_sock); 86 | 87 | if (!is_null(args->callbacks.disconnect_callback)) { 88 | args->callbacks.disconnect_callback(client_sock); 89 | } 90 | 91 | continue; 92 | } 93 | 94 | if (ret == WEBSOCKET_ERRORCODE_FATAL_ERROR) { 95 | log_debug("receive error. go to finalize...\n"); 96 | 97 | websocket_epoll_del(epoll_fd, client_sock); 98 | websocket_close(client_sock); 99 | 100 | if (!is_null(args->callbacks.disconnect_callback)) { 101 | args->callbacks.disconnect_callback(client_sock); 102 | } 103 | 104 | goto FINALIZE; 105 | } 106 | } 107 | } 108 | 109 | FINALIZE: 110 | websocket_epoll_del(epoll_fd, args->server_sock); 111 | websocket_close(epoll_fd); 112 | 113 | // Wipe buffer 114 | websocket_memset_s(buffer.request, buffer.capacity, 0x00, buffer.capacity); 115 | websocket_memset_s(buffer.response, buffer.capacity, 0x00, buffer.capacity); 116 | 117 | // Free buffer 118 | websocket_free(buffer.request); 119 | websocket_free(buffer.response); 120 | 121 | return true; 122 | } 123 | -------------------------------------------------------------------------------- /src/http/http.c: -------------------------------------------------------------------------------- 1 | #include "http.h" 2 | 3 | #include "../util/string.h" 4 | 5 | static inline size_t extract_keyword( 6 | const char* restrict buffer, 7 | const size_t buffer_size, 8 | const char token, 9 | char* restrict output) 10 | { 11 | int32_t keyword_length = skip_token(buffer, buffer_size, token); 12 | if (keyword_length == -1) { 13 | return -1; 14 | } 15 | 16 | websocket_memcpy(output, buffer, keyword_length); 17 | output[keyword_length] = '\0'; 18 | return keyword_length; 19 | } 20 | 21 | static inline bool extract_http_request_line( 22 | const char* restrict buffer, 23 | const size_t buffer_size, 24 | PHTTPRequestLine restrict line) 25 | { 26 | size_t remain_buffer_size = buffer_size; 27 | int32_t keyword_length; 28 | 29 | // method 30 | keyword_length = extract_keyword(buffer, remain_buffer_size, ' ', line->method); 31 | if (keyword_length == -1) { 32 | return false; 33 | } 34 | buffer += (keyword_length + 1); 35 | remain_buffer_size -= (keyword_length + 1); 36 | 37 | // target 38 | keyword_length = extract_keyword(buffer, remain_buffer_size, ' ', line->target); 39 | if (keyword_length == -1) { 40 | return false; 41 | } 42 | buffer += (keyword_length + 1); 43 | remain_buffer_size -= (keyword_length + 1); 44 | 45 | // HTTP version 46 | keyword_length = extract_keyword(buffer, remain_buffer_size, '\r', line->http_version); 47 | if (keyword_length == -1) { 48 | return false; 49 | } 50 | 51 | return true; 52 | } 53 | 54 | static inline bool extract_http_request_header_line( 55 | const char* restrict buffer, 56 | const size_t buffer_size, 57 | PHTTPRequestHeaderLine restrict line) 58 | { 59 | size_t remain_buffer_size = buffer_size; 60 | int32_t keyword_length; 61 | 62 | // Key 63 | keyword_length = extract_keyword(buffer, remain_buffer_size, ':', line->key); 64 | if (keyword_length == -1) { 65 | return false; 66 | } 67 | buffer += (keyword_length + 2); 68 | remain_buffer_size -= (keyword_length + 2); 69 | 70 | // Value 71 | keyword_length = extract_keyword(buffer, remain_buffer_size, '\r', line->value); 72 | if (keyword_length == -1) { 73 | return false; 74 | } 75 | 76 | return true; 77 | } 78 | 79 | bool extract_http_request_header( 80 | const char* restrict buffer, 81 | const size_t buffer_size, 82 | size_t* header_size, 83 | HTTPRequestHeaderLine lines[]) 84 | { 85 | size_t buffer_pos = 0; 86 | size_t header_pos = 0; 87 | size_t remain_buffer_size = buffer_size; 88 | 89 | while ((buffer_pos < buffer_size) && (header_pos < HTTP_HEADER_CAPACITY)) { 90 | const char* current_buffer = &buffer[buffer_pos]; 91 | PHTTPRequestHeaderLine current_line = &lines[header_pos]; 92 | 93 | if (!extract_http_request_header_line(current_buffer, remain_buffer_size, current_line)) { 94 | return false; 95 | } 96 | 97 | size_t new_pos = skip_next_line(current_buffer, remain_buffer_size); 98 | buffer_pos += new_pos; 99 | remain_buffer_size -= new_pos; 100 | header_pos++; 101 | 102 | if (buffer_pos + 2 <= buffer_size) { 103 | if (buffer[buffer_pos] == '\r' && buffer[buffer_pos + 1] == '\n') { 104 | break; 105 | } 106 | } 107 | } 108 | 109 | if (header_pos <= 0) { 110 | return false; 111 | } 112 | 113 | *header_size = header_pos; 114 | return true; 115 | } 116 | 117 | bool extract_http_request( 118 | const char* restrict buffer, 119 | const size_t buffer_size, 120 | PHTTPRequest restrict request) 121 | { 122 | if ( 123 | is_null(buffer) || 124 | is_null((char*)request->headers) || 125 | buffer_size <= 0) { 126 | return false; 127 | } 128 | 129 | size_t remain_buffer_size = buffer_size; 130 | if (!extract_http_request_line(buffer, remain_buffer_size, &request->line)) { 131 | return false; 132 | } 133 | 134 | size_t new_pos = skip_next_line(buffer, remain_buffer_size); 135 | if (new_pos == remain_buffer_size) { 136 | return false; 137 | } 138 | remain_buffer_size -= new_pos; 139 | 140 | if (!extract_http_request_header( 141 | &buffer[new_pos], 142 | remain_buffer_size, 143 | &request->header_size, 144 | request->headers)) { 145 | return false; 146 | } 147 | 148 | return true; 149 | } 150 | -------------------------------------------------------------------------------- /tests/crypto/sha1.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | extern "C" { 10 | #include "crypto/sha1.h" 11 | } 12 | 13 | namespace 14 | { 15 | 16 | static std::vector HexToBytes(const std::string& hex) 17 | { 18 | std::vector out; 19 | out.reserve(hex.size() / 2); 20 | 21 | auto is_space = [](char c) { 22 | return c == ' ' || c == '\n' || c == '\r' || c == '\t'; 23 | }; 24 | 25 | auto hexval = [](char c) -> int { 26 | if (c >= '0' && c <= '9') return c - '0'; 27 | if (c >= 'a' && c <= 'f') return 10 + (c - 'a'); 28 | if (c >= 'A' && c <= 'F') return 10 + (c - 'A'); 29 | return -1; 30 | }; 31 | 32 | for (size_t i = 0; i < hex.size();) { 33 | // skip whitespace 34 | while (i < hex.size() && is_space(hex[i])) 35 | ++i; 36 | if (i >= hex.size()) break; 37 | if (i + 1 >= hex.size()) break; // odd length after trimming; ignore last nibble 38 | char c1 = hex[i++]; 39 | // skip whitespace between nibbles if any (robustness) 40 | while (i < hex.size() && is_space(hex[i])) 41 | ++i; 42 | if (i >= hex.size()) break; 43 | char c2 = hex[i++]; 44 | int v1 = hexval(c1); 45 | int v2 = hexval(c2); 46 | if (v1 < 0 || v2 < 0) { 47 | // skip invalid pair 48 | continue; 49 | } 50 | out.push_back(static_cast((v1 << 4) | v2)); 51 | } 52 | return out; 53 | } 54 | 55 | static std::string BytesToHex(const uint8_t* bytes, size_t len) 56 | { 57 | static const char* kHex = "0123456789abcdef"; 58 | std::string s; 59 | s.resize(len * 2); 60 | for (size_t i = 0; i < len; ++i) { 61 | s[2 * i + 0] = kHex[(bytes[i] >> 4) & 0xF]; 62 | s[2 * i + 1] = kHex[(bytes[i] >> 0) & 0xF]; 63 | } 64 | return s; 65 | } 66 | 67 | TEST(Sha1Test, KnownVector_abc) 68 | { 69 | const std::string msg = "abc"; 70 | const char* expected_hex = 71 | "A9993E364706816ABA3E25717850C26C9CD0D89D"; 72 | 73 | uint8_t digest[SHA1_DIGEST_LENGTH] = {0}; 74 | sha1(msg.c_str(), msg.size(), digest); 75 | 76 | const auto expected = HexToBytes(expected_hex); 77 | ASSERT_EQ(expected.size(), static_cast(SHA1_DIGEST_LENGTH)); 78 | EXPECT_TRUE(std::equal(expected.begin(), expected.end(), digest)) 79 | << "got: " << BytesToHex(digest, SHA1_DIGEST_LENGTH); 80 | } 81 | 82 | TEST(Sha1Test, KnownVector_abcdbcdecdefdefg) 83 | { 84 | const std::string msg = 85 | "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"; 86 | const char* expected_hex = 87 | "84983E441C3BD26EBAAE4AA1F95129E5E54670F1"; 88 | 89 | uint8_t digest[SHA1_DIGEST_LENGTH] = {0}; 90 | sha1(msg.c_str(), msg.size(), digest); 91 | 92 | const auto expected = HexToBytes(expected_hex); 93 | ASSERT_EQ(expected.size(), static_cast(SHA1_DIGEST_LENGTH)); 94 | EXPECT_TRUE(std::equal(expected.begin(), expected.end(), digest)) 95 | << "got: " << BytesToHex(digest, SHA1_DIGEST_LENGTH); 96 | } 97 | 98 | TEST(Sha1Test, KnownVector_MillionA) 99 | { 100 | const std::string msg(1000000, 'a'); 101 | const char* expected_hex = 102 | "34AA973CD4C4DAA4F61EEB2BDBAD27316534016F"; 103 | 104 | uint8_t digest[SHA1_DIGEST_LENGTH] = {0}; 105 | sha1(msg.c_str(), msg.size(), digest); 106 | 107 | const auto expected = HexToBytes(expected_hex); 108 | ASSERT_EQ(expected.size(), static_cast(SHA1_DIGEST_LENGTH)); 109 | EXPECT_TRUE(std::equal(expected.begin(), expected.end(), digest)) 110 | << "got: " << BytesToHex(digest, SHA1_DIGEST_LENGTH); 111 | } 112 | 113 | TEST(Sha1Test, KnownVector_Empty) 114 | { 115 | const std::string msg = ""; 116 | const char* expected_hex = 117 | "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709"; // SHA1("") 118 | 119 | uint8_t digest[SHA1_DIGEST_LENGTH] = {0}; 120 | sha1(msg.c_str(), msg.size(), digest); 121 | 122 | const auto expected = HexToBytes(expected_hex); 123 | ASSERT_EQ(expected.size(), static_cast(SHA1_DIGEST_LENGTH)); 124 | EXPECT_TRUE(std::equal(expected.begin(), expected.end(), digest)) 125 | << "got: " << BytesToHex(digest, SHA1_DIGEST_LENGTH); 126 | } 127 | 128 | } // namespace 129 | -------------------------------------------------------------------------------- /src/arch/linux/sockoption.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_LINUX_SOCK_OPTION_H_ 2 | #define NOSTR_LINUX_SOCK_OPTION_H_ 3 | 4 | #define SOL_SOCKET 1 5 | #define SO_REUSEADDR 2 6 | #define SO_REUSEPORT 15 7 | 8 | enum { 9 | IPPROTO_IP = 0, // Dummy protocol for TCP. 10 | IPPROTO_ICMP = 1, // Internet Control Message Protocol. 11 | IPPROTO_IGMP = 2, // Internet Group Management Protocol. 12 | IPPROTO_IPIP = 4, // IPIP tunnels (older KA9Q tunnels use 94). 13 | IPPROTO_TCP = 6, // Transmission Control Protocol. 14 | IPPROTO_EGP = 8, // Exterior Gateway Protocol. 15 | IPPROTO_PUP = 12, // PUP protocol. 16 | IPPROTO_UDP = 17, // User Datagram Protocol. 17 | IPPROTO_IDP = 22, // XNS IDP protocol. 18 | IPPROTO_TP = 29, // SO Transport Protocol Class 4. 19 | IPPROTO_DCCP = 33, // Datagram Congestion Control Protocol. 20 | IPPROTO_IPV6 = 41, // IPv6 header. 21 | IPPROTO_RSVP = 46, // Reservation Protocol. 22 | IPPROTO_GRE = 47, // General Routing Encapsulation. 23 | IPPROTO_ESP = 50, // encapsulating security payload. 24 | IPPROTO_AH = 51, // authentication header. 25 | IPPROTO_MTP = 92, // Multicast Transport Protocol. 26 | IPPROTO_BEETPH = 94, // IP option pseudo header for BEET. 27 | IPPROTO_ENCAP = 98, // Encapsulation Header. 28 | IPPROTO_PIM = 103, // Protocol Independent Multicast. 29 | IPPROTO_COMP = 108, // Compression Header Protocol. 30 | IPPROTO_SCTP = 132, // Stream Control Transmission Protocol. 31 | IPPROTO_UDPLITE = 136, // UDP-Lite protocol. 32 | IPPROTO_MPLS = 137, // MPLS in IP. 33 | IPPROTO_RAW = 255, // Raw IP packets. 34 | IPPROTO_MAX 35 | }; 36 | 37 | #define TCP_NODELAY 1 // Don't delay send to coalesce packets 38 | #define TCP_MAXSEG 2 // Set maximum segment size 39 | #define TCP_CORK 3 // Control sending of partial frames 40 | #define TCP_KEEPIDLE 4 // Start keeplives after this period 41 | #define TCP_KEEPINTVL 5 // Interval between keepalives 42 | #define TCP_KEEPCNT 6 // Number of keepalives before death 43 | #define TCP_SYNCNT 7 // Number of SYN retransmits 44 | #define TCP_LINGER2 8 // Life time of orphaned FIN-WAIT-2 state 45 | #define TCP_DEFER_ACCEPT 9 // Wake up listener only when data arrive 46 | #define TCP_WINDOW_CLAMP 10 // Bound advertised window 47 | #define TCP_INFO 11 // Information about this connection. 48 | #define TCP_QUICKACK 12 // Bock/reenable quick ACKs. 49 | #define TCP_CONGESTION 13 // Congestion control algorithm. 50 | #define TCP_MD5SIG 14 // TCP MD5 Signature (RFC2385) 51 | #define TCP_COOKIE_TRANSACTIONS 15 // TCP Cookie Transactions 52 | #define TCP_THIN_LINEAR_TIMEOUTS 16 // Use linear timeouts for thin streams 53 | #define TCP_THIN_DUPACK 17 // Fast retrans. after 1 dupack 54 | #define TCP_USER_TIMEOUT 18 // How long for loss retry before timeout 55 | #define TCP_REPAIR 19 // TCP sock is under repair right now 56 | #define TCP_REPAIR_QUEUE 20 // Set TCP queue to repair 57 | #define TCP_QUEUE_SEQ 21 // Set sequence number of repaired queue. 58 | #define TCP_REPAIR_OPTIONS 22 // Repair TCP connection options 59 | #define TCP_FASTOPEN 23 // Enable FastOpen on listeners 60 | #define TCP_TIMESTAMP 24 // TCP time stamp 61 | #define TCP_NOTSENT_LOWAT 25 // Limit number of unsent bytes in write queue. 62 | #define TCP_CC_INFO 26 // Get Congestion Control (optional) info. 63 | #define TCP_SAVE_SYN 27 // Record SYN headers for new connections. 64 | #define TCP_SAVED_SYN 28 // Get SYN headers recorded for connection. 65 | #define TCP_REPAIR_WINDOW 29 // Get/set window parameters. 66 | #define TCP_FASTOPEN_CONNECT 30 // Attempt FastOpen with connect. 67 | #define TCP_ULP 31 // Attach a ULP to a TCP connection. 68 | #define TCP_MD5SIG_EXT 32 // TCP MD5 Signature with extensions. 69 | #define TCP_FASTOPEN_KEY 33 // Set the key for Fast Open (cookie). 70 | #define TCP_FASTOPEN_NO_COOKIE 34 // Enable TFO without a TFO cookie. 71 | #define TCP_ZEROCOPY_RECEIVE 35 72 | #define TCP_INQ 36 // Notify bytes available to read as a cmsg on read. 73 | #define TCP_CM_INQ TCP_INQ 74 | #define TCP_TX_DELAY 37 // Delay outgoing packets by XX usec. 75 | 76 | #define TCP_REPAIR_ON 1 77 | #define TCP_REPAIR_OFF 0 78 | #define TCP_REPAIR_OFF_NO_WP -1 79 | 80 | #endif 81 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # websocket 2 | 3 | ![Test](https://github.com/Hakkadaikon/websocket/actions/workflows/test.yml/badge.svg) 4 | ![Coverity](https://scan.coverity.com/projects/31257/badge.svg) 5 | 6 | Websocket server that complies with RFC6455.(The WebSocket Protocol) 7 | 8 | ![icon](https://github.com/user-attachments/assets/6747414b-d35f-4e1e-80ca-be8c039a4055) 9 | 10 | ## Usage 11 | 12 | ### Build 13 | 14 | ```shell 15 | # library build (host/release) 16 | # output : build/lib/ 17 | cmake -S . -B build -DCMAKE_BUILD_TYPE=Release 18 | cmake --build build 19 | 20 | # library build (host/debug) 21 | # output : build/lib/ 22 | cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug 23 | cmake --build build 24 | 25 | 26 | # library build (nix/release) 27 | # output : result/lib/ 28 | nix build 29 | 30 | # library build (nix/debug) 31 | # output : result/lib/ 32 | nix build .#debug 33 | 34 | # sample build (debug) 35 | # LDLIB : build/lib/ 36 | make BUILD=debug -C examples/echoback 37 | 38 | # sample build (release) 39 | # LDLIB : build/lib/ 40 | make BUILD=release -C examples/echoback 41 | 42 | # musl build with example/echoback (release & x86/64 & linux only) 43 | ./shell/musl_build.sh 44 | ``` 45 | 46 | ### Install 47 | 48 | ```shell 49 | # default install dir: 50 | # - /usr/local/lib/libwsserver.a 51 | # - /usr/local/include/websocket.h 52 | sudo cmake --install build 53 | ``` 54 | 55 | ### Format 56 | 57 | ```shell 58 | # Formatting source code (use clang-format) 59 | ./format.sh 60 | ``` 61 | 62 | ### Test 63 | 64 | ```shell 65 | cd tests 66 | make test 67 | ``` 68 | 69 | ### Run 70 | 71 | ```shell 72 | # Example: echoback server 73 | docker compose build --no-cache 74 | docker compose up 75 | 76 | # Static analysis (use clang-tidy) 77 | ./shell/static_analysis.sh 78 | ``` 79 | 80 | ### Examples 81 | 82 | #### Echo back 83 | 84 | ```c 85 | #include 86 | #include 87 | 88 | void websocket_receive_callback( 89 | const int client_sock, 90 | PWebSocketFrame frame, 91 | const size_t buffer_capacity, 92 | char* response_buffer) 93 | { 94 | switch (frame->opcode) { 95 | case WEBSOCKET_OP_CODE_TEXT: { 96 | frame->mask = 0; 97 | size_t frame_size = create_websocket_frame(frame, buffer_capacity, response_buffer); 98 | if (frame_size == 0) { 99 | log_error("Failed to create websocket frame.\n"); 100 | return; 101 | } 102 | 103 | websocket_send(client_sock, frame_size, response_buffer); 104 | } break; 105 | default: 106 | break; 107 | } 108 | } 109 | 110 | void websocket_connect_callback(int client_sock) 111 | { 112 | printf("[user] connect. socket fd : %d\n", client_sock); 113 | fflush(stdout); 114 | } 115 | 116 | void websocket_disconnect_callback(int client_sock) 117 | { 118 | printf("[user] disconnect. socket fd : %d\n", client_sock); 119 | fflush(stdout); 120 | } 121 | 122 | int main() 123 | { 124 | WebSocketInitArgs init_args; 125 | init_args.port_num = 8080; 126 | init_args.backlog = 5; 127 | 128 | int server_sock = websocket_server_init(&init_args); 129 | if (server_sock < WEBSOCKET_ERRORCODE_NONE) { 130 | log_error("websocket server init error.\n"); 131 | return 1; 132 | } 133 | 134 | WebSocketLoopArgs loop_args; 135 | loop_args.server_sock = server_sock; 136 | loop_args.callbacks.receive_callback = websocket_receive_callback; 137 | loop_args.callbacks.connect_callback = websocket_connect_callback; 138 | loop_args.callbacks.disconnect_callback = websocket_disconnect_callback; 139 | loop_args.buffer_capacity = 1024; 140 | 141 | websocket_server_loop(&loop_args); 142 | websocket_close(server_sock); 143 | 144 | log_error("websocket server end.\n"); 145 | return 0; 146 | } 147 | ``` 148 | 149 | ## Support 150 | 151 | ### Features 152 | 153 | - opcode 154 | - 0x0 (continuation) : No 155 | - 0x1 (text) : Yes (Interpret with user callbacks) 156 | - 0x2 (binary) : Yes (Interpret with user callbacks) 157 | - 0x8 (close) : Yes 158 | - 0x9 (ping) : No 159 | - 0xA (pong) : Yes (When a ping is received, a pong is sent back.) 160 | - TLS Support : No 161 | - Sub protocol : No (Sec-WebSocket-Protocol) 162 | - Extensions : No (Sec-WebSocket-Extensions) 163 | - Compression / Decode : No 164 | 165 | ### Platform 166 | 167 | - Linux : Ubuntu (22.04, 24.04) 168 | - MacOS : 13(Ventura), 14(Sonoma), 15(Sequoia) 169 | 170 | ## Dependencies 171 | 172 | - [clang-format](https://github.com/llvm/llvm-project/tree/main/clang/tools/clang-format) 173 | - [cmake](https://github.com/Kitware/CMake) 174 | - [googletest](https://github.com/google/googletest) 175 | 176 | ## Author 177 | 178 | Hakkadaikon 179 | -------------------------------------------------------------------------------- /src/util/string.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_STRING_H_ 2 | #define NOSTR_STRING_H_ 3 | 4 | #include "../util/types.h" 5 | #ifdef __APPLE__ 6 | #include 7 | #else 8 | #endif 9 | 10 | #define ptr_value(ptr) (*ptr) 11 | #define is_null(str) (str == NULL) 12 | #define is_empty_value(c) (c == '\0') 13 | #define is_empty(str) (is_empty_value(ptr_value(str))) 14 | #define is_null_or_empty(str) (is_null(str) || is_empty(str)) 15 | #define is_line_break(c) ((c == '\r') || (c == '\n')) 16 | #define is_white_space(c) \ 17 | ((c == ' ') || \ 18 | (c == '\t') || \ 19 | (c == '\r') || \ 20 | (c == '\n') || \ 21 | (c == '\v') || \ 22 | (c == '\f')) 23 | #define is_lower_char(c) ((c >= 'a') && (c <= 'z')) 24 | #define is_upper_char(c) ((c >= 'A') && (c <= 'Z')) 25 | #define is_number_char(c) ((c >= '0') && (c <= '9')) 26 | 27 | static inline bool is_utf8_white_space(const char* str) 28 | { 29 | // UTF-8: 0xE3 0x80 0x80 30 | return (str[0] == '\xE3') && 31 | (str[1] == '\x80') && 32 | (str[2] == '\x80'); 33 | } 34 | 35 | static inline bool compare_case_sensitive(const char a, const char b) 36 | { 37 | if (a != b) { 38 | return false; 39 | } 40 | 41 | return true; 42 | } 43 | 44 | static inline bool compare(const char a, const char b) 45 | { 46 | char lower_a = (a >= 'A' && a <= 'Z') ? (a + 'a' - 'A') : (a); 47 | char lower_b = (b >= 'A' && b <= 'Z') ? (b + 'a' - 'A') : (b); 48 | 49 | if (lower_a != lower_b) { 50 | return false; 51 | } 52 | 53 | return true; 54 | } 55 | 56 | static inline bool is_compare_str(const char* str1, const char* str2, const size_t str1capacity, const size_t str2capacity, const bool case_sensitive) 57 | { 58 | size_t capacity = (str1capacity > str2capacity) ? str2capacity : str1capacity; 59 | if (capacity == 0) { 60 | return false; 61 | } 62 | 63 | typedef bool (*PCompareFunc)(const char a, const char b); 64 | PCompareFunc compare_func = (case_sensitive) ? compare_case_sensitive : compare; 65 | 66 | for (size_t i = 0; i < capacity; i++) { 67 | if (!compare_func(str1[i], str2[i])) { 68 | return false; 69 | } 70 | } 71 | 72 | return true; 73 | } 74 | 75 | static inline int32_t search_str(const char* base, const size_t base_len, const char* target, const size_t target_len, const bool case_sensitive) 76 | { 77 | if (is_null_or_empty(base) || base_len <= 0 || target_len > base_len) { 78 | return -1; 79 | } 80 | 81 | if (is_null_or_empty(target) || target_len <= 0) { 82 | return -1; 83 | } 84 | 85 | for (size_t base_pos = 0; base_pos <= (base_len - target_len); base_pos++) { 86 | if (is_compare_str(&base[base_pos], target, base_len, target_len, case_sensitive)) { 87 | return base_pos; 88 | } 89 | } 90 | 91 | return -1; 92 | } 93 | 94 | static inline bool is_contain_str(const char* base, const size_t base_len, const char* target, const size_t target_len, const bool case_sensitive) 95 | { 96 | return (search_str(base, base_len, target, target_len, case_sensitive) != -1); 97 | } 98 | 99 | static inline int32_t skip_white_space(const char* buffer, const size_t buffer_size) 100 | { 101 | if (is_null(buffer) || buffer_size <= 0) { 102 | return -1; 103 | } 104 | 105 | size_t pos = 0; 106 | while (!is_empty(&buffer[pos]) && pos < buffer_size) { 107 | char current = ptr_value(&buffer[pos]); 108 | if (is_white_space(current)) { 109 | pos++; 110 | continue; 111 | } 112 | 113 | if (pos + 2 < buffer_size) { 114 | if (is_utf8_white_space(&buffer[pos])) { 115 | pos += 3; 116 | continue; 117 | } 118 | } 119 | 120 | return pos; 121 | } 122 | 123 | return pos; 124 | } 125 | 126 | static inline int32_t skip_word(const char* buffer, const size_t buffer_size) 127 | { 128 | for (size_t i = 0; i < buffer_size; i++) { 129 | if (is_white_space(buffer[i])) { 130 | return i; 131 | } 132 | 133 | if (is_empty_value(buffer[i])) { 134 | return i; 135 | } 136 | 137 | if (i + 2 >= buffer_size) { 138 | continue; 139 | } 140 | 141 | if (is_utf8_white_space(&buffer[i])) { 142 | return i; 143 | } 144 | } 145 | 146 | return buffer_size; 147 | } 148 | 149 | static inline int32_t skip_next_line(const char* buffer, const size_t buffer_len) 150 | { 151 | if (is_null(buffer) || buffer_len < 2) { 152 | return 0; 153 | } 154 | 155 | size_t buffer_pos = 0; 156 | while (buffer[buffer_pos] != '\0' && buffer_pos < buffer_len - 1) { 157 | char current = buffer[buffer_pos]; 158 | char next = buffer[buffer_pos + 1]; 159 | 160 | if (current == '\n') { 161 | return buffer_pos + 1; 162 | } 163 | 164 | if (next == '\n') { 165 | return buffer_pos + 2; 166 | } 167 | 168 | buffer_pos++; 169 | } 170 | 171 | return buffer_pos; 172 | } 173 | 174 | static inline int32_t skip_token(const char* buffer, const size_t buffer_size, const char token) 175 | { 176 | for (size_t i = 0; i < buffer_size; i++) { 177 | if (buffer[i] == token) { 178 | return i; 179 | } 180 | } 181 | 182 | return -1; 183 | } 184 | 185 | static size_t inline get_str_len(const char* str) 186 | { 187 | int32_t len = 0; 188 | while (str[len++] != '\0') 189 | ; 190 | 191 | return len - 1; 192 | } 193 | 194 | static size_t inline get_str_nlen(const char* str, const size_t capacity) 195 | { 196 | int32_t len = 0; 197 | while (str[len++] != '\0' && len < capacity) 198 | ; 199 | 200 | return len - 1; 201 | } 202 | 203 | #endif 204 | -------------------------------------------------------------------------------- /src/crypto/sha1.c: -------------------------------------------------------------------------------- 1 | /* 2 | * @see https://github.com/clibs/sha1 3 | * 4 | * Test Vectors (from FIPS PUB 180-1) 5 | * "abc" 6 | * A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D 7 | * "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" 8 | * 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 9 | * A million repetitions of "a" 10 | * 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F 11 | * #define LITTLE_ENDIAN * This should be #define'd already, if true. 12 | */ 13 | 14 | #include "sha1.h" 15 | 16 | #include "../util/allocator.h" 17 | #include "sha1_def.h" 18 | 19 | void sha1Transform(uint32_t state[5], const uint8_t buffer[64]); 20 | void sha1Init(Sha1Ctx* context); 21 | void sha1Update(Sha1Ctx* context, const uint8_t* data, uint32_t len); 22 | void sha1Final(uint8_t digest[20], Sha1Ctx* context); 23 | 24 | void sha1(const char* input, const size_t input_len, uint8_t* output) 25 | { 26 | Sha1Ctx ctx; 27 | uint32_t ii; 28 | 29 | sha1Init(&ctx); 30 | for (ii = 0; ii < input_len; ii++) { 31 | sha1Update(&ctx, (const uint8_t*)input + ii, 1); 32 | } 33 | 34 | sha1Final(output, &ctx); 35 | } 36 | 37 | /** 38 | * @brief Hash a single 512-bit block. This is the core of the algorithm. 39 | */ 40 | void sha1Transform(uint32_t state[5], const uint8_t buffer[64]) 41 | { 42 | uint32_t a, b, c, d, e; 43 | Char64Long16 block[1]; // use array to appear as a pointer 44 | 45 | websocket_memcpy(block, buffer, sizeof(Char64Long16)); 46 | 47 | // Copy context->state[] to working vars 48 | a = state[0]; 49 | b = state[1]; 50 | c = state[2]; 51 | d = state[3]; 52 | e = state[4]; 53 | 54 | // 4 rounds of 20 operations each. Loop unrolled. 55 | R0(a, b, c, d, e, 0); 56 | R0(e, a, b, c, d, 1); 57 | R0(d, e, a, b, c, 2); 58 | R0(c, d, e, a, b, 3); 59 | R0(b, c, d, e, a, 4); 60 | R0(a, b, c, d, e, 5); 61 | R0(e, a, b, c, d, 6); 62 | R0(d, e, a, b, c, 7); 63 | R0(c, d, e, a, b, 8); 64 | R0(b, c, d, e, a, 9); 65 | R0(a, b, c, d, e, 10); 66 | R0(e, a, b, c, d, 11); 67 | R0(d, e, a, b, c, 12); 68 | R0(c, d, e, a, b, 13); 69 | R0(b, c, d, e, a, 14); 70 | R0(a, b, c, d, e, 15); 71 | R1(e, a, b, c, d, 16); 72 | R1(d, e, a, b, c, 17); 73 | R1(c, d, e, a, b, 18); 74 | R1(b, c, d, e, a, 19); 75 | R2(a, b, c, d, e, 20); 76 | R2(e, a, b, c, d, 21); 77 | R2(d, e, a, b, c, 22); 78 | R2(c, d, e, a, b, 23); 79 | R2(b, c, d, e, a, 24); 80 | R2(a, b, c, d, e, 25); 81 | R2(e, a, b, c, d, 26); 82 | R2(d, e, a, b, c, 27); 83 | R2(c, d, e, a, b, 28); 84 | R2(b, c, d, e, a, 29); 85 | R2(a, b, c, d, e, 30); 86 | R2(e, a, b, c, d, 31); 87 | R2(d, e, a, b, c, 32); 88 | R2(c, d, e, a, b, 33); 89 | R2(b, c, d, e, a, 34); 90 | R2(a, b, c, d, e, 35); 91 | R2(e, a, b, c, d, 36); 92 | R2(d, e, a, b, c, 37); 93 | R2(c, d, e, a, b, 38); 94 | R2(b, c, d, e, a, 39); 95 | R3(a, b, c, d, e, 40); 96 | R3(e, a, b, c, d, 41); 97 | R3(d, e, a, b, c, 42); 98 | R3(c, d, e, a, b, 43); 99 | R3(b, c, d, e, a, 44); 100 | R3(a, b, c, d, e, 45); 101 | R3(e, a, b, c, d, 46); 102 | R3(d, e, a, b, c, 47); 103 | R3(c, d, e, a, b, 48); 104 | R3(b, c, d, e, a, 49); 105 | R3(a, b, c, d, e, 50); 106 | R3(e, a, b, c, d, 51); 107 | R3(d, e, a, b, c, 52); 108 | R3(c, d, e, a, b, 53); 109 | R3(b, c, d, e, a, 54); 110 | R3(a, b, c, d, e, 55); 111 | R3(e, a, b, c, d, 56); 112 | R3(d, e, a, b, c, 57); 113 | R3(c, d, e, a, b, 58); 114 | R3(b, c, d, e, a, 59); 115 | R4(a, b, c, d, e, 60); 116 | R4(e, a, b, c, d, 61); 117 | R4(d, e, a, b, c, 62); 118 | R4(c, d, e, a, b, 63); 119 | R4(b, c, d, e, a, 64); 120 | R4(a, b, c, d, e, 65); 121 | R4(e, a, b, c, d, 66); 122 | R4(d, e, a, b, c, 67); 123 | R4(c, d, e, a, b, 68); 124 | R4(b, c, d, e, a, 69); 125 | R4(a, b, c, d, e, 70); 126 | R4(e, a, b, c, d, 71); 127 | R4(d, e, a, b, c, 72); 128 | R4(c, d, e, a, b, 73); 129 | R4(b, c, d, e, a, 74); 130 | R4(a, b, c, d, e, 75); 131 | R4(e, a, b, c, d, 76); 132 | R4(d, e, a, b, c, 77); 133 | R4(c, d, e, a, b, 78); 134 | R4(b, c, d, e, a, 79); 135 | 136 | // Add the working vars back into context.state[] 137 | state[0] += a; 138 | state[1] += b; 139 | state[2] += c; 140 | state[3] += d; 141 | state[4] += e; 142 | 143 | // Wipe variables 144 | a = b = c = d = e = 0; 145 | websocket_memset_s(block, sizeof(block), '\0', sizeof(block)); 146 | } 147 | 148 | /** 149 | * @brief Initialize new context 150 | */ 151 | void sha1Init(Sha1Ctx* context) 152 | { 153 | /* SHA1 initialization constants */ 154 | context->state[0] = 0x67452301; 155 | context->state[1] = 0xEFCDAB89; 156 | context->state[2] = 0x98BADCFE; 157 | context->state[3] = 0x10325476; 158 | context->state[4] = 0xC3D2E1F0; 159 | context->count[0] = context->count[1] = 0; 160 | } 161 | 162 | /** 163 | * @brief Run your data through this. 164 | */ 165 | void sha1Update(Sha1Ctx* context, const uint8_t* data, uint32_t len) 166 | { 167 | uint32_t i; 168 | uint32_t j; 169 | 170 | j = context->count[0]; 171 | if ((context->count[0] += len << 3) < j) { 172 | context->count[1]++; 173 | } 174 | context->count[1] += (len >> 29); 175 | j = (j >> 3) & 63; 176 | if ((j + len) > 63) { 177 | websocket_memcpy(&context->buffer[j], data, (i = 64 - j)); 178 | sha1Transform(context->state, context->buffer); 179 | for (; i + 63 < len; i += 64) { 180 | sha1Transform(context->state, &data[i]); 181 | } 182 | j = 0; 183 | } else { 184 | i = 0; 185 | } 186 | websocket_memcpy(&context->buffer[j], &data[i], len - i); 187 | } 188 | 189 | /** 190 | * @brief Add padding and return the message digest. 191 | */ 192 | void sha1Final(uint8_t digest[SHA1_DIGEST_LENGTH], Sha1Ctx* context) 193 | { 194 | uint32_t i; 195 | uint8_t finalcount[8]; 196 | uint8_t c; 197 | 198 | for (i = 0; i < 8; i++) { 199 | // Endian independent 200 | finalcount[i] = (uint8_t)((context->count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 255); 201 | } 202 | 203 | c = 0200; 204 | sha1Update(context, &c, 1); 205 | while ((context->count[0] & 504) != 448) { 206 | c = 0000; 207 | sha1Update(context, &c, 1); 208 | } 209 | 210 | // Should cause a Sha1Transform() 211 | sha1Update(context, finalcount, 8); 212 | 213 | for (i = 0; i < SHA1_DIGEST_LENGTH; i++) { 214 | digest[i] = (uint8_t)((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255); 215 | } 216 | 217 | // Wipe variables 218 | websocket_memset_s(context, sizeof(*context), '\0', sizeof(*context)); 219 | websocket_memset_s(&finalcount, sizeof(finalcount), '\0', sizeof(finalcount)); 220 | } 221 | -------------------------------------------------------------------------------- /src/websocket/server/handshake.c: -------------------------------------------------------------------------------- 1 | #include "../../crypto/base64.h" 2 | #include "../../util/allocator.h" 3 | #include "../../util/string.h" 4 | #include "../websocket_local.h" 5 | 6 | #define IS_VALID_KEY(value, expected) is_compare_str(value, expected, HTTP_HEADER_KEY_CAPACITY, sizeof(expected), false) 7 | #define IS_VALID_VALUE(value, expected) is_compare_str(value, expected, HTTP_HEADER_VALUE_CAPACITY, sizeof(expected), false) 8 | #define IS_VALID_HTTP_VERSION(value, expected) is_compare_str(value, expected, HTTP_VERSION_CAPACITY, sizeof(expected), false) 9 | #define IS_VALID_HTTP_METHOD(value, expected) is_compare_str(value, expected, HTTP_METHOD_CAPACITY, sizeof(expected), false) 10 | 11 | static inline bool is_valid_host(const char* restrict host) 12 | { 13 | return true; 14 | } 15 | 16 | static inline bool is_valid_upgrade(const char* restrict value) 17 | { 18 | if (!IS_VALID_KEY(value, "websocket")) { 19 | str_error("Invalid websocket request header [Key: Upgrade] : ", value); 20 | return false; 21 | } 22 | 23 | return true; 24 | } 25 | 26 | static inline bool is_valid_connection(const char* restrict value) 27 | { 28 | if (!IS_VALID_KEY(value, "upgrade")) { 29 | str_error("Invalid websocket request header [Key: Connection] : ", value); 30 | return false; 31 | } 32 | 33 | return true; 34 | } 35 | 36 | static inline bool is_valid_version(const char* restrict value) 37 | { 38 | if (!IS_VALID_KEY(value, "13")) { 39 | str_error("Invalid websocket request header [Key: Sec-WebSocket-Version] : ", value); 40 | return false; 41 | } 42 | 43 | return true; 44 | } 45 | 46 | static inline bool is_valid_websocket_key(const char* restrict value) 47 | { 48 | if (get_str_nlen(value, HTTP_HEADER_VALUE_CAPACITY) < 16) { 49 | log_error("Invalid websocket request header [Key: Sec-WebSocket-Key] Length is less than 16.\n"); 50 | return false; 51 | } 52 | 53 | if (!is_base64(value)) { 54 | log_error("Invalid websocket request header [Key: Sec-WebSocket-Key] Client key is not base64.\n"); 55 | return false; 56 | } 57 | 58 | return true; 59 | } 60 | 61 | static inline bool is_valid_method(char* value) 62 | { 63 | if (!IS_VALID_HTTP_METHOD(value, "get")) { 64 | log_error("Invalid websocket request line: method is not GET\n"); 65 | return false; 66 | } 67 | 68 | return true; 69 | } 70 | 71 | static inline bool is_valid_target(char* value) 72 | { 73 | if (get_str_nlen(value, HTTP_TARGET_CAPACITY) <= 0) { 74 | log_error("Invalid websocket request line: target size is 0\n"); 75 | return false; 76 | } 77 | 78 | return true; 79 | } 80 | 81 | static inline bool is_valid_http_version(char* value) 82 | { 83 | bool has_error = false; 84 | 85 | if (!IS_VALID_HTTP_VERSION(value, "http/1.1") && 86 | !IS_VALID_HTTP_VERSION(value, "http/2.0") && 87 | !IS_VALID_HTTP_VERSION(value, "http/3.0")) { 88 | log_error("Invalid websocket request line: Invalid HTTP version(Not 1.1/2.0/3.0)\n"); 89 | return false; 90 | } 91 | 92 | return true; 93 | } 94 | 95 | static inline bool is_valid_request_line(PHTTPRequestLine restrict line) 96 | { 97 | if (!is_valid_method(line->method)) { 98 | return false; 99 | } 100 | 101 | if (!is_valid_target(line->target)) { 102 | return false; 103 | } 104 | 105 | if (!is_valid_http_version(line->http_version)) { 106 | return false; 107 | } 108 | 109 | return true; 110 | } 111 | 112 | static inline bool is_valid_request_header_line(PHTTPRequestHeaderLine restrict line) 113 | { 114 | if (IS_VALID_KEY(line->key, "host")) { 115 | if (is_valid_host(line->value)) { 116 | return true; 117 | } 118 | return false; 119 | } else if (IS_VALID_KEY(line->key, "upgrade")) { 120 | if (is_valid_upgrade(line->value)) { 121 | return true; 122 | } 123 | return false; 124 | } else if (IS_VALID_KEY(line->key, "connection")) { 125 | if (is_valid_connection(line->value)) { 126 | return true; 127 | } 128 | return false; 129 | } else if (IS_VALID_KEY(line->key, "sec-webSocket-key")) { 130 | if (is_valid_websocket_key(line->value)) { 131 | return true; 132 | } 133 | return false; 134 | } else if (IS_VALID_KEY(line->key, "sec-websocket-version")) { 135 | if (is_valid_version(line->value)) { 136 | return true; 137 | } 138 | return false; 139 | } 140 | 141 | return true; 142 | } 143 | 144 | static inline bool is_valid_request_header(PHTTPRequestHeaderLine restrict headers, size_t header_size) 145 | { 146 | for (size_t i = 0; i < header_size; i++) { 147 | if (!is_valid_request_header_line(&headers[i])) { 148 | return false; 149 | } 150 | } 151 | 152 | return true; 153 | } 154 | 155 | static inline bool is_valid_request(PHTTPRequest restrict request) 156 | { 157 | if (!is_valid_request_line(&request->line)) { 158 | return false; 159 | } 160 | 161 | if (!is_valid_request_header(request->headers, request->header_size)) { 162 | return false; 163 | } 164 | 165 | return true; 166 | } 167 | 168 | static inline char* select_websocket_client_key(PHTTPRequest restrict request) 169 | { 170 | for (size_t i = 0; i < request->header_size; i++) { 171 | PHTTPRequestHeaderLine line = &request->headers[i]; 172 | if (is_compare_str(line->key, "Sec-WebSocket-Key", sizeof(line->key), 17, false)) { 173 | return line->value; 174 | } 175 | } 176 | 177 | log_error("WebSocket client key is not found.\n"); 178 | return NULL; 179 | } 180 | 181 | static inline bool build_response_frame( 182 | const char* restrict accept_key, 183 | const int32_t accept_key_capacity, 184 | char* restrict buffer, 185 | const size_t capacity) 186 | { 187 | const char OK_MESSAGE[] = 188 | "HTTP/1.1 101 Switching Protocols\r\n" 189 | "Upgrade: websocket\r\n" 190 | "Connection: Upgrade\r\n" 191 | "Sec-WebSocket-Accept: "; 192 | const size_t OK_MESSAGE_LEN = sizeof(OK_MESSAGE) - 1; 193 | const size_t ACCEPT_KEY_LEN = get_str_nlen(accept_key, accept_key_capacity); 194 | const size_t REQUIRED_CAPACITY = OK_MESSAGE_LEN + ACCEPT_KEY_LEN + 5; 195 | 196 | if (capacity <= REQUIRED_CAPACITY) { 197 | return false; 198 | } 199 | 200 | char* ptr = buffer; 201 | websocket_memcpy(ptr, OK_MESSAGE, OK_MESSAGE_LEN); 202 | ptr += OK_MESSAGE_LEN; 203 | 204 | websocket_memcpy(ptr, accept_key, ACCEPT_KEY_LEN); 205 | ptr += ACCEPT_KEY_LEN; 206 | 207 | websocket_memcpy(ptr, "\r\n\r\n", 4); 208 | ptr += 4; 209 | 210 | return true; 211 | } 212 | 213 | bool client_handshake( 214 | const int32_t client_sock, 215 | const size_t bytes_read, 216 | PWebSocketRawBuffer buffer, 217 | PHTTPRequest restrict request) 218 | { 219 | bool has_error = false; 220 | 221 | if (!extract_http_request(buffer->request, bytes_read, request)) { 222 | has_error = true; 223 | goto FINALIZE; 224 | } 225 | 226 | if (!is_valid_request(request)) { 227 | has_error = true; 228 | goto FINALIZE; 229 | } 230 | 231 | char* client_key = select_websocket_client_key(request); 232 | if (is_null(client_key)) { 233 | has_error = true; 234 | goto FINALIZE; 235 | } 236 | 237 | char accept_key[HTTP_HEADER_VALUE_CAPACITY]; 238 | if (!generate_websocket_acceptkey(client_key, sizeof(accept_key), accept_key)) { 239 | has_error = true; 240 | goto FINALIZE; 241 | } 242 | 243 | if (has_error) { 244 | str_info("Invalid handshake request : ", buffer->request); 245 | } else { 246 | if (!build_response_frame(accept_key, sizeof(accept_key), buffer->response, buffer->capacity)) { 247 | has_error = true; 248 | goto FINALIZE; 249 | } 250 | 251 | size_t response_len = get_str_nlen(buffer->response, buffer->capacity); 252 | if (response_len == 0) { 253 | has_error = true; 254 | goto FINALIZE; 255 | } 256 | 257 | if (websocket_send(client_sock, response_len, buffer->response) != WEBSOCKET_ERRORCODE_NONE) { 258 | log_error("Failed to send OK frame."); 259 | has_error = true; 260 | goto FINALIZE; 261 | } 262 | 263 | log_debug("handshake success !"); 264 | str_debug("request : ", buffer->request); 265 | str_debug("response : ", buffer->response); 266 | } 267 | 268 | FINALIZE: 269 | // Wipe variables 270 | websocket_memset_s(accept_key, sizeof(accept_key), 0x00, sizeof(accept_key)); 271 | 272 | return !has_error; 273 | } 274 | -------------------------------------------------------------------------------- /src/websocket/parser.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file websocket_parse.c 3 | * 4 | * @brief Parses each parameter of a websocket frame stored in network byte order. 5 | * @see RFC6455 (https://datatracker.ietf.org/doc/html/rfc6455) 6 | */ 7 | 8 | #include "../util/allocator.h" 9 | #include "websocket_local.h" 10 | 11 | /** 12 | * @brief Parse raw data in network byte order into a websocket frame structure 13 | * 14 | * @param[in] raw Raw data (network byte order) 15 | * @param[in] frame_size Capacity of raw data 16 | * @param[out] frame Output destination of parsed frame 17 | * 18 | * @return true: Parse was successful / false: Failed parse 19 | */ 20 | bool parse_websocket_frame(const char* restrict raw, const size_t capacity, PWebSocketFrame restrict frame) 21 | { 22 | if (capacity < 2) { 23 | return false; 24 | } 25 | 26 | if (is_null(frame)) { 27 | return false; 28 | } 29 | 30 | if (is_null(frame->payload)) { 31 | return false; 32 | } 33 | 34 | // 0 1 2 3 35 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 36 | // +-+-+-+-+-------+-+-------------+-------------------------------+ 37 | // |F|R|R|R| opcode|M| Payload len | Extended payload length | 38 | // |I|S|S|S| (4) |A| (7) | (16/64) | 39 | // |N|V|V|V| |S| | (if payload len==126/127) | 40 | // | |1|2|3| |K| | | 41 | // +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + 42 | // | Extended payload length continued, if payload len == 127 | 43 | // + - - - - - - - - - - - - - - - +-------------------------------+ 44 | // | |Masking-key, if MASK set to 1 | 45 | // +-------------------------------+-------------------------------+ 46 | // | Masking-key (continued) | Payload Data | 47 | // +-------------------------------- - - - - - - - - - - - - - - - + 48 | // : Payload Data continued ... : 49 | // + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + 50 | // | Payload Data continued ... | 51 | // +---------------------------------------------------------------+ 52 | // 0-7bit 53 | // +-+-+-+-+-------+ 54 | // |F|R|R|R| opcode| 55 | // |I|S|S|S| (4) | 56 | // |N|V|V|V| | 57 | // | |1|2|3| | 58 | // +-+-+-+-+-------+ 59 | frame->fin = (raw[0] & 0x80) >> 7; 60 | frame->rsv1 = (raw[0] & 0x40) >> 6; 61 | frame->rsv2 = (raw[0] & 0x20) >> 5; 62 | frame->rsv3 = (raw[0] & 0x10) >> 4; 63 | frame->opcode = (raw[0] & 0x0F); 64 | 65 | // 8-15bit 66 | // +-+-------------+ 67 | // |M| Payload len | 68 | // |A| (7) | 69 | // |S| | 70 | // |K| | 71 | // +-+-------------+ 72 | frame->mask = (raw[1] & 0x80) >> 7; 73 | frame->payload_len = (raw[1] & 0x7F); 74 | 75 | size_t frame_offset = 2; 76 | 77 | // Expanded payload length 78 | // - nothing (if payload_len <= 125) 79 | // - 16-31bit (if payload_len = 126) 80 | // - 16-79bit (if payload_len = 127) 81 | // 82 | // 0 1 2 3 83 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 84 | // +-------------------------------+ 85 | // | Extended payload length | 86 | // | (16/64) | 87 | // | (if payload len==126/127) | 88 | // | | 89 | // +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + 90 | // | Extended payload length continued, if payload len == 127 | 91 | // + - - - - - - - - - - - - - - - +-------------------------------+ 92 | // | | 93 | // +-------------------------------+ 94 | if (frame->payload_len == 126) { 95 | if (capacity < 4) { 96 | return false; 97 | } 98 | frame->ext_payload_len = (raw[2] << 8) | raw[3]; 99 | if (frame->ext_payload_len > capacity) { 100 | return false; 101 | } 102 | frame_offset += 2; 103 | } else if (frame->payload_len == 127) { 104 | if (capacity < 10) { 105 | return false; 106 | } 107 | 108 | for (int32_t i = 0; i < 8; i++) { 109 | frame->ext_payload_len = (frame->ext_payload_len << 8) | raw[2 + i]; 110 | } 111 | frame_offset += 8; 112 | } else { 113 | frame->ext_payload_len = frame->payload_len; 114 | } 115 | 116 | // masking key 117 | // - 16-47bit (if payload_len <= 125) 118 | // - 32-63bit (if payload_len = 126) 119 | // - 80-111bit (if payload_len = 127) 120 | // 121 | // 0 1 2 3 122 | // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 123 | // + - - - - - - - - - - - - - - - +-------------------------------+ 124 | // | |Masking-key, if MASK set to 1 | 125 | // +-------------------------------+-------------------------------+ 126 | // | Masking-key (continued) | 127 | // +-------------------------------- 128 | if (frame->mask) { 129 | if (capacity < frame_offset + 4) { 130 | return false; 131 | } 132 | 133 | websocket_memcpy(frame->masking_key, &raw[frame_offset], 4); 134 | frame_offset += sizeof(frame->masking_key); 135 | } 136 | 137 | if (frame->ext_payload_len > (capacity - frame_offset)) { 138 | return false; 139 | } 140 | 141 | const char* payload_raw = &raw[frame_offset]; 142 | for (size_t i = 0; i < frame->ext_payload_len; i++) { 143 | frame->payload[i] = 144 | payload_raw[i] ^ (frame->mask ? frame->masking_key[i % 4] : 0); 145 | } 146 | frame->payload[frame->ext_payload_len] = '\0'; 147 | return true; 148 | } 149 | 150 | size_t create_websocket_frame(PWebSocketFrame restrict frame, const size_t capacity, char* restrict raw) 151 | { 152 | if (!frame || !raw || capacity < 2) { 153 | return 0; 154 | } 155 | 156 | size_t offset = 0; 157 | 158 | // 0-7bit 159 | // +-+-+-+-+-------+ 160 | // |F|R|R|R| opcode| 161 | // |I|S|S|S| (4) | 162 | // |N|V|V|V| | 163 | // | |1|2|3| | 164 | // +-+-+-+-+-------+ 165 | raw[offset] = ((frame->fin & 0x01) << 7) | 166 | ((frame->rsv1 & 0x01) << 6) | 167 | ((frame->rsv2 & 0x01) << 5) | 168 | ((frame->rsv3 & 0x01) << 4) | 169 | (frame->opcode & 0x0F); 170 | offset++; 171 | 172 | // 8-15bit 173 | // +-+-------------+ 174 | // |M| Payload len | 175 | // |A| (7) | 176 | // |S| | 177 | // |K| | 178 | // +-+-------------+ 179 | unsigned char mask = (frame->mask ? 0x80 : 0x00); 180 | 181 | if (frame->ext_payload_len <= 125 && !frame->mask) { 182 | raw[offset] = frame->ext_payload_len & 0x7F; 183 | offset++; 184 | websocket_memcpy(&raw[offset], frame->payload, frame->ext_payload_len); 185 | return offset + frame->ext_payload_len; 186 | } 187 | 188 | if (frame->ext_payload_len > 125 && frame->ext_payload_len <= 0xFFFF) { 189 | raw[offset] = mask | 126; // Mask set + payload length 126 190 | offset++; 191 | if (capacity < offset + 2) { 192 | return 0; 193 | } 194 | raw[offset] = (frame->ext_payload_len >> 8) & 0xFF; 195 | raw[offset + 1] = frame->ext_payload_len & 0xFF; 196 | offset += 2; 197 | } else if (frame->ext_payload_len > 0xFFFF) { 198 | raw[offset] = mask | 127; // Mask set + payload length 127 199 | offset++; 200 | if (capacity < offset + 8) { 201 | return 0; 202 | } 203 | for (int32_t i = 7; i >= 0; i--) { 204 | raw[offset + i] = frame->ext_payload_len & 0xFF; 205 | frame->ext_payload_len >>= 8; 206 | } 207 | offset += 8; 208 | } else { 209 | raw[offset] = mask | frame->ext_payload_len; 210 | offset++; 211 | } 212 | 213 | // Masking key (if mask is set) 214 | if (frame->mask) { 215 | if (capacity < offset + 4) { 216 | return 0; 217 | } 218 | websocket_memcpy(&raw[offset], frame->masking_key, 4); 219 | offset += 4; 220 | } 221 | 222 | // Payload data 223 | if (capacity < offset + frame->ext_payload_len) { 224 | return 0; 225 | } 226 | 227 | if (frame->mask) { 228 | // use loop unroll 229 | size_t i = 0; 230 | for (; i + 4 <= frame->ext_payload_len; i += 4) { 231 | raw[offset + i + 0] = frame->payload[i + 0] ^ frame->masking_key[0]; 232 | raw[offset + i + 1] = frame->payload[i + 1] ^ frame->masking_key[1]; 233 | raw[offset + i + 2] = frame->payload[i + 2] ^ frame->masking_key[2]; 234 | raw[offset + i + 3] = frame->payload[i + 3] ^ frame->masking_key[3]; 235 | } 236 | for (; i < frame->ext_payload_len; i++) { 237 | raw[offset + i] = frame->payload[i] ^ frame->masking_key[i % 4]; 238 | } 239 | } else { 240 | websocket_memcpy(&raw[offset], &frame->payload[0], frame->ext_payload_len); 241 | } 242 | 243 | offset += frame->ext_payload_len; 244 | 245 | return offset; 246 | } 247 | -------------------------------------------------------------------------------- /src/websocket/websocket.h: -------------------------------------------------------------------------------- 1 | #ifndef _NOSTR_WEBSOCKET_H_ 2 | #define _NOSTR_WEBSOCKET_H_ 3 | 4 | /** @file websocket.h 5 | * 6 | * @brief Parses each parameter of a websocket frame stored in network byte order. 7 | * @see RFC6455 (https://datatracker.ietf.org/doc/html/rfc6455) 8 | */ 9 | #ifdef __APPLE__ 10 | #include 11 | #include 12 | #include 13 | #else 14 | #ifndef __cplusplus 15 | #ifndef _INT8_T 16 | #define _INT8_T 17 | typedef char int8_t; 18 | #endif 19 | 20 | #ifndef _UINT8_T 21 | #define _UINT8_T 22 | typedef unsigned char uint8_t; 23 | #endif 24 | 25 | #ifndef _INT16_T 26 | #define _INT16_T 27 | typedef short int16_t; 28 | #endif 29 | 30 | #ifndef _UINT16_T 31 | #define _UINT16_T 32 | typedef unsigned short uint16_t; 33 | #endif 34 | 35 | #ifndef _INT32_T 36 | #define _INT32_T 37 | typedef int int32_t; 38 | #endif 39 | 40 | #ifndef _UINT32_T 41 | #define _UINT32_T 42 | typedef unsigned int uint32_t; 43 | #endif 44 | 45 | #ifndef _INT64_T 46 | #define _INT64_T 47 | typedef long long int64_t; 48 | #endif 49 | 50 | #ifndef _UINT64_T 51 | #define _UINT64_T 52 | typedef unsigned int uint64_t; 53 | #endif 54 | 55 | #ifndef _SIZE_T 56 | #define _SIZE_T 57 | typedef uint64_t size_t; 58 | #endif 59 | 60 | #ifndef _SSIZE_T 61 | #define _SSIZE_T 62 | typedef int64_t ssize_t; 63 | #endif 64 | 65 | #ifndef bool 66 | #define bool int32_t 67 | #endif 68 | 69 | #ifndef true 70 | #define true 1 71 | #endif 72 | 73 | #ifndef false 74 | #define false 0 75 | #endif 76 | #endif 77 | 78 | #ifndef NULL 79 | #define NULL ((void*)0) 80 | #endif 81 | #endif 82 | 83 | #ifndef STDOUT_FILENO 84 | #define STDOUT_FILENO 1 // Standard output. 85 | #endif 86 | 87 | #ifndef STDERR_FILENO 88 | #define STDERR_FILENO 2 // Standard error output. 89 | #endif 90 | 91 | /** 92 | * @enum WebSocketOpCode 93 | * @brief WebSocket frame type 94 | */ 95 | typedef enum { 96 | WEBSOCKET_OP_CODE_TEXT = 0x1, ///< When this frame is received, Invokes the user callback. 97 | WEBSOCKET_OP_CODE_BINARY = 0x2, ///< When this frame is received, Invokes the user callback. 98 | WEBSOCKET_OP_CODE_CLOSE = 0x8, ///< When this frame is received, closes the client socket. 99 | WEBSOCKET_OP_CODE_PING = 0x9, ///< When this frame is received, this server replies with a Pong. 100 | WEBSOCKET_OP_CODE_PONG = 0xA, ///< Pong frame. 101 | } WebSocketOpCode; 102 | 103 | /** 104 | * @brief Error code when calling a WebSocket function 105 | */ 106 | typedef enum { 107 | WEBSOCKET_ERRORCODE_FATAL_ERROR = -3, ///< When this code is received, This server will terminate processing. 108 | WEBSOCKET_ERRORCODE_SOCKET_CLOSE_ERROR = -2, ///< When this code is received, This server closes the client socket. 109 | WEBSOCKET_ERRORCODE_CONTINUABLE_ERROR = -1, ///< When this code is received, This server will not proceed to the next epoll loop and will continue. 110 | WEBSOCKET_ERRORCODE_NONE = 0, ///< Normal code. No action. 111 | } WebSocketErrorCode; 112 | 113 | /** 114 | * @brief Result of parsing the WebSocket frame 115 | */ 116 | typedef struct _WebSocketFrame { 117 | uint8_t fin; 118 | uint8_t rsv1; 119 | uint8_t rsv2; 120 | uint8_t rsv3; 121 | WebSocketOpCode opcode; 122 | uint8_t mask; 123 | uint8_t payload_len; 124 | uint8_t dummy[6]; 125 | uint64_t ext_payload_len; 126 | uint8_t masking_key[4]; 127 | char* payload; 128 | uint8_t dummy2[3]; 129 | } WebSocketFrame, *PWebSocketFrame; 130 | 131 | /** 132 | * @brief User callback that is called when a WebSocket frame is received. 133 | */ 134 | typedef void (*PWebSocketReceiveCallback)( 135 | const int32_t client_sock, ///< @param[in] client_sock Client socket that sent the data 136 | PWebSocketFrame frame, ///< @param[in] frame Parsed websocket frame 137 | const size_t buffer_capacity, ///< @param[in] buffer_capacity Response_buffer capacity. 138 | char* response_buffer ///< @param[in/out] response_buffer This buffer must be used to create the return frame. 139 | ); 140 | 141 | /** 142 | * @brief User callback to be called when connection is established 143 | */ 144 | typedef void (*PWebSocketConnectCallback)( 145 | const int32_t client_sock ///< @param[in] client_sock Client socket to connect 146 | ); 147 | 148 | /** 149 | * @ brief User callback that is called when a client is disconnected. 150 | * 151 | * When this callback is called, the connection has already been disconnected, 152 | * so BSD socket functions cannot be called using the client_sock argument. 153 | * It is passed to identify the client and release resources if they have been allocated. 154 | */ 155 | typedef void (*PWebSocketDisconnectCallback)( 156 | const int32_t client_sock ///< @param[in] client_sock Client socket to disconnect 157 | ); 158 | 159 | /** 160 | * @brief User callback list to pass to the WebSocket library. 161 | */ 162 | typedef struct { 163 | PWebSocketReceiveCallback receive_callback; ///< @see PWebSocketReceiveCallback 164 | PWebSocketConnectCallback connect_callback; ///< @see PWebSocketConnectCallback 165 | PWebSocketDisconnectCallback disconnect_callback; ///< @see PWebSocketDisconnectCallback 166 | } WebSocketCallbacks, *PWebSocketCallbacks; 167 | 168 | /** 169 | * @brief Arguments to pass to websocket_init() 170 | */ 171 | typedef struct { 172 | int32_t port_num; ///< WebSocket port number 173 | int32_t backlog; ///< Listen queue size 174 | } WebSocketInitArgs, *PWebSocketInitArgs; 175 | 176 | /** 177 | * @brief Arguments to pass to websocket_loop() 178 | */ 179 | typedef struct { 180 | int32_t server_sock; ///< Socket descriptor obtained by websocket_server_init() function 181 | int32_t dummy; ///< Dummy 182 | size_t buffer_capacity; ///< Capacity of the send and receive buffer for one client. 183 | WebSocketCallbacks callbacks; ///< @see WebSocketCallBacks 184 | } WebSocketLoopArgs, *PWebSocketLoopArgs; 185 | 186 | /** 187 | * @brief Parse raw data in network byte order into a websocket flame structure 188 | * 189 | * @param[in] raw raw data (network byte order) 190 | * @param[in] frame_size Size of webSocket frame 191 | * @param[out] frame Output destination of parsed frame 192 | * 193 | * @return true: Parse was successful / false: Failed parse 194 | */ 195 | bool parse_websocket_frame(const char* raw, const size_t frame_size, PWebSocketFrame frame); 196 | 197 | /** 198 | * @brief Creates raw data to send back to the client 199 | * 200 | * @param[in] frame Transmitted frame before it becomes raw data 201 | * @param[in] capacity Raw data capacity 202 | * @param[out] raw Raw data frame to be returned 203 | * 204 | * @return Size of raw data. If parsing fails, 0 is returned. 205 | */ 206 | size_t create_websocket_frame(PWebSocketFrame frame, const size_t capacity, char* raw); 207 | 208 | /** 209 | * @brief Initialize a WebSocket server. socket listen and register signal handler. 210 | * 211 | * @param[in] port_num Listening port number 212 | * @param[in] backlog Listen queue size 213 | * 214 | * @return Positive value: Server socket descriptor / Negative value: WebSocket error code 215 | * @see WebSocketErrorCode 216 | */ 217 | int32_t websocket_server_init(const PWebSocketInitArgs args); 218 | 219 | /** 220 | * @brief Wrapper for the BSD socket send() API. 221 | * 222 | * @param[in] sock_fd Destination socket descriptor 223 | * @param[in] buffer_size Buffer size 224 | * @param[in] buffer Buffer that stores the transmission data 225 | * 226 | * @return WebSocket error code 227 | * @see WebSocketErrorCode 228 | */ 229 | int32_t websocket_send(const int32_t sock_fd, const size_t buffer_size, const char* buffer); 230 | 231 | /** 232 | * @brief Wrapper for the BSD socket recv() API. 233 | * 234 | * @param[in] sock_fd Destination socket descriptor 235 | * @param[in] capacity Receive buffer capacity 236 | * @param[out] buffer Receive buffer. User must allocate the amount specified by the capacity variable. 237 | * 238 | * @return Positive value: Receive data size / Negative value: WebSocket error code 239 | * @see WebSocketErrorCode 240 | */ 241 | ssize_t websocket_recv(const int32_t sock_fd, const size_t capacity, char* buffer); 242 | 243 | /** 244 | * @brief Wrapper for the BSD socket close() API 245 | * 246 | * @param[in] sock_fd Socket descriptor 247 | * 248 | * @return WebSocket error code 249 | * @see WebSocketErrorCode 250 | */ 251 | int32_t websocket_close(const int32_t sock_fd); 252 | 253 | /** 254 | * @brief Enter the receive loop from the client. 255 | * This function will block until it is sent a SIGHUP/SIGINT/SIGTERM signal or detects a FATAL ERROR internally. 256 | * 257 | * @param[in] args @see WebSocketLoopArgs 258 | * 259 | * @return true: success / false: failure 260 | */ 261 | bool websocket_server_loop(PWebSocketLoopArgs args); 262 | 263 | void log_dump_local(const int32_t fd, const char* str); 264 | void var_dump_local(const int32_t fd, const char* str, const int32_t value); 265 | void str_dump_local(const int32_t fd, const char* str, const char* value); 266 | 267 | #if !defined(LOG_LEVEL_DEBUG) && !defined(LOG_LEVEL_INFO) && !defined(LOG_LEVEL_ERROR) 268 | #define LOG_LEVEL_ERROR 269 | #endif 270 | 271 | #ifdef LOG_LEVEL_DEBUG 272 | #define hex_dump(data, size) hex_dump_local(data, size) 273 | #define log_dump(fd, str) log_dump_local(fd, str) 274 | #define log_debug(str) log_dump(STDOUT_FILENO, str) 275 | #define log_info(str) log_dump(STDOUT_FILENO, str) 276 | #define log_error(str) log_dump(STDERR_FILENO, str) 277 | #define var_dump(fd, str, value) var_dump_local(fd, str, value) 278 | #define var_debug(str, value) var_dump(STDOUT_FILENO, str, value) 279 | #define var_info(str, value) var_dump(STDOUT_FILENO, str, value) 280 | #define var_error(str, value) var_dump(STDERR_FILENO, str, value) 281 | #define str_debug(str, value) str_dump_local(STDOUT_FILENO, str, value) 282 | #define str_info(str, value) str_dump_local(STDOUT_FILENO, str, value) 283 | #define str_error(str, value) str_dump_local(STDERR_FILENO, str, value) 284 | 285 | #elif defined(LOG_LEVEL_INFO) 286 | #define hex_dump(data, size) hex_dump_local(data, size) 287 | #define log_dump(fd, str) log_dump_local(fd, str) 288 | #define log_debug(str) 289 | #define log_info(str) log_dump(STDOUT_FILENO, str) 290 | #define log_error(str) log_dump(STDERR_FILENO, str) 291 | #define var_dump(fd, str, value) var_dump_local(fd, str, value) 292 | #define var_debug(str, value) 293 | #define var_info(str, value) var_dump(STDOUT_FILENO, str, value) 294 | #define var_error(str, value) var_dump(STDERR_FILENO, str, value) 295 | #define str_debug(str, value) 296 | #define str_info(str, value) str_dump_local(STDOUT_FILENO, str, value) 297 | #define str_error(str, value) str_dump_local(STDERR_FILENO, str, value) 298 | 299 | #elif defined(LOG_LEVEL_ERROR) 300 | #define hex_dump(data, size) hex_dump_local(data, size) 301 | #define log_dump(fd, str) log_dump_local(fd, str) 302 | #define log_debug(str) 303 | #define log_info(str) 304 | #define log_error(str) log_dump(STDERR_FILENO, str) 305 | #define var_dump(fd, str, value) var_dump_local(fd, str, value) 306 | #define var_debug(str, value) 307 | #define var_info(str, value) 308 | #define var_error(str, value) var_dump(STDERR_FILENO, str, value) 309 | #define str_debug(str, value) 310 | #define str_info(str, value) 311 | #define str_error(str, value) str_dump_local(STDERR_FILENO, str, value) 312 | 313 | #elif 314 | #define hex_dump(data, size) 315 | #define log_dump(fd, str) 316 | #define log_debug(str) 317 | #define log_info(str) 318 | #define log_error(str) 319 | #define var_dump(fd, str, value) 320 | #define var_debug(str, value) 321 | #define var_info(str, value) 322 | #define var_error(str, value) 323 | #define str_debug(str, value) 324 | #define str_info(str, value) 325 | #define str_error(str, value) 326 | #endif 327 | 328 | #endif 329 | -------------------------------------------------------------------------------- /src/arch/linux/x86_64/asm.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_LINUX_X86_64_ASM_H_ 2 | #define NOSTR_LINUX_X86_64_ASM_H_ 3 | 4 | #include "../../../util/types.h" 5 | 6 | extern void var_dump_local(const int32_t fd, const char* str, const int32_t value); 7 | extern void log_dump_local(const int32_t fd, const char* str); 8 | 9 | #define SYSCALL_EARLY_RETURN(ret) \ 10 | { \ 11 | if (ret < 0) { \ 12 | if (ret != -EAGAIN) { \ 13 | log_dump_local(1, __func__); \ 14 | var_dump_local(1, ":", ret); \ 15 | } \ 16 | errno = -ret; \ 17 | return -1; \ 18 | } \ 19 | } 20 | #define SYSCALL_SIZE_EARLY_RETURN(ret) \ 21 | { \ 22 | if (ret < 0) { \ 23 | if (ret != -EAGAIN) { \ 24 | log_dump_local(1, __func__); \ 25 | var_dump_local(1, ":", ret); \ 26 | } \ 27 | errno = -ret; \ 28 | return -1; \ 29 | } \ 30 | } 31 | 32 | #define linux_x8664_asm_syscall6( \ 33 | syscall_num, arg1, arg2, arg3, arg4, arg5, arg6) \ 34 | __linux_x8664_asm_syscall6( \ 35 | (int64_t)syscall_num, \ 36 | (int64_t)arg1, \ 37 | (int64_t)arg2, \ 38 | (int64_t)arg3, \ 39 | (int64_t)arg4, \ 40 | (int64_t)arg5, \ 41 | (int64_t)arg6) 42 | 43 | #define linux_x8664_asm_syscall5( \ 44 | syscall_num, arg1, arg2, arg3, arg4, arg5) \ 45 | __linux_x8664_asm_syscall5( \ 46 | (int64_t)syscall_num, \ 47 | (int64_t)arg1, \ 48 | (int64_t)arg2, \ 49 | (int64_t)arg3, \ 50 | (int64_t)arg4, \ 51 | (int64_t)arg5) 52 | 53 | #define linux_x8664_asm_syscall4( \ 54 | syscall_num, arg1, arg2, arg3, arg4) \ 55 | __linux_x8664_asm_syscall4( \ 56 | (int64_t)syscall_num, \ 57 | (int64_t)arg1, \ 58 | (int64_t)arg2, \ 59 | (int64_t)arg3, \ 60 | (int64_t)arg4) 61 | 62 | #define linux_x8664_asm_syscall3( \ 63 | syscall_num, arg1, arg2, arg3) \ 64 | __linux_x8664_asm_syscall3( \ 65 | (int64_t)syscall_num, \ 66 | (int64_t)arg1, \ 67 | (int64_t)arg2, \ 68 | (int64_t)arg3) 69 | 70 | #define linux_x8664_asm_syscall2( \ 71 | syscall_num, arg1, arg2) \ 72 | __linux_x8664_asm_syscall2( \ 73 | (int64_t)syscall_num, \ 74 | (int64_t)arg1, \ 75 | (int64_t)arg2) 76 | 77 | #define linux_x8664_asm_syscall1( \ 78 | syscall_num, arg1) \ 79 | __linux_x8664_asm_syscall1( \ 80 | (int64_t)syscall_num, \ 81 | (int64_t)arg1) 82 | 83 | #define linux_x8664_asm_syscall0( \ 84 | syscall_num, arg1) \ 85 | __linux_x8664_asm_syscall0( \ 86 | (int64_t)syscall_num) 87 | 88 | extern int32_t __linux_x8664_asm_syscall6( 89 | int64_t syscall_num, 90 | int64_t arg1, 91 | int64_t arg2, 92 | int64_t arg3, 93 | int64_t arg4, 94 | int64_t arg5, 95 | int64_t arg6); 96 | 97 | extern int32_t __linux_x8664_asm_syscall5( 98 | int64_t syscall_num, 99 | int64_t arg1, 100 | int64_t arg2, 101 | int64_t arg3, 102 | int64_t arg4, 103 | int64_t arg5); 104 | 105 | extern int32_t __linux_x8664_asm_syscall4( 106 | int64_t syscall_num, 107 | int64_t arg1, 108 | int64_t arg2, 109 | int64_t arg3, 110 | int64_t arg4); 111 | 112 | extern int32_t __linux_x8664_asm_syscall3( 113 | int64_t syscall_num, 114 | int64_t arg1, 115 | int64_t arg2, 116 | int64_t arg3); 117 | 118 | extern int32_t __linux_x8664_asm_syscall2( 119 | int64_t syscall_num, 120 | int64_t arg1, 121 | int64_t arg2); 122 | 123 | extern int32_t __linux_x8664_asm_syscall1( 124 | int64_t syscall_num, 125 | int64_t arg1); 126 | 127 | extern int32_t __linux_x8664_asm_syscall0( 128 | int64_t syscall_num); 129 | 130 | #define __NR_read 0 131 | #define __NR_write 1 132 | #define __NR_open 2 133 | #define __NR_close 3 134 | #define __NR_stat 4 135 | #define __NR_fstat 5 136 | #define __NR_lstat 6 137 | #define __NR_poll 7 138 | #define __NR_lseek 8 139 | #define __NR_mmap 9 140 | #define __NR_mprotect 10 141 | #define __NR_munmap 11 142 | #define __NR_brk 12 143 | #define __NR_rt_sigaction 13 144 | #define __NR_rt_sigprocmask 14 145 | #define __NR_rt_sigreturn 15 146 | #define __NR_ioctl 16 147 | #define __NR_pread64 17 148 | #define __NR_pwrite64 18 149 | #define __NR_readv 19 150 | #define __NR_writev 20 151 | #define __NR_access 21 152 | #define __NR_pipe 22 153 | #define __NR_select 23 154 | #define __NR_sched_yield 24 155 | #define __NR_mremap 25 156 | #define __NR_msync 26 157 | #define __NR_mincore 27 158 | #define __NR_madvise 28 159 | #define __NR_shmget 29 160 | #define __NR_shmat 30 161 | #define __NR_shmctl 31 162 | #define __NR_dup 32 163 | #define __NR_dup2 33 164 | #define __NR_pause 34 165 | #define __NR_nanosleep 35 166 | #define __NR_getitimer 36 167 | #define __NR_alarm 37 168 | #define __NR_setitimer 38 169 | #define __NR_getpid 39 170 | #define __NR_sendfile 40 171 | #define __NR_socket 41 172 | #define __NR_connect 42 173 | #define __NR_accept 43 174 | #define __NR_sendto 44 175 | #define __NR_recvfrom 45 176 | #define __NR_sendmsg 46 177 | #define __NR_recvmsg 47 178 | #define __NR_shutdown 48 179 | #define __NR_bind 49 180 | #define __NR_listen 50 181 | #define __NR_getsockname 51 182 | #define __NR_getpeername 52 183 | #define __NR_socketpair 53 184 | #define __NR_setsockopt 54 185 | #define __NR_getsockopt 55 186 | #define __NR_clone 56 187 | #define __NR_fork 57 188 | #define __NR_vfork 58 189 | #define __NR_execve 59 190 | #define __NR_exit 60 191 | #define __NR_wait4 61 192 | #define __NR_kill 62 193 | #define __NR_uname 63 194 | #define __NR_semget 64 195 | #define __NR_semop 65 196 | #define __NR_semctl 66 197 | #define __NR_shmdt 67 198 | #define __NR_msgget 68 199 | #define __NR_msgsnd 69 200 | #define __NR_msgrcv 70 201 | #define __NR_msgctl 71 202 | #define __NR_fcntl 72 203 | #define __NR_flock 73 204 | #define __NR_fsync 74 205 | #define __NR_fdatasync 75 206 | #define __NR_truncate 76 207 | #define __NR_ftruncate 77 208 | #define __NR_getdents 78 209 | #define __NR_getcwd 79 210 | #define __NR_chdir 80 211 | #define __NR_fchdir 81 212 | #define __NR_rename 82 213 | #define __NR_mkdir 83 214 | #define __NR_rmdir 84 215 | #define __NR_creat 85 216 | #define __NR_link 86 217 | #define __NR_unlink 87 218 | #define __NR_symlink 88 219 | #define __NR_readlink 89 220 | #define __NR_chmod 90 221 | #define __NR_fchmod 91 222 | #define __NR_chown 92 223 | #define __NR_fchown 93 224 | #define __NR_lchown 94 225 | #define __NR_umask 95 226 | #define __NR_gettimeofday 96 227 | #define __NR_getrlimit 97 228 | #define __NR_getrusage 98 229 | #define __NR_sysinfo 99 230 | #define __NR_times 100 231 | #define __NR_ptrace 101 232 | #define __NR_getuid 102 233 | #define __NR_syslog 103 234 | #define __NR_getgid 104 235 | #define __NR_setuid 105 236 | #define __NR_setgid 106 237 | #define __NR_geteuid 107 238 | #define __NR_getegid 108 239 | #define __NR_setpgid 109 240 | #define __NR_getppid 110 241 | #define __NR_getpgrp 111 242 | #define __NR_setsid 112 243 | #define __NR_setreuid 113 244 | #define __NR_setregid 114 245 | #define __NR_getgroups 115 246 | #define __NR_setgroups 116 247 | #define __NR_setresuid 117 248 | #define __NR_getresuid 118 249 | #define __NR_setresgid 119 250 | #define __NR_getresgid 120 251 | #define __NR_getpgid 121 252 | #define __NR_setfsuid 122 253 | #define __NR_setfsgid 123 254 | #define __NR_getsid 124 255 | #define __NR_capget 125 256 | #define __NR_capset 126 257 | #define __NR_rt_sigpending 127 258 | #define __NR_rt_sigtimedwait 128 259 | #define __NR_rt_sigqueueinfo 129 260 | #define __NR_rt_sigsuspend 130 261 | #define __NR_sigaltstack 131 262 | #define __NR_utime 132 263 | #define __NR_mknod 133 264 | #define __NR_uselib 134 265 | #define __NR_personality 135 266 | #define __NR_ustat 136 267 | #define __NR_statfs 137 268 | #define __NR_fstatfs 138 269 | #define __NR_sysfs 139 270 | #define __NR_getpriority 140 271 | #define __NR_setpriority 141 272 | #define __NR_sched_setparam 142 273 | #define __NR_sched_getparam 143 274 | #define __NR_sched_setscheduler 144 275 | #define __NR_sched_getscheduler 145 276 | #define __NR_sched_get_priority_max 146 277 | #define __NR_sched_get_priority_min 147 278 | #define __NR_sched_rr_get_interval 148 279 | #define __NR_mlock 149 280 | #define __NR_munlock 150 281 | #define __NR_mlockall 151 282 | #define __NR_munlockall 152 283 | #define __NR_vhangup 153 284 | #define __NR_modify_ldt 154 285 | #define __NR_pivot_root 155 286 | #define __NR__sysctl 156 287 | #define __NR_prctl 157 288 | #define __NR_arch_prctl 158 289 | #define __NR_adjtimex 159 290 | #define __NR_setrlimit 160 291 | #define __NR_chroot 161 292 | #define __NR_sync 162 293 | #define __NR_acct 163 294 | #define __NR_settimeofday 164 295 | #define __NR_mount 165 296 | #define __NR_umount2 166 297 | #define __NR_swapon 167 298 | #define __NR_swapoff 168 299 | #define __NR_reboot 169 300 | #define __NR_sethostname 170 301 | #define __NR_setdomainname 171 302 | #define __NR_iopl 172 303 | #define __NR_ioperm 173 304 | #define __NR_create_module 174 305 | #define __NR_init_module 175 306 | #define __NR_delete_module 176 307 | #define __NR_get_kernel_syms 177 308 | #define __NR_query_module 178 309 | #define __NR_quotactl 179 310 | #define __NR_nfsservctl 180 311 | #define __NR_getpmsg 181 312 | #define __NR_putpmsg 182 313 | #define __NR_afs_syscall 183 314 | #define __NR_tuxcall 184 315 | #define __NR_security 185 316 | #define __NR_gettid 186 317 | #define __NR_readahead 187 318 | #define __NR_setxattr 188 319 | #define __NR_lsetxattr 189 320 | #define __NR_fsetxattr 190 321 | #define __NR_getxattr 191 322 | #define __NR_lgetxattr 192 323 | #define __NR_fgetxattr 193 324 | #define __NR_listxattr 194 325 | #define __NR_llistxattr 195 326 | #define __NR_flistxattr 196 327 | #define __NR_removexattr 197 328 | #define __NR_lremovexattr 198 329 | #define __NR_fremovexattr 199 330 | #define __NR_tkill 200 331 | #define __NR_time 201 332 | #define __NR_futex 202 333 | #define __NR_sched_setaffinity 203 334 | #define __NR_sched_getaffinity 204 335 | #define __NR_set_thread_area 205 336 | #define __NR_io_setup 206 337 | #define __NR_io_destroy 207 338 | #define __NR_io_getevents 208 339 | #define __NR_io_submit 209 340 | #define __NR_io_cancel 210 341 | #define __NR_get_thread_area 211 342 | #define __NR_lookup_dcookie 212 343 | #define __NR_epoll_create 213 344 | #define __NR_epoll_ctl_old 214 345 | #define __NR_epoll_wait_old 215 346 | #define __NR_remap_file_pages 216 347 | #define __NR_getdents64 217 348 | #define __NR_set_tid_address 218 349 | #define __NR_restart_syscall 219 350 | #define __NR_semtimedop 220 351 | #define __NR_fadvise64 221 352 | #define __NR_timer_create 222 353 | #define __NR_timer_settime 223 354 | #define __NR_timer_gettime 224 355 | #define __NR_timer_getoverrun 225 356 | #define __NR_timer_delete 226 357 | #define __NR_clock_settime 227 358 | #define __NR_clock_gettime 228 359 | #define __NR_clock_getres 229 360 | #define __NR_clock_nanosleep 230 361 | #define __NR_exit_group 231 362 | #define __NR_epoll_wait 232 363 | #define __NR_epoll_ctl 233 364 | #define __NR_tgkill 234 365 | #define __NR_utimes 235 366 | #define __NR_vserver 236 367 | #define __NR_mbind 237 368 | #define __NR_set_mempolicy 238 369 | #define __NR_get_mempolicy 239 370 | #define __NR_mq_open 240 371 | #define __NR_mq_unlink 241 372 | #define __NR_mq_timedsend 242 373 | #define __NR_mq_timedreceive 243 374 | #define __NR_mq_notify 244 375 | #define __NR_mq_getsetattr 245 376 | #define __NR_kexec_load 246 377 | #define __NR_waitid 247 378 | #define __NR_add_key 248 379 | #define __NR_request_key 249 380 | #define __NR_keyctl 250 381 | #define __NR_ioprio_set 251 382 | #define __NR_ioprio_get 252 383 | #define __NR_inotify_init 253 384 | #define __NR_inotify_add_watch 254 385 | #define __NR_inotify_rm_watch 255 386 | #define __NR_migrate_pages 256 387 | #define __NR_openat 257 388 | #define __NR_mkdirat 258 389 | #define __NR_mknodat 259 390 | #define __NR_fchownat 260 391 | #define __NR_futimesat 261 392 | #define __NR_newfstatat 262 393 | #define __NR_unlinkat 263 394 | #define __NR_renameat 264 395 | #define __NR_linkat 265 396 | #define __NR_symlinkat 266 397 | #define __NR_readlinkat 267 398 | #define __NR_fchmodat 268 399 | #define __NR_faccessat 269 400 | #define __NR_pselect6 270 401 | #define __NR_ppoll 271 402 | #define __NR_unshare 272 403 | #define __NR_set_robust_list 273 404 | #define __NR_get_robust_list 274 405 | #define __NR_splice 275 406 | #define __NR_tee 276 407 | #define __NR_sync_file_range 277 408 | #define __NR_vmsplice 278 409 | #define __NR_move_pages 279 410 | #define __NR_utimensat 280 411 | #define __NR_epoll_pwait 281 412 | #define __NR_signalfd 282 413 | #define __NR_timerfd_create 283 414 | #define __NR_eventfd 284 415 | #define __NR_fallocate 285 416 | #define __NR_timerfd_settime 286 417 | #define __NR_timerfd_gettime 287 418 | #define __NR_accept4 288 419 | #define __NR_signalfd4 289 420 | #define __NR_eventfd2 290 421 | #define __NR_epoll_create1 291 422 | #define __NR_dup3 292 423 | #define __NR_pipe2 293 424 | #define __NR_inotify_init1 294 425 | #define __NR_preadv 295 426 | #define __NR_pwritev 296 427 | #define __NR_rt_tgsigqueueinfo 297 428 | #define __NR_perf_event_open 298 429 | #define __NR_recvmmsg 299 430 | #define __NR_fanotify_init 300 431 | #define __NR_fanotify_mark 301 432 | #define __NR_prlimit64 302 433 | #define __NR_name_to_handle_at 303 434 | #define __NR_open_by_handle_at 304 435 | #define __NR_clock_adjtime 305 436 | #define __NR_syncfs 306 437 | #define __NR_sendmmsg 307 438 | #define __NR_setns 308 439 | #define __NR_getcpu 309 440 | #define __NR_process_vm_readv 310 441 | #define __NR_process_vm_writev 311 442 | #define __NR_kcmp 312 443 | #define __NR_finit_module 313 444 | #define __NR_sched_setattr 314 445 | #define __NR_sched_getattr 315 446 | #define __NR_renameat2 316 447 | #define __NR_seccomp 317 448 | #define __NR_getrandom 318 449 | #define __NR_memfd_create 319 450 | #define __NR_kexec_file_load 320 451 | #define __NR_bpf 321 452 | #define __NR_execveat 322 453 | #define __NR_userfaultfd 323 454 | #define __NR_membarrier 324 455 | #define __NR_mlock2 325 456 | #define __NR_copy_file_range 326 457 | #define __NR_preadv2 327 458 | #define __NR_pwritev2 328 459 | #define __NR_pkey_mprotect 329 460 | #define __NR_pkey_alloc 330 461 | #define __NR_pkey_free 331 462 | #define __NR_statx 332 463 | #define __NR_io_pgetevents 333 464 | #define __NR_rseq 334 465 | #define __NR_pidfd_send_signal 424 466 | #define __NR_io_uring_setup 425 467 | #define __NR_io_uring_enter 426 468 | #define __NR_io_uring_register 427 469 | #define __NR_open_tree 428 470 | #define __NR_move_mount 429 471 | #define __NR_fsopen 430 472 | #define __NR_fsconfig 431 473 | #define __NR_fsmount 432 474 | #define __NR_fspick 433 475 | #define __NR_pidfd_open 434 476 | #define __NR_clone3 435 477 | 478 | #endif 479 | -------------------------------------------------------------------------------- /src/arch/linux/errno.h: -------------------------------------------------------------------------------- 1 | #ifndef NOSTR_LINUX_ERRNO_H_ 2 | #define NOSTR_LINUX_ERRNO_H_ 3 | 4 | #include "../../util/types.h" 5 | 6 | #define EPERM 1 // Operation not permitted 7 | #define ENOENT 2 // No such file or directory 8 | #define ESRCH 3 // No such process 9 | #define EINTR 4 // Interrupted system call 10 | #define EIO 5 // I/O error 11 | #define ENXIO 6 // No such device or address 12 | #define E2BIG 7 // Argument list too long 13 | #define ENOEXEC 8 // Exec format error 14 | #define EBADF 9 // Bad file number 15 | #define ECHILD 10 // No child processes 16 | #define EAGAIN 11 // Try again 17 | #define ENOMEM 12 // Out of memory 18 | #define EACCES 13 // Permission denied 19 | #define EFAULT 14 // Bad address 20 | #define ENOTBLK 15 // Block device required 21 | #define EBUSY 16 // Device or resource busy 22 | #define EEXIST 17 // File exists 23 | #define EXDEV 18 // Cross-device link 24 | #define ENODEV 19 // No such device 25 | #define ENOTDIR 20 // Not a directory 26 | #define EISDIR 21 // Is a directory 27 | #define EINVAL 22 // Invalid argument 28 | #define ENFILE 23 // File table overflow 29 | #define EMFILE 24 // Too many open files 30 | #define ENOTTY 25 // Not a typewriter 31 | #define ETXTBSY 26 // Text file busy 32 | #define EFBIG 27 // File too large 33 | #define ENOSPC 28 // No space left on device 34 | #define ESPIPE 29 // Illegal seek 35 | #define EROFS 30 // Read-only file system 36 | #define EMLINK 31 // Too many links 37 | #define EPIPE 32 // Broken pipe 38 | #define EDOM 33 // Math argument out of domain of func 39 | #define ERANGE 34 // Math result not representable 40 | #define EDEADLK 35 // Resource deadlock would occur 41 | #define ENAMETOOLONG 36 // File name too long 42 | #define ENOLCK 37 // No record locks available 43 | #define ENOSYS 38 // Invalid system call number 44 | #define ENOTEMPTY 39 // Directory not empty 45 | #define ELOOP 40 // Too many symbolic links encountered 46 | #define EWOULDBLOCK EAGAIN // Operation would block 47 | #define ENOMSG 42 // No message of desired type 48 | #define EIDRM 43 // Identifier removed 49 | #define ECHRNG 44 // Channel number out of range 50 | #define EL2NSYNC 45 // Level 2 not synchronized 51 | #define EL3HLT 46 // Level 3 halted 52 | #define EL3RST 47 // Level 3 reset 53 | #define ELNRNG 48 // Link number out of range 54 | #define EUNATCH 49 // Protocol driver not attached 55 | #define ENOCSI 50 // No CSI structure available 56 | #define EL2HLT 51 // Level 2 halted 57 | #define EBADE 52 // Invalid exchange 58 | #define EBADR 53 // Invalid request descriptor 59 | #define EXFULL 54 // Exchange full 60 | #define ENOANO 55 // No anode 61 | #define EBADRQC 56 // Invalid request code 62 | #define EBADSLT 57 // Invalid slot 63 | #define EDEADLOCK EDEADLK 64 | #define EBFONT 59 // Bad font file format 65 | #define ENOSTR 60 // Device not a stream 66 | #define ENODATA 61 // No data available 67 | #define ETIME 62 // Timer expired 68 | #define ENOSR 63 // Out of streams resources 69 | #define ENONET 64 // Machine is not on the network 70 | #define ENOPKG 65 // Package not installed 71 | #define EREMOTE 66 // Object is remote 72 | #define ENOLINK 67 // Link has been severed 73 | #define EADV 68 // Advertise error 74 | #define ESRMNT 69 // Srmount error 75 | #define ECOMM 70 // Communication error on send 76 | #define EPROTO 71 // Protocol error 77 | #define EMULTIHOP 72 // Multihop attempted 78 | #define EDOTDOT 73 // RFS specific error 79 | #define EBADMSG 74 // Not a data message 80 | #define EOVERFLOW 75 // Value too large for defined data type 81 | #define ENOTUNIQ 76 // Name not unique on network 82 | #define EBADFD 77 // File descriptor in bad state 83 | #define EREMCHG 78 // Remote address changed 84 | #define ELIBACC 79 // Can not access a needed shared library 85 | #define ELIBBAD 80 // Accessing a corrupted shared library 86 | #define ELIBSCN 81 // .lib section in a.out corrupted 87 | #define ELIBMAX 82 // Attempting to link in too many shared libraries 88 | #define ELIBEXEC 83 // Cannot exec a shared library directly 89 | #define EILSEQ 84 // Illegal byte sequence 90 | #define ERESTART 85 // Interrupted system call should be restarted 91 | #define ESTRPIPE 86 // Streams pipe error 92 | #define EUSERS 87 // Too many users 93 | #define ENOTSOCK 88 // Socket operation on non-socket 94 | #define EDESTADDRREQ 89 // Destination address required 95 | #define EMSGSIZE 90 // Message too long 96 | #define EPROTOTYPE 91 // Protocol wrong type for socket 97 | #define ENOPROTOOPT 92 // Protocol not available 98 | #define EPROTONOSUPPORT 93 // Protocol not supported 99 | #define ESOCKTNOSUPPORT 94 // Socket type not supported 100 | #define EOPNOTSUPP 95 // Operation not supported on transport endpoint 101 | #define EPFNOSUPPORT 96 // Protocol family not supported 102 | #define EAFNOSUPPORT 97 // Address family not supported by protocol 103 | #define EADDRINUSE 98 // Address already in use 104 | #define EADDRNOTAVAIL 99 // Cannot assign requested address 105 | #define ENETDOWN 100 // Network is down 106 | #define ENETUNREACH 101 // Network is unreachable 107 | #define ENETRESET 102 // Network dropped connection because of reset 108 | #define ECONNABORTED 103 // Software caused connection abort 109 | #define ECONNRESET 104 // Connection reset by peer 110 | #define ENOBUFS 105 // No buffer space available 111 | #define EISCONN 106 // Transport endpoint is already connected 112 | #define ENOTCONN 107 // Transport endpoint is not connected 113 | #define ESHUTDOWN 108 // Cannot send after transport endpoint shutdown 114 | #define ETOOMANYREFS 109 // Too many references: cannot splice 115 | #define ETIMEDOUT 110 // Connection timed out 116 | #define ECONNREFUSED 111 // Connection refused 117 | #define EHOSTDOWN 112 // Host is down 118 | #define EHOSTUNREACH 113 // No route to host 119 | #define EALREADY 114 // Operation already in progress 120 | #define EINPROGRESS 115 // Operation now in progress 121 | #define ESTALE 116 // Stale file handle 122 | #define EUCLEAN 117 // Structure needs cleaning 123 | #define ENOTNAM 118 // Not a XENIX named type file 124 | #define ENAVAIL 119 // No XENIX semaphores available 125 | #define EISNAM 120 // Is a named type file 126 | #define EREMOTEIO 121 // Remote I/O error 127 | #define EDQUOT 122 // Quota exceeded 128 | #define ENOMEDIUM 123 // No medium found 129 | #define EMEDIUMTYPE 124 // Wrong medium type 130 | #define ECANCELED 125 // Operation Canceled 131 | #define ENOKEY 126 // Required key not available 132 | #define EKEYEXPIRED 127 // Key has expired 133 | #define EKEYREVOKED 128 // Key has been revoked 134 | #define EKEYREJECTED 129 // Key was rejected by service 135 | #define EOWNERDEAD 130 // Owner died 136 | #define ENOTRECOVERABLE 131 // State not recoverable 137 | #define ERFKILL 132 // Operation not possible due to RF-kill 138 | #define EHWPOISON 133 // Memory page has hardware error 139 | 140 | #ifndef __cplusplus 141 | #define errno (*__errno_location()) 142 | 143 | static int32_t* __errno_location(void) 144 | { 145 | static thread_local int32_t my_errno = 0; 146 | return &my_errno; 147 | } 148 | 149 | static inline const char* strerror(const int errnum) 150 | { 151 | switch (errnum) { 152 | case EPERM: 153 | return "Operation not permitted"; 154 | case ENOENT: 155 | return "No such file or directory"; 156 | case ESRCH: 157 | return "No such process"; 158 | case EINTR: 159 | return "Interrupted system call"; 160 | case EIO: 161 | return "I/O error"; 162 | case ENXIO: 163 | return "No such device or address"; 164 | case E2BIG: 165 | return "Argument list too long"; 166 | case ENOEXEC: 167 | return "Exec format error"; 168 | case EBADF: 169 | return "Bad file number"; 170 | case ECHILD: 171 | return "No child processes"; 172 | case EAGAIN: 173 | return "Try again"; 174 | case ENOMEM: 175 | return "Out of memory"; 176 | case EACCES: 177 | return "Permission denied"; 178 | case EFAULT: 179 | return "Bad address"; 180 | case ENOTBLK: 181 | return "Block device required"; 182 | case EBUSY: 183 | return "Device or resource busy"; 184 | case EEXIST: 185 | return "File exists"; 186 | case EXDEV: 187 | return "Cross-device link"; 188 | case ENODEV: 189 | return "No such device"; 190 | case ENOTDIR: 191 | return "Not a directory"; 192 | case EISDIR: 193 | return "Is a directory"; 194 | case EINVAL: 195 | return "Invalid argument"; 196 | case ENFILE: 197 | return "File table overflow"; 198 | case EMFILE: 199 | return "Too many open files"; 200 | case ENOTTY: 201 | return "Not a typewriter"; 202 | case ETXTBSY: 203 | return "Text file busy"; 204 | case EFBIG: 205 | return "File too large"; 206 | case ENOSPC: 207 | return "No space left on device"; 208 | case ESPIPE: 209 | return "Illegal seek"; 210 | case EROFS: 211 | return "Read-only file system"; 212 | case EMLINK: 213 | return "Too many links"; 214 | case EPIPE: 215 | return "Broken pipe"; 216 | case EDOM: 217 | return "Math argument out of domain of func"; 218 | case ERANGE: 219 | return "Math result not representable"; 220 | case EDEADLK: 221 | return "Resource deadlock would occur"; 222 | case ENAMETOOLONG: 223 | return "File name too long"; 224 | case ENOLCK: 225 | return "No record locks available"; 226 | case ENOSYS: 227 | return "Invalid system call number"; 228 | case ENOTEMPTY: 229 | return "Directory not empty"; 230 | case ELOOP: 231 | return "Too many symbolic links encountered"; 232 | case ENOMSG: 233 | return "No message of desired type"; 234 | case EIDRM: 235 | return "Identifier removed"; 236 | case ECHRNG: 237 | return "Channel number out of range"; 238 | case EL2NSYNC: 239 | return "Level 2 not synchronized"; 240 | case EL3HLT: 241 | return "Level 3 halted"; 242 | case EL3RST: 243 | return "Level 3 reset"; 244 | case ELNRNG: 245 | return "Link number out of range"; 246 | case EUNATCH: 247 | return "Protocol driver not attached"; 248 | case ENOCSI: 249 | return "No CSI structure available"; 250 | case EL2HLT: 251 | return "Level 2 halted"; 252 | case EBADE: 253 | return "Invalid exchange"; 254 | case EBADR: 255 | return "Invalid request descriptor"; 256 | case EXFULL: 257 | return "Exchange full"; 258 | case ENOANO: 259 | return "No anode"; 260 | case EBADRQC: 261 | return "Invalid request code"; 262 | case EBADSLT: 263 | return "Invalid slot"; 264 | case EBFONT: 265 | return "Bad font file format"; 266 | case ENOSTR: 267 | return "Device not a stream"; 268 | case ENODATA: 269 | return "No data available"; 270 | case ETIME: 271 | return "Timer expired"; 272 | case ENOSR: 273 | return "Out of streams resources"; 274 | case ENONET: 275 | return "Machine is not on the network"; 276 | case ENOPKG: 277 | return "Package not installed"; 278 | case EREMOTE: 279 | return "Object is remote"; 280 | case ENOLINK: 281 | return "Link has been severed"; 282 | case EADV: 283 | return "Advertise error"; 284 | case ESRMNT: 285 | return "Srmount error"; 286 | case ECOMM: 287 | return "Communication error on send"; 288 | case EPROTO: 289 | return "Protocol error"; 290 | case EMULTIHOP: 291 | return "Multihop attempted"; 292 | case EDOTDOT: 293 | return "RFS specific error"; 294 | case EBADMSG: 295 | return "Not a data message"; 296 | case EOVERFLOW: 297 | return "Value too large for defined data type"; 298 | case ENOTUNIQ: 299 | return "Name not unique on network"; 300 | case EBADFD: 301 | return "File descriptor in bad state"; 302 | case EREMCHG: 303 | return "Remote address changed"; 304 | case ELIBACC: 305 | return "Cannot access a needed shared library"; 306 | case ELIBBAD: 307 | return "Accessing a corrupted shared library"; 308 | case ELIBSCN: 309 | return ".lib section in a.out corrupted"; 310 | case ELIBMAX: 311 | return "Attempting to link in too many shared libraries"; 312 | case ELIBEXEC: 313 | return "Cannot exec a shared library directly"; 314 | case EILSEQ: 315 | return "Illegal byte sequence"; 316 | case ERESTART: 317 | return "Interrupted system call should be restarted"; 318 | case ESTRPIPE: 319 | return "Streams pipe error"; 320 | case EUSERS: 321 | return "Too many users"; 322 | case ENOTSOCK: 323 | return "Socket operation on non-socket"; 324 | case EDESTADDRREQ: 325 | return "Destination address required"; 326 | case EMSGSIZE: 327 | return "Message too long"; 328 | case EPROTOTYPE: 329 | return "Protocol wrong type for socket"; 330 | case ENOPROTOOPT: 331 | return "Protocol not available"; 332 | case EPROTONOSUPPORT: 333 | return "Protocol not supported"; 334 | case ESOCKTNOSUPPORT: 335 | return "Socket type not supported"; 336 | case EOPNOTSUPP: 337 | return "Operation not supported on transport endpoint"; 338 | case EPFNOSUPPORT: 339 | return "Protocol family not supported"; 340 | case EAFNOSUPPORT: 341 | return "Address family not supported by protocol"; 342 | case EADDRINUSE: 343 | return "Address already in use"; 344 | case EADDRNOTAVAIL: 345 | return "Cannot assign requested address"; 346 | case ENETDOWN: 347 | return "Network is down"; 348 | case ENETUNREACH: 349 | return "Network is unreachable"; 350 | case ENETRESET: 351 | return "Network dropped connection because of reset"; 352 | case ECONNABORTED: 353 | return "Software caused connection abort"; 354 | case ECONNRESET: 355 | return "Connection reset by peer"; 356 | case ENOBUFS: 357 | return "No buffer space available"; 358 | case EISCONN: 359 | return "Transport endpoint is already connected"; 360 | case ENOTCONN: 361 | return "Transport endpoint is not connected"; 362 | case ESHUTDOWN: 363 | return "Cannot send after transport endpoint shutdown"; 364 | case ETOOMANYREFS: 365 | return "Too many references: cannot splice"; 366 | case ETIMEDOUT: 367 | return "Connection timed out"; 368 | case ECONNREFUSED: 369 | return "Connection refused"; 370 | case EHOSTDOWN: 371 | return "Host is down"; 372 | case EHOSTUNREACH: 373 | return "No route to host"; 374 | case EALREADY: 375 | return "Operation already in progress"; 376 | case EINPROGRESS: 377 | return "Operation now in progress"; 378 | case ESTALE: 379 | return "Stale file handle"; 380 | case EUCLEAN: 381 | return "Structure needs cleaning"; 382 | case ENOTNAM: 383 | return "Not a XENIX named type file"; 384 | case ENAVAIL: 385 | return "No XENIX semaphores available"; 386 | case EISNAM: 387 | return "Is a named type file"; 388 | case EREMOTEIO: 389 | return "Remote I/O error"; 390 | case EDQUOT: 391 | return "Quota exceeded"; 392 | case ENOMEDIUM: 393 | return "No medium found"; 394 | case EMEDIUMTYPE: 395 | return "Wrong medium type"; 396 | case ECANCELED: 397 | return "Operation Canceled"; 398 | case ENOKEY: 399 | return "Required key not available"; 400 | case EKEYEXPIRED: 401 | return "Key has expired"; 402 | case EKEYREVOKED: 403 | return "Key has been revoked"; 404 | case EKEYREJECTED: 405 | return "Key was rejected by service"; 406 | case EOWNERDEAD: 407 | return "Owner died"; 408 | case ENOTRECOVERABLE: 409 | return "State not recoverable"; 410 | case ERFKILL: 411 | return "Operation not possible due to RF-kill"; 412 | case EHWPOISON: 413 | return "Memory page has hardware error"; 414 | default: 415 | return "Unknown error"; 416 | } 417 | } 418 | #endif 419 | 420 | #endif 421 | --------------------------------------------------------------------------------