#![forbid(unsafe_code)]
#![cfg_attr(test, deny(warnings))]
extern crate sha2;
extern crate curl;
mod hash_list;
use std::io;
use curl::easy::Easy;
use sha2::sha2::Sha256;
use sha2::digest::Digest;
use hash_list::HashList;
use std::fs::create_dir_all;
pub struct TestAssetDef {
pub filename :String,
pub hash :String,
pub url :String,
}
#[derive(PartialEq, Eq, Hash, Clone)]
pub struct Sha256Hash([u8; 32]);
impl Sha256Hash {
pub fn from_digest(sha :&mut Sha256) -> Self {
let mut res = Sha256Hash([0; 32]);
sha.result(&mut res.0);
return res;
}
pub fn from_hex(s :&str) -> Result<Self, ()> {
let mut res = Sha256Hash([0; 32]);
let mut idx = 0;
let mut iter = s.chars();
loop {
let upper = match iter.next().and_then(|c| c.to_digit(16)) {
Some(v) => v as u8,
None => try!(Err(())),
};
let lower = match iter.next().and_then(|c| c.to_digit(16)) {
Some(v) => v as u8,
None => try!(Err(())),
};
res.0[idx] = (upper << 4) | lower;
idx += 1;
if idx == 32 {
break;
}
}
return Ok(res);
}
pub fn to_hex(&self) -> String {
let mut res = String::with_capacity(64);
for v in self.0.iter() {
use std::char::from_digit;
res.push(from_digit(*v as u32 >> 4, 16).unwrap());
res.push(from_digit(*v as u32 & 15, 16).unwrap());
}
return res;
}
}
#[derive(Debug)]
pub enum TaError {
Io(io::Error),
Curl(curl::Error),
DownloadFailed(u32),
BadHashFormat,
}
impl From<io::Error> for TaError {
fn from(err :io::Error) -> TaError {
TaError::Io(err)
}
}
impl From<curl::Error> for TaError {
fn from(err :curl::Error) -> TaError {
TaError::Curl(err)
}
}
enum DownloadOutcome {
WithHash(Sha256Hash),
DownloadFailed(u32),
}
fn download_test_file(client :&mut Easy,
tfile :&TestAssetDef, dir :&str) -> Result<DownloadOutcome, TaError> {
use std::io::Write;
use std::fs::File;
try!(client.url(&tfile.url));
let mut content = Vec::new();
{
let mut transfer = client.transfer();
try!(transfer.write_function(|data| {
content.extend_from_slice(data);
Ok(data.len())
}));
try!(transfer.perform());
}
let mut hasher = Sha256::new();
let mut file = try!(File::create(format!("{}/{}", dir, tfile.filename)));
try!(file.write_all(&content));
hasher.input(&content);
let response_code = try!(client.response_code());
if response_code < 200 || response_code > 399 {
return Ok(DownloadOutcome::DownloadFailed(response_code));
}
return Ok(DownloadOutcome::WithHash(Sha256Hash::from_digest(&mut hasher)));
}
pub fn download_test_files(defs :&[TestAssetDef],
dir :&str, verbose :bool) -> Result<(), TaError> {
let mut client = Easy::new();
try!(client.follow_location(true));
use std::io::ErrorKind;
let hash_list_path = format!("{}/hash_list", dir);
let mut hash_list = match HashList::from_file(&hash_list_path) {
Ok(l) => l,
Err(TaError::Io(ref e)) if e.kind() == ErrorKind::NotFound => HashList::new(),
e => { try!(e); unreachable!() },
};
try!(create_dir_all(dir));
for tfile in defs.iter() {
let tfile_hash = try!(Sha256Hash::from_hex(&tfile.hash).map_err(|_| TaError::BadHashFormat));
if hash_list.get_hash(&tfile.filename).map(|h| h == &tfile_hash)
.unwrap_or(false) {
if verbose {
println!("File {} has matching hash inside hash list, skipping download", tfile.filename);
}
continue;
}
if verbose {
print!("Fetching file {} ...", tfile.filename);
}
let outcome = try!(download_test_file(&mut client, tfile, dir));
use self::DownloadOutcome::*;
match &outcome {
&DownloadFailed(code) => return Err(TaError::DownloadFailed(code)),
&WithHash(ref hash) => hash_list.add_entry(&tfile.filename, hash),
}
if verbose {
print!(" => ");
match &outcome {
&DownloadFailed(code) => println!("Download failed with code {}", code),
&WithHash(ref found_hash) => {
if found_hash == &tfile_hash {
println!("Success")
} else {
println!("Hash mismatch: found {}, expected {}",
found_hash.to_hex(), tfile.hash)
}
},
}
}
}
try!(hash_list.to_file(&hash_list_path));
Ok(())
}