// Copyright (c) 2021 Tangram Robotics Inc. - All Rights Reserved // Unauthorized copying of this file, via any medium is strictly prohibited // Proprietary and confidential // ---------------------------- //! The official Tangram Vision SDK. // TODO: Official doc links? use flate2::read::GzDecoder; use minisign_verify::{PublicKey, Signature}; use regex::Regex; use std::{ env, ffi::OsString, fs::{read, File}, io::copy, path::PathBuf, process::Command, sync::Arc, time::Duration, }; use tar::Archive; use webpki_roots::TLS_SERVER_ROOTS; /// The URL for our S3 bucket where the rlibs can be downloaded from. const API_URL: &str = "https://tangram-vision-sdk-rlibs.s3.us-west-1.amazonaws.com"; /// Tangram Vision pubkey for signature verification const PUBKEY: &str = "RWTt9pEd+cZWZhuTuurze3G34fVfRJN/R9pXDy4LK0WJD8JYxciuHmhc"; fn main() { let target_triple = env::var("TARGET").expect("Could not get the TARGET from env. Your environment is broken."); let crate_version = env!("CARGO_PKG_VERSION"); let rustc = env::var_os("RUSTC").unwrap_or_else(|| OsString::from("rustc")); let rustc_version = String::from_utf8( Command::new(rustc) .arg("--version") .output() .expect("Failed to call `${RUSTC} --version`. Your environment is broken.") .stdout, ) .expect("Could not convert the output of `${RUSTC} --version` to a `String`."); // We unwrap here because this regex will always compile, even if it doesn't necessarily match // anything. let re = Regex::new(r"\b\d+\.\d+\.\d+\b").unwrap(); let rustc_version_triple = re .find(&rustc_version) .expect( "Could not find version triple (major.minor.patch) in `${RUSTC} --version`. \ This could mean that rustc is broken, or has changed convention. \ Check your environment, and contact support@tangramvision.com if the issue persists.", ) .as_str(); let re_nightly = Regex::new(r"nightly").unwrap(); if re_nightly.find(&rustc_version).is_some() { panic!( "The Tangram Vision SDK currently only supports stable Rust! \ Please install a non-nightly toolchain and try again." ); } let file_name = format!( "tangram-vision-sdk-{}_stable-{}_{}.tar.gz", crate_version, rustc_version_triple, target_triple, ); let sig_file_name = format!("{}.minisig", file_name); let out_dir = &PathBuf::from(env::var_os("OUT_DIR").expect("OUT_DIR is not defined in the environment!")) .join("../../../../"); let out_path = out_dir.join(&file_name); let signature_path = out_dir.join(&sig_file_name); // Early return in case we already have the file in place. if !out_path.exists() || !signature_path.exists() { let user_agent = format!("{}/{}", env!("CARGO_PKG_NAME"), crate_version); let mut tls_config = rustls::ClientConfig::new(); tls_config .root_store .add_server_trust_anchors(&TLS_SERVER_ROOTS); let agent = ureq::builder() .user_agent(&user_agent) .tls_config(Arc::new(tls_config)) .timeout_connect(Duration::from_secs(4)) .build(); let url = format!("{}/{}", API_URL, &file_name); let response = match agent.get(&url).call() { Ok(response) => response, Err(ureq::Error::Status(code, response)) => panic_on_status(&file_name, code, response), Err(ureq::Error::Transport(transport)) => panic_on_transport(&file_name, transport), }; let mut out = File::create(&out_path).unwrap_or_else(|_| { panic!("Failed to open a file to write \"{}\" to disk.", &file_name) }); copy(&mut response.into_reader(), &mut out) .unwrap_or_else(|_| panic!("Failed to copy \"{}\" to disk.", &file_name)); let sig_url = format!("{}/{}", API_URL, sig_file_name); let sig_response = match agent.get(&sig_url).call() { Ok(response) => response, Err(ureq::Error::Status(code, response)) => { panic_on_status(&sig_file_name, code, response) } Err(ureq::Error::Transport(transport)) => panic_on_transport(&sig_file_name, transport), }; let mut out = File::create(&signature_path).unwrap_or_else(|_| { panic!( "Failed to open a file to write \"{}\" to disk.", &sig_file_name, ) }); copy(&mut sig_response.into_reader(), &mut out) .unwrap_or_else(|_| panic!("Failed to copy \"{}\" to disk.", &sig_file_name)); } let lib_dir = out_dir.join("libs"); println!("cargo:rustc-link-search={}", lib_dir.display()); let public_key = PublicKey::from_base64(PUBKEY).expect("Unable to decode public key."); // Unwrap here because these files definitely exist by this point. let bin = read(&out_path).unwrap(); let signature = Signature::from_file(&signature_path).unwrap(); public_key.verify(&bin, &signature).expect( "Signature verification failed. \ Please make sure your network is secure and try again. \ Contact support@tangramvision.com if the issue persists.", ); let tar = GzDecoder::new(&bin[..]); let mut archive = Archive::new(tar); archive.unpack(&out_dir).expect( "Unpacking build artefacts failed. \ Try building again after a fresh `cargo clean`. \ Contact support@tangramvision.com if the issue persists.", ); } /// Function designed to panic and display a helpful error message to the user based on a failed /// web request (server emits bad (i.e. not 200 OK) status). /// /// # Panics /// /// Yes, yes it does. fn panic_on_status(file_name: &str, code: u16, response: ureq::Response) -> ureq::Response { if code == 404 || code == 403 { panic!( "The requested file ({}) was not found on the server. \ Make sure your platform and compiler are supported, \ and contact support if the issue persists.", file_name, ); } if code == 500 { panic!( "Received an internal error when downloading the SDK artefacts. \ Try building again and contact support@tangramvision.com if the issue persists." ); } panic!( "Received response with code {}. \ This is unusual, please contact support@tangramvision.com. \ Reponse text: {}", code, response.status_text(), ); } /// Function designed to panic and display a helpful error message to the user based on a failed /// web request (transport reasons). /// /// # Panics /// /// Yes, yes it does. fn panic_on_transport(file_name: &str, transport: ureq::Transport) -> ureq::Response { panic!( "Encountered a transport error when trying to get \"{}\" from the server. \ Please check your network connection and contact support@tangramvision.com \ if the error persists. \nFull transport error: \n{}\n", file_name, transport, ); }