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

Fixes #5341: Add UUID generation and use it on session ID and user profile identifier #5552

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class LoggingIdentifierController @Inject constructor(
private val appSessionIdRandom by lazy { Random(appSessionRandomSeed) }

private val sessionId by lazy { MutableStateFlow(computeSessionId()) }
private val appSessionId by lazy { MutableStateFlow(computeAppSessionId()) }
private val appSessionId by lazy { MutableStateFlow(generateCustomUUID().toString()) }
private val sessionIdDataProvider by lazy {
dataProviders.run { sessionId.convertToAutomaticDataProvider(SESSION_ID_DATA_PROVIDER_ID) }
}
Expand Down Expand Up @@ -166,4 +166,30 @@ class LoggingIdentifierController @Inject constructor(
*/
private fun Random.randomUuid(): UUID =
UUID.nameUUIDFromBytes(ByteArray(16).also { [email protected](it) })

/**
* Returns a new [UUID] as [ULong] using a custom algorithm derived from Twitter's snowflake algorithm.
*
* The [UUID] is made up of;
* - 1 bit - This will cater for the Year 2038 problem.
* - 41 bits - Timestamp representation of the UTC time of generation of the UUID.
* - 22 bits - Random number to increase the uniqueness of the UUID generated.
*
* Return type is [ULong] to take full advantage of all the bits and avoid generating
* negative [UUID]s
*/
fun generateCustomUUID(): ULong {
val SIGNED_BIT = 1L
val TIMESTAMP_BITS = 41
val RANDOM_BITS = 22
val MAX_RANDOM_VALUE: Long = (1L shl RANDOM_BITS) - 1

val currentTimestamp = System.currentTimeMillis()
val randomNumber = Random().nextLong() and MAX_RANDOM_VALUE

return (
(SIGNED_BIT shl (TIMESTAMP_BITS + RANDOM_BITS))
or (currentTimestamp shl RANDOM_BITS) or randomNumber
).toULong()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,9 @@ class ApplicationLifecycleObserver @Inject constructor(
CoroutineScope(backgroundDispatcher).launch {
// TODO(#5341): Replace appSessionId generation to the modified Twitter snowflake algorithm.
val appSessionId = loggingIdentifierController.getAppSessionIdFlow().value
featureFlagsLogger.logAllFeatureFlags(appSessionId)
val profileId = profileManagementController.fetchCurrentProfileUuid()

featureFlagsLogger.logAllFeatureFlags(appSessionId, profileId)
}.invokeOnCompletion { failure ->
if (failure != null) {
oppiaLogger.e(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ class FeatureFlagsLogger @Inject constructor(
*
* @param appSessionId denotes the id of the current appInForeground session
*/
fun logAllFeatureFlags(appSessionId: String) {
fun logAllFeatureFlags(appSessionId: String, currentProfileUuid: String?) {
val featureFlagItemList = mutableListOf<FeatureFlagItemContext>()
for (flag in featureFlagItemMap) {
featureFlagItemList.add(
Expand All @@ -117,6 +117,7 @@ class FeatureFlagsLogger @Inject constructor(
// TODO(#5341): Set the UUID value for this context
val featureFlagContext = FeatureFlagListContext.newBuilder()
.setAppSessionId(appSessionId)
.setUniqueUserUuid(currentProfileUuid)
.addAllFeatureFlags(featureFlagItemList)
.build()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ class ProfileManagementController @Inject constructor(
this.allowDownloadAccess = allowDownloadAccess
this.allowInLessonQuickLanguageSwitching = allowInLessonQuickLanguageSwitching
this.id = ProfileId.newBuilder().setInternalId(nextProfileId).build()
this.uuid = loggingIdentifierController.generateCustomUUID().toString()
dateCreatedTimestampMs = oppiaClock.getCurrentTimeMs()
this.isAdmin = isAdmin
readingTextSize = ReadingTextSize.MEDIUM_TEXT_SIZE
Expand Down Expand Up @@ -918,6 +919,49 @@ class ProfileManagementController @Inject constructor(
*/
suspend fun fetchCurrentLearnerId(): String? = getCurrentProfileId()?.let { fetchLearnerId(it) }

suspend fun fetchCurrentProfileUuid(): String {
val profileId = getCurrentProfileId()
val profileDatabase = profileDataStore.readDataAsync().await()

return if (profileDatabase.profilesMap[profileId?.internalId]?.uuid != null) {
profileDatabase.profilesMap[profileId!!.internalId]!!.uuid!!
} else {
if (profileId == null) return ""

val randomUuid = loggingIdentifierController.generateCustomUUID().toString()
updateCurrentProfileUuid(profileId, randomUuid)
randomUuid
}
}

/**
* Updates the UUID of an existing profile.
*
* @param profileId the ID corresponding to the profile being updated.
* @param newUuid New UUID for the profile being updated.
* @return a [DataProvider] that indicates the success/failure of this update operation.
*/
private fun updateCurrentProfileUuid(profileId: ProfileId, newUuid: String): DataProvider<Any?> {
val deferred = profileDataStore.storeDataWithCustomChannelAsync(
updateInMemoryCache = true
) {
val profile =
it.profilesMap[profileId.internalId] ?: return@storeDataWithCustomChannelAsync Pair(
it,
ProfileActionStatus.PROFILE_NOT_FOUND
)
val updatedProfile = profile.toBuilder().setUuid(newUuid).build()
val profileDatabaseBuilder = it.toBuilder().putProfiles(
profileId.internalId,
updatedProfile
)
Pair(profileDatabaseBuilder.build(), ProfileActionStatus.SUCCESS)
}
return dataProviders.createInMemoryDataProviderAsync(UPDATE_NAME_PROVIDER_ID) {
return@createInMemoryDataProviderAsync getDeferredResult(profileId, newUuid, deferred)
}
}

/**
* Returns the learner ID corresponding to the specified [profileId], or null if the specified
* profile doesn't exist.
Expand Down
3 changes: 3 additions & 0 deletions model/src/main/proto/profile.proto
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ message Profile {
// Unique ID given to each profile.
ProfileId id = 1;

// Unique 64-bit UUID assigned to each profile
string uuid = 20;

// Name of the user.
string name = 2;

Expand Down
Loading