Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,15 @@ jobs:
# DRBGs, the TLS PRF, ChaCha20 and Poly1305. Slow
# AES / DES KAT loops are tracked under TODO.
run: python demos/bearssl/smoke.py
- name: curl smoke (static + shared + system + BearSSL HTTPS)
# Compiles the whole libcurl source tree (HTTP + file + ws)
# and builds it as a static archive, a shared library, and an
# exe linked against each, plus a flavour bound to the system
# libcurl. HTTPS uses badc-compiled BearSSL. Transfers run
# against a hermetic loopback server. The Windows build is
# skipped for now (config-win32 surface pending), so the step
# is a no-op there.
run: python demos/curl/smoke.py
- name: raylib standalone build + cdb load gate (Windows)
# Build the vendored raylib + Lode Runner game standalone, then run
# it under cdb with show-loader-snaps. A win32 / CRT import bound to
Expand Down Expand Up @@ -862,7 +871,7 @@ jobs:
run: |
set -e
export BADC="$PWD/target/release/badc"
for d in sqlite3 miniz kissfft bzip2 tweetnacl monocypher bearssl lua stb quickjs raylib; do
for d in sqlite3 miniz kissfft bzip2 tweetnacl monocypher bearssl lua stb quickjs raylib curl; do
echo "::group::$d (N=${{ matrix.pressure }})"
python "demos/$d/smoke.py"
echo "::endgroup::"
Expand Down
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,16 @@ demos/bearssl/*
!demos/bearssl/smoke.py
!demos/bearssl/smoke_main.c
!demos/bearssl/README.md
# curl -- vendored by `demos/curl/setup.py` (library sources -> src/,
# public API headers -> include/curl/). Only the hand-written config,
# driver, binding header, runner, and README are committed.
demos/curl/*
!demos/curl/setup.py
!demos/curl/smoke.py
!demos/curl/README.md
!demos/curl/curl_client.c
!demos/curl/curl_config.h
!demos/curl/curl_syslink.h
# stb -- a collection of single-file public-domain headers.
# `setup.py` pulls the upstream archive from the vendor-deps
# release and drops a curated set of headers into `demos/stb/`;
Expand Down
38 changes: 20 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,25 +70,27 @@ a (fast) script.

There are various demo's under [`demos`](./demos/):

* Few small-ish ones (`threads.c`, `coro_pool.c`, `hello_server.c`),
* `maze.c ` - maze builder and solver,
* `gui_hello` - GUI demos for macOS, Linux and Windows,
* `wdm_driver`, `nt_hello`, `nt_loader` - examples of the Windows native (NT) executable, Windows driver,
* `efi_hello` - a UEFI binary,
* `sqlite3` - the most famous embedded database,
* `miniz` - compression, CRC32, integers, bit twiddling,
* `kissfft` - floating points, Fast Fourier Transform,
* `bzip2` - compression, integers, bit twiddling,
* `stb` - header-only C library with lots of incredible features (math
* Few small-ish ones ([`threads.c`](./demos/threads.c), [`coro_pool.c`](./demos/coro_pool.c), [`hello_server.c`](./demos/hello_server.c)),
* [`maze.c`](./demos/maze.c) - maze builder and solver,
* [`gui_hello`](./demos/gui_hello/) - GUI demos for macOS, Linux and Windows,
* [`wdm_driver`](./demos/wdm_driver/), [`nt_hello`](./demos/nt_hello/), [`nt_loader`](./demos/nt_loader/) - examples of the Windows native (NT) executable, Windows driver,
* [`efi_hello`](./demos/efi_hello/) - a UEFI binary,
* [`sqlite3`](./demos/sqlite3/) - the most famous embedded database ([sqlite.org](https://sqlite.org)),
* [`miniz`](./demos/miniz/) - compression, CRC32, integers, bit twiddling ([richgel999/miniz](https://github.com/richgel999/miniz)),
* [`kissfft`](./demos/kissfft/) - floating points, Fast Fourier Transform ([mborgerding/kissfft](https://github.com/mborgerding/kissfft)),
* [`bzip2`](./demos/bzip2/) - compression, integers, bit twiddling ([sourceware.org/bzip2](https://sourceware.org/bzip2/)),
* [`stb`](./demos/stb/) - header-only C library with lots of incredible features (math
noise generation, sound, JPEG, PNG, BMP, PSD support to name a few).
It really stresses all of the compiler.
* `chibicc` - a small C compiler
* `tinycc` - a cool and small C toolchain
* `TweetNaCl`, `Monocypher`, `BearSSL` - cryptography
* `Lua` - the embeddable scripting language
* `quickjs` - JavaScript interpreter
* [`TCL`](https://en.wikipedia.org/wiki/Tcl_(programming_language)) - Tool command language
* `Python` - Python 3.14
It really stresses all of the compiler ([nothings/stb](https://github.com/nothings/stb)),
* [`chibicc`](./demos/chibicc/) - a small C compiler ([rui314/chibicc](https://github.com/rui314/chibicc)),
* [`tinycc`](./demos/tinycc/) - a cool and small C toolchain ([TinyCC/tinycc](https://github.com/TinyCC/tinycc)),
* [`TweetNaCl`](./demos/tweetnacl/) ([tweetnacl.cr.yp.to](https://tweetnacl.cr.yp.to/)), [`Monocypher`](./demos/monocypher/) ([monocypher.org](https://monocypher.org/)), [`BearSSL`](./demos/bearssl/) ([bearssl.org](https://bearssl.org/)) - cryptography,
* [`Lua`](./demos/lua/) - the embeddable scripting language ([lua.org](https://www.lua.org/)),
* [`quickjs`](./demos/quickjs/) - JavaScript interpreter ([bellard.org/quickjs](https://bellard.org/quickjs/)),
* [`TCL`](./demos/tcl/) - Tool command language ([tcl-lang.org](https://www.tcl-lang.org/)),
* [`raylib`](./demos/raylib/) - Library for games, (there is also [`loderunner`](./demos/raylib/loderunner.c) game included) ([raylib.com](https://www.raylib.com/)),
* [`curl`](./demos/curl/) - The library and the tools that handle HTTP and friends on PCs, smart phones/watches, TVs, ... ([curl.se](https://curl.se/)),
* [`Python`](./demos/python/) - Python 3.14 ([python.org](https://www.python.org/)).

Besides these, there are some fun test fixtures implementing Horner scheme, RK4,
8-Queens and more.
Expand Down
12 changes: 12 additions & 0 deletions demos/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,18 @@ standalone build + headless run is wired for macOS today (the X11
/ Win32 header surface for the Linux / Windows ports is pending).
See [`raylib/README.md`](./raylib/README.md).

## curl/

badc compiles the curl 8.11.1 library (HTTP + `file://` + WebSocket,
threaded resolver, IPv6, no external dependencies) and builds it as a
static archive, a shared library, and an executable linked against
each, plus a flavour that binds a badc-compiled client to the
platform's installed libcurl to check the frontend matches the OS ABI.
HTTPS is provided by badc-compiled BearSSL (`USE_BEARSSL`); the smoke
drives HTTP / HTTPS / `file://` transfers against a hermetic loopback
server, so it needs no external network. See
[`curl/README.md`](./curl/README.md).

## efi_hello/

UEFI application that prints "Hello, EFI!" through
Expand Down
17 changes: 5 additions & 12 deletions demos/bearssl/smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,11 @@ def __exit__(self, *args: object) -> None:
WIN = sys.platform == "win32"
EXE_SUFFIX = ".exe" if WIN else ""

# Disable the Win32 CryptoAPI seeder in src/rand/sysrng.c: it
# pulls in `<wincrypt.h>` types and functions (HCRYPTPROV,
# PROV_RSA_FULL, CryptAcquireContext, ...) that c5's ambient
# Windows headers do not surface. The portable PRNGs still build
# and the smoke / KAT drivers never call br_prng_seeder_system,
# so the disabled codepath has no behavioural effect on the
# tests. The override is a no-op on POSIX, where BR_USE_URANDOM
# carries the runtime path. TODO: add a wincrypt.h surface to
# the c5 headers so this can come back on Windows.
BUILD_DEFINES: tuple[str, ...] = (
"BR_USE_WIN32_RAND=0",
)
# BearSSL selects its system RNG seeder automatically: the Win32 CryptoAPI on
# Windows (<wincrypt.h>) and /dev/urandom on POSIX. The Windows path compiles
# against the shipped wincrypt.h surface (HCRYPTPROV, CryptAcquireContext,
# CryptGenRandom, ...).
BUILD_DEFINES: tuple[str, ...] = ()
INCLUDE_PATHS = (
BEAR_DIR / "inc",
BEAR_DIR / "src",
Expand Down
53 changes: 53 additions & 0 deletions demos/curl/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# curl

badc compiles the [curl](https://curl.se) 8.11.1 library from source and
builds it three ways -- a static archive, a shared library, and an executable
that links against each -- plus a fourth flavour that links a badc-compiled
client against the platform's **installed** libcurl to prove the frontend
matches the OS ABI.

The vendored library is trimmed to no external dependencies: HTTP, `file://`,
and the WebSocket / URL / multi / easy APIs, with the threaded resolver and
IPv6 on. HTTPS is provided by [BearSSL](../bearssl) when the TLS lane is built
(`USE_BEARSSL`); the system-libcurl flavour gets HTTPS from the OS's own TLS
backend. The other protocols (FTP, SMTP, LDAP, ...) are compiled out via the
`CURL_DISABLE_*` set.

## Layout

`setup.py` fetches the pinned tarball (vendor-deps mirror, falling back to
`curl.se`) and drops curl's `lib/` tree in `src/` and the public headers in
`include/curl/`. Committed alongside:

* `curl_config.h` -- the hand-written build configuration (curl normally
generates this from configure/CMake). Describes exactly the POSIX surface
badc ships on macOS and Linux; Windows uses curl's own `lib/config-win32.h`.
* `curl_client.c` -- the driver: version / feature reporting, the URL API,
escape round-tripping, the string list, a `file://` transfer, and -- when a
base URL is passed -- an `http(s)://` fetch used by the loopback test.
* `curl_syslink.h` -- `#pragma dylib` / `#pragma binding` bindings for the
system-libcurl flavour.

## Build flavours

The smoke builds every flavour at `-O0` and `-O`:

* **static** -- `badc --ar` bundles the objects into `libcurl.a`; the driver
links it with `-L/-l`.
* **shared** -- `badc --shared --export-all` produces `libcurl.{dylib,so,dll}`;
the driver binds to it through a generated `#pragma` header.
* **system** -- the driver binds to the installed libcurl through
`curl_syslink.h`. macOS resolves `/usr/lib/libcurl.4.dylib` from the dyld
shared cache; Linux uses the `libcurl.so.4` soname. Skipped where absent
(Windows ships no redistributable libcurl).

## Running

```sh
cargo build --release --manifest-path Cargo.toml
python3 demos/curl/smoke.py
```

Each built driver runs the offline scenarios plus a `file://` transfer and an
`http://` transfer against a loopback HTTP server the harness starts on
`127.0.0.1` -- no external network, so it is deterministic under CI.
205 changes: 205 additions & 0 deletions demos/curl/curl_client.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
/* curl_client.c -- exercises libcurl's public API end to end.
*
* Runs against any libcurl the smoke links it to: the badc-built static or
* dynamic library, or the system libcurl (via the -include binding header).
* The scenarios are offline unless a base URL is passed on argv: version and
* feature reporting, the URL API, escape round-tripping, the string list, and
* a file:// transfer through the easy handle. When argv[1] is an http(s):// or
* file:// base URL the client additionally fetches "<base>/hello" and checks
* the body, which the smoke uses to drive a hermetic loopback server.
*/

#include <curl/curl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> /* getcwd (mapped to _getcwd on Windows) */

struct membuf {
char *data;
size_t len;
};

static size_t collect(char *ptr, size_t size, size_t nmemb, void *userp)
{
size_t n = size * nmemb;
struct membuf *m = (struct membuf *)userp;
char *grown = (char *)realloc(m->data, m->len + n + 1);
if(!grown)
return 0;
m->data = grown;
memcpy(m->data + m->len, ptr, n);
m->len += n;
m->data[m->len] = '\0';
return n;
}

static int fail(const char *what)
{
fprintf(stderr, "curl smoke FAIL: %s\n", what);
return 1;
}

static int scenario_version(void)
{
const char *v = curl_version();
if(!v || !strstr(v, "libcurl/"))
return fail("curl_version missing libcurl/ prefix");
printf("version OK: %s\n", v);

curl_version_info_data *vi = curl_version_info(CURLVERSION_NOW);
if(!vi)
return fail("curl_version_info returned NULL");
printf("version_info OK: num=0x%06x ssl=%s\n", vi->version_num,
(vi->features & CURL_VERSION_SSL) ? "yes" : "no");
return 0;
}

static int scenario_url(void)
{
CURLU *u = curl_url();
if(!u)
return fail("curl_url alloc");
/* CURLU_NON_SUPPORT_SCHEME so the structural parse succeeds whether or
not https is a compiled-in protocol (HTTP-only vs BearSSL builds). */
if(curl_url_set(u, CURLUPART_URL,
"https://bob@example.com:8443/dir/file?q=1&r=2",
CURLU_NON_SUPPORT_SCHEME)) {
curl_url_cleanup(u);
return fail("curl_url_set");
}
char *host = NULL, *port = NULL, *path = NULL, *query = NULL, *user = NULL;
int bad = curl_url_get(u, CURLUPART_HOST, &host, 0)
|| curl_url_get(u, CURLUPART_PORT, &port, 0)
|| curl_url_get(u, CURLUPART_PATH, &path, 0)
|| curl_url_get(u, CURLUPART_QUERY, &query, 0)
|| curl_url_get(u, CURLUPART_USER, &user, 0);
int ok = !bad && !strcmp(host, "example.com") && !strcmp(port, "8443")
&& !strcmp(path, "/dir/file") && !strcmp(query, "q=1&r=2")
&& !strcmp(user, "bob");
printf("url OK: host=%s port=%s path=%s query=%s user=%s\n",
host, port, path, query, user);
curl_free(host);
curl_free(port);
curl_free(path);
curl_free(query);
curl_free(user);
curl_url_cleanup(u);
return ok ? 0 : fail("url parts mismatch");
}

static int scenario_escape(void)
{
CURL *e = curl_easy_init();
if(!e)
return fail("curl_easy_init for escape");
const char *raw = "a b/c?d=e&f";
char *enc = curl_easy_escape(e, raw, 0);
int outlen = 0;
char *dec = enc ? curl_easy_unescape(e, enc, 0, &outlen) : NULL;
int ok = enc && dec && !strcmp(dec, raw) && !strchr(enc, ' ');
printf("escape OK: '%s' -> '%s' -> '%s'\n", raw, enc ? enc : "(null)",
dec ? dec : "(null)");
curl_free(enc);
curl_free(dec);
curl_easy_cleanup(e);
return ok ? 0 : fail("escape round-trip");
}

static int scenario_slist(void)
{
struct curl_slist *h = NULL;
h = curl_slist_append(h, "X-One: 1");
h = curl_slist_append(h, "X-Two: 2");
int n = 0;
for(struct curl_slist *p = h; p; p = p->next)
n++;
curl_slist_free_all(h);
printf("slist OK: %d entries\n", n);
return n == 2 ? 0 : fail("slist count");
}

static int transfer(const char *url, const char *want)
{
CURL *e = curl_easy_init();
if(!e)
return fail("curl_easy_init for transfer");
struct membuf body = {NULL, 0};
curl_easy_setopt(e, CURLOPT_URL, url);
curl_easy_setopt(e, CURLOPT_WRITEFUNCTION, collect);
curl_easy_setopt(e, CURLOPT_WRITEDATA, &body);
curl_easy_setopt(e, CURLOPT_TIMEOUT, 10L);
/* Loopback TLS uses a throwaway self-signed cert, so peer/host
verification is turned off for the hermetic test. */
curl_easy_setopt(e, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(e, CURLOPT_SSL_VERIFYHOST, 0L);
CURLcode rc = curl_easy_perform(e);
int ok = (rc == CURLE_OK) && body.data && strstr(body.data, want);
if(rc != CURLE_OK)
fprintf(stderr, " transfer %s: %s\n", url, curl_easy_strerror(rc));
else
printf("transfer OK: %s -> %zu bytes\n", url, body.len);
free(body.data);
curl_easy_cleanup(e);
return ok ? 0 : fail("transfer");
}

static int scenario_file(void)
{
/* Write a temp file and read it back through file://. */
const char *path = "curl_smoke_file.txt";
const char *content = "badc-file-scheme-payload";
FILE *f = fopen(path, "wb");
if(!f)
return fail("temp file create");
fwrite(content, 1, strlen(content), f);
fclose(f);
char url[512];
char cwd[400];
if(!getcwd(cwd, sizeof cwd))
return fail("getcwd");
#ifdef _WIN32
/* file:///C:/dir/file -- forward slashes and the extra leading slash for
the drive-letter path. */
for(char *p = cwd; *p; p++)
if(*p == '\\')
*p = '/';
snprintf(url, sizeof url, "file:///%s/%s", cwd, path);
#else
snprintf(url, sizeof url, "file://%s/%s", cwd, path);
#endif
int rc = transfer(url, content);
remove(path);
return rc;
}

int main(int argc, char **argv)
{
// Emit each progress line immediately. The Windows C runtime treats line
// buffering (_IOLBF) as full buffering (verified identical to _IOFBF under
// both this compiler and MSVC), so a piped stdout would hold the whole run
// and lose it if the process-exit flush is preempted by lingering network
// worker threads. Unbuffered output is written per line on every platform.
setvbuf(stdout, NULL, _IONBF, 0);
if(curl_global_init(CURL_GLOBAL_DEFAULT) != CURLE_OK)
return fail("curl_global_init");

int rc = 0;
rc |= scenario_version();
rc |= scenario_url();
rc |= scenario_escape();
rc |= scenario_slist();
rc |= scenario_file();

/* Optional networked fetch against a base URL supplied by the smoke. */
if(argc > 1 && argv[1][0]) {
char url[512];
snprintf(url, sizeof url, "%s/hello", argv[1]);
rc |= transfer(url, "hello-from-loopback");
}

curl_global_cleanup();
if(rc == 0)
printf("curl smoke: all scenarios green\n");
return rc;
}
Loading
Loading