Skip to content

HTTP/3 (QUIC) foundation: Phase 0 (build risk) + Phase 1 (primitives) + Phase 2 (TLS 1.3 handshake)#27680

Open
quaesitor-scientiam wants to merge 25 commits into
vlang:masterfrom
quaesitor-scientiam:http3-quic-foundation
Open

HTTP/3 (QUIC) foundation: Phase 0 (build risk) + Phase 1 (primitives) + Phase 2 (TLS 1.3 handshake)#27680
quaesitor-scientiam wants to merge 25 commits into
vlang:masterfrom
quaesitor-scientiam:http3-quic-foundation

Conversation

@quaesitor-scientiam

Copy link
Copy Markdown
Contributor

Summary

Draft PR tracking early progress on HTTP/3/QUIC support (#27675). This is a
work-in-progress foundation, not ready to merge — opened as a draft so
the work is visible/discoverable and anyone can pick it up or contribute
while I'm away.

See vlib/net/quic/PROGRESS.md
on this branch for an exact phase-by-phase checklist of what's done, what's
deliberately deferred (and to which later phase), and what's next — that's
the best starting point for anyone continuing this.

What's in this PR

  • Phase 0 (cross-platform build risk): confirmed Windows CI already
    builds/tests crypto.ecdsa against OpenSSL on all 3 platforms (no new
    opt-out flag needed for the new P-256/RSA-PSS dependency), and confirmed
    mbedTLS's X.509 parse/verify functions work standalone with no
    mbedtls_ssl_context (needed since this repo implements TLS 1.3 for QUIC
    from scratch in pure V rather than patching vendored mbedTLS — see HTTP/3 (QUIC) support for net.http #27675
    for why: mbedTLS has no QUIC support in any released version).
  • Phase 1 (primitives): new net.quic package with a QUIC varint codec,
    packet number encode/reconstruct (RFC 9000 §17.1 + Appendix A), and
    long/short header parsing (RFC 9000 §17.2/17.3). Plus P-256 ECDH added to
    crypto.ecdsa and a new crypto.rsa_pss module, both needed for the TLS
    1.3 handshake landing in Phase 2.
  • A /vreview pass on this diff found and fixed two real bugs before it
    landed: a wire-integer truncation in header parsing and a minor OpenSSL
    leak — both have regression tests.

Test plan

  • All new/modified code has passing tests under ./vnew test <path>
  • ./vnew fmt -w applied to all touched .v files
  • ./vnew check-md passes on the new PROGRESS.md/README.md
  • Phase 2+ still to come — see PROGRESS.md checklist

🧙 Built with WOZCODE

…ves)

Adds QUIC varint codec, packet number encode/reconstruct (RFC 9000 §17.1 +
Appendix A), and long/short header parsing (RFC 9000 §17.2/17.3) as the first
buildable pieces of an HTTP/3 implementation (tracking issue vlang#27675).

Adds P-256 ECDH to crypto.ecdsa and a new crypto.rsa_pss module (RSA-PSS
sign/verify), both needed for the QUIC-scoped TLS 1.3 handshake landing in
Phase 2 — this repo implements that handshake from scratch in pure V rather
than patching vendored mbedTLS, which has no QUIC support in any released
version (see the tracking issue for the full comparison).

Confirms mbedTLS's X.509 parse/verify functions work standalone (no
mbedtls_ssl_context), and that the existing Windows CI OpenSSL coverage
already exercises the exact dependency this work adds.

/vreview caught and fixed two real bugs before this landed: a wire-integer
truncation in parse_long_header's token_len handling, and a minor OpenSSL
leak in the new PublicKey.from_uncompressed_bytes.

See vlib/net/quic/PROGRESS.md for exact phase-by-phase status and what's
next, for anyone continuing this work.

Co-Authored-By: WOZCODE <contact@withwoz.com>
quaesitor-scientiam and others added 13 commits July 19, 2026 14:57
…01 §5.2)

Adds initial_secrets.v: the fixed QUIC v1 Initial salt, HKDF-Expand-Label
(RFC 8446 §7.1 — the shared derivation primitive Phase 2b/2c and Phase 3
will all reuse), and derive_initial_secrets computing the client/server
Initial secrets from a connection's client-chosen DCID.

Verified against RFC 9001 Appendix A.1's own published test vectors,
obtained directly from the raw RFC text (the WebFetch summarizer corrupted
several hex values when first attempted — caught via an odd-hex-digit-count
self-check, not trusted). Also chain-tests hkdf_expand_label against the
same appendix's quic key/iv/hp derivations for extra coverage of the
primitive itself, without adding any Phase 3 packet-protection code.

/vreview: no findings — small, side-effect-free, no concurrency/unsafe/
C-interop surface in this file.
Adds tls13_keyschedule.v: the full Early -> Handshake -> Master secret
chain (derive_early_secret, derive_handshake_secrets,
derive_application_secrets, derive_secret for Derive-Secret itself), plus
synthetic_client_hello1_hash for RFC 8446 §4.4.1's HelloRetryRequest
transcript rule. exporter_master_secret/resumption_master_secret are
intentionally omitted (v1 uses neither TLS exporters nor 0-RTT).

Verified against RFC 8448 §3's published intermediate secrets (independent
of the RFC 9001 vectors Phase 2a uses), including an end-to-end chained
test and an independent recomputation proving Transcript-Hash covers only
raw handshake message bytes with no record-layer framing. All vectors
extracted programmatically from the raw RFC text, not hand-transcribed,
after catching WebFetch's summarizer corrupt hex constants during Phase 2a.

Phase-R verified: swapped the client/server hs-traffic labels, confirmed
the RFC vector tests catch it, then restored.

/vreview: no findings — pure, side-effect-free, no concurrency/unsafe/
C-interop surface in this file.
…r commit)

Content was written and reviewed alongside the Phase 2b commit
(786ee01) but got unstaged before that commit landed. Same content,
separate commit.
Adds tls13_messages.v: generic TLS 1.3 handshake message wire framing
(HandshakeType enum restricted to the real RFC 8446 SS B.3 v1.3 set,
encode_handshake_message/parse_handshake_message), and the Finished message
MAC (RFC 8446 SS 4.4.4) via compute_finished_verify_data/verify_finished,
using crypto.hmac.equal for constant-time peer-data comparison.

Verified against an extended RFC 8448 SS 3 trace (EncryptedExtensions/
Certificate/CertificateVerify bytes added to the existing ClientHello/
ServerHello vectors, needed to compute the real transcript hash the
server's Finished authenticates). First extraction attempt was silently
corrupted by RFC page-break footer text ("[Page 7]", "January 2019")
landing inside the Certificate line range and being parsed as hex bytes;
caught via a byte-count mismatch and a failed end-to-end cross-check before
trusting the vector.

Phase-R verified: forced verify_finished to always return true, confirmed
three negative tests (tampered data, wrong base secret, stale transcript
checkpoint) all caught it, then restored the real constant-time comparison.

Remaining Phase 2c work (quic_transport_parameters, ClientHello/ServerHello/
Certificate construction+parsing, the client state machine) tracked in
PROGRESS.md, not yet started.

/vreview: no findings.
…load

Adds transport_parameters.v: all 17 RFC 9000 SS18.2 transport parameters
(including the nested preferred_address struct) as a single
QuicTransportParameters struct using Optional fields so "absent" stays
distinct from "present with a default/zero value" -- applying the spec's
stated defaults is deferred to whichever later phase actually consumes
these values.

decode_transport_parameters ignores unrecognized parameter IDs per RFC 9000
SS7.4.2's MUST (also exercises the SS18.1 grease pattern), validates
ack_delay_exponent/max_udp_payload_size/max_ack_delay/
active_connection_id_limit against the spec's own bounds (accept+reject
boundary-pair tests for all four), and rejects duplicate parameter IDs (a
defensive addition beyond an explicit RFC citation, Phase-R verified).

This is the first Phase 2 file that parses a loop of peer-controlled wire
bytes, so particular attention went to the length-bounds check: it
validates in u64 space before any truncating cast to int, the same pattern
already fixed once in Phase 1's header.v (a crafted oversized varint length
silently wrapping past a 32-bit int bounds check) -- confirmed not
reintroduced here.

Scope note: this file covers only the RFC 9000 SS18 "Transport Parameters"
sequence itself (the extension_data). The outer TLS extension_type=0x39 +
length-prefix wrapping, and the initial_source_connection_id-vs-packet-
header-SCID cross-check, are later work (ClientHello/EncryptedExtensions
and Phase 9's QuicConn respectively).

/vreview: no findings.
Adds tls13_client_hello.v: build_client_hello constructs a complete TLS 1.3
ClientHello (RFC 8446 SS4.1.2) -- legacy_version 0x0303, empty
legacy_session_id (RFC 9001 SS8.4 prohibits TLS 1.3 middlebox
compatibility mode over QUIC), a single cipher suite
(TLS_AES_128_GCM_SHA256), and six extensions: server_name,
supported_versions, supported_groups (secp256r1 only), signature_algorithms
(ECDSA P-256 + RSA-PSS), key_share (Phase 1's P-256 ECDH), and
quic_transport_parameters (wrapping Phase 2c part 2's payload).

Verified via two exact RFC 8448 SS3 cross-checks (supported_versions, and
server_name with hostname "server") -- real sub-structures RFC 8448's own
ClientHello happens to share byte-for-byte with ours despite the overall
messages differing (x25519 vs our P-256, no QUIC extension there) -- plus
hand-derived structural tests for the P-256-specific extensions and a
full-message structural round trip using a real generated ECDH key.

/vreview caught and fixed a real gap: QuicTransportParameters deliberately
doesn't reject the four server-only fields itself (documented as "the
client-side caller's job" in transport_parameters.v) -- build_client_hello
is that caller and hadn't actually done it, so a caller could silently
produce a ClientHello violating RFC 9000 SS18.2's "a client MUST NOT
include any server-only transport parameter." Fixed with four explicit
checks, one per field; Phase-R verified by deliberately mixing up which
field one check tested and confirming exactly the corresponding test (and
no other) failed.

Also traced through a potential wire-integer overflow in the key_share/
server_name extension encoders (the same bug class already fixed once in
Phase 1's header.v) -- confirmed unreachable: the outer encode_extension
length check is a strict superset of the inner field's own overflow
condition, so it always catches oversized input first. Not a finding, but
worth the record given that bug class's history in this codebase.

/vreview: 1 finding, fixed (see above).
Adds tls13_server_hello.v: generic TLS extension-list parsing
(parse_extension_list/find_extension, RFC 8446 SS4.2, duplicate-extension
rejection mirroring transport_parameters.v's duplicate-ID rejection for
the analogous QUIC TLV sequence). parse_server_hello returns a
ParsedHelloRetryRequest | ParsedServerHello sum type, distinguished by RFC
8446 SS4.1.3's magic Random value -- independently verified via a live
SHA-256 computation of "HelloRetryRequest", not just transcribed from the
RFC text. Validates every statically-checkable RFC 8446 SS4.1.3 MUST.
parse_encrypted_extensions rejects early_data (0-RTT not offered).

Verified against RFC 8448 SS3's own ServerHello for the happy path, plus
hand-built HRR (with and without cookie) and error-path tests. Phase-R
verified the HRR-vs-ServerHello discrimination itself (not just that both
shapes parse), by forcing the branch and confirming the RFC-vector test
catches it.

/vreview caught and fixed a real gap: the real-ServerHello key_share branch
didn't reject an empty key_exchange, even though RFC 8446 SS4.2.8's
opaque key_exchange<1..2^16-1> requires at least 1 byte -- the parallel
check already existed for the cookie extension but was missed here. Fixed
with an explicit check, Phase-R verified.

Deferred to Phase 2c's still-pending state machine (needs connection-level
state this parsing layer doesn't have): second-HRR rejection, and
cross-checking EncryptedExtensions/cipher_suite/selected_version/key_share
group against what was actually offered rather than fixed v1 values.

/vreview: 1 finding, fixed (see above).
…arsing

Adds tls13_certificate.v: parse_certificate (RFC 8446 SS4.4.2 --
certificate_request_context + a non-empty CertificateEntry chain, each a
non-empty DER cert_data plus per-entry extensions), parse_certificate_verify
(RFC 8446 SS4.4.3 -- algorithm validated against v1's own fixed offered
set, signature bytes), and certificate_verify_signed_content (RFC 8446
SS4.4.3's exact 64-byte-pad + context-string + separator +
transcript-hash construction).

Verified byte-for-byte against RFC 8446's own CertificateVerify worked
example (transcript hash = 32 bytes of 0x01), extracted programmatically
and independently reconstructed before use. Phase-R verified the
algorithm-allowlist check.

/vreview caught and fixed an over-strict check: this file initially
rejected a zero-length CertificateVerify signature, but RFC 8446 SS4.4.3
declares opaque signature<0..2^16-1> -- zero is syntactically legal,
unlike cert_data<1..2^24-1> and key_exchange<1..2^16-1> (real minimums of
1, correctly enforced elsewhere in this file and in tls13_server_hello.v).
No real implementation ever sends an empty signature, so this wasn't an
active interop break, but it was inconsistent with this file's own
exact-RFC-fidelity approach -- removed, test now asserts acceptance at
that boundary instead.

Deliberately NOT yet built, as its own next step: the actual mbedTLS X.509
chain validation and mbedtls_pk_verify_ext signature verification this
parsed data feeds into.

/vreview: 1 finding, fixed (see above).
Adds net.mbedtls/x509_standalone.v: build_certificate_chain/
verify_certificate_chain/free_certificate_chain, standalone (no
mbedtls_ssl_context, same discipline as Phase 0's x509_standalone_test.v).
New public API added to net.mbedtls rather than net.quic reimplementing C
bindings -- matches how net.http's TLS clients already depend on
net.mbedtls instead of duplicating it. net.quic/tls13_certificate_chain.v
wraps this for a ParsedCertificate.

Every C-interop calling convention verified against mbedTLS's actual
vendored source (x509_crt.c), not memory: DER certs need their exact
length, not the +1 NUL-terminator convention this module's PEM helpers
use (a real out-of-bounds read otherwise, though only source-verified,
since mbedTLS's DER parser tolerates a too-long buflen for well-formed
input and no test can observe the difference); mbedtls_x509_crt_parse_der
always copies its input; repeated parse calls on the same chain correctly
append via a walk-to-tail-and-link algorithm (also source-verified only --
no second real test cert exists yet to exercise a genuine multi-cert
chain functionally).

/vreview caught and fixed two real issues: (1)
VerifiedCertificateChain.free() was double-free-prone on a second call --
the exact class of bug SSLConn.shutdown() already guards against with a
documented comment, a sibling that should have been checked first; fixed
with a mut receiver that nulls the pointer after freeing (also only
reasoning-verified, same as above -- a double-free this size doesn't
reliably crash this platform's allocator either way). (2)
verify_certificate_chain's CA-bundle parse check used `< 0`, inconsistent
with every other PEM-parsing call site in net.mbedtls (`!= 0`) -- a real
gap, since mbedtls_x509_crt_parse can return a positive count of certs
that failed to parse within an otherwise-valid bundle; `< 0` would have
silently accepted that. Fixed to `!= 0`.

Also resolves the plan's flagged open question about mbedTLS's PSS
salt-length semantics, ahead of building the code that needs it: checked
the vendored mbedtls_config.h -- MBEDTLS_USE_PSA_CRYPTO is disabled, so
mbedtls_pk_verify_ext's "salt length not verified under PSA crypto" caveat
doesn't apply here. No rsa_pss module fallback needed.

Deliberately not yet built: the mbedtls_pk_verify_ext CertificateVerify
signature verification itself (needs a small C shim to safely extract the
leaf cert's embedded public key, following the established
mbedtls_helpers.h pattern), and CertificateRequest rejection.

/vreview: 2 findings, both fixed (see above).
…cation

Adds mbedtls_pk_verify_ext-based signature checking, completing the last
unbuilt piece of Certificate/CertificateVerify handling before the client
state machine. net.mbedtls gains get_leaf_public_key/verify_ecdsa_signature/
verify_rsa_pss_signature (x509_standalone.v) plus a v_mbedtls_x509_crt_get_pk
C shim (mbedtls_helpers.h) to safely extract a parsed cert's embedded public
key. net.quic's VerifiedCertificateChain gains
verify_certificate_verify_signature, dispatching CertificateVerify's
algorithm to the matching digest and feeding
certificate_verify_signed_content's already-tested RFC 8446 §4.4.3
construction.

RSA-PSS salt length is pinned to the exact digest length per RFC 8446
§4.2.3, confirmed real (not a PSA-crypto no-op) by reading rsa.c and
cross-checked against mbedTLS's own TLS 1.3 code doing the identical thing.
Tested with a genuine RSA-PSS sign+verify round trip using the repo's
existing self-signed test cert's matching private key (real cryptography,
not just reasoning about C-binding correctness); no EC key exists anywhere
in this repo, so ECDSA is tested only via rejecting an incompatible key
type, documented as an honest coverage gap.

/vreview caught and fixed a real bug: calling
verify_certificate_verify_signature on an already-freed chain dereferenced
a garbage near-null pointer (UB) instead of erroring cleanly. Phase-R
confirmed a real, reliable segfault via an isolated probe before fixing;
closed with a nil guard and a permanent regression test.

Co-Authored-By: WOZCODE <contact@withwoz.com>
Adds Tls13ClientHandshake, wiring ClientHello construction through the
client's own Finished message: process_server_hello (real ECDHE + Handshake
secret derivation, handling both ServerHello and HelloRetryRequest),
process_encrypted_extensions (transport-parameter extraction +
initial_source_connection_id cross-check), process_certificate_or_request
(chain verification, CertificateRequest rejection), process_certificate_verify
(signature check against the transcript checkpoint Certificate left behind),
and process_finished (server Finished verification, Application secret
derivation, client's own Finished). TlsAlert + tls_alert_to_quic_error
implement RFC 9001 §4.8's alert-to-CONNECTION_CLOSE mapping; every fatal
path carries the mapped code via error_with_code.

Second-HelloRetryRequest rejection is implemented; a first HRR is reported
as explicitly not-yet-implemented rather than half-built, since it needs a
cookie extension build_client_hello doesn't speak yet.

Tested end-to-end with a genuine fake TLS 1.3 server: real ECDHE, real
RSA-PSS CertificateVerify signing, real Finished HMACs computed
independently and cross-checked against the client's own output — both
sides agreeing, not just "didn't crash". Certificate chain trust itself
isn't exercised end-to-end (no CA-flagged test cert exists in this repo,
same gap as Phase 2c part 6); one test asserts the expected propagated
failure, another installs an already-verified chain directly to test the
remaining steps independently.

/vreview caught and fixed two bugs: free() had no idempotency guard, and a
second call double-freed the ephemeral ECDHE key (Phase-R confirmed a real,
reliable crash, unlike this codebase's other double-free discussions);
and certificate_request_context's RFC 8446 §4.4.2 must-be-empty requirement
was parsed but never checked. Both have regression tests confirmed to fail
on the pre-fix code.

Co-Authored-By: WOZCODE <contact@withwoz.com>
Adds vlib/net/quic/testdata/tls13_vectors/: a real QUIC+TLS 1.3 handshake
captured from Cloudflare quiche (cloudflare/quiche-qns, pinned by digest
via quic-interop-runner's published image reference), running client +
server + tcpdump in Docker with SSLKEYLOGFILE set (undocumented but
functional for quiche, confirmed empirically). Decrypted and dissected
with tshark 4.6.6 using the keylog; extract_handshake.py reconstructs each
handshake message's exact raw bytes from tshark's PDML tree, cross-checked
against tshark's own independently-reported size for every message before
being trusted (this caught two real bugs in the extraction script itself,
documented in the README).

tls13_quiche_vector_test.v parses every real captured message with this
module's own production functions and validates exactly what a standard
keylog capture can prove: message parsing against an independent
implementation's real wire bytes; a real ECDSA P-256 CertificateVerify
signature verifying successfully (closing the gap Phase 2c's own signature
work documented — no EC private key exists anywhere in this repo, so
x509_standalone_signature_test.v could only exercise ECDSA rejection,
never a genuine accepted signature); and both directions' real Finished
MACs using the keylog's real traffic secrets. Documents honestly what
this capture does NOT prove (the Early/Handshake Secret HKDF chain itself,
which needs the raw ECDHE shared secret a standard keylog doesn't export —
already covered separately via RFC 8448's worked values).

Directory structure follows this repo's own crypto/blake2b/testdata/
convention rather than inventing a new one.

Co-Authored-By: WOZCODE <contact@withwoz.com>
quaesitor-scientiam and others added 5 commits July 21, 2026 00:18
parse_version_negotiation (header.v) and find_extension
(tls13_server_hello.v) were flagging CI's report-missing-fn-doc check on
PR vlang#27680. No behavior change -- doc comments only.

Co-Authored-By: WOZCODE <contact@withwoz.com>
Root cause of PR vlang#27680's msvc-windows/tcc-windows CI failures: both jobs
run on windows-2022, unlike gcc-windows (windows-2025), and windows-2022
does not ship the OpenSSL-Win64 dev package that vlib/crypto/ecdsa/
ecdsa.c.v and vlib/crypto/rsa_pss/rsa_pss.c.v already expect via their
hardcoded #flag windows search paths -- confirmed via CI logs:
  - msvc-windows: `LINK : fatal error LNK1104: cannot open file
    'crypto.lib'` (no import library to link against)
  - tcc-windows: exit code -1073741515 (STATUS_DLL_NOT_FOUND) at runtime
    (TCC's linker is more lenient and produces an exe, but the DLL isn't
    discoverable at runtime)
Every net.quic test file fails as a side effect, since the module
transitively imports crypto.ecdsa/crypto.rsa_pss for the QUIC handshake's
ECDHE key exchange.

Fix: install OpenSSL via choco (which lands at the exact
C:/Program Files/OpenSSL-Win64 path ecdsa.c.v/rsa_pss.c.v already check)
in both jobs, and add its bin/ directory to PATH so the runtime DLL is
discoverable too -- addressing both the MSVC link-time and TCC run-time
failure modes with one step. No application code changes.

Co-Authored-By: WOZCODE <contact@withwoz.com>
The previous commit (3524f88) guessed the choco openssl package
deploys to C:\Program Files\OpenSSL-Win64\ -- confirmed wrong by the
actual CI run: choco's log shows "Deployed to 'C:\Program Files\OpenSSL\'"
(no -Win64 suffix), which is the SECOND of ecdsa.c.v/rsa_pss.c.v's two
hardcoded #flag windows fallback paths, not the first. Point the
verification/PATH-append step at the path choco actually uses.

Co-Authored-By: WOZCODE <contact@withwoz.com>
Reverted the earlier attempt to fix this via per-compiler #flag gates
(#flag msvc/gcc/tinyc/clang) in ecdsa.c.v/rsa_pss.c.v -- confirmed by
reading vlib/v/builder/cflags.v's get_os_cflags() that V's flag selection
only ever honors an exact OS-name match, an unconditional flag, or one
hardcoded `mingw` special case; compiler-name gates are accepted by the
parser but never actually selected at build time. That change silently
dropped -lcrypto entirely for every compiler, breaking the local build
(confirmed: undefined references to EVP_PKEY_* symbols).

The real fix belongs in CI: MSVC's `-l` flag (unlike gcc/tcc/clang's,
which imply a `lib` prefix) has no such convention, so V passes -lcrypto
straight through as a literal `crypto.lib` reference -- and this OpenSSL
package only ships `libcrypto.lib`. Alias it with a plain file copy right
after install; a Windows import library doesn't embed its own filename,
so this is safe and needs no code changes at all.

tcc-windows still fails with a *different* symptom (STATUS_DLL_NOT_FOUND
at runtime, not a link-time error) -- left for separate investigation.

Co-Authored-By: WOZCODE <contact@withwoz.com>
…dows DLL failure

Two bugs found via CI sanitizer failures on PR vlang#27680, not caught by earlier
/vreview passes:

- tls13_client_hello_test.v: test_build_client_hello_structure created an
  ecdsa.PrivateKey/PublicKey without freeing them (ASan: 4232 bytes leaked).
  Every other net.quic/ecdsa test frees these; this one didn't.

- x509_standalone.v/x509_standalone_test.v: passed a .bytes()-derived buffer
  (exactly .len bytes, no NUL) to mbedtls_x509_crt_parse with length+1, which
  needs a real NUL terminator for PEM input -- a one-byte heap-buffer-overflow
  (confirmed via ASan). Fixed by using the V string's own internally
  NUL-terminated .str buffer instead, matching every other PEM call site in
  the module (ssl_connection.c.v).

For tcc-windows' STATUS_DLL_NOT_FOUND failure (every test touching
crypto.ecdsa/crypto.rsa_pss/net.quic fails to even launch): confirmed against
tinycc's own source that -lcrypto resolves via libcrypto.def, and TCC's
pe_load_def reads that file's own LIBRARY directive for the DLL name to embed
-- it does not derive it from the -l flag. If the installed .def's LIBRARY
name doesn't match the actual installed DLL (a known OpenSSL packaging
inconsistency after a DLL rename to a versioned filename), TCC embeds a
reference to a file that doesn't exist, while MSVC (which reads the DLL name
from the .lib's own already-correct build-time metadata) launches fine. Added
a CI diagnostic + self-healing step that reads each .def's LIBRARY directive
and aliases the real DLL under that name if it doesn't already exist, mirroring
the crypto.lib/libcrypto.lib alias already used for msvc-windows.

Tests: x509_standalone_test.v and tls13_client_hello_test.v pass locally via
vnew. Holding off on propagating any of this through the 6 downstream stacked
branches until vlang#27680 itself is confirmed green, per standing instruction.

Co-Authored-By: WOZCODE <contact@withwoz.com>
quaesitor-scientiam and others added 6 commits July 22, 2026 11:18
… a DLL

The previous fix (aliasing the real DLL under whatever name libcrypto.def's
LIBRARY directive declares) failed on the actual CI run: confirmed against
tinycc's own source (get_token(), tcc_add_dllref()) that TCC does NOT strip
quotes from a quoted LIBRARY value and does NOT append a .dll extension --
it uses the raw token as-is. This OpenSSL 4.0.1 package's libcrypto.def/
libssl.def declare `LIBRARY "libcrypto-4-x64"` (quoted, no extension), so TCC
embeds the literal string `"libcrypto-4-x64"` -- quote characters included --
as the runtime DLL name. `"` is illegal in a Windows filename, so no file can
ever be created to match it; a file-aliasing workaround is structurally
impossible here (confirmed live: the CI run failed at Copy-Item with "The
filename, directory name, or volume label syntax is incorrect" for exactly
this reason).

Fix at the source instead: rewrite each .def's LIBRARY line in place (strip
quotes, append .dll if missing) before TCC ever reads it. The corrected name
(libcrypto-4-x64.dll) already exists as a real file in bin\ -- this package
ships both v3 and v4 runtime DLLs side by side -- so no aliasing is needed
once the .def itself is valid.

Verified locally (PowerShell) against the exact reported LIBRARY line format
plus 3 edge cases (already-correct, no LIBRARY line, unquoted-missing-.dll)
before pushing, to avoid a third failed CI round-trip on a scripting bug.

Co-Authored-By: WOZCODE <contact@withwoz.com>
…eaders)

docker-ubuntu-musl's full `test-self vlib` run fails to compile every file
touching crypto.ecdsa/crypto.rsa_pss/net.quic with "Header file
<openssl/ecdsa.h> ... was not found" -- this container image never had
OpenSSL dev headers installed. Confirmed this is a real, new gap (not the
job's known pre-existing flakiness): master's own docker-ubuntu-musl failure
has a completely different, unrelated cause (missing X11/Xlib.h for
sokol.sapp, only 1 file), while ours cascades to all 16 net.quic test files
(V compiles a package as one unit, so even ecdsa-unrelated files like
varint_test.v get swept in).

The sibling docker-alpine-musl-gcc/tcc jobs never hit this because they run
narrower test subsets (vlib/builtin only, or VTEST_JUST_ESSENTIAL=1) that
don't exercise crypto.ecdsa/net.quic at all.

libssl-dev is the correct Debian/Ubuntu package (this container uses apt, not
apk) -- confirmed the existing libsqlite3-dev dependency already proves a
plain apt-installed glibc dev package links fine under this job's
`-cc musl-gcc` VFLAGS, so mixing libc ABIs is not a concern here.

Co-Authored-By: WOZCODE <contact@withwoz.com>
…ubuntu-musl

The previous commit (e4b3708) diagnosed docker-ubuntu-musl's "openssl/ecdsa.h
not found" failure as a missing package -- wrong: apt confirmed libssl-dev was
ALREADY installed. Reproduced the actual failure directly in a local musl-gcc
sandbox (WSL + musl-tools) using the exact guarded-include codegen V emits
(`#if __has_include(<openssl/ecdsa.h>) ... #error ...`).

Root cause, confirmed by direct repro: ecdsa.c.v/rsa_pss.c.v's unconditional
`#flag -I/usr/include/openssl` points ONE DIRECTORY TOO DEEP -- `#include
<openssl/ecdsa.h>` already spells out the openssl/ prefix, so that flag makes
the compiler look for .../openssl/openssl/ecdsa.h. Every other compiler masks
this because /usr/include is on its implicit default search path regardless;
musl-gcc's -nostdinc removes that fallback, surfacing the bug for the first
time. Fixed both files' unconditional and linux-specific flags to point at
the parent include/ dir instead, matching the already-correct darwin/windows
flags. Verified against the reviewed commit's exact error text via a local
musl-gcc build (exit 1, VERROR_MESSAGE) before the fix, exit 0 (has_include
succeeds) after.

That alone isn't sufficient, also confirmed by direct repro: fixing the -I
path only gets past __has_include(); actually compiling then pulls in
Ubuntu's glibc-targeted OpenSSL headers, which transitively include glibc's
own <stdio.h>/<time.h>/<limits.h> -- these conflict at the type level with
musl's own parallel headers already active via musl-gcc's -isystem wrapper
(__gnuc_va_list/__time64_t undefined, va_list/__BYTE_ORDER redefined
incompatibly). A glibc-targeted OpenSSL dev package is not usable from a
musl-gcc build without a musl-native OpenSSL -- out of scope here.

Pragmatic resolution: added crypto.ecdsa/crypto.rsa_pss/net.quic's test files
to vtest-self.v's existing skip_on_ubuntu_musl list, the established
mechanism for exactly this class of problem (already used for an identical
glibc-vs-musl-gcc conflict: pg_sqlite_consistency_test.v's "pg + sqlite dev
headers pull in glibc-only sys/cdefs.h on musl-gcc"). Comment documents the
full chain so a future net.quic/crypto.rsa_pss test file addition knows to
extend the list too.

The -I path fix itself is kept regardless of the musl-gcc conclusion -- it's
a genuine correctness fix matching the file's own established convention on
every other platform (darwin/windows already point at the parent dir).

Co-Authored-By: WOZCODE <contact@withwoz.com>
Same pre-existing sokol.sapp/X11/Xlib.h gap already skipped for
draw_rect_empty_test.v and text_rendering_test.v -- this docker-ubuntu-musl
image has never had X11 dev headers, unrelated to anything in this PR. With
this, docker-ubuntu-musl's only remaining failure on http3-quic-foundation
is resolved: 16 -> 1 (this one) -> 0.

Co-Authored-By: WOZCODE <contact@withwoz.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant