Home > database >  Syntax for updating underlying instance when looping?
Syntax for updating underlying instance when looping?

Time:09-13

I have a HashMap with different hashing algorithms which implement digest::DynDigest, but when they are looped over and update()d the final hash is always the same (Sha224 d14a...) (you can change the file PathBuf parameter to anything and get the exact same hash every time). So the update() and/or finalize() is not updating the instance in the HashMap.

What is the correct way to loop over the different hashers so that HashMap keeps correct updated state?

use digest::{Digest, DynDigest}; // https://crates.io/crates/digest/reverse_dependencies
use sha2; // implements digest::DynDigest
use std::collections::HashMap;
use std::fs::File;
use std::io;
use std::io::{BufReader, Read};
use std::path::PathBuf;

fn checksum_to_hex(bytes: &[u8]) -> String {
    let mut s: String = String::new();

    for b in bytes {
        s.push_str(format!("{:02x}", b).as_str());
    }

    s
}

pub fn hash_file(
    fpath: PathBuf,
    hashers: HashMap<&str, Box<dyn DynDigest>>,
) -> io::Result<HashMap<&str, String>> {
    let f = File::open(fpath).expect("failed to open file for hashing");

    let mut buffer = [0u8; 1048576];
    let mut reader = BufReader::new(f);

    loop {
        let count = reader.read(&mut buffer)?;
        if count == 0 {
            break;
        }

        for (_, mut hasher) in hashers.to_owned() {
            hasher.update(&buffer[..count]);
        }
    }

    // Hash results per hasher
    let mut hashes: HashMap<&str, String> = HashMap::new();

    for (k, hasher) in hashers.to_owned() {
        let res = checksum_to_hex(hasher.finalize().to_vec().as_slice());
        hashes.insert(k, res);
    }

    Ok(hashes)
}

fn main() {
    let mut hashers: HashMap<&str, Box<dyn DynDigest>> = HashMap::new();

    hashers.insert("Sha224", Box::new(sha2::Sha224::new()));
    hashers.insert("Sha256", Box::new(sha2::Sha256::new()));
    hashers.insert("Sha512", Box::new(sha2::Sha512::new()));

    for (htype, hashres) in hash_file(PathBuf::from("/bin/ls"), hashers).expect("") {
        println!("  {} {}", htype, hashres);
    }
}

Rust Playground

CodePudding user response:

You need to replace the first hashers.to_owned() with hashers.iter_mut():

pub fn hash_file(
    fpath: PathBuf,
    mut hashers: HashMap<&str, Box<dyn DynDigest>>,
) -> io::Result<HashMap<&str, String>> {
    let f = File::open(fpath).expect("failed to open file for hashing");

    let mut buffer = [0u8; 1048576];
    let mut reader = BufReader::new(f);

    loop {
        let count = reader.read(&mut buffer)?;
        if count == 0 {
            break;
        }

        for (_, hasher) in hashers.iter_mut() {
        // or `for hasher in hashers.values_mut()`
            hasher.update(&buffer[..count]);
        }
    }

    // Hash results per hasher
    let mut hashes: HashMap<&str, String> = HashMap::new();

    for (k, hasher) in hashers {
        let res = checksum_to_hex(hasher.finalize().to_vec().as_slice());
        hashes.insert(k, res);
    }

    Ok(hashes)
}

playground

.to_owned() will clone (create an independent deep copy) the map. When you iterate over that, you're iterating through a different map than the one you eventually return.

Instead, you want to iterate over references to the elements: this is what .iter_mut() (or .values_mut() if you only need the values) will give you.

This way, you don't need the second .to_owned(), but you do need to mark the hashers argument as mutable.

  • Related