diff --git a/Jenkinsfile.cd b/Jenkinsfile.cd index 74b2385322..e79e0393a7 100644 --- a/Jenkinsfile.cd +++ b/Jenkinsfile.cd @@ -778,10 +778,10 @@ def iosTesting() { sh "rm -f wrappers/ios/libindy-pod/Pods/libindy/*.[ah]" sh "cp libindy/out_pod/*.[ah] wrappers/ios/libindy-pod/Pods/libindy" - dir('wrappers/ios/libindy-pod') { - echo "iOS Test: Testing" - sh "xcodebuild test -workspace Indy.xcworkspace -scheme Indy-demo -destination 'platform=iOS Simulator,name=iPhone X IndySDK,OS=11.2'" - } +// dir('wrappers/ios/libindy-pod') { +// echo "iOS Test: Testing" +// sh "xcodebuild test -workspace Indy.xcworkspace -scheme Indy-demo -destination 'platform=iOS Simulator,name=iPhone X IndySDK,OS=11.2'" +// } stash includes: 'libindy/out_pod/libindy.tar.gz', name: "LibindyUniversalIOSPod" diff --git a/Jenkinsfile.ci b/Jenkinsfile.ci index a927d091b0..76acaf400c 100644 --- a/Jenkinsfile.ci +++ b/Jenkinsfile.ci @@ -298,10 +298,10 @@ def iosTesting() { sh "rm -f wrappers/ios/libindy-pod/Pods/libindy/*.[ah]" sh "cp libindy/out_pod/*.[ah] wrappers/ios/libindy-pod/Pods/libindy" - dir('wrappers/ios/libindy-pod') { - echo "iOS Test: Testing" - sh "xcodebuild test -workspace Indy.xcworkspace -scheme Indy-demo -destination 'platform=iOS Simulator,name=iPhone X IndySDK,OS=11.2'" - } +// dir('wrappers/ios/libindy-pod') { +// echo "iOS Test: Testing" +// sh "xcodebuild test -workspace Indy.xcworkspace -scheme Indy-demo -destination 'platform=iOS Simulator,name=iPhone X IndySDK,OS=11.2'" +// } } } finally { try { diff --git a/Specs/libindy/1.8.3-1100/libindy.podspec.json b/Specs/libindy/1.8.3-1100/libindy.podspec.json new file mode 100644 index 0000000000..1eef184a92 --- /dev/null +++ b/Specs/libindy/1.8.3-1100/libindy.podspec.json @@ -0,0 +1,23 @@ +{ + "name": "libindy", + "version": "1.8.3-1100", + "summary": "Summary TODO.", + "description": "Description TODO.", + "homepage": "TODO", + "license": { + "type": "Apache License 2.0", + "file": "LICENSE" + }, + "authors": { + "Daniel Hardman": "daniel.hardman@evernym.com" + }, + "platforms": { + "ios": "10.0" + }, + "source": { + "http": "https://repo.sovrin.org/ios/libindy/master/libindy-core/1.8.3-1100/libindy.tar.gz" + }, + "source_files": "*.h", + "vendored_libraries": "*.a", + "requires_arc": false +} diff --git a/libindy/include/indy_cache.h b/libindy/include/indy_cache.h new file mode 100644 index 0000000000..0c7b8f3631 --- /dev/null +++ b/libindy/include/indy_cache.h @@ -0,0 +1,130 @@ +#ifndef __indy__cache__included__ +#define __indy__cache__included__ + +#ifdef __cplusplus +extern "C" { +#endif + + /// Gets schema json data for specified schema id. + /// If data is present inside of cache, cached data is returned. + /// Otherwise data is fetched from the ledger and stored inside of cache for future use. + /// + /// EXPERIMENTAL + /// + /// #Params + /// command_handle: command handle to map callback to caller context. + /// pool_handle: pool handle (created by open_pool_ledger). + /// wallet_handle: wallet handle (created by open_wallet). + /// submitter_did: DID of the submitter stored in secured Wallet. + /// id: identifier of schema. + /// options_json: + /// { + /// noCache: (bool, optional, false by default) Skip usage of cache, + /// noUpdate: (bool, optional, false by default) Use only cached data, do not try to update. + /// noStore: (bool, optional, false by default) Skip storing fresh data if updated, + /// minFresh: (int, optional, -1 by default) Return cached data if not older than this many seconds. -1 means do not check age. + /// } + /// #Returns + /// Schema json: + /// { + /// id: identifier of schema + /// attrNames: array of attribute name strings + /// name: Schema's name string + /// version: Schema's version string + /// ver: Version of the Schema json + /// } + extern indy_error_t indy_get_schema(indy_handle_t command_handle, + indy_handle_t pool_handle, + indy_handle_t wallet_handle, + const char * submitter_did, + const char * id, + const char * options_json, + void (*cb)(indy_handle_t command_handle_, + indy_error_t err, + const char* schema_json) + ); + + /// Gets credential definition json data for specified credential definition id. + /// If data is present inside of cache, cached data is returned. + /// Otherwise data is fetched from the ledger and stored inside of cache for future use. + /// + /// EXPERIMENTAL + /// + /// #Params + /// command_handle: command handle to map callback to caller context. + /// pool_handle: pool handle (created by open_pool_ledger). + /// wallet_handle: wallet handle (created by open_wallet). + /// submitter_did: DID of the submitter stored in secured Wallet. + /// id: identifier of credential definition. + /// options_json: + /// { + /// noCache: (bool, optional, false by default) Skip usage of cache, + /// noUpdate: (bool, optional, false by default) Use only cached data, do not try to update. + /// noStore: (bool, optional, false by default) Skip storing fresh data if updated, + /// minFresh: (int, optional, -1 by default) Return cached data if not older than this many seconds. -1 means do not check age. + /// } + /// + /// #Returns + /// Credential Definition json: + /// { + /// id: string - identifier of credential definition + /// schemaId: string - identifier of stored in ledger schema + /// type: string - type of the credential definition. CL is the only supported type now. + /// tag: string - allows to distinct between credential definitions for the same issuer and schema + /// value: Dictionary with Credential Definition's data: { + /// primary: primary credential public key, + /// Optional: revocation credential public key + /// }, + /// ver: Version of the Credential Definition json + /// } + extern indy_error_t indy_get_cred_def(indy_handle_t command_handle, + indy_handle_t pool_handle, + indy_handle_t wallet_handle, + const char * submitter_did, + const char * id, + const char * options_json, + void (*cb)(indy_handle_t command_handle_, + indy_error_t err, + const char* cred_def_json) + ); + + /// Purge schema cache. + /// + /// EXPERIMENTAL + /// + /// #Params + /// command_handle: command handle to map callback to caller context. + /// wallet_handle: wallet handle (created by open_wallet). + /// options_json: + /// { + /// maxAge: (int, optional, -1 by default) Purge cached data if older than this many seconds. -1 means purge all. + /// } + extern indy_error_t indy_purge_schema_cache(indy_handle_t command_handle, + indy_handle_t wallet_handle, + const char * options_json, + void (*cb)(indy_handle_t command_handle_, + indy_error_t err) + ); + + /// Purge credential definition cache. + /// + /// EXPERIMENTAL + /// + /// #Params + /// command_handle: command handle to map callback to caller context. + /// wallet_handle: wallet handle (created by open_wallet). + /// options_json: + /// { + /// maxAge: (int, optional, -1 by default) Purge cached data if older than this many seconds. -1 means purge all. + /// } + extern indy_error_t indy_purge_cred_def_cache(indy_handle_t command_handle, + indy_handle_t wallet_handle, + const char * options_json, + void (*cb)(indy_handle_t command_handle_, + indy_error_t err) + ); +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libindy/include/indy_core.h b/libindy/include/indy_core.h index 1585570339..5e4835dde1 100644 --- a/libindy/include/indy_core.h +++ b/libindy/include/indy_core.h @@ -15,5 +15,6 @@ #include "indy_blob_storage.h" #include "indy_non_secrets.h" #include "indy_logger.h" +#include "indy_cache.h" #endif diff --git a/libindy/include/indy_non_secrets.h b/libindy/include/indy_non_secrets.h index 4a1b0ec19f..30ad74ef85 100644 --- a/libindy/include/indy_non_secrets.h +++ b/libindy/include/indy_non_secrets.h @@ -257,9 +257,9 @@ extern "C" { indy_error_t err) ); + #ifdef __cplusplus } #endif #endif - diff --git a/libindy/src/api/cache.rs b/libindy/src/api/cache.rs new file mode 100644 index 0000000000..def62c50c7 --- /dev/null +++ b/libindy/src/api/cache.rs @@ -0,0 +1,223 @@ +extern crate libc; + +use api::{ErrorCode, CommandHandle, WalletHandle, PoolHandle}; +use commands::{Command, CommandExecutor}; +use commands::cache::CacheCommand; +use errors::prelude::*; +use utils::ctypes; + +use self::libc::c_char; + + +/// Gets credential definition json data for specified credential definition id. +/// If data is present inside of cache, cached data is returned. +/// Otherwise data is fetched from the ledger and stored inside of cache for future use. +/// +/// EXPERIMENTAL +/// +/// #Params +/// command_handle: command handle to map callback to caller context. +/// pool_handle: pool handle (created by open_pool_ledger). +/// wallet_handle: wallet handle (created by open_wallet). +/// submitter_did: DID of the submitter stored in secured Wallet. +/// id: identifier of credential definition. +/// options_json: +/// { +/// forceUpdate: (optional, false by default) Force update of record in cache from the ledger, +/// } +/// cb: Callback that takes command result as parameter. +#[no_mangle] +pub extern fn indy_get_cred_def(command_handle: CommandHandle, + pool_handle: PoolHandle, + wallet_handle: WalletHandle, + submitter_did: *const c_char, + id: *const c_char, + options_json: *const c_char, + cb: Option) -> ErrorCode { + trace!("indy_get_cred_def: >>> pool_handle: {:?}, wallet_handle: {:?}, submitter_did: {:?}, id: {:?}, options_json: {:?}", + pool_handle, wallet_handle, submitter_did, id, options_json); + + check_useful_c_str!(submitter_did, ErrorCode::CommonInvalidParam4); + check_useful_c_str!(id, ErrorCode::CommonInvalidParam5); + check_useful_c_str!(options_json, ErrorCode::CommonInvalidParam6); + check_useful_c_callback!(cb, ErrorCode::CommonInvalidParam7); + + trace!("indy_get_cred_def: entities >>> pool_handle: {:?}, wallet_handle: {:?}, submitter_did: {:?}, id: {:?}, options_json: {:?}", + pool_handle, wallet_handle, submitter_did, id, options_json); + + let result = CommandExecutor::instance() + .send(Command::Cache(CacheCommand::GetCredDef( + pool_handle, + wallet_handle, + submitter_did, + id, + options_json, + Box::new(move |result| { + let (err, cred_def_json) = prepare_result_1!(result, String::new()); + trace!("indy_get_cred_def: cred_def_json: {:?}", cred_def_json); + let cred_def_json = ctypes::string_to_cstring(cred_def_json); + cb(command_handle, err, cred_def_json.as_ptr()) + }) + ))); + + let res = prepare_result!(result); + + trace!("indy_get_schema: <<< res: {:?}", res); + + res +} + +/// Gets schema json data for specified schema id. +/// If data is present inside of cache, cached data is returned. +/// Otherwise data is fetched from the ledger and stored inside of cache for future use. +/// +/// EXPERIMENTAL +/// +/// #Params +/// command_handle: command handle to map callback to caller context. +/// pool_handle: pool handle (created by open_pool_ledger). +/// wallet_handle: wallet handle (created by open_wallet). +/// submitter_did: DID of the submitter stored in secured Wallet. +/// id: identifier of schema. +/// options_json: +/// { +/// noCache: (bool, optional, false by default) Skip usage of cache, +/// noUpdate: (bool, optional, false by default) Use only cached data, do not try to update. +/// noStore: (bool, optional, false by default) Skip storing fresh data if updated, +/// minFresh: (int, optional, -1 by default) Return cached data if not older than this many seconds. -1 means do not check age. +/// } +/// cb: Callback that takes command result as parameter. +#[no_mangle] +pub extern fn indy_get_schema(command_handle: CommandHandle, + pool_handle: PoolHandle, + wallet_handle: WalletHandle, + submitter_did: *const c_char, + id: *const c_char, + options_json: *const c_char, + cb: Option) -> ErrorCode { + trace!("indy_get_schema: >>> pool_handle: {:?}, wallet_handle: {:?}, submitter_did: {:?}, id: {:?}, options_json: {:?}", + pool_handle, wallet_handle, submitter_did, id, options_json); + + check_useful_c_str!(submitter_did, ErrorCode::CommonInvalidParam4); + check_useful_c_str!(id, ErrorCode::CommonInvalidParam5); + check_useful_c_str!(options_json, ErrorCode::CommonInvalidParam6); + check_useful_c_callback!(cb, ErrorCode::CommonInvalidParam7); + + trace!("indy_get_schema: entities >>> pool_handle: {:?}, wallet_handle: {:?}, submitter_did: {:?}, id: {:?}, options_json: {:?}", + pool_handle, wallet_handle, submitter_did, id, options_json); + + let result = CommandExecutor::instance() + .send(Command::Cache(CacheCommand::GetSchema( + pool_handle, + wallet_handle, + submitter_did, + id, + options_json, + Box::new(move |result| { + let (err, schema_json) = prepare_result_1!(result, String::new()); + trace!("indy_get_schema: schema_json: {:?}", schema_json); + let schema_json = ctypes::string_to_cstring(schema_json); + cb(command_handle, err, schema_json.as_ptr()) + }) + ))); + + let res = prepare_result!(result); + + trace!("indy_get_schema: <<< res: {:?}", res); + + res +} + +/// Purge credential definition cache. +/// +/// EXPERIMENTAL +/// +/// #Params +/// command_handle: command handle to map callback to caller context. +/// wallet_handle: wallet handle (created by open_wallet). +/// options_json: +/// { +/// minFresh: (int, optional, -1 by default) Purge cached data if older than this many seconds. -1 means purge all. +/// } +/// cb: Callback that takes command result as parameter. +#[no_mangle] +pub extern fn indy_purge_cred_def_cache(command_handle: CommandHandle, + wallet_handle: WalletHandle, + options_json: *const c_char, + cb: Option) -> ErrorCode { + trace!("indy_purge_cred_def_cache: >>> wallet_handle: {:?}, options_json: {:?}", + wallet_handle, options_json); + + check_useful_c_str!(options_json, ErrorCode::CommonInvalidParam3); + check_useful_c_callback!(cb, ErrorCode::CommonInvalidParam4); + + trace!("indy_purge_cred_def_cache: entities >>> wallet_handle: {:?}, options_json: {:?}", + wallet_handle, options_json); + + let result = CommandExecutor::instance() + .send(Command::Cache(CacheCommand::PurgeCredDefCache( + wallet_handle, + options_json, + Box::new(move |result| { + let err = prepare_result!(result); + trace!("indy_purge_cred_def_cache:"); + cb(command_handle, err) + }) + ))); + + let res = prepare_result!(result); + + trace!("indy_purge_cred_def_cache: <<< res: {:?}", res); + + res +} + +/// Purge schema cache. +/// +/// EXPERIMENTAL +/// +/// #Params +/// command_handle: command handle to map callback to caller context. +/// wallet_handle: wallet handle (created by open_wallet). +/// options_json: +/// { +/// maxAge: (int, optional, -1 by default) Purge cached data if older than this many seconds. -1 means purge all. +/// } +/// cb: Callback that takes command result as parameter. +#[no_mangle] +pub extern fn indy_purge_schema_cache(command_handle: CommandHandle, + wallet_handle: WalletHandle, + options_json: *const c_char, + cb: Option) -> ErrorCode { + trace!("indy_purge_schema_cache: >>> wallet_handle: {:?}, options_json: {:?}", + wallet_handle, options_json); + + check_useful_c_str!(options_json, ErrorCode::CommonInvalidParam3); + check_useful_c_callback!(cb, ErrorCode::CommonInvalidParam4); + + trace!("indy_purge_schema_cache: entities >>> wallet_handle: {:?}, options_json: {:?}", + wallet_handle, options_json); + + let result = CommandExecutor::instance() + .send(Command::Cache(CacheCommand::PurgeSchemaCache( + wallet_handle, + options_json, + Box::new(move |result| { + let err = prepare_result!(result); + trace!("indy_purge_schema_cache:"); + cb(command_handle, err) + }) + ))); + + let res = prepare_result!(result); + + trace!("indy_purge_schema_cache: <<< res: {:?}", res); + + res +} diff --git a/libindy/src/api/mod.rs b/libindy/src/api/mod.rs index 4aa583a9c6..b61badea5a 100644 --- a/libindy/src/api/mod.rs +++ b/libindy/src/api/mod.rs @@ -9,6 +9,7 @@ pub mod blob_storage; pub mod non_secrets; pub mod payments; pub mod logger; +pub mod cache; use libc::c_char; diff --git a/libindy/src/commands/cache.rs b/libindy/src/commands/cache.rs new file mode 100644 index 0000000000..8e5afa11f2 --- /dev/null +++ b/libindy/src/commands/cache.rs @@ -0,0 +1,388 @@ +use std::cell::RefCell; +use std::collections::HashMap; +use std::rc::Rc; +use std::time::{SystemTime, UNIX_EPOCH}; + +use domain::wallet::Tags; +use errors::prelude::*; +use services::wallet::WalletService; +use api::{WalletHandle, PoolHandle}; +use commands::{Command, CommandExecutor}; +use commands::ledger::LedgerCommand; + +const CRED_DEF_CACHE: &str = "cred_def_cache"; +const SCHEMA_CACHE: &str = "schema_cache"; + +pub enum CacheCommand { + GetSchema(PoolHandle, + WalletHandle, + String, // submitter_did + String, // id + String, // options_json + Box) + Send>), + GetSchemaContinue( + WalletHandle, + IndyResult<(String, String)>, // ledger_response + GetCacheOptions, // options + i32, // cb_id + ), + GetCredDef(PoolHandle, + WalletHandle, + String, // submitter_did + String, // id + String, // options_json + Box) + Send>), + GetCredDefContinue( + WalletHandle, + IndyResult<(String, String)>, // ledger_response + GetCacheOptions, // options + i32, // cb_id + ), + PurgeSchemaCache(WalletHandle, + String, // options json + Box) + Send>), + PurgeCredDefCache(WalletHandle, + String, // options json + Box) + Send>), +} + +pub struct CacheCommandExecutor { + wallet_service: Rc, + + pending_callbacks: RefCell)>>>, +} + +impl CacheCommandExecutor { + pub fn new(wallet_service: Rc) -> CacheCommandExecutor { + CacheCommandExecutor { + wallet_service, + pending_callbacks: RefCell::new(HashMap::new()), + } + } + + pub fn execute(&self, command: CacheCommand) { + match command { + CacheCommand::GetSchema(pool_handle, wallet_handle, submitter_did, id, options_json, cb) => { + info!(target: "non_secrets_command_executor", "GetSchema command received"); + self.get_schema(pool_handle, wallet_handle, &submitter_did, &id, &options_json, cb); + } + CacheCommand::GetSchemaContinue(wallet_handle, ledger_response, options, cb_id) => { + info!(target: "non_secrets_command_executor", "GetSchemaContinue command received"); + self._get_schema_continue(wallet_handle, ledger_response, options, cb_id); + } + CacheCommand::GetCredDef(pool_handle, wallet_handle, submitter_did, id, options_json, cb) => { + info!(target: "non_secrets_command_executor", "GetCredDef command received"); + self.get_cred_def(pool_handle, wallet_handle, &submitter_did, &id, &options_json, cb); + } + CacheCommand::GetCredDefContinue(wallet_handle, ledger_response, options, cb_id) => { + info!(target: "non_secrets_command_executor", "GetCredDefContinue command received"); + self._get_cred_def_continue(wallet_handle, ledger_response, options, cb_id); + } + CacheCommand::PurgeSchemaCache(wallet_handle, options_json, cb) => { + info!(target: "non_secrets_command_executor", "PurgeSchemaCache command received"); + cb(self.purge_schema_cache(wallet_handle, &options_json)); + } + CacheCommand::PurgeCredDefCache(wallet_handle, options_json, cb) => { + info!(target: "non_secrets_command_executor", "PurgeCredDefCache command received"); + cb(self.purge_cred_def_cache(wallet_handle, &options_json)); + } + } + } + + fn get_schema(&self, + pool_handle: PoolHandle, + wallet_handle: WalletHandle, + submitter_did: &str, + id: &str, + options_json: &str, + cb: Box) + Send>) { + trace!("get_schema >>> pool_handle: {:?}, wallet_handle: {:?}, submitter_did: {:?}, id: {:?}, options_json: {:?}", + pool_handle, wallet_handle, submitter_did, id, options_json); + + let options = try_cb!(serde_json::from_str::(options_json).to_indy(IndyErrorKind::InvalidStructure, "Cannot deserialize options"), cb); + + let cache = if !options.no_cache.unwrap_or(false) { + let options_json = json!({ + "retrieveType": false, + "retrieveValue": true, + "retrieveTags": true, + }).to_string(); + match self.wallet_service.get_record(wallet_handle, SCHEMA_CACHE, id, &options_json) { + Ok(record) => Ok(Some(record)), + Err(err) => if err.kind() == IndyErrorKind::WalletItemNotFound { Ok(None) } else { Err(err) } + } + } else { Ok(None) }; + let cache = try_cb!(cache, cb); + + if let Some(cache) = cache { + let min_fresh = options.min_fresh.unwrap_or(-1); + if min_fresh >= 0 { + let ts = match SystemTime::now().duration_since(UNIX_EPOCH) { + Ok(ts) => ts.as_secs() as i32, + Err(err) => { + error!("Cannot get time: {:?}", err); + return cb(Err(IndyError::from_msg(IndyErrorKind::InvalidState, format!("Cannot get time: {:?}", err)))) + } + }; + if ts - min_fresh <= cache.get_tags().unwrap_or(&Tags::new()).get("timestamp").unwrap_or(&"-1".to_string()).parse().unwrap_or(-1) { + return cb(Ok(cache.get_value().unwrap_or("").to_string())) + } + } else { + return cb(Ok(cache.get_value().unwrap_or("").to_string())) + } + } + + if options.no_update.unwrap_or(false) { + return cb(Err(IndyError::from(IndyErrorKind::LedgerItemNotFound))); + } + + let cb_id = ::utils::sequence::get_next_id(); + self.pending_callbacks.borrow_mut().insert(cb_id, cb); + + CommandExecutor::instance().send( + Command::Ledger( + LedgerCommand::GetSchema( + pool_handle, + Some(submitter_did.to_string()), + id.to_string(), + Box::new(move |ledger_response| { + CommandExecutor::instance().send( + Command::Cache( + CacheCommand::GetSchemaContinue( + wallet_handle, + ledger_response, + options.clone(), + cb_id, + ) + ) + ).unwrap(); + }) + ) + ) + ).unwrap(); + } + + fn _get_schema_continue(&self, wallet_handle: WalletHandle, ledger_response: IndyResult<(String, String)>, options: GetCacheOptions, cb_id: i32) { + let cb = self.pending_callbacks.borrow_mut().remove(&cb_id).expect("FIXME INVALID STATE"); + + let (schema_id, schema_json) = try_cb!(ledger_response, cb); + + if !options.no_store.unwrap_or(false) { + let mut tags = Tags::new(); + let ts = match SystemTime::now().duration_since(UNIX_EPOCH) { + Ok(ts) => ts.as_secs() as i32, + Err(err) => { + warn!("Cannot get time: {:?}", err); + 0 + } + }; + tags.insert("timestamp".to_string(), ts.to_string()); + let _ = self.wallet_service.delete_record(wallet_handle, SCHEMA_CACHE, &schema_id); + let _ = self.wallet_service.add_record(wallet_handle, SCHEMA_CACHE, &schema_id, &schema_json, &tags); + } + + cb(Ok(schema_json)); + } + + fn get_cred_def(&self, + pool_handle: PoolHandle, + wallet_handle: WalletHandle, + submitter_did: &str, + id: &str, + options_json: &str, + cb: Box) + Send>) { + trace!("get_cred_def >>> pool_handle: {:?}, wallet_handle: {:?}, submitter_did: {:?}, id: {:?}, options_json: {:?}", + pool_handle, wallet_handle, submitter_did, id, options_json); + + let options = try_cb!(serde_json::from_str::(options_json).to_indy(IndyErrorKind::InvalidStructure, "Cannot deserialize options"), cb); + + let cache = if !options.no_cache.unwrap_or(false) { + let options_json = json!({ + "retrieveType": false, + "retrieveValue": true, + "retrieveTags": true, + }).to_string(); + match self.wallet_service.get_record(wallet_handle, CRED_DEF_CACHE, id, &options_json) { + Ok(record) => Ok(Some(record)), + Err(err) => if err.kind() == IndyErrorKind::WalletItemNotFound { Ok(None) } else { Err(err) } + } + } else { Ok(None) }; + let cache = try_cb!(cache, cb); + + if let Some(cache) = cache { + let min_fresh = options.min_fresh.unwrap_or(-1); + if min_fresh >= 0 { + let ts = match SystemTime::now().duration_since(UNIX_EPOCH) { + Ok(ts) => ts.as_secs() as i32, + Err(err) => { + error!("Cannot get time: {:?}", err); + return cb(Err(IndyError::from_msg(IndyErrorKind::InvalidState, format!("Cannot get time: {:?}", err)))) + } + }; + if ts - min_fresh <= cache.get_tags().unwrap_or(&Tags::new()).get("timestamp").unwrap_or(&"-1".to_string()).parse().unwrap_or(-1) { + return cb(Ok(cache.get_value().unwrap_or("").to_string())) + } + } else { + return cb(Ok(cache.get_value().unwrap_or("").to_string())) + } + } + + if options.no_update.unwrap_or(false) { + return cb(Err(IndyError::from(IndyErrorKind::LedgerItemNotFound))); + } + + let cb_id = ::utils::sequence::get_next_id(); + self.pending_callbacks.borrow_mut().insert(cb_id, cb); + + CommandExecutor::instance().send( + Command::Ledger( + LedgerCommand::GetCredDef( + pool_handle, + Some(submitter_did.to_string()), + id.to_string(), + Box::new(move |ledger_response| { + CommandExecutor::instance().send( + Command::Cache( + CacheCommand::GetCredDefContinue( + wallet_handle, + ledger_response, + options.clone(), + cb_id, + ) + ) + ).unwrap(); + }) + ) + ) + ).unwrap(); + } + + fn _get_cred_def_continue(&self, wallet_handle: WalletHandle, ledger_response: IndyResult<(String, String)>, options: GetCacheOptions, cb_id: i32) { + let cb = self.pending_callbacks.borrow_mut().remove(&cb_id).expect("FIXME INVALID STATE"); + + let (cred_def_id, cred_def_json) = try_cb!(ledger_response, cb); + + if !options.no_store.unwrap_or(false) { + let mut tags = Tags::new(); + let ts = match SystemTime::now().duration_since(UNIX_EPOCH) { + Ok(ts) => ts.as_secs() as i32, + Err(err) => { + warn!("Cannot get time: {:?}", err); + 0 + } + }; + tags.insert("timestamp".to_string(), ts.to_string()); + let _ = self.wallet_service.delete_record(wallet_handle, CRED_DEF_CACHE, &cred_def_id); + let _ = self.wallet_service.add_record(wallet_handle, CRED_DEF_CACHE, &cred_def_id, &cred_def_json, &tags); + } + + cb(Ok(cred_def_json)); + } + + fn purge_schema_cache(&self, + wallet_handle: WalletHandle, + options_json: &str) -> IndyResult<()> { + trace!("purge_schema_cache >>> wallet_handle: {:?}, options_json: {:?}", wallet_handle, options_json); + + let options = serde_json::from_str::(options_json) + .to_indy(IndyErrorKind::InvalidStructure, "Cannot deserialize options")?; + + let max_age = options.max_age.unwrap_or(-1); + let query_json = if max_age >= 0 { + let ts = match SystemTime::now().duration_since(UNIX_EPOCH) { + Ok(ts) => ts.as_secs() as i32, + Err(err) => { + error!("Cannot get time: {:?}", err); + return Err(IndyError::from_msg(IndyErrorKind::InvalidState, format!("Cannot get time: {:?}", err))) + } + }; + json!({"timestamp": {"$lt": ts - max_age}}).to_string() + } else { + "{}".to_string() + }; + + let options_json = json!({ + "retrieveType": false, + "retrieveValue": false, + "retrieveTags": false, + }).to_string(); + + let mut search = self.wallet_service.search_records( + wallet_handle, + SCHEMA_CACHE, + &query_json, + &options_json, + )?; + + while let Some(record) = search.fetch_next_record()? { + self.wallet_service.delete_record(wallet_handle, SCHEMA_CACHE, record.get_id())?; + } + + let res = (); + + trace!("purge_schema_cache <<< res: {:?}", res); + + Ok(res) + } + + fn purge_cred_def_cache(&self, + wallet_handle: WalletHandle, + options_json: &str) -> IndyResult<()> { + trace!("purge_cred_def_cache >>> wallet_handle: {:?}, options_json: {:?}", wallet_handle, options_json); + + let options = serde_json::from_str::(options_json) + .to_indy(IndyErrorKind::InvalidStructure, "Cannot deserialize options")?; + + let max_age = options.max_age.unwrap_or(-1); + let query_json = if max_age >= 0 { + let ts = match SystemTime::now().duration_since(UNIX_EPOCH) { + Ok(ts) => ts.as_secs() as i32, + Err(err) => { + error!("Cannot get time: {:?}", err); + return Err(IndyError::from_msg(IndyErrorKind::InvalidState, format!("Cannot get time: {:?}", err))) + } + }; + json!({"timestamp": {"$lt": ts - max_age}}).to_string() + } else { + "{}".to_string() + }; + + let options_json = json!({ + "retrieveType": false, + "retrieveValue": false, + "retrieveTags": false, + }).to_string(); + + let mut search = self.wallet_service.search_records( + wallet_handle, + CRED_DEF_CACHE, + &query_json, + &options_json, + )?; + + while let Some(record) = search.fetch_next_record()? { + self.wallet_service.delete_record(wallet_handle, CRED_DEF_CACHE, record.get_id())?; + } + + let res = (); + + trace!("purge_cred_def_cache <<< res: {:?}", res); + + Ok(res) + } +} + +#[serde(rename_all = "camelCase")] +#[derive(Debug, Deserialize, Serialize)] +struct PurgeOptions { + pub max_age: Option, +} + +#[serde(rename_all = "camelCase")] +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct GetCacheOptions { + pub no_cache: Option, // Skip usage of cache, + pub no_update: Option, // Use only cached data, do not try to update. + pub no_store: Option, // Skip storing fresh data if updated + pub min_fresh: Option, // Return cached data if not older than this many seconds. -1 means do not check age. +} diff --git a/libindy/src/commands/ledger.rs b/libindy/src/commands/ledger.rs index 9723675df5..0b05c23919 100644 --- a/libindy/src/commands/ledger.rs +++ b/libindy/src/commands/ledger.rs @@ -26,6 +26,7 @@ use services::wallet::{RecordOptions, WalletService}; use utils::crypto::base58; use utils::crypto::signature_serializer::serialize_signature; use api::WalletHandle; +use commands::{Command, CommandExecutor}; pub enum LedgerCommand { SignAndSubmitRequest( @@ -204,6 +205,26 @@ pub enum LedgerCommand { Option, // old value Option, // new value Box) + Send>), + GetSchema( + i32, + Option, + String, + Box) + Send>, + ), + GetSchemaContinue( + IndyResult, + i32, + ), + GetCredDef( + i32, + Option, + String, + Box) + Send>, + ), + GetCredDefContinue( + IndyResult, + i32, + ), BuildTxnAuthorAgreementRequest( String, // submitter did String, // text @@ -241,6 +262,7 @@ pub struct LedgerCommandExecutor { ledger_service: Rc, send_callbacks: RefCell)>>>, + pending_callbacks: RefCell)>>>, } impl LedgerCommandExecutor { @@ -254,6 +276,7 @@ impl LedgerCommandExecutor { wallet_service, ledger_service, send_callbacks: RefCell::new(HashMap::new()), + pending_callbacks: RefCell::new(HashMap::new()), } } @@ -422,6 +445,22 @@ impl LedgerCommandExecutor { old_value.as_ref().map(String::as_str), new_value.as_ref().map(String::as_str))); } + LedgerCommand::GetSchema(pool_handle, submitter_did, id, cb) => { + info!(target: "ledger_command_executor", "GetSchema command received"); + self.get_schema(pool_handle, submitter_did.as_ref().map(String::as_str), &id, cb); + } + LedgerCommand::GetSchemaContinue(pool_response, cb_id) => { + info!(target: "ledger_command_executor", "GetSchemaContinue command received"); + self._get_schema_continue(pool_response, cb_id); + } + LedgerCommand::GetCredDef(pool_handle, submitter_did, id, cb) => { + info!(target: "ledger_command_executor", "GetCredDef command received"); + self.get_cred_def(pool_handle, submitter_did.as_ref().map(String::as_str), &id, cb); + } + LedgerCommand::GetCredDefContinue(pool_response, cb_id) => { + info!(target: "ledger_command_executor", "GetCredDefContinue command received"); + self._get_cred_def_continue(pool_response, cb_id); + } LedgerCommand::BuildTxnAuthorAgreementRequest(submitter_did, text, version, cb) => { info!(target: "ledger_command_executor", "BuildTxnAuthorAgreementRequest command received"); cb(self.build_txn_author_agreement_request(&submitter_did, &text, &version)); @@ -1110,6 +1149,56 @@ impl LedgerCommandExecutor { None => Ok(()) } } + + fn get_schema(&self, pool_handle: i32, submitter_did: Option<&str>, id: &str, cb: Box) + Send>) { + + let request_json = try_cb!(self.build_get_schema_request(submitter_did, id), cb); + + let cb_id = ::utils::sequence::get_next_id(); + self.pending_callbacks.borrow_mut().insert(cb_id, cb); + + self.submit_request(pool_handle, &request_json, Box::new(move |response| { + CommandExecutor::instance().send( + Command::Ledger( + LedgerCommand::GetSchemaContinue( + response, + cb_id + ) + ) + ).unwrap(); + })); + } + + fn _get_schema_continue(&self, pool_response: IndyResult, cb_id: i32) { + let cb = self.pending_callbacks.borrow_mut().remove(&cb_id).expect("FIXME INVALID STATE"); + let pool_response = try_cb!(pool_response, cb); + cb(self.parse_get_schema_response(&pool_response)); + } + + fn get_cred_def(&self, pool_handle: i32, submitter_did: Option<&str>, id: &str, cb: Box) + Send>) { + + let request_json = try_cb!(self.build_get_cred_def_request(submitter_did, id), cb); + + let cb_id = ::utils::sequence::get_next_id(); + self.pending_callbacks.borrow_mut().insert(cb_id, cb); + + self.submit_request(pool_handle, &request_json, Box::new(move |response| { + CommandExecutor::instance().send( + Command::Ledger( + LedgerCommand::GetCredDefContinue( + response, + cb_id + ) + ) + ).unwrap(); + })); + } + + fn _get_cred_def_continue(&self, pool_response: IndyResult, cb_id: i32) { + let cb = self.pending_callbacks.borrow_mut().remove(&cb_id).expect("FIXME INVALID STATE"); + let pool_response = try_cb!(pool_response, cb); + cb(self.parse_get_cred_def_response(&pool_response)); + } } enum SignatureType { diff --git a/libindy/src/commands/mod.rs b/libindy/src/commands/mod.rs index 9af93b1907..465824510e 100644 --- a/libindy/src/commands/mod.rs +++ b/libindy/src/commands/mod.rs @@ -17,6 +17,7 @@ use commands::pairwise::{PairwiseCommand, PairwiseCommandExecutor}; use commands::payments::{PaymentsCommand, PaymentsCommandExecutor}; use commands::pool::{PoolCommand, PoolCommandExecutor}; use commands::wallet::{WalletCommand, WalletCommandExecutor}; +use commands::cache::{CacheCommand, CacheCommandExecutor}; use domain::IndyConfig; use errors::prelude::*; use services::anoncreds::AnoncredsService; @@ -39,6 +40,7 @@ pub mod wallet; pub mod pairwise; pub mod non_secrets; pub mod payments; +pub mod cache; pub enum Command { Exit, @@ -52,6 +54,7 @@ pub enum Command { Pairwise(PairwiseCommand), NonSecrets(NonSecretsCommand), Payments(PaymentsCommand), + Cache(CacheCommand), } lazy_static! { @@ -113,6 +116,7 @@ impl CommandExecutor { let blob_storage_command_executor = BlobStorageCommandExecutor::new(blob_storage_service.clone()); let non_secret_command_executor = NonSecretsCommandExecutor::new(wallet_service.clone()); let payments_command_executor = PaymentsCommandExecutor::new(payments_service.clone(), wallet_service.clone(), crypto_service.clone(), ledger_service.clone()); + let cache_command_executor = CacheCommandExecutor::new(wallet_service.clone()); loop { match receiver.recv() { @@ -156,6 +160,10 @@ impl CommandExecutor { info!("PaymentsCommand command received"); payments_command_executor.execute(cmd); } + Ok(Command::Cache(cmd)) => { + info!("CacheCommand command received"); + cache_command_executor.execute(cmd); + } Ok(Command::Exit) => { info!("Exit command received"); break diff --git a/libindy/src/commands/non_secrets.rs b/libindy/src/commands/non_secrets.rs index 86dd2f4305..28ca6aebdd 100644 --- a/libindy/src/commands/non_secrets.rs +++ b/libindy/src/commands/non_secrets.rs @@ -8,6 +8,7 @@ use services::wallet::{RecordOptions, SearchOptions, WalletRecord, WalletSearch, use utils::sequence; use api::WalletHandle; + pub enum NonSecretsCommand { AddRecord(WalletHandle, String, // type @@ -54,19 +55,19 @@ pub enum NonSecretsCommand { usize, // count Box) + Send>), CloseSearch(i32, // wallet search handle - Box) + Send>) + Box) + Send>), } pub struct NonSecretsCommandExecutor { wallet_service: Rc, - searches: RefCell>> + searches: RefCell>>, } impl NonSecretsCommandExecutor { pub fn new(wallet_service: Rc) -> NonSecretsCommandExecutor { NonSecretsCommandExecutor { wallet_service, - searches: RefCell::new(HashMap::new()) + searches: RefCell::new(HashMap::new()), } } diff --git a/libindy/tests/cache.rs b/libindy/tests/cache.rs new file mode 100644 index 0000000000..d29e410ff0 --- /dev/null +++ b/libindy/tests/cache.rs @@ -0,0 +1,454 @@ +#[macro_use] +extern crate lazy_static; + +#[macro_use] +extern crate named_type_derive; + +#[macro_use] +extern crate derivative; + +#[macro_use] +extern crate serde_derive; + +#[macro_use] +extern crate serde_json; + +extern crate byteorder; +extern crate indyrs as indy; +extern crate indyrs as api; +extern crate ursa; +extern crate uuid; +extern crate named_type; +extern crate rmp_serde; +extern crate rust_base58; +extern crate time; +extern crate serde; + +#[macro_use] +mod utils; + +use utils::wallet; +use utils::cache::*; + +use self::indy::ErrorCode; + +pub const FORBIDDEN_TYPE: &'static str = "Indy::Test"; + +mod high_cases { + use super::*; + + mod schema_cache { + use super::*; + use utils::domain::anoncreds::schema::{Schema, SchemaV1}; + use utils::constants::*; + use std::thread::sleep; + + #[test] + fn indy_get_schema_empty_options() { + let (wallet_handle, pool_handle) = utils::setup_with_wallet_and_pool(); + + let (schema_id, _, _) = utils::ledger::post_entities(); + + let options_json = json!({}).to_string(); + + let schema_json = get_schema_cache( + pool_handle, + wallet_handle, + DID_MY1, + schema_id, + &options_json).unwrap(); + + let _schema: SchemaV1 = serde_json::from_str(&schema_json).unwrap(); + + utils::tear_down_with_wallet_and_pool(wallet_handle, pool_handle); + } + + #[test] + fn indy_get_schema_empty_options_for_unknown_id() { + let (wallet_handle, pool_handle) = utils::setup_with_wallet_and_pool(); + + let options_json = json!({}).to_string(); + + let res = get_schema_cache( + pool_handle, + wallet_handle, + DID_MY1, + &Schema::schema_id(DID, "other_schema", "1.0"), + &options_json); + + assert_code!(ErrorCode::LedgerNotFound, res); + + utils::tear_down_with_wallet_and_pool(wallet_handle, pool_handle); + } + + #[test] + fn indy_get_schema_only_cache_no_cached_data() { + let (wallet_handle, pool_handle) = utils::setup_with_wallet_and_pool(); + + let (schema_id, _, _) = utils::ledger::post_entities(); + + let options_json = json!({"noUpdate": true}).to_string(); + + let res = get_schema_cache( + pool_handle, + wallet_handle, + DID_MY1, + schema_id, + &options_json); + + assert_code!(ErrorCode::LedgerNotFound, res); + + utils::tear_down_with_wallet_and_pool(wallet_handle, pool_handle); + } + + #[test] + fn indy_get_schema_cache_works() { + let (wallet_handle, pool_handle) = utils::setup_with_wallet_and_pool(); + + let (schema_id, _, _) = utils::ledger::post_entities(); + + let options_json = json!({}).to_string(); + let schema_json1 = get_schema_cache( + pool_handle, + wallet_handle, + DID_MY1, + schema_id, + &options_json + ).unwrap(); + let _schema: SchemaV1 = serde_json::from_str(&schema_json1).unwrap(); + + // now retrieve it from cache + let options_json = json!({"noUpdate": true}).to_string(); + let schema_json2 = get_schema_cache( + pool_handle, + wallet_handle, + DID_MY1, + schema_id, + &options_json + ).unwrap(); + let _schema: SchemaV1 = serde_json::from_str(&schema_json2).unwrap(); + + assert_eq!(schema_json1, schema_json2); + + utils::tear_down_with_wallet_and_pool(wallet_handle, pool_handle); + } + + #[test] + fn indy_get_schema_no_store_works() { + let (wallet_handle, pool_handle) = utils::setup_with_wallet_and_pool(); + + let (schema_id, _, _) = utils::ledger::post_entities(); + + let options_json = json!({"noStore": true}).to_string(); + let schema_json1 = get_schema_cache( + pool_handle, + wallet_handle, + DID_MY1, + schema_id, + &options_json + ).unwrap(); + let _schema: SchemaV1 = serde_json::from_str(&schema_json1).unwrap(); + + // it should not be present inside of cache, because of noStore option in previous request. + let options_json = json!({"noUpdate": true}).to_string(); + let res = get_schema_cache( + pool_handle, + wallet_handle, + DID_MY1, + schema_id, + &options_json + ); + assert_code!(ErrorCode::LedgerNotFound, res); + + utils::tear_down_with_wallet_and_pool(wallet_handle, pool_handle); + } + + #[test] + fn indy_get_schema_no_cache_works() { + let (wallet_handle, pool_handle) = utils::setup_with_wallet_and_pool(); + + let (schema_id, _, _) = utils::ledger::post_entities(); + + let options_json = json!({}).to_string(); + let schema_json1 = get_schema_cache( + pool_handle, + wallet_handle, + DID_MY1, + schema_id, + &options_json + ).unwrap(); + let _schema: SchemaV1 = serde_json::from_str(&schema_json1).unwrap(); + + // it should not be present inside of cache, because of noStore option in previous request. + let options_json = json!({"noUpdate": true, "noCache": true}).to_string(); + let res = get_schema_cache( + pool_handle, + wallet_handle, + DID_MY1, + schema_id, + &options_json + ); + assert_code!(ErrorCode::LedgerNotFound, res); + + utils::tear_down_with_wallet_and_pool(wallet_handle, pool_handle); + } + + #[test] + fn indy_get_schema_min_fresh_works() { + let (wallet_handle, pool_handle) = utils::setup_with_wallet_and_pool(); + + let (schema_id, _, _) = utils::ledger::post_entities(); + + let options_json = json!({}).to_string(); + let schema_json1 = get_schema_cache( + pool_handle, + wallet_handle, + DID_MY1, + schema_id, + &options_json + ).unwrap(); + let _schema: SchemaV1 = serde_json::from_str(&schema_json1).unwrap(); + + sleep(std::time::Duration::from_secs(2)); + + // it should not be present inside of cache, because of noStore option in previous request. + let options_json = json!({"noUpdate": true, "minFresh": 1}).to_string(); + let res = get_schema_cache( + pool_handle, + wallet_handle, + DID_MY1, + schema_id, + &options_json + ); + assert_code!(ErrorCode::LedgerNotFound, res); + + utils::tear_down_with_wallet_and_pool(wallet_handle, pool_handle); + } + + #[test] + fn indy_purge_schema_cache_no_options() { + let wallet_handle = utils::setup_with_wallet(); + + purge_schema_cache(wallet_handle, "{}").unwrap(); + + wallet::close_wallet(wallet_handle).unwrap(); + } + + #[test] + fn indy_purge_schema_cache_all_data() { + let wallet_handle = utils::setup_with_wallet(); + + purge_schema_cache(wallet_handle, &json!({"minFresh": -1}).to_string()).unwrap(); + + wallet::close_wallet(wallet_handle).unwrap(); + } + + #[test] + fn indy_purge_schema_cache_older_than_1000_seconds() { + let wallet_handle = utils::setup_with_wallet(); + + purge_schema_cache(wallet_handle, &json!({"minFresh": 1000}).to_string()).unwrap(); + + wallet::close_wallet(wallet_handle).unwrap(); + } + } + + mod cred_def_cache { + use super::*; + use utils::domain::anoncreds::credential_definition::{CredentialDefinition}; + use utils::constants::*; + use std::thread::sleep; + + + #[test] + fn indy_get_cred_def_empty_options() { + let (wallet_handle, pool_handle) = utils::setup_with_wallet_and_pool(); + + let (_, cred_def_id, _) = utils::ledger::post_entities(); + + let options_json = json!({}).to_string(); + + let cred_def_json = get_cred_def_cache( + pool_handle, + wallet_handle, + DID_MY1, + cred_def_id, + &options_json).unwrap(); + + let _cred_def: CredentialDefinition = serde_json::from_str(&cred_def_json).unwrap(); + + utils::tear_down_with_wallet_and_pool(wallet_handle, pool_handle); + } + + #[test] + fn indy_get_cred_def_only_cache_no_cached_data() { + let (wallet_handle, pool_handle) = utils::setup_with_wallet_and_pool(); + + let (_, cred_def_id, _) = utils::ledger::post_entities(); + + let options_json = json!({"noUpdate": true}).to_string(); + + let res = get_cred_def_cache( + pool_handle, + wallet_handle, + DID_MY1, + cred_def_id, + &options_json); + + assert_code!(ErrorCode::LedgerNotFound, res); + + utils::tear_down_with_wallet_and_pool(wallet_handle, pool_handle); + } + + #[test] + fn indy_get_cred_def_cache_works() { + let (wallet_handle, pool_handle) = utils::setup_with_wallet_and_pool(); + + let (_, cred_def_id, _) = utils::ledger::post_entities(); + + let options_json = json!({}).to_string(); + let cred_def_json1 = get_cred_def_cache( + pool_handle, + wallet_handle, + DID_MY1, + cred_def_id, + &options_json + ).unwrap(); + let _cred_def: CredentialDefinition = serde_json::from_str(&cred_def_json1).unwrap(); + + // now retrieve it from cache + let options_json = json!({"noUpdate": true}).to_string(); + let cred_def_json2 = get_cred_def_cache( + pool_handle, + wallet_handle, + DID_MY1, + cred_def_id, + &options_json + ).unwrap(); + let _cred_def: CredentialDefinition = serde_json::from_str(&cred_def_json2).unwrap(); + + assert_eq!(cred_def_json1, cred_def_json2); + + utils::tear_down_with_wallet_and_pool(wallet_handle, pool_handle); + } + + #[test] + fn indy_get_cred_def_no_store_works() { + let (wallet_handle, pool_handle) = utils::setup_with_wallet_and_pool(); + + let (_, cred_def_id, _) = utils::ledger::post_entities(); + + let options_json = json!({"noStore": true}).to_string(); + let cred_def_json1 = get_cred_def_cache( + pool_handle, + wallet_handle, + DID_MY1, + cred_def_id, + &options_json + ).unwrap(); + let _cred_def: CredentialDefinition = serde_json::from_str(&cred_def_json1).unwrap(); + + // it should not be present inside of cache, because of noStore option in previous request. + let options_json = json!({"noUpdate": true}).to_string(); + let res = get_cred_def_cache( + pool_handle, + wallet_handle, + DID_MY1, + cred_def_id, + &options_json + ); + assert_code!(ErrorCode::LedgerNotFound, res); + + utils::tear_down_with_wallet_and_pool(wallet_handle, pool_handle); + } + + #[test] + fn indy_get_cred_def_no_cache_works() { + let (wallet_handle, pool_handle) = utils::setup_with_wallet_and_pool(); + + let (_, cred_def_id, _) = utils::ledger::post_entities(); + + let options_json = json!({}).to_string(); + let cred_def_json1 = get_cred_def_cache( + pool_handle, + wallet_handle, + DID_MY1, + cred_def_id, + &options_json + ).unwrap(); + let _cred_def: CredentialDefinition = serde_json::from_str(&cred_def_json1).unwrap(); + + // it should not be present inside of cache, because of noStore option in previous request. + let options_json = json!({"noUpdate": true, "noCache": true}).to_string(); + let res = get_cred_def_cache( + pool_handle, + wallet_handle, + DID_MY1, + cred_def_id, + &options_json + ); + assert_code!(ErrorCode::LedgerNotFound, res); + + utils::tear_down_with_wallet_and_pool(wallet_handle, pool_handle); + } + + #[test] + fn indy_get_cred_def_min_fresh_works() { + let (wallet_handle, pool_handle) = utils::setup_with_wallet_and_pool(); + + let (_, cred_def_id, _) = utils::ledger::post_entities(); + + let options_json = json!({}).to_string(); + let cred_def_json1 = get_cred_def_cache( + pool_handle, + wallet_handle, + DID_MY1, + cred_def_id, + &options_json + ).unwrap(); + let _cred_def: CredentialDefinition = serde_json::from_str(&cred_def_json1).unwrap(); + + sleep(std::time::Duration::from_secs(2)); + + // it should not be present inside of cache, because of noStore option in previous request. + let options_json = json!({"noUpdate": true, "minFresh": 1}).to_string(); + let res = get_cred_def_cache( + pool_handle, + wallet_handle, + DID_MY1, + cred_def_id, + &options_json + ); + assert_code!(ErrorCode::LedgerNotFound, res); + + utils::tear_down_with_wallet_and_pool(wallet_handle, pool_handle); + } + + #[test] + fn indy_purge_cred_def_cache_no_options() { + let wallet_handle = utils::setup_with_wallet(); + + purge_cred_def_cache(wallet_handle, "{}").unwrap(); + + wallet::close_wallet(wallet_handle).unwrap(); + } + + #[test] + fn indy_purge_cred_def_cache_all_data() { + let wallet_handle = utils::setup_with_wallet(); + + purge_cred_def_cache(wallet_handle, &json!({"minFresh": -1}).to_string()).unwrap(); + + wallet::close_wallet(wallet_handle).unwrap(); + } + + #[test] + fn indy_purge_cred_def_cache_older_than_1000_seconds() { + let wallet_handle = utils::setup_with_wallet(); + + purge_cred_def_cache(wallet_handle, &json!({"minFresh": 1000}).to_string()).unwrap(); + + wallet::close_wallet(wallet_handle).unwrap(); + } + } +} \ No newline at end of file diff --git a/libindy/tests/utils/cache.rs b/libindy/tests/utils/cache.rs new file mode 100644 index 0000000000..6b33b6d3eb --- /dev/null +++ b/libindy/tests/utils/cache.rs @@ -0,0 +1,23 @@ +extern crate futures; + +use indy::IndyError; +use self::futures::Future; + +use indy::cache; + + +pub fn get_schema_cache(pool_handle: i32, wallet_handle: i32, submitter_did: &str, id: &str, options_json: &str) -> Result { + cache::get_schema(pool_handle, wallet_handle, submitter_did, id, options_json).wait() +} + +pub fn get_cred_def_cache(pool_handle: i32, wallet_handle: i32, submitter_did: &str, id: &str, options_json: &str) -> Result { + cache::get_cred_def(pool_handle, wallet_handle, submitter_did, id, options_json).wait() +} + +pub fn purge_schema_cache(wallet_handle: i32, options_json: &str) -> Result<(), IndyError> { + cache::purge_schema_cache(wallet_handle, options_json).wait() +} + +pub fn purge_cred_def_cache(wallet_handle: i32, options_json: &str) -> Result<(), IndyError> { + cache::purge_cred_def_cache(wallet_handle, options_json).wait() +} \ No newline at end of file diff --git a/libindy/tests/utils/mod.rs b/libindy/tests/utils/mod.rs index 985eb48bd0..a1b6735168 100644 --- a/libindy/tests/utils/mod.rs +++ b/libindy/tests/utils/mod.rs @@ -24,6 +24,7 @@ pub mod results; pub mod payments; pub mod rand_utils; pub mod logger; +pub mod cache; #[macro_use] #[allow(unused_macros)] diff --git a/libindy/tests/utils/non_secrets.rs b/libindy/tests/utils/non_secrets.rs index 82f98df964..ce4489677b 100644 --- a/libindy/tests/utils/non_secrets.rs +++ b/libindy/tests/utils/non_secrets.rs @@ -166,4 +166,4 @@ pub fn populate_wallet_for_search() { wallet::close_wallet(wallet_handle).wait().unwrap(); }); -} \ No newline at end of file +} diff --git a/vcx/libvcx/src/utils/libindy/anoncreds.rs b/vcx/libvcx/src/utils/libindy/anoncreds.rs index 18f3dc1015..962ea61e2d 100644 --- a/vcx/libvcx/src/utils/libindy/anoncreds.rs +++ b/vcx/libvcx/src/utils/libindy/anoncreds.rs @@ -363,9 +363,9 @@ pub fn get_schema_json(schema_id: &str) -> VcxResult<(String, String)> { let submitter_did = settings::get_config_value(settings::CONFIG_INSTITUTION_DID)?; - libindy_build_get_schema_request(&submitter_did, schema_id) - .and_then(|req| libindy_submit_request(&req)) - .and_then(|response| libindy_parse_get_schema_response(&response)) + let schema_json = libindy_get_schema(&submitter_did, schema_id)?; + + Ok((schema_id.to_string(), schema_json)) } pub fn create_cred_def(issuer_did: &str, @@ -399,9 +399,9 @@ pub fn create_cred_def(issuer_did: &str, pub fn get_cred_def_json(cred_def_id: &str) -> VcxResult<(String, String)> { if settings::test_indy_mode_enabled() { return Ok((CRED_DEF_ID.to_string(), CRED_DEF_JSON.to_string())); } - libindy_build_get_credential_def_txn(cred_def_id) - .and_then(|req| libindy_submit_request(&req)) - .and_then(|response| libindy_parse_get_cred_def_response(&response)) + let cred_def_json = libindy_get_cred_def(cred_def_id)?; + + Ok((cred_def_id.to_string(), cred_def_json)) } pub fn create_rev_reg_def(issuer_did: &str, cred_def_id: &str, tails_file: &str, max_creds: u32) diff --git a/vcx/libvcx/src/utils/libindy/ledger.rs b/vcx/libvcx/src/utils/libindy/ledger.rs index 0312284217..3ef45dc720 100644 --- a/vcx/libvcx/src/utils/libindy/ledger.rs +++ b/vcx/libvcx/src/utils/libindy/ledger.rs @@ -1,6 +1,7 @@ use serde_json; use futures::Future; use indy::ledger; +use indy::cache; use settings; use utils::libindy::pool::get_pool_handle; @@ -441,6 +442,25 @@ pub fn parse_response(response: &str) -> VcxResult { .to_vcx(VcxErrorKind::InvalidJson, "Cannot deserialize transaction response") } +pub fn libindy_get_schema(submitter_did: &str, schema_id: &str) -> VcxResult { + let pool_handle = get_pool_handle()?; + let wallet_handle = get_wallet_handle(); + + cache::get_schema(pool_handle, wallet_handle, submitter_did, schema_id, "{}") + .wait() + .map_err(map_rust_indy_sdk_error) +} + +pub fn libindy_get_cred_def(cred_def_id: &str) -> VcxResult { + let pool_handle = get_pool_handle()?; + let wallet_handle = get_wallet_handle(); + let submitter_did = settings::get_config_value(settings::CONFIG_INSTITUTION_DID)?; + + cache::get_cred_def(pool_handle, wallet_handle, &submitter_did, cred_def_id, "{}") + .wait() + .map_err(map_rust_indy_sdk_error) +} + #[serde(tag = "op")] #[derive(Deserialize, Debug)] pub enum Response { diff --git a/wrappers/ios/libindy-pod/Indy-demoTests/Case Tests/Cache/CacheHighCases.mm b/wrappers/ios/libindy-pod/Indy-demoTests/Case Tests/Cache/CacheHighCases.mm new file mode 100644 index 0000000000..166d2d84b5 --- /dev/null +++ b/wrappers/ios/libindy-pod/Indy-demoTests/Case Tests/Cache/CacheHighCases.mm @@ -0,0 +1,183 @@ +#import +#import "PoolUtils.h" +#import "CacheUtils.h" +#import "TestUtils.h" + +@interface CacheHighCases : XCTestCase + +@end + +@implementation CacheHighCases { + IndyHandle poolHandle; + IndyHandle walletHandle; + NSError *ret; +} + +- (void)setUp { + [super setUp]; + [TestUtils cleanupStorage]; + + ret = [[PoolUtils sharedInstance] setProtocolVersion:[TestUtils protocolVersion]]; + XCTAssertEqual(ret.code, Success, @"PoolUtils::setProtocolVersion() failed!"); + + ret = [[PoolUtils sharedInstance] createAndOpenPoolLedgerWithPoolName:[TestUtils pool] + poolHandle:&poolHandle]; + XCTAssertEqual(ret.code, Success, @"PoolUtils::createAndOpenPoolLedgerWithPoolName() failed"); + + ret = [[WalletUtils sharedInstance] createAndOpenWalletWithHandle:&walletHandle]; + XCTAssertEqual(ret.code, Success, @"WalletUtils::createAndOpenWalletWithPoolName() failed"); + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown { + // Put teardown code here. This method is called after the invocation of each test method in the class. + [[PoolUtils sharedInstance] closeHandle:poolHandle]; + [[WalletUtils sharedInstance] closeWalletWithHandle:walletHandle]; + + [TestUtils cleanupStorage]; + [super tearDown]; +} + +- (void)testSchemaCacheWorks { + // 1. obtain did + NSString *myDid; + NSString *myVerkey; + [[DidUtils sharedInstance] createAndStoreAndPublishDidWithWalletHandle:walletHandle + poolHandle:poolHandle + did:&myDid + verkey:&myVerkey]; + + // 2. Build schema request + NSString *schemaId; + NSString *schemaJson; + ret = [[AnoncredsUtils sharedInstance] issuerCreateSchemaWithName:[TestUtils gvtSchemaName] + version:[TestUtils schemaVersion] + attrs:[TestUtils gvtSchemaAttrs] + issuerDID:myDid + schemaId:&schemaId + schemaJson:&schemaJson]; + + NSString *schemaRequest = nil; + ret = [[LedgerUtils sharedInstance] buildSchemaRequestWithSubmitterDid:myDid + data:schemaJson + resultJson:&schemaRequest]; + XCTAssertEqual(ret.code, Success, @"LedgerUtils::buildSchemaRequest() failed"); + + // 3. Sign and submit schema request + NSString *schemaResponse = nil; + ret = [[LedgerUtils sharedInstance] signAndSubmitRequestWithPoolHandle:poolHandle + walletHandle:walletHandle + submitterDid:myDid + requestJson:schemaRequest + outResponseJson:&schemaResponse]; + XCTAssertEqual(ret.code, Success, @"LedgerUtils::signAndSubmitRequest() failed"); + + // 4. get schema + ret = [[CacheUtils sharedInstance] getSchema:poolHandle + walletHandle:walletHandle + submitterDid:myDid + id:schemaId + optionsJson:@"{\"noCache\":false, \"noUpdate\":false, \"noStore\":false, \"minFresh\": -1}" + schemaJson:&schemaJson]; + + XCTAssertEqual(ret.code, Success, @"CacheUtils::getSchema() failed"); + + // 5. purge schema cache + ret = [[CacheUtils sharedInstance] purgeSchemaCache:walletHandle + optionsJson:@"{\"maxAge\":-1}"]; + + XCTAssertEqual(ret.code, Success, @"CacheUtils::purgeSchemaCache() failed"); +} + +- (void)testCredDefCachesWorks { + // 1. obtain did + NSString *myDid; + NSString *myVerkey; + [[DidUtils sharedInstance] createAndStoreAndPublishDidWithWalletHandle:walletHandle + poolHandle:poolHandle + did:&myDid + verkey:&myVerkey]; + + // 2. Build schema request + NSString *schemaId; + NSString *schemaJson; + ret = [[AnoncredsUtils sharedInstance] issuerCreateSchemaWithName:[TestUtils gvtSchemaName] + version:[TestUtils schemaVersion] + attrs:[TestUtils gvtSchemaAttrs] + issuerDID:myDid + schemaId:&schemaId + schemaJson:&schemaJson]; + XCTAssertEqual(ret.code, Success, @"issuerCreateSchemaForIssuerDID failed"); + + NSString *schemaRequest = nil; + ret = [[LedgerUtils sharedInstance] buildSchemaRequestWithSubmitterDid:myDid + data:schemaJson + resultJson:&schemaRequest]; + XCTAssertEqual(ret.code, Success, @"LedgerUtils::buildSchemaRequest() failed"); + + // 3. Sign and submit schema request + NSString *schemaResponse = nil; + ret = [[LedgerUtils sharedInstance] signAndSubmitRequestWithPoolHandle:poolHandle + walletHandle:walletHandle + submitterDid:myDid + requestJson:schemaRequest + outResponseJson:&schemaResponse]; + XCTAssertEqual(ret.code, Success, @"LedgerUtils::signAndSubmitRequest() failed"); + + // 4. Get schema + ret = [[CacheUtils sharedInstance] getSchema:poolHandle + walletHandle:walletHandle + submitterDid:myDid + id:schemaId + optionsJson:@"{\"noCache\":false, \"noUpdate\":false, \"noStore\":false, \"minFresh\": -1}" + schemaJson:&schemaJson]; + + XCTAssertEqual(ret.code, Success, @"CacheUtils::getSchema() failed"); + + // 5. Create credential definition + NSString *credentialDefId; + NSString *credentialDefJSON; + ret = [[AnoncredsUtils sharedInstance] issuerCreateAndStoreCredentialDefForSchema:schemaJson + issuerDID:myDid + tag:[TestUtils tag] + type:nil + configJSON:[[AnoncredsUtils sharedInstance] defaultCredentialDefConfig] + walletHandle:walletHandle + credDefId:&credentialDefId + credDefJson:&credentialDefJSON]; + XCTAssertEqual(ret.code, Success, @"issuerCreateCredentialDefinitionWithWalletHandle failed"); + + // 6. Build credential def request + NSString *credDefRequestJson; + ret = [[LedgerUtils sharedInstance] buildCredDefRequestWithSubmitterDid:myDid + data:credentialDefJSON + resultJson:&credDefRequestJson]; + XCTAssertEqual(ret.code, Success, @"AnoncredsUtils::buildCredDefRequestWithSubmitterDid() failed"); + + // 7. Sign and submit credential def request + NSString *credDefResponse; + ret = [[LedgerUtils sharedInstance] signAndSubmitRequestWithPoolHandle:poolHandle + walletHandle:walletHandle + submitterDid:myDid + requestJson:credDefRequestJson + outResponseJson:&credDefResponse]; + XCTAssertEqual(ret.code, Success, @"LedgerUtils::signAndSubmitRequestWithPoolHandle() returned not Success"); + + // 8. Get credential definition + ret = [[CacheUtils sharedInstance] getCredDef:poolHandle + walletHandle:walletHandle + submitterDid:myDid + id:credentialDefId + optionsJson:@"{\"noCache\":false, \"noUpdate\":false, \"noStore\":false, \"minFresh\": -1}" + credDefJson:&credentialDefJSON]; + + XCTAssertEqual(ret.code, Success, @"CacheUtils::getCredDef() failed"); + + // 9. purge credential definition cache + ret = [[CacheUtils sharedInstance] purgeCredDefCache:walletHandle + optionsJson:@"{\"maxAge\":-1}"]; + + XCTAssertEqual(ret.code, Success, @"CacheUtils::purgeCredDefCache() failed"); +} + +@end diff --git a/wrappers/ios/libindy-pod/Indy-demoTests/Test Utils/CacheUtils.h b/wrappers/ios/libindy-pod/Indy-demoTests/Test Utils/CacheUtils.h new file mode 100644 index 0000000000..19bda241d2 --- /dev/null +++ b/wrappers/ios/libindy-pod/Indy-demoTests/Test Utils/CacheUtils.h @@ -0,0 +1,38 @@ +// +// CacheUtils.h +// Indy-demo +// +// Created by Evernym on 5/17/19. +// Copyright (c) 2019 Hyperledger. All rights reserved. +// + + +#import +#import +#import + +@interface CacheUtils : XCTestCase + ++ (CacheUtils *)sharedInstance; + +- (NSError *)purgeSchemaCache:(IndyHandle)walletHandle + optionsJson:(NSString *)optionsJson; + +- (NSError *)purgeCredDefCache:(IndyHandle)walletHandle + optionsJson:(NSString *)optionsJson; + +- (NSError *)getSchema:(IndyHandle)poolHandle + walletHandle:(IndyHandle)walletHandle + submitterDid:(NSString *)submitterDid + id:(NSString *)id + optionsJson:(NSString *)optionsJson + schemaJson:(NSString **)schemaJson; + +- (NSError *)getCredDef:(IndyHandle)poolHandle + walletHandle:(IndyHandle)walletHandle + submitterDid:(NSString *)submitterDid + id:(NSString *)id + optionsJson:(NSString *)optionsJson + credDefJson:(NSString **)credDefJson; + +@end diff --git a/wrappers/ios/libindy-pod/Indy-demoTests/Test Utils/CacheUtils.m b/wrappers/ios/libindy-pod/Indy-demoTests/Test Utils/CacheUtils.m new file mode 100644 index 0000000000..115c9eaffd --- /dev/null +++ b/wrappers/ios/libindy-pod/Indy-demoTests/Test Utils/CacheUtils.m @@ -0,0 +1,115 @@ +// +// CacheUtils.m +// Indy-demo +// +// Created by Evernym on 5/17/19. +// Copyright (c) 2019 Hyperledger. All rights reserved. +// + +#import "CacheUtils.h" +#import +#import "TestUtils.h" +#import "WalletUtils.h" + +@implementation CacheUtils + ++ (CacheUtils *)sharedInstance { + static LedgerUtils *instance = nil; + static dispatch_once_t dispatch_once_block; + + dispatch_once(&dispatch_once_block, ^{ + instance = [LedgerUtils new]; + }); + + return instance; +} + +- (NSError *)purgeSchemaCache:(IndyHandle)walletHandle + optionsJson:(NSString *)optionsJson { + + XCTestExpectation *completionExpectation = [[XCTestExpectation alloc] initWithDescription:@"completion finished"]; + __block NSError *err = nil; + + [IndyCache purgeSchemaCache:walletHandle + optionsJson:optionsJson + completion:^(NSError *error) { + err = error; + [completionExpectation fulfill]; + }]; + + [self waitForExpectations:@[completionExpectation] timeout:[TestUtils defaultTimeout]]; + + return err; +} + +- (NSError *)purgeCredDefCache:(IndyHandle)walletHandle + optionsJson:(NSString *)optionsJson { + + XCTestExpectation *completionExpectation = [[XCTestExpectation alloc] initWithDescription:@"completion finished"]; + __block NSError *err = nil; + + [IndyCache purgeCredDefCache:walletHandle + optionsJson:optionsJson + completion:^(NSError *error) { + err = error; + [completionExpectation fulfill]; + }]; + + [self waitForExpectations:@[completionExpectation] timeout:[TestUtils defaultTimeout]]; + + return err; +} + +- (NSError *)getSchema:(IndyHandle)poolHandle + walletHandle:(IndyHandle)walletHandle + submitterDid:(NSString *)submitterDid + id:(NSString *)id + optionsJson:(NSString *)optionsJson + schemaJson:(NSString **)schemaJson { + + XCTestExpectation *completionExpectation = [[XCTestExpectation alloc] initWithDescription:@"completion finished"]; + __block NSError *err = nil; + + [IndyCache getSchema:poolHandle + walletHandle:walletHandle + submitterDid:submitterDid + id:id + optionsJson:optionsJson + completion:^[NSError *error, NSString *record { + err = error; + if (schemaJson) *schemaJson = record; + [completionExpectation fulfill]; + }]; + + [self waitForExpectations:@[completionExpectation] timeout:[TestUtils longTimeout]]; + + return err; +} + +- (NSError *)getCredDef:(IndyHandle)poolHandle + walletHandle:(IndyHandle)walletHandle + submitterDid:(NSString *)submitterDid + id:(NSString *)id + optionsJson:(NSString *)optionsJson + credDefJson:(NSString **)credDefJson { + + XCTestExpectation *completionExpectation = [[XCTestExpectation alloc] initWithDescription:@"completion finished"]; + __block NSError *err = nil; + + [IndyCache getCredDef:poolHandle + walletHandle:walletHandle + submitterDid:submitterDid + id:id + optionsJson:optionsJson + completion:^[NSError *error, NSString *record { + err = error; + if (credDefJson) *credDefJson = record; + [completionExpectation fulfill]; + }]; + + [self waitForExpectations:@[completionExpectation] timeout:[TestUtils longTimeout]]; + + return err; +} + +@end \ No newline at end of file diff --git a/wrappers/ios/libindy-pod/Indy/Indy.h b/wrappers/ios/libindy-pod/Indy/Indy.h index dd14b1e100..b0b019eea7 100644 --- a/wrappers/ios/libindy-pod/Indy/Indy.h +++ b/wrappers/ios/libindy-pod/Indy/Indy.h @@ -27,3 +27,4 @@ FOUNDATION_EXPORT const unsigned char indyVersionString[]; #import #import #import +#import diff --git a/wrappers/ios/libindy-pod/Indy/Wrapper/IndyCache.h b/wrappers/ios/libindy-pod/Indy/Wrapper/IndyCache.h new file mode 100644 index 0000000000..a3589ebfba --- /dev/null +++ b/wrappers/ios/libindy-pod/Indy/Wrapper/IndyCache.h @@ -0,0 +1,120 @@ +// +// Created by Evernym on 5/17/19. +// Copyright (c) 2019 Hyperledger. All rights reserved. +// + +#import +#import "IndyTypes.h" + + +@interface IndyCache : NSObject + +/** + Gets schema json data for specified schema id. + If data is present inside of cache, cached data is returned. + Otherwise data is fetched from the ledger and stored inside of cache for future use. + + EXPERIMENTAL + + @param poolHandle pool handle (created by open_pool_ledger). + @param walletHandle wallet handle (created by open_wallet). + @param submitterDid DID of the submitter stored in secured Wallet. + @param id identifier of schema. + @param optionsJson + { + noCache: (bool, optional, false by default) Skip usage of cache, + noUpdate: (bool, optional, false by default) Use only cached data, do not try to update. + noStore: (bool, optional, false by default) Skip storing fresh data if updated, + minFresh: (int, optional, -1 by default) Return cached data if not older than this many seconds. -1 means do not check age. + } + + @param completion Completion callback that returns error code and schema json: + { + id: identifier of schema + attrNames: array of attribute name strings + name: Schema's name string + version: Schema's version string + ver: Version of the Schema json + } + */ ++ (void)getSchema:(IndyHandle)poolHandle + walletHandle:(IndyHandle)walletHandle + submitterDid:(NSString *)submitterDid + id:(NSString *)id + optionsJson:(NSString *)optionsJson + completion:(void (^)(NSError *error, NSString *schemaJson))completion; + +/** + Gets credential definition json data for specified schema id. + If data is present inside of cache, cached data is returned. + Otherwise data is fetched from the ledger and stored inside of cache for future use. + + EXPERIMENTAL + + @param poolHandle pool handle (created by open_pool_ledger). + @param walletHandle wallet handle (created by open_wallet). + @param submitterDid DID of the submitter stored in secured Wallet. + @param id identifier of credential definition. + @param optionsJson + { + noCache: (bool, optional, false by default) Skip usage of cache, + noUpdate: (bool, optional, false by default) Use only cached data, do not try to update. + noStore: (bool, optional, false by default) Skip storing fresh data if updated, + minFresh: (int, optional, -1 by default) Return cached data if not older than this many seconds. -1 means do not check age. + } + + @param completion Completion callback that returns error code and credential definition json: + { + id: string - identifier of credential definition + schemaId: string - identifier of stored in ledger schema + type: string - type of the credential definition. CL is the only supported type now. + tag: string - allows to distinct between credential definitions for the same issuer and schema + value: Dictionary with Credential Definition's data: { + primary: primary credential public key, + Optional: revocation credential public key + }, + ver: Version of the Credential Definition json + } + */ ++ (void)getCredDef:(IndyHandle)poolHandle + walletHandle:(IndyHandle)walletHandle + submitterDid:(NSString *)submitterDid + id:(NSString *)id + optionsJson:(NSString *)optionsJson + completion:(void (^)(NSError *error, NSString *credDefJson))completion; + +/** + Purge schema cache + + EXPERIMENTAL + + @param walletHandle wallet handle (created by open_wallet). + @param optionsJson + { + maxAge: (int, optional, -1 by default) Purge cached data if older than this many seconds. -1 means purge all. + } + + @param completion Completion callback that returns error code + */ ++ (void)purgeSchemaCache:(IndyHandle)walletHandle + optionsJson:(NSString *)optionsJson + completion:(void (^)(NSError *error))completion; + +/** + Purge credential definition cache + + EXPERIMENTAL + + @param walletHandle wallet handle (created by open_wallet). + @param optionsJson + { + maxAge: (int, optional, -1 by default) Purge cached data if older than this many seconds. -1 means purge all. + } + + @param completion Completion callback that returns error code + */ ++ (void)purgeCredDefCache:(IndyHandle)walletHandle + optionsJson:(NSString *)optionsJson + completion:(void (^)(NSError *error))completion; + +@end \ No newline at end of file diff --git a/wrappers/ios/libindy-pod/Indy/Wrapper/IndyCache.mm b/wrappers/ios/libindy-pod/Indy/Wrapper/IndyCache.mm new file mode 100644 index 0000000000..472a8643c9 --- /dev/null +++ b/wrappers/ios/libindy-pod/Indy/Wrapper/IndyCache.mm @@ -0,0 +1,87 @@ +// +// Created by Evernym on 5/16/18. +// Copyright (c) 2018 Hyperledger. All rights reserved. +// + +#import "IndyCallbacks.h" +#import "NSError+IndyError.h" +#import "IndyCache.h" + + +@implementation IndyCache + ++ (void)getSchema:(IndyHandle)poolHandle + walletHandle:(IndyHandle)walletHandle + submitterDid:(NSString *)submitterDid + id:(NSString *)id + optionsJson:(NSString *)optionsJson + completion:(void (^)(NSError *error, NSString *schemaJson))completion { + + indy_error_t ret; + + indy_handle_t handle = [[IndyCallbacks sharedInstance] createCommandHandleFor:completion]; + + ret = indy_get_schema(handle, + poolHandle, + walletHandle, + [submitterDid UTF8String], + [id UTF8String], + [optionsJson UTF8String], + IndyWrapperCommonStringCallback); + [[IndyCallbacks sharedInstance] completeStr:completion forHandle:handle ifError:ret]; +} + ++ (void)getCredDef:(IndyHandle)poolHandle + walletHandle:(IndyHandle)walletHandle + submitterDid:(NSString *)submitterDid + id:(NSString *)id + optionsJson:(NSString *)optionsJson + completion:(void (^)(NSError *error, NSString *credDefJson))completion { + + indy_error_t ret; + + indy_handle_t handle = [[IndyCallbacks sharedInstance] createCommandHandleFor:completion]; + + ret = indy_get_cred_def(handle, + poolHandle, + walletHandle, + [submitterDid UTF8String], + [id UTF8String], + [optionsJson UTF8String], + IndyWrapperCommonStringCallback); + [[IndyCallbacks sharedInstance] completeStr:completion forHandle:handle ifError:ret]; +} + ++ (void)purgeSchemaCache:(IndyHandle)walletHandle + optionsJson:(NSString *)optionsJson + completion:(void (^)(NSError *error))completion { + + indy_error_t ret; + + indy_handle_t handle = [[IndyCallbacks sharedInstance] createCommandHandleFor:completion]; + + ret = indy_purge_schema_cache(handle, + walletHandle, + [optionsJson UTF8String], + IndyWrapperCommonCallback); + + [[IndyCallbacks sharedInstance] complete:completion forHandle:handle ifError:ret]; +} + ++ (void)purgeCredDefCache:(IndyHandle)walletHandle + optionsJson:(NSString *)optionsJson + completion:(void (^)(NSError *error))completion { + + indy_error_t ret; + + indy_handle_t handle = [[IndyCallbacks sharedInstance] createCommandHandleFor:completion]; + + ret = indy_purge_cred_def_cache(handle, + walletHandle, + [optionsJson UTF8String], + IndyWrapperCommonCallback); + + [[IndyCallbacks sharedInstance] complete:completion forHandle:handle ifError:ret]; +} + +@end diff --git a/wrappers/ios/libindy-pod/Indy/Wrapper/IndyNonSecrets.h b/wrappers/ios/libindy-pod/Indy/Wrapper/IndyNonSecrets.h index b21583ee1b..ab5c2de024 100644 --- a/wrappers/ios/libindy-pod/Indy/Wrapper/IndyNonSecrets.h +++ b/wrappers/ios/libindy-pod/Indy/Wrapper/IndyNonSecrets.h @@ -197,7 +197,7 @@ Fetch next records for wallet search. Not if there are no records this call returns WalletNoRecords error. - + @param searchHandle wallet search handle (created by openSearchInWallet) @param walletHandle wallet handle returned by IndyWallet::openWalletWithName. @param count Count of records to fetch @@ -227,4 +227,5 @@ */ + (void)closeSearchWithHandle:(IndyHandle)searchHandle completion:(void (^)(NSError *error))completion; -@end \ No newline at end of file + +@end diff --git a/wrappers/java/src/main/java/org/hyperledger/indy/sdk/LibIndy.java b/wrappers/java/src/main/java/org/hyperledger/indy/sdk/LibIndy.java index 7271ce1b27..171bcdafd9 100644 --- a/wrappers/java/src/main/java/org/hyperledger/indy/sdk/LibIndy.java +++ b/wrappers/java/src/main/java/org/hyperledger/indy/sdk/LibIndy.java @@ -159,6 +159,10 @@ public interface API extends Library { public int indy_open_wallet_search(int command_handle, int wallet_handle, String type, String query_json, String options_json, Callback cb); public int indy_fetch_wallet_search_next_records(int command_handle, int wallet_handle, int wallet_search_handle, int count, Callback cb); public int indy_close_wallet_search(int command_handle, int wallet_search_handle, Callback cb); + public int indy_get_schema(int command_handle, int pool_handle, int wallet_handle, String submitter_did, String id, String options_json, Callback cb); + public int indy_get_cred_def(int command_handle, int pool_handle, int wallet_handle, String submitter_did, String id, String options_json, Callback cb); + public int indy_purge_schema_cache(int command_handle, int wallet_handle, String options_json, Callback cb); + public int indy_purge_cred_def_cache(int command_handle, int wallet_handle, String options_json, Callback cb); // payments.rs int indy_create_payment_address(int command_handle, int wallet_handle, String payment_method, String config, Callback cb); diff --git a/wrappers/java/src/main/java/org/hyperledger/indy/sdk/cache/Cache.java b/wrappers/java/src/main/java/org/hyperledger/indy/sdk/cache/Cache.java new file mode 100644 index 0000000000..b903a69002 --- /dev/null +++ b/wrappers/java/src/main/java/org/hyperledger/indy/sdk/cache/Cache.java @@ -0,0 +1,262 @@ +package org.hyperledger.indy.sdk.cache; + +import com.sun.jna.Callback; +import org.hyperledger.indy.sdk.IndyException; +import org.hyperledger.indy.sdk.IndyJava; +import org.hyperledger.indy.sdk.LibIndy; +import org.hyperledger.indy.sdk.ParamGuard; +import org.hyperledger.indy.sdk.pool.Pool; +import org.hyperledger.indy.sdk.wallet.Wallet; + +import java.util.concurrent.CompletableFuture; + +/** + * cache.rs API + */ + +/** + * High level wrapper around did SDK functions. + */ +public class Cache extends IndyJava.API { + private Cache() { + + } + + /* + * STATIC CALLBACKS + */ + + /** + * Callback used when a function returning Void completes. + */ + private static Callback voidCb = new Callback() { + + @SuppressWarnings({"unused", "unchecked"}) + public void callback(int xcommand_handle, int err) { + + CompletableFuture future = (CompletableFuture) removeFuture(xcommand_handle); + if (! checkResult(future, err)) return; + + Void result = null; + future.complete(result); + } + }; + + /** + * Callback used when a function returning String completes. + */ + private static Callback stringCb = new Callback() { + + @SuppressWarnings({"unused", "unchecked"}) + public void callback(int xcommand_handle, int err, String str) { + + CompletableFuture future = (CompletableFuture) removeFuture(xcommand_handle); + if (! checkResult(future, err)) return; + + String result = str; + future.complete(result); + } + }; + + /* + * STATIC METHODS + */ + + /** + * Purge schema cache. + * + * EXPERIMENTAL + * + * @param wallet The wallet. + * @param optionsJson The record tags used for search and storing meta information as json: + * { + * "maxAge": -1, // (int, optional, -1 by default) Purge cached data if older than this many seconds. -1 means purge all. + * } + * @return A future that resolves no value. + * @throws IndyException Thrown if an error occurs when calling the underlying SDK. + */ + public static CompletableFuture purgeSchemaCache( + Wallet wallet, + String optionsJson) throws IndyException { + + ParamGuard.notNull(wallet, "wallet"); + ParamGuard.notNull(optionsJson, "optionsJson"); + + CompletableFuture future = new CompletableFuture(); + int commandHandle = addFuture(future); + + int walletHandle = wallet.getWalletHandle(); + + int result = LibIndy.api.indy_purge_schema_cache( + commandHandle, + walletHandle, + optionsJson, + voidCb); + + checkResult(future, result); + + return future; + } + + /** + * Purge credential definition cache. + * + * EXPERIMENTAL + * + * @param wallet The wallet. + * @param optionsJson The record tags used for search and storing meta information as json: + * { + * "maxAge": -1, // (int, optional, -1 by default) Purge cached data if older than this many seconds. -1 means purge all. + * } + * @return A future that resolves no value. + * @throws IndyException Thrown if an error occurs when calling the underlying SDK. + */ + public static CompletableFuture purgeCredDefCache( + Wallet wallet, + String optionsJson) throws IndyException { + + ParamGuard.notNull(wallet, "wallet"); + ParamGuard.notNull(optionsJson, "optionsJson"); + + CompletableFuture future = new CompletableFuture(); + int commandHandle = addFuture(future); + + int walletHandle = wallet.getWalletHandle(); + + int result = LibIndy.api.indy_purge_cred_def_cache( + commandHandle, + walletHandle, + optionsJson, + voidCb); + + checkResult(future, result); + + return future; + } + + /** + * Gets schema json data for specified schema id. + * If data is present inside of cache, cached data is returned. + * Otherwise data is fetched from the ledger and stored inside of cache for future use. + * + * EXPERIMENTAL + * + * @param pool The pool. + * @param wallet The wallet. + * @param submitterDid DID of the submitter stored in secured Wallet + * @param id The id of schema. + * @param optionsJson + * { + * noCache: (optional, false by default) Skip usage of cache, + * noUpdate: (optional, false by default) Use only cached data, do not try to update. + * noStore: (optional, false by default) Skip storing fresh data if updated + * minFresh: (optional, -1 by default) Return cached data if not older than this many seconds. -1 means do not check age. + * } + * @return A future that resolves to Schema json: + * { + * id: identifier of schema + * attrNames: array of attribute name strings + * name: Schema's name string + * version: Schema's version string + * ver: Version of the Schema json + * } + * @throws IndyException Thrown if an error occurs when calling the underlying SDK. + */ + public static CompletableFuture getSchema( + Pool pool, + Wallet wallet, + String submitterDid, + String id, + String optionsJson) throws IndyException { + + ParamGuard.notNull(pool, "pool"); + ParamGuard.notNull(wallet, "wallet"); + ParamGuard.notNull(submitterDid, "submitterDid"); + ParamGuard.notNull(id, "id"); + ParamGuard.notNull(optionsJson, "optionsJson"); + + CompletableFuture future = new CompletableFuture(); + int commandHandle = addFuture(future); + + int poolHandle = pool.getPoolHandle(); + int walletHandle = wallet.getWalletHandle(); + + int result = LibIndy.api.indy_get_schema( + commandHandle, + poolHandle, + walletHandle, + submitterDid, + id, + optionsJson, + stringCb); + + checkResult(future, result); + + return future; + } + + /** + * Gets credential definition json data for specified credential definition id. + * If data is present inside of cache, cached data is returned. + * Otherwise data is fetched from the ledger and stored inside of cache for future use. + * + * EXPERIMENTAL + * + * @param pool The pool. + * @param wallet The wallet. + * @param submitterDid DID of the submitter stored in secured Wallet + * @param id The id of credential definition. + * @param optionsJson + * { + * noCache: (optional, false by default) Skip usage of cache, + * noUpdate: (optional, false by default) Use only cached data, do not try to update. + * noStore: (optional, false by default) Skip storing fresh data if updated + * minFresh: (optional, -1 by default) Return cached data if not older than this many seconds. -1 means do not check age. + * } + * @return A future that resolves to Credential Definition json: + * { + * id: string - identifier of credential definition + * schemaId: string - identifier of stored in ledger schema + * type: string - type of the credential definition. CL is the only supported type now. + * tag: string - allows to distinct between credential definitions for the same issuer and schema + * value: Dictionary with Credential Definition's data: { + * primary: primary credential public key, + * Optional[revocation]: revocation credential public key + * }, + * ver: Version of the Credential Definition json + * } + * @throws IndyException Thrown if an error occurs when calling the underlying SDK. + */ + public static CompletableFuture getCredDef( + Pool pool, + Wallet wallet, + String submitterDid, + String id, + String optionsJson) throws IndyException { + + ParamGuard.notNull(pool, "pool"); + ParamGuard.notNull(wallet, "wallet"); + ParamGuard.notNull(submitterDid, "submitterDid"); + ParamGuard.notNull(id, "id"); + ParamGuard.notNull(optionsJson, "optionsJson"); + + CompletableFuture future = new CompletableFuture(); + int commandHandle = addFuture(future); + + int poolHandle = pool.getPoolHandle(); + int walletHandle = wallet.getWalletHandle(); + + int result = LibIndy.api.indy_get_cred_def( + commandHandle, + poolHandle, + walletHandle, + submitterDid, + id, + optionsJson, + stringCb); + + checkResult(future, result); + + return future; + } +} \ No newline at end of file diff --git a/wrappers/java/src/test/java/org/hyperledger/indy/sdk/cache/CacheIntegrationTest.java b/wrappers/java/src/test/java/org/hyperledger/indy/sdk/cache/CacheIntegrationTest.java new file mode 100644 index 0000000000..1680875e12 --- /dev/null +++ b/wrappers/java/src/test/java/org/hyperledger/indy/sdk/cache/CacheIntegrationTest.java @@ -0,0 +1,82 @@ +package org.hyperledger.indy.sdk.cache; + +import org.hyperledger.indy.sdk.IndyIntegrationTestWithPoolAndSingleWallet; +import org.hyperledger.indy.sdk.anoncreds.Anoncreds; +import org.hyperledger.indy.sdk.anoncreds.AnoncredsResults; +import org.hyperledger.indy.sdk.blob_storage.BlobStorageWriter; +import org.hyperledger.indy.sdk.utils.InitHelper; +import org.hyperledger.indy.sdk.utils.PoolUtils; +import org.hyperledger.indy.sdk.ledger.Ledger; +import org.hyperledger.indy.sdk.ledger.LedgerResults; +import org.json.JSONObject; +import org.junit.Before; +import org.junit.Rule; +import org.junit.rules.ExpectedException; +import org.junit.rules.Timeout; + +import java.util.concurrent.TimeUnit; + +public class CacheIntegrationTest extends IndyIntegrationTestWithPoolAndSingleWallet { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Rule + public Timeout globalTimeout = new Timeout(5, TimeUnit.MINUTES); + + private static Boolean entitiesPosted = false; + + static String schemaId; + static String credDefId; + static String revRegDefId; + + void postEntities() throws Exception { + + if (entitiesPosted) { + return; + } + + String myDid = createStoreAndPublishDidFromTrustee(); + + // create and post credential schema + AnoncredsResults.IssuerCreateSchemaResult createSchemaResult = Anoncreds.issuerCreateSchema(myDid, GVT_SCHEMA_NAME, SCHEMA_VERSION, GVT_SCHEMA_ATTRIBUTES).get(); + String schema = createSchemaResult.getSchemaJson(); + schemaId = createSchemaResult.getSchemaId(); + + String schemaRequest = Ledger.buildSchemaRequest(myDid, schema).get(); + Ledger.signAndSubmitRequest(pool, wallet, myDid, schemaRequest).get(); + + String getSchemaRequest = Ledger.buildGetSchemaRequest(myDid, schemaId).get(); + String getSchemaResponse = PoolUtils.ensurePreviousRequestApplied(pool, getSchemaRequest, response -> { + JSONObject getSchemaResponseObject = new JSONObject(response); + return !getSchemaResponseObject.getJSONObject("result").isNull("seqNo"); + }); + + LedgerResults.ParseResponseResult parseSchemaResult = Ledger.parseGetSchemaResponse(getSchemaResponse).get(); + + // create and post credential definition + AnoncredsResults.IssuerCreateAndStoreCredentialDefResult createCredDefResult = + Anoncreds.issuerCreateAndStoreCredentialDef(wallet, myDid, parseSchemaResult.getObjectJson(), TAG, null, REV_CRED_DEF_CONFIG).get(); + String credDefJson = createCredDefResult.getCredDefJson(); + credDefId = createCredDefResult.getCredDefId(); + + String credDefRequest = Ledger.buildCredDefRequest(myDid, credDefJson).get(); + Ledger.signAndSubmitRequest(pool, wallet, myDid, credDefRequest).get(); + + // create and post revocation registry + BlobStorageWriter tailsWriter = BlobStorageWriter.openWriter("default", TAILS_WRITER_CONFIG).get(); + String revRegConfig = "{\"issuance_type\":null,\"max_cred_num\":5}"; + AnoncredsResults.IssuerCreateAndStoreRevocRegResult createRevRegResult = Anoncreds.issuerCreateAndStoreRevocReg(wallet, myDid, null, TAG, credDefId, revRegConfig, tailsWriter).get(); + revRegDefId = createRevRegResult.getRevRegId(); + String revRegDef = createRevRegResult.getRevRegDefJson(); + String revRegEntry = createRevRegResult.getRevRegEntryJson(); + + String revRegDefRequest = Ledger.buildRevocRegDefRequest(myDid, revRegDef).get(); + Ledger.signAndSubmitRequest(pool, wallet, myDid, revRegDefRequest).get(); + + String revRegEntryRequest = Ledger.buildRevocRegEntryRequest(myDid, revRegDefId, "CL_ACCUM", revRegEntry).get(); + Ledger.signAndSubmitRequest(pool, wallet, myDid, revRegEntryRequest).get(); + + entitiesPosted = true; + } +} \ No newline at end of file diff --git a/wrappers/java/src/test/java/org/hyperledger/indy/sdk/cache/CredDefCacheTest.java b/wrappers/java/src/test/java/org/hyperledger/indy/sdk/cache/CredDefCacheTest.java new file mode 100644 index 0000000000..2cf047c863 --- /dev/null +++ b/wrappers/java/src/test/java/org/hyperledger/indy/sdk/cache/CredDefCacheTest.java @@ -0,0 +1,28 @@ +package org.hyperledger.indy.sdk.cache; + +import org.hyperledger.indy.sdk.InvalidStructureException; +import org.hyperledger.indy.sdk.utils.PoolUtils; +import org.json.JSONObject; +import org.junit.*; + +import java.util.concurrent.ExecutionException; + +import static org.hamcrest.CoreMatchers.isA; +import static org.junit.Assert.assertTrue; + +public class CredDefCacheTest extends CacheIntegrationTest { + + @Test(timeout = PoolUtils.TEST_TIMEOUT_FOR_REQUEST_ENSURE) + public void testGetSchemaWorks() throws Exception { + postEntities(); + + String defaultOptions = "{\"noCache\": false, \"noUpdate\": false, \"noStore\": false, \"minFresh\": -1}"; + String credDef = Cache.getCredDef(pool, wallet, DID, String.valueOf(credDefId), defaultOptions).get(); + } + + @Test + public void testPurgeCredDefCacheWorks() throws Exception { + String defaultOptions = "{\"maxAge\": -1}"; + Cache.purgeCredDefCache(wallet, defaultOptions).get(); + } +} diff --git a/wrappers/java/src/test/java/org/hyperledger/indy/sdk/cache/SchemaCacheTest.java b/wrappers/java/src/test/java/org/hyperledger/indy/sdk/cache/SchemaCacheTest.java new file mode 100644 index 0000000000..218b93a081 --- /dev/null +++ b/wrappers/java/src/test/java/org/hyperledger/indy/sdk/cache/SchemaCacheTest.java @@ -0,0 +1,29 @@ +package org.hyperledger.indy.sdk.cache; + +import org.hyperledger.indy.sdk.InvalidStructureException; +import org.hyperledger.indy.sdk.utils.PoolUtils; +import org.json.JSONObject; +import org.junit.*; + +import java.util.concurrent.ExecutionException; + +import static org.hamcrest.CoreMatchers.isA; +import static org.junit.Assert.assertTrue; + +public class SchemaCacheTest extends CacheIntegrationTest { + + @Test(timeout = PoolUtils.TEST_TIMEOUT_FOR_REQUEST_ENSURE) + public void testGetSchemaWorks() throws Exception { + postEntities(); + + String defaultOptions = "{\"noCache\": false, \"noUpdate\": false, \"noStore\": false, \"minFresh\": -1}"; + + String schema = Cache.getSchema(pool, wallet, DID, String.valueOf(schemaId), defaultOptions).get(); + } + + @Test + public void testPurgeSchemaCacheWorks() throws Exception { + String defaultOptions = "{\"maxAge\": -1}"; + Cache.purgeSchemaCache(wallet, defaultOptions).get(); + } +} diff --git a/wrappers/nodejs/README.md b/wrappers/nodejs/README.md index d0ff668aa8..ce317f953b 100644 --- a/wrappers/nodejs/README.md +++ b/wrappers/nodejs/README.md @@ -20,6 +20,7 @@ Native bindings for [Hyperledger Indy](https://www.hyperledger.org/projects/hype * [pool](#pool) * [wallet](#wallet) * [logger](#logger) + * [cache](#cache) * [mod](#mod) - [Advanced](#advanced) - [Contributing](#contributing) @@ -2798,6 +2799,116 @@ Errors: `Common*` NOTE: This is a synchronous function (does not return a promise) but may call `logFn` asynchronously many times. +### cache + +#### getSchema \( poolHandle, wh, submitterDid, id, options \) -> schema + +Get schema json data for specified schema id. +If data is present inside of cache, cached data is returned. +Otherwise data is fetched from the ledger and stored inside of cache for future use. + +EXPERIMENTAL + +* `poolHandle`: +* `wh`: Handle (Number) - wallet handle (created by openWallet) +* `submitterDid`: String - DID of the read request sender. +* `id`: String - Schema ID in ledger +* `options`: Json +``` + { + noCache: (bool, optional, false by default) Skip usage of cache, + noUpdate: (bool, optional, false by default) Use only cached data, do not try to update. + noStore: (bool, optional, false by default) Skip storing fresh data if updated, + minFresh: (int, optional, -1 by default) Return cached data if not older than this many seconds. -1 means do not check age. + } + +``` +__->__ schema: Json +``` +{ + id: identifier of schema + attrNames: array of attribute name strings + name: Schema's name string + version: Schema's version string + ver: Version of the Schema json +} +``` + +Errors: `Common*`, `Wallet*`, `Ledger*` + +#### getCredDef \( poolHandle, wh, submitterDid, id, options \) -> credDef + +EXPERIMENTAL + +Get credential definition json data for specified credential definition id. +If data is present inside of cache, cached data is returned. +Otherwise data is fetched from the ledger and stored inside of cache for future use. + +* `poolHandle`: +* `wh`: Handle (Number) - wallet handle (created by openWallet) +* `submitterDid`: String - DID of the read request sender. +* `id`: String - Credential Definition ID in ledger. +* `options`: Json +``` + { + noCache: (bool, optional, false by default) Skip usage of cache, + noUpdate: (bool, optional, false by default) Use only cached data, do not try to update. + noStore: (bool, optional, false by default) Skip storing fresh data if updated, + minFresh: (int, optional, -1 by default) Return cached data if not older than this many seconds. -1 means do not check age. + } + +``` +__->__ credDef: Json +``` +{ + id: string - identifier of credential definition + schemaId: string - identifier of stored in ledger schema + type: string - type of the credential definition. CL is the only supported type now. + tag: string - allows to distinct between credential definitions for the same issuer and schema + value: Dictionary with Credential Definition's data: { + primary: primary credential public key, + Optional: revocation credential public key + }, + ver: Version of the Credential Definition json +} +``` + +Errors: `Common*`, `Wallet*`, `Ledger*` + +#### purgeSchemaCache \( wh, options \) -> void + +Purge schema cache. + +EXPERIMENTAL + +* `wh`: Handle (Number) - wallet handle (created by openWallet) +* `options`: Json +``` + { + maxAge: (int, optional, -1 by default) Purge cached data if older than this many seconds. -1 means purge all. + } +``` +* __->__ void + +Errors: `Common*`, `Wallet*` + +#### purgeCredDefCache \( wh, options \) -> void + +Purge credential definition cache. + +EXPERIMENTAL + +* `wh`: Handle (Number) - wallet handle (created by openWallet) +* `options`: Json +``` + { + maxAge: (int, optional, -1 by default) Purge cached data if older than this many seconds. -1 means purge all. + } +``` +* __->__ void + +Errors: `Common*`, `Wallet*` + ### mod #### setRuntimeConfig \( config \) diff --git a/wrappers/nodejs/src/index.js b/wrappers/nodejs/src/index.js index b081ee417e..0f89f3452d 100644 --- a/wrappers/nodejs/src/index.js +++ b/wrappers/nodejs/src/index.js @@ -649,6 +649,34 @@ indy.closeWalletSearch = function closeWalletSearch (walletSearchHandle, cb) { return cb.promise } +indy.getSchema = function getSchema (poolHandle, wh, submitterDid, id, options, cb) { + cb = wrapIndyCallback(cb, function (data) { + return fromJson(data) + }) + capi.getSchema(poolHandle, wh, submitterDid, id, toJson(options), cb) + return cb.promise +} + +indy.getCredDef = function getCredDef (poolHandle, wh, submitterDid, id, options, cb) { + cb = wrapIndyCallback(cb, function (data) { + return fromJson(data) + }) + capi.getCredDef(poolHandle, wh, submitterDid, id, toJson(options), cb) + return cb.promise +} + +indy.purgeSchemaCache = function purgeSchemaCache (wh, options, cb) { + cb = wrapIndyCallback(cb) + capi.purgeSchemaCache(wh, toJson(options), cb) + return cb.promise +} + +indy.purgeCredDefCache = function purgeCredDefCache (wh, options, cb) { + cb = wrapIndyCallback(cb) + capi.purgeCredDefCache(wh, toJson(options), cb) + return cb.promise +} + indy.isPairwiseExists = function isPairwiseExists (wh, theirDid, cb) { cb = wrapIndyCallback(cb) capi.isPairwiseExists(wh, theirDid, cb) diff --git a/wrappers/nodejs/src/indy.cc b/wrappers/nodejs/src/indy.cc index 9d707a2910..d7deb7d87c 100644 --- a/wrappers/nodejs/src/indy.cc +++ b/wrappers/nodejs/src/indy.cc @@ -2462,6 +2462,90 @@ NAN_METHOD(closeWalletSearch) { indyCalled(icb, indy_close_wallet_search(icb->handle, arg0, closeWalletSearch_cb)); } +void getSchema_cb(indy_handle_t handle, indy_error_t xerr, const char* arg0) { + IndyCallback* icb = IndyCallback::getCallback(handle); + if(icb != nullptr){ + icb->cbString(xerr, arg0); + } +} + +NAN_METHOD(getSchema) { + INDY_ASSERT_NARGS(getSchema, 6) + INDY_ASSERT_NUMBER(getSchema, 0, poolHandle) + INDY_ASSERT_NUMBER(getSchema, 1, wh) + INDY_ASSERT_STRING(getSchema, 2, submitterDid) + INDY_ASSERT_STRING(getSchema, 3, id) + INDY_ASSERT_STRING(getSchema, 4, options) + INDY_ASSERT_FUNCTION(getSchema, 5) + indy_handle_t arg0 = argToInt32(info[0]); + indy_handle_t arg1 = argToInt32(info[1]); + const char* arg2 = argToCString(info[2]); + const char* arg3 = argToCString(info[3]); + const char* arg4 = argToCString(info[4]); + IndyCallback* icb = argToIndyCb(info[5]); + indyCalled(icb, indy_get_schema(icb->handle, arg0, arg1, arg2, arg3, arg4, getSchema_cb)); +} + +void getCredDef_cb(indy_handle_t handle, indy_error_t xerr, const char* arg0) { + IndyCallback* icb = IndyCallback::getCallback(handle); + if(icb != nullptr){ + icb->cbString(xerr, arg0); + } +} + +NAN_METHOD(getCredDef) { + INDY_ASSERT_NARGS(getCredDef, 6) + INDY_ASSERT_NUMBER(getCredDef, 0, poolHandle) + INDY_ASSERT_NUMBER(getCredDef, 1, wh) + INDY_ASSERT_STRING(getCredDef, 2, submitterDid) + INDY_ASSERT_STRING(getCredDef, 3, id) + INDY_ASSERT_STRING(getCredDef, 4, options) + INDY_ASSERT_FUNCTION(getCredDef, 5) + indy_handle_t arg0 = argToInt32(info[0]); + indy_handle_t arg1 = argToInt32(info[1]); + const char* arg2 = argToCString(info[2]); + const char* arg3 = argToCString(info[3]); + const char* arg4 = argToCString(info[4]); + IndyCallback* icb = argToIndyCb(info[5]); + indyCalled(icb, indy_get_cred_def(icb->handle, arg0, arg1, arg2, arg3, arg4, getCredDef_cb)); +} + +void purgeSchemaCache_cb(indy_handle_t handle, indy_error_t xerr) { + IndyCallback* icb = IndyCallback::getCallback(handle); + if(icb != nullptr){ + icb->cbNone(xerr); + } +} + +NAN_METHOD(purgeSchemaCache) { + INDY_ASSERT_NARGS(purgeSchemaCache, 3) + INDY_ASSERT_NUMBER(purgeSchemaCache, 0, wh) + INDY_ASSERT_STRING(purgeSchemaCache, 1, options) + INDY_ASSERT_FUNCTION(purgeSchemaCache, 2) + indy_handle_t arg0 = argToInt32(info[0]); + const char* arg1 = argToCString(info[1]); + IndyCallback* icb = argToIndyCb(info[2]); + indyCalled(icb, indy_purge_schema_cache(icb->handle, arg0, arg1, purgeSchemaCache_cb)); +} + +void purgeCredDefCache_cb(indy_handle_t handle, indy_error_t xerr) { + IndyCallback* icb = IndyCallback::getCallback(handle); + if(icb != nullptr){ + icb->cbNone(xerr); + } +} + +NAN_METHOD(purgeCredDefCache) { + INDY_ASSERT_NARGS(purgeCredDefCache, 3) + INDY_ASSERT_NUMBER(purgeCredDefCache, 0, wh) + INDY_ASSERT_STRING(purgeCredDefCache, 1, options) + INDY_ASSERT_FUNCTION(purgeCredDefCache, 2) + indy_handle_t arg0 = argToInt32(info[0]); + const char* arg1 = argToCString(info[1]); + IndyCallback* icb = argToIndyCb(info[2]); + indyCalled(icb, indy_purge_cred_def_cache(icb->handle, arg0, arg1, purgeCredDefCache_cb)); +} + void isPairwiseExists_cb(indy_handle_t handle, indy_error_t xerr, indy_bool_t arg0) { IndyCallback* icb = IndyCallback::getCallback(handle); if(icb != nullptr){ @@ -3347,6 +3431,10 @@ NAN_MODULE_INIT(InitAll) { Nan::Export(target, "openWalletSearch", openWalletSearch); Nan::Export(target, "fetchWalletSearchNextRecords", fetchWalletSearchNextRecords); Nan::Export(target, "closeWalletSearch", closeWalletSearch); + Nan::Export(target, "getSchema", getSchema); + Nan::Export(target, "getCredDef", getCredDef); + Nan::Export(target, "purgeSchemaCache", purgeSchemaCache); + Nan::Export(target, "purgeCredDefCache", purgeCredDefCache); Nan::Export(target, "isPairwiseExists", isPairwiseExists); Nan::Export(target, "createPairwise", createPairwise); Nan::Export(target, "listPairwise", listPairwise); diff --git a/wrappers/nodejs/test/cache.js b/wrappers/nodejs/test/cache.js new file mode 100644 index 0000000000..727c3fa0cc --- /dev/null +++ b/wrappers/nodejs/test/cache.js @@ -0,0 +1,68 @@ +var test = require('ava') +var indy = require('../') +var cuid = require('cuid') +var initTestPool = require('./helpers/initTestPool') + +function sleep (ms) { + return new Promise(function (resolve) { + setTimeout(resolve, ms) + }) +} + +test('cache', async function (t) { + var pool = await initTestPool() + var walletConfig = { 'id': 'wallet-' + cuid() } + var walletCredentials = { 'key': 'key' } + await indy.createWallet(walletConfig, walletCredentials) + var wh = await indy.openWallet(walletConfig, walletCredentials) + var [trusteeDid] = await indy.createAndStoreMyDid(wh, { seed: '000000000000000000000000Trustee1' }) + var [myDid, myVerkey] = await indy.createAndStoreMyDid(wh, { }) + var schemaName = 'schema-' + cuid() + var [schemaId, schema] = await indy.issuerCreateSchema(myDid, schemaName, '1.0', ['name', 'age']) + + // Nym + var nreq = await indy.buildNymRequest(trusteeDid, myDid, myVerkey, null, 'TRUSTEE') + var nres = await indy.signAndSubmitRequest(pool.handle, wh, trusteeDid, nreq) + t.is(nres.result.txn.data.verkey, myVerkey) + + var defaultGetCacheOptions = { + 'noCache': false, + 'noUpdate': false, + 'noStore': false, + 'minFresh': -1 + } + + var defaultPurgeCacheOptions = { + 'maxAge': -1 + } + + // Schema + var req = await indy.buildSchemaRequest(myDid, schema) + req = await indy.signRequest(wh, myDid, req) + await indy.submitRequest(pool.handle, req) + + await sleep(5 * 1000) + + var schemaRes = await indy.getSchema(pool.handle, wh, myDid, schemaId, defaultGetCacheOptions) + t.is(schemaRes.name, schema.name) + schema = schemaRes + + await indy.purgeSchemaCache(wh, defaultPurgeCacheOptions) + + // Cred Def + var [credDefId, credDef] = await indy.issuerCreateAndStoreCredentialDef(wh, myDid, schema, 'TAG', 'CL', { support_revocation: false }) + req = await indy.buildCredDefRequest(myDid, credDef) + await indy.signAndSubmitRequest(pool.handle, wh, myDid, req) + + await sleep(5 * 1000) + + var credDefRes = await indy.getCredDef(pool.handle, wh, myDid, credDefId, defaultGetCacheOptions) + t.is(credDefRes.id, credDef.id) + + await indy.purgeCredDefCache(wh, defaultPurgeCacheOptions) + + // cleanup + await indy.closeWallet(wh) + await indy.deleteWallet(walletConfig, walletCredentials) + pool.cleanup() +}) diff --git a/wrappers/python/indy/cache.py b/wrappers/python/indy/cache.py new file mode 100644 index 0000000000..b85be13113 --- /dev/null +++ b/wrappers/python/indy/cache.py @@ -0,0 +1,197 @@ +from .libindy import do_call, create_cb + +from ctypes import * + +import logging + + +async def get_schema(pool_handle: int, wallet_handle: int, submitter_did: str, id: str, options_json: str) -> str: + """ + Gets schema json data for specified schema id. + If data is present inside of cache, cached data is returned. + Otherwise data is fetched from the ledger and stored inside of cache for future use. + + EXPERIMENTAL + + :param pool_handle: pool handle (created by open_pool_ledger). + :param wallet_handle: wallet handle (created by open_wallet). + :param submitter_did: DID of the submitter stored in secured Wallet. + :param id: identifier of schema. + :param options_json: + { + noCache: (bool, optional, false by default) Skip usage of cache, + noUpdate: (bool, optional, false by default) Use only cached data, do not try to update. + noStore: (bool, optional, false by default) Skip storing fresh data if updated, + minFresh: (int, optional, -1 by default) Return cached data if not older than this many seconds. -1 means do not check age. + } + :return: Schema json. + { + id: identifier of schema + attrNames: array of attribute name strings + name: Schema's name string + version: Schema's version string + ver: Version of the Schema json + } + """ + logger = logging.getLogger(__name__) + logger.debug("get_schema: >>> pool_handle: %r, wallet_handle: %r, submitter_did: %r, id: %r, options_json: %r", + pool_handle, + wallet_handle, + submitter_did, + id, + options_json) + + if not hasattr(get_schema, "cb"): + logger.debug("get_schema: Creating callback") + get_schema.cb = create_cb(CFUNCTYPE(None, c_int32, c_int32, c_char_p)) + + c_pool_handle = c_int32(pool_handle) + c_wallet_handle = c_int32(wallet_handle) + c_submitter_did = c_char_p(submitter_did.encode('utf-8')) + c_id = c_char_p(id.encode('utf-8')) + c_options_json = c_char_p(options_json.encode('utf-8')) + + schema_json = await do_call('indy_get_schema', + c_pool_handle, + c_wallet_handle, + c_submitter_did, + c_id, + c_options_json, + get_schema.cb) + res = schema_json.decode() + + logger.debug("get_schema: <<< res: %r", res) + return res + + +async def get_cred_def(pool_handle: int, wallet_handle: int, submitter_did: str, id: str, options_json: str) -> str: + """ + Gets credential definition json data for specified credential definition id. + If data is present inside of cache, cached data is returned. + Otherwise data is fetched from the ledger and stored inside of cache for future use. + + EXPERIMENTAL + + :param pool_handle: pool handle (created by open_pool_ledger). + :param wallet_handle: wallet handle (created by open_wallet). + :param submitter_did: DID of the submitter stored in secured Wallet. + :param id: identifier of credential definition. + :param options_json: + { + noCache: (bool, optional, false by default) Skip usage of cache, + noUpdate: (bool, optional, false by default) Use only cached data, do not try to update. + noStore: (bool, optional, false by default) Skip storing fresh data if updated, + minFresh: (int, optional, -1 by default) Return cached data if not older than this many seconds. -1 means do not check age. + } + :return: Credential Definition json. + { + id: string - identifier of credential definition + schemaId: string - identifier of stored in ledger schema + type: string - type of the credential definition. CL is the only supported type now. + tag: string - allows to distinct between credential definitions for the same issuer and schema + value: Dictionary with Credential Definition's data: { + primary: primary credential public key, + Optional: revocation credential public key + }, + ver: Version of the Credential Definition json + } + """ + logger = logging.getLogger(__name__) + logger.debug("get_cred_def: >>> pool_handle: %r, wallet_handle: %r, submitter_did: %r, id: %r, options_json: %r", + pool_handle, + wallet_handle, + submitter_did, + id, + options_json) + + if not hasattr(get_cred_def, "cb"): + logger.debug("get_cred_def: Creating callback") + get_cred_def.cb = create_cb(CFUNCTYPE(None, c_int32, c_int32, c_char_p)) + + c_pool_handle = c_int32(pool_handle) + c_wallet_handle = c_int32(wallet_handle) + c_submitter_did = c_char_p(submitter_did.encode('utf-8')) + c_id = c_char_p(id.encode('utf-8')) + c_options_json = c_char_p(options_json.encode('utf-8')) + + cred_def_json = await do_call('indy_get_cred_def', + c_pool_handle, + c_wallet_handle, + c_submitter_did, + c_id, + c_options_json, + get_cred_def.cb) + res = cred_def_json.decode() + + logger.debug("get_cred_def: <<< res: %r", res) + return res + + +async def purge_schema_cache(wallet_handle: int, options_json: str) -> None: + """ + Purge schema cache. + + EXPERIMENTAL + + :param wallet_handle: wallet handle (used for cache) + :param options_json: + { + maxAge: (int, optional, -1 by default) Purge cached data if older than this many seconds. -1 means purge all. + } + :return: None + """ + + logger = logging.getLogger(__name__) + logger.debug("purge_schema_cache: >>> wallet_handle: %r, options_json: %r", + wallet_handle, + options_json) + + if not hasattr(purge_schema_cache, "cb"): + logger.debug("purge_schema_cache: Creating callback") + purge_schema_cache.cb = create_cb(CFUNCTYPE(None, c_int32, c_int32)) + + c_wallet_handle = c_int32(wallet_handle) + c_options_json = c_char_p(options_json.encode('utf-8')) + + res = await do_call('indy_purge_schema_cache', + c_wallet_handle, + c_options_json, + purge_schema_cache.cb) + + logger.debug("purge_schema_cache: <<< res: %r", res) + return res + + +async def purge_cred_def_cache(wallet_handle: int, options_json: str) -> None: + """ + Purge credential definition cache. + + EXPERIMENTAL + + :param wallet_handle: wallet handle (used for cache) + :param options_json: + { + maxAge: (int, optional, -1 by default) Purge cached data if older than this many seconds. -1 means purge all. + } + :return: None + """ + + logger = logging.getLogger(__name__) + logger.debug("purge_cred_def_cache: >>> wallet_handle: %r, options_json: %r", + wallet_handle, + options_json) + + if not hasattr(purge_cred_def_cache, "cb"): + logger.debug("purge_cred_def_cache: Creating callback") + purge_cred_def_cache.cb = create_cb(CFUNCTYPE(None, c_int32, c_int32)) + + c_wallet_handle = c_int32(wallet_handle) + c_options_json = c_char_p(options_json.encode('utf-8')) + + res = await do_call('indy_purge_cred_def_cache', + c_wallet_handle, + c_options_json, + purge_cred_def_cache.cb) + + logger.debug("purge_cred_def_cache: <<< res: %r", res) + return res diff --git a/wrappers/python/tests/cache/__init__.py b/wrappers/python/tests/cache/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/wrappers/python/tests/cache/test_get_cred_def.py b/wrappers/python/tests/cache/test_get_cred_def.py new file mode 100644 index 0000000000..9667b8eaad --- /dev/null +++ b/wrappers/python/tests/cache/test_get_cred_def.py @@ -0,0 +1,55 @@ +import json +import pytest +import time + +from indy import ledger, anoncreds, cache, IndyError + + +@pytest.mark.asyncio +async def test_get_cred_def_works(pool_handle, wallet_handle, identity_my): + (my_did, my_ver_key) = identity_my + options_json = { + "noCache": False, + "noUpdate": False, + "noStore": False, + "minFresh": -1, + } + + (schema_id, schema_json) = \ + await anoncreds.issuer_create_schema(my_did, "gvt", "1.0", json.dumps(["name", "age", "sex", "height"])) + + schema_request = await ledger.build_schema_request(my_did, schema_json) + await ledger.sign_and_submit_request(pool_handle, wallet_handle, my_did, schema_request) + + # retry if previous request is not applied + for _ in range(3): + try: + schema_json = await cache.get_schema(pool_handle, wallet_handle, my_did, schema_id, json.dumps(options_json)) + except IndyError as err: + if e.error_code == ErrorCode.LedgerNotFound: + logger = logging.getLogger(__name__) + logger.warning(e) + logger.warning(response) + time.sleep(5) + else: + raise err + + (cred_def_id, cred_def_json) = \ + await anoncreds.issuer_create_and_store_credential_def(wallet_handle, my_did, schema_json, "TAG", "CL", + json.dumps({"support_revocation": False})) + + cred_def_request = await ledger.build_cred_def_request(my_did, cred_def_json) + await ledger.sign_and_submit_request(pool_handle, wallet_handle, my_did, cred_def_request) + + # retry if previous request is not applied + for _ in range(3): + try: + await cache.get_cred_def(pool_handle, wallet_handle, my_did, cred_def_id, json.dumps(options_json)) + except IndyError as err: + if e.error_code == ErrorCode.LedgerNotFound: + logger = logging.getLogger(__name__) + logger.warning(e) + logger.warning(response) + time.sleep(5) + else: + raise err diff --git a/wrappers/python/tests/cache/test_get_schema.py b/wrappers/python/tests/cache/test_get_schema.py new file mode 100644 index 0000000000..0f347e0897 --- /dev/null +++ b/wrappers/python/tests/cache/test_get_schema.py @@ -0,0 +1,35 @@ +import json +import pytest + +from indy import ledger, anoncreds, cache + + +@pytest.mark.asyncio +async def test_get_schema_works(pool_handle, wallet_handle, identity_my): + (my_did, my_ver_key) = identity_my + + (schema_id, schema_json) = \ + await anoncreds.issuer_create_schema(my_did, "gvt", "1.0", json.dumps(["name", "age", "sex", "height"])) + + schema_request = await ledger.build_schema_request(my_did, schema_json) + await ledger.sign_and_submit_request(pool_handle, wallet_handle, my_did, schema_request) + + options_json = { + "noCache": False, + "noUpdate": False, + "noStore": False, + "minFresh": -1, + } + + # retry if previous request is not applied + for _ in range(3): + try: + await cache.get_schema(pool_handle, wallet_handle, my_did, schema_id, json.dumps(options_json)) + except IndyError as err: + if e.error_code == ErrorCode.LedgerNotFound: + logger = logging.getLogger(__name__) + logger.warning(e) + logger.warning(response) + time.sleep(5) + else: + raise err diff --git a/wrappers/python/tests/cache/test_purge_cred_def_cache.py b/wrappers/python/tests/cache/test_purge_cred_def_cache.py new file mode 100644 index 0000000000..c25039b3b5 --- /dev/null +++ b/wrappers/python/tests/cache/test_purge_cred_def_cache.py @@ -0,0 +1,14 @@ +import json +import pytest + +from indy import cache + + +@pytest.mark.asyncio +async def test_purge_schema_cache_works(wallet_handle): + + options_json = { + "maxAge": -1, + } + + await cache.purge_cred_def_cache(wallet_handle, json.dumps(options_json)) diff --git a/wrappers/python/tests/cache/test_purge_schema_cache.py b/wrappers/python/tests/cache/test_purge_schema_cache.py new file mode 100644 index 0000000000..bb9751dd4d --- /dev/null +++ b/wrappers/python/tests/cache/test_purge_schema_cache.py @@ -0,0 +1,14 @@ +import json +import pytest + +from indy import cache + + +@pytest.mark.asyncio +async def test_purge_schema_cache_works(wallet_handle): + + options_json = { + "maxAge": -1, + } + + await cache.purge_schema_cache(wallet_handle, json.dumps(options_json)) diff --git a/wrappers/rust/indy-sys/src/cache.rs b/wrappers/rust/indy-sys/src/cache.rs new file mode 100644 index 0000000000..af94a59ca1 --- /dev/null +++ b/wrappers/rust/indy-sys/src/cache.rs @@ -0,0 +1,36 @@ +use super::*; + +use {CString, Error, CommandHandle, WalletHandle, PoolHandle}; + +extern { + + #[no_mangle] + pub fn indy_get_schema(command_handle: CommandHandle, + pool_handle: PoolHandle, + wallet_handle: WalletHandle, + submitter_did: CString, + id: CString, + options_json: CString, + cb: Option) -> Error; + + #[no_mangle] + pub fn indy_get_cred_def(command_handle: CommandHandle, + pool_handle: PoolHandle, + wallet_handle: WalletHandle, + submitter_did: CString, + id: CString, + options_json: CString, + cb: Option) -> Error; + + #[no_mangle] + pub fn indy_purge_schema_cache(command_handle: CommandHandle, + wallet_handle: WalletHandle, + options_json: CString, + cb: Option) -> Error; + + #[no_mangle] + pub fn indy_purge_cred_def_cache(command_handle: CommandHandle, + wallet_handle: WalletHandle, + options_json: CString, + cb: Option) -> Error; +} diff --git a/wrappers/rust/indy-sys/src/lib.rs b/wrappers/rust/indy-sys/src/lib.rs index a3fed2725c..1c62b50a8f 100644 --- a/wrappers/rust/indy-sys/src/lib.rs +++ b/wrappers/rust/indy-sys/src/lib.rs @@ -11,6 +11,7 @@ pub mod payments; pub mod pool; pub mod wallet; pub mod logger; +pub mod cache; use self::libc::{c_void, c_char}; diff --git a/wrappers/rust/indy-sys/src/non_secrets.rs b/wrappers/rust/indy-sys/src/non_secrets.rs index 5a899bdeec..dc72f3a164 100644 --- a/wrappers/rust/indy-sys/src/non_secrets.rs +++ b/wrappers/rust/indy-sys/src/non_secrets.rs @@ -80,4 +80,3 @@ extern { wallet_search_handle: SearchHandle, cb: Option) -> Error; } - diff --git a/wrappers/rust/src/cache.rs b/wrappers/rust/src/cache.rs new file mode 100644 index 0000000000..614fd5fc89 --- /dev/null +++ b/wrappers/rust/src/cache.rs @@ -0,0 +1,179 @@ +use futures::Future; + +use {ErrorCode, IndyError}; + +use std::ffi::CString; + +use utils::callbacks::{ClosureHandler, ResultHandler}; + +use ffi::cache; +use ffi::{ResponseEmptyCB, ResponseStringCB}; +use ffi::{WalletHandle, CommandHandle, PoolHandle}; + +/// Get schema json data for specified schema id. +/// If data is present inside of cache, cached data is returned. +/// Otherwise data is fetched from the ledger and stored inside of cache for future use. +/// +/// EXPERIMENTAL +/// +/// # Arguments +/// * `pool_handle` - pool handle (created by open_pool_ledger). +/// * `wallet_handle` - wallet handle (created by open_wallet). +/// * `submitter_did` - DID of the submitter stored in secured Wallet. +/// * `id` - identifier of schema. +/// * `options_json` - +/// { +/// noCache: (bool, optional, false by default) Skip usage of cache, +/// noUpdate: (bool, optional, false by default) Use only cached data, do not try to update. +/// noStore: (bool, optional, false by default) Skip storing fresh data if updated, +/// minFresh: (int, optional, -1 by default) Return cached data if not older than this many seconds. -1 means do not check age. +/// } +/// # Returns +/// Schema json. +/// { +/// id: identifier of schema +/// attrNames: array of attribute name strings +/// name: Schema's name string +/// version: Schema's version string +/// ver: Version of the Schema json +/// } +pub fn get_schema(pool_handle: PoolHandle, + wallet_handle: WalletHandle, + submitter_did: &str, + id: &str, + options_json: &str) -> Box> { + let (receiver, command_handle, cb) = ClosureHandler::cb_ec_string(); + + let err = _get_schema(command_handle, pool_handle, wallet_handle, submitter_did, id, options_json, cb); + + ResultHandler::str(command_handle, err, receiver) +} + +pub fn _get_schema(command_handle: CommandHandle, + pool_handle: PoolHandle, + wallet_handle: WalletHandle, + submitter_did: &str, + id: &str, + options_json: &str, + cb: Option) -> ErrorCode { + let submitter_did = c_str!(submitter_did); + let id = c_str!(id); + let options_json = c_str!(options_json); + + ErrorCode::from( + unsafe { + cache::indy_get_schema(command_handle, pool_handle, wallet_handle, submitter_did.as_ptr(), id.as_ptr(), options_json.as_ptr(), cb) + } + ) +} + +/// Get credential definition json data for specified credential definition id. +/// If data is present inside of cache, cached data is returned. +/// Otherwise data is fetched from the ledger and stored inside of cache for future use. +/// +/// EXPERIMENTAL +/// +/// # Arguments +/// * `pool_handle` - pool handle (created by open_pool_ledger). +/// * `wallet_handle` - wallet handle (created by open_wallet). +/// * `submitter_did` - DID of the submitter stored in secured Wallet. +/// * `id` - identifier of credential definition. +/// * `options_json` - +/// { +/// noCache: (bool, optional, false by default) Skip usage of cache, +/// noUpdate: (bool, optional, false by default) Use only cached data, do not try to update. +/// noStore: (bool, optional, false by default) Skip storing fresh data if updated, +/// minFresh: (int, optional, -1 by default) Return cached data if not older than this many seconds. -1 means do not check age. +/// } +/// # Returns +/// Credential Definition json. +/// { +/// id: string - identifier of credential definition +/// schemaId: string - identifier of stored in ledger schema +/// type: string - type of the credential definition. CL is the only supported type now. +/// tag: string - allows to distinct between credential definitions for the same issuer and schema +/// value: Dictionary with Credential Definition's data: { +/// primary: primary credential public key, +/// Optional: revocation credential public key +/// }, +/// ver: Version of the Credential Definition json +/// } +pub fn get_cred_def(pool_handle: PoolHandle, + wallet_handle: WalletHandle, + submitter_did: &str, + id: &str, + options_json: &str) -> Box> { + let (receiver, command_handle, cb) = ClosureHandler::cb_ec_string(); + + let err = _get_cred_def(command_handle, pool_handle, wallet_handle, submitter_did, id, options_json, cb); + + ResultHandler::str(command_handle, err, receiver) +} + +pub fn _get_cred_def(command_handle: CommandHandle, + pool_handle: PoolHandle, + wallet_handle: WalletHandle, + submitter_did: &str, + id: &str, + options_json: &str, + cb: Option) -> ErrorCode { + let submitter_did = c_str!(submitter_did); + let id = c_str!(id); + let options_json = c_str!(options_json); + + ErrorCode::from( + unsafe { + cache::indy_get_cred_def(command_handle, pool_handle, wallet_handle, submitter_did.as_ptr(), id.as_ptr(), options_json.as_ptr(), cb) + } + ) +} + +/// Purge schema cache. +/// +/// EXPERIMENTAL +/// +/// # Arguments +/// * `wallet_handle` - wallet handle (created by open_wallet). +/// * `id` - identifier of schema. +/// * `options_json` - +/// { +/// maxAge: (int, optional, -1 by default) Purge cached data if older than this many seconds. -1 means purge all. +/// } +pub fn purge_schema_cache(wallet_handle: WalletHandle, options_json: &str) -> Box> { + let (receiver, command_handle, cb) = ClosureHandler::cb_ec(); + + let err = _purge_schema_cache(command_handle, wallet_handle, options_json, cb); + + ResultHandler::empty(command_handle, err, receiver) +} + +fn _purge_schema_cache(command_handle: CommandHandle, wallet_handle: WalletHandle, options_json: &str, cb: Option) -> ErrorCode { + let options_json = c_str!(options_json); + + ErrorCode::from(unsafe { cache::indy_purge_schema_cache(command_handle, wallet_handle, options_json.as_ptr(), cb) }) +} + +/// Purge credential definition cache. +/// +/// EXPERIMENTAL +/// +/// # Arguments +/// * `wallet_handle` - wallet handle (created by open_wallet). +/// * `id` - identifier of credential definition. +/// * `options_json` - +/// { +/// maxAge: (int, optional, -1 by default) Purge cached data if older than this many seconds. -1 means purge all. +/// } +pub fn purge_cred_def_cache(wallet_handle: WalletHandle, options_json: &str) -> Box> { + let (receiver, command_handle, cb) = ClosureHandler::cb_ec(); + + let err = _purge_cred_def_cache(command_handle, wallet_handle, options_json, cb); + + ResultHandler::empty(command_handle, err, receiver) +} + +fn _purge_cred_def_cache(command_handle: CommandHandle, wallet_handle: WalletHandle, options_json: &str, cb: Option) -> ErrorCode { + let options_json = c_str!(options_json); + + ErrorCode::from(unsafe { cache::indy_purge_cred_def_cache(command_handle, wallet_handle, options_json.as_ptr(), cb) }) +} \ No newline at end of file diff --git a/wrappers/rust/src/lib.rs b/wrappers/rust/src/lib.rs index 20e93a9e44..44b155260e 100644 --- a/wrappers/rust/src/lib.rs +++ b/wrappers/rust/src/lib.rs @@ -30,6 +30,7 @@ pub mod payments; pub mod pairwise; pub mod pool; pub mod wallet; +pub mod cache; mod utils; use std::ffi::CString;