uni-sdk 0.3.0

The official Unimatrix SDK for Rust
Documentation
use std::collections::BTreeMap;

use hmac::{Hmac, Mac};
use sha2::Sha256;
use url::form_urlencoded;

use crate::{UniError, DEFAULT_SIGNING_ALGORITHM};

pub(crate) fn sign(
    query: &mut BTreeMap<String, String>,
    secret: &str,
    timestamp: u64,
    nonce: &str,
) -> crate::Result<()> {
    query.insert("algorithm".to_owned(), DEFAULT_SIGNING_ALGORITHM.to_owned());
    query.insert("timestamp".to_owned(), timestamp.to_string());
    query.insert("nonce".to_owned(), nonce.to_owned());

    let canonical = canonical_query(query);
    let mut mac = Hmac::<Sha256>::new_from_slice(secret.as_bytes())
        .map_err(|_| UniError::Configuration("invalid signing key".to_owned()))?;
    mac.update(canonical.as_bytes());
    query.insert(
        "signature".to_owned(),
        hex::encode(mac.finalize().into_bytes()),
    );
    Ok(())
}

pub(crate) fn canonical_query(query: &BTreeMap<String, String>) -> String {
    let mut serializer = form_urlencoded::Serializer::new(String::new());
    for (key, value) in query {
        serializer.append_pair(key, value);
    }
    serializer.finish()
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn signature_is_stable_and_url_encoded() {
        let mut query = BTreeMap::from([
            ("accessKeyId".to_owned(), "access key/+".to_owned()),
            ("action".to_owned(), "sms.message.send".to_owned()),
        ]);

        sign(&mut query, "secret", 1_650_000_000, "0123456789abcdef").unwrap();

        assert_eq!(
            canonical_query(&BTreeMap::from([
                ("accessKeyId".to_owned(), "access key/+".to_owned()),
                ("action".to_owned(), "sms.message.send".to_owned()),
                ("algorithm".to_owned(), "hmac-sha256".to_owned()),
                ("nonce".to_owned(), "0123456789abcdef".to_owned()),
                ("timestamp".to_owned(), "1650000000".to_owned()),
            ])),
            "accessKeyId=access+key%2F%2B&action=sms.message.send&algorithm=hmac-sha256&nonce=0123456789abcdef&timestamp=1650000000"
        );
        assert_eq!(
            query["signature"],
            "25213ad680913a8dc2d21c54fe08a4cb6fc1fe9f256d956766975d6d6cdb1943"
        );
    }
}