My first ever Rust program!

Here, for posterity, is my first ever Rust program. It checks the key log on the Upspin Key Server.

extern crate sha2;
extern crate reqwest;

use std::io::Read;
use sha2::{Sha256, Digest};

fn main() {
    let mut resp = match reqwest::get("https://key.upspin.io/log") {
        Ok(resp) => resp,
        Err(e) => panic!(e),
    };
    assert!(resp.status().is_success());

    let mut content = String::new();
    match resp.read_to_string(&mut content) {
        Ok(_) => {}
        Err(e) => panic!(e),
    }

    let mut hasher = Sha256::default();

    let mut ct = 0;
    let mut last_hash = "".to_string();
    for line in content.lines() {
        if line.starts_with("SHA256:") {
            let mut fields = line.split(":");

            // skip first token
            match fields.next() {
                Some(_) => {}
                _ => {
                    println!("Bad SHA256 line: {}", line);
                    continue;
                }
            };

            last_hash = fields.next().unwrap().to_string();
            let expected = string_to_u8_array(&last_hash);
            let output = hasher.result();
            assert_eq!(output.as_slice(), expected.as_slice());
        } else {
            hasher = Sha256::default();
            hasher.input(line.as_bytes());
            let newline = "\n".as_bytes();
            hasher.input(newline);
            if last_hash != "" {
                hasher.input(last_hash.as_bytes());
            }
        }

        ct += 1;
        println!("Line {}", ct);
    }
}

use std::u8;
fn string_to_u8_array(hex: &String) -> Vec<u8> {

    // Make vector of bytes from octets
    let mut bytes = Vec::new();
    for i in 0..(hex.len() / 2) {
        let res = u8::from_str_radix(&hex[2 * i..2 * i + 2], 16);
        match res {
            Ok(v) => bytes.push(v),
            Err(e) => {
                println!("Problem with hex: {}", e);
                return bytes;
            }
        };
    }
    return bytes;
}

I found myself sprinkling mut’s and unpack()’s here and there like the mutation unpacking fairy, hoping something would work. I don’t think that how you are supposed to do it, but we’ll see.

People give Go a bad time about the size of it’s compiled files, so I expected this little Rust program to be much smaller. But not so:

$ ls -lh ../target/{debug,release}/upspin
-rwxrwxr-x 2 jra jra  23M Jul 21 15:12 ../target/debug/upspin
-rwxrwxr-x 2 jra jra 5.1M Jul 21 15:14 ../target/release/upspin

People also say, “Go is fat because it is statically linked”. Pure Rust code is also self contained in the program’s ELF file. But the 5.1 meg release image does not count several quite large shared libraries that it depends on:

$ ldd ../target/release/upspin
	linux-vdso.so.1 =>  (0x00007fff319c2000)
	libssl.so.1.0.0 => /lib/x86_64-linux-gnu/libssl.so.1.0.0 (0x00007f3d7a6cc000)
	libcrypto.so.1.0.0 => /lib/x86_64-linux-gnu/libcrypto.so.1.0.0 (0x00007f3d7a288000)
	libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f3d7a083000)
	librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007f3d79e7b000)
	libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f3d79c5e000)
	libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f3d79a47000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f3d7967d000)
	/lib64/ld-linux-x86-64.so.2 (0x00005555d33b8000)

From a code safety point of view, I find it really troubling that even if we work so hard to make the compiler happy to prove our code is secure, we are still depending on libc and the platform’s OpenSSL. I remember the first time I started digging down into Go and realized that it wasn’t even linking libc, and it gave me a fantastic feeling of finally cutting the cord to the old, dangerous way of programming. With Rust (and hyper) we’re right back to depending on C again. Yuck.

Finally, the incredible speedup from debug to release images was surprising:

$ time ../target/debug/upspin > /dev/null

real	0m3.631s
user	0m0.888s
sys	0m0.020s
$ time ../target/release/upspin > /dev/null

real	0m2.961s
user	0m0.068s
sys	0m0.016s

Look at user, not real. Real is dominated by waiting on the far-end server. Real is more timing the TLS decryption, the parsing and the hashing.

Leave a Reply