How to repeat a large block in a match, changing only one type inside it

I need to do the same operations as in Some(Md5Sess), changing only the type that calls digest. So change from <md5::Md5>::digest to <sha2::Sha256>::digest etc and possibly many others:

 match challenge.algorithm {
        Some(Md5Sess) => {
            //I need to repeat this entire block, only changing the `md5::Md5` by the correct hashing function
            let a1_part1 = <md5::Md5>::digest(format!("{}:{}:{}", username, realm, password).as_bytes());
            let cnonce = match cnonce {
                Some(cnonce) => cnonce,
                None => return Err(DigestError::MissingCNonce)
            };
            let a1_part2 = format!("{}:{}", nonce, cnonce);
            a1 = format!("{:x}:{}", a1_part1, a1_part2);
            let entity_body = match &body {
                Some(body) => body.as_ref(),
                None => "".as_bytes()
            };
            hash_common::<md5::Md5>(method, chosen_qop, a1, nonce, Some(cnonce), nonce_count, uri, body)
        },
        Some(Sha512Trunc256Sess) => {
            Err(DigestError::DigestAlgorithmNotImplemented(Sha512Trunc256Sess))
        },
        Some(Sha256Sess) => {
            Err(DigestError::DigestAlgorithmNotImplemented(Sha256Sess))
        },
        Some(Algorithm::Other(s)) => {
            Err(DigestError::DigestAlgorithmNotImplemented(Algorithm::Other(s)))
        },
    }

My only idea is to create a function that is generic on the type of the hash, with the inconvenient of having lots of arguments, for each variable used in that block.

Is there a better way to solve this problem?

I tried with macros too, only to remember that in Rust, macros are not like in C where they don't care about the text being used. I did a macro that matched by the type of the hash like my_macro!(md5::Md5) but then it complained about the variables being used in the block.

You could define a local closure that does most of the work; something like:

let do_challenge = |digest: fn(String)->String, common: fn(...)| {
            let a1_part1 = digest(format!("{}:{}:{}", username, realm, password).as_bytes());
            let cnonce = match cnonce {
                Some(cnonce) => cnonce,
                None => return Err(DigestError::MissingCNonce)
            };
            let a1_part2 = format!("{}:{}", nonce, cnonce);
            a1 = format!("{:x}:{}", a1_part1, a1_part2);
            let entity_body = match &body {
                Some(body) => body.as_ref(),
                None => "".as_bytes()
            };
            common(method, chosen_qop, a1, nonce, Some(cnonce), nonce_count, uri, body)
}

match challenge.algorithm {
    Some(Md5Sess) => {
        do_challenge(
            |x| format!("{:x}", <md5::Md5>::digest(x)),
            hash_common::<md5::Md5>
        )
    }
    // ...
}

(Untested; may contain syntax errors)


I think this can work if you define the macro where the variables are already in scope.

1 Like

Mostly spitballing, but...

fn algorithm_to_digest_and_hash(algorithm: ThatEnum)
  -> (  fn(T1, T2, T3) -> T0, 
        fn(U1, U2, U3, U4, U5, U6, U7) -> U0
     )
{
    match algorithm {
        ThatEnum::Md5Sess => {
             (<md5::Md5>::digest, hash_common::<md5::Md5>)
        }
        // ...
    }
}

Guess this is just @2e71828's suggestion, inside-out.

1 Like

Something like this, rigth? The problem is the return type, which has that type. What should be Self? It should be the Md5 or Sha256. But closures cannot be generic, can they?

let do_sess_case = |digest: fn(&[u8])->GenericArray<u8, Self::OutputSize>| {
        let a1_part1 = digest(format!("{}:{}:{}", username, realm, password).as_bytes());
        let cnonce = match cnonce {
            Some(cnonce) => cnonce,
            None => return Err(DigestError::MissingCNonce)
        };
        let a1_part2 = format!("{}:{}", nonce, cnonce);
        a1 = format!("{}:{}", hex::encode(a1_part1), a1_part2);
        let entity_body = match &body {
            Some(body) => body.as_ref(),
            None => "".as_bytes()
        };
        hash_common::<md5::Md5>(method, chosen_qop, a1, nonce, Some(cnonce), nonce_count, uri, body)
    };

They can't. You'll need to unify the return type somehow, probably by collecting the GenericArray into a Vec<u8>:

do_sess_case(
    |x| <md5::Md5>::digest(x).into_iter().collect::<Vec<_>>(),
    ...
)

You can avoid repeating this by defining your own helper trait:

trait DigestVec {
    fn digest_vec(&[u8])->Vec<u8>;
}

impl<T:Digest> DigestVec for T {
    fn digest_vec(x: &[u8])->Vec<[u8]> {
        Self::digext(x).into_iter().collect()
    }
}

// Inside your function
do_sess_case(
    <md5::Md5>::digest_vec,
    hash_common::<md5::Md5>
)

I confused. Return type is ok, just use the return type of hash_common:

let do_sess_case = |digest: fn(&[u8])-> String | -> std::result::Result<String, DigestError>{
    let a1_part1 = digest(format!("{}:{}:{}", username, realm, password).as_bytes());
    let cnonce = match cnonce {
        Some(cnonce) => cnonce,
        None => return Err(DigestError::MissingCNonce)
    };
    let a1_part2 = format!("{}:{}", nonce, cnonce);
    a1 = format!("{}:{}", hex::encode(a1_part1), a1_part2);
    let entity_body = match &body {
        Some(body) => body.as_ref(),
        None => "".as_bytes()
    };
    hash_common::<md5::Md5>(method, chosen_qop, a1, nonce, Some(cnonce), nonce_count, uri, body)
};

However there are 2 problems. How do I call this closure?

do_sess_case(<md5::Md5>::digest)

won't work.

Also look that inside of the closure, hash_common::<md5::Md5>(...). This should also be dependent on the hash being used.

Another unpolished idea before I leave the computer for a bit. I'm assuming you only depend on the Digest trait in this skeleton:

trait MyOwnHashHelperTrait: Digest {
   fn do_sess_case(your, twenty, args) -> Result<What, Ever> {
        let a1_part1 = Self::digest(...);
        // ...
        hash_common<Self>(...)
    }
}

impl MyOwnHashHelperTrait for md5::Md5 {}
// etc

fn that_you_asked_about() -> Result<What, Ever> {
    match challenge.algorithm {
        Some(Md5Sess) => <md5::Md5>::do_sess_case(your, twenty, args),
        // ...
    }
}

Or just fn do_sess_case<T: Digest>(...) { ... } for that matter, if you only need this manoeuvring for the one function. Also had some ideas about making it dynamic dispatchy so you could do something like

challenge.algorithm.digester().do_sess_case(...)

But to be honest, I don't have a good feel for if I'm even going in a direction that makes sense for your use case or not.

1 Like

I was avoiding defining a function with lots of arguments but I guess I'm gonna have to

OK, I explored this a bit more.

  • Lines 1--76 are my best guess at your context.
  • Lines 80--125 don't add anything new, but pulled all the common parts out of the match.
  • Lines 130--176 is one way to pull out the common functionality.
  • Lines 179--228 are an alternative where you don't define a function with a lot of arguments (but still returns a fn pointer with all those arguments, so maybe still annoying).

For each section I just copied the previous and altered things a bit until the types lined up and compiled. I'm not sure how close I am to your use case really, because there's too much missing context. I left some notes/assumptions in the comments.

This was a pretty sloppy exercise, but I hope it's useful anyway.

Thanks, this was awesome. Unfortunately I simplified the example to make it easier to understand. There are 2 cases: the one where I use that giant block, varying only the hash function, which are the ones ending with Sess (like Md5Sess, Sha256Sess, ...) and the ones not ending with Sess, for which I repeat another block of function.

I think it's important to match the challenge before and then apply the block of functions, but I don't see any elegant solution

1 Like

Right, I thought it might be something like that. Sorry there's no readily apparent elegant solution.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.