// A Rustls stub for TryTLS
//
// Author: Joachim Viide
// See: https://github.com/HowNetWorks/trytls-rustls-stub
//

use rustls::{ClientConfig, ClientConnection, Error, OwnedTrustAnchor, RootCertStore};
use std::{
    convert::TryInto,
    env,
    error::Error as StdError,
    fs::File,
    io::{BufReader, Read, Write},
    net::TcpStream,
    process,
    sync::Arc,
};

enum Verdict {
    Accept,
    Reject(Error),
}

fn parse_args(args: &[String]) -> Result<(String, u16, ClientConfig), Box<dyn StdError>> {
    let mut root_store = RootCertStore::empty();
    match args.len() {
        3 => {
            root_store.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.0.iter().map(
                |ta| {
                    OwnedTrustAnchor::from_subject_spki_name_constraints(
                        ta.subject,
                        ta.spki,
                        ta.name_constraints,
                    )
                },
            ));
        }
        4 => {
            let f = File::open(&args[3])?;
            root_store
                .add_parsable_certificates(&rustls_pemfile::certs(&mut BufReader::new(f)).unwrap());
        }
        _ => {
            return Err(From::from("Incorrect number of arguments"));
        }
    };
    let config = rustls::ClientConfig::builder()
        .with_safe_defaults()
        .with_root_certificates(root_store)
        .with_no_client_auth();

    let port = args[2].parse()?;
    Ok((args[1].clone(), port, config))
}

fn communicate(
    host: String,
    port: u16,
    config: ClientConfig,
) -> Result<Verdict, Box<dyn StdError>> {
    let server_name = host.as_str().try_into().unwrap();
    let rc_config = Arc::new(config);
    let mut client = ClientConnection::new(rc_config, server_name).unwrap();
    let mut stream = TcpStream::connect((&*host, port))?;

    client
        .writer()
        .write_all(b"GET / HTTP/1.0\r\nConnection: close\r\nContent-Length: 0\r\n\r\n")?;
    loop {
        while client.wants_write() {
            client.write_tls(&mut stream)?;
        }

        if client.wants_read() {
            if client.read_tls(&mut stream)? == 0 {
                return Err(From::from("Connection closed"));
            }

            if let Err(err) = client.process_new_packets() {
                return match err {
                    Error::InvalidCertificateData(_)
                    | Error::InvalidCertificateSignature
                    | Error::InvalidCertificateSignatureType
                    | Error::InvalidCertificateEncoding
                    | Error::AlertReceived(_) => Ok(Verdict::Reject(err)),
                    _ => Err(From::from(format!("{:?}", err))),
                };
            }

            if client.reader().read(&mut [0])? > 0 {
                return Ok(Verdict::Accept);
            }
        }
    }
}

fn main() {
    let args: Vec<String> = env::args().collect();
    let (host, port, config) = parse_args(&args).unwrap_or_else(|err| {
        println!("Argument error: {}", err);
        process::exit(2);
    });

    match communicate(host, port, config) {
        Ok(Verdict::Accept) => {
            println!("ACCEPT");
            process::exit(0);
        }
        Ok(Verdict::Reject(reason)) => {
            println!("{:?}", reason);
            println!("REJECT");
            process::exit(0);
        }
        Err(err) => {
            println!("{}", err);
            process::exit(1);
        }
    }
}
