Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Converts storage and secure area to asynchronous APIs #856

Merged
merged 1 commit into from
Jan 30, 2025

Conversation

sorotokin
Copy link
Contributor

@sorotokin sorotokin commented Jan 28, 2025

Moves to new Storage API and propagates asynchronous ("suspend") function signatures through the existing code.

We will need to define concurrency model for DocumentStore/Document/Credential APIs in future PRs.

Also adjusts unit tests to use new APIs

@sorotokin sorotokin requested a review from kdeus January 28, 2025 02:16
* obtain a [DocumentStore] lock by running the code that accesses these APIs inside
* [DocumentStore.withLock] (or a convenience shortcut [Document.withLock]). Note that
* there is a single lock for all documents and credentials in a [DocumentStore]
* instance and only a single coroutine can hold the lock at the same time.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have to look at this in more detail but my gut feeling is that it's not appropriate to ask API callers to obtain a lock before calling an API. And it's also not very common to have this requirement. I don't think we should require this, it's a bit of an anti-pattern.

The ordinary way this is done - and the way I thought we'd do it when you mentioned a DocumentStore-wide lock - is that the API entry-points the app calls will take the lock. Is there a specific reason we can't do that instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't disagree with the general gist of it, but there is a lot of nuance here. Let's move this to the chat.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, removed the locking altogether for now. We'll need to figure out how to do it later.

@sorotokin sorotokin force-pushed the migrate-storage branch 5 times, most recently from e2769df to 46a0dbf Compare January 29, 2025 05:11
Copy link
Contributor

@davidz25 davidz25 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did a read-through. Looks great and thanks for all your hard work on this! Comments included but my only real question I have is that we're not entirely consistent with asynchronous object creation...

In some cases the constructors are still public and for the Credential hierarchy you expect the user to call the constructor and then a suspend function after that. I think in general we should move to suspend create() on the Companion object and in the case of multiple constructors (as e.g. Credential), they should be suspend createFoo() and suspend createBar() functions on the Companion object, I think. It would be good if that were made more consistent...

private const val METADATA_PREFIX = "@"

/**
* Creates an instance of AndroidKeystoreSecureArea.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ITYM CloudSecureArea here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -444,6 +447,8 @@ class DeviceRetrievalHelper internal constructor(
* The [Listener.onError] callback can be called at any time - for
* example - if the remote verifier disconnects without using session termination or if the
* underlying transport encounters an unrecoverable error.
*
* TODO: consider redoing this API through flows.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, DeviceRetrievalHelper is now replaced by PresentmentModel and MdocTransport which both use flows. The idea is to deprecate all of identity-android.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, removed that TODO

@@ -454,7 +459,7 @@ class DeviceRetrievalHelper internal constructor(
*
* @param eReaderKey the ephemeral reader key.
*/
fun onEReaderKeyReceived(eReaderKey: EcPublicKey)
suspend fun onEReaderKeyReceived(eReaderKey: EcPublicKey)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not convinced we need to make these suspend. What's the rationale?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is going away, I'll put it back the way it was. I'll just need to add runBlocking in one place.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's going to be deprecated and will be moved to multipaz-legacy library with the rest of identity-android and identity-android-legacy. We'll keep using it in wallet, other downstreams may keep using it, so it needs to keep working.

I think runBlocking before invoking this will be fine. The listener implementations are not supposed to be doing any blocking I/O anyway and I don't think any of them do.

*/
suspend fun create(context: Context, storage: Storage): AndroidKeystoreSecureArea {
return AndroidKeystoreSecureArea(context, storage.getTable(tableSpec))
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A separate create() function is needed b/c constructors cannot be suspend?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, storage.getTable(tableSpec) is asynchronous

@@ -102,13 +105,15 @@ import java.io.ByteArrayInputStream
* what the device supports.
*
* This implementation works only on Android and requires API level 24 or later.
*
* Use [AndroidKeystoreSecureArea.create] to create an instance of this class.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make the constructor private or protected? Seems like that would force users to use create()...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is an oversight.

*/
fun createKey(alias: String, createKeySettings: CreateKeySettings)
suspend fun createKey(alias: String?, createKeySettings: CreateKeySettings): String
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually we want this to return KeyInfo in the future.... maybe add alias to KeyInfo and return KeyInfo now? I think that's only a few lines of code...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK

* requested URL and configure the instance as needed. Key to the map is Secure Area
* identifier prefix of the Secure Area identifier up until the '?' character.
* @param factoryFunc a function to create a new Secure Area with the requested identifier.
*
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment needs both formatting changes (two empty lines, "can" should start with a capital) and also references now non-existant factoryFunc

builder.block()
builder.build()
})
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a non-standard builder pattern... do we even need a builder-ish thing here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pretty standard Kotlin DSL... And what it does is trickier than it seems:

// This builds SecureAreaRepository *synchronously*, can be called in static initializers
val r = SecureAreaRepository.build {
   // *asynchronous code* that does actual building
   add(...)
   addImplementation(...)
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, just checking, I'm pretty rusty when it comes to DSLs in Kotlin. Fine with me!

* @param docType the docType of the credential
*
* [SecureAreaBoundCredential.generateKey] must be called before using this object.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So you have this requirement b/c constructors can't be suspend. How about making the constructor private/protected and have a static suspend create() instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is the same issue as in SecureAreaBoundCredential. I agree that we want something like this, but I think these APIs probably should be designed for concurrency/locking from grounds up.

@@ -38,38 +38,46 @@ open class SecureAreaBoundCredential : Credential {
/**
* Constructs a new [SecureAreaBoundCredential].
*
* [generateKey] providing [CreateKeySettings] must be called before using this object.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as with MdocCredential, make the constructor private and have a static suspend create() that plays the role of a suspend constructor.

Copy link
Contributor

@davidz25 davidz25 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed, LGTM to merge now and we can work on addressing concurrency/locking and API for DocumentStore, Document, and Credential in separate PRs. Cc @kdeus

@kdeus
Copy link
Contributor

kdeus commented Jan 30, 2025

As discussed, LGTM to merge now and we can work on addressing concurrency/locking and API for DocumentStore, Document, and Credential in separate PRs. Cc @kdeus

I've done a few spot checks, not a thorough review.
+1 to merge now, fix later.

@sorotokin sorotokin merged commit bb9ea05 into main Jan 30, 2025
5 checks passed
@sorotokin sorotokin deleted the migrate-storage branch January 30, 2025 06:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants