indy-credx 1.1.1

Verifiable credential issuance and presentation for Hyperledger Indy (https://www.hyperledger.org/projects), which provides a distributed-ledger-based foundation for self-sovereign identity (https://sovrin.org).
Documentation
use std::collections::HashSet;
use std::convert::TryInto;
use std::os::raw::c_char;
use std::ptr;

use ffi_support::{rust_string_to_c, FfiStr};

use super::error::{catch_error, ErrorCode};
use super::object::{IndyObject, ObjectHandle};
use super::util::{FfiList, FfiStrList};
use crate::error::Result;
use crate::services::{
    issuer::create_credential,
    prover::process_credential,
    types::{Credential, CredentialRevocationConfig, MakeCredentialValues},
    utils::encode_credential_attribute,
};

#[derive(Debug)]
#[repr(C)]
pub struct FfiCredRevInfo<'a> {
    reg_def: ObjectHandle,
    reg_def_private: ObjectHandle,
    registry: ObjectHandle,
    reg_idx: i64,
    reg_used: FfiList<'a, i64>,
}

struct RevocationConfig {
    reg_def: IndyObject,
    reg_def_private: IndyObject,
    registry: IndyObject,
    reg_idx: u32,
    reg_used: HashSet<u32>,
}

impl RevocationConfig {
    pub fn as_ref_config(&self) -> Result<CredentialRevocationConfig> {
        Ok(CredentialRevocationConfig {
            reg_def: self.reg_def.cast_ref()?,
            reg_def_private: self.reg_def_private.cast_ref()?,
            registry: self.registry.cast_ref()?,
            registry_idx: self.reg_idx,
            registry_used: &self.reg_used,
        })
    }
}

#[no_mangle]
pub extern "C" fn credx_create_credential(
    cred_def: ObjectHandle,
    cred_def_private: ObjectHandle,
    cred_offer: ObjectHandle,
    cred_request: ObjectHandle,
    attr_names: FfiStrList,
    attr_raw_values: FfiStrList,
    attr_enc_values: FfiStrList,
    revocation: *const FfiCredRevInfo,
    cred_p: *mut ObjectHandle,
    rev_reg_p: *mut ObjectHandle,
    rev_delta_p: *mut ObjectHandle,
) -> ErrorCode {
    catch_error(|| {
        check_useful_c_ptr!(cred_p);
        if attr_names.is_empty() {
            return Err(err_msg!("Cannot create credential with no attribute"));
        }
        if attr_names.len() != attr_raw_values.len() {
            return Err(err_msg!(
                "Mismatch between length of attribute names and raw values"
            ));
        }
        let enc_values = attr_enc_values.as_slice()?;
        let mut cred_values = MakeCredentialValues::default();
        for (attr_idx, (name, raw)) in attr_names
            .as_slice()?
            .iter()
            .zip(attr_raw_values.as_slice()?)
            .enumerate()
        {
            let name = name
                .as_opt_str()
                .ok_or_else(|| err_msg!("Missing attribute name"))?
                .to_string();
            let raw = raw
                .as_opt_str()
                .ok_or_else(|| err_msg!("Missing attribute raw value"))?
                .to_string();
            let encoded = if attr_idx < enc_values.len() {
                enc_values[attr_idx].as_opt_str().map(str::to_string)
            } else {
                None
            };
            if let Some(encoded) = encoded {
                cred_values.add_encoded(name, raw, encoded);
            } else {
                cred_values.add_raw(name, raw)?;
            }
        }
        let revocation_config = if !revocation.is_null() {
            let revocation = unsafe { &*revocation };
            let mut reg_used = HashSet::new();
            for reg_idx in revocation.reg_used.as_slice()? {
                reg_used.insert(
                    (*reg_idx)
                        .try_into()
                        .map_err(|_| err_msg!("Invalid revocation index"))?,
                );
            }
            Some(RevocationConfig {
                reg_def: revocation.reg_def.load()?,
                reg_def_private: revocation.reg_def_private.load()?,
                registry: revocation.registry.load()?,
                reg_idx: revocation
                    .reg_idx
                    .try_into()
                    .map_err(|_| err_msg!("Invalid revocation index"))?,
                reg_used,
            })
        } else {
            None
        };
        let (cred, rev_reg, rev_delta) = create_credential(
            cred_def.load()?.cast_ref()?,
            cred_def_private.load()?.cast_ref()?,
            cred_offer.load()?.cast_ref()?,
            cred_request.load()?.cast_ref()?,
            cred_values.into(),
            revocation_config
                .as_ref()
                .map(RevocationConfig::as_ref_config)
                .transpose()?,
        )?;
        let cred = ObjectHandle::create(cred)?;
        let rev_reg = rev_reg
            .map(ObjectHandle::create)
            .transpose()?
            .unwrap_or_default();
        let rev_delta = rev_delta
            .map(ObjectHandle::create)
            .transpose()?
            .unwrap_or_default();
        unsafe {
            *cred_p = cred;
            *rev_reg_p = rev_reg;
            *rev_delta_p = rev_delta;
        };
        Ok(())
    })
}

#[no_mangle]
pub extern "C" fn credx_encode_credential_attributes(
    attr_raw_values: FfiStrList,
    result_p: *mut *const c_char,
) -> ErrorCode {
    catch_error(|| {
        let mut result = String::new();
        for raw_val in attr_raw_values.as_slice()? {
            let enc_val = encode_credential_attribute(
                raw_val
                    .as_opt_str()
                    .ok_or_else(|| err_msg!("Missing attribute raw value"))?,
            )?;
            if !result.is_empty() {
                result.push(',');
            }
            result.push_str(enc_val.as_str());
        }
        unsafe { *result_p = rust_string_to_c(result) };
        Ok(())
    })
}

#[no_mangle]
pub extern "C" fn credx_process_credential(
    cred: ObjectHandle,
    cred_req_metadata: ObjectHandle,
    link_secret: ObjectHandle,
    cred_def: ObjectHandle,
    rev_reg_def: ObjectHandle,
    cred_p: *mut ObjectHandle,
) -> ErrorCode {
    catch_error(|| {
        check_useful_c_ptr!(cred_p);
        let mut cred = cred
            .load()?
            .cast_ref::<Credential>()?
            .try_clone()
            .map_err(err_map!(Unexpected, "Error copying credential"))?;
        process_credential(
            &mut cred,
            cred_req_metadata.load()?.cast_ref()?,
            link_secret.load()?.cast_ref()?,
            cred_def.load()?.cast_ref()?,
            rev_reg_def
                .opt_load()?
                .as_ref()
                .map(IndyObject::cast_ref)
                .transpose()?,
        )?;
        let cred = ObjectHandle::create(cred)?;
        unsafe { *cred_p = cred };
        Ok(())
    })
}

impl_indy_object!(Credential, "Credential");
impl_indy_object_from_json!(Credential, credx_credential_from_json);

#[no_mangle]
pub extern "C" fn credx_credential_get_attribute(
    handle: ObjectHandle,
    name: FfiStr,
    result_p: *mut *const c_char,
) -> ErrorCode {
    catch_error(|| {
        check_useful_c_ptr!(result_p);
        let cred = handle.load()?;
        let cred = cred.cast_ref::<Credential>()?;
        let val = match name.as_opt_str().unwrap_or_default() {
            "schema_id" => rust_string_to_c(cred.schema_id.to_string()),
            "cred_def_id" => rust_string_to_c(cred.cred_def_id.to_string()),
            "rev_reg_id" => cred
                .rev_reg_id
                .as_ref()
                .map(|s| rust_string_to_c(s.to_string()))
                .unwrap_or(ptr::null_mut()),
            "rev_reg_index" => cred
                .signature
                .extract_index()
                .map(|s| rust_string_to_c(s.to_string()))
                .unwrap_or(ptr::null_mut()),
            s => return Err(err_msg!("Unsupported attribute: {}", s)),
        };
        unsafe { *result_p = val };
        Ok(())
    })
}