~jojo/Carth

6731d0ffb1881ecb08ab51e19de1733995b2cb55 — JoJo 13 days ago c7e7355 master
std-rs: add tcp connection, tls connection w rustls

Basically works, but rustls won't last. It together with webpki is way
to restrictive in what certificates validate, so it's not very usable
for non-web, self-signed scenarios like gemini.
5 files changed, 444 insertions(+), 21 deletions(-)

M std-rs/Cargo.lock
M std-rs/Cargo.toml
A std-rs/src/io.rs
M std-rs/src/lib.rs
A std-rs/src/net.rs
M std-rs/Cargo.lock => std-rs/Cargo.lock +237 -0
@@ 1,14 1,251 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "base64"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"

[[package]]
name = "bumpalo"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"

[[package]]
name = "carth-std-rs"
version = "0.3.1"
dependencies = [
 "libc",
 "rustls",
 "webpki",
]

[[package]]
name = "cc"
version = "1.0.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed67cbde08356238e75fc4656be4749481eeffb09e19f320a25237d5221c985d"

[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"

[[package]]
name = "js-sys"
version = "0.3.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca059e81d9486668f12d455a4ea6daa600bd408134cd17e3d3fb5a32d1f016f8"
dependencies = [
 "wasm-bindgen",
]

[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"

[[package]]
name = "libc"
version = "0.2.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005"

[[package]]
name = "log"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
dependencies = [
 "cfg-if",
]

[[package]]
name = "once_cell"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "260e51e7efe62b592207e9e13a68e43692a7a279171d6ba57abd208bf23645ad"

[[package]]
name = "proc-macro2"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
dependencies = [
 "unicode-xid",
]

[[package]]
name = "quote"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
dependencies = [
 "proc-macro2",
]

[[package]]
name = "ring"
version = "0.16.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "952cd6b98c85bbc30efa1ba5783b8abf12fec8b3287ffa52605b9432313e34e4"
dependencies = [
 "cc",
 "libc",
 "once_cell",
 "spin",
 "untrusted",
 "web-sys",
 "winapi",
]

[[package]]
name = "rustls"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d1126dcf58e93cee7d098dbda643b5f92ed724f1f6a63007c1116eed6700c81"
dependencies = [
 "base64",
 "log",
 "ring",
 "sct",
 "webpki",
]

[[package]]
name = "sct"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c"
dependencies = [
 "ring",
 "untrusted",
]

[[package]]
name = "spin"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"

[[package]]
name = "syn"
version = "1.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc371affeffc477f42a221a1e4297aedcea33d47d19b61455588bd9d8f6b19ac"
dependencies = [
 "proc-macro2",
 "quote",
 "unicode-xid",
]

[[package]]
name = "unicode-xid"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"

[[package]]
name = "untrusted"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"

[[package]]
name = "wasm-bindgen"
version = "0.2.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ac64ead5ea5f05873d7c12b545865ca2b8d28adfc50a49b84770a3a97265d42"
dependencies = [
 "cfg-if",
 "wasm-bindgen-macro",
]

[[package]]
name = "wasm-bindgen-backend"
version = "0.2.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f22b422e2a757c35a73774860af8e112bff612ce6cb604224e8e47641a9e4f68"
dependencies = [
 "bumpalo",
 "lazy_static",
 "log",
 "proc-macro2",
 "quote",
 "syn",
 "wasm-bindgen-shared",
]

[[package]]
name = "wasm-bindgen-macro"
version = "0.2.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b13312a745c08c469f0b292dd2fcd6411dba5f7160f593da6ef69b64e407038"
dependencies = [
 "quote",
 "wasm-bindgen-macro-support",
]

[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f249f06ef7ee334cc3b8ff031bfc11ec99d00f34d86da7498396dc1e3b1498fe"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
 "wasm-bindgen-backend",
 "wasm-bindgen-shared",
]

[[package]]
name = "wasm-bindgen-shared"
version = "0.2.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d649a3145108d7d3fbcde896a468d1bd636791823c9921135218ad89be08307"

[[package]]
name = "web-sys"
version = "0.3.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bf6ef87ad7ae8008e15a355ce696bed26012b7caa21605188cfd8214ab51e2d"
dependencies = [
 "js-sys",
 "wasm-bindgen",
]

[[package]]
name = "webpki"
version = "0.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab146130f5f790d45f82aeeb09e55a256573373ec64409fc19a6fb82fb1032ae"
dependencies = [
 "ring",
 "untrusted",
]

[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
 "winapi-i686-pc-windows-gnu",
 "winapi-x86_64-pc-windows-gnu",
]

[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"

[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

M std-rs/Cargo.toml => std-rs/Cargo.toml +3 -1
@@ 10,6 10,8 @@ license = "AGPL-3.0-or-later"

[dependencies]
libc = "0.2"
rustls = { version = "0.18", features = ["dangerous_configuration"] }
webpki = "0.21"

[lib]
crate-type = ["staticlib", "cdylib"]
\ No newline at end of file
crate-type = ["staticlib", "cdylib", "rlib"]

A std-rs/src/io.rs => std-rs/src/io.rs +59 -0
@@ 0,0 1,59 @@
use std::io::{self, Read, Write};
use std::mem;

use crate::*;

pub trait ReadWrite: Read + Write {}

impl<T> ReadWrite for T where T: Read + Write {}

pub type Handle = *mut dyn ReadWrite;

#[derive(Clone, Copy)]
#[repr(C)]
pub struct FfiHandle(*mut u8, *mut u8);

pub fn handle_to_ffi(h: Handle) -> FfiHandle {
    unsafe { mem::transmute(h) }
}

pub unsafe fn handle_from_ffi(h: FfiHandle) -> Handle {
    mem::transmute(h)
}

#[no_mangle]
pub unsafe extern "C" fn stdrs_close_handle(h: FfiHandle) {
    drop(Box::from_raw(handle_from_ffi(h)))
}

#[no_mangle]
pub unsafe extern "C" fn stdrs_read_handle(
    h: FfiHandle,
    mut buf: Array<u8>,
) -> Cons<Array<u8>, Maybe<usize>> {
    let res = (*handle_from_ffi(h)).read(buf.as_slice_mut());
    Cons(
        buf,
        match res {
            Ok(n) => Maybe::Some(n),
            Err(ref e) if e.kind() == io::ErrorKind::Interrupted => Maybe::Some(0),
            Err(_) => Maybe::None,
        },
    )
}

#[no_mangle]
pub unsafe extern "C" fn stdrs_write_handle(
    h: FfiHandle,
    buf: Array<u8>,
) -> Cons<Array<u8>, Maybe<usize>> {
    let res = (*handle_from_ffi(h)).write(buf.as_slice());
    Cons(
        buf,
        match res {
            Ok(n) => Maybe::Some(n),
            Err(ref e) if e.kind() == io::ErrorKind::Interrupted => Maybe::Some(0),
            Err(_) => Maybe::None,
        },
    )
}

M std-rs/src/lib.rs => std-rs/src/lib.rs +55 -20
@@ 2,10 2,12 @@
#![allow(non_camel_case_types)]

mod ffi;
pub mod io;
pub mod net;

use libc::*;
use std::fs::File;
use std::io::{self, Read, Write};
use std::io::{Read, Write};
use std::{alloc, mem, slice, str};

#[no_mangle]


@@ 26,6 28,9 @@ pub extern "C" fn install_stackoverflow_handler() {
}

#[repr(C)]
pub struct Cons<A, B>(pub A, pub B);

#[repr(C)]
pub struct Array<A> {
    elems: *mut A,
    len: u64,


@@ 35,7 40,7 @@ impl<A> Array<A>
where
    A: Clone,
{
    fn new(src: &[A]) -> Array<A> {
    pub fn new(src: &[A]) -> Array<A> {
        unsafe {
            let len = src.len();
            let p = ffi::GC_malloc(len * mem::size_of::<A>()) as *mut A;


@@ 47,27 52,65 @@ where
            }
        }
    }

    pub fn as_slice(&self) -> &[A] {
        unsafe { slice::from_raw_parts(self.elems, self.len as usize) }
    }

    pub fn as_slice_mut(&mut self) -> &mut [A] {
        unsafe { slice::from_raw_parts_mut(self.elems, self.len as usize) }
    }

    pub unsafe fn shallow_copy(&self) -> Self {
        Self {
            elems: self.elems,
            len: self.len,
        }
    }
}

#[repr(C)]
pub struct Str {
    array: Array<u8>,
    pub array: Array<u8>,
}

impl Str {
    fn new(s: &str) -> Str {
    pub fn new(s: &str) -> Str {
        Str {
            array: Array::new(s.as_bytes()),
        }
    }

    pub fn as_str<'s>(&'s self) -> &'s str {
        unsafe {
            let Array { elems, len } = self.array;
            let slice = slice::from_raw_parts(elems, len as usize);
            str::from_utf8_unchecked(slice)
        }
    }

    pub unsafe fn shallow_copy(&self) -> Self {
        Self {
            array: self.array.shallow_copy(),
        }
    }
}

#[repr(C)]
#[repr(C, u8)]
pub enum Maybe<A> {
    None,
    Some(A),
}

impl<A> Maybe<A> {
    pub fn unwrap(self) -> A {
        match self {
            Maybe::None => panic!("Maybe::unwrap on None"),
            Maybe::Some(a) => a,
        }
    }
}

impl<A> std::ops::Try for Maybe<A> {
    type Ok = A;
    type Error = std::option::NoneError;


@@ 101,31 144,23 @@ fn heap_alloc(size: u64) -> *mut u8 {

#[no_mangle]
pub extern "C" fn carth_str_eq(s1: Str, s2: Str) -> bool {
    let (s1, s2) = (from_carth_str(&s1), from_carth_str(&s2));
    let (s1, s2) = (s1.as_str(), s2.as_str());
    s1 == s2
}

#[export_name = "unsafe-display-inline"]
pub extern "C" fn display_inline(s: Str) {
    let s = from_carth_str(&s);
    let s = s.as_str();
    print!("{}", s);
    io::stdout().flush().ok();
    std::io::stdout().flush().ok();
}

#[export_name = "str-append"]
pub extern "C" fn str_append(s1: Str, s2: Str) -> Str {
    let (s1, s2) = (from_carth_str(&s1), from_carth_str(&s2));
    let (s1, s2) = (s1.as_str(), s2.as_str());
    Str::new(&(s1.to_string() + s2))
}

fn from_carth_str<'s>(s: &'s Str) -> &'s str {
    unsafe {
        let Array { elems, len } = s.array;
        let slice = slice::from_raw_parts(elems, len as usize);
        str::from_utf8_unchecked(slice)
    }
}

#[export_name = "show-int"]
pub extern "C" fn show_int(n: i64) -> Str {
    Str::new(&n.to_string())


@@ 143,14 178,14 @@ pub extern "C" fn show_f64(n: f64) -> Str {

#[export_name = "-panic"]
pub extern "C" fn panic(s: Str) {
    eprintln!("*** Panic: {}", from_carth_str(&s));
    eprintln!("*** Panic: {}", s.as_str());
    std::process::abort()
}

#[export_name = "-get-contents"]
pub extern "C" fn get_contents() -> Str {
    let mut s = String::new();
    io::stdin()
    std::io::stdin()
        .read_to_string(&mut s)
        .expect("read all of stdin");
    Str::new(&s)


@@ 158,7 193,7 @@ pub extern "C" fn get_contents() -> Str {

#[export_name = "unsafe-read-file"]
pub extern "C" fn read_file(fp: Str) -> Maybe<Str> {
    let fp = from_carth_str(&fp);
    let fp = fp.as_str();
    let mut f = File::open(fp).ok()?;
    let mut s = String::new();
    f.read_to_string(&mut s).ok()?;

A std-rs/src/net.rs => std-rs/src/net.rs +90 -0
@@ 0,0 1,90 @@
use crate::io::*;

use rustls::*;
use webpki;

use std::net::TcpStream;
use std::sync::Arc;

use crate::*;

#[repr(u8)]
pub enum Verifier {
    DangerousNone,
    Tofu,
}

impl ServerCertVerifier for Verifier {
    fn verify_server_cert(
        &self,
        _: &RootCertStore,
        _: &[Certificate],
        _: webpki::DNSNameRef<'_>,
        _: &[u8],
    ) -> Result<ServerCertVerified, TLSError> {
        match *self {
            Verifier::DangerousNone => Ok(ServerCertVerified::assertion()),
            Verifier::Tofu => todo!(),
        }
    }
}

#[no_mangle]
pub extern "C" fn stdrs_tcp_connect(host: Str, port: u16) -> FfiHandle {
    handle_to_ffi(Box::into_raw(
        Box::new(TcpStream::connect((host.as_str(), port)).expect("socket")) as _,
    ))
}

// Would have loved to use rustls for this, since it's rust, but there are problems that
// prevent it's effecient usage when using self signed certs as we do in gemini. See
// https://github.com/briansmith/webpki/issues/90.
#[no_mangle]
pub unsafe extern "C" fn stdrs_tls_connect(domain: Str, transport: FfiHandle) -> FfiHandle {
    let domain = domain.as_str();
    let transport = handle_from_ffi(transport);
    let mut config = rustls::ClientConfig::new();
    config
        .dangerous()
        .set_certificate_verifier(Arc::new(Verifier::DangerousNone));
    let dns_name = webpki::DNSNameRef::try_from_ascii_str(domain).expect("dns name");
    let sess = rustls::ClientSession::new(&Arc::new(config), dns_name);
    let tls = rustls::StreamOwned::new(sess, Box::from_raw(transport));
    handle_to_ffi(Box::into_raw(Box::new(tls) as _))
}

// fn main() {
//     // let (domain, path) = ("gus.guru", "/search");
//     let (domain, path) = ("gemini.circumlunar.space", "/");
//     let port = 1965;
//     get_data(domain, path, port);
//     // let header = resp.lines().next().expect("number of response lines >= 1");
//     // let code = header[0..2]
//     //     .parse::<u8>()
//     //     .expect("header beginning with 2-digit status code");
//     // let meta = &header[3..];
//     // if meta.as_bytes().len() > 1024 {
//     //     panic!("Response META longer than 1024 bytes. Error.");
//     // }
//     // let body = &resp[header.len() + 2..];
//     // println!("code: {}, meta: {}, body:\n\n{}", code, meta, body);
// }

// fn get_data(domain: &str, path: &str, port: u16) {
//     let mut config = ClientConfig::new();
//     config
//         .dangerous()
//         .set_certificate_verifier(Arc::new(Verifier::DangerousNone));
//     let dns_name = webpki::DNSNameRef::try_from_ascii_str(domain).expect("dns name");
//     let sess = ClientSession::new(&Arc::new(config), dns_name);
//     let sock = TcpStream::connect((domain, port)).expect("socket");
//     let mut tls = StreamOwned::new(sess, sock);
//     write!(&mut tls, "gemini://{}{}\r\n", domain, path).expect("written gemini message");
//     let code = &mut [0u8; 2][..];
//     tls.read_exact(code).unwrap();
//     let code = str::from_utf8(code).expect("valid utf-8 gemini status code");
//     println!("code: {}", code);
//     let mut resp = Vec::new();
//     println!("read result: {:?}\n", tls.read_to_end(&mut resp));
//     String::from_utf8(resp).expect("valid utf-8 response")
// }