music: strengthen auxio uids

Strengthen Auxio-style UIDs.

These UIDs now leverage SHA-256 hashes with null values now writing
themselves as 0 in order to avoid possible message collissions from
other value arrangements.
This commit is contained in:
Alexander Capehart 2022-12-25 20:32:35 -07:00
parent 9cf8d54353
commit cc8f429044
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
3 changed files with 33 additions and 29 deletions

View file

@ -61,8 +61,9 @@ class DetailViewModel(application: Application) :
private val _currentSong = MutableStateFlow<DetailSong?>(null) private val _currentSong = MutableStateFlow<DetailSong?>(null)
/** /**
* The current [DetailSong] to display. Null if there is nothing to show. TODO: De-couple Song * The current [DetailSong] to display. Null if there is nothing to show.
* and Properties? *
* TODO: De-couple Song and Properties?
*/ */
val currentSong: StateFlow<DetailSong?> val currentSong: StateFlow<DetailSong?>
get() = _currentSong get() = _currentSong

View file

@ -191,9 +191,9 @@ sealed class Music : Item {
// tags in a music item. For easier use with MusicBrainz IDs, we transform // tags in a music item. For easier use with MusicBrainz IDs, we transform
// this into a UUID too. // this into a UUID too.
val uuid = val uuid =
MessageDigest.getInstance("MD5").run { MessageDigest.getInstance("SHA-256").run {
updates() updates()
digest().toUuid() digestUUID()
} }
return UID(Format.AUXIO, mode, uuid) return UID(Format.AUXIO, mode, uuid)
@ -1369,8 +1369,6 @@ class Date private constructor(private val tokens: List<Int>) : Comparable<Date>
// --- MUSIC UID CREATION UTILITIES --- // --- MUSIC UID CREATION UTILITIES ---
// TODO: Use a stronger hash (SHA-2?, append a 0 for null values to further waterfall effect??)
/** /**
* Update a [MessageDigest] with a lowercase [String]. * Update a [MessageDigest] with a lowercase [String].
* @param string The [String] to hash. If null, it will not be hashed. * @param string The [String] to hash. If null, it will not be hashed.
@ -1378,6 +1376,8 @@ class Date private constructor(private val tokens: List<Int>) : Comparable<Date>
private fun MessageDigest.update(string: String?) { private fun MessageDigest.update(string: String?) {
if (string != null) { if (string != null) {
update(string.lowercase().toByteArray()) update(string.lowercase().toByteArray())
} else {
update(0)
} }
} }
@ -1388,6 +1388,8 @@ private fun MessageDigest.update(string: String?) {
private fun MessageDigest.update(date: Date?) { private fun MessageDigest.update(date: Date?) {
if (date != null) { if (date != null) {
update(date.toString().toByteArray()) update(date.toString().toByteArray())
}else {
update(0)
} }
} }
@ -1406,35 +1408,36 @@ private fun MessageDigest.update(strings: List<String?>) {
private fun MessageDigest.update(n: Int?) { private fun MessageDigest.update(n: Int?) {
if (n != null) { if (n != null) {
update(byteArrayOf(n.toByte(), n.shr(8).toByte(), n.shr(16).toByte(), n.shr(24).toByte())) update(byteArrayOf(n.toByte(), n.shr(8).toByte(), n.shr(16).toByte(), n.shr(24).toByte()))
}else {
update(0)
} }
} }
/** /**
* Convert a [ByteArray] to a [UUID]. Assumes that the [ByteArray] has a length of 16. * Digest a 8-byte+ hash into a [UUID].
* @return A [UUID] derived from the [ByteArray]'s contents. Internally, the two [Long]s in the * @return A [UUID] derived from the first 8 bytes of the digest.
* [UUID] will be little-endian.
*/ */
fun ByteArray.toUuid(): UUID { fun MessageDigest.digestUUID(): UUID {
check(size == 16) val digest = digest()
return UUID( return UUID(
get(0) digest[0]
.toLong() .toLong()
.shl(56) .shl(56)
.or(get(1).toLong().and(0xFF).shl(48)) .or(digest[1].toLong().and(0xFF).shl(48))
.or(get(2).toLong().and(0xFF).shl(40)) .or(digest[2].toLong().and(0xFF).shl(40))
.or(get(3).toLong().and(0xFF).shl(32)) .or(digest[3].toLong().and(0xFF).shl(32))
.or(get(4).toLong().and(0xFF).shl(24)) .or(digest[4].toLong().and(0xFF).shl(24))
.or(get(5).toLong().and(0xFF).shl(16)) .or(digest[5].toLong().and(0xFF).shl(16))
.or(get(6).toLong().and(0xFF).shl(8)) .or(digest[6].toLong().and(0xFF).shl(8))
.or(get(7).toLong().and(0xFF)), .or(digest[7].toLong().and(0xFF)),
get(8) digest[8]
.toLong() .toLong()
.shl(56) .shl(56)
.or(get(9).toLong().and(0xFF).shl(48)) .or(digest[9].toLong().and(0xFF).shl(48))
.or(get(10).toLong().and(0xFF).shl(40)) .or(digest[10].toLong().and(0xFF).shl(40))
.or(get(11).toLong().and(0xFF).shl(32)) .or(digest[11].toLong().and(0xFF).shl(32))
.or(get(12).toLong().and(0xFF).shl(24)) .or(digest[12].toLong().and(0xFF).shl(24))
.or(get(13).toLong().and(0xFF).shl(16)) .or(digest[13].toLong().and(0xFF).shl(16))
.or(get(14).toLong().and(0xFF).shl(8)) .or(digest[14].toLong().and(0xFF).shl(8))
.or(get(15).toLong().and(0xFF))) .or(digest[15].toLong().and(0xFF)))
} }

View file

@ -76,11 +76,11 @@ abstract class ViewBindingFragment<VB : ViewBinding> : Fragment() {
} }
/** /**
* Delegate to automatically create and destroy an object derived from the [ViewBinding]. TODO: * Delegate to automatically create and destroy an object derived from the [ViewBinding].
* Phase this out, it's really dumb
* @param create Block to create the object from the [ViewBinding]. * @param create Block to create the object from the [ViewBinding].
*/ */
fun <T> lifecycleObject(create: (VB) -> T): ReadOnlyProperty<Fragment, T> { fun <T> lifecycleObject(create: (VB) -> T): ReadOnlyProperty<Fragment, T> {
// TODO: Phase this out.
lifecycleObjects.add(LifecycleObject(null, create)) lifecycleObjects.add(LifecycleObject(null, create))
return object : ReadOnlyProperty<Fragment, T> { return object : ReadOnlyProperty<Fragment, T> {