Skip to content

Commit

Permalink
Add initial storage manager
Browse files Browse the repository at this point in the history
Signed-off-by: Tiago Nascimento <[email protected]>
  • Loading branch information
theosirian committed Jun 7, 2024
1 parent 1249a65 commit 4dbeb00
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 0 deletions.
1 change: 1 addition & 0 deletions WalletSdk/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ dependencies {
implementation("com.google.zxing:core:3.3.3")
implementation("com.google.accompanist:accompanist-permissions:0.34.0")
/* End UI dependencies */
implementation("androidx.datastore:datastore-preferences:1.1.1")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("com.android.support.test:runner:1.0.2")
androidTestImplementation("com.android.support.test.espresso:espresso-core:3.0.2")
Expand Down
137 changes: 137 additions & 0 deletions WalletSdk/src/main/java/com/spruceid/wallet/sdk/StorageManager.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import android.content.Context
import android.util.Base64
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.*
import androidx.datastore.preferences.preferencesDataStoreFile
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map

private class DataStoreSingleton private constructor(context: Context) {
val dataStore: DataStore<Preferences> = store(context, "default")

companion object {
private const val FILENAME_PREFIX = "cadmv-datastore_"

private fun location(context: Context, file: String) =
context.preferencesDataStoreFile(FILENAME_PREFIX + file.lowercase())

private fun store(context: Context, file: String): DataStore<Preferences> =
PreferenceDataStoreFactory.create(produceFile = { location(context, file) })

@Volatile
private var instance: DataStoreSingleton? = null

fun getInstance(context: Context) =
instance ?: synchronized(this) {
instance ?: DataStoreSingleton(context).also { instance = it }
}
}
}

object StorageManager {

/// Function: encrypt
///
/// Encrypts the given string.
///
/// Arguments:
/// value - The string value to be encrypted
private fun encrypt(value: String): Result<ByteArray> {
try {
// TODO: Use KeyManager.encrypt or equivalent
val encoded = Base64.encode(
value.toByteArray(), Base64.URL_SAFE xor Base64.NO_PADDING xor Base64.NO_WRAP
)
return Result.success(encoded)
} catch (e: Exception) {
return Result.failure(e)
}
}

/// Function: decrypt
///
/// Decrypts the given byte array.
///
/// Arguments:
/// value - The byte array to be decrypted
private fun decrypt(value: ByteArray): Result<String> {
try {
// TODO: Use KeyManager.encrypt or equivalent
val decoded = Base64.decode(
value, Base64.URL_SAFE xor Base64.NO_PADDING xor Base64.NO_WRAP
)
return Result.success(decoded.decodeToString())
} catch (e: Exception) {
return Result.failure(Exception())
}
}

/// Function: add
///
/// Adds a key-value pair to storage. Should the key already exist, the value will be
/// replaced.
///
/// Arguments:
/// context - The application context to be able to access the DataStore
/// key - The key to add
/// value - The value to add under the key
suspend fun add(context: Context, key: String, value: String): Result<Unit> {
val storeKey = byteArrayPreferencesKey(key)
val storeValue = encrypt(value)

if (storeValue.isFailure) {
return Result.failure(Exception("Failed to encrypt value for storage"))
}

DataStoreSingleton.getInstance(context).dataStore.edit { store ->
store[storeKey] = storeValue.getOrThrow()
}

return Result.success(Unit)
}

/// Function: get
///
/// Retrieves the value from storage identified by key.
///
/// Arguments:
/// context - The application context to be able to access the DataStore
/// key - The key to retrieve
suspend fun get(context: Context, key: String): Result<String?> {
val storeKey = byteArrayPreferencesKey(key)
return DataStoreSingleton.getInstance(context).dataStore.data.map { store ->
try {
store[storeKey]?.let { v ->
val storeValue = decrypt(v)
when {
storeValue.isSuccess -> Result.success(storeValue.getOrThrow())
storeValue.isFailure -> Result.failure(storeValue.exceptionOrNull()!!)
else -> Result.failure(Exception("Failed to decrypt value for storage"))
}
} ?: Result.success(null)
} catch (e: Exception) {
Result.failure(e)
}
}.catch { exception ->
emit(Result.failure(exception))
}.first()
}

/// Function: remove
///
/// Removes a key-value pair from storage by key.
///
/// Arguments:
/// context - The application context to be able to access the DataStore
/// key - The key to remove
suspend fun remove(context: Context, key: String): Result<Unit> {
val storeKey = stringPreferencesKey(key)
DataStoreSingleton.getInstance(context).dataStore.edit { store ->
if (store.contains(storeKey)) {
store.remove(storeKey)
}
}
return Result.success(Unit)
}
}

0 comments on commit 4dbeb00

Please sign in to comment.