nova-tls
TLS client/server for Nova — client + server handshake,
certificate-verification modes (system roots / custom CA / SPKI pinning /
insecure-skip-verify for tests), mutual TLS (mTLS), and io.Read/io.Write
conformance on top of std.net.TcpStream.
Backend: mbedTLS via a
thin C shim (native/tls_c_shim.c) — pure C, no Rust/cargo required to build
this package. mbedTLS v3.6.2 is vendored at native/mbedtls/ (Plan 193 Ф.2
gate-3, 2026-07-12 — see native/mbedtls/VENDORED.md) and self-builds on
first nova test/nova build via the generic [ffi] vendor_src_dirs
build-and-cache mechanism — no manual vcpkg/system install step required
(a manually dropped-in prebuilt lib under native/lib/ still works too and
is used as-is, skipping the vendor build).
Extracted from the Nova monorepo’s std/tls (Plan 116 core design + Plan 195
mbedTLS backend swap) into a standalone repository per
Plan 193
— the reference instance of Nova’s native-module pattern
(Plan 195:
.nv facade + .c shim + prebuilt .lib, wired through [ffi], zero Rust).
Public API is unchanged from std.tls — only the module path moved
(std.tls.* -> tls.*; see “Module path” note below).
Usage
import tls.{TlsStream, ClientConfig, VerificationMode}
import std.net.{Net, TcpStream, SocketAddr, real_net}
fn fetch(host str, port u16) Net -> Result[(), TlsError] {
ro tcp = TcpStream.connect(SocketAddr.new(host, port))!!
ro cfg = ClientConfig.new(host)
consume stream = TlsStream.connect(tcp, cfg)!!
stream.write_all("GET / HTTP/1.1\r\nHost: ${host}\r\n\r\n".bytes())!!
ro resp = stream.read_to_vec(4096)!!
stream.close()
Ok(())
}
Layout
nova-tls/
├── nova.toml [package] name = "tls"; [lib] src = "src"; [ffi] native shim
├── native/
│ ├── tls_c_shim.c mbedTLS backend (compiled via [ffi] c_shims)
│ ├── tls_shim.h C-side prototypes (Nova <-> C ABI contract)
│ └── tls_mozilla_roots.h compiled-in Mozilla root CA bundle
└── src/
├── ffi.nv extern "C" fn declarations against native/tls_c_shim.c
├── error.nv TlsError (typed error surface)
├── config.nv ClientConfig / ServerConfig / VerificationMode / ClientCertMode
├── client.nv TlsStream.connect (client handshake)
├── server.nv TlsStream.accept (server handshake)
├── stream.nv TlsStream (sans-I/O pump + io.Read/io.Write conformance)
├── *_test.nv peer tests (same-module, positive)
├── neg/ EXPECT_COMPILE_ERROR fixtures (standalone CUs)
└── testdata/ self-signed fixture certs (server + mTLS client CA/leaf)
Module path
D78 rev-4 (root peers, spec/decisions/07-modules.md “Root peers —
.nv-файлы прямо в source root”) lets .nv files that sit directly in
the package’s source root (src/, per [lib] src above) declare the
single-segment module <package_name> form — a peer group analogous to
Cargo’s lib.rs. This package’s whole surface lives directly in src/
(src/{client,server,stream,...}.nv, all declaring module tls), so the
resulting module is the bare package name, tls — no statter. Import as
import tls.{TlsStream, ...}, both from another package’s [dependencies]
consumer and from an independent same-package file (e.g. src/neg/*.nv
uses import tls.{ClientConfig} to reach these peers).
Before the rev-4 amendment this package used to live at src/tls/*.nv
under the older rev-3 parent_dir.target rule, which forced the domain
folder to repeat the package name (module tls.tls, import
tls.tls.{...}) — migrated to root peers 2026-07-13 (Plan 202 Ф.3).
Building standalone
Requires the Nova toolchain (nova CLI + clang). No Rust/cargo, no manual
mbedTLS install — mbedTLS self-builds from the vendored source at
native/mbedtls/ on first run (Plan 193 Ф.2 gate-3; drop a prebuilt
mbedtls/mbedx509/mbedcrypto under native/lib/ instead if you’d
rather skip that one-time build, e.g. vcpkg install mbedtls).
# Boehm GC (mandatory Nova runtime dep) needs its own lib/include dirs —
# point NOVA_GC_LIB_DIR (+ optional NOVA_GC_INCLUDE_DIR) at a prebuilt
# bdwgc if it isn't reachable via the default vcpkg/system lookup
# (see compiler-codegen/src/test_runner.rs detect_boehm).
#
# `nova` does not (yet) bundle/locate the standard library relative to the
# nova.exe install — a standalone package must point it at a Nova checkout's
# std/ via NOVA_STD_PATH (compiler-codegen/src/manifest.rs resolve_std_path):
export NOVA_STD_PATH=/path/to/nova/std
# Ditto for the compiler's own C runtime (compiler-codegen/nova_rt/ + the
# libuv submodule it needs) — NOVA_CG_INCLUDE / NOVA_RT_DIR, symmetric with
# NOVA_STD_PATH above (resolve_paths in nova-cli/src/main.rs; this is what
# closed the Plan 193 Ф.1 blocker previously documented here — no more need
# to vendor the compiler's runtime into this repo):
export NOVA_CG_INCLUDE=/path/to/nova/compiler-codegen
export NOVA_RT_DIR=/path/to/nova/compiler-codegen/nova_rt
# Use `nova test`, not `nova build <single-file>`, for anything beyond a
# syntax/import smoke check — this package has no `main`, and isolated
# single-file builds of a library CU can hit generic-inference ambiguities
# that a full test CU resolves via its own call sites (verified upstream:
# the same `ffi.nv` hits it identically inside the Nova monorepo).
nova test src
License
Dual-licensed under MIT or Apache-2.0, at your option — same terms as the Nova compiler and standard library.