Repository: google/OpenSK
File: src/ctap/data_formats.rs (Rust)

Code before:

#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
pub struct Extensions(BTreeMap<String, cbor::Value>);

impl TryFrom<&cbor::Value> for Extensions {
    type Error = Ctap2StatusCode;

    fn try_from(cbor_value: &cbor::Value) -> Result<Self, Ctap2StatusCode> {
        let mut extensions = BTreeMap::new();
        for (extension_key, extension_value) in read_map(cbor_value)? {
            if let cbor::KeyType::TextString(extension_key_string) = extension_key {
                extensions.insert(extension_key_string.to_string(), extension_value.clone());
            } else {
                return Err(Ctap2StatusCode::CTAP2_ERR_CBOR_UNEXPECTED_TYPE);
            }
        }
        Ok(Extensions(extensions))
    }
}

impl From<Extensions> for cbor::Value {
    fn from(extensions: Extensions) -> Self {
        cbor_map_btree!(extensions
            .0
            .iter()
            .map(|(key, value)| (cbor_text!(key), value.clone()))
            .collect())
    }
}

impl Extensions {
    #[cfg(test)]
    pub fn new(extension_map: BTreeMap<String, cbor::Value>) -> Self {
        Extensions(extension_map)
    }

    pub fn has_make_credential_hmac_secret(&self) -> Result<bool, Ctap2StatusCode> {
        self.0
            .get("hmac-secret")
            .map(read_bool)
            .unwrap_or(Ok(false))
    }

    pub fn get_assertion_hmac_secret(
        &self,
    ) -> Option<Result<GetAssertionHmacSecretInput, Ctap2StatusCode>> {
        self.0
            .get("hmac-secret")
            .map(GetAssertionHmacSecretInput::try_from)
    }
}

Review comment (suggestion):
Since the source is consumed by this function, you should be able to avoid cloning by using `into_iter` instead, which consumes the container.

Code after:

#[cfg_attr(any(test, feature = "debug_ctap"), derive(Debug, PartialEq))]
pub struct Extensions(BTreeMap<String, cbor::Value>);

impl TryFrom<&cbor::Value> for Extensions {
    type Error = Ctap2StatusCode;

    fn try_from(cbor_value: &cbor::Value) -> Result<Self, Ctap2StatusCode> {
        let mut extensions = BTreeMap::new();
        for (extension_key, extension_value) in read_map(cbor_value)? {
            if let cbor::KeyType::TextString(extension_key_string) = extension_key {
                extensions.insert(extension_key_string.to_string(), extension_value.clone());
            } else {
                return Err(Ctap2StatusCode::CTAP2_ERR_CBOR_UNEXPECTED_TYPE);
            }
        }
        Ok(Extensions(extensions))
    }
}

impl From<Extensions> for cbor::Value {
    fn from(extensions: Extensions) -> Self {
        cbor_map_btree!(extensions
            .0
            .into_iter()
            .map(|(key, value)| (cbor_text!(key), value))
            .collect())
    }
}

impl Extensions {
    #[cfg(test)]
    pub fn new(extension_map: BTreeMap<String, cbor::Value>) -> Self {
        Extensions(extension_map)
    }

    pub fn has_make_credential_hmac_secret(&self) -> Result<bool, Ctap2StatusCode> {
        self.0
            .get("hmac-secret")
            .map(read_bool)
            .unwrap_or(Ok(false))
    }

    pub fn get_assertion_hmac_secret(
        &self,
    ) -> Option<Result<GetAssertionHmacSecretInput, Ctap2StatusCode>> {
        self.0
            .get("hmac-secret")
            .map(GetAssertionHmacSecretInput::try_from)
    }
}