Skip to content

Refactor LCP to remove koi and joda#780

Merged
qnga merged 9 commits into
readium:developfrom
stevenzeck:refactor/lcp-hash-and-dates
May 21, 2026
Merged

Refactor LCP to remove koi and joda#780
qnga merged 9 commits into
readium:developfrom
stevenzeck:refactor/lcp-hash-and-dates

Conversation

@stevenzeck
Copy link
Copy Markdown
Contributor

@stevenzeck stevenzeck commented Apr 26, 2026

  1. Removes koi in favor of MessageDigest for hashing the passphrase.
  2. Removes joda-time in favor of Kotlin Instant.
  3. Refactored to remove warnings and weak warnings from CRLService (constants being all uppercase, and using ktx for preferences.edit).
  4. Added tests.

Relates to #779.

@qnga
Copy link
Copy Markdown
Member

qnga commented Apr 28, 2026

Instant is now available in the stdlib. I guess it was not at the time of #506 @mickael-menu? I suggest to remove the Readium Instant type and privately use kotlinx-datetime for conversion operations. This PR introduces kotlinx-datetime in the public API, which partially defeats the initial purpose.

@mickael-menu mickael-menu requested a review from Copilot April 29, 2026 16:49
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Refactors the LCP and test-app modules to remove legacy dependencies (koi, joda-time) by replacing hashing and date/time handling with Kotlin/stdlib-based equivalents, and adds regression tests for the updated behavior.

Changes:

  • Replaced koi-based passphrase hashing with MessageDigest-backed SHA-256 utilities and updated PassphrasesService.
  • Replaced joda-time usage in the test app with Kotlin/stdlib time and DateFormat-based formatting.
  • Added unit tests for passphrase hashing behavior and CRL caching/expiration behavior.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
test-app/src/main/java/org/readium/r2/testapp/utils/UserError.kt Swaps joda DateTime formatting for Kotlin time Instant formatting in interpolated error messages.
test-app/src/main/java/org/readium/r2/testapp/outline/HighlightsFragment.kt Replaces joda timestamp formatting with DateFormat + Date.
test-app/src/main/java/org/readium/r2/testapp/outline/BookmarksFragment.kt Replaces joda timestamp formatting with DateFormat + Date.
test-app/src/main/java/org/readium/r2/testapp/drm/DrmManagementFragment.kt Replaces joda date formatting with DateFormat-based helper for nullable Date.
test-app/src/main/java/org/readium/r2/testapp/data/BookRepository.kt Replaces bookmark/book creation timestamps with Clock.System.now().toEpochMilliseconds().
test-app/build.gradle.kts Removes the joda-time dependency from the test app.
readium/lcp/src/main/java/org/readium/r2/lcp/util/Digest.kt Adds SHA-256 helpers for ByteArray and String (hex output) using MessageDigest.
readium/lcp/src/main/java/org/readium/r2/lcp/service/PassphrasesService.kt Updates passphrase hashing to use the new sha256Hex() helper.
readium/lcp/src/main/java/org/readium/r2/lcp/service/CRLService.kt Replaces joda-based CRL expiration tracking with Kotlin time + kotlinx-datetime helpers and SharedPreferences ktx edit.
readium/lcp/src/test/java/org/readium/r2/lcp/service/PassphrasesServiceTest.kt Adds tests asserting passphrases are hashed (or not) as expected before persistence.
readium/lcp/src/test/java/org/readium/r2/lcp/service/CRLServiceTest.kt Adds Robolectric tests for CRL retrieval behavior (local vs network) based on expiration.
readium/lcp/build.gradle.kts Removes koi and joda-time from LCP module; adds kotlinx-datetime.
gradle/libs.versions.toml Removes joda-time version and library entries.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread test-app/src/main/java/org/readium/r2/testapp/utils/UserError.kt
Comment on lines +41 to +45
/**
* Returns the SHA-256 sum of the string encoded as a lowercase hex string.
*/
internal fun String.sha256Hex(): String =
toByteArray().sha256().toHexString()
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

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

sha256Hex() relies on ByteArray.toHexString(), which is treated as an experimental stdlib API elsewhere in the codebase (e.g. LicensesService.checkSha256() opts in to ExperimentalStdlibApi). Without an @OptIn(ExperimentalStdlibApi::class) here (or an alternative stable hex encoder), this will introduce compiler warnings or fail builds configured to treat warnings as errors.

Suggested change
/**
* Returns the SHA-256 sum of the string encoded as a lowercase hex string.
*/
internal fun String.sha256Hex(): String =
toByteArray().sha256().toHexString()
private fun ByteArray.toLowercaseHexString(): String {
val hexChars = "0123456789abcdef".toCharArray()
val result = CharArray(size * 2)
forEachIndexed { index, byte ->
val value = byte.toInt() and 0xFF
result[index * 2] = hexChars[value ushr 4]
result[index * 2 + 1] = hexChars[value and 0x0F]
}
return String(result)
}
/**
* Returns the SHA-256 sum of the string encoded as a lowercase hex string.
*/
internal fun String.sha256Hex(): String =
toByteArray().sha256().toLowercaseHexString()

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

@stevenzeck stevenzeck Apr 29, 2026

Choose a reason for hiding this comment

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

As of Kotlin 2.2, ByteArray.toHexString is stable and does not need an annotation. The one in LicensesService is leftover.

The format of HexFormat.Default in public fun ByteArray.toHexString(format: HexFormat = HexFormat.Default) has upperCase = false. I can update ours to be explicit too: toByteArray().sha256().toHexString(format = HexFormat.Default).

@stevenzeck
Copy link
Copy Markdown
Contributor Author

stevenzeck commented Apr 29, 2026

I addressed Copilot's comments as well as those discussed elsewhere. For now, I'll leave CRLServiceTest to use mockk, as NetworkService will be going away in favor of Shared's HttpClient. Making a fake NetworkService requires a lot more work that isn't worth it for this PR.

Copy link
Copy Markdown
Member

@qnga qnga left a comment

Choose a reason for hiding this comment

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

Sounds good, thanks!

@qnga qnga merged commit 6d89b0f into readium:develop May 21, 2026
4 checks passed
@stevenzeck stevenzeck deleted the refactor/lcp-hash-and-dates branch May 21, 2026 14:19
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