Zero-copy FTP/HTTP Daemon compatible with all POSIX systems
| 1 | /* |
| 2 | MIT License |
| 3 | |
| 4 | Copyright (c) 2026 Seregon |
| 5 | |
| 6 | Permission is hereby granted, free of charge, to any person obtaining a copy |
| 7 | of this software and associated documentation files (the "Software"), to deal |
| 8 | in the Software without restriction, including without limitation the rights |
| 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 10 | copies of the Software, and to permit persons to whom the Software is |
| 11 | furnished to do so, subject to the following conditions: |
| 12 | |
| 13 | The above copyright notice and this permission notice shall be included in all |
| 14 | copies or substantial portions of the Software. |
| 15 | |
| 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| 22 | SOFTWARE. |
| 23 | */ |
| 24 | |
| 25 | /** |
| 26 | * @file http_config.h |
| 27 | * @brief HTTP server configuration |
| 28 | */ |
| 29 | |
| 30 | #ifndef HTTP_CONFIG_H |
| 31 | #define HTTP_CONFIG_H |
| 32 | |
| 33 | #include <stdint.h> |
| 34 | |
| 35 | /* Compile-time feature toggle */ |
| 36 | #ifndef ENABLE_ZHTTPD |
| 37 | #define ENABLE_ZHTTPD 1 |
| 38 | #endif |
| 39 | |
| 40 | #ifndef ENABLE_HTTP_GZIP |
| 41 | #define ENABLE_HTTP_GZIP 0 |
| 42 | #endif |
| 43 | |
| 44 | #ifndef HTTP_DEBUG_LOG_HEADERS |
| 45 | #define HTTP_DEBUG_LOG_HEADERS 0 |
| 46 | #endif |
| 47 | |
| 48 | /*---------------------------------------------------------------------------* |
| 49 | * Web root directory — static files are served from this path. |
| 50 | * |
| 51 | * When set, serve_static() reads files from disk instead of the embedded |
| 52 | * http_resources.c blob. This decouples the web UI from the payload binary, |
| 53 | * making development faster and the payload ~10 MB smaller. |
| 54 | * |
| 55 | * On PS5: /data/zftpd/web/ |
| 56 | * On desktop (dev): ./web/ (relative to cwd) |
| 57 | *---------------------------------------------------------------------------*/ |
| 58 | #ifndef HTTP_WEB_ROOT |
| 59 | #if defined(PS5) || defined(PLATFORM_PS5) || defined(PS4) || defined(PLATFORM_PS4) |
| 60 | #define HTTP_WEB_ROOT "/data/zftpd/web/" |
| 61 | #else |
| 62 | #define HTTP_WEB_ROOT "web/" |
| 63 | #endif |
| 64 | #endif |
| 65 | |
| 66 | /* Server configuration */ |
| 67 | #define HTTP_DEFAULT_PORT 8888 |
| 68 | #define HTTP_MAX_CONNECTIONS 100 |
| 69 | #define HTTP_REQUEST_TIMEOUT 30 |
| 70 | #define HTTP_KEEPALIVE_TIMEOUT 60 |
| 71 | |
| 72 | /* Buffer sizes */ |
| 73 | #define HTTP_REQUEST_BUFFER_SIZE 8192 |
| 74 | #define HTTP_RESPONSE_BUFFER_SIZE 8192 |
| 75 | #define HTTP_URI_MAX_LENGTH 2048 |
| 76 | #define HTTP_HEADER_MAX_COUNT 32 |
| 77 | #define HTTP_HEADER_LINE_MAX 1024 |
| 78 | |
| 79 | /* |
| 80 | * File transfer chunk size for sendfile() in /api/download. |
| 81 | * |
| 82 | * PS5 REGRESSION NOTE: |
| 83 | * PS5's modified FreeBSD kernel triggers an internal buffer limit with |
| 84 | * sendfile() chunks >= 1 MB, returning EAGAIN (sbytes = 0) even on |
| 85 | * nominally-blocking sockets. Each EAGAIN costs a usleep(1 ms) yield |
| 86 | * (to avoid busy-spinning). At 1 MB/chunk: 1.2 GB / 1 MB × 1 ms = |
| 87 | * ~1.2 s of extra sleep latency per download — the observed regression |
| 88 | * ("previously downloaded 1.2 GB immediately"). |
| 89 | * |
| 90 | * 512 KB chunks stay well below the 1 MB trigger threshold, eliminating |
| 91 | * the EAGAIN storms while keeping syscall count reasonable (2× increase |
| 92 | * vs 1 MB, negligible vs I/O latency). |
| 93 | */ |
| 94 | #define HTTP_SENDFILE_CHUNK_SIZE (512 * 1024) |
| 95 | |
| 96 | /* Thread stack size (bytes) */ |
| 97 | #ifndef HTTP_THREAD_STACK_SIZE |
| 98 | #define HTTP_THREAD_STACK_SIZE (512U * 1024U) |
| 99 | #endif |
| 100 | |
| 101 | /* CSRF token length in hex characters (32 hex = 16 random bytes) */ |
| 102 | #define HTTP_CSRF_TOKEN_LENGTH 32 |
| 103 | |
| 104 | /*---------------------------------------------------------------------------* |
| 105 | * Upload feature toggle (disabled by default for security) |
| 106 | *---------------------------------------------------------------------------*/ |
| 107 | #ifndef ENABLE_WEB_UPLOAD |
| 108 | #define ENABLE_WEB_UPLOAD 0 |
| 109 | #endif |
| 110 | |
| 111 | /*---------------------------------------------------------------------------* |
| 112 | * Upload streaming performance tuning |
| 113 | * |
| 114 | * HTTP_UPLOAD_CHUNK_SIZE |
| 115 | * Heap-allocated read buffer used exclusively while an upload is active. |
| 116 | * Each event-loop iteration drains up to this many bytes from the socket. |
| 117 | * |
| 118 | * Rationale: |
| 119 | * The connection's header buffer (HTTP_REQUEST_BUFFER_SIZE = 8 KB) is |
| 120 | * reused during streaming, capping each read() at 8 KB. At 113 MB/s |
| 121 | * that requires ~13 800 read() + kqueue round-trips per second — well |
| 122 | * beyond what a single-threaded event loop can sustain. |
| 123 | * |
| 124 | * 256 KB reduces the required syscall rate to ~440/s at 113 MB/s, |
| 125 | * comfortably within budget while keeping per-upload heap overhead low. |
| 126 | * The buffer is allocated on upload start and freed on completion, so |
| 127 | * idle connections pay no memory cost. |
| 128 | * |
| 129 | * HTTP_UPLOAD_RCVBUF_SIZE |
| 130 | * SO_RCVBUF hint set on each accepted client socket. A larger kernel |
| 131 | * receive buffer absorbs TCP bursts between event-loop wakeups and keeps |
| 132 | * the sender's congestion window open. 2 MB is sufficient for RTTs up |
| 133 | * to ~140 µs at 113 MB/s (BDP = 113e6 * 140e-6 ≈ 15 KB; 2 MB is 133× |
| 134 | * the BDP — intentionally oversized so the kernel never stalls). |
| 135 | * |
| 136 | * IMPORTANT: This is a hint only. The kernel caps it at |
| 137 | * net.core.rmem_max (Linux) or kern.ipc.maxsockbuf (FreeBSD/PS5). |
| 138 | * Setting it higher than the system maximum is silently ignored. |
| 139 | *---------------------------------------------------------------------------*/ |
| 140 | #ifndef HTTP_UPLOAD_CHUNK_SIZE |
| 141 | #define HTTP_UPLOAD_CHUNK_SIZE (512U * 1024U) /* 512 KB per active upload */ |
| 142 | #endif |
| 143 | |
| 144 | #ifndef HTTP_UPLOAD_RCVBUF_SIZE |
| 145 | #define HTTP_UPLOAD_RCVBUF_SIZE (2U * 1024U * 1024U) /* 2 MB SO_RCVBUF hint */ |
| 146 | #endif |
| 147 | |
| 148 | /*---------------------------------------------------------------------------* |
| 149 | * Download pread() chunk size (PATH B — pread + pal_send_all) |
| 150 | * |
| 151 | * Used when sendfile() is unsafe (PS5/PS4 PFS/exFAT filesystems). |
| 152 | * Larger chunks mean fewer pread()+send_all() round-trips per MB. |
| 153 | * 2 MB halves the syscall count vs the previous 512 KB while staying |
| 154 | * well within the event-loop thread's heap budget. |
| 155 | *---------------------------------------------------------------------------*/ |
| 156 | #ifndef HTTP_DOWNLOAD_PREAD_CHUNK |
| 157 | #define HTTP_DOWNLOAD_PREAD_CHUNK (2U * 1024U * 1024U) /* 2 MB */ |
| 158 | #endif |
| 159 | |
| 160 | /*---------------------------------------------------------------------------* |
| 161 | * HTTP client send buffer (SO_SNDBUF) — download throughput on PS5/PS4 |
| 162 | * |
| 163 | * OrbisOS (PS5/PS4) clamps TCP send-buffer auto-tuning to a system |
| 164 | * maximum that is lower than what a GbE LAN download needs. Without |
| 165 | * an explicit SO_SNDBUF the accepted socket keeps the kernel default |
| 166 | * (~256 KB on tested firmwares), forcing pal_send_all() to block as |
| 167 | * soon as the buffer fills and limiting throughput to ~400 Mbps. |
| 168 | * |
| 169 | * Setting 4 MB matches FTP_TCP_DATA_SNDBUF (the same fix applied to |
| 170 | * FTP data sockets) and allows the TCP pipeline to stay full at |
| 171 | * 1 Gbps LAN RTTs (0.1–1 ms), recovering the missing 300+ Mbps. |
| 172 | * |
| 173 | * On other platforms SO_SNDBUF is left to kernel auto-tuning (set to 0 |
| 174 | * here so the caller can skip the setsockopt() call entirely). |
| 175 | *---------------------------------------------------------------------------*/ |
| 176 | #ifndef HTTP_SNDBUF_SIZE |
| 177 | #if defined(PS5) || defined(PLATFORM_PS5) || defined(PS4) || defined(PLATFORM_PS4) |
| 178 | #define HTTP_SNDBUF_SIZE (4U * 1024U * 1024U) /* 4 MB — bypass OrbisOS clamp */ |
| 179 | #else |
| 180 | #define HTTP_SNDBUF_SIZE 0U /* 0 = leave kernel auto-tuning active */ |
| 181 | #endif |
| 182 | #endif |
| 183 | |
| 184 | #endif /* HTTP_CONFIG_H */ |
| 185 |