From 36791b76027d22d4ca7a2ed26670f9e7a4e845c4 Mon Sep 17 00:00:00 2001 From: jaswanthkumarpolisetty Date: Thu, 27 Nov 2025 07:10:05 +0530 Subject: [PATCH 1/6] [INJIMOB-3545]: Implement Context Caching in VC-Verifier Signed-off-by: jaswanthkumarpolisetty --- .../io/mosip/vccred/example/MainActivity.kt | 24 ++++++++++--- .../vercred/vcverifier/CredentialsVerifier.kt | 25 ++++++++++--- .../vcverifier/PresentationVerifier.kt | 3 +- .../io/mosip/vercred/vcverifier/data/Data.kt | 12 +++++-- .../io/mosip/vercred/vcverifier/utils/Util.kt | 7 +++- .../utils/WalletAwareDocumentLoader.kt | 36 +++++++++++++++++++ 6 files changed, 92 insertions(+), 15 deletions(-) create mode 100644 vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/WalletAwareDocumentLoader.kt diff --git a/vc-verifier/kotlin/example/src/main/java/io/mosip/vccred/example/MainActivity.kt b/vc-verifier/kotlin/example/src/main/java/io/mosip/vccred/example/MainActivity.kt index 4cdbf36e..2b356a0d 100644 --- a/vc-verifier/kotlin/example/src/main/java/io/mosip/vccred/example/MainActivity.kt +++ b/vc-verifier/kotlin/example/src/main/java/io/mosip/vccred/example/MainActivity.kt @@ -18,6 +18,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow @@ -26,7 +27,12 @@ import androidx.compose.ui.unit.dp import io.mosip.vccred.example.ui.theme.VcverifierTheme import io.mosip.vercred.vcverifier.CredentialsVerifier import io.mosip.vercred.vcverifier.constants.CredentialFormat +import io.mosip.vercred.vcverifier.data.CacheEntry import io.mosip.vercred.vcverifier.data.VerificationResult +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.util.concurrent.ConcurrentHashMap class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -46,14 +52,21 @@ class MainActivity : ComponentActivity() { @Composable fun VerifyVC(modifier: Modifier = Modifier) { val verificationResult = remember { mutableStateOf(null) } + val walletCache = remember { ConcurrentHashMap() } + val ttlMillis: Long = 1 * 60 * 1000 + val scope = rememberCoroutineScope() Column(modifier = Modifier.padding(30.dp)) { Button( onClick = { - val thread = Thread{ - verificationResult.value = verifyVc() + scope.launch(Dispatchers.IO) { + verificationResult.value = null + val result = verifyVc(walletCache, ttlMillis) + withContext(Dispatchers.Main) { + verificationResult.value = result + } + walletCache.putAll(result.Cachediff) } - thread.start() }, modifier = Modifier .padding(16.dp) @@ -94,9 +107,10 @@ fun VerifyVC(modifier: Modifier = Modifier) { } -fun verifyVc(): VerificationResult{ +fun verifyVc(walletCache: ConcurrentHashMap, ttlMillis: Long): VerificationResult { val credentialsVerifier = CredentialsVerifier() - return credentialsVerifier.verify(farmerVc, CredentialFormat.LDP_VC) + val result = credentialsVerifier.verify(farmerVc, CredentialFormat.LDP_VC, walletCache,ttlMillis) + return result } @Preview(showBackground = true) diff --git a/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/CredentialsVerifier.kt b/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/CredentialsVerifier.kt index d4493c78..292042c5 100644 --- a/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/CredentialsVerifier.kt +++ b/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/CredentialsVerifier.kt @@ -8,10 +8,12 @@ import io.mosip.vercred.vcverifier.constants.CredentialVerifierConstants.ERROR_M import io.mosip.vercred.vcverifier.constants.CredentialVerifierConstants.EXCEPTION_DURING_VERIFICATION import io.mosip.vercred.vcverifier.credentialverifier.CredentialVerifierFactory import io.mosip.vercred.vcverifier.credentialverifier.VerifiableCredential +import io.mosip.vercred.vcverifier.data.CacheEntry import io.mosip.vercred.vcverifier.data.CredentialStatusResult import io.mosip.vercred.vcverifier.data.CredentialVerificationSummary import io.mosip.vercred.vcverifier.data.ValidationStatus import io.mosip.vercred.vcverifier.data.VerificationResult +import io.mosip.vercred.vcverifier.utils.Util import java.util.logging.Logger @@ -37,10 +39,20 @@ class CredentialsVerifier { logger.warning("Credential verification failed") return false } + return true } - fun verify(credential: String, credentialFormat: CredentialFormat): VerificationResult { + fun verify(credential: String, + credentialFormat: CredentialFormat, + walletCache: MutableMap? = null, + expiryTime: Long? = null): VerificationResult { + if (walletCache != null) { + Util.walletCache = walletCache + } + expiryTime?.let { + Util.ttlMillis = it + } val credentialVerifier = credentialVerifierFactory.get(credentialFormat) val validationStatus = credentialVerifier.validate(credential) if (validationStatus.validationMessage.isNotEmpty() && !validationStatus.validationErrorCode.contentEquals( @@ -50,7 +62,8 @@ class CredentialsVerifier { return VerificationResult( false, validationStatus.validationMessage, - validationStatus.validationErrorCode + validationStatus.validationErrorCode, + Util.walletCache ) } return try { @@ -59,19 +72,21 @@ class CredentialsVerifier { return VerificationResult( true, validationStatus.validationMessage, - validationStatus.validationErrorCode + validationStatus.validationErrorCode, + Util.walletCache ) } return VerificationResult( false, ERROR_MESSAGE_VERIFICATION_FAILED, - ERROR_CODE_VERIFICATION_FAILED + ERROR_CODE_VERIFICATION_FAILED, + Util.walletCache ) } catch (e: Exception) { val errorCode = validationStatus.validationErrorCode.takeIf { !it.isNullOrEmpty() } ?: ERROR_CODE_VERIFICATION_FAILED - VerificationResult(false, "$EXCEPTION_DURING_VERIFICATION${e.message}", errorCode) + VerificationResult(false, "$EXCEPTION_DURING_VERIFICATION${e.message}", errorCode,Util.walletCache) } } diff --git a/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/PresentationVerifier.kt b/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/PresentationVerifier.kt index 33a66d59..47c36823 100644 --- a/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/PresentationVerifier.kt +++ b/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/PresentationVerifier.kt @@ -12,6 +12,7 @@ import io.mosip.vercred.vcverifier.constants.CredentialVerifierConstants.ED25519 import io.mosip.vercred.vcverifier.constants.CredentialVerifierConstants.ED25519_PROOF_TYPE_2020 import io.mosip.vercred.vcverifier.constants.CredentialVerifierConstants.JSON_WEB_PROOF_TYPE_2020 import io.mosip.vercred.vcverifier.constants.Shared +import io.mosip.vercred.vcverifier.data.CacheEntry import io.mosip.vercred.vcverifier.data.PresentationVerificationResult import io.mosip.vercred.vcverifier.data.PresentationResultWithCredentialStatus import io.mosip.vercred.vcverifier.data.VCResult @@ -141,7 +142,7 @@ class PresentationVerifier { private fun getVCVerificationResults(verifiableCredentials: JSONArray): List { return verifiableCredentials.asIterable().map { item -> val verificationResult: VerificationResult = - credentialsVerifier.verify((item as JSONObject).toString(), CredentialFormat.LDP_VC) + credentialsVerifier.verify((item as JSONObject).toString(), CredentialFormat.LDP_VC, mutableMapOf()) val singleVCVerification: VerificationStatus = Util.getVerificationStatus(verificationResult) diff --git a/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/data/Data.kt b/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/data/Data.kt index e1d9eaa0..7a7634ad 100644 --- a/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/data/Data.kt +++ b/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/data/Data.kt @@ -1,12 +1,13 @@ package io.mosip.vercred.vcverifier.data +import com.apicatalog.jsonld.document.JsonDocument import io.mosip.vercred.vcverifier.exception.StatusCheckException data class VerificationResult( var verificationStatus: Boolean, var verificationMessage: String = "", - var verificationErrorCode: String - + var verificationErrorCode: String, + val Cachediff: Map ) data class PresentationVerificationResult( @@ -64,4 +65,9 @@ data class CredentialStatusResult( data class CredentialVerificationSummary( val verificationResult: VerificationResult, val credentialStatus: List -) \ No newline at end of file +) + +data class CacheEntry( + val document: JsonDocument, + val expiryTime: Long +) diff --git a/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/Util.kt b/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/Util.kt index 4586f43b..b6a219c3 100644 --- a/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/Util.kt +++ b/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/Util.kt @@ -1,5 +1,6 @@ package io.mosip.vercred.vcverifier.utils +import WalletAwareDocumentLoader import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import foundation.identity.jsonld.ConfigurableDocumentLoader @@ -12,6 +13,7 @@ import io.mosip.vercred.vcverifier.constants.CredentialVerifierConstants.JWS_ES2 import io.mosip.vercred.vcverifier.constants.CredentialVerifierConstants.JWS_ES256_SIGN_ALGO_CONST import io.mosip.vercred.vcverifier.constants.CredentialVerifierConstants.JWS_PS256_SIGN_ALGO_CONST import io.mosip.vercred.vcverifier.constants.CredentialVerifierConstants.JWS_RS256_SIGN_ALGO_CONST +import io.mosip.vercred.vcverifier.data.CacheEntry import io.mosip.vercred.vcverifier.data.DataModel import io.mosip.vercred.vcverifier.data.VerificationResult import io.mosip.vercred.vcverifier.data.VerificationStatus @@ -26,11 +28,14 @@ import java.security.MessageDigest import java.security.PublicKey import java.security.cert.CertificateFactory import java.security.cert.X509Certificate +import java.util.concurrent.ConcurrentHashMap import kotlin.text.Charsets.UTF_8 object Util { var documentLoader : ConfigurableDocumentLoader? = null + var walletCache: MutableMap = ConcurrentHashMap() + var ttlMillis: Long = 30 * 60 * 1000 val SUPPORTED_JWS_ALGORITHMS = setOf( JWS_PS256_SIGN_ALGO_CONST, @@ -45,7 +50,7 @@ object Util { fun getConfigurableDocumentLoader(): ConfigurableDocumentLoader { return documentLoader ?: run { - val loader = ConfigurableDocumentLoader() + val loader = WalletAwareDocumentLoader(ttlMillis,walletCache) loader.isEnableHttps = true loader.isEnableHttp = true loader.isEnableFile = false diff --git a/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/WalletAwareDocumentLoader.kt b/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/WalletAwareDocumentLoader.kt new file mode 100644 index 00000000..ebf94c95 --- /dev/null +++ b/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/WalletAwareDocumentLoader.kt @@ -0,0 +1,36 @@ +import foundation.identity.jsonld.ConfigurableDocumentLoader +import com.apicatalog.jsonld.document.Document +import com.apicatalog.jsonld.document.JsonDocument +import com.apicatalog.jsonld.loader.DocumentLoaderOptions +import io.mosip.vercred.vcverifier.data.CacheEntry +import java.net.URI + +class WalletAwareDocumentLoader( + private val ttlMillis: Long, + private val walletCache: MutableMap +) : ConfigurableDocumentLoader() { + override fun loadDocument(url: URI, options: DocumentLoaderOptions): Document { + val now = System.currentTimeMillis() + val expiryTime = now + ttlMillis + val urlStr = url.toString() + + walletCache[urlStr]?.let { cachedEntry -> + if(cachedEntry.expiryTime > now) { + return cachedEntry.document + } + else { + walletCache.remove(urlStr) + } + } + + val fetched = super.loadDocument(url, options) + + if (fetched is JsonDocument) { + walletCache[urlStr] = CacheEntry( + document = fetched, + expiryTime = expiryTime + ) + } + return fetched + } +} From b82b6015a5a331a9a38cb63bb3a4147480902585 Mon Sep 17 00:00:00 2001 From: jaswanthkumarpolisetty Date: Thu, 27 Nov 2025 12:05:50 +0530 Subject: [PATCH 2/6] [INJIMOB-3545]: Update test cases to walletAwareDocumentLoader Signed-off-by: jaswanthkumarpolisetty --- .../verifier/LdpVerifier.kt | 3 +- .../io/mosip/vercred/vcverifier/utils/Util.kt | 26 +++-- .../utils/WalletAwareDocumentLoader.kt | 24 ++-- .../vercred/vcverifier/utils/UtilsTest.kt | 10 +- .../utils/WalletAwareDocumentLoaderTest.kt | 104 ++++++++++++++++++ 5 files changed, 143 insertions(+), 24 deletions(-) create mode 100644 vc-verifier/kotlin/vcverifier/src/test/java/io/mosip/vercred/vcverifier/utils/WalletAwareDocumentLoaderTest.kt diff --git a/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/LdpVerifier.kt b/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/LdpVerifier.kt index 40f33874..578dd81b 100644 --- a/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/LdpVerifier.kt +++ b/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/credentialverifier/verifier/LdpVerifier.kt @@ -1,5 +1,6 @@ package io.mosip.vercred.vcverifier.credentialverifier.verifier +import com.apicatalog.jsonld.loader.DocumentLoader import com.nimbusds.jose.JWSObject import foundation.identity.jsonld.ConfigurableDocumentLoader import foundation.identity.jsonld.JsonLDObject @@ -32,7 +33,7 @@ class LdpVerifier { fun verify(credential: String): Boolean { logger.info("Received Credentials Verification - Start") - val confDocumentLoader: ConfigurableDocumentLoader = Util.getConfigurableDocumentLoader() + val confDocumentLoader: DocumentLoader = Util.getConfigurableDocumentLoader() val vcJsonLdObject: JsonLDObject = JsonLDObject.fromJson(credential) vcJsonLdObject.documentLoader = confDocumentLoader diff --git a/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/Util.kt b/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/Util.kt index b6a219c3..d735d1ed 100644 --- a/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/Util.kt +++ b/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/Util.kt @@ -1,6 +1,7 @@ package io.mosip.vercred.vcverifier.utils import WalletAwareDocumentLoader +import com.apicatalog.jsonld.loader.DocumentLoader import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import foundation.identity.jsonld.ConfigurableDocumentLoader @@ -33,7 +34,7 @@ import kotlin.text.Charsets.UTF_8 object Util { - var documentLoader : ConfigurableDocumentLoader? = null + var documentLoader: DocumentLoader? = null var walletCache: MutableMap = ConcurrentHashMap() var ttlMillis: Long = 30 * 60 * 1000 @@ -48,14 +49,23 @@ object Util { return System.getProperty("java.vm.name")?.contains("Dalvik") ?: false } - fun getConfigurableDocumentLoader(): ConfigurableDocumentLoader { - return documentLoader ?: run { - val loader = WalletAwareDocumentLoader(ttlMillis,walletCache) - loader.isEnableHttps = true - loader.isEnableHttp = true - loader.isEnableFile = false - loader + fun getConfigurableDocumentLoader(): DocumentLoader { + if (documentLoader != null) return documentLoader!! + + val base = ConfigurableDocumentLoader().apply { + isEnableHttps = true + isEnableHttp = true + isEnableFile = false } + + val loader = WalletAwareDocumentLoader( + ttlMillis = ttlMillis, + walletCache = walletCache, + delegate = base + ) + + documentLoader = loader + return loader } fun getVerificationStatus(verificationResult: VerificationResult): VerificationStatus { diff --git a/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/WalletAwareDocumentLoader.kt b/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/WalletAwareDocumentLoader.kt index ebf94c95..54820076 100644 --- a/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/WalletAwareDocumentLoader.kt +++ b/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/WalletAwareDocumentLoader.kt @@ -1,36 +1,36 @@ import foundation.identity.jsonld.ConfigurableDocumentLoader import com.apicatalog.jsonld.document.Document import com.apicatalog.jsonld.document.JsonDocument +import com.apicatalog.jsonld.loader.DocumentLoader import com.apicatalog.jsonld.loader.DocumentLoaderOptions import io.mosip.vercred.vcverifier.data.CacheEntry import java.net.URI class WalletAwareDocumentLoader( private val ttlMillis: Long, - private val walletCache: MutableMap -) : ConfigurableDocumentLoader() { + private val walletCache: MutableMap, + private val delegate: ConfigurableDocumentLoader +) : DocumentLoader { + override fun loadDocument(url: URI, options: DocumentLoaderOptions): Document { val now = System.currentTimeMillis() - val expiryTime = now + ttlMillis val urlStr = url.toString() - walletCache[urlStr]?.let { cachedEntry -> - if(cachedEntry.expiryTime > now) { - return cachedEntry.document - } - else { - walletCache.remove(urlStr) - } + walletCache[urlStr]?.let { entry -> + if (entry.expiryTime > now) return entry.document + walletCache.remove(urlStr) } - val fetched = super.loadDocument(url, options) + val fetched = delegate.loadDocument(url, options) if (fetched is JsonDocument) { walletCache[urlStr] = CacheEntry( document = fetched, - expiryTime = expiryTime + expiryTime = now + ttlMillis ) } + return fetched } } + diff --git a/vc-verifier/kotlin/vcverifier/src/test/java/io/mosip/vercred/vcverifier/utils/UtilsTest.kt b/vc-verifier/kotlin/vcverifier/src/test/java/io/mosip/vercred/vcverifier/utils/UtilsTest.kt index a55e32b7..75f1aef3 100644 --- a/vc-verifier/kotlin/vcverifier/src/test/java/io/mosip/vercred/vcverifier/utils/UtilsTest.kt +++ b/vc-verifier/kotlin/vcverifier/src/test/java/io/mosip/vercred/vcverifier/utils/UtilsTest.kt @@ -16,6 +16,7 @@ import org.threeten.bp.ZoneOffset import org.threeten.bp.format.DateTimeFormatter import java.io.ByteArrayOutputStream import java.util.Date +import java.util.concurrent.ConcurrentHashMap class UtilsTest { @@ -233,7 +234,8 @@ class UtilsTest { val result = VerificationResult( verificationStatus = true, verificationMessage = "Valid VC", - verificationErrorCode = "" + verificationErrorCode = "", + ConcurrentHashMap() ) val status = Util.getVerificationStatus(result) @@ -245,7 +247,8 @@ class UtilsTest { val result = VerificationResult( verificationStatus = true, verificationMessage = "Expired", - verificationErrorCode = CredentialValidatorConstants.ERROR_CODE_VC_EXPIRED + verificationErrorCode = CredentialValidatorConstants.ERROR_CODE_VC_EXPIRED, + ConcurrentHashMap() ) val status = Util.getVerificationStatus(result) @@ -257,7 +260,8 @@ class UtilsTest { val result = VerificationResult( verificationStatus = false, verificationMessage = "Invalid signature", - verificationErrorCode = "SIGNATURE_INVALID" + verificationErrorCode = "SIGNATURE_INVALID", + ConcurrentHashMap() ) val status = Util.getVerificationStatus(result) diff --git a/vc-verifier/kotlin/vcverifier/src/test/java/io/mosip/vercred/vcverifier/utils/WalletAwareDocumentLoaderTest.kt b/vc-verifier/kotlin/vcverifier/src/test/java/io/mosip/vercred/vcverifier/utils/WalletAwareDocumentLoaderTest.kt new file mode 100644 index 00000000..a302adfd --- /dev/null +++ b/vc-verifier/kotlin/vcverifier/src/test/java/io/mosip/vercred/vcverifier/utils/WalletAwareDocumentLoaderTest.kt @@ -0,0 +1,104 @@ +package io.mosip.vercred.vcverifier.utils + +import WalletAwareDocumentLoader +import com.apicatalog.jsonld.document.JsonDocument +import com.apicatalog.jsonld.loader.DocumentLoader +import com.apicatalog.jsonld.loader.DocumentLoaderOptions +import foundation.identity.jsonld.ConfigurableDocumentLoader +import io.mosip.vercred.vcverifier.data.CacheEntry +import org.junit.jupiter.api.Assertions.* +import org.junit.jupiter.api.Test +import java.net.URI + +class WalletAwareDocumentLoaderTest { + + + private fun jsonDoc(content: String): JsonDocument { + return JsonDocument.of(content.byteInputStream()) + } + + class FakeDelegateLoader(private val returned: JsonDocument) : ConfigurableDocumentLoader() { + override fun loadDocument(url: URI, options: DocumentLoaderOptions) = returned + } + + + @Test + fun `cache hit - return cached document`() { + val ttl = 10_000L + val url = URI("https://example.com/context") + + val cachedDoc = jsonDoc("{\"cached\": true}") + val newDoc = jsonDoc("{\"new\": true}") // should NOT be used + + val walletCache = mutableMapOf( + url.toString() to CacheEntry( + cachedDoc, + expiryTime = System.currentTimeMillis() + 5000 // still valid + ) + ) + + val loader = WalletAwareDocumentLoader( + ttlMillis = ttl, + walletCache = walletCache, + delegate = FakeDelegateLoader(newDoc) + ) + + val result = loader.loadDocument(url, DocumentLoaderOptions()) + + assertSame(cachedDoc, result) // returned from cache + } + + // ------------------------------------------------------------ + // TEST 2: Expired Cache -> Reload -> Update Cache + // ------------------------------------------------------------ + @Test + fun `expired cache - fetch new document and update cache`() { + val ttl = 10_000L + val url = URI("https://example.com/context") + + val expiredDoc = jsonDoc("{\"expired\": true}") + val freshDoc = jsonDoc("{\"fresh\": true}") + + val walletCache = mutableMapOf( + url.toString() to CacheEntry( + expiredDoc, + expiryTime = System.currentTimeMillis() - 2000 // EXPIRED + ) + ) + + val loader = WalletAwareDocumentLoader( + ttlMillis = ttl, + walletCache = walletCache, + delegate = FakeDelegateLoader(freshDoc) + ) + + val result = loader.loadDocument(url, DocumentLoaderOptions()) + + assertSame(freshDoc, result) + assertEquals(freshDoc, walletCache[url.toString()]!!.document) + } + + // ------------------------------------------------------------ + // TEST 3: Cache Miss -> Load -> Store in Cache + // ------------------------------------------------------------ + @Test + fun `cache miss - fetch and store document`() { + val ttl = 10_000L + val url = URI("https://example.com/context") + + val fetchedDoc = jsonDoc("{\"loaded\": true}") + val walletCache = mutableMapOf() + + val loader = WalletAwareDocumentLoader( + ttlMillis = ttl, + walletCache = walletCache, + delegate = FakeDelegateLoader(fetchedDoc) + ) + + val result = loader.loadDocument(url, DocumentLoaderOptions()) + + assertSame(fetchedDoc, result) + assertTrue(walletCache.containsKey(url.toString())) + assertEquals(fetchedDoc, walletCache[url.toString()]!!.document) + } +} From d190220b6b0ea777858c8bac7357b4dc695bf5d7 Mon Sep 17 00:00:00 2001 From: jaswanthkumarpolisetty Date: Thu, 27 Nov 2025 14:26:06 +0530 Subject: [PATCH 3/6] [INJIMOB-3545]: Clear build issue Signed-off-by: jaswanthkumarpolisetty --- vc-verifier/kotlin/gradle/libs.versions.toml | 2 ++ vc-verifier/kotlin/vcverifier/build.gradle.kts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/vc-verifier/kotlin/gradle/libs.versions.toml b/vc-verifier/kotlin/gradle/libs.versions.toml index 7f5e2dc9..c6ba325c 100644 --- a/vc-verifier/kotlin/gradle/libs.versions.toml +++ b/vc-verifier/kotlin/gradle/libs.versions.toml @@ -27,6 +27,7 @@ annotationJvm = "1.9.1" cbor = "0.9" identity = "20231002" authleteSdJwt = "1.5" +caffeine = "3.1.8" [libraries] junitJupiter = { group = "org.junit.jupiter", name = "junit-jupiter", version.ref = "junit" } @@ -62,6 +63,7 @@ mockWebServer = { group = "com.squareup.okhttp3", name = "mockwebserver", versio annotation-jvm = { group = "androidx.annotation", name = "annotation-jvm", version.ref = "annotationJvm" } cbor = { group = "co.nstant.in", name = "cbor", version.ref = "cbor" } identity = { group = "com.android.identity", name = "identity-credential", version.ref = "identity" } +caffeine = { group = "com.github.ben-manes.caffeine", name = "caffeine", version.ref = "caffeine" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } diff --git a/vc-verifier/kotlin/vcverifier/build.gradle.kts b/vc-verifier/kotlin/vcverifier/build.gradle.kts index 47419546..db483849 100644 --- a/vc-verifier/kotlin/vcverifier/build.gradle.kts +++ b/vc-verifier/kotlin/vcverifier/build.gradle.kts @@ -65,7 +65,9 @@ dependencies { implementation(libs.annotation.jvm) implementation(libs.authelete.sd.jwt) implementation(libs.threetenbp) + implementation(libs.caffeine) + testImplementation(libs.caffeine) testImplementation(libs.mockk) testImplementation(libs.junitJupiter) testImplementation(libs.mockWebServer) From ba7ecd776872acb3ce16be5bbe067272047ccea6 Mon Sep 17 00:00:00 2001 From: jaswanthkumarpolisetty Date: Thu, 27 Nov 2025 15:44:45 +0530 Subject: [PATCH 4/6] [INJIMOB-3545]: Clear code rabbit comments Signed-off-by: jaswanthkumarpolisetty --- .../kotlin/vcverifier/build.gradle.kts | 2 -- .../io/mosip/vercred/vcverifier/data/Data.kt | 2 +- .../io/mosip/vercred/vcverifier/utils/Util.kt | 34 +++++++++++-------- .../utils/WalletAwareDocumentLoader.kt | 2 ++ .../utils/WalletAwareDocumentLoaderTest.kt | 9 +---- 5 files changed, 23 insertions(+), 26 deletions(-) diff --git a/vc-verifier/kotlin/vcverifier/build.gradle.kts b/vc-verifier/kotlin/vcverifier/build.gradle.kts index db483849..47419546 100644 --- a/vc-verifier/kotlin/vcverifier/build.gradle.kts +++ b/vc-verifier/kotlin/vcverifier/build.gradle.kts @@ -65,9 +65,7 @@ dependencies { implementation(libs.annotation.jvm) implementation(libs.authelete.sd.jwt) implementation(libs.threetenbp) - implementation(libs.caffeine) - testImplementation(libs.caffeine) testImplementation(libs.mockk) testImplementation(libs.junitJupiter) testImplementation(libs.mockWebServer) diff --git a/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/data/Data.kt b/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/data/Data.kt index 7a7634ad..9cf2f7c1 100644 --- a/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/data/Data.kt +++ b/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/data/Data.kt @@ -7,7 +7,7 @@ data class VerificationResult( var verificationStatus: Boolean, var verificationMessage: String = "", var verificationErrorCode: String, - val Cachediff: Map + val cachediff: Map ) data class PresentationVerificationResult( diff --git a/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/Util.kt b/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/Util.kt index d735d1ed..650f5486 100644 --- a/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/Util.kt +++ b/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/Util.kt @@ -1,6 +1,5 @@ package io.mosip.vercred.vcverifier.utils -import WalletAwareDocumentLoader import com.apicatalog.jsonld.loader.DocumentLoader import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper @@ -37,6 +36,7 @@ object Util { var documentLoader: DocumentLoader? = null var walletCache: MutableMap = ConcurrentHashMap() var ttlMillis: Long = 30 * 60 * 1000 + private val loaderLock = Any() val SUPPORTED_JWS_ALGORITHMS = setOf( JWS_PS256_SIGN_ALGO_CONST, @@ -49,25 +49,29 @@ object Util { return System.getProperty("java.vm.name")?.contains("Dalvik") ?: false } - fun getConfigurableDocumentLoader(): DocumentLoader { - if (documentLoader != null) return documentLoader!! + fun getConfigurableDocumentLoader (): DocumentLoader { + documentLoader?.let { return it } + synchronized(loaderLock) { + documentLoader?.let { return it } - val base = ConfigurableDocumentLoader().apply { - isEnableHttps = true - isEnableHttp = true - isEnableFile = false - } + val base = ConfigurableDocumentLoader().apply { + isEnableHttps = true + isEnableHttp = true + isEnableFile = false + } - val loader = WalletAwareDocumentLoader( - ttlMillis = ttlMillis, - walletCache = walletCache, - delegate = base - ) + val loader = WalletAwareDocumentLoader( + ttlMillis = ttlMillis, + walletCache = walletCache, + delegate = base + ) - documentLoader = loader - return loader + documentLoader = loader + return loader + } } + fun getVerificationStatus(verificationResult: VerificationResult): VerificationStatus { if (verificationResult.verificationStatus) { if (verificationResult.verificationErrorCode == CredentialValidatorConstants.ERROR_CODE_VC_EXPIRED) { diff --git a/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/WalletAwareDocumentLoader.kt b/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/WalletAwareDocumentLoader.kt index 54820076..7f99c861 100644 --- a/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/WalletAwareDocumentLoader.kt +++ b/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/WalletAwareDocumentLoader.kt @@ -1,3 +1,5 @@ +package io.mosip.vercred.vcverifier.utils + import foundation.identity.jsonld.ConfigurableDocumentLoader import com.apicatalog.jsonld.document.Document import com.apicatalog.jsonld.document.JsonDocument diff --git a/vc-verifier/kotlin/vcverifier/src/test/java/io/mosip/vercred/vcverifier/utils/WalletAwareDocumentLoaderTest.kt b/vc-verifier/kotlin/vcverifier/src/test/java/io/mosip/vercred/vcverifier/utils/WalletAwareDocumentLoaderTest.kt index a302adfd..8ffcd711 100644 --- a/vc-verifier/kotlin/vcverifier/src/test/java/io/mosip/vercred/vcverifier/utils/WalletAwareDocumentLoaderTest.kt +++ b/vc-verifier/kotlin/vcverifier/src/test/java/io/mosip/vercred/vcverifier/utils/WalletAwareDocumentLoaderTest.kt @@ -1,8 +1,7 @@ package io.mosip.vercred.vcverifier.utils -import WalletAwareDocumentLoader + import com.apicatalog.jsonld.document.JsonDocument -import com.apicatalog.jsonld.loader.DocumentLoader import com.apicatalog.jsonld.loader.DocumentLoaderOptions import foundation.identity.jsonld.ConfigurableDocumentLoader import io.mosip.vercred.vcverifier.data.CacheEntry @@ -48,9 +47,6 @@ class WalletAwareDocumentLoaderTest { assertSame(cachedDoc, result) // returned from cache } - // ------------------------------------------------------------ - // TEST 2: Expired Cache -> Reload -> Update Cache - // ------------------------------------------------------------ @Test fun `expired cache - fetch new document and update cache`() { val ttl = 10_000L @@ -78,9 +74,6 @@ class WalletAwareDocumentLoaderTest { assertEquals(freshDoc, walletCache[url.toString()]!!.document) } - // ------------------------------------------------------------ - // TEST 3: Cache Miss -> Load -> Store in Cache - // ------------------------------------------------------------ @Test fun `cache miss - fetch and store document`() { val ttl = 10_000L From 06b9c0d442d52463108098dde547466adc77907f Mon Sep 17 00:00:00 2001 From: jaswanthkumarpolisetty Date: Fri, 28 Nov 2025 07:38:44 +0530 Subject: [PATCH 5/6] [INJIMOB-3545]: Clear build issue in tests Signed-off-by: jaswanthkumarpolisetty --- .../io/mosip/vccred/example/MainActivity.kt | 2 +- vc-verifier/kotlin/gradle/libs.versions.toml | 2 -- .../utils/WalletAwareDocumentLoader.kt | 4 +-- .../utils/WalletAwareDocumentLoaderTest.kt | 28 ++++++++++--------- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/vc-verifier/kotlin/example/src/main/java/io/mosip/vccred/example/MainActivity.kt b/vc-verifier/kotlin/example/src/main/java/io/mosip/vccred/example/MainActivity.kt index 2b356a0d..cbd24d6d 100644 --- a/vc-verifier/kotlin/example/src/main/java/io/mosip/vccred/example/MainActivity.kt +++ b/vc-verifier/kotlin/example/src/main/java/io/mosip/vccred/example/MainActivity.kt @@ -65,7 +65,7 @@ fun VerifyVC(modifier: Modifier = Modifier) { withContext(Dispatchers.Main) { verificationResult.value = result } - walletCache.putAll(result.Cachediff) + walletCache.putAll(result.cachediff) } }, modifier = Modifier diff --git a/vc-verifier/kotlin/gradle/libs.versions.toml b/vc-verifier/kotlin/gradle/libs.versions.toml index c6ba325c..7f5e2dc9 100644 --- a/vc-verifier/kotlin/gradle/libs.versions.toml +++ b/vc-verifier/kotlin/gradle/libs.versions.toml @@ -27,7 +27,6 @@ annotationJvm = "1.9.1" cbor = "0.9" identity = "20231002" authleteSdJwt = "1.5" -caffeine = "3.1.8" [libraries] junitJupiter = { group = "org.junit.jupiter", name = "junit-jupiter", version.ref = "junit" } @@ -63,7 +62,6 @@ mockWebServer = { group = "com.squareup.okhttp3", name = "mockwebserver", versio annotation-jvm = { group = "androidx.annotation", name = "annotation-jvm", version.ref = "annotationJvm" } cbor = { group = "co.nstant.in", name = "cbor", version.ref = "cbor" } identity = { group = "com.android.identity", name = "identity-credential", version.ref = "identity" } -caffeine = { group = "com.github.ben-manes.caffeine", name = "caffeine", version.ref = "caffeine" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } diff --git a/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/WalletAwareDocumentLoader.kt b/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/WalletAwareDocumentLoader.kt index 7f99c861..ec4e56dc 100644 --- a/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/WalletAwareDocumentLoader.kt +++ b/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/WalletAwareDocumentLoader.kt @@ -1,6 +1,6 @@ package io.mosip.vercred.vcverifier.utils -import foundation.identity.jsonld.ConfigurableDocumentLoader + import com.apicatalog.jsonld.document.Document import com.apicatalog.jsonld.document.JsonDocument import com.apicatalog.jsonld.loader.DocumentLoader @@ -11,7 +11,7 @@ import java.net.URI class WalletAwareDocumentLoader( private val ttlMillis: Long, private val walletCache: MutableMap, - private val delegate: ConfigurableDocumentLoader + private val delegate: DocumentLoader ) : DocumentLoader { override fun loadDocument(url: URI, options: DocumentLoaderOptions): Document { diff --git a/vc-verifier/kotlin/vcverifier/src/test/java/io/mosip/vercred/vcverifier/utils/WalletAwareDocumentLoaderTest.kt b/vc-verifier/kotlin/vcverifier/src/test/java/io/mosip/vercred/vcverifier/utils/WalletAwareDocumentLoaderTest.kt index 8ffcd711..9ad868c6 100644 --- a/vc-verifier/kotlin/vcverifier/src/test/java/io/mosip/vercred/vcverifier/utils/WalletAwareDocumentLoaderTest.kt +++ b/vc-verifier/kotlin/vcverifier/src/test/java/io/mosip/vercred/vcverifier/utils/WalletAwareDocumentLoaderTest.kt @@ -1,50 +1,51 @@ package io.mosip.vercred.vcverifier.utils - import com.apicatalog.jsonld.document.JsonDocument +import com.apicatalog.jsonld.loader.DocumentLoader import com.apicatalog.jsonld.loader.DocumentLoaderOptions -import foundation.identity.jsonld.ConfigurableDocumentLoader import io.mosip.vercred.vcverifier.data.CacheEntry +import io.mockk.every +import io.mockk.mockk import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test import java.net.URI class WalletAwareDocumentLoaderTest { - private fun jsonDoc(content: String): JsonDocument { return JsonDocument.of(content.byteInputStream()) } - class FakeDelegateLoader(private val returned: JsonDocument) : ConfigurableDocumentLoader() { - override fun loadDocument(url: URI, options: DocumentLoaderOptions) = returned + private fun mockDelegate(returned: JsonDocument): DocumentLoader { + val mock = mockk() + every { mock.loadDocument(any(), any()) } returns returned + return mock } - @Test fun `cache hit - return cached document`() { val ttl = 10_000L val url = URI("https://example.com/context") val cachedDoc = jsonDoc("{\"cached\": true}") - val newDoc = jsonDoc("{\"new\": true}") // should NOT be used + val newDoc = jsonDoc("{\"new\": true}") val walletCache = mutableMapOf( url.toString() to CacheEntry( cachedDoc, - expiryTime = System.currentTimeMillis() + 5000 // still valid + expiryTime = System.currentTimeMillis() + 5000 ) ) val loader = WalletAwareDocumentLoader( ttlMillis = ttl, walletCache = walletCache, - delegate = FakeDelegateLoader(newDoc) + delegate = mockDelegate(newDoc) ) val result = loader.loadDocument(url, DocumentLoaderOptions()) - assertSame(cachedDoc, result) // returned from cache + assertSame(cachedDoc, result) } @Test @@ -58,14 +59,14 @@ class WalletAwareDocumentLoaderTest { val walletCache = mutableMapOf( url.toString() to CacheEntry( expiredDoc, - expiryTime = System.currentTimeMillis() - 2000 // EXPIRED + expiryTime = System.currentTimeMillis() - 1000 ) ) val loader = WalletAwareDocumentLoader( ttlMillis = ttl, walletCache = walletCache, - delegate = FakeDelegateLoader(freshDoc) + delegate = mockDelegate(freshDoc) ) val result = loader.loadDocument(url, DocumentLoaderOptions()) @@ -85,7 +86,7 @@ class WalletAwareDocumentLoaderTest { val loader = WalletAwareDocumentLoader( ttlMillis = ttl, walletCache = walletCache, - delegate = FakeDelegateLoader(fetchedDoc) + delegate = mockDelegate(fetchedDoc) ) val result = loader.loadDocument(url, DocumentLoaderOptions()) @@ -95,3 +96,4 @@ class WalletAwareDocumentLoaderTest { assertEquals(fetchedDoc, walletCache[url.toString()]!!.document) } } + From a804be8a43aa9d57fc4957e02a21d32e6579f761 Mon Sep 17 00:00:00 2001 From: jaswanthkumarpolisetty Date: Wed, 3 Dec 2025 11:25:42 +0530 Subject: [PATCH 6/6] [INJIMOB-3545]: clear review comment by code rabbit Signed-off-by: jaswanthkumarpolisetty --- .../src/main/java/io/mosip/vercred/vcverifier/utils/Util.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/Util.kt b/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/Util.kt index 650f5486..922eeee2 100644 --- a/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/Util.kt +++ b/vc-verifier/kotlin/vcverifier/src/main/java/io/mosip/vercred/vcverifier/utils/Util.kt @@ -33,6 +33,7 @@ import kotlin.text.Charsets.UTF_8 object Util { + @Volatile var documentLoader: DocumentLoader? = null var walletCache: MutableMap = ConcurrentHashMap() var ttlMillis: Long = 30 * 60 * 1000