Skip to content

Commit

Permalink
Replace ThreadExecutor with Coroutines (#416)
Browse files Browse the repository at this point in the history
  • Loading branch information
mitrejcevski authored Nov 17, 2023
1 parent 85ef1e2 commit 05a1e96
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 105 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import android.content.Context
import android.graphics.Bitmap
import android.graphics.Color.BLACK
import android.graphics.Color.WHITE
import android.os.Handler
import android.os.Looper
import android.view.View
import android.widget.ImageView
import androidx.lifecycle.LiveData
Expand All @@ -32,8 +30,9 @@ import com.google.zxing.BarcodeFormat
import com.google.zxing.MultiFormatWriter
import com.google.zxing.WriterException
import com.google.zxing.common.BitMatrix
import kotlinx.coroutines.suspendCancellableCoroutine
import java.util.*
import java.util.concurrent.Executors
import kotlin.coroutines.resume

class TransferManager private constructor(private val context: Context) {

Expand Down Expand Up @@ -159,90 +158,82 @@ class TransferManager private constructor(private val context: Context) {
}

@Throws(IllegalStateException::class)
fun addDocumentToResponse(
suspend fun addDocumentToResponse(
credentialName: String,
docType: String,
issuerSignedEntriesToRequest: MutableMap<String, Collection<String>>,
deviceResponseGenerator: DeviceResponseGenerator,
authKey: Credential.AuthenticationKey?,
authKeyUnlockData: SecureArea.KeyUnlockData?,
onResultReady: (result: AddDocumentToResponseResult) -> Unit
) {
// Putting together the response involves a bit of work and also potentially
// blocking I/O. Do all this on a worker thread.
Executors.newSingleThreadExecutor().execute(kotlinx.coroutines.Runnable {
var result: AddDocumentToResponseResult
var signingKeyUsageLimitPassed = false
val documentManager = DocumentManager.getInstance(context)
val documentInformation = documentManager.getDocumentInformation(credentialName)
requireValidProperty(documentInformation) { "Document not found!" }
) = suspendCancellableCoroutine { continuation ->
var result: AddDocumentToResponseResult
var signingKeyUsageLimitPassed = false
val documentManager = DocumentManager.getInstance(context)
val documentInformation = documentManager.getDocumentInformation(credentialName)
requireValidProperty(documentInformation) { "Document not found!" }

val credential = requireNotNull(documentManager.getCredentialByName(credentialName))
val dataElements = issuerSignedEntriesToRequest.keys.flatMap { key ->
issuerSignedEntriesToRequest.getOrDefault(key, emptyList()).map { value ->
CredentialRequest.DataElement(key, value, false)
}
val credential = requireNotNull(documentManager.getCredentialByName(credentialName))
val dataElements = issuerSignedEntriesToRequest.keys.flatMap { key ->
issuerSignedEntriesToRequest.getOrDefault(key, emptyList()).map { value ->
CredentialRequest.DataElement(key, value, false)
}
}

val request = CredentialRequest(dataElements)
val request = CredentialRequest(dataElements)

val authKeyToUse : Credential.AuthenticationKey
if (authKey != null) {
authKeyToUse = authKey
} else {
authKeyToUse = credential.findAuthenticationKey(Timestamp.now())
?: throw IllegalStateException("No auth key available")
}
val authKeyToUse: Credential.AuthenticationKey
if (authKey != null) {
authKeyToUse = authKey
} else {
authKeyToUse = credential.findAuthenticationKey(Timestamp.now())
?: throw IllegalStateException("No auth key available")
}

if (authKeyToUse.usageCount >= documentInformation.maxUsagesPerKey) {
logWarning("Using Auth Key previously used ${authKeyToUse.usageCount} times, and maxUsagesPerKey is ${documentInformation.maxUsagesPerKey}")
signingKeyUsageLimitPassed = true
}
if (authKeyToUse.usageCount >= documentInformation.maxUsagesPerKey) {
logWarning("Using Auth Key previously used ${authKeyToUse.usageCount} times, and maxUsagesPerKey is ${documentInformation.maxUsagesPerKey}")
signingKeyUsageLimitPassed = true
}

val staticAuthData = StaticAuthDataParser(authKeyToUse.issuerProvidedData).parse()
val mergedIssuerNamespaces = MdocUtil.mergeIssuerNamesSpaces(
request,
credential.applicationData.getNameSpacedData("credentialData"),
staticAuthData
)
val staticAuthData = StaticAuthDataParser(authKeyToUse.issuerProvidedData).parse()
val mergedIssuerNamespaces = MdocUtil.mergeIssuerNamesSpaces(
request,
credential.applicationData.getNameSpacedData("credentialData"),
staticAuthData
)

val transcript = communication.getSessionTranscript() ?: byteArrayOf()
val transcript = communication.getSessionTranscript() ?: byteArrayOf()

try {
val generator = DocumentGenerator(docType, staticAuthData.issuerAuth, transcript)
.setIssuerNamespaces(mergedIssuerNamespaces)
val keyInfo = authKeyToUse.secureArea.getKeyInfo(authKeyToUse.alias)
if ((keyInfo.keyPurposes and SecureArea.KEY_PURPOSE_SIGN) != 0) {
generator.setDeviceNamespacesSignature(
NameSpacedData.Builder().build(),
authKeyToUse.secureArea,
authKeyToUse.alias,
authKeyUnlockData,
SecureArea.ALGORITHM_ES256
)
} else {
generator.setDeviceNamespacesMac(
NameSpacedData.Builder().build(),
authKeyToUse.secureArea,
authKeyToUse.alias,
authKeyUnlockData,
communication.deviceRetrievalHelper!!.eReaderKey
)
}
val data = generator.generate()
deviceResponseGenerator.addDocument(data)
log("Increasing usage count on ${authKeyToUse.alias}")
authKeyToUse.increaseUsageCount()
ProvisioningUtil.getInstance(context).trackUsageTimestamp(credential)
result = AddDocumentToResponseResult.DocumentAdded(signingKeyUsageLimitPassed)
} catch (lockedException: SecureArea.KeyLockedException) {
result = AddDocumentToResponseResult.DocumentLocked(authKeyToUse)
}
// Post the result on the UI thread
Handler(Looper.getMainLooper()).post {
onResultReady(result)
try {
val generator = DocumentGenerator(docType, staticAuthData.issuerAuth, transcript)
.setIssuerNamespaces(mergedIssuerNamespaces)
val keyInfo = authKeyToUse.secureArea.getKeyInfo(authKeyToUse.alias)
if ((keyInfo.keyPurposes and SecureArea.KEY_PURPOSE_SIGN) != 0) {
generator.setDeviceNamespacesSignature(
NameSpacedData.Builder().build(),
authKeyToUse.secureArea,
authKeyToUse.alias,
authKeyUnlockData,
SecureArea.ALGORITHM_ES256
)
} else {
generator.setDeviceNamespacesMac(
NameSpacedData.Builder().build(),
authKeyToUse.secureArea,
authKeyToUse.alias,
authKeyUnlockData,
communication.deviceRetrievalHelper!!.eReaderKey
)
}
})
val data = generator.generate()
deviceResponseGenerator.addDocument(data)
log("Increasing usage count on ${authKeyToUse.alias}")
authKeyToUse.increaseUsageCount()
ProvisioningUtil.getInstance(context).trackUsageTimestamp(credential)
result = AddDocumentToResponseResult.DocumentAdded(signingKeyUsageLimitPassed)
} catch (lockedException: SecureArea.KeyLockedException) {
result = AddDocumentToResponseResult.DocumentLocked(authKeyToUse)
}
continuation.resume(result)
}

fun stopPresentation(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ import androidx.databinding.ObservableInt
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.android.identity.util.Constants.DEVICE_RESPONSE_STATUS_OK
import androidx.lifecycle.viewModelScope
import com.android.identity.android.legacy.CredentialInvalidatedException
import com.android.identity.credential.Credential
import com.android.identity.mdoc.response.DeviceResponseGenerator
import com.android.identity.mdoc.request.DeviceRequestParser
import com.android.identity.mdoc.response.DeviceResponseGenerator
import com.android.identity.securearea.SecureArea
import com.android.identity.util.Constants.DEVICE_RESPONSE_STATUS_OK
import com.android.identity.wallet.R
import com.android.identity.wallet.authconfirmation.RequestedDocumentData
import com.android.identity.wallet.authconfirmation.RequestedElement
Expand All @@ -22,9 +23,12 @@ import com.android.identity.wallet.document.DocumentInformation
import com.android.identity.wallet.document.DocumentManager
import com.android.identity.wallet.transfer.AddDocumentToResponseResult
import com.android.identity.wallet.transfer.TransferManager
import com.android.identity.wallet.util.PreferencesHelper
import com.android.identity.wallet.util.TransferStatus
import com.android.identity.wallet.util.logWarning
import com.android.identity.wallet.util.PreferencesHelper
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

class TransferDocumentViewModel(val app: Application) : AndroidViewModel(app) {

Expand Down Expand Up @@ -105,37 +109,44 @@ class TransferDocumentViewModel(val app: Application) : AndroidViewModel(app) {
) {
val elementsToSend = signedElements.collect()
val responseGenerator = DeviceResponseGenerator(DEVICE_RESPONSE_STATUS_OK)
elementsToSend.forEach { signedDocument ->
try {
val issuerSignedEntries = signedDocument.issuerSignedEntries()
transferManager.addDocumentToResponse(
signedDocument.identityCredentialName,
signedDocument.documentType,
issuerSignedEntries,
responseGenerator,
authKey,
authKeyUnlockData,
onResultReady = {
if (it !is AddDocumentToResponseResult.DocumentAdded) {
onResultReady(it)
return@addDocumentToResponse
}
transferManager.sendResponse(responseGenerator.generate(), PreferencesHelper.isConnectionAutoCloseEnabled())
transferManager.setResponseServed()
val documentsCount = elementsToSend.count()
documentsSent.set(app.getString(R.string.txt_documents_sent, documentsCount))
cleanUp()
onResultReady(it)
viewModelScope.launch {
elementsToSend.forEach { signedDocument ->
try {
val issuerSignedEntries = signedDocument.issuerSignedEntries()
val result = withContext(Dispatchers.IO) { //<- Offload from UI thread
transferManager.addDocumentToResponse(
signedDocument.identityCredentialName,
signedDocument.documentType,
issuerSignedEntries,
responseGenerator,
authKey,
authKeyUnlockData,
)
}
)
} catch (e: CredentialInvalidatedException) {
logWarning("Credential '${signedDocument.identityCredentialName}' is invalid. Deleting.")
documentManager.deleteCredentialByName(signedDocument.identityCredentialName)
Toast.makeText(app.applicationContext, "Deleting invalid credential "
+ signedDocument.identityCredentialName,
Toast.LENGTH_SHORT).show()
} catch (e: NoSuchElementException) {
logWarning("No requestedDocument for " + signedDocument.documentType)
if (result !is AddDocumentToResponseResult.DocumentAdded) {
onResultReady(result)
return@forEach
}
transferManager.sendResponse(
responseGenerator.generate(),
PreferencesHelper.isConnectionAutoCloseEnabled()
)
transferManager.setResponseServed()
val documentsCount = elementsToSend.count()
documentsSent.set(app.getString(R.string.txt_documents_sent, documentsCount))
cleanUp()
onResultReady(result)
} catch (e: CredentialInvalidatedException) {
logWarning("Credential '${signedDocument.identityCredentialName}' is invalid. Deleting.")
documentManager.deleteCredentialByName(signedDocument.identityCredentialName)
Toast.makeText(
app.applicationContext, "Deleting invalid credential "
+ signedDocument.identityCredentialName,
Toast.LENGTH_SHORT
).show()
} catch (e: NoSuchElementException) {
logWarning("No requestedDocument for " + signedDocument.documentType)
}
}
}
}
Expand Down

0 comments on commit 05a1e96

Please sign in to comment.