// Wire // ==== // JS twins of wire.c: one char code per octet in, each char's low byte out. function wire_text(b, n) { let s = ""; for (let i = 0; i < n; i += 8192) { s += String.fromCharCode.apply(null, b.subarray(i, Math.min(i + 8192, n))); } return s; } // A char above 255 is not an octet; the caller gets EINVAL. function wire_octets(data) { const b = new Uint8Array(data.length); for (let i = 0; i < data.length; i += 1) { const c = data.charCodeAt(i); if (c > 255) { return null; } b[i] = c; } return b; } // Twins of wire_words and wire_words_octets in wire.c: a Tuple (n, words), with // byte i in bits 8*(i%4) of word i/4 and 2^d words, the fewest that hold n. function wire_words(b, n) { const w = Math.ceil(n / 4); let size = 1; while (size < w) { size *= 2; } const a = Array(size).fill(0); for (let i = 0; i < n; i += 1) { a[i >> 2] = (a[i >> 2] | (b[i] << (8 * (i & 3)))) >>> 0; } return { $: CID(Tuple), fst: n, snd: a }; } // null when n runs past the words. function wire_words_octets(n, a) { n = Number(n); if (n > 4 * a.length) { return null; } const b = new Uint8Array(n); for (let i = 0; i < n; i += 1) { b[i] = (a[i >> 2] >>> (8 * (i & 3))) & 255; } return b; } // A deadline in performance.now() ms, or undefined for none. function wire_deadline(ms) { return Number(ms) ? performance.now() + Number(ms) : undefined; } function wire_late(at) { return at !== undefined && performance.now() >= at; } function wire_timedout() { return io_sys().mac ? 60 : 110; } function accept_deadline(listener, ms, k) { if (!Number.isInteger(ms) || ms < 1 || ms > 2147483647) { return io_tup(listener, io_fail(22)); } const sys = io_sys(); const deadline = wire_deadline(ms); const go = () => { for (;;) { if (wire_late(deadline)) { return io_tup(listener, io_fail(wire_timedout())); } const fd = sys.accept(listener, null, null); if (fd >= 0) { const flags = sys.fcntl(fd, 3, 0); if (flags < 0 || sys.fcntl(fd, 4, flags | (sys.mac ? 4 : 0x800)) < 0) { const code = sys.errno(); sys.close(fd); return io_tup(listener, io_fail(code)); } return io_tup(listener, io_done(fd)); } const code = sys.errno(); if (code === 4) continue; // EINTR does not reset the budget. if (code === (sys.mac ? 35 : 11)) { io_park_on(listener, false, k, go, deadline); return undefined; } return io_tup(listener, io_fail(code)); } }; return go(); } function wire_ip_lib() { if (globalThis.BEND_WIRE_IP === undefined) { const ffi = require("bun:ffi"); const mac = process.platform === "darwin"; globalThis.BEND_WIRE_IP = { ffi, mac, c: ffi.dlopen(mac ? "libSystem.dylib" : "libc.so.6", { inet_pton: { args: ["i32", "cstring", "ptr"], returns: "i32" }, inet_ntop: { args: ["i32", "ptr", "ptr", "u64"], returns: "ptr" }, }).symbols, }; } return globalThis.BEND_WIRE_IP; } function wire_numeric_addr(host, port) { if (host.includes("\0") || port > 65535) { return null; } const v4 = io_addr(host, port); if (v4 !== null) { return v4; } const { ffi, mac, c } = wire_ip_lib(); const family = mac ? 30 : 10; const ip = new Uint8Array(16); if (c.inet_pton(family, Buffer.from(host + "\0"), ffi.ptr(ip)) !== 1) { return null; } const addr = new Uint8Array(28); if (mac) { addr[0] = 28; addr[1] = family; } else { addr[0] = family; } addr[2] = (port >> 8) & 255; addr[3] = port & 255; addr.set(ip, 8); return addr; } function wire_ipv6_text(peer) { const { ffi, mac, c } = wire_ip_lib(); const out = new Uint8Array(46); c.inet_ntop(mac ? 30 : 10, ffi.ptr(peer.subarray(8, 24)), ffi.ptr(out), out.length); return Buffer.from(out).toString("utf8").replace(/\0.*/, ""); } function recv(socket, max, ms, k) { return wire_recv(socket, max, ms, k, wire_text); } function recv_words(socket, max, ms, k) { return wire_recv(socket, max, ms, k, wire_words); } function wire_recv(socket, max, ms, k, out) { const sys = io_sys(); const fd = socket; const b = new Uint8Array(Math.max(Number(max), 1)); const again = sys.mac ? 35 : 11; const at = wire_deadline(ms); const go = () => { const n = Number(sys.recv(fd, sys.ptr(b), Number(max), 0)); if (n < 0) { const code = sys.errno(); if (code === again) { if (wire_late(at)) { return io_tup(socket, io_fail(wire_timedout())); } io_park_on(fd, false, k, go, at); return undefined; } return io_tup(socket, io_fail(code)); } return io_tup(socket, io_done(out(b, n))); }; return go(); } function send(socket, data, k) { return wire_send(socket, wire_octets(data), k); } function send_words(socket, n, words, k) { return wire_send(socket, wire_words_octets(n, words), k); } function send_words_deadline(socket, len, words, ms, k) { const sys = io_sys(); let made = 0; let b = null; const end = (code) => { b = null; // Release the host copy; the consumed Bend array is not retained. return io_tup(socket, io_tup(made, code ? io_fail(code) : io_done({ $: CID(Unit) }))); }; if (!Number.isInteger(ms) || ms < 1 || ms > 2147483647 || !Number.isInteger(len) || len < 0 || len > 4294967295 || len > 4 * words.length) { return end(22); } const deadline = wire_deadline(ms); b = wire_words_octets(len, words); words = null; const go = () => { while (made < b.length) { if (wire_late(deadline)) return end(wire_timedout()); const part = b.subarray(made); const n = Number(sys.send(socket, sys.ptr(part), part.length, 0)); if (n < 0) { const code = sys.errno(); if (code === 4) continue; // EINTR, without resetting the budget. if (code === (sys.mac ? 35 : 11)) { io_park_on(socket, true, k, go, deadline); return undefined; } return end(code); } if (n === 0) return end(32); // EPIPE: no progress must not spin forever. made += n; } return end(0); }; return go(); } function wire_send(socket, b, k) { const sys = io_sys(); const fd = socket; if (b === null) { return io_tup(socket, io_fail(22)); } const again = sys.mac ? 35 : 11; const go = (at) => { while (at < b.length) { const part = b.subarray(at); const n = Number(sys.send(fd, sys.ptr(part), part.length, 0)); if (n < 0) { const code = sys.errno(); if (code === again) { io_park_on(fd, true, k, () => go(at)); return undefined; } return io_tup(socket, io_fail(code)); } at += n; } return io_tup(socket, io_done({ $: CID(Unit) })); }; return go(0); } function recv_from(socket, max, ms, k) { return wire_recv_from(socket, max, ms, k, wire_text); } function recv_from_words(socket, max, ms, k) { return wire_recv_from(socket, max, ms, k, wire_words); } function wire_recv_from(socket, max, ms, k, out) { const sys = io_sys(); const fd = socket; const b = new Uint8Array(Math.max(Number(max), 1)); const peer = new Uint8Array(28); const len = new Uint32Array([peer.length]); const deadline = wire_deadline(ms); const go = () => { len[0] = peer.length; const n = Number(sys.recvfrom(fd, sys.ptr(b), Number(max), 0, sys.ptr(peer), sys.ptr(len))); if (n < 0) { const code = sys.errno(); if (code === (sys.mac ? 35 : 11)) { if (wire_late(deadline)) { return io_tup(socket, io_fail(wire_timedout())); } io_park_on(fd, false, k, go, deadline); return undefined; } return io_tup(socket, io_fail(code)); } const family = sys.mac ? peer[1] : peer[0] | (peer[1] << 8); const host = family === (sys.mac ? 30 : 10) ? wire_ipv6_text(peer) : family === 2 ? peer[4] + "." + peer[5] + "." + peer[6] + "." + peer[7] : null; if (host === null) { return io_tup(socket, io_fail(22)); } const port = (peer[2] << 8) | peer[3]; return io_tup(socket, io_done(io_tup(host, port, out(b, n)))); }; return go(); } function send_to(socket, host, port, data, k) { return wire_send_to(socket, host, port, wire_octets(data), k); } function send_to_words(socket, host, port, n, words, k) { return wire_send_to(socket, host, port, wire_words_octets(n, words), k); } function wire_send_to(socket, host, port, b, k) { const sys = io_sys(); const fd = socket; const at = wire_numeric_addr(host, Number(port)); if (at === null || b === null) { return io_tup(socket, io_fail(22)); } const go = () => { const sent = sys.sendto(fd, sys.ptr(b), b.length, 0, sys.ptr(at), at.length); if (Number(sent) < 0) { const code = sys.errno(); if (code === (sys.mac ? 35 : 11)) { io_park_on(fd, true, k, go); return undefined; } return io_tup(socket, io_fail(code)); } return io_tup(socket, io_done({ $: CID(Unit) })); }; return go(); } // TLS // === // Twins of the TLS effects in wire.c: OpenSSL 3 through bun:ffi, the SSL // object per fd in a Map, peer verification always on, TLS 1.2 floor. function wire_tls() { if (globalThis.BEND_TLS !== undefined) { return globalThis.BEND_TLS; } globalThis.BEND_TLS = null; const ffi = require("bun:ffi"); const paths = [process.env.BEND_LIBSSL, "/opt/homebrew/opt/openssl@3/lib/libssl.3.dylib", "/usr/local/opt/openssl@3/lib/libssl.3.dylib", "libssl.3.dylib", "libssl.so.3"]; const T = { i: "i32", l: "i64", U: "u64", p: "ptr", c: "cstring", v: "void" }; const syms = Object.fromEntries(("TLS_client_method:>p SSL_CTX_new:p>p" + " SSL_CTX_free:p>v SSL_CTX_load_verify_locations:ppp>i" + " SSL_CTX_set_default_verify_paths:p>i SSL_CTX_set_verify:pip>v" + " SSL_CTX_ctrl:pilp>l SSL_new:p>p SSL_set_fd:pi>i" + " SSL_ctrl:pilp>l SSL_set1_host:pp>i SSL_connect:p>i SSL_read:ppi>i" + " SSL_write:ppi>i SSL_get_error:pi>i SSL_shutdown:p>i SSL_free:p>v" + " SSL_get_verify_result:p>l X509_verify_cert_error_string:l>c SSL_set_alpn_protos:ppi>i SSL_get0_alpn_selected:ppp>v" + " SSL_use_certificate_chain_file:pp>i SSL_use_PrivateKey_file:ppi>i SSL_check_private_key:p>i").split(" ").map((s) => { const [name, args, ret] = s.split(/[:>]/); return [name, { args: [...args].map((a) => T[a]), returns: T[ret] }]; })); for (const p of paths) { if (!p) { continue; } try { const s = ffi.dlopen(p, syms).symbols; const ctx = s.SSL_CTX_new(s.TLS_client_method()); if (!ctx || s.SSL_CTX_set_default_verify_paths(ctx) !== 1) { return null; } s.SSL_CTX_set_verify(ctx, 1, null); s.SSL_CTX_ctrl(ctx, 123, 0x0303n, null); // A bare EOF is an error. close_notify is the only clean close. globalThis.BEND_TLS = { s, ctx, ffi, by: new Map(), own: new Map() }; return globalThis.BEND_TLS; } catch { continue; } } return null; } function wire_alpn_pack(list) { const b = []; for (const part of list.split(",")) { const name = part; if (name.length === 0 || name.length > 255) { return null; } b.push(name.length); for (let i = 0; i < name.length; i += 1) { b.push(name.charCodeAt(i)); } } return new Uint8Array(b); } function wire_alpn_selected(t, ssl) { const data = new BigUint64Array(1); const len = new Uint32Array(1); t.s.SSL_get0_alpn_selected(ssl, t.ffi.ptr(data), t.ffi.ptr(len)); const n = Number(len[0]); if (n === 0) { return wire_text(new Uint8Array(0), 0); } const read = t.ffi.read; const p = Number(data[0]); const view = new Uint8Array(n); for (let i = 0; i < n; i += 1) { view[i] = read.u8(p, i); } return wire_text(view, n); } function wire_cstr(t, text) { const b = Buffer.from(text + "\0", "utf8"); return { b, p: t.ffi.ptr(b) }; } function wire_tls_drop(t, fd) { const ssl = t.by.get(fd); if (ssl) { t.s.SSL_free(ssl); t.by.delete(fd); } const own = t.own && t.own.get(fd); if (own) { t.s.SSL_CTX_free(own); t.own.delete(fd); } } function wire_tls_connect_go(t, socket, ssl, at, k, done) { const s = t.s; const fd = socket; const go = () => { const r = s.SSL_connect(ssl); if (r === 1) { return io_tup(socket, io_done(done(t, ssl))); } const err = s.SSL_get_error(ssl, r); if (err === 2 || err === 3) { if (wire_late(at)) { wire_tls_drop(t, fd); return io_tup(socket, io_fail(wire_timedout())); } io_park_on(fd, err === 3, k, go, at); return undefined; } const v = s.SSL_get_verify_result(ssl); const why = v !== 0n ? String(s.X509_verify_cert_error_string(v)) : "TLS handshake failed"; wire_tls_drop(t, fd); return io_tup(socket, { $: CID(Fail), error: io_tup(100, why) }); }; return go(); } function wire_tls_ca(t, ca) { if (!ca || ca.includes("\0")) { return { code: 22, why: "Invalid TLS CA path" }; } const s = t.s; const ctx = s.SSL_CTX_new(s.TLS_client_method()); if (!ctx) { return { code: 12, why: "TLS CA load failed" }; } const path = wire_cstr(t, ca); if (s.SSL_CTX_load_verify_locations(ctx, path.p, null) !== 1) { s.SSL_CTX_free(ctx); return { code: 22, why: "TLS CA load failed" }; } s.SSL_CTX_set_verify(ctx, 1, null); s.SSL_CTX_ctrl(ctx, 123, 0x0303n, null); return ctx; } function wire_tls_connect_setup(socket, host, ms, k, setup, done, ca) { const at = wire_deadline(ms); const t = wire_tls(); if (t === null) { return io_tup(socket, { $: CID(Fail), error: io_tup(2, "TLS needs OpenSSL 3 (libssl.3); set BEND_LIBSSL to its path") }); } const s = t.s; const fd = socket; let ctx = t.ctx; let own = null; if (ca !== undefined) { const made = wire_tls_ca(t, ca); if (made && made.why) { return io_tup(socket, { $: CID(Fail), error: io_tup(made.code, made.why) }); } own = made; ctx = made; } const ssl = s.SSL_new(ctx); if (!ssl) { if (own) s.SSL_CTX_free(own); return io_tup(socket, io_fail(12)); } t.by.set(fd, ssl); if (own) t.own.set(fd, own); const name = wire_cstr(t, host); const fail = (why, code = 100) => { wire_tls_drop(t, fd); return io_tup(socket, { $: CID(Fail), error: io_tup(code, why) }); }; if (s.SSL_set_fd(ssl, fd) !== 1 || Number(s.SSL_ctrl(ssl, 55, 0n, name.p)) !== 1 || s.SSL_set1_host(ssl, name.p) !== 1) { return fail("TLS setup failed"); } const err = setup(t, ssl); if (err !== null) { return typeof err === "string" ? fail(err) : fail(err.why, err.code); } return wire_tls_connect_go(t, socket, ssl, at, k, done); } function tls_connect(socket, host, ms, k) { return wire_tls_connect_setup(socket, host, ms, k, () => null, () => ({ $: CID(Unit) })); } function tls_connect_alpn(socket, host, ms, protos, k) { return wire_tls_connect_setup(socket, host, ms, k, (t, ssl) => { const abuf = wire_alpn_pack(protos); if (abuf === null) { return "ALPN setup failed"; } if (t.s.SSL_set_alpn_protos(ssl, t.ffi.ptr(abuf), abuf.length) !== 0) { return "ALPN setup failed"; } return null; }, (t, ssl) => wire_alpn_selected(t, ssl)); } function tls_connect_cert(socket, host, ms, cert, key, k) { return wire_tls_connect_setup(socket, host, ms, k, (t, ssl) => { if (!cert || cert.includes("\0") || !key || key.includes("\0")) { return { code: 22, why: "Invalid TLS client certificate or key path" }; } const certPath = wire_cstr(t, cert); const keyPath = wire_cstr(t, key); if (t.s.SSL_use_certificate_chain_file(ssl, certPath.p) !== 1) { return { code: 22, why: "TLS client certificate load failed" }; } if (t.s.SSL_use_PrivateKey_file(ssl, keyPath.p, 1) !== 1) { return { code: 22, why: "TLS client key load failed" }; } if (t.s.SSL_check_private_key(ssl) !== 1) { return { code: 22, why: "TLS client key does not match certificate" }; } return null; }, () => ({ $: CID(Unit) })); } function tls_connect_ca(socket, host, ms, ca, k) { return wire_tls_connect_setup(socket, host, ms, k, () => null, () => ({ $: CID(Unit) }), ca); } function tls_connect_alpn_ca(socket, host, ms, protos, ca, k) { return wire_tls_connect_setup(socket, host, ms, k, (t, ssl) => { const abuf = wire_alpn_pack(protos); if (abuf === null || t.s.SSL_set_alpn_protos(ssl, t.ffi.ptr(abuf), abuf.length) !== 0) { return "ALPN setup failed"; } return null; }, (t, ssl) => wire_alpn_selected(t, ssl), ca); } function tls_send(socket, data, k) { return wire_tls_send(socket, wire_octets(data), k); } function tls_send_words(socket, n, words, k) { return wire_tls_send(socket, wire_words_octets(n, words), k); } function wire_tls_send(socket, b, k) { const t = wire_tls(); const ssl = t && t.by.get(socket); if (!ssl) { return io_tup(socket, io_fail(9)); } if (b === null) { return io_tup(socket, io_fail(22)); } const go = (at) => { while (at < b.length) { const n = t.s.SSL_write(ssl, t.ffi.ptr(b, at), b.length - at); if (n > 0) { at += n; continue; } const err = t.s.SSL_get_error(ssl, n); if (err === 2 || err === 3) { io_park_on(socket, err === 3, k, () => go(at)); return undefined; } return io_tup(socket, io_fail(32)); } return io_tup(socket, io_done({ $: CID(Unit) })); }; return go(0); } function tls_recv(socket, max, ms, k) { return wire_tls_recv(socket, max, ms, k, wire_text); } function tls_recv_words(socket, max, ms, k) { return wire_tls_recv(socket, max, ms, k, wire_words); } function wire_tls_recv(socket, max, ms, k, out) { const at = wire_deadline(ms); const t = wire_tls(); const ssl = t && t.by.get(socket); if (!ssl) { return io_tup(socket, io_fail(9)); } const b = new Uint8Array(Math.max(Number(max), 1)); const go = () => { const n = t.s.SSL_read(ssl, t.ffi.ptr(b), b.length); if (n > 0) { return io_tup(socket, io_done(out(b, n))); } const err = t.s.SSL_get_error(ssl, n); if (err === 2 || err === 3) { if (wire_late(at)) { return io_tup(socket, io_fail(wire_timedout())); } io_park_on(socket, err === 3, k, go, at); return undefined; } if (err === 6) { return io_tup(socket, io_done(out(b, 0))); } return io_tup(socket, { $: CID(Fail), error: io_tup(5, "TLS read failed") }); }; return go(); } function tls_close(socket) { const t = wire_tls(); const ssl = t && t.by.get(socket); if (ssl) { t.s.SSL_shutdown(ssl); wire_tls_drop(t, socket); } io_sys().close(socket); return { $: CID(Unit) }; } // Twin of connect in wire.c: a TCP connect with a deadline. function connect(host, port, ms, k) { const sys = io_sys(); const addr = wire_numeric_addr(host, Number(port)); if (addr === null) { return io_fail(22); } const fd = sys.socket(addr.length === 28 ? (sys.mac ? 30 : 10) : 2, 1, 0); if (fd < 0) { return io_fail(sys.errno()); } const at = wire_deadline(ms); const end = (code) => { if (code !== 0) { sys.close(fd); return io_fail(code); } return io_done(fd); }; const error = () => { const v = new Int32Array([0]); const l = new Uint32Array([4]); return sys.getsockopt(fd, sys.mac ? 0xffff : 1, sys.mac ? 0x1007 : 4, sys.ptr(v), sys.ptr(l)) < 0 ? sys.errno() : v[0]; }; if (sys.fcntl(fd, 4, sys.fcntl(fd, 3, 0) | (sys.mac ? 4 : 0x800)) < 0) { return end(sys.errno()); } const pending = [sys.mac ? 36 : 115, sys.mac ? 37 : 114]; const done = sys.mac ? 56 : 106; const go = () => { const failed = error(); if (failed !== 0) { return end(failed); } const code = sys.connect(fd, sys.ptr(addr), addr.length) >= 0 ? 0 : sys.errno(); if (code === 0 || code === done) { return end(0); } if (pending.includes(code)) { if (wire_late(at)) { return end(wire_timedout()); } io_park_on(fd, true, k, go, at); return undefined; } return end(code); }; return go(); } io_eff(CID(connect), connect); io_eff(CID(accept.deadline), accept_deadline); io_eff(CID(recv), recv); io_eff(CID(send), send); io_eff(CID(recv_from), recv_from); io_eff(CID(send_to), send_to); io_eff(CID(recv_from.words), recv_from_words); io_eff(CID(send_to.words), send_to_words); io_eff(CID(tls.connect), tls_connect); io_eff(CID(tls.connect.alpn), tls_connect_alpn); io_eff(CID(tls.connect.cert), tls_connect_cert); io_eff(CID(tls.connect.ca), tls_connect_ca); io_eff(CID(tls.connect.alpn.ca), tls_connect_alpn_ca); io_eff(CID(tls.send), tls_send); io_eff(CID(tls.recv), tls_recv); io_eff(CID(tls.close), tls_close); io_eff(CID(recv.words), recv_words); io_eff(CID(send.words), send_words); io_eff(CID(send.words.deadline), send_words_deadline); io_eff(CID(tls.recv.words), tls_recv_words); io_eff(CID(tls.send.words), tls_send_words);