e7738de6d2
CI / Deep Native Runtime Cases (1/6) (push) Has been skipped
CI / Native Preflight (push) Failing after 1s
CI / Native Runtime Cases (1/2) (push) Failing after 0s
CI / Native Runtime Cases (2/2) (push) Failing after 1s
CI / Native Metadata Reports (push) Failing after 0s
CI / Native Direct Backend Artifacts (push) Failing after 0s
CI / Native Sanitizer Smoke (push) Failing after 1s
CI / Command Contract Snapshots (push) Failing after 1s
CI / Deep Conformance Suite (push) Has been skipped
CI / Graph Build Perf (push) Failing after 1s
CI / Deep Native Preflight (push) Has been skipped
CI / Deep Native Runtime Cases (2/6) (push) Has been skipped
CI / Deep Native Runtime Cases (3/6) (push) Has been skipped
CI / Conformance Suite (push) Failing after 1s
CI / Workspace Checks (push) Failing after 0s
CI / Deep Native Runtime Cases (5/6) (push) Has been skipped
CI / Deep Native Runtime Cases (6/6) (push) Has been skipped
CI / Deep Native Runtime Cases (4/6) (push) Has been skipped
CI / Deep Graph Build Perf (push) Has been skipped
4516 lines
158 KiB
C
4516 lines
158 KiB
C
#if !defined(_WIN32) && !defined(_XOPEN_SOURCE)
|
|
#define _XOPEN_SOURCE 700
|
|
#endif
|
|
#if !defined(_WIN32) && !defined(_POSIX_C_SOURCE)
|
|
#define _POSIX_C_SOURCE 200809L
|
|
#endif
|
|
|
|
#include "zero_runtime.h"
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <limits.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
|
|
#if defined(_WIN32)
|
|
#include <conio.h>
|
|
#include <direct.h>
|
|
#include <io.h>
|
|
#include <process.h>
|
|
#include <windows.h>
|
|
typedef int ZeroWriteResult;
|
|
typedef int ZeroReadResult;
|
|
typedef struct _stat ZeroRuntimeStat;
|
|
#define ZERO_RUNTIME_WRITE _write
|
|
#define ZERO_RUNTIME_OPEN _open
|
|
#define ZERO_RUNTIME_READ _read
|
|
#define ZERO_RUNTIME_CLOSE _close
|
|
#define ZERO_RUNTIME_FSTAT _fstat
|
|
#define ZERO_RUNTIME_STAT _stat
|
|
#define ZERO_RUNTIME_LSEEK _lseeki64
|
|
#define ZERO_RUNTIME_CHDIR _chdir
|
|
#define ZERO_RUNTIME_OPEN_FLAGS (_O_RDONLY | _O_BINARY)
|
|
#define ZERO_RUNTIME_IS_REGULAR(mode) (((mode) & _S_IFREG) != 0)
|
|
#define ZERO_RUNTIME_IS_DIR(mode) (((mode) & _S_IFDIR) != 0)
|
|
#define ZERO_RUNTIME_ISATTY _isatty
|
|
#define ZERO_RUNTIME_STDIN_FD 0
|
|
#define ZERO_RUNTIME_STDOUT_FD 1
|
|
#ifndef ENABLE_VIRTUAL_TERMINAL_INPUT
|
|
#define ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200
|
|
#endif
|
|
#else
|
|
#include <dirent.h>
|
|
#include <poll.h>
|
|
#include <signal.h>
|
|
#include <sys/select.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/wait.h>
|
|
#include <termios.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
|
|
#include <util.h>
|
|
#else
|
|
#include <pty.h>
|
|
#endif
|
|
typedef ssize_t ZeroWriteResult;
|
|
typedef ssize_t ZeroReadResult;
|
|
typedef struct stat ZeroRuntimeStat;
|
|
#define ZERO_RUNTIME_WRITE write
|
|
#define ZERO_RUNTIME_OPEN open
|
|
#define ZERO_RUNTIME_READ read
|
|
#define ZERO_RUNTIME_CLOSE close
|
|
#define ZERO_RUNTIME_FSTAT fstat
|
|
#define ZERO_RUNTIME_STAT stat
|
|
#define ZERO_RUNTIME_LSEEK lseek
|
|
#define ZERO_RUNTIME_CHDIR chdir
|
|
#define ZERO_RUNTIME_OPEN_FLAGS O_RDONLY
|
|
#define ZERO_RUNTIME_IS_REGULAR(mode) S_ISREG(mode)
|
|
#define ZERO_RUNTIME_IS_DIR(mode) S_ISDIR(mode)
|
|
#define ZERO_RUNTIME_ISATTY isatty
|
|
#define ZERO_RUNTIME_STDIN_FD STDIN_FILENO
|
|
#define ZERO_RUNTIME_STDOUT_FD STDOUT_FILENO
|
|
#endif
|
|
|
|
#define ZERO_RUNTIME_PATH_BYTES 4096u
|
|
#define ZERO_RUNTIME_READ_CHUNK 1048576u
|
|
#define ZERO_RUNTIME_PROC_COMMAND_BYTES 4096u
|
|
#define ZERO_RUNTIME_PROC_ARGS_BYTES 4096u
|
|
#define ZERO_RUNTIME_PROC_ENV_BYTES 4096u
|
|
#define ZERO_RUNTIME_PROC_MAX_ARGS 64u
|
|
#define ZERO_RUNTIME_PROC_CHILD_MAX 64u
|
|
#define ZERO_RUNTIME_PROC_CHILD_SLOT_BITS 7u
|
|
#define ZERO_RUNTIME_PROC_CHILD_SLOT_MASK ((1u << ZERO_RUNTIME_PROC_CHILD_SLOT_BITS) - 1u)
|
|
#define ZERO_RUNTIME_PROC_CHILD_GENERATION_MAX ((uint32_t)INT32_MAX >> ZERO_RUNTIME_PROC_CHILD_SLOT_BITS)
|
|
#define ZERO_RUNTIME_PROC_DRAIN_CHUNK 4096u
|
|
#define ZERO_RUNTIME_PROC_BUFFER_MAX (16u * 1024u * 1024u)
|
|
|
|
static uint64_t zero_runtime_pack_span(int has, size_t offset, size_t len);
|
|
|
|
uint32_t zero_rand_entropy_u32(void) {
|
|
#if defined(_WIN32)
|
|
LARGE_INTEGER counter;
|
|
QueryPerformanceCounter(&counter);
|
|
uint64_t value = (uint64_t)counter.QuadPart ^ (uint64_t)GetTickCount64() ^ ((uint64_t)GetCurrentProcessId() << 32);
|
|
value ^= value >> 33;
|
|
value *= 0xff51afd7ed558ccdull;
|
|
value ^= value >> 33;
|
|
return (uint32_t)value;
|
|
#else
|
|
uint32_t value = 0;
|
|
int fd = open("/dev/urandom", O_RDONLY);
|
|
if (fd < 0) return 0;
|
|
ssize_t got = read(fd, &value, sizeof(value));
|
|
close(fd);
|
|
return got == (ssize_t)sizeof(value) ? value : 0;
|
|
#endif
|
|
}
|
|
|
|
static ZeroMaybeUsize zero_runtime_none_usize(void) {
|
|
return (ZeroMaybeUsize){0, 0};
|
|
}
|
|
|
|
static int zero_runtime_path_copy(ZeroByteView path, char out[ZERO_RUNTIME_PATH_BYTES]) {
|
|
if ((!path.ptr && path.len > 0) || path.len == 0 || path.len >= ZERO_RUNTIME_PATH_BYTES) return 0;
|
|
memcpy(out, path.ptr, path.len);
|
|
out[path.len] = '\0';
|
|
return 1;
|
|
}
|
|
|
|
static int zero_runtime_open_readonly(const char *path) {
|
|
int fd;
|
|
do {
|
|
fd = ZERO_RUNTIME_OPEN(path, ZERO_RUNTIME_OPEN_FLAGS);
|
|
} while (fd < 0 && errno == EINTR);
|
|
return fd;
|
|
}
|
|
|
|
static int zero_runtime_open_write_truncate(const char *path) {
|
|
#if defined(_WIN32)
|
|
int fd;
|
|
do {
|
|
fd = ZERO_RUNTIME_OPEN(path, _O_WRONLY | _O_CREAT | _O_TRUNC | _O_BINARY, _S_IREAD | _S_IWRITE);
|
|
} while (fd < 0 && errno == EINTR);
|
|
return fd;
|
|
#else
|
|
int fd;
|
|
do {
|
|
fd = ZERO_RUNTIME_OPEN(path, O_WRONLY | O_CREAT | O_TRUNC, 0666);
|
|
} while (fd < 0 && errno == EINTR);
|
|
return fd;
|
|
#endif
|
|
}
|
|
|
|
static int zero_runtime_open_write_append(const char *path) {
|
|
#if defined(_WIN32)
|
|
int fd;
|
|
do {
|
|
fd = ZERO_RUNTIME_OPEN(path, _O_WRONLY | _O_CREAT | _O_APPEND | _O_BINARY, _S_IREAD | _S_IWRITE);
|
|
} while (fd < 0 && errno == EINTR);
|
|
return fd;
|
|
#else
|
|
int fd;
|
|
do {
|
|
fd = ZERO_RUNTIME_OPEN(path, O_WRONLY | O_CREAT | O_APPEND, 0666);
|
|
} while (fd < 0 && errno == EINTR);
|
|
return fd;
|
|
#endif
|
|
}
|
|
|
|
static int zero_runtime_fd_regular_size(int fd, uint64_t *size_out) {
|
|
ZeroRuntimeStat st;
|
|
if (ZERO_RUNTIME_FSTAT(fd, &st) != 0) return 0;
|
|
if (!ZERO_RUNTIME_IS_REGULAR(st.st_mode)) return 0;
|
|
if (size_out) *size_out = st.st_size < 0 ? 0 : (uint64_t)st.st_size;
|
|
return 1;
|
|
}
|
|
|
|
static int zero_runtime_path_is_dir(const char *path) {
|
|
ZeroRuntimeStat st;
|
|
return path && ZERO_RUNTIME_STAT(path, &st) == 0 && ZERO_RUNTIME_IS_DIR(st.st_mode);
|
|
}
|
|
|
|
static int zero_runtime_close_fd(int fd) {
|
|
return ZERO_RUNTIME_CLOSE(fd) == 0;
|
|
}
|
|
|
|
static int zero_runtime_read_fd(int fd, ZeroMutByteView buffer, uint64_t *read_len_out) {
|
|
size_t total = 0;
|
|
while (total < buffer.len) {
|
|
size_t remaining = buffer.len - total;
|
|
unsigned chunk = remaining > ZERO_RUNTIME_READ_CHUNK ? ZERO_RUNTIME_READ_CHUNK : (unsigned)remaining;
|
|
ZeroReadResult read_len = ZERO_RUNTIME_READ(fd, buffer.ptr + total, chunk);
|
|
if (read_len < 0) {
|
|
if (errno == EINTR) continue;
|
|
return 0;
|
|
}
|
|
if (read_len == 0) break;
|
|
total += (size_t)read_len;
|
|
}
|
|
if (read_len_out) *read_len_out = (uint64_t)total;
|
|
return 1;
|
|
}
|
|
|
|
int zero_world_write(int fd, const char *buf, unsigned len) {
|
|
if (len == 0) return 0;
|
|
if (!buf) return 1;
|
|
const char *cursor = buf;
|
|
unsigned remaining = len;
|
|
while (remaining > 0) {
|
|
ZeroWriteResult written = ZERO_RUNTIME_WRITE(fd, cursor, remaining);
|
|
if (written < 0) {
|
|
if (errno == EINTR) continue;
|
|
return 1;
|
|
}
|
|
if (written == 0) return 1;
|
|
cursor += (unsigned)written;
|
|
remaining -= (unsigned)written;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
ZeroMaybeUsize zero_fs_read_bytes(ZeroByteView path, ZeroMutByteView buffer) {
|
|
if (!buffer.ptr) return zero_runtime_none_usize();
|
|
char path_buf[ZERO_RUNTIME_PATH_BYTES];
|
|
if (!zero_runtime_path_copy(path, path_buf)) return zero_runtime_none_usize();
|
|
|
|
int fd = zero_runtime_open_readonly(path_buf);
|
|
if (fd < 0) return zero_runtime_none_usize();
|
|
|
|
uint64_t total = 0;
|
|
uint64_t read_len = 0;
|
|
int ok = zero_runtime_fd_regular_size(fd, &total) && zero_runtime_read_fd(fd, buffer, &read_len);
|
|
int closed = zero_runtime_close_fd(fd);
|
|
if (!ok || !closed) return zero_runtime_none_usize();
|
|
/* snprintf convention: report the total file size so callers can detect
|
|
truncation when the value exceeds the buffer length. */
|
|
if (read_len > total) total = read_len;
|
|
return (ZeroMaybeUsize){1, total};
|
|
}
|
|
|
|
ZeroMaybeUsize zero_fs_read_bytes_at(ZeroByteView path, uint64_t offset, ZeroMutByteView buffer) {
|
|
if (!buffer.ptr) return zero_runtime_none_usize();
|
|
char path_buf[ZERO_RUNTIME_PATH_BYTES];
|
|
if (!zero_runtime_path_copy(path, path_buf)) return zero_runtime_none_usize();
|
|
|
|
int fd = zero_runtime_open_readonly(path_buf);
|
|
if (fd < 0) return zero_runtime_none_usize();
|
|
|
|
uint64_t total = 0;
|
|
uint64_t read_len = 0;
|
|
int ok = zero_runtime_fd_regular_size(fd, &total);
|
|
if (ok && offset < total && buffer.len > 0) {
|
|
ok = ZERO_RUNTIME_LSEEK(fd, (int64_t)offset, SEEK_SET) == (int64_t)offset &&
|
|
zero_runtime_read_fd(fd, buffer, &read_len);
|
|
}
|
|
int closed = zero_runtime_close_fd(fd);
|
|
if (!ok || !closed) return zero_runtime_none_usize();
|
|
/* snprintf convention: report the total file size so callers can loop
|
|
offset += len(buffer) until offset reaches the returned total. */
|
|
if (read_len > 0 && offset + read_len > total) total = offset + read_len;
|
|
return (ZeroMaybeUsize){1, total};
|
|
}
|
|
|
|
static ZeroMaybeUsize zero_runtime_write_bytes_to_path(ZeroByteView path, ZeroByteView bytes, int append) {
|
|
if (!bytes.ptr && bytes.len > 0) return zero_runtime_none_usize();
|
|
char path_buf[ZERO_RUNTIME_PATH_BYTES];
|
|
if (!zero_runtime_path_copy(path, path_buf)) return zero_runtime_none_usize();
|
|
int fd = append ? zero_runtime_open_write_append(path_buf) : zero_runtime_open_write_truncate(path_buf);
|
|
if (fd < 0) return zero_runtime_none_usize();
|
|
|
|
size_t total = 0;
|
|
int ok = 1;
|
|
while (total < bytes.len) {
|
|
size_t remaining = bytes.len - total;
|
|
unsigned chunk = remaining > ZERO_RUNTIME_READ_CHUNK ? ZERO_RUNTIME_READ_CHUNK : (unsigned)remaining;
|
|
ZeroWriteResult written = ZERO_RUNTIME_WRITE(fd, bytes.ptr + total, chunk);
|
|
if (written < 0) {
|
|
if (errno == EINTR) continue;
|
|
ok = 0;
|
|
break;
|
|
}
|
|
if (written == 0) {
|
|
ok = 0;
|
|
break;
|
|
}
|
|
total += (size_t)written;
|
|
}
|
|
int closed = zero_runtime_close_fd(fd);
|
|
if (!ok || !closed) return zero_runtime_none_usize();
|
|
return (ZeroMaybeUsize){1, (uint64_t)total};
|
|
}
|
|
|
|
ZeroMaybeUsize zero_fs_write_bytes(ZeroByteView path, ZeroByteView bytes) {
|
|
return zero_runtime_write_bytes_to_path(path, bytes, 0);
|
|
}
|
|
|
|
ZeroMaybeUsize zero_fs_append_bytes(ZeroByteView path, ZeroByteView bytes) {
|
|
return zero_runtime_write_bytes_to_path(path, bytes, 1);
|
|
}
|
|
|
|
ZeroMaybeUsize zero_fs_dir_entry_count(ZeroByteView path) {
|
|
char path_buf[ZERO_RUNTIME_PATH_BYTES];
|
|
if (!zero_runtime_path_copy(path, path_buf)) return zero_runtime_none_usize();
|
|
#if defined(_WIN32)
|
|
char search_buf[ZERO_RUNTIME_PATH_BYTES + 3u];
|
|
size_t path_len = strlen(path_buf);
|
|
if (path_len + 3u >= sizeof(search_buf)) return zero_runtime_none_usize();
|
|
memcpy(search_buf, path_buf, path_len);
|
|
if (path_len > 0 && search_buf[path_len - 1u] != '\\' && search_buf[path_len - 1u] != '/') {
|
|
search_buf[path_len++] = '\\';
|
|
}
|
|
search_buf[path_len++] = '*';
|
|
search_buf[path_len] = '\0';
|
|
|
|
WIN32_FIND_DATAA data;
|
|
HANDLE handle = FindFirstFileA(search_buf, &data);
|
|
if (handle == INVALID_HANDLE_VALUE) return zero_runtime_none_usize();
|
|
uint64_t count = 0;
|
|
do {
|
|
const char *name = data.cFileName;
|
|
if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) continue;
|
|
count++;
|
|
} while (FindNextFileA(handle, &data));
|
|
FindClose(handle);
|
|
return (ZeroMaybeUsize){1, count};
|
|
#else
|
|
DIR *dir = opendir(path_buf);
|
|
if (!dir) return zero_runtime_none_usize();
|
|
uint64_t count = 0;
|
|
struct dirent *entry = NULL;
|
|
while ((entry = readdir(dir)) != NULL) {
|
|
const char *name = entry->d_name;
|
|
if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) continue;
|
|
count++;
|
|
}
|
|
closedir(dir);
|
|
return (ZeroMaybeUsize){1, count};
|
|
#endif
|
|
}
|
|
|
|
uint64_t zero_fs_dir_entry_name(ZeroMutByteView buffer, ZeroByteView path, uint64_t index) {
|
|
if ((!buffer.ptr && buffer.len > 0) || index > UINT32_MAX) return 0;
|
|
char path_buf[ZERO_RUNTIME_PATH_BYTES];
|
|
if (!zero_runtime_path_copy(path, path_buf)) return 0;
|
|
#if defined(_WIN32)
|
|
char search_buf[ZERO_RUNTIME_PATH_BYTES + 3u];
|
|
size_t path_len = strlen(path_buf);
|
|
if (path_len + 3u >= sizeof(search_buf)) return 0;
|
|
memcpy(search_buf, path_buf, path_len);
|
|
if (path_len > 0 && search_buf[path_len - 1u] != '\\' && search_buf[path_len - 1u] != '/') {
|
|
search_buf[path_len++] = '\\';
|
|
}
|
|
search_buf[path_len++] = '*';
|
|
search_buf[path_len] = '\0';
|
|
|
|
WIN32_FIND_DATAA data;
|
|
HANDLE handle = FindFirstFileA(search_buf, &data);
|
|
if (handle == INVALID_HANDLE_VALUE) return 0;
|
|
uint64_t visible = 0;
|
|
do {
|
|
const char *name = data.cFileName;
|
|
if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) continue;
|
|
if (visible == index) {
|
|
size_t len = strlen(name);
|
|
if (len > buffer.len || len > 0x7fffffffu) {
|
|
FindClose(handle);
|
|
return 0;
|
|
}
|
|
if (len > 0) memcpy(buffer.ptr, name, len);
|
|
FindClose(handle);
|
|
return zero_runtime_pack_span(1, 0, len);
|
|
}
|
|
visible++;
|
|
} while (FindNextFileA(handle, &data));
|
|
FindClose(handle);
|
|
return 0;
|
|
#else
|
|
DIR *dir = opendir(path_buf);
|
|
if (!dir) return 0;
|
|
uint64_t visible = 0;
|
|
struct dirent *entry = NULL;
|
|
while ((entry = readdir(dir)) != NULL) {
|
|
const char *name = entry->d_name;
|
|
if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) continue;
|
|
if (visible == index) {
|
|
size_t len = strlen(name);
|
|
if (len > buffer.len || len > 0x7fffffffu) {
|
|
closedir(dir);
|
|
return 0;
|
|
}
|
|
if (len > 0) memcpy(buffer.ptr, name, len);
|
|
closedir(dir);
|
|
return zero_runtime_pack_span(1, 0, len);
|
|
}
|
|
visible++;
|
|
}
|
|
closedir(dir);
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
static int zero_runtime_stat_path(const char *path, ZeroRuntimeStat *st) {
|
|
if (!path || !path[0] || !st) return 0;
|
|
#if defined(_WIN32)
|
|
return _stat(path, st) == 0;
|
|
#else
|
|
return stat(path, st) == 0;
|
|
#endif
|
|
}
|
|
|
|
uint32_t zero_fs_path_op(ZeroByteView path, uint32_t op) {
|
|
char path_buf[ZERO_RUNTIME_PATH_BYTES];
|
|
if (!zero_runtime_path_copy(path, path_buf)) return 0;
|
|
switch ((ZeroFsPathOp)op) {
|
|
case ZERO_FS_PATH_EXISTS: {
|
|
ZeroRuntimeStat st;
|
|
return zero_runtime_stat_path(path_buf, &st) ? 1u : 0u;
|
|
}
|
|
case ZERO_FS_PATH_IS_DIR: {
|
|
ZeroRuntimeStat st;
|
|
if (!zero_runtime_stat_path(path_buf, &st)) return 0;
|
|
#if defined(_WIN32)
|
|
return (st.st_mode & _S_IFDIR) != 0 ? 1u : 0u;
|
|
#else
|
|
return S_ISDIR(st.st_mode) ? 1u : 0u;
|
|
#endif
|
|
}
|
|
case ZERO_FS_PATH_MAKE_DIR:
|
|
#if defined(_WIN32)
|
|
return _mkdir(path_buf) == 0 ? 1u : 0u;
|
|
#else
|
|
return mkdir(path_buf, 0777) == 0 ? 1u : 0u;
|
|
#endif
|
|
case ZERO_FS_PATH_REMOVE_DIR:
|
|
#if defined(_WIN32)
|
|
return _rmdir(path_buf) == 0 ? 1u : 0u;
|
|
#else
|
|
return rmdir(path_buf) == 0 ? 1u : 0u;
|
|
#endif
|
|
case ZERO_FS_PATH_REMOVE:
|
|
#if defined(_WIN32)
|
|
return _unlink(path_buf) == 0 ? 1u : 0u;
|
|
#else
|
|
return unlink(path_buf) == 0 ? 1u : 0u;
|
|
#endif
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
uint32_t zero_fs_rename(ZeroByteView from_path, ZeroByteView to_path) {
|
|
char from_buf[ZERO_RUNTIME_PATH_BYTES];
|
|
char to_buf[ZERO_RUNTIME_PATH_BYTES];
|
|
if (!zero_runtime_path_copy(from_path, from_buf) || !zero_runtime_path_copy(to_path, to_buf)) return 0;
|
|
return rename(from_buf, to_buf) == 0 ? 1u : 0u;
|
|
}
|
|
|
|
uint32_t zero_fs_atomic_write(ZeroByteView path, ZeroByteView temp_path, ZeroByteView bytes) {
|
|
char path_buf[ZERO_RUNTIME_PATH_BYTES];
|
|
char temp_buf[ZERO_RUNTIME_PATH_BYTES];
|
|
if (!zero_runtime_path_copy(path, path_buf) || !zero_runtime_path_copy(temp_path, temp_buf)) return 0;
|
|
ZeroMaybeUsize wrote = zero_runtime_write_bytes_to_path(temp_path, bytes, 0);
|
|
if (!wrote.has || wrote.value != bytes.len) {
|
|
remove(temp_buf);
|
|
return 0;
|
|
}
|
|
#if defined(_WIN32)
|
|
remove(path_buf);
|
|
#endif
|
|
if (rename(temp_buf, path_buf) != 0) {
|
|
remove(temp_buf);
|
|
return 0;
|
|
}
|
|
return 1u;
|
|
}
|
|
|
|
static int zero_runtime_ascii_space(unsigned char byte) {
|
|
return byte == ' ' || byte == '\t' || byte == '\n' || byte == '\r' || byte == '\f' || byte == '\v';
|
|
}
|
|
|
|
static int zero_runtime_proc_parse_command(ZeroByteView command, char storage[ZERO_RUNTIME_PROC_COMMAND_BYTES], char *argv[ZERO_RUNTIME_PROC_MAX_ARGS]) {
|
|
if (!command.ptr || command.len == 0 || command.len >= ZERO_RUNTIME_PROC_COMMAND_BYTES) return 0;
|
|
for (size_t i = 0; i < command.len; i++) {
|
|
if (command.ptr[i] == 0) return 0;
|
|
}
|
|
|
|
size_t read = 0;
|
|
size_t write = 0;
|
|
size_t argc = 0;
|
|
while (read < command.len) {
|
|
while (read < command.len && zero_runtime_ascii_space(command.ptr[read])) read++;
|
|
if (read >= command.len) break;
|
|
if (argc + 1 >= ZERO_RUNTIME_PROC_MAX_ARGS) return 0;
|
|
|
|
size_t token_start = write;
|
|
argv[argc++] = &storage[token_start];
|
|
while (read < command.len && !zero_runtime_ascii_space(command.ptr[read])) {
|
|
if (command.ptr[read] == '\'') {
|
|
read++;
|
|
int closed = 0;
|
|
while (read < command.len) {
|
|
if (command.ptr[read] == '\'') {
|
|
closed = 1;
|
|
read++;
|
|
break;
|
|
}
|
|
storage[write++] = (char)command.ptr[read++];
|
|
}
|
|
if (!closed) return 0;
|
|
} else if (command.ptr[read] == '\\' && read + 1 < command.len) {
|
|
read++;
|
|
storage[write++] = (char)command.ptr[read++];
|
|
} else {
|
|
storage[write++] = (char)command.ptr[read++];
|
|
}
|
|
}
|
|
if (write == token_start) {
|
|
argc--;
|
|
} else {
|
|
storage[write++] = 0;
|
|
}
|
|
}
|
|
if (argc == 0) return 0;
|
|
argv[argc] = NULL;
|
|
return 1;
|
|
}
|
|
|
|
static int zero_runtime_proc_build_argv(ZeroByteView program, ZeroByteView args, char storage[ZERO_RUNTIME_PROC_ARGS_BYTES], char *argv[ZERO_RUNTIME_PROC_MAX_ARGS]) {
|
|
if (!program.ptr || program.len == 0 || (!args.ptr && args.len > 0)) return 0;
|
|
for (size_t i = 0; i < program.len; i++) {
|
|
if (program.ptr[i] == 0) return 0;
|
|
}
|
|
for (size_t i = 0; i < args.len; i++) {
|
|
if (args.ptr[i] == 0) return 0;
|
|
}
|
|
|
|
size_t write = 0;
|
|
size_t argc = 0;
|
|
if (program.len >= ZERO_RUNTIME_PROC_ARGS_BYTES) return 0;
|
|
argv[argc++] = &storage[write];
|
|
memcpy(&storage[write], program.ptr, program.len);
|
|
write += program.len;
|
|
storage[write++] = 0;
|
|
|
|
size_t line_start = 0;
|
|
while (line_start < args.len) {
|
|
size_t line_end = line_start;
|
|
while (line_end < args.len && args.ptr[line_end] != '\n') line_end++;
|
|
if (line_end > line_start) {
|
|
if (argc + 1 >= ZERO_RUNTIME_PROC_MAX_ARGS) return 0;
|
|
size_t line_len = line_end - line_start;
|
|
if (write + line_len >= ZERO_RUNTIME_PROC_ARGS_BYTES) return 0;
|
|
argv[argc++] = &storage[write];
|
|
memcpy(&storage[write], &args.ptr[line_start], line_len);
|
|
write += line_len;
|
|
storage[write++] = 0;
|
|
}
|
|
line_start = line_end + 1;
|
|
}
|
|
|
|
argv[argc] = NULL;
|
|
return 1;
|
|
}
|
|
|
|
static int zero_runtime_proc_copy_env_block(ZeroByteView env, char storage[ZERO_RUNTIME_PROC_ENV_BYTES]) {
|
|
if (!env.ptr && env.len > 0) return 0;
|
|
if (env.len >= ZERO_RUNTIME_PROC_ENV_BYTES) return 0;
|
|
for (size_t i = 0; i < env.len; i++) {
|
|
if (env.ptr[i] == 0) return 0;
|
|
}
|
|
size_t line_start = 0;
|
|
while (line_start < env.len) {
|
|
size_t line_end = line_start;
|
|
while (line_end < env.len && env.ptr[line_end] != '\n') line_end++;
|
|
if (line_end > line_start) {
|
|
size_t equals = line_start;
|
|
while (equals < line_end && env.ptr[equals] != '=') equals++;
|
|
if (equals == line_start || equals == line_end) return 0;
|
|
}
|
|
line_start = line_end + 1;
|
|
}
|
|
if (env.len > 0) memcpy(storage, env.ptr, env.len);
|
|
storage[env.len] = '\0';
|
|
return 1;
|
|
}
|
|
|
|
static int zero_runtime_proc_set_env(const char *name, const char *value) {
|
|
#if defined(_WIN32)
|
|
return SetEnvironmentVariableA(name, value ? value : "") != 0;
|
|
#else
|
|
return setenv(name, value ? value : "", 1) == 0;
|
|
#endif
|
|
}
|
|
|
|
static int zero_runtime_proc_apply_env_block(char *env) {
|
|
if (!env || !env[0]) return 1;
|
|
char *cursor = env;
|
|
while (*cursor) {
|
|
char *line = cursor;
|
|
while (*cursor && *cursor != '\n') cursor++;
|
|
if (*cursor == '\n') {
|
|
*cursor = '\0';
|
|
cursor++;
|
|
}
|
|
if (!line[0]) continue;
|
|
char *equals = strchr(line, '=');
|
|
if (!equals || equals == line) return 0;
|
|
*equals = '\0';
|
|
if (!zero_runtime_proc_set_env(line, equals + 1)) return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
#if !defined(_WIN32)
|
|
static int zero_runtime_set_fd_nonblock(int fd) {
|
|
int flags = fcntl(fd, F_GETFL, 0);
|
|
if (flags < 0) return 0;
|
|
return fcntl(fd, F_SETFL, flags | O_NONBLOCK) == 0;
|
|
}
|
|
#endif
|
|
|
|
static int32_t zero_proc_spawn_inherit_argv_impl(char *argv[ZERO_RUNTIME_PROC_MAX_ARGS], const char *cwd, char *env) {
|
|
#if defined(_WIN32)
|
|
(void)argv;
|
|
(void)cwd;
|
|
(void)env;
|
|
return 127;
|
|
#else
|
|
pid_t pid = fork();
|
|
if (pid < 0) return 127;
|
|
|
|
if (pid == 0) {
|
|
if (cwd && ZERO_RUNTIME_CHDIR(cwd) != 0) _exit(127);
|
|
if (!zero_runtime_proc_apply_env_block(env)) _exit(127);
|
|
execvp(argv[0], argv);
|
|
_exit(127);
|
|
}
|
|
|
|
int status = 0;
|
|
while (waitpid(pid, &status, 0) < 0) {
|
|
if (errno == EINTR) continue;
|
|
return 127;
|
|
}
|
|
if (WIFEXITED(status)) return (int32_t)WEXITSTATUS(status);
|
|
if (WIFSIGNALED(status)) return (int32_t)(128 + WTERMSIG(status));
|
|
return 127;
|
|
#endif
|
|
}
|
|
|
|
int32_t zero_proc_spawn_inherit(ZeroByteView command) {
|
|
char storage[ZERO_RUNTIME_PROC_COMMAND_BYTES];
|
|
char *argv[ZERO_RUNTIME_PROC_MAX_ARGS];
|
|
if (!zero_runtime_proc_parse_command(command, storage, argv)) return 127;
|
|
return zero_proc_spawn_inherit_argv_impl(argv, NULL, NULL);
|
|
}
|
|
|
|
int32_t zero_proc_spawn_inherit_args(ZeroByteView program, ZeroByteView args, ZeroByteView cwd, ZeroByteView env) {
|
|
char argv_storage[ZERO_RUNTIME_PROC_ARGS_BYTES];
|
|
char *argv[ZERO_RUNTIME_PROC_MAX_ARGS];
|
|
char cwd_buf[ZERO_RUNTIME_PATH_BYTES];
|
|
char env_buf[ZERO_RUNTIME_PROC_ENV_BYTES];
|
|
if (!zero_runtime_proc_build_argv(program, args, argv_storage, argv) ||
|
|
!zero_runtime_path_copy(cwd, cwd_buf) ||
|
|
!zero_runtime_proc_copy_env_block(env, env_buf)) return 127;
|
|
return zero_proc_spawn_inherit_argv_impl(argv, cwd_buf, env_buf);
|
|
}
|
|
|
|
static ZeroMaybeUsize zero_proc_capture_argv_impl(char *argv[ZERO_RUNTIME_PROC_MAX_ARGS], const char *cwd, char *env, ZeroMutByteView buffer) {
|
|
if (!buffer.ptr) return zero_runtime_none_usize();
|
|
|
|
#if defined(_WIN32)
|
|
(void)argv;
|
|
(void)cwd;
|
|
(void)env;
|
|
return zero_runtime_none_usize();
|
|
#else
|
|
int pipe_fd[2];
|
|
if (pipe(pipe_fd) != 0) return zero_runtime_none_usize();
|
|
|
|
pid_t pid = fork();
|
|
if (pid < 0) {
|
|
ZERO_RUNTIME_CLOSE(pipe_fd[0]);
|
|
ZERO_RUNTIME_CLOSE(pipe_fd[1]);
|
|
return zero_runtime_none_usize();
|
|
}
|
|
|
|
if (pid == 0) {
|
|
if (cwd && ZERO_RUNTIME_CHDIR(cwd) != 0) _exit(127);
|
|
if (!zero_runtime_proc_apply_env_block(env)) _exit(127);
|
|
ZERO_RUNTIME_CLOSE(pipe_fd[0]);
|
|
if (dup2(pipe_fd[1], STDOUT_FILENO) < 0) _exit(127);
|
|
ZERO_RUNTIME_CLOSE(pipe_fd[1]);
|
|
execvp(argv[0], argv);
|
|
_exit(127);
|
|
}
|
|
|
|
ZERO_RUNTIME_CLOSE(pipe_fd[1]);
|
|
if (!zero_runtime_set_fd_nonblock(pipe_fd[0])) {
|
|
ZERO_RUNTIME_CLOSE(pipe_fd[0]);
|
|
int ignored_status = 0;
|
|
while (waitpid(pid, &ignored_status, 0) < 0 && errno == EINTR) {}
|
|
return zero_runtime_none_usize();
|
|
}
|
|
uint64_t total = 0;
|
|
int too_large = 0;
|
|
int read_ok = 1;
|
|
int wait_ok = 1;
|
|
int status = 0;
|
|
int reaped = 0;
|
|
unsigned char chunk[4096];
|
|
while (!reaped) {
|
|
if (pipe_fd[0] >= 0) {
|
|
for (;;) {
|
|
ZeroReadResult n = ZERO_RUNTIME_READ(pipe_fd[0], chunk, sizeof(chunk));
|
|
if (n < 0) {
|
|
if (errno == EINTR) continue;
|
|
if (errno == EAGAIN || errno == EWOULDBLOCK) break;
|
|
read_ok = 0;
|
|
break;
|
|
}
|
|
if (n == 0) {
|
|
ZERO_RUNTIME_CLOSE(pipe_fd[0]);
|
|
pipe_fd[0] = -1;
|
|
break;
|
|
}
|
|
for (ZeroReadResult i = 0; i < n; i++) {
|
|
if (total < buffer.len) {
|
|
buffer.ptr[total] = chunk[i];
|
|
} else {
|
|
too_large = 1;
|
|
}
|
|
total++;
|
|
}
|
|
}
|
|
}
|
|
if (!read_ok) break;
|
|
for (;;) {
|
|
pid_t got = waitpid(pid, &status, WNOHANG);
|
|
if (got == 0) break;
|
|
if (got < 0) {
|
|
if (errno == EINTR) continue;
|
|
wait_ok = 0;
|
|
} else {
|
|
reaped = 1;
|
|
}
|
|
break;
|
|
}
|
|
if (!wait_ok || reaped) break;
|
|
if (pipe_fd[0] >= 0) {
|
|
struct pollfd fd = {.fd = pipe_fd[0], .events = POLLIN | POLLHUP | POLLERR};
|
|
int rc = 0;
|
|
do {
|
|
rc = poll(&fd, 1, 50);
|
|
} while (rc < 0 && errno == EINTR);
|
|
if (rc < 0) {
|
|
read_ok = 0;
|
|
break;
|
|
}
|
|
} else {
|
|
(void)poll(NULL, 0, 50);
|
|
}
|
|
}
|
|
|
|
if (pipe_fd[0] >= 0) {
|
|
if (reaped) {
|
|
struct pollfd fd = {.fd = pipe_fd[0], .events = POLLIN | POLLHUP | POLLERR};
|
|
int rc = 0;
|
|
do {
|
|
rc = poll(&fd, 1, 50);
|
|
} while (rc < 0 && errno == EINTR);
|
|
if (rc < 0) read_ok = 0;
|
|
}
|
|
while (read_ok) {
|
|
ZeroReadResult n = ZERO_RUNTIME_READ(pipe_fd[0], chunk, sizeof(chunk));
|
|
if (n < 0) {
|
|
if (errno == EINTR) continue;
|
|
if (errno == EAGAIN || errno == EWOULDBLOCK) break;
|
|
read_ok = 0;
|
|
break;
|
|
}
|
|
if (n == 0) break;
|
|
for (ZeroReadResult i = 0; i < n; i++) {
|
|
if (total < buffer.len) {
|
|
buffer.ptr[total] = chunk[i];
|
|
} else {
|
|
too_large = 1;
|
|
}
|
|
total++;
|
|
}
|
|
}
|
|
ZERO_RUNTIME_CLOSE(pipe_fd[0]);
|
|
}
|
|
|
|
if (!reaped) {
|
|
while (waitpid(pid, &status, 0) < 0) {
|
|
if (errno == EINTR) continue;
|
|
wait_ok = 0;
|
|
break;
|
|
}
|
|
}
|
|
if (!read_ok || !wait_ok || too_large) return zero_runtime_none_usize();
|
|
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) return zero_runtime_none_usize();
|
|
return (ZeroMaybeUsize){1, total};
|
|
#endif
|
|
}
|
|
|
|
ZeroMaybeUsize zero_proc_capture(ZeroByteView command, ZeroMutByteView buffer) {
|
|
char storage[ZERO_RUNTIME_PROC_COMMAND_BYTES];
|
|
char *argv[ZERO_RUNTIME_PROC_MAX_ARGS];
|
|
if (!zero_runtime_proc_parse_command(command, storage, argv)) return zero_runtime_none_usize();
|
|
return zero_proc_capture_argv_impl(argv, NULL, NULL, buffer);
|
|
}
|
|
|
|
ZeroMaybeUsize zero_proc_capture_args(ZeroByteView program, ZeroByteView args, ZeroMutByteView buffer) {
|
|
char argv_storage[ZERO_RUNTIME_PROC_ARGS_BYTES];
|
|
char *argv[ZERO_RUNTIME_PROC_MAX_ARGS];
|
|
if (!zero_runtime_proc_build_argv(program, args, argv_storage, argv)) return zero_runtime_none_usize();
|
|
return zero_proc_capture_argv_impl(argv, NULL, NULL, buffer);
|
|
}
|
|
|
|
static int32_t zero_proc_capture_files_argv_impl(char *argv[ZERO_RUNTIME_PROC_MAX_ARGS], const char *cwd, char *env, const char *stdout_buf, const char *stderr_buf) {
|
|
#if defined(_WIN32)
|
|
(void)argv;
|
|
(void)cwd;
|
|
(void)env;
|
|
(void)stdout_buf;
|
|
(void)stderr_buf;
|
|
return 127;
|
|
#else
|
|
int stdout_fd = zero_runtime_open_write_truncate(stdout_buf);
|
|
if (stdout_fd < 0) return 127;
|
|
int stderr_fd = zero_runtime_open_write_truncate(stderr_buf);
|
|
if (stderr_fd < 0) {
|
|
ZERO_RUNTIME_CLOSE(stdout_fd);
|
|
return 127;
|
|
}
|
|
|
|
pid_t pid = fork();
|
|
if (pid < 0) {
|
|
ZERO_RUNTIME_CLOSE(stdout_fd);
|
|
ZERO_RUNTIME_CLOSE(stderr_fd);
|
|
return 127;
|
|
}
|
|
|
|
if (pid == 0) {
|
|
if (cwd && ZERO_RUNTIME_CHDIR(cwd) != 0) _exit(127);
|
|
if (!zero_runtime_proc_apply_env_block(env)) _exit(127);
|
|
if (dup2(stdout_fd, STDOUT_FILENO) < 0) _exit(127);
|
|
if (dup2(stderr_fd, STDERR_FILENO) < 0) _exit(127);
|
|
ZERO_RUNTIME_CLOSE(stdout_fd);
|
|
ZERO_RUNTIME_CLOSE(stderr_fd);
|
|
execvp(argv[0], argv);
|
|
_exit(127);
|
|
}
|
|
|
|
ZERO_RUNTIME_CLOSE(stdout_fd);
|
|
ZERO_RUNTIME_CLOSE(stderr_fd);
|
|
|
|
int status = 0;
|
|
while (waitpid(pid, &status, 0) < 0) {
|
|
if (errno == EINTR) continue;
|
|
return 127;
|
|
}
|
|
if (WIFEXITED(status)) return (int32_t)WEXITSTATUS(status);
|
|
if (WIFSIGNALED(status)) return (int32_t)(128 + WTERMSIG(status));
|
|
return 127;
|
|
#endif
|
|
}
|
|
|
|
int32_t zero_proc_capture_files(ZeroByteView command, ZeroByteView stdout_path, ZeroByteView stderr_path) {
|
|
char storage[ZERO_RUNTIME_PROC_COMMAND_BYTES];
|
|
char *argv[ZERO_RUNTIME_PROC_MAX_ARGS];
|
|
char stdout_buf[ZERO_RUNTIME_PATH_BYTES];
|
|
char stderr_buf[ZERO_RUNTIME_PATH_BYTES];
|
|
if (!zero_runtime_proc_parse_command(command, storage, argv) ||
|
|
!zero_runtime_path_copy(stdout_path, stdout_buf) ||
|
|
!zero_runtime_path_copy(stderr_path, stderr_buf)) {
|
|
return 127;
|
|
}
|
|
|
|
return zero_proc_capture_files_argv_impl(argv, NULL, NULL, stdout_buf, stderr_buf);
|
|
}
|
|
|
|
int32_t zero_proc_capture_files_args(ZeroByteView program, ZeroByteView args, ZeroByteView stdout_path, ZeroByteView stderr_path) {
|
|
char argv_storage[ZERO_RUNTIME_PROC_ARGS_BYTES];
|
|
char *argv[ZERO_RUNTIME_PROC_MAX_ARGS];
|
|
char stdout_buf[ZERO_RUNTIME_PATH_BYTES];
|
|
char stderr_buf[ZERO_RUNTIME_PATH_BYTES];
|
|
if (!zero_runtime_proc_build_argv(program, args, argv_storage, argv) ||
|
|
!zero_runtime_path_copy(stdout_path, stdout_buf) ||
|
|
!zero_runtime_path_copy(stderr_path, stderr_buf)) return 127;
|
|
return zero_proc_capture_files_argv_impl(argv, NULL, NULL, stdout_buf, stderr_buf);
|
|
}
|
|
|
|
#if !defined(_WIN32)
|
|
typedef struct {
|
|
unsigned char *data;
|
|
size_t offset;
|
|
size_t len;
|
|
size_t cap;
|
|
int discard;
|
|
} ZeroRuntimeProcPipeBuffer;
|
|
|
|
typedef struct {
|
|
int active;
|
|
uint32_t generation;
|
|
int reaped;
|
|
int status;
|
|
pid_t pid;
|
|
int stdin_fd;
|
|
int stdout_fd;
|
|
int stderr_fd;
|
|
int pty_fd;
|
|
int pty;
|
|
ZeroRuntimeProcPipeBuffer stdout_buffer;
|
|
ZeroRuntimeProcPipeBuffer stderr_buffer;
|
|
ZeroRuntimeProcPipeBuffer pty_buffer;
|
|
} ZeroRuntimeProcChild;
|
|
|
|
static ZeroRuntimeProcChild zero_proc_children[ZERO_RUNTIME_PROC_CHILD_MAX];
|
|
static int zero_proc_sigpipe_ignored;
|
|
|
|
static int zero_proc_signal_child(ZeroRuntimeProcChild *child, int signal_number);
|
|
|
|
static uint32_t zero_proc_child_next_generation(uint32_t generation) {
|
|
generation++;
|
|
if (generation == 0 || generation > ZERO_RUNTIME_PROC_CHILD_GENERATION_MAX) return 1;
|
|
return generation;
|
|
}
|
|
|
|
static int32_t zero_proc_child_handle(int index, uint32_t generation) {
|
|
if (index < 0 || index >= (int)ZERO_RUNTIME_PROC_CHILD_MAX || generation == 0) return 0;
|
|
return (int32_t)((generation << ZERO_RUNTIME_PROC_CHILD_SLOT_BITS) | ((uint32_t)index + 1u));
|
|
}
|
|
|
|
static int zero_proc_child_index(int32_t child) {
|
|
if (child <= 0) return -1;
|
|
uint32_t handle = (uint32_t)child;
|
|
uint32_t slot = handle & ZERO_RUNTIME_PROC_CHILD_SLOT_MASK;
|
|
uint32_t generation = handle >> ZERO_RUNTIME_PROC_CHILD_SLOT_BITS;
|
|
if (slot == 0 || slot > ZERO_RUNTIME_PROC_CHILD_MAX || generation == 0) return -1;
|
|
int index = (int)(slot - 1u);
|
|
ZeroRuntimeProcChild *candidate = &zero_proc_children[index];
|
|
return candidate->active && candidate->generation == generation ? index : -1;
|
|
}
|
|
|
|
static int zero_proc_child_alloc(void) {
|
|
for (int i = 0; i < (int)ZERO_RUNTIME_PROC_CHILD_MAX; i++) {
|
|
if (!zero_proc_children[i].active) return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static void zero_proc_child_close_fd(int *fd) {
|
|
if (*fd >= 0) {
|
|
ZERO_RUNTIME_CLOSE(*fd);
|
|
*fd = -1;
|
|
}
|
|
}
|
|
|
|
static void zero_proc_child_close_pair(int fds[2]) {
|
|
if (!fds) return;
|
|
zero_proc_child_close_fd(&fds[0]);
|
|
zero_proc_child_close_fd(&fds[1]);
|
|
}
|
|
|
|
static void zero_proc_child_close_pipes(ZeroRuntimeProcChild *child) {
|
|
if (!child) return;
|
|
zero_proc_child_close_fd(&child->stdin_fd);
|
|
zero_proc_child_close_fd(&child->stdout_fd);
|
|
zero_proc_child_close_fd(&child->stderr_fd);
|
|
zero_proc_child_close_fd(&child->pty_fd);
|
|
}
|
|
|
|
static void zero_proc_pipe_buffer_free(ZeroRuntimeProcPipeBuffer *buffer) {
|
|
if (!buffer) return;
|
|
free(buffer->data);
|
|
*buffer = (ZeroRuntimeProcPipeBuffer){0};
|
|
}
|
|
|
|
static void zero_proc_child_free_buffers(ZeroRuntimeProcChild *child) {
|
|
if (!child) return;
|
|
zero_proc_pipe_buffer_free(&child->stdout_buffer);
|
|
zero_proc_pipe_buffer_free(&child->stderr_buffer);
|
|
zero_proc_pipe_buffer_free(&child->pty_buffer);
|
|
}
|
|
|
|
static void zero_proc_pipe_buffer_compact(ZeroRuntimeProcPipeBuffer *buffer) {
|
|
if (!buffer || buffer->offset == 0) return;
|
|
if (buffer->offset >= buffer->len) {
|
|
buffer->offset = 0;
|
|
buffer->len = 0;
|
|
return;
|
|
}
|
|
memmove(buffer->data, buffer->data + buffer->offset, buffer->len - buffer->offset);
|
|
buffer->len -= buffer->offset;
|
|
buffer->offset = 0;
|
|
}
|
|
|
|
static int zero_proc_pipe_buffer_append(ZeroRuntimeProcPipeBuffer *buffer, const unsigned char *data, size_t len) {
|
|
if (!buffer || (!data && len > 0)) return 0;
|
|
if (len == 0 || buffer->discard) return 1;
|
|
zero_proc_pipe_buffer_compact(buffer);
|
|
if (len > ZERO_RUNTIME_PROC_BUFFER_MAX - buffer->len) {
|
|
size_t available = ZERO_RUNTIME_PROC_BUFFER_MAX - buffer->len;
|
|
if (available > 0 && !zero_proc_pipe_buffer_append(buffer, data, available)) return 0;
|
|
buffer->discard = 1;
|
|
return 1;
|
|
}
|
|
size_t need = buffer->len + len;
|
|
if (need > buffer->cap) {
|
|
size_t cap = buffer->cap ? buffer->cap : 4096u;
|
|
while (cap < need) {
|
|
if (cap > ZERO_RUNTIME_PROC_BUFFER_MAX / 2u) {
|
|
cap = ZERO_RUNTIME_PROC_BUFFER_MAX;
|
|
break;
|
|
}
|
|
cap *= 2u;
|
|
}
|
|
unsigned char *next = (unsigned char *)realloc(buffer->data, cap);
|
|
if (!next) {
|
|
buffer->discard = 1;
|
|
return 1;
|
|
}
|
|
buffer->data = next;
|
|
buffer->cap = cap;
|
|
}
|
|
memcpy(buffer->data + buffer->len, data, len);
|
|
buffer->len += len;
|
|
return 1;
|
|
}
|
|
|
|
static ZeroMaybeUsize zero_proc_pipe_buffer_read(ZeroRuntimeProcPipeBuffer *buffer, ZeroMutByteView out) {
|
|
if (!buffer || buffer->offset >= buffer->len || out.len == 0) return zero_runtime_none_usize();
|
|
size_t available = buffer->len - buffer->offset;
|
|
size_t take = available < out.len ? available : out.len;
|
|
memcpy(out.ptr, buffer->data + buffer->offset, take);
|
|
buffer->offset += take;
|
|
if (buffer->offset == buffer->len) {
|
|
buffer->offset = 0;
|
|
buffer->len = 0;
|
|
}
|
|
return (ZeroMaybeUsize){1, (uint64_t)take};
|
|
}
|
|
|
|
static void zero_proc_child_drain_fd(int *fd, ZeroRuntimeProcPipeBuffer *buffer) {
|
|
if (!fd || *fd < 0 || !buffer) return;
|
|
unsigned char chunk[ZERO_RUNTIME_PROC_DRAIN_CHUNK];
|
|
for (;;) {
|
|
ZeroReadResult got = ZERO_RUNTIME_READ(*fd, chunk, sizeof(chunk));
|
|
if (got > 0) {
|
|
(void)zero_proc_pipe_buffer_append(buffer, chunk, (size_t)got);
|
|
continue;
|
|
}
|
|
if (got == 0) {
|
|
zero_proc_child_close_fd(fd);
|
|
return;
|
|
}
|
|
if (errno == EINTR) continue;
|
|
if (errno == EAGAIN || errno == EWOULDBLOCK) return;
|
|
zero_proc_child_close_fd(fd);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void zero_proc_child_drain_output(ZeroRuntimeProcChild *child) {
|
|
if (!child) return;
|
|
if (child->pty) {
|
|
zero_proc_child_drain_fd(&child->pty_fd, &child->pty_buffer);
|
|
} else {
|
|
zero_proc_child_drain_fd(&child->stdout_fd, &child->stdout_buffer);
|
|
zero_proc_child_drain_fd(&child->stderr_fd, &child->stderr_buffer);
|
|
}
|
|
}
|
|
|
|
static int zero_proc_child_output_open(const ZeroRuntimeProcChild *child) {
|
|
if (!child) return 0;
|
|
if (child->pty) return child->pty_fd >= 0;
|
|
return child->stdout_fd >= 0 || child->stderr_fd >= 0;
|
|
}
|
|
|
|
static int zero_proc_child_set_nonblock(int fd) {
|
|
return zero_runtime_set_fd_nonblock(fd);
|
|
}
|
|
|
|
static int zero_proc_child_set_cloexec(int fd) {
|
|
int flags = fcntl(fd, F_GETFD, 0);
|
|
if (flags < 0) return 0;
|
|
return fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == 0;
|
|
}
|
|
|
|
static int32_t zero_proc_status_from_wait(int status) {
|
|
if (WIFEXITED(status)) return (int32_t)WEXITSTATUS(status);
|
|
if (WIFSIGNALED(status)) return (int32_t)(128 + WTERMSIG(status));
|
|
return 127;
|
|
}
|
|
|
|
static int zero_proc_child_reap(ZeroRuntimeProcChild *child, int nohang) {
|
|
if (!child || !child->active) return 0;
|
|
if (child->reaped) return 1;
|
|
int raw = 0;
|
|
for (;;) {
|
|
pid_t got = waitpid(child->pid, &raw, nohang ? WNOHANG : 0);
|
|
if (got == 0) return 0;
|
|
if (got < 0) {
|
|
if (errno == EINTR) continue;
|
|
child->status = 127;
|
|
child->reaped = 1;
|
|
return 1;
|
|
}
|
|
child->status = zero_proc_status_from_wait(raw);
|
|
child->reaped = 1;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
static void zero_proc_child_poll_output(ZeroRuntimeProcChild *child, int timeout_ms) {
|
|
if (!child) return;
|
|
struct pollfd fds[2];
|
|
nfds_t count = 0;
|
|
if (child->pty) {
|
|
if (child->pty_fd >= 0) {
|
|
fds[count++] = (struct pollfd){.fd = child->pty_fd, .events = POLLIN | POLLHUP | POLLERR};
|
|
}
|
|
} else {
|
|
if (child->stdout_fd >= 0) {
|
|
fds[count++] = (struct pollfd){.fd = child->stdout_fd, .events = POLLIN | POLLHUP | POLLERR};
|
|
}
|
|
if (child->stderr_fd >= 0) {
|
|
fds[count++] = (struct pollfd){.fd = child->stderr_fd, .events = POLLIN | POLLHUP | POLLERR};
|
|
}
|
|
}
|
|
if (count == 0) {
|
|
if (timeout_ms < 0) {
|
|
(void)zero_proc_child_reap(child, 0);
|
|
} else if (timeout_ms > 0) {
|
|
(void)poll(NULL, 0, timeout_ms);
|
|
}
|
|
return;
|
|
}
|
|
int rc = 0;
|
|
do {
|
|
rc = poll(fds, count, timeout_ms);
|
|
} while (rc < 0 && errno == EINTR);
|
|
if (rc > 0) zero_proc_child_drain_output(child);
|
|
}
|
|
|
|
static int zero_proc_child_wait_with_drain(ZeroRuntimeProcChild *child, int timeout_ms) {
|
|
if (!child || !child->active) return 0;
|
|
int elapsed_ms = 0;
|
|
for (;;) {
|
|
zero_proc_child_drain_output(child);
|
|
if (zero_proc_child_reap(child, 1)) {
|
|
zero_proc_child_drain_output(child);
|
|
return 1;
|
|
}
|
|
if (timeout_ms == 0) return 0;
|
|
int slice_ms = 50;
|
|
if (timeout_ms > 0) {
|
|
if (elapsed_ms >= timeout_ms) return 0;
|
|
slice_ms = timeout_ms - elapsed_ms;
|
|
if (slice_ms > 50) slice_ms = 50;
|
|
}
|
|
zero_proc_child_poll_output(child, slice_ms);
|
|
if (slice_ms > 0) elapsed_ms += slice_ms;
|
|
}
|
|
}
|
|
|
|
static int zero_proc_child_wait_output_closed(ZeroRuntimeProcChild *child, int timeout_ms) {
|
|
if (!child || !child->active) return 0;
|
|
int elapsed_ms = 0;
|
|
for (;;) {
|
|
zero_proc_child_drain_output(child);
|
|
if (!zero_proc_child_output_open(child)) return 1;
|
|
if (timeout_ms == 0) return 0;
|
|
int slice_ms = 50;
|
|
if (timeout_ms > 0) {
|
|
if (elapsed_ms >= timeout_ms) return 0;
|
|
slice_ms = timeout_ms - elapsed_ms;
|
|
if (slice_ms > 50) slice_ms = 50;
|
|
}
|
|
zero_proc_child_poll_output(child, slice_ms);
|
|
if (slice_ms > 0) elapsed_ms += slice_ms;
|
|
}
|
|
}
|
|
|
|
static int zero_proc_child_close_handle(ZeroRuntimeProcChild *child) {
|
|
if (!child || !child->active) return 0;
|
|
if (!child->reaped && !zero_proc_child_wait_with_drain(child, 0)) {
|
|
(void)zero_proc_signal_child(child, SIGTERM);
|
|
(void)zero_proc_child_wait_with_drain(child, 250);
|
|
if (!child->reaped) {
|
|
(void)zero_proc_signal_child(child, SIGKILL);
|
|
(void)zero_proc_child_wait_with_drain(child, -1);
|
|
}
|
|
}
|
|
zero_proc_child_drain_output(child);
|
|
if (zero_proc_child_output_open(child)) {
|
|
(void)zero_proc_signal_child(child, SIGTERM);
|
|
(void)zero_proc_child_wait_output_closed(child, 250);
|
|
if (zero_proc_child_output_open(child)) {
|
|
(void)zero_proc_signal_child(child, SIGKILL);
|
|
(void)zero_proc_child_wait_output_closed(child, 250);
|
|
}
|
|
}
|
|
zero_proc_child_close_pipes(child);
|
|
zero_proc_child_free_buffers(child);
|
|
child->active = 0;
|
|
return 1;
|
|
}
|
|
|
|
static int zero_proc_pid_running(int32_t pid) {
|
|
if (pid <= 0) return 0;
|
|
if (kill((pid_t)pid, 0) == 0) return 1;
|
|
return errno == EPERM ? 1 : 0;
|
|
}
|
|
|
|
static int zero_proc_signal_pid(int32_t pid, int signal_number) {
|
|
if (pid <= 0) return 0;
|
|
return kill((pid_t)pid, signal_number) == 0 ? 1 : 0;
|
|
}
|
|
|
|
static int zero_proc_signal_group_pid(int32_t pid, int signal_number) {
|
|
if (pid <= 1) return 0;
|
|
return kill(-(pid_t)pid, signal_number) == 0 ? 1 : 0;
|
|
}
|
|
|
|
static void zero_proc_kill_and_reap_pid(pid_t pid) {
|
|
if (pid <= 0) return;
|
|
(void)zero_proc_signal_group_pid((int32_t)pid, SIGTERM);
|
|
(void)zero_proc_signal_pid((int32_t)pid, SIGTERM);
|
|
|
|
int status = 0;
|
|
for (;;) {
|
|
pid_t got = waitpid(pid, &status, WNOHANG);
|
|
if (got == pid) return;
|
|
if (got < 0) {
|
|
if (errno == EINTR) continue;
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
|
|
(void)zero_proc_signal_group_pid((int32_t)pid, SIGKILL);
|
|
(void)zero_proc_signal_pid((int32_t)pid, SIGKILL);
|
|
for (;;) {
|
|
pid_t got = waitpid(pid, &status, 0);
|
|
if (got == pid) return;
|
|
if (got < 0 && errno == EINTR) continue;
|
|
return;
|
|
}
|
|
}
|
|
|
|
static int zero_proc_signal_child(ZeroRuntimeProcChild *child, int signal_number) {
|
|
if (!child || child->pid <= 0) return 0;
|
|
if (zero_proc_signal_group_pid((int32_t)child->pid, signal_number)) return 1;
|
|
return zero_proc_signal_pid((int32_t)child->pid, signal_number);
|
|
}
|
|
#endif
|
|
|
|
static int32_t zero_proc_spawn_child_argv_impl(char *argv[ZERO_RUNTIME_PROC_MAX_ARGS], const char *cwd, char *env) {
|
|
#if defined(_WIN32)
|
|
(void)argv;
|
|
(void)cwd;
|
|
(void)env;
|
|
return 0;
|
|
#else
|
|
if (cwd && !zero_runtime_path_is_dir(cwd)) return 0;
|
|
int index = zero_proc_child_alloc();
|
|
if (index < 0) return 0;
|
|
|
|
int stdin_pipe[2] = {-1, -1};
|
|
int stdout_pipe[2] = {-1, -1};
|
|
int stderr_pipe[2] = {-1, -1};
|
|
if (pipe(stdin_pipe) != 0 || pipe(stdout_pipe) != 0 || pipe(stderr_pipe) != 0) {
|
|
zero_proc_child_close_pair(stdin_pipe);
|
|
zero_proc_child_close_pair(stdout_pipe);
|
|
zero_proc_child_close_pair(stderr_pipe);
|
|
return 0;
|
|
}
|
|
if (!zero_proc_child_set_cloexec(stdin_pipe[0]) ||
|
|
!zero_proc_child_set_cloexec(stdin_pipe[1]) ||
|
|
!zero_proc_child_set_cloexec(stdout_pipe[0]) ||
|
|
!zero_proc_child_set_cloexec(stdout_pipe[1]) ||
|
|
!zero_proc_child_set_cloexec(stderr_pipe[0]) ||
|
|
!zero_proc_child_set_cloexec(stderr_pipe[1])) {
|
|
zero_proc_child_close_pair(stdin_pipe);
|
|
zero_proc_child_close_pair(stdout_pipe);
|
|
zero_proc_child_close_pair(stderr_pipe);
|
|
return 0;
|
|
}
|
|
|
|
pid_t pid = fork();
|
|
if (pid < 0) {
|
|
zero_proc_child_close_pair(stdin_pipe);
|
|
zero_proc_child_close_pair(stdout_pipe);
|
|
zero_proc_child_close_pair(stderr_pipe);
|
|
return 0;
|
|
}
|
|
|
|
if (pid == 0) {
|
|
(void)setpgid(0, 0);
|
|
if (cwd && ZERO_RUNTIME_CHDIR(cwd) != 0) _exit(127);
|
|
if (!zero_runtime_proc_apply_env_block(env)) _exit(127);
|
|
ZERO_RUNTIME_CLOSE(stdin_pipe[1]);
|
|
ZERO_RUNTIME_CLOSE(stdout_pipe[0]);
|
|
ZERO_RUNTIME_CLOSE(stderr_pipe[0]);
|
|
if (dup2(stdin_pipe[0], STDIN_FILENO) < 0) _exit(127);
|
|
if (dup2(stdout_pipe[1], STDOUT_FILENO) < 0) _exit(127);
|
|
if (dup2(stderr_pipe[1], STDERR_FILENO) < 0) _exit(127);
|
|
ZERO_RUNTIME_CLOSE(stdin_pipe[0]);
|
|
ZERO_RUNTIME_CLOSE(stdout_pipe[1]);
|
|
ZERO_RUNTIME_CLOSE(stderr_pipe[1]);
|
|
execvp(argv[0], argv);
|
|
_exit(127);
|
|
}
|
|
|
|
(void)setpgid(pid, pid);
|
|
ZERO_RUNTIME_CLOSE(stdin_pipe[0]);
|
|
ZERO_RUNTIME_CLOSE(stdout_pipe[1]);
|
|
ZERO_RUNTIME_CLOSE(stderr_pipe[1]);
|
|
if (!zero_proc_child_set_nonblock(stdin_pipe[1]) ||
|
|
!zero_proc_child_set_nonblock(stdout_pipe[0]) ||
|
|
!zero_proc_child_set_nonblock(stderr_pipe[0])) {
|
|
ZERO_RUNTIME_CLOSE(stdin_pipe[1]);
|
|
ZERO_RUNTIME_CLOSE(stdout_pipe[0]);
|
|
ZERO_RUNTIME_CLOSE(stderr_pipe[0]);
|
|
zero_proc_kill_and_reap_pid(pid);
|
|
return 0;
|
|
}
|
|
if (!zero_proc_sigpipe_ignored) {
|
|
signal(SIGPIPE, SIG_IGN);
|
|
zero_proc_sigpipe_ignored = 1;
|
|
}
|
|
|
|
uint32_t generation = zero_proc_child_next_generation(zero_proc_children[index].generation);
|
|
zero_proc_children[index] = (ZeroRuntimeProcChild){
|
|
.active = 1,
|
|
.generation = generation,
|
|
.reaped = 0,
|
|
.status = 127,
|
|
.pid = pid,
|
|
.stdin_fd = stdin_pipe[1],
|
|
.stdout_fd = stdout_pipe[0],
|
|
.stderr_fd = stderr_pipe[0],
|
|
.pty_fd = -1,
|
|
.pty = 0,
|
|
};
|
|
return zero_proc_child_handle(index, generation);
|
|
#endif
|
|
}
|
|
|
|
static int32_t zero_proc_spawn_child_impl(ZeroByteView command, const char *cwd, char *env) {
|
|
char storage[ZERO_RUNTIME_PROC_COMMAND_BYTES];
|
|
char *argv[ZERO_RUNTIME_PROC_MAX_ARGS];
|
|
if (!zero_runtime_proc_parse_command(command, storage, argv)) return 0;
|
|
return zero_proc_spawn_child_argv_impl(argv, cwd, env);
|
|
}
|
|
|
|
int32_t zero_proc_spawn_child(ZeroByteView command) {
|
|
return zero_proc_spawn_child_impl(command, NULL, NULL);
|
|
}
|
|
|
|
int32_t zero_proc_spawn_child_in(ZeroByteView command, ZeroByteView cwd) {
|
|
char cwd_buf[ZERO_RUNTIME_PATH_BYTES];
|
|
if (!zero_runtime_path_copy(cwd, cwd_buf)) return 0;
|
|
return zero_proc_spawn_child_impl(command, cwd_buf, NULL);
|
|
}
|
|
|
|
int32_t zero_proc_spawn_child_in_env(ZeroByteView command, ZeroByteView cwd, ZeroByteView env) {
|
|
char cwd_buf[ZERO_RUNTIME_PATH_BYTES];
|
|
char env_buf[ZERO_RUNTIME_PROC_ENV_BYTES];
|
|
if (!zero_runtime_path_copy(cwd, cwd_buf) || !zero_runtime_proc_copy_env_block(env, env_buf)) return 0;
|
|
return zero_proc_spawn_child_impl(command, cwd_buf, env_buf);
|
|
}
|
|
|
|
int32_t zero_proc_spawn_child_args(ZeroByteView program, ZeroByteView args, ZeroByteView cwd, ZeroByteView env) {
|
|
char argv_storage[ZERO_RUNTIME_PROC_ARGS_BYTES];
|
|
char *argv[ZERO_RUNTIME_PROC_MAX_ARGS];
|
|
char cwd_buf[ZERO_RUNTIME_PATH_BYTES];
|
|
char env_buf[ZERO_RUNTIME_PROC_ENV_BYTES];
|
|
if (!zero_runtime_proc_build_argv(program, args, argv_storage, argv) ||
|
|
!zero_runtime_path_copy(cwd, cwd_buf) ||
|
|
!zero_runtime_proc_copy_env_block(env, env_buf)) return 0;
|
|
return zero_proc_spawn_child_argv_impl(argv, cwd_buf, env_buf);
|
|
}
|
|
|
|
static int32_t zero_pty_spawn_argv_impl(char *argv[ZERO_RUNTIME_PROC_MAX_ARGS], const char *cwd, char *env) {
|
|
#if defined(_WIN32)
|
|
(void)argv;
|
|
(void)cwd;
|
|
(void)env;
|
|
return 0;
|
|
#else
|
|
if (cwd && !zero_runtime_path_is_dir(cwd)) return 0;
|
|
int index = zero_proc_child_alloc();
|
|
if (index < 0) return 0;
|
|
|
|
int master_fd = -1;
|
|
pid_t pid = forkpty(&master_fd, NULL, NULL, NULL);
|
|
if (pid < 0) {
|
|
return 0;
|
|
}
|
|
|
|
if (pid == 0) {
|
|
(void)setpgid(0, 0);
|
|
if (cwd && ZERO_RUNTIME_CHDIR(cwd) != 0) _exit(127);
|
|
if (!zero_runtime_proc_apply_env_block(env)) _exit(127);
|
|
execvp(argv[0], argv);
|
|
_exit(127);
|
|
}
|
|
|
|
(void)setpgid(pid, pid);
|
|
if (!zero_proc_child_set_cloexec(master_fd) || !zero_proc_child_set_nonblock(master_fd)) {
|
|
ZERO_RUNTIME_CLOSE(master_fd);
|
|
zero_proc_kill_and_reap_pid(pid);
|
|
return 0;
|
|
}
|
|
if (!zero_proc_sigpipe_ignored) {
|
|
signal(SIGPIPE, SIG_IGN);
|
|
zero_proc_sigpipe_ignored = 1;
|
|
}
|
|
|
|
uint32_t generation = zero_proc_child_next_generation(zero_proc_children[index].generation);
|
|
zero_proc_children[index] = (ZeroRuntimeProcChild){
|
|
.active = 1,
|
|
.generation = generation,
|
|
.reaped = 0,
|
|
.status = 127,
|
|
.pid = pid,
|
|
.stdin_fd = -1,
|
|
.stdout_fd = -1,
|
|
.stderr_fd = -1,
|
|
.pty_fd = master_fd,
|
|
.pty = 1,
|
|
};
|
|
return zero_proc_child_handle(index, generation);
|
|
#endif
|
|
}
|
|
|
|
static int32_t zero_pty_spawn_impl(ZeroByteView command, const char *cwd, char *env) {
|
|
char storage[ZERO_RUNTIME_PROC_COMMAND_BYTES];
|
|
char *argv[ZERO_RUNTIME_PROC_MAX_ARGS];
|
|
if (!zero_runtime_proc_parse_command(command, storage, argv)) return 0;
|
|
return zero_pty_spawn_argv_impl(argv, cwd, env);
|
|
}
|
|
|
|
int32_t zero_pty_spawn(ZeroByteView command) {
|
|
return zero_pty_spawn_impl(command, NULL, NULL);
|
|
}
|
|
|
|
int32_t zero_pty_spawn_in(ZeroByteView command, ZeroByteView cwd) {
|
|
char cwd_buf[ZERO_RUNTIME_PATH_BYTES];
|
|
if (!zero_runtime_path_copy(cwd, cwd_buf)) return 0;
|
|
return zero_pty_spawn_impl(command, cwd_buf, NULL);
|
|
}
|
|
|
|
int32_t zero_pty_spawn_in_env(ZeroByteView command, ZeroByteView cwd, ZeroByteView env) {
|
|
char cwd_buf[ZERO_RUNTIME_PATH_BYTES];
|
|
char env_buf[ZERO_RUNTIME_PROC_ENV_BYTES];
|
|
if (!zero_runtime_path_copy(cwd, cwd_buf) || !zero_runtime_proc_copy_env_block(env, env_buf)) return 0;
|
|
return zero_pty_spawn_impl(command, cwd_buf, env_buf);
|
|
}
|
|
|
|
int32_t zero_pty_spawn_args(ZeroByteView program, ZeroByteView args, ZeroByteView cwd, ZeroByteView env) {
|
|
char argv_storage[ZERO_RUNTIME_PROC_ARGS_BYTES];
|
|
char *argv[ZERO_RUNTIME_PROC_MAX_ARGS];
|
|
char cwd_buf[ZERO_RUNTIME_PATH_BYTES];
|
|
char env_buf[ZERO_RUNTIME_PROC_ENV_BYTES];
|
|
if (!zero_runtime_proc_build_argv(program, args, argv_storage, argv) ||
|
|
!zero_runtime_path_copy(cwd, cwd_buf) ||
|
|
!zero_runtime_proc_copy_env_block(env, env_buf)) return 0;
|
|
return zero_pty_spawn_argv_impl(argv, cwd_buf, env_buf);
|
|
}
|
|
|
|
int32_t zero_pty_resize(int32_t child, uint64_t columns, uint64_t rows) {
|
|
#if defined(_WIN32)
|
|
(void)child;
|
|
(void)columns;
|
|
(void)rows;
|
|
return 0;
|
|
#else
|
|
int index = zero_proc_child_index(child);
|
|
if (index < 0 || columns == 0 || rows == 0 || columns > UINT16_MAX || rows > UINT16_MAX) return 0;
|
|
ZeroRuntimeProcChild *slot = &zero_proc_children[index];
|
|
if (!slot->pty || slot->pty_fd < 0) return 0;
|
|
#if defined(TIOCSWINSZ)
|
|
struct winsize size;
|
|
memset(&size, 0, sizeof(size));
|
|
size.ws_col = (unsigned short)columns;
|
|
size.ws_row = (unsigned short)rows;
|
|
return ioctl(slot->pty_fd, TIOCSWINSZ, &size) == 0 ? 1 : 0;
|
|
#else
|
|
return 0;
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
int32_t zero_proc_child_op(int32_t child, uint32_t op) {
|
|
#if defined(_WIN32)
|
|
(void)child;
|
|
(void)op;
|
|
return 0;
|
|
#else
|
|
switch ((ZeroProcChildOp)op) {
|
|
case ZERO_PROC_CHILD_OP_PID_RUNNING:
|
|
return zero_proc_pid_running(child);
|
|
case ZERO_PROC_CHILD_OP_KILL_PID:
|
|
return zero_proc_signal_pid(child, SIGTERM);
|
|
case ZERO_PROC_CHILD_OP_INTERRUPT_PID:
|
|
return zero_proc_signal_pid(child, SIGINT);
|
|
case ZERO_PROC_CHILD_OP_KILL_GROUP_PID:
|
|
return zero_proc_signal_group_pid(child, SIGTERM);
|
|
case ZERO_PROC_CHILD_OP_INTERRUPT_GROUP_PID:
|
|
return zero_proc_signal_group_pid(child, SIGINT);
|
|
default:
|
|
break;
|
|
}
|
|
int index = zero_proc_child_index(child);
|
|
if (index < 0) return op == ZERO_PROC_CHILD_OP_WAIT ? 127 : 0;
|
|
ZeroRuntimeProcChild *slot = &zero_proc_children[index];
|
|
switch ((ZeroProcChildOp)op) {
|
|
case ZERO_PROC_CHILD_OP_VALID:
|
|
return 1;
|
|
case ZERO_PROC_CHILD_OP_PID:
|
|
return (int32_t)slot->pid;
|
|
case ZERO_PROC_CHILD_OP_RUNNING:
|
|
return zero_proc_child_reap(slot, 1) ? 0 : 1;
|
|
case ZERO_PROC_CHILD_OP_WAIT:
|
|
zero_proc_child_wait_with_drain(slot, -1);
|
|
return slot->status;
|
|
case ZERO_PROC_CHILD_OP_KILL:
|
|
if (zero_proc_child_reap(slot, 1)) return 1;
|
|
return zero_proc_signal_child(slot, SIGTERM);
|
|
case ZERO_PROC_CHILD_OP_INTERRUPT:
|
|
if (zero_proc_child_reap(slot, 1)) return 1;
|
|
return zero_proc_signal_child(slot, SIGINT);
|
|
case ZERO_PROC_CHILD_OP_CLOSE_STDIN:
|
|
if (slot->pty) return 1;
|
|
zero_proc_child_close_fd(&slot->stdin_fd);
|
|
return 1;
|
|
case ZERO_PROC_CHILD_OP_CLOSE:
|
|
return zero_proc_child_close_handle(slot);
|
|
default:
|
|
return 0;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
ZeroMaybeUsize zero_proc_child_io(int32_t child, ZeroMutByteView buffer, uint32_t op) {
|
|
if (!buffer.ptr && buffer.len > 0) return zero_runtime_none_usize();
|
|
#if defined(_WIN32)
|
|
(void)child;
|
|
(void)op;
|
|
return zero_runtime_none_usize();
|
|
#else
|
|
int index = zero_proc_child_index(child);
|
|
if (index < 0) return zero_runtime_none_usize();
|
|
ZeroRuntimeProcChild *slot = &zero_proc_children[index];
|
|
int fd = -1;
|
|
if (slot->pty) {
|
|
if (op == ZERO_PROC_CHILD_IO_READ_STDOUT || op == ZERO_PROC_CHILD_IO_WRITE_STDIN) fd = slot->pty_fd;
|
|
else return zero_runtime_none_usize();
|
|
} else {
|
|
if (op == ZERO_PROC_CHILD_IO_READ_STDOUT) fd = slot->stdout_fd;
|
|
else if (op == ZERO_PROC_CHILD_IO_READ_STDERR) fd = slot->stderr_fd;
|
|
else if (op == ZERO_PROC_CHILD_IO_WRITE_STDIN) fd = slot->stdin_fd;
|
|
else return zero_runtime_none_usize();
|
|
}
|
|
if (op == ZERO_PROC_CHILD_IO_WRITE_STDIN) {
|
|
if (fd < 0) return zero_runtime_none_usize();
|
|
if (buffer.len == 0) return (ZeroMaybeUsize){1, 0};
|
|
ZeroWriteResult written = ZERO_RUNTIME_WRITE(fd, buffer.ptr, buffer.len > (size_t)UINT_MAX ? UINT_MAX : (unsigned)buffer.len);
|
|
if (written < 0) {
|
|
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) return zero_runtime_none_usize();
|
|
if (slot->pty) zero_proc_child_close_fd(&slot->pty_fd);
|
|
else zero_proc_child_close_fd(&slot->stdin_fd);
|
|
return zero_runtime_none_usize();
|
|
}
|
|
return (ZeroMaybeUsize){1, (uint64_t)written};
|
|
}
|
|
|
|
if (buffer.len == 0) return zero_runtime_none_usize();
|
|
ZeroRuntimeProcPipeBuffer *pending = NULL;
|
|
if (slot->pty) pending = &slot->pty_buffer;
|
|
else if (op == ZERO_PROC_CHILD_IO_READ_STDOUT) pending = &slot->stdout_buffer;
|
|
else if (op == ZERO_PROC_CHILD_IO_READ_STDERR) pending = &slot->stderr_buffer;
|
|
ZeroMaybeUsize buffered = zero_proc_pipe_buffer_read(pending, buffer);
|
|
if (buffered.has) return buffered;
|
|
if (fd < 0) return zero_runtime_none_usize();
|
|
|
|
ZeroReadResult got = ZERO_RUNTIME_READ(fd, buffer.ptr, buffer.len > (size_t)UINT_MAX ? UINT_MAX : (unsigned)buffer.len);
|
|
if (got < 0) {
|
|
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) return zero_runtime_none_usize();
|
|
if (slot->pty) zero_proc_child_close_fd(&slot->pty_fd);
|
|
else if (op == ZERO_PROC_CHILD_IO_READ_STDOUT) zero_proc_child_close_fd(&slot->stdout_fd);
|
|
else zero_proc_child_close_fd(&slot->stderr_fd);
|
|
return zero_runtime_none_usize();
|
|
}
|
|
if (got == 0) {
|
|
if (slot->pty) zero_proc_child_close_fd(&slot->pty_fd);
|
|
else if (op == ZERO_PROC_CHILD_IO_READ_STDOUT) zero_proc_child_close_fd(&slot->stdout_fd);
|
|
else zero_proc_child_close_fd(&slot->stderr_fd);
|
|
return zero_runtime_none_usize();
|
|
}
|
|
return (ZeroMaybeUsize){1, (uint64_t)got};
|
|
#endif
|
|
}
|
|
|
|
uint64_t zero_parse_u32(ZeroByteView text) {
|
|
if ((!text.ptr && text.len > 0) || text.len == 0) return 0;
|
|
uint64_t total = 0;
|
|
for (size_t i = 0; i < text.len; i++) {
|
|
unsigned char byte = text.ptr[i];
|
|
if (byte < '0' || byte > '9') return 0;
|
|
uint64_t digit = (uint64_t)(byte - '0');
|
|
if (total > (UINT32_MAX - digit) / 10u) return 0;
|
|
total = total * 10u + digit;
|
|
}
|
|
return (1ull << 32) | total;
|
|
}
|
|
|
|
uint64_t zero_parse_i32(ZeroByteView text) {
|
|
if ((!text.ptr && text.len > 0) || text.len == 0) return 0;
|
|
size_t index = 0;
|
|
uint32_t limit = 2147483647u;
|
|
uint32_t negative = 0;
|
|
if (text.ptr[0] == '-') {
|
|
negative = 1;
|
|
limit = 2147483648u;
|
|
index = 1;
|
|
} else if (text.ptr[0] == '+') {
|
|
index = 1;
|
|
}
|
|
if (index == text.len) return 0;
|
|
uint64_t total = 0;
|
|
for (; index < text.len; index++) {
|
|
unsigned char byte = text.ptr[index];
|
|
if (byte < '0' || byte > '9') return 0;
|
|
uint64_t digit = (uint64_t)(byte - '0');
|
|
if (total > ((uint64_t)limit - digit) / 10u) return 0;
|
|
total = total * 10u + digit;
|
|
}
|
|
int32_t value = negative ? (total == 2147483648ull ? INT32_MIN : -(int32_t)total) : (int32_t)total;
|
|
return (1ull << 32) | (uint32_t)value;
|
|
}
|
|
|
|
uint32_t zero_fmt_u32(ZeroMutByteView buffer, uint32_t value) {
|
|
if (!buffer.ptr) return 0;
|
|
unsigned char tmp[10];
|
|
size_t len = 0;
|
|
do {
|
|
tmp[len++] = (unsigned char)('0' + (value % 10u));
|
|
value /= 10u;
|
|
} while (value > 0 && len < sizeof(tmp));
|
|
if (len > buffer.len) return 0;
|
|
for (size_t i = 0; i < len; i++) buffer.ptr[i] = tmp[len - 1 - i];
|
|
return (uint32_t)len;
|
|
}
|
|
|
|
uint32_t zero_fmt_bool(ZeroMutByteView buffer, uint32_t value) {
|
|
static const unsigned char true_text[] = {'t', 'r', 'u', 'e'};
|
|
static const unsigned char false_text[] = {'f', 'a', 'l', 's', 'e'};
|
|
const unsigned char *text = value ? true_text : false_text;
|
|
size_t len = value ? sizeof(true_text) : sizeof(false_text);
|
|
if (!buffer.ptr || len > buffer.len) return 0;
|
|
memcpy(buffer.ptr, text, len);
|
|
return (uint32_t)len;
|
|
}
|
|
|
|
uint32_t zero_fmt_hex_lower_u32(ZeroMutByteView buffer, uint32_t value) {
|
|
if (!buffer.ptr) return 0;
|
|
static const unsigned char digits[] = "0123456789abcdef";
|
|
unsigned char tmp[8];
|
|
size_t len = 0;
|
|
do {
|
|
tmp[len++] = digits[value & 0xfu];
|
|
value >>= 4;
|
|
} while (value > 0 && len < sizeof(tmp));
|
|
if (len > buffer.len) return 0;
|
|
for (size_t i = 0; i < len; i++) buffer.ptr[i] = tmp[len - 1 - i];
|
|
return (uint32_t)len;
|
|
}
|
|
|
|
uint32_t zero_fmt_i32(ZeroMutByteView buffer, int32_t value) {
|
|
if (!buffer.ptr) return 0;
|
|
if (value >= 0) return zero_fmt_u32(buffer, (uint32_t)value);
|
|
if (buffer.len == 0) return 0;
|
|
buffer.ptr[0] = '-';
|
|
uint32_t magnitude = value == INT32_MIN ? 2147483648u : (uint32_t)(-value);
|
|
ZeroMutByteView rest = {buffer.ptr + 1, buffer.len - 1};
|
|
uint32_t written = zero_fmt_u32(rest, magnitude);
|
|
return written ? written + 1 : 0;
|
|
}
|
|
|
|
uint32_t zero_fmt_usize(ZeroMutByteView buffer, uint64_t value) {
|
|
if (!buffer.ptr) return 0;
|
|
unsigned char tmp[20];
|
|
size_t len = 0;
|
|
do {
|
|
tmp[len++] = (unsigned char)('0' + (value % 10u));
|
|
value /= 10u;
|
|
} while (value > 0 && len < sizeof(tmp));
|
|
if (len > buffer.len) return 0;
|
|
for (size_t i = 0; i < len; i++) buffer.ptr[i] = tmp[len - 1 - i];
|
|
return (uint32_t)len;
|
|
}
|
|
|
|
uint64_t zero_args_find(uint64_t argc, const char *const *argv, ZeroByteView name) {
|
|
if (!argv || (!name.ptr && name.len > 0)) return 0;
|
|
for (uint64_t i = 1; i < argc; i++) {
|
|
const char *arg = argv[i];
|
|
if (!arg) continue;
|
|
size_t len = strlen(arg);
|
|
if (len == name.len && memcmp(arg, name.ptr, len) == 0) {
|
|
return (1ull << 32) | (i & 0xffffffffull);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
uint32_t zero_ascii_op(uint32_t byte, uint32_t op) {
|
|
byte &= 0xffu;
|
|
uint32_t is_digit = byte >= '0' && byte <= '9';
|
|
uint32_t is_lower = byte >= 'a' && byte <= 'z';
|
|
uint32_t is_upper = byte >= 'A' && byte <= 'Z';
|
|
uint32_t is_alpha = is_lower || is_upper;
|
|
switch (op) {
|
|
case ZERO_ASCII_OP_IS_DIGIT:
|
|
return is_digit;
|
|
case ZERO_ASCII_OP_IS_LOWER:
|
|
return is_lower;
|
|
case ZERO_ASCII_OP_IS_UPPER:
|
|
return is_upper;
|
|
case ZERO_ASCII_OP_IS_ALPHA:
|
|
return is_alpha;
|
|
case ZERO_ASCII_OP_IS_ALNUM:
|
|
return is_alpha || is_digit;
|
|
case ZERO_ASCII_OP_IS_WHITESPACE:
|
|
return byte == ' ' || byte == '\t' || byte == '\n' || byte == '\r';
|
|
case ZERO_ASCII_OP_IS_HEX_DIGIT:
|
|
return is_digit || (byte >= 'A' && byte <= 'F') || (byte >= 'a' && byte <= 'f');
|
|
case ZERO_ASCII_OP_TO_LOWER:
|
|
return is_upper ? byte + ('a' - 'A') : byte;
|
|
case ZERO_ASCII_OP_TO_UPPER:
|
|
return is_lower ? byte - ('a' - 'A') : byte;
|
|
case ZERO_ASCII_OP_DIGIT_VALUE:
|
|
return is_digit ? 0x100u | (byte - '0') : 0;
|
|
case ZERO_ASCII_OP_HEX_VALUE:
|
|
if (is_digit) return 0x100u | (byte - '0');
|
|
if (byte >= 'A' && byte <= 'F') return 0x100u | (byte - 'A' + 10u);
|
|
if (byte >= 'a' && byte <= 'f') return 0x100u | (byte - 'a' + 10u);
|
|
return 0;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static int zero_str_view_valid(ZeroByteView view) {
|
|
return view.ptr || view.len == 0;
|
|
}
|
|
|
|
static uint32_t zero_str_some_len(size_t len) {
|
|
if (len > UINT32_MAX - 1u) return 0;
|
|
return (uint32_t)(len + 1u);
|
|
}
|
|
|
|
static unsigned char zero_str_lower(unsigned char byte) {
|
|
return byte >= 'A' && byte <= 'Z' ? (unsigned char)(byte + ('a' - 'A')) : byte;
|
|
}
|
|
|
|
static unsigned char zero_str_upper(unsigned char byte) {
|
|
return byte >= 'a' && byte <= 'z' ? (unsigned char)(byte - ('a' - 'A')) : byte;
|
|
}
|
|
|
|
typedef struct {
|
|
uint32_t state[8];
|
|
uint64_t bit_len;
|
|
unsigned char block[64];
|
|
size_t block_len;
|
|
} ZeroSha256;
|
|
|
|
static uint32_t zero_sha256_rotr(uint32_t value, unsigned bits) {
|
|
return (value >> bits) | (value << (32u - bits));
|
|
}
|
|
|
|
static uint32_t zero_sha256_load_be32(const unsigned char *ptr) {
|
|
return ((uint32_t)ptr[0] << 24) |
|
|
((uint32_t)ptr[1] << 16) |
|
|
((uint32_t)ptr[2] << 8) |
|
|
(uint32_t)ptr[3];
|
|
}
|
|
|
|
static void zero_sha256_store_be32(unsigned char *ptr, uint32_t value) {
|
|
ptr[0] = (unsigned char)((value >> 24) & 0xffu);
|
|
ptr[1] = (unsigned char)((value >> 16) & 0xffu);
|
|
ptr[2] = (unsigned char)((value >> 8) & 0xffu);
|
|
ptr[3] = (unsigned char)(value & 0xffu);
|
|
}
|
|
|
|
static void zero_sha256_transform(ZeroSha256 *sha, const unsigned char block[64]) {
|
|
static const uint32_t k[64] = {
|
|
UINT32_C(0x428a2f98), UINT32_C(0x71374491), UINT32_C(0xb5c0fbcf), UINT32_C(0xe9b5dba5),
|
|
UINT32_C(0x3956c25b), UINT32_C(0x59f111f1), UINT32_C(0x923f82a4), UINT32_C(0xab1c5ed5),
|
|
UINT32_C(0xd807aa98), UINT32_C(0x12835b01), UINT32_C(0x243185be), UINT32_C(0x550c7dc3),
|
|
UINT32_C(0x72be5d74), UINT32_C(0x80deb1fe), UINT32_C(0x9bdc06a7), UINT32_C(0xc19bf174),
|
|
UINT32_C(0xe49b69c1), UINT32_C(0xefbe4786), UINT32_C(0x0fc19dc6), UINT32_C(0x240ca1cc),
|
|
UINT32_C(0x2de92c6f), UINT32_C(0x4a7484aa), UINT32_C(0x5cb0a9dc), UINT32_C(0x76f988da),
|
|
UINT32_C(0x983e5152), UINT32_C(0xa831c66d), UINT32_C(0xb00327c8), UINT32_C(0xbf597fc7),
|
|
UINT32_C(0xc6e00bf3), UINT32_C(0xd5a79147), UINT32_C(0x06ca6351), UINT32_C(0x14292967),
|
|
UINT32_C(0x27b70a85), UINT32_C(0x2e1b2138), UINT32_C(0x4d2c6dfc), UINT32_C(0x53380d13),
|
|
UINT32_C(0x650a7354), UINT32_C(0x766a0abb), UINT32_C(0x81c2c92e), UINT32_C(0x92722c85),
|
|
UINT32_C(0xa2bfe8a1), UINT32_C(0xa81a664b), UINT32_C(0xc24b8b70), UINT32_C(0xc76c51a3),
|
|
UINT32_C(0xd192e819), UINT32_C(0xd6990624), UINT32_C(0xf40e3585), UINT32_C(0x106aa070),
|
|
UINT32_C(0x19a4c116), UINT32_C(0x1e376c08), UINT32_C(0x2748774c), UINT32_C(0x34b0bcb5),
|
|
UINT32_C(0x391c0cb3), UINT32_C(0x4ed8aa4a), UINT32_C(0x5b9cca4f), UINT32_C(0x682e6ff3),
|
|
UINT32_C(0x748f82ee), UINT32_C(0x78a5636f), UINT32_C(0x84c87814), UINT32_C(0x8cc70208),
|
|
UINT32_C(0x90befffa), UINT32_C(0xa4506ceb), UINT32_C(0xbef9a3f7), UINT32_C(0xc67178f2)
|
|
};
|
|
uint32_t w[64];
|
|
for (size_t i = 0; i < 16; i++) w[i] = zero_sha256_load_be32(block + i * 4u);
|
|
for (size_t i = 16; i < 64; i++) {
|
|
uint32_t s0 = zero_sha256_rotr(w[i - 15u], 7) ^ zero_sha256_rotr(w[i - 15u], 18) ^ (w[i - 15u] >> 3);
|
|
uint32_t s1 = zero_sha256_rotr(w[i - 2u], 17) ^ zero_sha256_rotr(w[i - 2u], 19) ^ (w[i - 2u] >> 10);
|
|
w[i] = w[i - 16u] + s0 + w[i - 7u] + s1;
|
|
}
|
|
|
|
uint32_t a = sha->state[0];
|
|
uint32_t b = sha->state[1];
|
|
uint32_t c = sha->state[2];
|
|
uint32_t d = sha->state[3];
|
|
uint32_t e = sha->state[4];
|
|
uint32_t f = sha->state[5];
|
|
uint32_t g = sha->state[6];
|
|
uint32_t h = sha->state[7];
|
|
|
|
for (size_t i = 0; i < 64; i++) {
|
|
uint32_t s1 = zero_sha256_rotr(e, 6) ^ zero_sha256_rotr(e, 11) ^ zero_sha256_rotr(e, 25);
|
|
uint32_t ch = (e & f) ^ ((~e) & g);
|
|
uint32_t temp1 = h + s1 + ch + k[i] + w[i];
|
|
uint32_t s0 = zero_sha256_rotr(a, 2) ^ zero_sha256_rotr(a, 13) ^ zero_sha256_rotr(a, 22);
|
|
uint32_t maj = (a & b) ^ (a & c) ^ (b & c);
|
|
uint32_t temp2 = s0 + maj;
|
|
h = g;
|
|
g = f;
|
|
f = e;
|
|
e = d + temp1;
|
|
d = c;
|
|
c = b;
|
|
b = a;
|
|
a = temp1 + temp2;
|
|
}
|
|
|
|
sha->state[0] += a;
|
|
sha->state[1] += b;
|
|
sha->state[2] += c;
|
|
sha->state[3] += d;
|
|
sha->state[4] += e;
|
|
sha->state[5] += f;
|
|
sha->state[6] += g;
|
|
sha->state[7] += h;
|
|
}
|
|
|
|
static void zero_sha256_init(ZeroSha256 *sha) {
|
|
sha->state[0] = UINT32_C(0x6a09e667);
|
|
sha->state[1] = UINT32_C(0xbb67ae85);
|
|
sha->state[2] = UINT32_C(0x3c6ef372);
|
|
sha->state[3] = UINT32_C(0xa54ff53a);
|
|
sha->state[4] = UINT32_C(0x510e527f);
|
|
sha->state[5] = UINT32_C(0x9b05688c);
|
|
sha->state[6] = UINT32_C(0x1f83d9ab);
|
|
sha->state[7] = UINT32_C(0x5be0cd19);
|
|
sha->bit_len = 0;
|
|
sha->block_len = 0;
|
|
}
|
|
|
|
static void zero_sha256_update(ZeroSha256 *sha, const unsigned char *bytes, size_t len) {
|
|
if (!bytes && len > 0) return;
|
|
while (len > 0) {
|
|
size_t take = 64u - sha->block_len;
|
|
if (take > len) take = len;
|
|
memcpy(sha->block + sha->block_len, bytes, take);
|
|
sha->block_len += take;
|
|
bytes += take;
|
|
len -= take;
|
|
if (sha->block_len == 64u) {
|
|
zero_sha256_transform(sha, sha->block);
|
|
sha->bit_len += 512u;
|
|
sha->block_len = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void zero_sha256_final(ZeroSha256 *sha, unsigned char digest[32]) {
|
|
uint64_t total_bits = sha->bit_len + (uint64_t)sha->block_len * 8u;
|
|
sha->block[sha->block_len++] = 0x80u;
|
|
if (sha->block_len > 56u) {
|
|
while (sha->block_len < 64u) sha->block[sha->block_len++] = 0;
|
|
zero_sha256_transform(sha, sha->block);
|
|
sha->block_len = 0;
|
|
}
|
|
while (sha->block_len < 56u) sha->block[sha->block_len++] = 0;
|
|
for (size_t i = 0; i < 8; i++) sha->block[63u - i] = (unsigned char)((total_bits >> (i * 8u)) & 0xffu);
|
|
zero_sha256_transform(sha, sha->block);
|
|
for (size_t i = 0; i < 8; i++) zero_sha256_store_be32(digest + i * 4u, sha->state[i]);
|
|
}
|
|
|
|
static void zero_sha256_hash(ZeroByteView bytes, unsigned char digest[32]) {
|
|
ZeroSha256 sha;
|
|
zero_sha256_init(&sha);
|
|
zero_sha256_update(&sha, bytes.ptr, bytes.len);
|
|
zero_sha256_final(&sha, digest);
|
|
}
|
|
|
|
static void zero_sha256_hmac(ZeroByteView key, ZeroByteView bytes, unsigned char digest[32]) {
|
|
unsigned char key_block[64];
|
|
unsigned char key_hash[32];
|
|
unsigned char ipad[64];
|
|
unsigned char opad[64];
|
|
unsigned char inner[32];
|
|
memset(key_block, 0, sizeof(key_block));
|
|
if (key.len > 64u) {
|
|
zero_sha256_hash(key, key_hash);
|
|
memcpy(key_block, key_hash, sizeof(key_hash));
|
|
} else if (key.len > 0) {
|
|
memcpy(key_block, key.ptr, key.len);
|
|
}
|
|
for (size_t i = 0; i < 64u; i++) {
|
|
ipad[i] = (unsigned char)(key_block[i] ^ 0x36u);
|
|
opad[i] = (unsigned char)(key_block[i] ^ 0x5cu);
|
|
}
|
|
|
|
ZeroSha256 sha;
|
|
zero_sha256_init(&sha);
|
|
zero_sha256_update(&sha, ipad, sizeof(ipad));
|
|
zero_sha256_update(&sha, bytes.ptr, bytes.len);
|
|
zero_sha256_final(&sha, inner);
|
|
|
|
zero_sha256_init(&sha);
|
|
zero_sha256_update(&sha, opad, sizeof(opad));
|
|
zero_sha256_update(&sha, inner, sizeof(inner));
|
|
zero_sha256_final(&sha, digest);
|
|
}
|
|
|
|
static uint32_t zero_crypto_write_digest_hex(ZeroMutByteView buffer, const unsigned char digest[32]) {
|
|
if (buffer.len < 64u) return 0;
|
|
static const unsigned char hex[] = "0123456789abcdef";
|
|
for (size_t i = 0; i < 32u; i++) {
|
|
buffer.ptr[i * 2u] = hex[digest[i] >> 4];
|
|
buffer.ptr[i * 2u + 1u] = hex[digest[i] & 0x0fu];
|
|
}
|
|
return zero_str_some_len(64u);
|
|
}
|
|
|
|
uint32_t zero_crypto_digest(ZeroMutByteView buffer, ZeroByteView bytes, uint32_t op) {
|
|
if (!buffer.ptr || !zero_str_view_valid(bytes)) return 0;
|
|
unsigned char digest[32];
|
|
zero_sha256_hash(bytes, digest);
|
|
if (op == ZERO_CRYPTO_DIGEST_SHA256) {
|
|
if (buffer.len < 32u) return 0;
|
|
memcpy(buffer.ptr, digest, 32u);
|
|
return zero_str_some_len(32u);
|
|
}
|
|
if (op == ZERO_CRYPTO_DIGEST_SHA256_HEX) {
|
|
return zero_crypto_write_digest_hex(buffer, digest);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
uint32_t zero_crypto_hmac_sha256(ZeroMutByteView buffer, ZeroByteView key, ZeroByteView bytes) {
|
|
if (!buffer.ptr || !zero_str_view_valid(key) || !zero_str_view_valid(bytes) || buffer.len < 32u) return 0;
|
|
unsigned char digest[32];
|
|
zero_sha256_hmac(key, bytes, digest);
|
|
memcpy(buffer.ptr, digest, sizeof(digest));
|
|
return zero_str_some_len(sizeof(digest));
|
|
}
|
|
|
|
uint32_t zero_crypto_hmac_sha256_hex(ZeroMutByteView buffer, ZeroByteView key, ZeroByteView bytes) {
|
|
if (!buffer.ptr || !zero_str_view_valid(key) || !zero_str_view_valid(bytes)) return 0;
|
|
unsigned char digest[32];
|
|
zero_sha256_hmac(key, bytes, digest);
|
|
return zero_crypto_write_digest_hex(buffer, digest);
|
|
}
|
|
|
|
static int zero_str_ascii_space(unsigned char byte) {
|
|
return byte == ' ' || byte == '\t' || byte == '\n' || byte == '\r';
|
|
}
|
|
|
|
static size_t zero_text_utf8_sequence_len(unsigned char first) {
|
|
if (first < 128u) return 1;
|
|
if (first >= 194u && first <= 223u) return 2;
|
|
if (first >= 224u && first <= 239u) return 3;
|
|
if (first >= 240u && first <= 244u) return 4;
|
|
return 0;
|
|
}
|
|
|
|
static int zero_text_is_cont(unsigned char byte) {
|
|
return byte >= 128u && byte <= 191u;
|
|
}
|
|
|
|
static uint64_t zero_text_utf8_len_encoded(ZeroByteView text, int count_codepoints) {
|
|
if (!zero_str_view_valid(text)) return 0;
|
|
size_t index = 0;
|
|
uint64_t count = 0;
|
|
while (index < text.len) {
|
|
unsigned char first = text.ptr[index];
|
|
size_t width = zero_text_utf8_sequence_len(first);
|
|
if (width == 0 || index + width > text.len) return 0;
|
|
if (width == 2) {
|
|
if (!zero_text_is_cont(text.ptr[index + 1])) return 0;
|
|
} else if (width == 3) {
|
|
unsigned char second = text.ptr[index + 1];
|
|
if (!zero_text_is_cont(second) || !zero_text_is_cont(text.ptr[index + 2])) return 0;
|
|
if (first == 224u && second < 160u) return 0;
|
|
if (first == 237u && second > 159u) return 0;
|
|
} else if (width == 4) {
|
|
unsigned char second = text.ptr[index + 1];
|
|
if (!zero_text_is_cont(second) || !zero_text_is_cont(text.ptr[index + 2]) || !zero_text_is_cont(text.ptr[index + 3])) return 0;
|
|
if (first == 240u && second < 144u) return 0;
|
|
if (first == 244u && second > 143u) return 0;
|
|
}
|
|
index += width;
|
|
count++;
|
|
}
|
|
if (!count_codepoints) return 1;
|
|
if (count == UINT64_MAX) return 0;
|
|
return count + 1u;
|
|
}
|
|
|
|
uint64_t zero_text_op(ZeroByteView text, uint32_t op) {
|
|
switch (op) {
|
|
case ZERO_TEXT_OP_IS_ASCII:
|
|
if (!zero_str_view_valid(text)) return 0;
|
|
for (size_t i = 0; i < text.len; i++) {
|
|
if (text.ptr[i] >= 128u) return 0;
|
|
}
|
|
return 1;
|
|
case ZERO_TEXT_OP_UTF8_VALID:
|
|
return zero_text_utf8_len_encoded(text, 0);
|
|
case ZERO_TEXT_OP_UTF8_LEN:
|
|
return zero_text_utf8_len_encoded(text, 1);
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
#define ZERO_TERM_KEY_ARROW_UP UINT32_C(1114113)
|
|
#define ZERO_TERM_KEY_ARROW_DOWN UINT32_C(1114114)
|
|
#define ZERO_TERM_KEY_ARROW_RIGHT UINT32_C(1114115)
|
|
#define ZERO_TERM_KEY_ARROW_LEFT UINT32_C(1114116)
|
|
#define ZERO_TERM_KEY_DELETE UINT32_C(1114117)
|
|
#define ZERO_TERM_KEY_HOME UINT32_C(1114118)
|
|
#define ZERO_TERM_KEY_END UINT32_C(1114119)
|
|
#define ZERO_TERM_KEY_PAGE_UP UINT32_C(1114120)
|
|
#define ZERO_TERM_KEY_PAGE_DOWN UINT32_C(1114121)
|
|
#define ZERO_TERM_KEY_INSERT UINT32_C(1114122)
|
|
#define ZERO_TERM_KEY_SHIFT_TAB UINT32_C(1114123)
|
|
#define ZERO_TERM_KEY_F1 UINT32_C(1114124)
|
|
#define ZERO_TERM_KEY_F2 UINT32_C(1114125)
|
|
#define ZERO_TERM_KEY_F3 UINT32_C(1114126)
|
|
#define ZERO_TERM_KEY_F4 UINT32_C(1114127)
|
|
#define ZERO_TERM_KEY_F5 UINT32_C(1114128)
|
|
#define ZERO_TERM_KEY_F6 UINT32_C(1114129)
|
|
#define ZERO_TERM_KEY_F7 UINT32_C(1114130)
|
|
#define ZERO_TERM_KEY_F8 UINT32_C(1114131)
|
|
#define ZERO_TERM_KEY_F9 UINT32_C(1114132)
|
|
#define ZERO_TERM_KEY_F10 UINT32_C(1114133)
|
|
#define ZERO_TERM_KEY_F11 UINT32_C(1114134)
|
|
#define ZERO_TERM_KEY_F12 UINT32_C(1114135)
|
|
#define ZERO_TERM_KEY_PASTE_START UINT32_C(1114136)
|
|
#define ZERO_TERM_KEY_PASTE_END UINT32_C(1114137)
|
|
|
|
static uint32_t zero_term_utf8_key(ZeroByteView text, size_t *width_out) {
|
|
if (text.len == 0 || !text.ptr) return 0;
|
|
unsigned char first = text.ptr[0];
|
|
size_t width = zero_text_utf8_sequence_len(first);
|
|
if (width == 0 || width > text.len) return 0;
|
|
uint32_t code = first;
|
|
if (width == 2) {
|
|
unsigned char second = text.ptr[1];
|
|
if (!zero_text_is_cont(second)) return 0;
|
|
code = ((uint32_t)(first & 0x1fu) << 6) | (uint32_t)(second & 0x3fu);
|
|
} else if (width == 3) {
|
|
unsigned char second = text.ptr[1];
|
|
unsigned char third = text.ptr[2];
|
|
if (!zero_text_is_cont(second) || !zero_text_is_cont(third)) return 0;
|
|
if (first == 224u && second < 160u) return 0;
|
|
if (first == 237u && second > 159u) return 0;
|
|
code = ((uint32_t)(first & 0x0fu) << 12) | ((uint32_t)(second & 0x3fu) << 6) | (uint32_t)(third & 0x3fu);
|
|
} else if (width == 4) {
|
|
unsigned char second = text.ptr[1];
|
|
unsigned char third = text.ptr[2];
|
|
unsigned char fourth = text.ptr[3];
|
|
if (!zero_text_is_cont(second) || !zero_text_is_cont(third) || !zero_text_is_cont(fourth)) return 0;
|
|
if (first == 240u && second < 144u) return 0;
|
|
if (first == 244u && second > 143u) return 0;
|
|
code = ((uint32_t)(first & 0x07u) << 18) | ((uint32_t)(second & 0x3fu) << 12) | ((uint32_t)(third & 0x3fu) << 6) | (uint32_t)(fourth & 0x3fu);
|
|
}
|
|
if (width_out) *width_out = width;
|
|
return code;
|
|
}
|
|
|
|
static uint32_t zero_term_key_decode(ZeroByteView text, int return_len) {
|
|
if (!zero_str_view_valid(text) || text.len == 0) return 0;
|
|
unsigned char first = text.ptr[0];
|
|
if (first == 0x1bu) {
|
|
if (text.len == 1) return return_len ? 1u : 27u;
|
|
if (text.len >= 3 && (text.ptr[1] == '[' || text.ptr[1] == 'O')) {
|
|
unsigned char code = text.ptr[2];
|
|
if (code == 'A') return return_len ? 3u : ZERO_TERM_KEY_ARROW_UP;
|
|
if (code == 'B') return return_len ? 3u : ZERO_TERM_KEY_ARROW_DOWN;
|
|
if (code == 'C') return return_len ? 3u : ZERO_TERM_KEY_ARROW_RIGHT;
|
|
if (code == 'D') return return_len ? 3u : ZERO_TERM_KEY_ARROW_LEFT;
|
|
if (code == 'H') return return_len ? 3u : ZERO_TERM_KEY_HOME;
|
|
if (code == 'F') return return_len ? 3u : ZERO_TERM_KEY_END;
|
|
if (code == 'Z' && text.ptr[1] == '[') return return_len ? 3u : ZERO_TERM_KEY_SHIFT_TAB;
|
|
if (code == 'P' && text.ptr[1] == 'O') return return_len ? 3u : ZERO_TERM_KEY_F1;
|
|
if (code == 'Q' && text.ptr[1] == 'O') return return_len ? 3u : ZERO_TERM_KEY_F2;
|
|
if (code == 'R' && text.ptr[1] == 'O') return return_len ? 3u : ZERO_TERM_KEY_F3;
|
|
if (code == 'S' && text.ptr[1] == 'O') return return_len ? 3u : ZERO_TERM_KEY_F4;
|
|
}
|
|
if (text.len >= 4 && text.ptr[1] == '[' && text.ptr[2] == '2' && text.ptr[3] == '~') {
|
|
return return_len ? 4u : ZERO_TERM_KEY_INSERT;
|
|
}
|
|
if (text.len >= 4 && text.ptr[1] == '[' && text.ptr[2] == '3' && text.ptr[3] == '~') {
|
|
return return_len ? 4u : ZERO_TERM_KEY_DELETE;
|
|
}
|
|
if (text.len >= 4 && text.ptr[1] == '[' && (text.ptr[2] == '1' || text.ptr[2] == '7') && text.ptr[3] == '~') {
|
|
return return_len ? 4u : ZERO_TERM_KEY_HOME;
|
|
}
|
|
if (text.len >= 4 && text.ptr[1] == '[' && (text.ptr[2] == '4' || text.ptr[2] == '8') && text.ptr[3] == '~') {
|
|
return return_len ? 4u : ZERO_TERM_KEY_END;
|
|
}
|
|
if (text.len >= 4 && text.ptr[1] == '[' && text.ptr[2] == '5' && text.ptr[3] == '~') {
|
|
return return_len ? 4u : ZERO_TERM_KEY_PAGE_UP;
|
|
}
|
|
if (text.len >= 4 && text.ptr[1] == '[' && text.ptr[2] == '6' && text.ptr[3] == '~') {
|
|
return return_len ? 4u : ZERO_TERM_KEY_PAGE_DOWN;
|
|
}
|
|
if (text.len >= 5 && text.ptr[1] == '[' && text.ptr[4] == '~') {
|
|
unsigned char a = text.ptr[2];
|
|
unsigned char b = text.ptr[3];
|
|
if (a == '1' && b == '1') return return_len ? 5u : ZERO_TERM_KEY_F1;
|
|
if (a == '1' && b == '2') return return_len ? 5u : ZERO_TERM_KEY_F2;
|
|
if (a == '1' && b == '3') return return_len ? 5u : ZERO_TERM_KEY_F3;
|
|
if (a == '1' && b == '4') return return_len ? 5u : ZERO_TERM_KEY_F4;
|
|
if (a == '1' && b == '5') return return_len ? 5u : ZERO_TERM_KEY_F5;
|
|
if (a == '1' && b == '7') return return_len ? 5u : ZERO_TERM_KEY_F6;
|
|
if (a == '1' && b == '8') return return_len ? 5u : ZERO_TERM_KEY_F7;
|
|
if (a == '1' && b == '9') return return_len ? 5u : ZERO_TERM_KEY_F8;
|
|
if (a == '2' && b == '0') return return_len ? 5u : ZERO_TERM_KEY_F9;
|
|
if (a == '2' && b == '1') return return_len ? 5u : ZERO_TERM_KEY_F10;
|
|
if (a == '2' && b == '3') return return_len ? 5u : ZERO_TERM_KEY_F11;
|
|
if (a == '2' && b == '4') return return_len ? 5u : ZERO_TERM_KEY_F12;
|
|
}
|
|
if (text.len >= 6 && text.ptr[1] == '[' && text.ptr[2] == '2' && text.ptr[3] == '0' && text.ptr[5] == '~') {
|
|
if (text.ptr[4] == '0') return return_len ? 6u : ZERO_TERM_KEY_PASTE_START;
|
|
if (text.ptr[4] == '1') return return_len ? 6u : ZERO_TERM_KEY_PASTE_END;
|
|
}
|
|
return 0;
|
|
}
|
|
if (first == '\r' || first == '\n') return return_len ? 1u : 13u;
|
|
if (first == '\t') return return_len ? 1u : 9u;
|
|
if (first == 0x7fu || first == 0x08u) return return_len ? 1u : 127u;
|
|
if (first < 32u) return return_len ? 1u : (uint32_t)first;
|
|
if (first < 128u) return return_len ? 1u : (uint32_t)first;
|
|
size_t width = 0;
|
|
uint32_t code = zero_term_utf8_key(text, &width);
|
|
if (!code) return 0;
|
|
return return_len ? (uint32_t)width : code;
|
|
}
|
|
|
|
static uint64_t zero_term_size_or(uint64_t fallback, int height) {
|
|
#if !defined(_WIN32) && defined(TIOCGWINSZ)
|
|
struct winsize size;
|
|
if (ioctl(ZERO_RUNTIME_STDOUT_FD, TIOCGWINSZ, &size) == 0) {
|
|
unsigned value = height ? size.ws_row : size.ws_col;
|
|
if (value > 0) return value;
|
|
}
|
|
#endif
|
|
return fallback;
|
|
}
|
|
|
|
#if defined(_WIN32)
|
|
static DWORD zero_term_original_mode;
|
|
static int zero_term_raw_active;
|
|
static int zero_term_restore_registered;
|
|
|
|
static void zero_term_restore_at_exit(void) {
|
|
if (!zero_term_raw_active) return;
|
|
HANDLE input = GetStdHandle(STD_INPUT_HANDLE);
|
|
if (input == INVALID_HANDLE_VALUE) return;
|
|
SetConsoleMode(input, zero_term_original_mode);
|
|
zero_term_raw_active = 0;
|
|
}
|
|
|
|
static uint64_t zero_term_enter_raw_mode(void) {
|
|
HANDLE input = GetStdHandle(STD_INPUT_HANDLE);
|
|
if (input == INVALID_HANDLE_VALUE) return 0;
|
|
DWORD mode = 0;
|
|
if (!GetConsoleMode(input, &mode)) return 0;
|
|
if (zero_term_raw_active) return 1;
|
|
DWORD raw = mode;
|
|
raw &= ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
|
|
raw |= ENABLE_VIRTUAL_TERMINAL_INPUT;
|
|
if (!SetConsoleMode(input, raw)) return 0;
|
|
zero_term_original_mode = mode;
|
|
zero_term_raw_active = 1;
|
|
if (!zero_term_restore_registered) {
|
|
atexit(zero_term_restore_at_exit);
|
|
zero_term_restore_registered = 1;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static uint64_t zero_term_leave_raw_mode(void) {
|
|
if (!zero_term_raw_active) return 1;
|
|
HANDLE input = GetStdHandle(STD_INPUT_HANDLE);
|
|
if (input == INVALID_HANDLE_VALUE) return 0;
|
|
if (!SetConsoleMode(input, zero_term_original_mode)) return 0;
|
|
zero_term_raw_active = 0;
|
|
return 1;
|
|
}
|
|
#else
|
|
static struct termios zero_term_original_mode;
|
|
static int zero_term_raw_active;
|
|
static int zero_term_restore_registered;
|
|
|
|
static void zero_term_restore_at_exit(void) {
|
|
if (!zero_term_raw_active) return;
|
|
tcsetattr(ZERO_RUNTIME_STDIN_FD, TCSAFLUSH, &zero_term_original_mode);
|
|
zero_term_raw_active = 0;
|
|
}
|
|
|
|
static uint64_t zero_term_enter_raw_mode(void) {
|
|
if (!ZERO_RUNTIME_ISATTY(ZERO_RUNTIME_STDIN_FD)) return 0;
|
|
struct termios mode;
|
|
if (tcgetattr(ZERO_RUNTIME_STDIN_FD, &mode) != 0) return 0;
|
|
if (zero_term_raw_active) return 1;
|
|
zero_term_original_mode = mode;
|
|
mode.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
|
|
mode.c_oflag &= ~(OPOST);
|
|
mode.c_cflag |= CS8;
|
|
#if defined(IEXTEN)
|
|
mode.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
|
|
#else
|
|
mode.c_lflag &= ~(ECHO | ICANON | ISIG);
|
|
#endif
|
|
mode.c_cc[VMIN] = 0;
|
|
mode.c_cc[VTIME] = 0;
|
|
if (tcsetattr(ZERO_RUNTIME_STDIN_FD, TCSAFLUSH, &mode) != 0) return 0;
|
|
zero_term_raw_active = 1;
|
|
if (!zero_term_restore_registered) {
|
|
atexit(zero_term_restore_at_exit);
|
|
zero_term_restore_registered = 1;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static uint64_t zero_term_leave_raw_mode(void) {
|
|
if (!zero_term_raw_active) return 1;
|
|
if (tcsetattr(ZERO_RUNTIME_STDIN_FD, TCSAFLUSH, &zero_term_original_mode) != 0) return 0;
|
|
zero_term_raw_active = 0;
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
static size_t zero_term_read_chunk_len(size_t len) {
|
|
return len > (size_t)UINT_MAX ? (size_t)UINT_MAX : len;
|
|
}
|
|
|
|
ZeroMaybeUsize zero_term_read_input(ZeroMutByteView buffer) {
|
|
if (!buffer.ptr || buffer.len == 0) return zero_runtime_none_usize();
|
|
size_t chunk = zero_term_read_chunk_len(buffer.len);
|
|
#if defined(_WIN32)
|
|
HANDLE input = GetStdHandle(STD_INPUT_HANDLE);
|
|
if (input == INVALID_HANDLE_VALUE) return zero_runtime_none_usize();
|
|
if (ZERO_RUNTIME_ISATTY(ZERO_RUNTIME_STDIN_FD)) {
|
|
if (!_kbhit()) return zero_runtime_none_usize();
|
|
int first = _getch();
|
|
if (first == 0 || first == 0xe0) {
|
|
if (!_kbhit()) return zero_runtime_none_usize();
|
|
int second = _getch();
|
|
const unsigned char *sequence = NULL;
|
|
size_t len = 0;
|
|
static const unsigned char up[] = {0x1b, '[', 'A'};
|
|
static const unsigned char down[] = {0x1b, '[', 'B'};
|
|
static const unsigned char right[] = {0x1b, '[', 'C'};
|
|
static const unsigned char left[] = {0x1b, '[', 'D'};
|
|
static const unsigned char del[] = {0x1b, '[', '3', '~'};
|
|
if (second == 72) { sequence = up; len = sizeof(up); }
|
|
else if (second == 80) { sequence = down; len = sizeof(down); }
|
|
else if (second == 77) { sequence = right; len = sizeof(right); }
|
|
else if (second == 75) { sequence = left; len = sizeof(left); }
|
|
else if (second == 83) { sequence = del; len = sizeof(del); }
|
|
if (!sequence || len > buffer.len) return zero_runtime_none_usize();
|
|
memcpy(buffer.ptr, sequence, len);
|
|
return (ZeroMaybeUsize){1, (uint64_t)len};
|
|
}
|
|
buffer.ptr[0] = (unsigned char)first;
|
|
return (ZeroMaybeUsize){1, 1};
|
|
}
|
|
DWORD type = GetFileType(input);
|
|
if (type == FILE_TYPE_PIPE) {
|
|
DWORD available = 0;
|
|
if (!PeekNamedPipe(input, NULL, 0, NULL, &available, NULL) || available == 0) return zero_runtime_none_usize();
|
|
if ((uint64_t)chunk > (uint64_t)available) chunk = (size_t)available;
|
|
} else if (type != FILE_TYPE_DISK) {
|
|
return zero_runtime_none_usize();
|
|
}
|
|
int got = ZERO_RUNTIME_READ(ZERO_RUNTIME_STDIN_FD, buffer.ptr, (unsigned)chunk);
|
|
if (got <= 0) return zero_runtime_none_usize();
|
|
return (ZeroMaybeUsize){1, (uint64_t)got};
|
|
#else
|
|
for (;;) {
|
|
fd_set read_set;
|
|
FD_ZERO(&read_set);
|
|
FD_SET(ZERO_RUNTIME_STDIN_FD, &read_set);
|
|
struct timeval timeout;
|
|
timeout.tv_sec = 0;
|
|
timeout.tv_usec = 0;
|
|
int ready = select(ZERO_RUNTIME_STDIN_FD + 1, &read_set, NULL, NULL, &timeout);
|
|
if (ready < 0) {
|
|
if (errno == EINTR) continue;
|
|
return zero_runtime_none_usize();
|
|
}
|
|
if (ready == 0) return zero_runtime_none_usize();
|
|
ZeroReadResult got = ZERO_RUNTIME_READ(ZERO_RUNTIME_STDIN_FD, buffer.ptr, (unsigned)chunk);
|
|
if (got < 0) {
|
|
if (errno == EINTR) continue;
|
|
if (errno == EAGAIN || errno == EWOULDBLOCK) return zero_runtime_none_usize();
|
|
return zero_runtime_none_usize();
|
|
}
|
|
if (got == 0) return zero_runtime_none_usize();
|
|
return (ZeroMaybeUsize){1, (uint64_t)got};
|
|
}
|
|
#endif
|
|
}
|
|
|
|
uint64_t zero_term_op(uint64_t fallback, uint32_t op) {
|
|
switch ((ZeroTermOp)op) {
|
|
case ZERO_TERM_OP_STDIN_IS_TTY:
|
|
return ZERO_RUNTIME_ISATTY(ZERO_RUNTIME_STDIN_FD) ? 1u : 0u;
|
|
case ZERO_TERM_OP_STDOUT_IS_TTY:
|
|
return ZERO_RUNTIME_ISATTY(ZERO_RUNTIME_STDOUT_FD) ? 1u : 0u;
|
|
case ZERO_TERM_OP_WIDTH_OR:
|
|
return zero_term_size_or(fallback, 0);
|
|
case ZERO_TERM_OP_HEIGHT_OR:
|
|
return zero_term_size_or(fallback, 1);
|
|
case ZERO_TERM_OP_ENTER_RAW_MODE:
|
|
return zero_term_enter_raw_mode();
|
|
case ZERO_TERM_OP_LEAVE_RAW_MODE:
|
|
return zero_term_leave_raw_mode();
|
|
default:
|
|
return fallback;
|
|
}
|
|
}
|
|
|
|
static int zero_parse_ascii_digit(unsigned char byte) {
|
|
return byte >= '0' && byte <= '9';
|
|
}
|
|
|
|
static int zero_parse_ascii_alpha(unsigned char byte) {
|
|
return (byte >= 'A' && byte <= 'Z') || (byte >= 'a' && byte <= 'z');
|
|
}
|
|
|
|
static int zero_parse_identifier_start(unsigned char byte) {
|
|
return zero_parse_ascii_alpha(byte) || byte == '_';
|
|
}
|
|
|
|
static int zero_parse_identifier_byte(unsigned char byte) {
|
|
return zero_parse_identifier_start(byte) || zero_parse_ascii_digit(byte);
|
|
}
|
|
|
|
static uint64_t zero_parse_u32_max(ZeroByteView text, uint32_t max) {
|
|
if (!zero_str_view_valid(text) || text.len == 0) return 0;
|
|
uint64_t total = 0;
|
|
for (size_t i = 0; i < text.len; i++) {
|
|
unsigned char byte = text.ptr[i];
|
|
if (!zero_parse_ascii_digit(byte)) return 0;
|
|
uint64_t digit = (uint64_t)(byte - '0');
|
|
if (total > ((uint64_t)max - digit) / 10u) return 0;
|
|
total = total * 10u + digit;
|
|
}
|
|
return (1ull << 32) | total;
|
|
}
|
|
|
|
uint64_t zero_parse_op(ZeroByteView text, uint32_t arg, uint32_t op) {
|
|
if (!zero_str_view_valid(text)) return 0;
|
|
unsigned char first = text.len > 0 ? text.ptr[0] : 0;
|
|
switch (op) {
|
|
case ZERO_PARSE_OP_IS_ASCII_DIGIT:
|
|
return text.len > 0 && zero_parse_ascii_digit(first) ? 1 : 0;
|
|
case ZERO_PARSE_OP_IS_ASCII_ALPHA:
|
|
return text.len > 0 && zero_parse_ascii_alpha(first) ? 1 : 0;
|
|
case ZERO_PARSE_OP_IS_IDENTIFIER_START:
|
|
return text.len > 0 && zero_parse_identifier_start(first) ? 1 : 0;
|
|
case ZERO_PARSE_OP_IS_WHITESPACE:
|
|
return text.len > 0 && zero_str_ascii_space(first) ? 1 : 0;
|
|
case ZERO_PARSE_OP_SCAN_DIGITS: {
|
|
size_t index = 0;
|
|
while (index < text.len && zero_parse_ascii_digit(text.ptr[index])) index++;
|
|
return index;
|
|
}
|
|
case ZERO_PARSE_OP_SCAN_IDENTIFIER: {
|
|
if (text.len == 0 || !zero_parse_identifier_start(first)) return 0;
|
|
size_t index = 1;
|
|
while (index < text.len && zero_parse_identifier_byte(text.ptr[index])) index++;
|
|
return index;
|
|
}
|
|
case ZERO_PARSE_OP_SCAN_UNTIL_BYTE: {
|
|
unsigned char target = (unsigned char)(arg & 0xffu);
|
|
for (size_t i = 0; i < text.len; i++) {
|
|
if (text.ptr[i] == target) return i;
|
|
}
|
|
return text.len;
|
|
}
|
|
case ZERO_PARSE_OP_SCAN_WHITESPACE: {
|
|
size_t index = 0;
|
|
while (index < text.len && zero_str_ascii_space(text.ptr[index])) index++;
|
|
return index;
|
|
}
|
|
case ZERO_PARSE_OP_PARSE_BOOL:
|
|
if (text.len == 4 && memcmp(text.ptr, "true", 4) == 0) return (1ull << 32) | 1u;
|
|
if (text.len == 5 && memcmp(text.ptr, "false", 5) == 0) return (1ull << 32);
|
|
return 0;
|
|
case ZERO_PARSE_OP_PARSE_U8:
|
|
return zero_parse_u32_max(text, UINT8_MAX);
|
|
case ZERO_PARSE_OP_PARSE_U16:
|
|
return zero_parse_u32_max(text, UINT16_MAX);
|
|
case ZERO_PARSE_OP_TERM_KEY_CODE:
|
|
return zero_term_key_decode(text, 0);
|
|
case ZERO_PARSE_OP_TERM_KEY_BYTE_LEN:
|
|
return zero_term_key_decode(text, 1);
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
ZeroMaybeUsize zero_parse_usize(ZeroByteView text) {
|
|
if (!zero_str_view_valid(text) || text.len == 0) return (ZeroMaybeUsize){0, 0};
|
|
uint64_t total = 0;
|
|
for (size_t i = 0; i < text.len; i++) {
|
|
unsigned char byte = text.ptr[i];
|
|
if (!zero_parse_ascii_digit(byte)) return (ZeroMaybeUsize){0, 0};
|
|
uint64_t digit = (uint64_t)(byte - '0');
|
|
if (total > (UINT64_MAX - digit) / 10u) return (ZeroMaybeUsize){0, 0};
|
|
total = total * 10u + digit;
|
|
}
|
|
return (ZeroMaybeUsize){1, total};
|
|
}
|
|
|
|
static int64_t zero_time_floor_div(int64_t value, int64_t divisor) {
|
|
int64_t quotient = value / divisor;
|
|
int64_t remainder = value % divisor;
|
|
if (value < 0 && remainder != 0) return quotient - 1;
|
|
return quotient;
|
|
}
|
|
|
|
static int64_t zero_time_sleep_ns(int64_t ns) {
|
|
if (ns <= 0) return 1;
|
|
#if defined(_WIN32)
|
|
uint64_t remaining_ms = ((uint64_t)ns + 999999u) / 1000000u;
|
|
while (remaining_ms > 0) {
|
|
DWORD chunk = remaining_ms > 0xffffffffu ? 0xffffffffu : (DWORD)remaining_ms;
|
|
Sleep(chunk);
|
|
remaining_ms -= chunk;
|
|
}
|
|
return 1;
|
|
#else
|
|
struct timespec req;
|
|
req.tv_sec = (time_t)(ns / 1000000000);
|
|
req.tv_nsec = (long)(ns % 1000000000);
|
|
while (nanosleep(&req, &req) != 0) {
|
|
if (errno == EINTR) continue;
|
|
return 0;
|
|
}
|
|
return 1;
|
|
#endif
|
|
}
|
|
|
|
static int64_t zero_time_wall_seconds(void) {
|
|
#if defined(_WIN32)
|
|
FILETIME ft;
|
|
GetSystemTimeAsFileTime(&ft);
|
|
uint64_t ticks = ((uint64_t)ft.dwHighDateTime << 32) | (uint64_t)ft.dwLowDateTime;
|
|
if (ticks < 116444736000000000ull) return 0;
|
|
return (int64_t)((ticks - 116444736000000000ull) / 10000000ull);
|
|
#else
|
|
return (int64_t)time(NULL);
|
|
#endif
|
|
}
|
|
|
|
#if defined(_WIN32)
|
|
static uint64_t zero_time_scale_qpc_remainder_ns(uint64_t remainder, uint64_t frequency) {
|
|
if (frequency == 0) return 0;
|
|
uint64_t quotient = 0;
|
|
uint64_t rem = 0;
|
|
uint64_t term_quotient = remainder / frequency;
|
|
uint64_t term_rem = remainder % frequency;
|
|
uint64_t multiplier = 1000000000ull;
|
|
while (multiplier > 0) {
|
|
if ((multiplier & 1ull) != 0) {
|
|
if (quotient > UINT64_MAX - term_quotient) return UINT64_MAX;
|
|
quotient += term_quotient;
|
|
uint64_t gap = frequency - term_rem;
|
|
if (term_rem != 0 && rem >= gap) {
|
|
if (quotient == UINT64_MAX) return UINT64_MAX;
|
|
quotient++;
|
|
rem -= gap;
|
|
} else {
|
|
rem += term_rem;
|
|
}
|
|
}
|
|
multiplier >>= 1;
|
|
if (multiplier == 0) break;
|
|
if (term_quotient > UINT64_MAX / 2u) return UINT64_MAX;
|
|
term_quotient *= 2u;
|
|
uint64_t gap = frequency - term_rem;
|
|
if (term_rem != 0 && term_rem >= gap) {
|
|
if (term_quotient == UINT64_MAX) return UINT64_MAX;
|
|
term_quotient++;
|
|
term_rem -= gap;
|
|
} else {
|
|
term_rem += term_rem;
|
|
}
|
|
}
|
|
return quotient;
|
|
}
|
|
#endif
|
|
|
|
static int64_t zero_time_monotonic_ns(void) {
|
|
#if defined(_WIN32)
|
|
LARGE_INTEGER frequency;
|
|
LARGE_INTEGER counter;
|
|
if (!QueryPerformanceFrequency(&frequency) || !QueryPerformanceCounter(&counter) || frequency.QuadPart <= 0 || counter.QuadPart < 0) return 0;
|
|
uint64_t qpc_frequency = (uint64_t)frequency.QuadPart;
|
|
uint64_t qpc_ticks = (uint64_t)counter.QuadPart;
|
|
uint64_t seconds = qpc_ticks / qpc_frequency;
|
|
uint64_t remainder_ns = zero_time_scale_qpc_remainder_ns(qpc_ticks % qpc_frequency, qpc_frequency);
|
|
if (seconds > (uint64_t)(INT64_MAX / 1000000000ll)) return INT64_MAX;
|
|
uint64_t whole_ns = seconds * 1000000000ull;
|
|
if (remainder_ns > (uint64_t)INT64_MAX - whole_ns) return INT64_MAX;
|
|
return (int64_t)(whole_ns + remainder_ns);
|
|
#else
|
|
struct timespec ts;
|
|
if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) return 0;
|
|
return (int64_t)ts.tv_sec * 1000000000ll + (int64_t)ts.tv_nsec;
|
|
#endif
|
|
}
|
|
|
|
int64_t zero_time_op(int64_t a, int64_t b, int64_t c, uint32_t op) {
|
|
switch (op) {
|
|
case ZERO_TIME_OP_AS_US_FLOOR:
|
|
return zero_time_floor_div(a, 1000);
|
|
case ZERO_TIME_OP_AS_MS_FLOOR:
|
|
return zero_time_floor_div(a, 1000000);
|
|
case ZERO_TIME_OP_AS_SECONDS_FLOOR:
|
|
return zero_time_floor_div(a, 1000000000);
|
|
case ZERO_TIME_OP_MIN:
|
|
return a < b ? a : b;
|
|
case ZERO_TIME_OP_MAX:
|
|
return a < b ? b : a;
|
|
case ZERO_TIME_OP_CLAMP: {
|
|
int64_t lower = b < c ? b : c;
|
|
int64_t upper = b < c ? c : b;
|
|
if (a < lower) return lower;
|
|
if (upper < a) return upper;
|
|
return a;
|
|
}
|
|
case ZERO_TIME_OP_SLEEP:
|
|
return zero_time_sleep_ns(a);
|
|
case ZERO_TIME_OP_WALL_SECONDS:
|
|
(void)a;
|
|
(void)b;
|
|
(void)c;
|
|
return zero_time_wall_seconds();
|
|
case ZERO_TIME_OP_MONOTONIC:
|
|
(void)a;
|
|
(void)b;
|
|
(void)c;
|
|
return zero_time_monotonic_ns();
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static uint64_t zero_math_clamp_u64(uint64_t value, uint64_t low, uint64_t high) {
|
|
uint64_t lower = low < high ? low : high;
|
|
uint64_t upper = low < high ? high : low;
|
|
if (value < lower) return lower;
|
|
if (upper < value) return upper;
|
|
return value;
|
|
}
|
|
|
|
static int64_t zero_math_clamp_i64(int64_t value, int64_t low, int64_t high) {
|
|
int64_t lower = low < high ? low : high;
|
|
int64_t upper = low < high ? high : low;
|
|
if (value < lower) return lower;
|
|
if (upper < value) return upper;
|
|
return value;
|
|
}
|
|
|
|
static uint64_t zero_math_some_u32(uint32_t value) {
|
|
return (1ull << 32) | (uint64_t)value;
|
|
}
|
|
|
|
static ZeroMaybeUsize zero_math_some_usize(uint64_t value) {
|
|
return (ZeroMaybeUsize){1, value};
|
|
}
|
|
|
|
static uint64_t zero_math_checked_add_u32(uint32_t left, uint32_t right) {
|
|
if (left > UINT32_MAX - right) return 0;
|
|
return zero_math_some_u32(left + right);
|
|
}
|
|
|
|
static uint64_t zero_math_checked_sub_u32(uint32_t left, uint32_t right) {
|
|
if (left < right) return 0;
|
|
return zero_math_some_u32(left - right);
|
|
}
|
|
|
|
static uint64_t zero_math_checked_mul_u32(uint32_t left, uint32_t right) {
|
|
if (left != 0 && right > UINT32_MAX / left) return 0;
|
|
return zero_math_some_u32(left * right);
|
|
}
|
|
|
|
static uint64_t zero_math_checked_add_i32(int32_t left, int32_t right) {
|
|
int64_t wide = (int64_t)left + (int64_t)right;
|
|
if (wide > INT32_MAX || wide < INT32_MIN) return 0;
|
|
return zero_math_some_u32((uint32_t)(int32_t)wide);
|
|
}
|
|
|
|
static uint64_t zero_math_checked_sub_i32(int32_t left, int32_t right) {
|
|
int64_t wide = (int64_t)left - (int64_t)right;
|
|
if (wide > INT32_MAX || wide < INT32_MIN) return 0;
|
|
return zero_math_some_u32((uint32_t)(int32_t)wide);
|
|
}
|
|
|
|
static uint64_t zero_math_checked_mul_i32(int32_t left, int32_t right) {
|
|
int64_t wide = (int64_t)left * (int64_t)right;
|
|
if (wide > INT32_MAX || wide < INT32_MIN) return 0;
|
|
return zero_math_some_u32((uint32_t)(int32_t)wide);
|
|
}
|
|
|
|
static uint32_t zero_math_gcd_u32(uint32_t a, uint32_t b) {
|
|
while (b != 0) {
|
|
uint32_t next = a % b;
|
|
a = b;
|
|
b = next;
|
|
}
|
|
return a;
|
|
}
|
|
|
|
static uint64_t zero_math_checked_lcm_u32(uint32_t left, uint32_t right) {
|
|
if (left == 0 || right == 0) return zero_math_some_u32(0);
|
|
uint32_t gcd = zero_math_gcd_u32(left, right);
|
|
return zero_math_checked_mul_u32(left / gcd, right);
|
|
}
|
|
|
|
static uint64_t zero_math_checked_pow_u32(uint32_t base, uint32_t exponent) {
|
|
uint32_t result = 1;
|
|
uint32_t current = base;
|
|
uint32_t exp = exponent;
|
|
while (exp > 0) {
|
|
if (exp % 2 == 1) {
|
|
uint64_t next_result = zero_math_checked_mul_u32(result, current);
|
|
if ((next_result >> 32) == 0) return 0;
|
|
result = (uint32_t)next_result;
|
|
}
|
|
exp = exp / 2;
|
|
if (exp > 0) {
|
|
uint64_t next_current = zero_math_checked_mul_u32(current, current);
|
|
if ((next_current >> 32) == 0) return 0;
|
|
current = (uint32_t)next_current;
|
|
}
|
|
}
|
|
return zero_math_some_u32(result);
|
|
}
|
|
|
|
static uint32_t zero_math_pow_u32(uint32_t base, uint32_t exponent) {
|
|
uint32_t result = 1;
|
|
uint32_t current = base;
|
|
uint32_t exp = exponent;
|
|
while (exp > 0) {
|
|
if (exp % 2 == 1) result = result * current;
|
|
current = current * current;
|
|
exp = exp / 2;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static uint32_t zero_math_mod_pow_u32(uint32_t base, uint32_t exponent, uint32_t modulus) {
|
|
if (modulus == 0) return 0;
|
|
uint64_t wide_modulus = modulus;
|
|
uint64_t result = 1ull % wide_modulus;
|
|
uint64_t current = ((uint64_t)base) % wide_modulus;
|
|
uint32_t exp = exponent;
|
|
while (exp > 0) {
|
|
if (exp % 2 == 1) result = result * current % wide_modulus;
|
|
current = current * current % wide_modulus;
|
|
exp = exp / 2;
|
|
}
|
|
return (uint32_t)result;
|
|
}
|
|
|
|
static uint32_t zero_math_is_prime_u32(uint32_t value) {
|
|
if (value < 2) return 0;
|
|
if (value == 2) return 1;
|
|
if (value % 2 == 0) return 0;
|
|
uint32_t divisor = 3;
|
|
while (divisor <= value / divisor) {
|
|
if (value % divisor == 0) return 0;
|
|
divisor += 2;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static uint32_t zero_math_sqrt_floor_u32(uint32_t value) {
|
|
uint32_t low = 0;
|
|
uint32_t high = 65536;
|
|
while (low + 1 < high) {
|
|
uint32_t mid = low + (high - low) / 2;
|
|
if (mid <= value / mid) low = mid;
|
|
else high = mid;
|
|
}
|
|
return low;
|
|
}
|
|
|
|
static uint64_t zero_math_factorial_u32(uint32_t value) {
|
|
uint32_t result = 1;
|
|
uint32_t next = 2;
|
|
while (next <= value) {
|
|
uint64_t multiplied = zero_math_checked_mul_u32(result, next);
|
|
if ((multiplied >> 32) == 0) return 0;
|
|
result = (uint32_t)multiplied;
|
|
next++;
|
|
}
|
|
return zero_math_some_u32(result);
|
|
}
|
|
|
|
static uint64_t zero_math_binomial_u32(uint32_t n, uint32_t k0) {
|
|
if (k0 > n) return 0;
|
|
uint32_t k = k0;
|
|
if (k > n - k) k = n - k;
|
|
uint32_t result = 1;
|
|
uint32_t i = 1;
|
|
while (i <= k) {
|
|
uint32_t numerator = n - k + i;
|
|
uint32_t denominator = i;
|
|
uint32_t numerator_gcd = zero_math_gcd_u32(numerator, denominator);
|
|
numerator = numerator / numerator_gcd;
|
|
denominator = denominator / numerator_gcd;
|
|
uint32_t result_gcd = zero_math_gcd_u32(result, denominator);
|
|
result = result / result_gcd;
|
|
denominator = denominator / result_gcd;
|
|
if (denominator != 1) return 0;
|
|
uint64_t multiplied = zero_math_checked_mul_u32(result, numerator);
|
|
if ((multiplied >> 32) == 0) return 0;
|
|
result = (uint32_t)multiplied;
|
|
i++;
|
|
}
|
|
return zero_math_some_u32(result);
|
|
}
|
|
|
|
static uint32_t zero_math_divisor_count_u32(uint32_t value) {
|
|
if (value == 0) return 0;
|
|
uint32_t count = 0;
|
|
uint32_t divisor = 1;
|
|
while (divisor <= value / divisor) {
|
|
if (value % divisor == 0) {
|
|
uint32_t paired = value / divisor;
|
|
count += paired == divisor ? 1u : 2u;
|
|
}
|
|
divisor++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static uint32_t zero_math_proper_divisor_sum_u32(uint32_t value) {
|
|
if (value <= 1) return 0;
|
|
uint32_t sum = 1;
|
|
uint32_t divisor = 2;
|
|
while (divisor <= value / divisor) {
|
|
if (value % divisor == 0) {
|
|
uint32_t paired = value / divisor;
|
|
sum += divisor;
|
|
if (paired != divisor) sum += paired;
|
|
}
|
|
divisor++;
|
|
}
|
|
return sum;
|
|
}
|
|
|
|
static ZeroMaybeUsize zero_math_checked_add_usize(uint64_t left, uint64_t right) {
|
|
if (left > UINT64_MAX - right) return (ZeroMaybeUsize){0, 0};
|
|
return zero_math_some_usize(left + right);
|
|
}
|
|
|
|
static ZeroMaybeUsize zero_math_checked_sub_usize(uint64_t left, uint64_t right) {
|
|
if (left < right) return (ZeroMaybeUsize){0, 0};
|
|
return zero_math_some_usize(left - right);
|
|
}
|
|
|
|
static ZeroMaybeUsize zero_math_checked_mul_usize(uint64_t left, uint64_t right) {
|
|
if (left != 0 && right > UINT64_MAX / left) return (ZeroMaybeUsize){0, 0};
|
|
return zero_math_some_usize(left * right);
|
|
}
|
|
|
|
ZeroMaybeUsize zero_math_usize_op(uint64_t a, uint64_t b, uint64_t c, uint32_t op) {
|
|
(void)c;
|
|
switch (op) {
|
|
case ZERO_MATH_OP_CHECKED_ADD_USIZE:
|
|
return zero_math_checked_add_usize(a, b);
|
|
case ZERO_MATH_OP_CHECKED_SUB_USIZE:
|
|
return zero_math_checked_sub_usize(a, b);
|
|
case ZERO_MATH_OP_CHECKED_MUL_USIZE:
|
|
return zero_math_checked_mul_usize(a, b);
|
|
default:
|
|
return (ZeroMaybeUsize){0, 0};
|
|
}
|
|
}
|
|
|
|
uint64_t zero_math_op(int64_t a, int64_t b, int64_t c, uint32_t op) {
|
|
switch (op) {
|
|
case ZERO_MATH_OP_MIN_I32: {
|
|
int32_t left = (int32_t)a;
|
|
int32_t right = (int32_t)b;
|
|
return (uint64_t)(int64_t)(left < right ? left : right);
|
|
}
|
|
case ZERO_MATH_OP_MAX_I32: {
|
|
int32_t left = (int32_t)a;
|
|
int32_t right = (int32_t)b;
|
|
return (uint64_t)(int64_t)(left > right ? left : right);
|
|
}
|
|
case ZERO_MATH_OP_CLAMP_I32: {
|
|
int32_t value = (int32_t)a;
|
|
int32_t low = (int32_t)b;
|
|
int32_t high = (int32_t)c;
|
|
int32_t lower = low < high ? low : high;
|
|
int32_t upper = low < high ? high : low;
|
|
if (value < lower) return (uint64_t)(int64_t)lower;
|
|
if (upper < value) return (uint64_t)(int64_t)upper;
|
|
return (uint64_t)(int64_t)value;
|
|
}
|
|
case ZERO_MATH_OP_MIN_I64:
|
|
return (uint64_t)(a < b ? a : b);
|
|
case ZERO_MATH_OP_MAX_I64:
|
|
return (uint64_t)(a > b ? a : b);
|
|
case ZERO_MATH_OP_CLAMP_I64:
|
|
return (uint64_t)zero_math_clamp_i64(a, b, c);
|
|
case ZERO_MATH_OP_MIN_U32: {
|
|
uint32_t left = (uint32_t)a;
|
|
uint32_t right = (uint32_t)b;
|
|
return left < right ? left : right;
|
|
}
|
|
case ZERO_MATH_OP_MAX_U32: {
|
|
uint32_t left = (uint32_t)a;
|
|
uint32_t right = (uint32_t)b;
|
|
return left > right ? left : right;
|
|
}
|
|
case ZERO_MATH_OP_CLAMP_U32:
|
|
return (uint32_t)zero_math_clamp_u64((uint32_t)a, (uint32_t)b, (uint32_t)c);
|
|
case ZERO_MATH_OP_MIN_U64:
|
|
case ZERO_MATH_OP_MIN_USIZE: {
|
|
uint64_t left = (uint64_t)a;
|
|
uint64_t right = (uint64_t)b;
|
|
return left < right ? left : right;
|
|
}
|
|
case ZERO_MATH_OP_MAX_U64:
|
|
case ZERO_MATH_OP_MAX_USIZE: {
|
|
uint64_t left = (uint64_t)a;
|
|
uint64_t right = (uint64_t)b;
|
|
return left > right ? left : right;
|
|
}
|
|
case ZERO_MATH_OP_CLAMP_U64:
|
|
case ZERO_MATH_OP_CLAMP_USIZE:
|
|
return zero_math_clamp_u64((uint64_t)a, (uint64_t)b, (uint64_t)c);
|
|
case ZERO_MATH_OP_ABS_I32: {
|
|
int32_t value = (int32_t)a;
|
|
if (value == INT32_MIN) return 2147483648u;
|
|
return value < 0 ? (uint32_t)(-value) : (uint32_t)value;
|
|
}
|
|
case ZERO_MATH_OP_ABS_I64:
|
|
if (a == INT64_MIN) return 9223372036854775808ull;
|
|
return a < 0 ? (uint64_t)(-a) : (uint64_t)a;
|
|
case ZERO_MATH_OP_CHECKED_ADD_U32:
|
|
return zero_math_checked_add_u32((uint32_t)a, (uint32_t)b);
|
|
case ZERO_MATH_OP_CHECKED_SUB_U32:
|
|
return zero_math_checked_sub_u32((uint32_t)a, (uint32_t)b);
|
|
case ZERO_MATH_OP_CHECKED_MUL_U32:
|
|
return zero_math_checked_mul_u32((uint32_t)a, (uint32_t)b);
|
|
case ZERO_MATH_OP_SATURATING_ADD_U32: {
|
|
uint64_t result = zero_math_checked_add_u32((uint32_t)a, (uint32_t)b);
|
|
return (result >> 32) ? (uint32_t)result : UINT32_MAX;
|
|
}
|
|
case ZERO_MATH_OP_SATURATING_SUB_U32: {
|
|
uint64_t result = zero_math_checked_sub_u32((uint32_t)a, (uint32_t)b);
|
|
return (result >> 32) ? (uint32_t)result : 0;
|
|
}
|
|
case ZERO_MATH_OP_SATURATING_MUL_U32: {
|
|
uint64_t result = zero_math_checked_mul_u32((uint32_t)a, (uint32_t)b);
|
|
return (result >> 32) ? (uint32_t)result : UINT32_MAX;
|
|
}
|
|
case ZERO_MATH_OP_CHECKED_ADD_I32:
|
|
return zero_math_checked_add_i32((int32_t)a, (int32_t)b);
|
|
case ZERO_MATH_OP_CHECKED_SUB_I32:
|
|
return zero_math_checked_sub_i32((int32_t)a, (int32_t)b);
|
|
case ZERO_MATH_OP_CHECKED_MUL_I32:
|
|
return zero_math_checked_mul_i32((int32_t)a, (int32_t)b);
|
|
case ZERO_MATH_OP_SATURATING_ADD_I32: {
|
|
int64_t wide = (int64_t)(int32_t)a + (int64_t)(int32_t)b;
|
|
if (wide > INT32_MAX) return (uint32_t)INT32_MAX;
|
|
if (wide < INT32_MIN) return (uint64_t)(int64_t)INT32_MIN;
|
|
return (uint64_t)(int64_t)(int32_t)wide;
|
|
}
|
|
case ZERO_MATH_OP_SATURATING_SUB_I32: {
|
|
int64_t wide = (int64_t)(int32_t)a - (int64_t)(int32_t)b;
|
|
if (wide > INT32_MAX) return (uint32_t)INT32_MAX;
|
|
if (wide < INT32_MIN) return (uint64_t)(int64_t)INT32_MIN;
|
|
return (uint64_t)(int64_t)(int32_t)wide;
|
|
}
|
|
case ZERO_MATH_OP_SATURATING_MUL_I32: {
|
|
int32_t left = (int32_t)a;
|
|
int32_t right = (int32_t)b;
|
|
int64_t wide = (int64_t)left * (int64_t)right;
|
|
if (wide > INT32_MAX) return (uint32_t)INT32_MAX;
|
|
if (wide < INT32_MIN) return (uint64_t)(int64_t)INT32_MIN;
|
|
return (uint64_t)(int64_t)(int32_t)wide;
|
|
}
|
|
case ZERO_MATH_OP_GCD_U32:
|
|
return zero_math_gcd_u32((uint32_t)a, (uint32_t)b);
|
|
case ZERO_MATH_OP_LCM_U32: {
|
|
uint32_t left = (uint32_t)a;
|
|
uint32_t right = (uint32_t)b;
|
|
if (left == 0 || right == 0) return 0;
|
|
return (uint32_t)((left / zero_math_gcd_u32(left, right)) * right);
|
|
}
|
|
case ZERO_MATH_OP_CHECKED_LCM_U32:
|
|
return zero_math_checked_lcm_u32((uint32_t)a, (uint32_t)b);
|
|
case ZERO_MATH_OP_POW_U32:
|
|
return zero_math_pow_u32((uint32_t)a, (uint32_t)b);
|
|
case ZERO_MATH_OP_CHECKED_POW_U32:
|
|
return zero_math_checked_pow_u32((uint32_t)a, (uint32_t)b);
|
|
case ZERO_MATH_OP_MOD_POW_U32:
|
|
return zero_math_mod_pow_u32((uint32_t)a, (uint32_t)b, (uint32_t)c);
|
|
case ZERO_MATH_OP_IS_PRIME_U32:
|
|
return zero_math_is_prime_u32((uint32_t)a);
|
|
case ZERO_MATH_OP_SQRT_FLOOR_U32:
|
|
return zero_math_sqrt_floor_u32((uint32_t)a);
|
|
case ZERO_MATH_OP_FACTORIAL_U32:
|
|
return zero_math_factorial_u32((uint32_t)a);
|
|
case ZERO_MATH_OP_BINOMIAL_U32:
|
|
return zero_math_binomial_u32((uint32_t)a, (uint32_t)b);
|
|
case ZERO_MATH_OP_DIVISOR_COUNT_U32:
|
|
return zero_math_divisor_count_u32((uint32_t)a);
|
|
case ZERO_MATH_OP_PROPER_DIVISOR_SUM_U32:
|
|
return zero_math_proper_divisor_sum_u32((uint32_t)a);
|
|
case ZERO_MATH_OP_SATURATING_ADD_USIZE: {
|
|
ZeroMaybeUsize result = zero_math_checked_add_usize((uint64_t)a, (uint64_t)b);
|
|
return result.has ? result.value : UINT64_MAX;
|
|
}
|
|
case ZERO_MATH_OP_SATURATING_SUB_USIZE: {
|
|
ZeroMaybeUsize result = zero_math_checked_sub_usize((uint64_t)a, (uint64_t)b);
|
|
return result.has ? result.value : 0;
|
|
}
|
|
case ZERO_MATH_OP_SATURATING_MUL_USIZE: {
|
|
ZeroMaybeUsize result = zero_math_checked_mul_usize((uint64_t)a, (uint64_t)b);
|
|
return result.has ? result.value : UINT64_MAX;
|
|
}
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static size_t zero_search_lower_bound_i32(const int32_t *items, size_t len, int32_t needle) {
|
|
size_t low = 0;
|
|
size_t high = len;
|
|
while (low < high) {
|
|
size_t mid = low + (high - low) / 2u;
|
|
if (items[mid] < needle) low = mid + 1u;
|
|
else high = mid;
|
|
}
|
|
return low;
|
|
}
|
|
|
|
static size_t zero_search_upper_bound_i32(const int32_t *items, size_t len, int32_t needle) {
|
|
size_t low = 0;
|
|
size_t high = len;
|
|
while (low < high) {
|
|
size_t mid = low + (high - low) / 2u;
|
|
if (items[mid] <= needle) low = mid + 1u;
|
|
else high = mid;
|
|
}
|
|
return low;
|
|
}
|
|
|
|
static size_t zero_search_lower_bound_u32(const uint32_t *items, size_t len, uint32_t needle) {
|
|
size_t low = 0;
|
|
size_t high = len;
|
|
while (low < high) {
|
|
size_t mid = low + (high - low) / 2u;
|
|
if (items[mid] < needle) low = mid + 1u;
|
|
else high = mid;
|
|
}
|
|
return low;
|
|
}
|
|
|
|
static size_t zero_search_upper_bound_u32(const uint32_t *items, size_t len, uint32_t needle) {
|
|
size_t low = 0;
|
|
size_t high = len;
|
|
while (low < high) {
|
|
size_t mid = low + (high - low) / 2u;
|
|
if (items[mid] <= needle) low = mid + 1u;
|
|
else high = mid;
|
|
}
|
|
return low;
|
|
}
|
|
|
|
static size_t zero_search_lower_bound_usize(const uint64_t *items, size_t len, uint64_t needle) {
|
|
size_t low = 0;
|
|
size_t high = len;
|
|
while (low < high) {
|
|
size_t mid = low + (high - low) / 2u;
|
|
if (items[mid] < needle) low = mid + 1u;
|
|
else high = mid;
|
|
}
|
|
return low;
|
|
}
|
|
|
|
static size_t zero_search_upper_bound_usize(const uint64_t *items, size_t len, uint64_t needle) {
|
|
size_t low = 0;
|
|
size_t high = len;
|
|
while (low < high) {
|
|
size_t mid = low + (high - low) / 2u;
|
|
if (items[mid] <= needle) low = mid + 1u;
|
|
else high = mid;
|
|
}
|
|
return low;
|
|
}
|
|
|
|
uint64_t zero_search_op(ZeroByteView items, int64_t needle, uint32_t op) {
|
|
if (!zero_str_view_valid(items)) return 0;
|
|
switch (op) {
|
|
case ZERO_SEARCH_OP_LOWER_BOUND_I32:
|
|
return zero_search_lower_bound_i32((const int32_t *)items.ptr, items.len, (int32_t)needle);
|
|
case ZERO_SEARCH_OP_BINARY_I32: {
|
|
size_t index = zero_search_lower_bound_i32((const int32_t *)items.ptr, items.len, (int32_t)needle);
|
|
return index < items.len && ((const int32_t *)items.ptr)[index] == (int32_t)needle ? index : items.len;
|
|
}
|
|
case ZERO_SEARCH_OP_LOWER_BOUND_U32:
|
|
return zero_search_lower_bound_u32((const uint32_t *)items.ptr, items.len, (uint32_t)needle);
|
|
case ZERO_SEARCH_OP_BINARY_U32: {
|
|
size_t index = zero_search_lower_bound_u32((const uint32_t *)items.ptr, items.len, (uint32_t)needle);
|
|
return index < items.len && ((const uint32_t *)items.ptr)[index] == (uint32_t)needle ? index : items.len;
|
|
}
|
|
case ZERO_SEARCH_OP_LOWER_BOUND_USIZE:
|
|
return zero_search_lower_bound_usize((const uint64_t *)items.ptr, items.len, (uint64_t)needle);
|
|
case ZERO_SEARCH_OP_BINARY_USIZE: {
|
|
size_t index = zero_search_lower_bound_usize((const uint64_t *)items.ptr, items.len, (uint64_t)needle);
|
|
return index < items.len && ((const uint64_t *)items.ptr)[index] == (uint64_t)needle ? index : items.len;
|
|
}
|
|
case ZERO_SEARCH_OP_UPPER_BOUND_I32:
|
|
return zero_search_upper_bound_i32((const int32_t *)items.ptr, items.len, (int32_t)needle);
|
|
case ZERO_SEARCH_OP_UPPER_BOUND_U32:
|
|
return zero_search_upper_bound_u32((const uint32_t *)items.ptr, items.len, (uint32_t)needle);
|
|
case ZERO_SEARCH_OP_UPPER_BOUND_USIZE:
|
|
return zero_search_upper_bound_usize((const uint64_t *)items.ptr, items.len, (uint64_t)needle);
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static void zero_sort_insertion_i32(int32_t *items, size_t len) {
|
|
for (size_t index = 1; index < len; index++) {
|
|
int32_t value = items[index];
|
|
size_t cursor = index;
|
|
while (cursor > 0 && items[cursor - 1u] > value) {
|
|
items[cursor] = items[cursor - 1u];
|
|
cursor--;
|
|
}
|
|
items[cursor] = value;
|
|
}
|
|
}
|
|
|
|
static void zero_sort_insertion_u32(uint32_t *items, size_t len) {
|
|
for (size_t index = 1; index < len; index++) {
|
|
uint32_t value = items[index];
|
|
size_t cursor = index;
|
|
while (cursor > 0 && items[cursor - 1u] > value) {
|
|
items[cursor] = items[cursor - 1u];
|
|
cursor--;
|
|
}
|
|
items[cursor] = value;
|
|
}
|
|
}
|
|
|
|
static void zero_sort_insertion_usize(uint64_t *items, size_t len) {
|
|
for (size_t index = 1; index < len; index++) {
|
|
uint64_t value = items[index];
|
|
size_t cursor = index;
|
|
while (cursor > 0 && items[cursor - 1u] > value) {
|
|
items[cursor] = items[cursor - 1u];
|
|
cursor--;
|
|
}
|
|
items[cursor] = value;
|
|
}
|
|
}
|
|
|
|
void zero_sort_op(ZeroMutByteView items, uint32_t op) {
|
|
if ((!items.ptr && items.len > 0)) return;
|
|
switch (op) {
|
|
case ZERO_SORT_OP_INSERTION_I32:
|
|
zero_sort_insertion_i32((int32_t *)items.ptr, items.len);
|
|
break;
|
|
case ZERO_SORT_OP_INSERTION_U32:
|
|
zero_sort_insertion_u32((uint32_t *)items.ptr, items.len);
|
|
break;
|
|
case ZERO_SORT_OP_INSERTION_USIZE:
|
|
zero_sort_insertion_usize((uint64_t *)items.ptr, items.len);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
uint32_t zero_sort_is_sorted_op(ZeroByteView items, uint32_t op) {
|
|
if (!zero_str_view_valid(items)) return 0;
|
|
switch (op) {
|
|
case ZERO_SORT_OP_IS_SORTED_I32: {
|
|
const int32_t *values = (const int32_t *)items.ptr;
|
|
for (size_t i = 1; i < items.len; i++) if (values[i] < values[i - 1u]) return 0;
|
|
return 1;
|
|
}
|
|
case ZERO_SORT_OP_IS_SORTED_U32: {
|
|
const uint32_t *values = (const uint32_t *)items.ptr;
|
|
for (size_t i = 1; i < items.len; i++) if (values[i] < values[i - 1u]) return 0;
|
|
return 1;
|
|
}
|
|
case ZERO_SORT_OP_IS_SORTED_USIZE: {
|
|
const uint64_t *values = (const uint64_t *)items.ptr;
|
|
for (size_t i = 1; i < items.len; i++) if (values[i] < values[i - 1u]) return 0;
|
|
return 1;
|
|
}
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static int zero_str_eq_at(ZeroByteView text, size_t start, ZeroByteView needle) {
|
|
if (start > text.len || needle.len > text.len - start) return 0;
|
|
for (size_t i = 0; i < needle.len; i++) {
|
|
if (text.ptr[start + i] != needle.ptr[i]) return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
uint32_t zero_str_buffer_op(ZeroMutByteView buffer, ZeroByteView text, uint32_t op) {
|
|
if (!buffer.ptr || !zero_str_view_valid(text) || text.len > buffer.len) return 0;
|
|
uint32_t some_len = zero_str_some_len(text.len);
|
|
if (!some_len) return 0;
|
|
for (size_t i = 0; i < text.len; i++) {
|
|
unsigned char byte = text.ptr[i];
|
|
switch (op) {
|
|
case ZERO_STR_OP_REVERSE:
|
|
byte = text.ptr[text.len - 1u - i];
|
|
break;
|
|
case ZERO_STR_OP_COPY:
|
|
break;
|
|
case ZERO_STR_OP_TO_LOWER_ASCII:
|
|
byte = zero_str_lower(byte);
|
|
break;
|
|
case ZERO_STR_OP_TO_UPPER_ASCII:
|
|
byte = zero_str_upper(byte);
|
|
break;
|
|
default:
|
|
return 0;
|
|
}
|
|
buffer.ptr[i] = byte;
|
|
}
|
|
return some_len;
|
|
}
|
|
|
|
uint32_t zero_str_concat(ZeroMutByteView buffer, ZeroByteView left, ZeroByteView right) {
|
|
if (!buffer.ptr || !zero_str_view_valid(left) || !zero_str_view_valid(right)) return 0;
|
|
if (left.len > buffer.len || right.len > buffer.len - left.len) return 0;
|
|
uint32_t some_len = zero_str_some_len(left.len + right.len);
|
|
if (!some_len) return 0;
|
|
if (left.len > 0) memcpy(buffer.ptr, left.ptr, left.len);
|
|
if (right.len > 0) memcpy(buffer.ptr + left.len, right.ptr, right.len);
|
|
return some_len;
|
|
}
|
|
|
|
uint32_t zero_str_repeat(ZeroMutByteView buffer, ZeroByteView text, uint64_t count) {
|
|
if (!buffer.ptr || !zero_str_view_valid(text)) return 0;
|
|
if (text.len > 0 && count > (uint64_t)(buffer.len / text.len)) return 0;
|
|
uint64_t total64 = (uint64_t)text.len * count;
|
|
if (total64 > UINT32_MAX - 1u) return 0;
|
|
size_t total = (size_t)total64;
|
|
uint32_t some_len = zero_str_some_len(total);
|
|
if (!some_len) return 0;
|
|
size_t out = 0;
|
|
for (uint64_t r = 0; r < count; r++) {
|
|
if (text.len > 0) memcpy(buffer.ptr + out, text.ptr, text.len);
|
|
out += text.len;
|
|
}
|
|
return some_len;
|
|
}
|
|
|
|
uint64_t zero_str_trim_op(ZeroByteView text, uint32_t op) {
|
|
if (!zero_str_view_valid(text) || text.len > UINT32_MAX) return 0;
|
|
size_t start = 0;
|
|
size_t end = text.len;
|
|
if (op == ZERO_STR_OP_PATH_BASENAME ||
|
|
op == ZERO_STR_OP_PATH_DIRNAME ||
|
|
op == ZERO_STR_OP_PATH_EXTENSION) {
|
|
while (end > 0 && (text.ptr[end - 1u] == '/' || text.ptr[end - 1u] == '\\')) end--;
|
|
if (op == ZERO_STR_OP_PATH_BASENAME) {
|
|
start = end;
|
|
while (start > 0 && text.ptr[start - 1u] != '/' && text.ptr[start - 1u] != '\\') start--;
|
|
return ((uint64_t)(uint32_t)start << 32) | (uint32_t)(end - start);
|
|
}
|
|
if (op == ZERO_STR_OP_PATH_DIRNAME) {
|
|
size_t root_len = text.len > 0 && (text.ptr[0] == '/' || text.ptr[0] == '\\') ? 1u : 0u;
|
|
if (root_len > 0 && end == 0) return (uint32_t)root_len;
|
|
size_t split = end;
|
|
while (split > 0 && text.ptr[split - 1u] != '/' && text.ptr[split - 1u] != '\\') split--;
|
|
while (split > root_len && (text.ptr[split - 1u] == '/' || text.ptr[split - 1u] == '\\')) split--;
|
|
return (uint32_t)split;
|
|
}
|
|
start = end;
|
|
while (start > 0 && text.ptr[start - 1u] != '/' && text.ptr[start - 1u] != '\\') start--;
|
|
size_t dot = end;
|
|
size_t scan = end;
|
|
while (scan > start) {
|
|
scan--;
|
|
if (text.ptr[scan] == '.') {
|
|
dot = scan;
|
|
scan = start;
|
|
}
|
|
}
|
|
if (dot == end || dot == start) return ((uint64_t)(uint32_t)end << 32);
|
|
return ((uint64_t)(uint32_t)(dot + 1u) << 32) | (uint32_t)(end - dot - 1u);
|
|
}
|
|
if (op == ZERO_STR_OP_PARSE_TOKEN_ASCII) {
|
|
while (start < end && zero_str_ascii_space(text.ptr[start])) start++;
|
|
size_t token_end = start;
|
|
while (token_end < end && !zero_str_ascii_space(text.ptr[token_end])) token_end++;
|
|
return ((uint64_t)(uint32_t)start << 32) | (uint32_t)(token_end - start);
|
|
}
|
|
if (op == ZERO_STR_OP_TRIM_ASCII || op == ZERO_STR_OP_TRIM_START_ASCII) {
|
|
while (start < end && zero_str_ascii_space(text.ptr[start])) start++;
|
|
}
|
|
if (op == ZERO_STR_OP_TRIM_ASCII || op == ZERO_STR_OP_TRIM_END_ASCII) {
|
|
while (end > start && zero_str_ascii_space(text.ptr[end - 1u])) end--;
|
|
}
|
|
return ((uint64_t)(uint32_t)start << 32) | (uint32_t)(end - start);
|
|
}
|
|
|
|
uint64_t zero_str_pair_op(ZeroByteView text, ZeroByteView needle, uint32_t op) {
|
|
if (!zero_str_view_valid(text) || !zero_str_view_valid(needle)) return 0;
|
|
switch (op) {
|
|
case ZERO_STR_OP_STARTS_WITH:
|
|
return needle.len <= text.len && zero_str_eq_at(text, 0, needle) ? 1 : 0;
|
|
case ZERO_STR_OP_ENDS_WITH:
|
|
return needle.len <= text.len && zero_str_eq_at(text, text.len - needle.len, needle) ? 1 : 0;
|
|
case ZERO_STR_OP_CONTAINS:
|
|
return zero_str_pair_op(text, needle, ZERO_STR_OP_INDEX_OF) != text.len || needle.len == 0 ? 1 : 0;
|
|
case ZERO_STR_OP_INDEX_OF:
|
|
if (needle.len == 0) return 0;
|
|
if (needle.len > text.len) return text.len;
|
|
for (size_t start = 0; start <= text.len - needle.len; start++) {
|
|
if (zero_str_eq_at(text, start, needle)) return start;
|
|
}
|
|
return text.len;
|
|
case ZERO_STR_OP_LAST_INDEX_OF:
|
|
if (needle.len == 0 || needle.len > text.len) return text.len;
|
|
for (size_t start = text.len - needle.len + 1u; start > 0; start--) {
|
|
size_t candidate = start - 1u;
|
|
if (zero_str_eq_at(text, candidate, needle)) return candidate;
|
|
}
|
|
return text.len;
|
|
case ZERO_STR_OP_COUNT: {
|
|
if (needle.len == 0) return text.len + 1u;
|
|
size_t index = 0;
|
|
uint64_t count = 0;
|
|
while (index <= text.len) {
|
|
ZeroByteView rest = { text.ptr ? text.ptr + index : NULL, text.len - index };
|
|
uint64_t found = zero_str_pair_op(rest, needle, ZERO_STR_OP_INDEX_OF);
|
|
if (found == rest.len) return count;
|
|
count++;
|
|
index += (size_t)found + needle.len;
|
|
}
|
|
return count;
|
|
}
|
|
case ZERO_STR_OP_EQL_IGNORE_ASCII_CASE:
|
|
if (text.len != needle.len) return 0;
|
|
for (size_t i = 0; i < text.len; i++) {
|
|
if (zero_str_lower(text.ptr[i]) != zero_str_lower(needle.ptr[i])) return 0;
|
|
}
|
|
return 1;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
uint64_t zero_str_count_byte(ZeroByteView text, uint32_t byte) {
|
|
if (!zero_str_view_valid(text)) return 0;
|
|
uint64_t count = 0;
|
|
unsigned char target = (unsigned char)(byte & 0xffu);
|
|
for (size_t i = 0; i < text.len; i++) {
|
|
if (text.ptr[i] == target) count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
uint64_t zero_str_word_count_ascii(ZeroByteView text) {
|
|
if (!zero_str_view_valid(text)) return 0;
|
|
uint64_t count = 0;
|
|
int in_word = 0;
|
|
for (size_t i = 0; i < text.len; i++) {
|
|
if (zero_str_ascii_space(text.ptr[i])) {
|
|
in_word = 0;
|
|
} else if (!in_word) {
|
|
count++;
|
|
in_word = 1;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
typedef struct {
|
|
const unsigned char *ptr;
|
|
size_t len;
|
|
size_t pos;
|
|
size_t tokens;
|
|
size_t error_pos;
|
|
} ZeroJsonScanner;
|
|
|
|
static int zero_json_fail(ZeroJsonScanner *scanner, size_t pos) {
|
|
if (scanner) scanner->error_pos = pos;
|
|
return 0;
|
|
}
|
|
|
|
static int zero_json_hex_value(unsigned char ch) {
|
|
if (ch >= '0' && ch <= '9') return (int)(ch - '0');
|
|
if (ch >= 'a' && ch <= 'f') return (int)(ch - 'a' + 10);
|
|
if (ch >= 'A' && ch <= 'F') return (int)(ch - 'A' + 10);
|
|
return -1;
|
|
}
|
|
|
|
static int zero_json_hex4_value(const unsigned char *ptr, size_t len, size_t pos, uint16_t *out) {
|
|
if (!ptr || pos + 4 > len) return 0;
|
|
uint16_t value = 0;
|
|
for (size_t i = 0; i < 4; i++) {
|
|
int digit = zero_json_hex_value(ptr[pos + i]);
|
|
if (digit < 0) return 0;
|
|
value = (uint16_t)((value << 4) | (uint16_t)digit);
|
|
}
|
|
if (out) *out = value;
|
|
return 1;
|
|
}
|
|
|
|
static int zero_json_high_surrogate(uint16_t value) {
|
|
return value >= 0xd800u && value <= 0xdbffu;
|
|
}
|
|
|
|
static int zero_json_low_surrogate(uint16_t value) {
|
|
return value >= 0xdc00u && value <= 0xdfffu;
|
|
}
|
|
|
|
static uint32_t zero_json_surrogate_codepoint(uint16_t high, uint16_t low) {
|
|
uint32_t high_part = (uint32_t)high - 0xd800u;
|
|
uint32_t low_part = (uint32_t)low - 0xdc00u;
|
|
return 0x10000u + ((high_part << 10) | low_part);
|
|
}
|
|
|
|
static size_t zero_json_write_utf8_bytes(uint32_t codepoint, unsigned char out[4]) {
|
|
if (codepoint <= 0x7fu) {
|
|
out[0] = (unsigned char)codepoint;
|
|
return 1;
|
|
}
|
|
if (codepoint <= 0x7ffu) {
|
|
out[0] = (unsigned char)(0xc0u | (codepoint >> 6));
|
|
out[1] = (unsigned char)(0x80u | (codepoint & 0x3fu));
|
|
return 2;
|
|
}
|
|
if (codepoint <= 0xffffu) {
|
|
out[0] = (unsigned char)(0xe0u | (codepoint >> 12));
|
|
out[1] = (unsigned char)(0x80u | ((codepoint >> 6) & 0x3fu));
|
|
out[2] = (unsigned char)(0x80u | (codepoint & 0x3fu));
|
|
return 3;
|
|
}
|
|
if (codepoint <= 0x10ffffu) {
|
|
out[0] = (unsigned char)(0xf0u | (codepoint >> 18));
|
|
out[1] = (unsigned char)(0x80u | ((codepoint >> 12) & 0x3fu));
|
|
out[2] = (unsigned char)(0x80u | ((codepoint >> 6) & 0x3fu));
|
|
out[3] = (unsigned char)(0x80u | (codepoint & 0x3fu));
|
|
return 4;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void zero_json_skip_ws(ZeroJsonScanner *scanner) {
|
|
while (scanner->pos < scanner->len) {
|
|
unsigned char ch = scanner->ptr[scanner->pos];
|
|
if (ch != ' ' && ch != '\n' && ch != '\r' && ch != '\t') return;
|
|
scanner->pos++;
|
|
}
|
|
}
|
|
|
|
static size_t zero_json_skip_ws_at(ZeroByteView input, size_t pos) {
|
|
while (pos < input.len) {
|
|
unsigned char ch = input.ptr[pos];
|
|
if (ch != ' ' && ch != '\n' && ch != '\r' && ch != '\t') break;
|
|
pos++;
|
|
}
|
|
return pos;
|
|
}
|
|
|
|
static int zero_json_parse_value(ZeroJsonScanner *scanner, unsigned depth);
|
|
static size_t zero_json_utf8_char_len(const unsigned char *ptr, size_t len, size_t pos);
|
|
|
|
static int zero_json_parse_string(ZeroJsonScanner *scanner) {
|
|
if (scanner->pos >= scanner->len || scanner->ptr[scanner->pos] != '"') return zero_json_fail(scanner, scanner->pos);
|
|
scanner->pos++;
|
|
while (scanner->pos < scanner->len) {
|
|
size_t char_pos = scanner->pos;
|
|
unsigned char ch = scanner->ptr[scanner->pos++];
|
|
if (ch == '"') return 1;
|
|
if (ch < 0x20u) return zero_json_fail(scanner, char_pos);
|
|
if (ch >= 0x80u) {
|
|
size_t width = zero_json_utf8_char_len(scanner->ptr, scanner->len, char_pos);
|
|
if (width == 0) return zero_json_fail(scanner, char_pos);
|
|
scanner->pos = char_pos + width;
|
|
continue;
|
|
}
|
|
if (ch != '\\') continue;
|
|
if (scanner->pos >= scanner->len) return zero_json_fail(scanner, scanner->pos);
|
|
size_t escape_pos = scanner->pos;
|
|
unsigned char esc = scanner->ptr[scanner->pos++];
|
|
if (esc == '"' || esc == '\\' || esc == '/' || esc == 'b' || esc == 'f' || esc == 'n' || esc == 'r' || esc == 't') continue;
|
|
if (esc != 'u') return zero_json_fail(scanner, escape_pos);
|
|
uint16_t unit = 0;
|
|
if (!zero_json_hex4_value(scanner->ptr, scanner->len, scanner->pos, &unit)) return zero_json_fail(scanner, scanner->pos);
|
|
scanner->pos += 4;
|
|
if (zero_json_high_surrogate(unit)) {
|
|
size_t pair_pos = scanner->pos;
|
|
if (scanner->pos + 6 > scanner->len || scanner->ptr[scanner->pos] != '\\' || scanner->ptr[scanner->pos + 1] != 'u') return zero_json_fail(scanner, pair_pos);
|
|
scanner->pos += 2;
|
|
uint16_t low = 0;
|
|
if (!zero_json_hex4_value(scanner->ptr, scanner->len, scanner->pos, &low) || !zero_json_low_surrogate(low)) return zero_json_fail(scanner, scanner->pos);
|
|
scanner->pos += 4;
|
|
} else if (zero_json_low_surrogate(unit)) {
|
|
return zero_json_fail(scanner, escape_pos);
|
|
}
|
|
}
|
|
return zero_json_fail(scanner, scanner->len);
|
|
}
|
|
|
|
static int zero_json_match_literal(ZeroJsonScanner *scanner, const char *literal) {
|
|
size_t start = scanner->pos;
|
|
for (size_t i = 0; literal[i]; i++) {
|
|
if (scanner->pos >= scanner->len || scanner->ptr[scanner->pos] != (unsigned char)literal[i]) {
|
|
size_t error_pos = scanner->pos;
|
|
scanner->pos = start;
|
|
return zero_json_fail(scanner, error_pos);
|
|
}
|
|
scanner->pos++;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int zero_json_parse_number(ZeroJsonScanner *scanner) {
|
|
size_t start = scanner->pos;
|
|
if (scanner->pos < scanner->len && scanner->ptr[scanner->pos] == '-') scanner->pos++;
|
|
if (scanner->pos >= scanner->len) {
|
|
size_t error_pos = scanner->pos;
|
|
scanner->pos = start;
|
|
return zero_json_fail(scanner, error_pos);
|
|
}
|
|
if (scanner->ptr[scanner->pos] == '0') {
|
|
scanner->pos++;
|
|
} else if (scanner->ptr[scanner->pos] >= '1' && scanner->ptr[scanner->pos] <= '9') {
|
|
while (scanner->pos < scanner->len && scanner->ptr[scanner->pos] >= '0' && scanner->ptr[scanner->pos] <= '9') scanner->pos++;
|
|
} else {
|
|
size_t error_pos = scanner->pos;
|
|
scanner->pos = start;
|
|
return zero_json_fail(scanner, error_pos);
|
|
}
|
|
if (scanner->pos < scanner->len && scanner->ptr[scanner->pos] == '.') {
|
|
scanner->pos++;
|
|
size_t digits = scanner->pos;
|
|
while (scanner->pos < scanner->len && scanner->ptr[scanner->pos] >= '0' && scanner->ptr[scanner->pos] <= '9') scanner->pos++;
|
|
if (scanner->pos == digits) {
|
|
size_t error_pos = scanner->pos;
|
|
scanner->pos = start;
|
|
return zero_json_fail(scanner, error_pos);
|
|
}
|
|
}
|
|
if (scanner->pos < scanner->len && (scanner->ptr[scanner->pos] == 'e' || scanner->ptr[scanner->pos] == 'E')) {
|
|
scanner->pos++;
|
|
if (scanner->pos < scanner->len && (scanner->ptr[scanner->pos] == '+' || scanner->ptr[scanner->pos] == '-')) scanner->pos++;
|
|
size_t digits = scanner->pos;
|
|
while (scanner->pos < scanner->len && scanner->ptr[scanner->pos] >= '0' && scanner->ptr[scanner->pos] <= '9') scanner->pos++;
|
|
if (scanner->pos == digits) {
|
|
size_t error_pos = scanner->pos;
|
|
scanner->pos = start;
|
|
return zero_json_fail(scanner, error_pos);
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int zero_json_parse_array(ZeroJsonScanner *scanner, unsigned depth) {
|
|
if (scanner->pos >= scanner->len || scanner->ptr[scanner->pos] != '[') return zero_json_fail(scanner, scanner->pos);
|
|
scanner->tokens++;
|
|
scanner->pos++;
|
|
zero_json_skip_ws(scanner);
|
|
if (scanner->pos < scanner->len && scanner->ptr[scanner->pos] == ']') {
|
|
scanner->pos++;
|
|
return 1;
|
|
}
|
|
for (;;) {
|
|
if (!zero_json_parse_value(scanner, depth + 1)) return 0;
|
|
zero_json_skip_ws(scanner);
|
|
if (scanner->pos < scanner->len && scanner->ptr[scanner->pos] == ']') {
|
|
scanner->pos++;
|
|
return 1;
|
|
}
|
|
if (scanner->pos >= scanner->len || scanner->ptr[scanner->pos] != ',') return zero_json_fail(scanner, scanner->pos);
|
|
scanner->pos++;
|
|
zero_json_skip_ws(scanner);
|
|
}
|
|
}
|
|
|
|
static int zero_json_parse_object(ZeroJsonScanner *scanner, unsigned depth) {
|
|
if (scanner->pos >= scanner->len || scanner->ptr[scanner->pos] != '{') return zero_json_fail(scanner, scanner->pos);
|
|
scanner->tokens++;
|
|
scanner->pos++;
|
|
zero_json_skip_ws(scanner);
|
|
if (scanner->pos < scanner->len && scanner->ptr[scanner->pos] == '}') {
|
|
scanner->pos++;
|
|
return 1;
|
|
}
|
|
for (;;) {
|
|
if (!zero_json_parse_string(scanner)) return 0;
|
|
scanner->tokens++;
|
|
zero_json_skip_ws(scanner);
|
|
if (scanner->pos >= scanner->len || scanner->ptr[scanner->pos] != ':') return zero_json_fail(scanner, scanner->pos);
|
|
scanner->pos++;
|
|
if (!zero_json_parse_value(scanner, depth + 1)) return 0;
|
|
zero_json_skip_ws(scanner);
|
|
if (scanner->pos < scanner->len && scanner->ptr[scanner->pos] == '}') {
|
|
scanner->pos++;
|
|
return 1;
|
|
}
|
|
if (scanner->pos >= scanner->len || scanner->ptr[scanner->pos] != ',') return zero_json_fail(scanner, scanner->pos);
|
|
scanner->pos++;
|
|
zero_json_skip_ws(scanner);
|
|
}
|
|
}
|
|
|
|
static int zero_json_parse_value(ZeroJsonScanner *scanner, unsigned depth) {
|
|
if (depth > 64) return zero_json_fail(scanner, scanner->pos);
|
|
zero_json_skip_ws(scanner);
|
|
if (scanner->pos >= scanner->len) return zero_json_fail(scanner, scanner->pos);
|
|
unsigned char ch = scanner->ptr[scanner->pos];
|
|
if (ch == '{') return zero_json_parse_object(scanner, depth);
|
|
if (ch == '[') return zero_json_parse_array(scanner, depth);
|
|
if (ch == '"') {
|
|
scanner->tokens++;
|
|
return zero_json_parse_string(scanner);
|
|
}
|
|
if (ch == 't') {
|
|
scanner->tokens++;
|
|
return zero_json_match_literal(scanner, "true");
|
|
}
|
|
if (ch == 'f') {
|
|
scanner->tokens++;
|
|
return zero_json_match_literal(scanner, "false");
|
|
}
|
|
if (ch == 'n') {
|
|
scanner->tokens++;
|
|
return zero_json_match_literal(scanner, "null");
|
|
}
|
|
if (ch == '-' || (ch >= '0' && ch <= '9')) {
|
|
scanner->tokens++;
|
|
return zero_json_parse_number(scanner);
|
|
}
|
|
return zero_json_fail(scanner, scanner->pos);
|
|
}
|
|
|
|
int64_t zero_json_parse_bytes(ZeroByteView input) {
|
|
if (!input.ptr || input.len == 0) return -1;
|
|
ZeroJsonScanner scanner = {input.ptr, input.len, 0, 0, 0};
|
|
if (!zero_json_parse_value(&scanner, 0)) return -1;
|
|
zero_json_skip_ws(&scanner);
|
|
if (scanner.pos != scanner.len || scanner.tokens > INT64_MAX) return scanner.pos != scanner.len ? -2 : -1;
|
|
return (int64_t)scanner.tokens;
|
|
}
|
|
|
|
static uint64_t zero_json_error_offset(ZeroByteView input, uint32_t *status_out) {
|
|
if (!input.ptr || input.len == 0) {
|
|
if (status_out) *status_out = 1;
|
|
return 0;
|
|
}
|
|
ZeroJsonScanner scanner = {input.ptr, input.len, 0, 0, 0};
|
|
if (!zero_json_parse_value(&scanner, 0)) {
|
|
if (status_out) *status_out = 1;
|
|
return scanner.error_pos;
|
|
}
|
|
zero_json_skip_ws(&scanner);
|
|
if (scanner.pos != scanner.len || scanner.tokens > INT64_MAX) {
|
|
if (status_out) *status_out = scanner.pos != scanner.len ? 2u : 1u;
|
|
return scanner.pos;
|
|
}
|
|
if (status_out) *status_out = 0;
|
|
return input.len;
|
|
}
|
|
|
|
uint64_t zero_json_diagnostic(ZeroByteView input, uint32_t op) {
|
|
uint32_t status = 0;
|
|
uint64_t offset = zero_json_error_offset(input, &status);
|
|
if (op == 0) return status;
|
|
if (op == 1) return offset;
|
|
uint64_t line = 1;
|
|
uint64_t column = 1;
|
|
uint64_t start = 0;
|
|
if (input.ptr) {
|
|
for (uint64_t index = 0; index < offset && index < input.len; index++) {
|
|
if (input.ptr[index] == '\n') {
|
|
line++;
|
|
start = index + 1;
|
|
}
|
|
}
|
|
column = offset >= start ? offset - start + 1 : 1;
|
|
}
|
|
if (op == 2) return line;
|
|
if (op == 3) return column;
|
|
return status;
|
|
}
|
|
|
|
static uint64_t zero_runtime_pack_span(int has, size_t offset, size_t len) {
|
|
if (!has || offset > UINT32_MAX || len > 0x7fffffffu) return 0;
|
|
return (1ull << 63) | ((uint64_t)len << 32) | (uint64_t)(uint32_t)offset;
|
|
}
|
|
|
|
static uint64_t zero_json_pack_span(int has, size_t offset, size_t len) {
|
|
return zero_runtime_pack_span(has, offset, len);
|
|
}
|
|
|
|
static int zero_json_key_matches(ZeroByteView input, size_t start, size_t end, ZeroByteView key) {
|
|
if (!input.ptr || start >= end || input.ptr[start] != '"') return 0;
|
|
size_t pos = start + 1;
|
|
size_t key_pos = 0;
|
|
while (pos + 1 < end) {
|
|
unsigned char decoded = input.ptr[pos];
|
|
unsigned char scratch[4] = {0};
|
|
size_t scratch_len = 0;
|
|
if (decoded == '\\') {
|
|
pos++;
|
|
if (pos + 1 >= end) return 0;
|
|
unsigned char escaped = input.ptr[pos];
|
|
if (escaped == '"' || escaped == '\\' || escaped == '/') {
|
|
decoded = escaped;
|
|
} else if (escaped == 'b') {
|
|
decoded = 8;
|
|
} else if (escaped == 'f') {
|
|
decoded = 12;
|
|
} else if (escaped == 'n') {
|
|
decoded = 10;
|
|
} else if (escaped == 'r') {
|
|
decoded = 13;
|
|
} else if (escaped == 't') {
|
|
decoded = 9;
|
|
} else if (escaped == 'u') {
|
|
uint16_t unit = 0;
|
|
if (!zero_json_hex4_value(input.ptr, input.len, pos + 1, &unit)) return 0;
|
|
uint32_t codepoint = unit;
|
|
if (zero_json_high_surrogate(unit)) {
|
|
if (pos + 10 >= end || input.ptr[pos + 5] != '\\' || input.ptr[pos + 6] != 'u') return 0;
|
|
uint16_t low = 0;
|
|
if (!zero_json_hex4_value(input.ptr, input.len, pos + 7, &low) || !zero_json_low_surrogate(low)) return 0;
|
|
codepoint = zero_json_surrogate_codepoint(unit, low);
|
|
pos += 10;
|
|
} else if (zero_json_low_surrogate(unit)) {
|
|
return 0;
|
|
} else {
|
|
pos += 4;
|
|
}
|
|
scratch_len = zero_json_write_utf8_bytes(codepoint, scratch);
|
|
if (scratch_len == 0) return 0;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
if (scratch_len > 0) {
|
|
for (size_t i = 0; i < scratch_len; i++) {
|
|
if (key_pos >= key.len || (key.len > 0 && key.ptr[key_pos] != scratch[i])) return 0;
|
|
key_pos++;
|
|
}
|
|
} else {
|
|
if (key_pos >= key.len || (key.len > 0 && key.ptr[key_pos] != decoded)) return 0;
|
|
key_pos++;
|
|
}
|
|
pos++;
|
|
}
|
|
return key_pos == key.len;
|
|
}
|
|
|
|
uint64_t zero_json_field(ZeroByteView input, ZeroByteView key) {
|
|
if (!input.ptr || (key.len > 0 && !key.ptr)) return 0;
|
|
size_t start = zero_json_skip_ws_at(input, 0);
|
|
if (start >= input.len || input.ptr[start] != '{') return 0;
|
|
size_t pos = zero_json_skip_ws_at(input, start + 1);
|
|
if (pos < input.len && input.ptr[pos] == '}') {
|
|
size_t trailing = zero_json_skip_ws_at(input, pos + 1);
|
|
return trailing == input.len ? 0 : 0;
|
|
}
|
|
int found = 0;
|
|
size_t found_start = 0;
|
|
size_t found_end = 0;
|
|
while (pos < input.len) {
|
|
ZeroJsonScanner key_scanner = {input.ptr, input.len, pos, 0, pos};
|
|
if (!zero_json_parse_string(&key_scanner)) return 0;
|
|
size_t key_end = key_scanner.pos;
|
|
int matched = zero_json_key_matches(input, pos, key_end, key);
|
|
pos = zero_json_skip_ws_at(input, key_end);
|
|
if (pos >= input.len || input.ptr[pos] != ':') return 0;
|
|
size_t value_start = zero_json_skip_ws_at(input, pos + 1);
|
|
ZeroJsonScanner value_scanner = {input.ptr, input.len, value_start, 0, value_start};
|
|
if (!zero_json_parse_value(&value_scanner, 0)) return 0;
|
|
size_t value_end = value_scanner.pos;
|
|
if (matched && !found) {
|
|
found = 1;
|
|
found_start = value_start;
|
|
found_end = value_end;
|
|
}
|
|
pos = zero_json_skip_ws_at(input, value_end);
|
|
if (pos < input.len && input.ptr[pos] == '}') {
|
|
size_t trailing = zero_json_skip_ws_at(input, pos + 1);
|
|
if (trailing != input.len) return 0;
|
|
return zero_json_pack_span(found, found_start, found_end - found_start);
|
|
}
|
|
if (pos >= input.len || input.ptr[pos] != ',') return 0;
|
|
pos = zero_json_skip_ws_at(input, pos + 1);
|
|
if (pos >= input.len || input.ptr[pos] == '}') return 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
uint64_t zero_json_lookup_scalar(ZeroByteView input, ZeroByteView key, uint32_t op) {
|
|
uint64_t packed = zero_json_field(input, key);
|
|
if ((packed >> 63) == 0) return 0;
|
|
size_t offset = (size_t)(uint32_t)packed;
|
|
size_t len = (size_t)((packed >> 32) & 0x7fffffffu);
|
|
if (offset > input.len || len > input.len - offset) return 0;
|
|
ZeroByteView raw = {input.ptr + offset, len};
|
|
if (op == 0) return zero_parse_u32(raw);
|
|
if (op == 1) {
|
|
static const unsigned char true_lit[] = {'t', 'r', 'u', 'e'};
|
|
static const unsigned char false_lit[] = {'f', 'a', 'l', 's', 'e'};
|
|
if (raw.len == sizeof(true_lit) && memcmp(raw.ptr, true_lit, sizeof(true_lit)) == 0) return (1ull << 32) | 1u;
|
|
if (raw.len == sizeof(false_lit) && memcmp(raw.ptr, false_lit, sizeof(false_lit)) == 0) return (1ull << 32);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int zero_json_continuation(unsigned char ch) {
|
|
return (ch & 0xc0u) == 0x80u;
|
|
}
|
|
|
|
static size_t zero_json_utf8_char_len(const unsigned char *ptr, size_t len, size_t pos) {
|
|
if (!ptr || pos >= len) return 0;
|
|
unsigned char ch = ptr[pos];
|
|
if (ch < 0x80u) return 1;
|
|
if (ch >= 0xc2u && ch <= 0xdfu) {
|
|
return pos + 1 < len && zero_json_continuation(ptr[pos + 1]) ? 2 : 0;
|
|
}
|
|
if (ch == 0xe0u) {
|
|
return pos + 2 < len && ptr[pos + 1] >= 0xa0u && ptr[pos + 1] <= 0xbfu && zero_json_continuation(ptr[pos + 2]) ? 3 : 0;
|
|
}
|
|
if ((ch >= 0xe1u && ch <= 0xecu) || (ch >= 0xeeu && ch <= 0xefu)) {
|
|
return pos + 2 < len && zero_json_continuation(ptr[pos + 1]) && zero_json_continuation(ptr[pos + 2]) ? 3 : 0;
|
|
}
|
|
if (ch == 0xedu) {
|
|
return pos + 2 < len && ptr[pos + 1] >= 0x80u && ptr[pos + 1] <= 0x9fu && zero_json_continuation(ptr[pos + 2]) ? 3 : 0;
|
|
}
|
|
if (ch == 0xf0u) {
|
|
return pos + 3 < len && ptr[pos + 1] >= 0x90u && ptr[pos + 1] <= 0xbfu && zero_json_continuation(ptr[pos + 2]) && zero_json_continuation(ptr[pos + 3]) ? 4 : 0;
|
|
}
|
|
if (ch >= 0xf1u && ch <= 0xf3u) {
|
|
return pos + 3 < len && zero_json_continuation(ptr[pos + 1]) && zero_json_continuation(ptr[pos + 2]) && zero_json_continuation(ptr[pos + 3]) ? 4 : 0;
|
|
}
|
|
if (ch == 0xf4u) {
|
|
return pos + 3 < len && ptr[pos + 1] >= 0x80u && ptr[pos + 1] <= 0x8fu && zero_json_continuation(ptr[pos + 2]) && zero_json_continuation(ptr[pos + 3]) ? 4 : 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
uint64_t zero_json_string_decode(ZeroMutByteView buffer, ZeroByteView raw) {
|
|
if ((!buffer.ptr && buffer.len > 0) || !raw.ptr || raw.len < 2 || raw.ptr[0] != '"') return 0;
|
|
size_t pos = 1;
|
|
size_t out = 0;
|
|
while (pos < raw.len) {
|
|
unsigned char ch = raw.ptr[pos++];
|
|
if (ch == '"') {
|
|
return pos == raw.len ? zero_json_pack_span(1, 0, out) : 0;
|
|
}
|
|
if (ch < 0x20u) return 0;
|
|
if (ch != '\\') {
|
|
size_t char_len = ch < 0x80u ? 1 : zero_json_utf8_char_len(raw.ptr, raw.len, pos - 1);
|
|
if (char_len == 0 || out + char_len > buffer.len) return 0;
|
|
for (size_t i = 0; i < char_len; i++) buffer.ptr[out++] = raw.ptr[pos - 1 + i];
|
|
pos += char_len - 1;
|
|
continue;
|
|
}
|
|
if (pos >= raw.len) return 0;
|
|
unsigned char escaped = raw.ptr[pos++];
|
|
unsigned char decoded = 0;
|
|
if (escaped == '"' || escaped == '\\' || escaped == '/') {
|
|
decoded = escaped;
|
|
} else if (escaped == 'b') {
|
|
decoded = 8;
|
|
} else if (escaped == 'f') {
|
|
decoded = 12;
|
|
} else if (escaped == 'n') {
|
|
decoded = 10;
|
|
} else if (escaped == 'r') {
|
|
decoded = 13;
|
|
} else if (escaped == 't') {
|
|
decoded = 9;
|
|
} else if (escaped == 'u') {
|
|
uint16_t unit = 0;
|
|
if (!zero_json_hex4_value(raw.ptr, raw.len, pos, &unit)) return 0;
|
|
uint32_t codepoint = unit;
|
|
pos += 4;
|
|
if (zero_json_high_surrogate(unit)) {
|
|
if (pos + 6 > raw.len || raw.ptr[pos] != '\\' || raw.ptr[pos + 1] != 'u') return 0;
|
|
pos += 2;
|
|
uint16_t low = 0;
|
|
if (!zero_json_hex4_value(raw.ptr, raw.len, pos, &low) || !zero_json_low_surrogate(low)) return 0;
|
|
codepoint = zero_json_surrogate_codepoint(unit, low);
|
|
pos += 4;
|
|
} else if (zero_json_low_surrogate(unit)) {
|
|
return 0;
|
|
}
|
|
unsigned char scratch[4] = {0};
|
|
size_t scratch_len = zero_json_write_utf8_bytes(codepoint, scratch);
|
|
if (scratch_len == 0 || out + scratch_len > buffer.len) return 0;
|
|
for (size_t i = 0; i < scratch_len; i++) buffer.ptr[out++] = scratch[i];
|
|
continue;
|
|
} else {
|
|
return 0;
|
|
}
|
|
if (out >= buffer.len) return 0;
|
|
buffer.ptr[out++] = decoded;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
uint64_t zero_json_string_field(ZeroMutByteView buffer, ZeroByteView input, ZeroByteView key) {
|
|
uint64_t packed = zero_json_field(input, key);
|
|
if ((packed >> 63) == 0) return 0;
|
|
size_t offset = (size_t)(uint32_t)packed;
|
|
size_t len = (size_t)((packed >> 32) & 0x7fffffffu);
|
|
if (offset > input.len || len > input.len - offset) return 0;
|
|
return zero_json_string_decode(buffer, (ZeroByteView){input.ptr + offset, len});
|
|
}
|
|
|
|
static int zero_json_write_byte(ZeroMutByteView buffer, size_t *out, unsigned char byte) {
|
|
if (!out || *out >= buffer.len) return 0;
|
|
buffer.ptr[*out] = byte;
|
|
*out += 1;
|
|
return 1;
|
|
}
|
|
|
|
static int zero_json_write_escape_pair(ZeroMutByteView buffer, size_t *out, unsigned char escaped) {
|
|
return zero_json_write_byte(buffer, out, '\\') && zero_json_write_byte(buffer, out, escaped);
|
|
}
|
|
|
|
static int zero_json_write_u00_escape(ZeroMutByteView buffer, size_t *out, unsigned char byte) {
|
|
static const unsigned char hex[] = "0123456789abcdef";
|
|
return zero_json_write_byte(buffer, out, '\\') &&
|
|
zero_json_write_byte(buffer, out, 'u') &&
|
|
zero_json_write_byte(buffer, out, '0') &&
|
|
zero_json_write_byte(buffer, out, '0') &&
|
|
zero_json_write_byte(buffer, out, hex[(byte >> 4) & 0xfu]) &&
|
|
zero_json_write_byte(buffer, out, hex[byte & 0xfu]);
|
|
}
|
|
|
|
uint64_t zero_json_write_string(ZeroMutByteView buffer, ZeroByteView text) {
|
|
if (!buffer.ptr || (text.len > 0 && !text.ptr)) return 0;
|
|
size_t out = 0;
|
|
if (!zero_json_write_byte(buffer, &out, '"')) return 0;
|
|
size_t pos = 0;
|
|
while (pos < text.len) {
|
|
unsigned char byte = text.ptr[pos];
|
|
if (byte == '"') {
|
|
if (!zero_json_write_escape_pair(buffer, &out, '"')) return 0;
|
|
pos++;
|
|
} else if (byte == '\\') {
|
|
if (!zero_json_write_escape_pair(buffer, &out, '\\')) return 0;
|
|
pos++;
|
|
} else if (byte == '\b') {
|
|
if (!zero_json_write_escape_pair(buffer, &out, 'b')) return 0;
|
|
pos++;
|
|
} else if (byte == '\f') {
|
|
if (!zero_json_write_escape_pair(buffer, &out, 'f')) return 0;
|
|
pos++;
|
|
} else if (byte == '\n') {
|
|
if (!zero_json_write_escape_pair(buffer, &out, 'n')) return 0;
|
|
pos++;
|
|
} else if (byte == '\r') {
|
|
if (!zero_json_write_escape_pair(buffer, &out, 'r')) return 0;
|
|
pos++;
|
|
} else if (byte == '\t') {
|
|
if (!zero_json_write_escape_pair(buffer, &out, 't')) return 0;
|
|
pos++;
|
|
} else if (byte < 0x20u) {
|
|
if (!zero_json_write_u00_escape(buffer, &out, byte)) return 0;
|
|
pos++;
|
|
} else {
|
|
size_t width = zero_json_utf8_char_len(text.ptr, text.len, pos);
|
|
if (width == 0 || out + width > buffer.len) return 0;
|
|
for (size_t i = 0; i < width; i++) buffer.ptr[out++] = text.ptr[pos + i];
|
|
pos += width;
|
|
}
|
|
}
|
|
if (!zero_json_write_byte(buffer, &out, '"')) return 0;
|
|
return zero_json_pack_span(1, 0, out);
|
|
}
|
|
|
|
static int zero_json_write_span_bytes(ZeroMutByteView buffer, size_t *out, ZeroByteView bytes) {
|
|
if (!out || (bytes.len > 0 && !bytes.ptr) || *out > buffer.len || bytes.len > buffer.len - *out) return 0;
|
|
if (bytes.len > 0) memcpy(buffer.ptr + *out, bytes.ptr, bytes.len);
|
|
*out += bytes.len;
|
|
return 1;
|
|
}
|
|
|
|
static int zero_json_write_u32_value(ZeroMutByteView buffer, size_t *out, uint32_t value) {
|
|
unsigned char digits[10];
|
|
size_t len = 0;
|
|
do {
|
|
digits[len++] = (unsigned char)('0' + (value % 10u));
|
|
value /= 10u;
|
|
} while (value != 0);
|
|
while (len > 0) {
|
|
if (!zero_json_write_byte(buffer, out, digits[--len])) return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int zero_json_write_bool_value(ZeroMutByteView buffer, size_t *out, uint32_t value) {
|
|
static const unsigned char true_lit[] = {'t', 'r', 'u', 'e'};
|
|
static const unsigned char false_lit[] = {'f', 'a', 'l', 's', 'e'};
|
|
ZeroByteView lit = value ? (ZeroByteView){true_lit, sizeof(true_lit)} : (ZeroByteView){false_lit, sizeof(false_lit)};
|
|
return zero_json_write_span_bytes(buffer, out, lit);
|
|
}
|
|
|
|
static int zero_json_write_string_value_at(ZeroMutByteView buffer, size_t *out, ZeroByteView text) {
|
|
if (!out || *out > buffer.len) return 0;
|
|
uint64_t packed = zero_json_write_string((ZeroMutByteView){buffer.ptr + *out, buffer.len - *out}, text);
|
|
if ((packed >> 63) == 0) return 0;
|
|
size_t len = (size_t)((packed >> 32) & 0x7fffffffu);
|
|
*out += len;
|
|
return 1;
|
|
}
|
|
|
|
typedef enum {
|
|
ZERO_JSON_WRITE_FIELD_RAW = 0,
|
|
ZERO_JSON_WRITE_FIELD_STRING = 1,
|
|
ZERO_JSON_WRITE_FIELD_U32 = 2,
|
|
ZERO_JSON_WRITE_FIELD_BOOL = 3
|
|
} ZeroJsonWriteFieldKind;
|
|
|
|
static int zero_json_write_field_at(ZeroMutByteView buffer, size_t *out, ZeroByteView key, ZeroByteView value, uint64_t scalar, ZeroJsonWriteFieldKind kind) {
|
|
if (!zero_json_write_string_value_at(buffer, out, key)) return 0;
|
|
if (!zero_json_write_byte(buffer, out, ':')) return 0;
|
|
switch (kind) {
|
|
case ZERO_JSON_WRITE_FIELD_RAW:
|
|
if (zero_json_parse_bytes(value) < 0) return 0;
|
|
return zero_json_write_span_bytes(buffer, out, value);
|
|
case ZERO_JSON_WRITE_FIELD_STRING:
|
|
return zero_json_write_string_value_at(buffer, out, value);
|
|
case ZERO_JSON_WRITE_FIELD_U32:
|
|
return zero_json_write_u32_value(buffer, out, (uint32_t)scalar);
|
|
case ZERO_JSON_WRITE_FIELD_BOOL:
|
|
return zero_json_write_bool_value(buffer, out, scalar ? 1u : 0u);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static uint64_t zero_json_write_field_result(ZeroMutByteView buffer, ZeroByteView key, ZeroByteView value, uint64_t scalar, ZeroJsonWriteFieldKind kind) {
|
|
if (!buffer.ptr) return 0;
|
|
size_t out = 0;
|
|
if (!zero_json_write_field_at(buffer, &out, key, value, scalar, kind)) return 0;
|
|
return zero_json_pack_span(1, 0, out);
|
|
}
|
|
|
|
static uint64_t zero_json_write_object1_result(ZeroMutByteView buffer, ZeroByteView key, ZeroByteView value, uint64_t scalar, ZeroJsonWriteFieldKind kind) {
|
|
if (!buffer.ptr) return 0;
|
|
size_t out = 0;
|
|
if (!zero_json_write_byte(buffer, &out, '{')) return 0;
|
|
if (!zero_json_write_field_at(buffer, &out, key, value, scalar, kind)) return 0;
|
|
if (!zero_json_write_byte(buffer, &out, '}')) return 0;
|
|
return zero_json_pack_span(1, 0, out);
|
|
}
|
|
|
|
uint64_t zero_json_write_field_raw(ZeroMutByteView buffer, ZeroByteView key, ZeroByteView value) {
|
|
return zero_json_write_field_result(buffer, key, value, 0, ZERO_JSON_WRITE_FIELD_RAW);
|
|
}
|
|
|
|
uint64_t zero_json_write_field_string(ZeroMutByteView buffer, ZeroByteView key, ZeroByteView value) {
|
|
return zero_json_write_field_result(buffer, key, value, 0, ZERO_JSON_WRITE_FIELD_STRING);
|
|
}
|
|
|
|
uint64_t zero_json_write_field_u32(ZeroMutByteView buffer, ZeroByteView key, uint32_t value) {
|
|
return zero_json_write_field_result(buffer, key, (ZeroByteView){0}, value, ZERO_JSON_WRITE_FIELD_U32);
|
|
}
|
|
|
|
uint64_t zero_json_write_field_bool(ZeroMutByteView buffer, ZeroByteView key, uint32_t value) {
|
|
return zero_json_write_field_result(buffer, key, (ZeroByteView){0}, value ? 1u : 0u, ZERO_JSON_WRITE_FIELD_BOOL);
|
|
}
|
|
|
|
uint64_t zero_json_write_object1_string(ZeroMutByteView buffer, ZeroByteView key, ZeroByteView value) {
|
|
return zero_json_write_object1_result(buffer, key, value, 0, ZERO_JSON_WRITE_FIELD_STRING);
|
|
}
|
|
|
|
uint64_t zero_json_write_object1_u32(ZeroMutByteView buffer, ZeroByteView key, uint32_t value) {
|
|
return zero_json_write_object1_result(buffer, key, (ZeroByteView){0}, value, ZERO_JSON_WRITE_FIELD_U32);
|
|
}
|
|
|
|
uint64_t zero_json_write_object1_bool(ZeroMutByteView buffer, ZeroByteView key, uint32_t value) {
|
|
return zero_json_write_object1_result(buffer, key, (ZeroByteView){0}, value ? 1u : 0u, ZERO_JSON_WRITE_FIELD_BOOL);
|
|
}
|
|
|
|
uint64_t zero_json_write_object2_fields(ZeroMutByteView buffer, ZeroByteView field0, ZeroByteView field1) {
|
|
if (!buffer.ptr) return 0;
|
|
size_t out = 0;
|
|
if (!zero_json_write_byte(buffer, &out, '{')) return 0;
|
|
if (!zero_json_write_span_bytes(buffer, &out, field0)) return 0;
|
|
if (!zero_json_write_byte(buffer, &out, ',')) return 0;
|
|
if (!zero_json_write_span_bytes(buffer, &out, field1)) return 0;
|
|
if (!zero_json_write_byte(buffer, &out, '}')) return 0;
|
|
ZeroByteView candidate = {buffer.ptr, out};
|
|
if (zero_json_parse_bytes(candidate) < 0) return 0;
|
|
return zero_json_pack_span(1, 0, out);
|
|
}
|
|
|
|
uint64_t zero_json_write_object2_string_field(ZeroMutByteView buffer, ZeroByteView key, ZeroByteView value, ZeroByteView field1) {
|
|
if (!buffer.ptr) return 0;
|
|
size_t out = 0;
|
|
if (!zero_json_write_byte(buffer, &out, '{')) return 0;
|
|
if (!zero_json_write_field_at(buffer, &out, key, value, 0, ZERO_JSON_WRITE_FIELD_STRING)) return 0;
|
|
if (!zero_json_write_byte(buffer, &out, ',')) return 0;
|
|
if (!zero_json_write_span_bytes(buffer, &out, field1)) return 0;
|
|
if (!zero_json_write_byte(buffer, &out, '}')) return 0;
|
|
ZeroByteView candidate = {buffer.ptr, out};
|
|
if (zero_json_parse_bytes(candidate) < 0) return 0;
|
|
return zero_json_pack_span(1, 0, out);
|
|
}
|
|
|
|
uint64_t zero_json_write_object2_u32_field(ZeroMutByteView buffer, ZeroByteView key, uint32_t value, ZeroByteView field1) {
|
|
if (!buffer.ptr) return 0;
|
|
size_t out = 0;
|
|
if (!zero_json_write_byte(buffer, &out, '{')) return 0;
|
|
if (!zero_json_write_field_at(buffer, &out, key, (ZeroByteView){0}, value, ZERO_JSON_WRITE_FIELD_U32)) return 0;
|
|
if (!zero_json_write_byte(buffer, &out, ',')) return 0;
|
|
if (!zero_json_write_span_bytes(buffer, &out, field1)) return 0;
|
|
if (!zero_json_write_byte(buffer, &out, '}')) return 0;
|
|
ZeroByteView candidate = {buffer.ptr, out};
|
|
if (zero_json_parse_bytes(candidate) < 0) return 0;
|
|
return zero_json_pack_span(1, 0, out);
|
|
}
|
|
|
|
uint64_t zero_json_write_object2_bool_field(ZeroMutByteView buffer, ZeroByteView key, uint32_t value, ZeroByteView field1) {
|
|
if (!buffer.ptr) return 0;
|
|
size_t out = 0;
|
|
if (!zero_json_write_byte(buffer, &out, '{')) return 0;
|
|
if (!zero_json_write_field_at(buffer, &out, key, (ZeroByteView){0}, value ? 1u : 0u, ZERO_JSON_WRITE_FIELD_BOOL)) return 0;
|
|
if (!zero_json_write_byte(buffer, &out, ',')) return 0;
|
|
if (!zero_json_write_span_bytes(buffer, &out, field1)) return 0;
|
|
if (!zero_json_write_byte(buffer, &out, '}')) return 0;
|
|
ZeroByteView candidate = {buffer.ptr, out};
|
|
if (zero_json_parse_bytes(candidate) < 0) return 0;
|
|
return zero_json_pack_span(1, 0, out);
|
|
}
|
|
|
|
uint64_t zero_json_write_array2_strings(ZeroMutByteView buffer, ZeroByteView value0, ZeroByteView value1) {
|
|
if (!buffer.ptr) return 0;
|
|
size_t out = 0;
|
|
if (!zero_json_write_byte(buffer, &out, '[')) return 0;
|
|
if (!zero_json_write_string_value_at(buffer, &out, value0)) return 0;
|
|
if (!zero_json_write_byte(buffer, &out, ',')) return 0;
|
|
if (!zero_json_write_string_value_at(buffer, &out, value1)) return 0;
|
|
if (!zero_json_write_byte(buffer, &out, ']')) return 0;
|
|
return zero_json_pack_span(1, 0, out);
|
|
}
|
|
|
|
uint64_t zero_json_write_array2_u32(ZeroMutByteView buffer, uint32_t value0, uint32_t value1) {
|
|
if (!buffer.ptr) return 0;
|
|
size_t out = 0;
|
|
if (!zero_json_write_byte(buffer, &out, '[')) return 0;
|
|
if (!zero_json_write_u32_value(buffer, &out, value0)) return 0;
|
|
if (!zero_json_write_byte(buffer, &out, ',')) return 0;
|
|
if (!zero_json_write_u32_value(buffer, &out, value1)) return 0;
|
|
if (!zero_json_write_byte(buffer, &out, ']')) return 0;
|
|
return zero_json_pack_span(1, 0, out);
|
|
}
|
|
|
|
uint64_t zero_json_write_array2_bools(ZeroMutByteView buffer, uint32_t value0, uint32_t value1) {
|
|
if (!buffer.ptr) return 0;
|
|
size_t out = 0;
|
|
if (!zero_json_write_byte(buffer, &out, '[')) return 0;
|
|
if (!zero_json_write_bool_value(buffer, &out, value0 ? 1u : 0u)) return 0;
|
|
if (!zero_json_write_byte(buffer, &out, ',')) return 0;
|
|
if (!zero_json_write_bool_value(buffer, &out, value1 ? 1u : 0u)) return 0;
|
|
if (!zero_json_write_byte(buffer, &out, ']')) return 0;
|
|
return zero_json_pack_span(1, 0, out);
|
|
}
|
|
|
|
uint32_t zero_str_contains(ZeroByteView text, ZeroByteView needle) {
|
|
return (uint32_t)zero_str_pair_op(text, needle, ZERO_STR_OP_CONTAINS);
|
|
}
|
|
|
|
static int zero_http_header_token_char(unsigned char ch) {
|
|
return (ch >= 'A' && ch <= 'Z') ||
|
|
(ch >= 'a' && ch <= 'z') ||
|
|
(ch >= '0' && ch <= '9') ||
|
|
ch == '!' || ch == '#' || ch == '$' || ch == '%' ||
|
|
ch == '&' || ch == '\'' || ch == '*' || ch == '+' ||
|
|
ch == '-' || ch == '.' || ch == '^' || ch == '_' ||
|
|
ch == '`' || ch == '|' || ch == '~';
|
|
}
|
|
|
|
static unsigned char zero_http_header_lower(unsigned char ch) {
|
|
return ch >= 'A' && ch <= 'Z' ? (unsigned char)(ch + ('a' - 'A')) : ch;
|
|
}
|
|
|
|
static int zero_http_header_name_equal(const unsigned char *ptr, size_t len, ZeroByteView name) {
|
|
if (!ptr || !name.ptr || len != name.len || len == 0) return 0;
|
|
for (size_t i = 0; i < len; i++) {
|
|
unsigned char a = ptr[i];
|
|
unsigned char b = name.ptr[i];
|
|
if (!zero_http_header_token_char(b)) return 0;
|
|
if (zero_http_header_lower(a) != zero_http_header_lower(b)) return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static uint64_t zero_http_header_pack(size_t offset, size_t len) {
|
|
if (offset > 0x7fffffffu || len > 0xffffffffu) return 0;
|
|
return (1ull << 63) | ((uint64_t)(uint32_t)offset << 32) | (uint64_t)(uint32_t)len;
|
|
}
|
|
|
|
static uint64_t zero_http_span_pack(size_t offset, size_t len) {
|
|
if (offset > 0xffffffffu || len > 0x7fffffffu) return 0;
|
|
return (1ull << 63) | ((uint64_t)(uint32_t)len << 32) | (uint64_t)(uint32_t)offset;
|
|
}
|
|
|
|
static uint32_t zero_http_read_u32le(const unsigned char *ptr) {
|
|
return (uint32_t)ptr[0] |
|
|
((uint32_t)ptr[1] << 8) |
|
|
((uint32_t)ptr[2] << 16) |
|
|
((uint32_t)ptr[3] << 24);
|
|
}
|
|
|
|
static int zero_http_response_has_meta(ZeroByteView response) {
|
|
if (!response.ptr || response.len < ZERO_HTTP_RESPONSE_META_BYTES) return 0;
|
|
return response.ptr[0] == 'Z' &&
|
|
response.ptr[1] == 'H' &&
|
|
response.ptr[2] == 'R' &&
|
|
response.ptr[3] == '1';
|
|
}
|
|
|
|
static size_t zero_http_raw_header_len(ZeroByteView response) {
|
|
if (!response.ptr || response.len == 0) return 0;
|
|
size_t pos = 0;
|
|
while (pos < response.len) {
|
|
size_t line_start = pos;
|
|
while (pos < response.len && response.ptr[pos] != '\n') pos++;
|
|
size_t line_end = pos;
|
|
if (line_end > line_start && response.ptr[line_end - 1] == '\r') line_end--;
|
|
size_t next = pos < response.len ? pos + 1 : pos;
|
|
if (line_end == line_start) return next;
|
|
pos = next;
|
|
}
|
|
return response.len;
|
|
}
|
|
|
|
uint32_t zero_http_response_len(ZeroByteView response) {
|
|
if (!zero_http_response_has_meta(response)) {
|
|
return response.len > 0xffffffffu ? 0xffffffffu : (uint32_t)response.len;
|
|
}
|
|
uint32_t headers_len = zero_http_read_u32le(response.ptr + 8);
|
|
uint32_t body_len = zero_http_read_u32le(response.ptr + 12);
|
|
if (headers_len > 0xffffffffu - body_len) return 0xffffffffu;
|
|
return headers_len + body_len;
|
|
}
|
|
|
|
uint32_t zero_http_response_headers_len(ZeroByteView response) {
|
|
if (zero_http_response_has_meta(response)) return zero_http_read_u32le(response.ptr + 8);
|
|
size_t len = zero_http_raw_header_len(response);
|
|
return len > 0xffffffffu ? 0xffffffffu : (uint32_t)len;
|
|
}
|
|
|
|
uint32_t zero_http_response_body_offset(ZeroByteView response) {
|
|
if (zero_http_response_has_meta(response)) {
|
|
uint32_t headers_len = zero_http_read_u32le(response.ptr + 8);
|
|
return ZERO_HTTP_RESPONSE_META_BYTES + headers_len;
|
|
}
|
|
size_t offset = zero_http_raw_header_len(response);
|
|
return offset > 0xffffffffu ? 0xffffffffu : (uint32_t)offset;
|
|
}
|
|
|
|
uint64_t zero_http_header_value(ZeroByteView headers, ZeroByteView name) {
|
|
if (!headers.ptr || !name.ptr || headers.len == 0 || name.len == 0) return 0;
|
|
for (size_t i = 0; i < name.len; i++) {
|
|
if (!zero_http_header_token_char(name.ptr[i])) return 0;
|
|
}
|
|
|
|
size_t header_offset = 0;
|
|
size_t header_len = headers.len;
|
|
if (zero_http_response_has_meta(headers)) {
|
|
header_offset = ZERO_HTTP_RESPONSE_META_BYTES;
|
|
header_len = zero_http_read_u32le(headers.ptr + 8);
|
|
if (header_offset > headers.len || header_len > headers.len - header_offset) return 0;
|
|
}
|
|
|
|
size_t pos = header_offset;
|
|
size_t limit = header_offset + header_len;
|
|
while (pos < limit) {
|
|
size_t line_start = pos;
|
|
while (pos < limit && headers.ptr[pos] != '\n') pos++;
|
|
size_t line_end = pos;
|
|
if (line_end > line_start && headers.ptr[line_end - 1] == '\r') line_end--;
|
|
if (pos < limit) pos++;
|
|
|
|
if (line_end == line_start) break;
|
|
size_t colon = line_start;
|
|
while (colon < line_end && headers.ptr[colon] != ':') colon++;
|
|
if (colon == line_start || colon >= line_end) continue;
|
|
if (!zero_http_header_name_equal(headers.ptr + line_start, colon - line_start, name)) continue;
|
|
|
|
size_t value_start = colon + 1;
|
|
while (value_start < line_end && (headers.ptr[value_start] == ' ' || headers.ptr[value_start] == '\t')) value_start++;
|
|
size_t value_end = line_end;
|
|
while (value_end > value_start && (headers.ptr[value_end - 1] == ' ' || headers.ptr[value_end - 1] == '\t')) value_end--;
|
|
return zero_http_header_pack(value_start, value_end - value_start);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
uint32_t zero_http_header_found(uint64_t value) {
|
|
return (value >> 63) ? 1u : 0u;
|
|
}
|
|
|
|
uint32_t zero_http_header_offset(uint64_t value) {
|
|
return zero_http_header_found(value) ? (uint32_t)((value >> 32) & 0x7fffffffu) : 0u;
|
|
}
|
|
|
|
uint32_t zero_http_header_len(uint64_t value) {
|
|
return zero_http_header_found(value) ? (uint32_t)(value & 0xffffffffu) : 0u;
|
|
}
|
|
|
|
static int zero_http_token_char(unsigned char ch) {
|
|
return (ch >= 'A' && ch <= 'Z') ||
|
|
(ch >= 'a' && ch <= 'z') ||
|
|
(ch >= '0' && ch <= '9') ||
|
|
ch == '!' || ch == '#' || ch == '$' || ch == '%' ||
|
|
ch == '&' || ch == '\'' || ch == '*' || ch == '+' ||
|
|
ch == '-' || ch == '.' || ch == '^' || ch == '_' ||
|
|
ch == '`' || ch == '|' || ch == '~';
|
|
}
|
|
|
|
static int zero_http_method_valid(ZeroByteView method) {
|
|
if (!method.ptr || method.len == 0) return 0;
|
|
for (size_t i = 0; i < method.len; i++) {
|
|
if (!zero_http_token_char(method.ptr[i])) return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static ZeroByteView zero_http_trim_trailing_cr(ZeroByteView view) {
|
|
if (view.len > 0 && view.ptr[view.len - 1] == '\r') view.len--;
|
|
return view;
|
|
}
|
|
|
|
static int zero_http_first_line(ZeroByteView request, ZeroByteView *line_out) {
|
|
if (line_out) *line_out = (ZeroByteView){0};
|
|
if (!request.ptr || request.len == 0) return 0;
|
|
size_t line_end = 0;
|
|
while (line_end < request.len && request.ptr[line_end] != '\n') line_end++;
|
|
if (line_end >= request.len) return 0;
|
|
ZeroByteView line = zero_http_trim_trailing_cr((ZeroByteView){request.ptr, line_end});
|
|
if (line.len == 0) return 0;
|
|
if (line_out) *line_out = line;
|
|
return 1;
|
|
}
|
|
|
|
static int zero_http_request_target(ZeroByteView request, size_t *offset_out, size_t *len_out) {
|
|
if (offset_out) *offset_out = 0;
|
|
if (len_out) *len_out = 0;
|
|
ZeroByteView line = {0};
|
|
if (!zero_http_first_line(request, &line)) return 0;
|
|
size_t space = 0;
|
|
while (space < line.len && line.ptr[space] != ' ') space++;
|
|
if (space == 0 || space + 1 >= line.len) return 0;
|
|
ZeroByteView method = {line.ptr, space};
|
|
if (!zero_http_method_valid(method)) return 0;
|
|
size_t start = space + 1;
|
|
size_t end = start;
|
|
while (end < line.len && line.ptr[end] != ' ') {
|
|
if (line.ptr[end] <= 32u) return 0;
|
|
end++;
|
|
}
|
|
if (end == start) return 0;
|
|
if (end < line.len) {
|
|
size_t version_start = end;
|
|
while (version_start < line.len && line.ptr[version_start] == ' ') version_start++;
|
|
if (version_start < line.len) {
|
|
ZeroByteView version = {line.ptr + version_start, line.len - version_start};
|
|
if (!((version.len == 8 && memcmp(version.ptr, "HTTP/1.0", 8) == 0) ||
|
|
(version.len == 8 && memcmp(version.ptr, "HTTP/1.1", 8) == 0))) {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
if (offset_out) *offset_out = (size_t)(line.ptr + start - request.ptr);
|
|
if (len_out) *len_out = end - start;
|
|
return 1;
|
|
}
|
|
|
|
uint64_t zero_http_request_method_name(ZeroByteView request) {
|
|
ZeroByteView line = {0};
|
|
if (!zero_http_first_line(request, &line)) return 0;
|
|
size_t space = 0;
|
|
while (space < line.len && line.ptr[space] != ' ') space++;
|
|
if (space == 0 || space >= line.len) return 0;
|
|
ZeroByteView method = {line.ptr, space};
|
|
if (!zero_http_method_valid(method)) return 0;
|
|
return zero_http_span_pack(0, space);
|
|
}
|
|
|
|
uint64_t zero_http_request_path(ZeroByteView request) {
|
|
size_t target_offset = 0;
|
|
size_t target_len = 0;
|
|
if (!zero_http_request_target(request, &target_offset, &target_len)) return 0;
|
|
const unsigned char *target = request.ptr + target_offset;
|
|
|
|
size_t path_start = 0;
|
|
if (target_len >= 7 && memcmp(target, "http://", 7) == 0) {
|
|
path_start = 7;
|
|
while (path_start < target_len && target[path_start] != '/') path_start++;
|
|
if (path_start >= target_len) return zero_http_span_pack(target_offset + 5, 1);
|
|
} else if (target_len >= 8 && memcmp(target, "https://", 8) == 0) {
|
|
path_start = 8;
|
|
while (path_start < target_len && target[path_start] != '/') path_start++;
|
|
if (path_start >= target_len) return zero_http_span_pack(target_offset + 6, 1);
|
|
}
|
|
|
|
size_t path_end = path_start;
|
|
while (path_end < target_len && target[path_end] != '?' && target[path_end] != '#') path_end++;
|
|
if (path_end == path_start) return 0;
|
|
return zero_http_span_pack(target_offset + path_start, path_end - path_start);
|
|
}
|
|
|
|
uint32_t zero_http_request_matches(ZeroByteView request, ZeroByteView method, ZeroByteView path) {
|
|
if ((!method.ptr && method.len > 0) || (!path.ptr && path.len > 0)) return 0;
|
|
uint64_t method_span = zero_http_request_method_name(request);
|
|
if ((method_span >> 63) == 0) return 0;
|
|
size_t method_len = (size_t)((method_span >> 32) & 0x7fffffffu);
|
|
size_t method_offset = (size_t)(method_span & 0xffffffffu);
|
|
if (method_offset > request.len || method_len > request.len - method_offset) return 0;
|
|
if (method_len != method.len || (method_len > 0 && memcmp(request.ptr + method_offset, method.ptr, method_len) != 0)) return 0;
|
|
|
|
uint64_t path_span = zero_http_request_path(request);
|
|
if ((path_span >> 63) == 0) return 0;
|
|
size_t path_len = (size_t)((path_span >> 32) & 0x7fffffffu);
|
|
size_t path_offset = (size_t)(path_span & 0xffffffffu);
|
|
if (path_offset > request.len || path_len > request.len - path_offset) return 0;
|
|
return path_len == path.len && (path_len == 0 || memcmp(request.ptr + path_offset, path.ptr, path_len) == 0);
|
|
}
|
|
|
|
static int zero_http_body_start(ZeroByteView request, size_t *start_out) {
|
|
if (start_out) *start_out = 0;
|
|
if (!request.ptr) return 0;
|
|
size_t start = 0;
|
|
while (start < request.len) {
|
|
size_t end = start;
|
|
while (end < request.len && request.ptr[end] != '\n') end++;
|
|
size_t line_end = end;
|
|
if (line_end > start && request.ptr[line_end - 1] == '\r') line_end--;
|
|
size_t next = end < request.len ? end + 1 : end;
|
|
if (line_end == start) {
|
|
if (start_out) *start_out = next;
|
|
return 1;
|
|
}
|
|
start = next;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int zero_http_value_starts_with_ignore_ascii_case(ZeroByteView value, const char *prefix, size_t prefix_len) {
|
|
if (!prefix || value.len < prefix_len) return 0;
|
|
for (size_t i = 0; i < prefix_len; i++) {
|
|
if (zero_http_header_lower(value.ptr[i]) != zero_http_header_lower((unsigned char)prefix[i])) return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int zero_http_request_has_json_content_type(ZeroByteView request) {
|
|
static const unsigned char header_name[] = "content-type";
|
|
static const char json_prefix[] = "application/json";
|
|
uint64_t header = zero_http_header_value(request, (ZeroByteView){header_name, sizeof(header_name) - 1});
|
|
if ((header >> 63) == 0) return 0;
|
|
size_t offset = (size_t)((header >> 32) & 0x7fffffffu);
|
|
size_t len = (size_t)(header & 0xffffffffu);
|
|
if (offset > request.len || len > request.len - offset) return 0;
|
|
ZeroByteView value = {request.ptr + offset, len};
|
|
if (!zero_http_value_starts_with_ignore_ascii_case(value, json_prefix, sizeof(json_prefix) - 1)) return 0;
|
|
return value.len == sizeof(json_prefix) - 1 || value.ptr[sizeof(json_prefix) - 1] == ';';
|
|
}
|
|
|
|
uint64_t zero_http_request_body_within(ZeroByteView request, uint64_t max, uint32_t require_json) {
|
|
size_t body_start = 0;
|
|
if (!zero_http_body_start(request, &body_start)) return 0;
|
|
if (body_start > request.len) return 0;
|
|
size_t body_len = request.len - body_start;
|
|
if (body_len > max) return 0;
|
|
ZeroByteView body = {request.ptr + body_start, body_len};
|
|
if (require_json) {
|
|
if (!zero_http_request_has_json_content_type(request)) return 0;
|
|
if (zero_json_parse_bytes(body) < 0) return 0;
|
|
}
|
|
return zero_http_span_pack(body_start, body_len);
|
|
}
|
|
|
|
static const char *zero_http_status_reason(uint32_t status) {
|
|
switch (status) {
|
|
case 100: return "Continue";
|
|
case 200: return "OK";
|
|
case 201: return "Created";
|
|
case 202: return "Accepted";
|
|
case 204: return "No Content";
|
|
case 301: return "Moved Permanently";
|
|
case 302: return "Found";
|
|
case 304: return "Not Modified";
|
|
case 400: return "Bad Request";
|
|
case 401: return "Unauthorized";
|
|
case 403: return "Forbidden";
|
|
case 404: return "Not Found";
|
|
case 405: return "Method Not Allowed";
|
|
case 409: return "Conflict";
|
|
case 422: return "Unprocessable Content";
|
|
case 429: return "Too Many Requests";
|
|
case 500: return "Internal Server Error";
|
|
case 502: return "Bad Gateway";
|
|
case 503: return "Service Unavailable";
|
|
default: return "Unknown";
|
|
}
|
|
}
|
|
|
|
static int zero_http_write_bytes(ZeroMutByteView buffer, size_t *offset, const unsigned char *bytes, size_t len) {
|
|
if (!offset || !buffer.ptr || (!bytes && len > 0)) return 0;
|
|
if (*offset > buffer.len || len > buffer.len - *offset) return 0;
|
|
if (len > 0) memcpy(buffer.ptr + *offset, bytes, len);
|
|
*offset += len;
|
|
return 1;
|
|
}
|
|
|
|
static int zero_http_write_literal(ZeroMutByteView buffer, size_t *offset, const char *text) {
|
|
return zero_http_write_bytes(buffer, offset, (const unsigned char *)text, strlen(text));
|
|
}
|
|
|
|
static int zero_http_write_u32_decimal(ZeroMutByteView buffer, size_t *offset, uint32_t value) {
|
|
unsigned char tmp[10];
|
|
size_t len = 0;
|
|
do {
|
|
tmp[len++] = (unsigned char)('0' + (value % 10u));
|
|
value /= 10u;
|
|
} while (value > 0 && len < sizeof(tmp));
|
|
if (!offset || *offset > buffer.len || len > buffer.len - *offset) return 0;
|
|
for (size_t i = 0; i < len; i++) buffer.ptr[*offset + i] = tmp[len - 1 - i];
|
|
*offset += len;
|
|
return 1;
|
|
}
|
|
|
|
uint32_t zero_http_write_json_response(ZeroMutByteView buffer, uint32_t status, ZeroByteView body) {
|
|
if (!buffer.ptr || (!body.ptr && body.len > 0) || body.len > 0xffffffffu) return 0;
|
|
size_t offset = 0;
|
|
const char *reason = zero_http_status_reason(status);
|
|
if (!zero_http_write_literal(buffer, &offset, "HTTP/1.1 ") ||
|
|
!zero_http_write_u32_decimal(buffer, &offset, status) ||
|
|
!zero_http_write_literal(buffer, &offset, " ") ||
|
|
!zero_http_write_literal(buffer, &offset, reason) ||
|
|
!zero_http_write_literal(buffer, &offset, "\r\ncontent-type: application/json\r\nconnection: close\r\ncontent-length: ") ||
|
|
!zero_http_write_u32_decimal(buffer, &offset, (uint32_t)body.len) ||
|
|
!zero_http_write_literal(buffer, &offset, "\r\n\r\n") ||
|
|
!zero_http_write_bytes(buffer, &offset, body.ptr, body.len) ||
|
|
offset > 0xffffffffu) {
|
|
return 0;
|
|
}
|
|
return (uint32_t)offset;
|
|
}
|
|
|
|
uint32_t zero_http_result_ok(uint64_t result) {
|
|
uint32_t status = zero_http_result_status(result);
|
|
return zero_http_result_error(result) == ZERO_HTTP_OK && status >= 200 && status < 300 ? 1u : 0u;
|
|
}
|
|
|
|
uint32_t zero_http_result_status(uint64_t result) {
|
|
return (uint32_t)((result >> 32) & 0xffffu);
|
|
}
|
|
|
|
uint32_t zero_http_result_body_len(uint64_t result) {
|
|
return (uint32_t)(result & 0xffffffffu);
|
|
}
|
|
|
|
uint32_t zero_http_result_error(uint64_t result) {
|
|
return (uint32_t)((result >> 48) & 0xffffu);
|
|
}
|