mirror of
https://github.com/oxen-io/session-android.git
synced 2025-02-20 00:18:26 +00:00
feat: re-add bencode utility and fix tests to use bytearray instead of assuming utf-8 encoding for strings
This commit is contained in:
parent
7762d534bb
commit
d2e80c3157
@ -19,11 +19,16 @@ import okhttp3.RequestBody
|
|||||||
import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI
|
import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI
|
||||||
import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionRequest
|
import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionRequest
|
||||||
import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionResponse
|
import org.session.libsession.messaging.sending_receiving.notifications.SubscriptionResponse
|
||||||
|
import org.session.libsession.messaging.utilities.SodiumUtilities
|
||||||
import org.session.libsession.snode.OnionRequestAPI
|
import org.session.libsession.snode.OnionRequestAPI
|
||||||
import org.session.libsession.snode.SnodeAPI
|
import org.session.libsession.snode.SnodeAPI
|
||||||
import org.session.libsession.snode.Version
|
import org.session.libsession.snode.Version
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.getLocalNumber
|
import org.session.libsession.utilities.TextSecurePreferences.Companion.getLocalNumber
|
||||||
|
import org.session.libsession.utilities.bencode.Bencode
|
||||||
|
import org.session.libsession.utilities.bencode.BencodeDict
|
||||||
|
import org.session.libsession.utilities.bencode.BencodeList
|
||||||
|
import org.session.libsession.utilities.bencode.BencodeString
|
||||||
import org.session.libsignal.utilities.Base64
|
import org.session.libsignal.utilities.Base64
|
||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
import org.session.libsignal.utilities.Namespace
|
import org.session.libsignal.utilities.Namespace
|
||||||
@ -55,6 +60,37 @@ class FirebasePushManager(private val context: Context, private val prefs: TextS
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun decrypt(encPayload: ByteArray) {
|
||||||
|
val encKey = getOrCreateNotificationKey()
|
||||||
|
val nonce = encPayload.take(AEAD.XCHACHA20POLY1305_IETF_NPUBBYTES).toByteArray()
|
||||||
|
val payload = encPayload.drop(AEAD.XCHACHA20POLY1305_IETF_NPUBBYTES).toByteArray()
|
||||||
|
val decrypted = SodiumUtilities.decrypt(payload, encKey.asBytes, nonce)
|
||||||
|
?: return Log.e("Loki", "Failed to decrypt push notification")
|
||||||
|
val bencoded = Bencode.Decoder(decrypted)
|
||||||
|
val expectedList = (bencoded.decode() as? BencodeList)
|
||||||
|
?: return Log.e("Loki", "Failed to decode bencoded list from payload")
|
||||||
|
|
||||||
|
val (metadata, content) = expectedList.values
|
||||||
|
val metadataDict = (metadata as? BencodeDict)?.values
|
||||||
|
?: return Log.e("Loki", "Failed to decode metadata dict")
|
||||||
|
|
||||||
|
val push = """
|
||||||
|
Push metadata received was:
|
||||||
|
@: ${metadataDict["@"]}
|
||||||
|
#: ${metadataDict["#"]}
|
||||||
|
n: ${metadataDict["n"]}
|
||||||
|
l: ${metadataDict["l"]}
|
||||||
|
B: ${metadataDict["B"]}
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
Log.d("Loki", "push")
|
||||||
|
|
||||||
|
val contentBytes = (content as? BencodeString)?.value
|
||||||
|
?: return Log.e("Loki", "Failed to decode content string")
|
||||||
|
|
||||||
|
// TODO: something with contentBytes
|
||||||
|
}
|
||||||
|
|
||||||
override fun register(force: Boolean) {
|
override fun register(force: Boolean) {
|
||||||
val currentInstanceIdJob = firebaseInstanceIdJob
|
val currentInstanceIdJob = firebaseInstanceIdJob
|
||||||
if (currentInstanceIdJob != null && currentInstanceIdJob.isActive && !force) return
|
if (currentInstanceIdJob != null && currentInstanceIdJob.isActive && !force) return
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package org.thoughtcrime.securesms.notifications
|
package org.thoughtcrime.securesms.notifications
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import dagger.Binds
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
@ -17,5 +18,12 @@ object FirebasePushModule {
|
|||||||
fun provideFirebasePushManager(
|
fun provideFirebasePushManager(
|
||||||
@ApplicationContext context: Context,
|
@ApplicationContext context: Context,
|
||||||
prefs: TextSecurePreferences,
|
prefs: TextSecurePreferences,
|
||||||
): PushManager = FirebasePushManager(context, prefs)
|
) = FirebasePushManager(context, prefs)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
abstract class FirebaseBindingModule {
|
||||||
|
@Binds
|
||||||
|
abstract fun bindPushManager(firebasePushManager: FirebasePushManager): PushManager
|
||||||
}
|
}
|
@ -17,7 +17,7 @@ import javax.inject.Inject
|
|||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class PushNotificationService : FirebaseMessagingService() {
|
class PushNotificationService : FirebaseMessagingService() {
|
||||||
|
|
||||||
@Inject lateinit var pushManager: PushManager
|
@Inject lateinit var pushManager: FirebasePushManager
|
||||||
|
|
||||||
override fun onNewToken(token: String) {
|
override fun onNewToken(token: String) {
|
||||||
super.onNewToken(token)
|
super.onNewToken(token)
|
||||||
@ -32,7 +32,7 @@ class PushNotificationService : FirebaseMessagingService() {
|
|||||||
// assume this is the new push notification content
|
// assume this is the new push notification content
|
||||||
// deal with the enc payload (probably decrypting through the PushManager?
|
// deal with the enc payload (probably decrypting through the PushManager?
|
||||||
Log.d("Loki", "TODO: deal with the enc_payload\n${message.data["enc_payload"]}")
|
Log.d("Loki", "TODO: deal with the enc_payload\n${message.data["enc_payload"]}")
|
||||||
pushManager.decrypt(message.data)
|
pushManager.decrypt(Base64.decode(message.data["enc_payload"]))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val base64EncodedData = message.data?.get("ENCRYPTED_DATA")
|
val base64EncodedData = message.data?.get("ENCRYPTED_DATA")
|
||||||
|
@ -0,0 +1,169 @@
|
|||||||
|
package org.session.libsession.utilities.bencode
|
||||||
|
|
||||||
|
import java.util.LinkedList
|
||||||
|
|
||||||
|
object Bencode {
|
||||||
|
class Decoder(source: ByteArray) {
|
||||||
|
|
||||||
|
private val iterator = LinkedList<Byte>().apply {
|
||||||
|
addAll(source.asIterable())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode an element based on next marker assumed to be string/int/list/dict or return null
|
||||||
|
*/
|
||||||
|
fun decode(): BencodeElement? {
|
||||||
|
val result = when (iterator.peek()?.toInt()?.toChar()) {
|
||||||
|
in NUMBERS -> decodeString()
|
||||||
|
INT_INDICATOR -> decodeInt()
|
||||||
|
LIST_INDICATOR -> decodeList()
|
||||||
|
DICT_INDICATOR -> decodeDict()
|
||||||
|
else -> {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a string element from iterator assumed to have structure `{length}:{data}`
|
||||||
|
*/
|
||||||
|
private fun decodeString(): BencodeString? {
|
||||||
|
val lengthStrings = buildString {
|
||||||
|
while (iterator.isNotEmpty() && iterator.peek()?.toInt()?.toChar() != SEPARATOR) {
|
||||||
|
append(iterator.pop().toInt().toChar())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
iterator.pop() // drop `:`
|
||||||
|
val length = lengthStrings.toIntOrNull(10) ?: return null
|
||||||
|
val remaining = (0 until length).map { iterator.pop() }.toByteArray()
|
||||||
|
return BencodeString(remaining)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode an int element from iterator assumed to have structure `i{int}e`
|
||||||
|
*/
|
||||||
|
private fun decodeInt(): BencodeElement? {
|
||||||
|
iterator.pop() // drop `i`
|
||||||
|
val intString = buildString {
|
||||||
|
while (iterator.isNotEmpty() && iterator.peek()?.toInt()?.toChar() != END_INDICATOR) {
|
||||||
|
append(iterator.pop().toInt().toChar())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val asInt = intString.toIntOrNull(10) ?: return null
|
||||||
|
iterator.pop() // drop `e`
|
||||||
|
return BencodeInteger(asInt)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a list element from iterator assumed to have structure `l{data}e`
|
||||||
|
*/
|
||||||
|
private fun decodeList(): BencodeElement {
|
||||||
|
iterator.pop() // drop `l`
|
||||||
|
val listElements = mutableListOf<BencodeElement>()
|
||||||
|
while (iterator.isNotEmpty() && iterator.peek()?.toInt()?.toChar() != END_INDICATOR) {
|
||||||
|
decode()?.let { nextElement ->
|
||||||
|
listElements += nextElement
|
||||||
|
}
|
||||||
|
}
|
||||||
|
iterator.pop() // drop `e`
|
||||||
|
return BencodeList(listElements)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a dict element from iterator assumed to have structure `d{data}e`
|
||||||
|
*/
|
||||||
|
private fun decodeDict(): BencodeElement? {
|
||||||
|
iterator.pop() // drop `d`
|
||||||
|
val dictElements = mutableMapOf<String,BencodeElement>()
|
||||||
|
while (iterator.isNotEmpty() && iterator.peek()?.toInt()?.toChar() != END_INDICATOR) {
|
||||||
|
val key = decodeString() ?: return null
|
||||||
|
val value = decode() ?: return null
|
||||||
|
dictElements += key.value.decodeToString() to value
|
||||||
|
}
|
||||||
|
iterator.pop() // drop `e`
|
||||||
|
return BencodeDict(dictElements)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val NUMBERS = arrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9')
|
||||||
|
private const val INT_INDICATOR = 'i'
|
||||||
|
private const val LIST_INDICATOR = 'l'
|
||||||
|
private const val DICT_INDICATOR = 'd'
|
||||||
|
private const val END_INDICATOR = 'e'
|
||||||
|
private const val SEPARATOR = ':'
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class BencodeElement {
|
||||||
|
abstract fun encode(): ByteArray
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.bencode() = BencodeString(this.encodeToByteArray())
|
||||||
|
fun Int.bencode() = BencodeInteger(this)
|
||||||
|
|
||||||
|
data class BencodeString(val value: ByteArray): BencodeElement() {
|
||||||
|
override fun encode(): ByteArray = buildString {
|
||||||
|
append(value.size.toString())
|
||||||
|
append(':')
|
||||||
|
}.toByteArray() + value
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
|
other as BencodeString
|
||||||
|
|
||||||
|
if (!value.contentEquals(other.value)) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return value.contentHashCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data class BencodeInteger(val value: Int): BencodeElement() {
|
||||||
|
override fun encode(): ByteArray = buildString {
|
||||||
|
append('i')
|
||||||
|
append(value.toString())
|
||||||
|
append('e')
|
||||||
|
}.toByteArray()
|
||||||
|
}
|
||||||
|
data class BencodeList(val values: List<BencodeElement>): BencodeElement() {
|
||||||
|
|
||||||
|
constructor(vararg values: BencodeElement) : this(values.toList())
|
||||||
|
|
||||||
|
override fun encode(): ByteArray = "l".toByteArray() +
|
||||||
|
values.fold(byteArrayOf()) { array, element -> array + element.encode() } +
|
||||||
|
"e".toByteArray()
|
||||||
|
}
|
||||||
|
data class BencodeDict(val values: Map<String, BencodeElement>): BencodeElement() {
|
||||||
|
|
||||||
|
constructor(vararg values: Pair<String, BencodeElement>) : this(values.toMap())
|
||||||
|
|
||||||
|
override fun encode(): ByteArray = "d".toByteArray() +
|
||||||
|
values.entries.fold(byteArrayOf()) { array, (key, value) ->
|
||||||
|
array + key.bencode().encode() + value.encode()
|
||||||
|
} + "e".toByteArray()
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
|
other as BencodeDict
|
||||||
|
|
||||||
|
if (values != other.values) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return values.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,107 @@
|
|||||||
|
package org.session.libsession.utilities
|
||||||
|
|
||||||
|
import org.junit.Assert.assertArrayEquals
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Test
|
||||||
|
import org.session.libsession.utilities.bencode.Bencode
|
||||||
|
import org.session.libsession.utilities.bencode.BencodeDict
|
||||||
|
import org.session.libsession.utilities.bencode.BencodeInteger
|
||||||
|
import org.session.libsession.utilities.bencode.BencodeList
|
||||||
|
import org.session.libsession.utilities.bencode.bencode
|
||||||
|
|
||||||
|
class BencoderTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `it should decode a basic string`() {
|
||||||
|
val basicString = "5:howdy".toByteArray()
|
||||||
|
val bencoder = Bencode.Decoder(basicString)
|
||||||
|
val result = bencoder.decode()
|
||||||
|
assertEquals("howdy".bencode(), result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `it should decode a basic integer`() {
|
||||||
|
val basicInteger = "i3e".toByteArray()
|
||||||
|
val bencoder = Bencode.Decoder(basicInteger)
|
||||||
|
val result = bencoder.decode()
|
||||||
|
assertEquals(BencodeInteger(3), result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `it should decode a list of integers`() {
|
||||||
|
val basicIntList = "li1ei2ee".toByteArray()
|
||||||
|
val bencoder = Bencode.Decoder(basicIntList)
|
||||||
|
val result = bencoder.decode()
|
||||||
|
assertEquals(
|
||||||
|
BencodeList(
|
||||||
|
1.bencode(),
|
||||||
|
2.bencode()
|
||||||
|
),
|
||||||
|
result
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `it should decode a basic dict`() {
|
||||||
|
val basicDict = "d4:spaml1:a1:bee".toByteArray()
|
||||||
|
val bencoder = Bencode.Decoder(basicDict)
|
||||||
|
val result = bencoder.decode()
|
||||||
|
assertEquals(
|
||||||
|
BencodeDict(
|
||||||
|
"spam" to BencodeList(
|
||||||
|
"a".bencode(),
|
||||||
|
"b".bencode()
|
||||||
|
)
|
||||||
|
),
|
||||||
|
result
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `it should encode a basic string`() {
|
||||||
|
val basicString = "5:howdy".toByteArray()
|
||||||
|
val element = "howdy".bencode()
|
||||||
|
assertArrayEquals(basicString, element.encode())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `it should encode a basic int`() {
|
||||||
|
val basicInt = "i3e".toByteArray()
|
||||||
|
val element = 3.bencode()
|
||||||
|
assertArrayEquals(basicInt, element.encode())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `it should encode a basic list`() {
|
||||||
|
val basicList = "li1ei2ee".toByteArray()
|
||||||
|
val element = BencodeList(1.bencode(),2.bencode())
|
||||||
|
assertArrayEquals(basicList, element.encode())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `it should encode a basic dict`() {
|
||||||
|
val basicDict = "d4:spaml1:a1:bee".toByteArray()
|
||||||
|
val element = BencodeDict(
|
||||||
|
"spam" to BencodeList(
|
||||||
|
"a".bencode(),
|
||||||
|
"b".bencode()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertArrayEquals(basicDict, element.encode())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `it should encode a more complex real world case`() {
|
||||||
|
val source = "d15:lastReadMessaged66:031122334455667788990011223344556677889900112233445566778899001122i1234568790e66:051122334455667788990011223344556677889900112233445566778899001122i1234568790ee5:seqNoi1ee".toByteArray()
|
||||||
|
val result = Bencode.Decoder(source).decode()
|
||||||
|
val expected = BencodeDict(
|
||||||
|
"lastReadMessage" to BencodeDict(
|
||||||
|
"051122334455667788990011223344556677889900112233445566778899001122" to 1234568790.bencode(),
|
||||||
|
"031122334455667788990011223344556677889900112233445566778899001122" to 1234568790.bencode()
|
||||||
|
),
|
||||||
|
"seqNo" to BencodeInteger(1)
|
||||||
|
)
|
||||||
|
assertEquals(expected, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user