Skip to content

Commit f093b88

Browse files
Fix more crashes (#1036)
1 parent 44718c4 commit f093b88

File tree

11 files changed

+170
-31
lines changed

11 files changed

+170
-31
lines changed

app/src/main/java/org/thoughtcrime/securesms/groups/EnterCommunityUrlFragment.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ class EnterCommunityUrlFragment : Fragment() {
6666
groups.iterator().forEach { defaultGroup ->
6767
val chip = layoutInflater.inflate(R.layout.default_group_chip, binding.defaultRoomsFlexboxLayout, false) as Chip
6868
val drawable = defaultGroup.image?.let { bytes ->
69-
val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
69+
val bitmap = BitmapFactory.decodeByteArray(bytes.data, bytes.offset, bytes.len)
7070
RoundedBitmapDrawableFactory.create(resources, bitmap).apply {
7171
isCircular = true
7272
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package org.session.libsignal.utilities
2+
3+
import org.junit.Assert.assertArrayEquals
4+
import org.junit.Assert.assertEquals
5+
import org.junit.Test
6+
import org.session.libsignal.utilities.ByteArraySlice.Companion.view
7+
8+
class ByteArraySliceTest {
9+
@Test
10+
fun `view works`() {
11+
val sliced = byteArrayOf(1, 2, 3, 4, 5).view(1..3)
12+
assertEquals(listOf<Byte>(2, 3, 4), sliced.asList())
13+
}
14+
15+
@Test
16+
fun `re-view works`() {
17+
val sliced = byteArrayOf(1, 2, 3, 4, 5).view(1..3)
18+
val resliced = sliced.view(1..2)
19+
assertEquals(listOf<Byte>(3, 4), resliced.asList())
20+
}
21+
22+
@Test
23+
fun `decodeToString works`() {
24+
assertEquals(
25+
"hel",
26+
"hello, world".toByteArray().view(0..2).decodeToString()
27+
)
28+
}
29+
30+
@Test
31+
fun `inputStream works`() {
32+
assertArrayEquals(
33+
"hello, world".toByteArray(),
34+
"hello, world".toByteArray().inputStream().readBytes()
35+
)
36+
}
37+
38+
@Test
39+
fun `able to view empty array`() {
40+
val sliced = byteArrayOf().view()
41+
assertEquals(0, sliced.len)
42+
assertEquals(0, sliced.offset)
43+
}
44+
}

libsession/src/main/java/org/session/libsession/messaging/file_server/FileServerApi.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import org.session.libsession.snode.utilities.await
1515
import org.session.libsignal.utilities.HTTP
1616
import org.session.libsignal.utilities.JsonUtil
1717
import org.session.libsignal.utilities.Log
18+
import org.session.libsignal.utilities.ByteArraySlice
1819
import org.session.libsignal.utilities.toHexString
1920
import kotlin.time.Duration.Companion.milliseconds
2021

@@ -51,7 +52,7 @@ object FileServerApi {
5152
return RequestBody.create("application/json".toMediaType(), parametersAsJSON)
5253
}
5354

54-
private fun send(request: Request): Promise<ByteArray, Exception> {
55+
private fun send(request: Request): Promise<ByteArraySlice, Exception> {
5556
val url = server.toHttpUrlOrNull() ?: return Promise.ofFail(Error.InvalidURL)
5657
val urlBuilder = HttpUrl.Builder()
5758
.scheme(url.scheme)
@@ -106,7 +107,7 @@ object FileServerApi {
106107
}
107108
}
108109

109-
fun download(file: String): Promise<ByteArray, Exception> {
110+
fun download(file: String): Promise<ByteArraySlice, Exception> {
110111
val request = Request(verb = HTTP.Verb.GET, endpoint = "file/$file")
111112
return send(request)
112113
}

libsession/src/main/java/org/session/libsession/messaging/jobs/AttachmentDownloadJob.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ import org.session.libsignal.exceptions.NonRetryableException
1818
import org.session.libsignal.streams.AttachmentCipherInputStream
1919
import org.session.libsignal.utilities.Base64
2020
import org.session.libsignal.utilities.Log
21+
import org.session.libsignal.utilities.ByteArraySlice.Companion.write
2122
import java.io.File
2223
import java.io.FileInputStream
24+
import java.io.FileOutputStream
2325
import java.io.InputStream
2426

2527
class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long) : Job {
@@ -138,8 +140,8 @@ class AttachmentDownloadJob(val attachmentID: Long, val databaseMessageID: Long)
138140
Log.d("AttachmentDownloadJob", "downloading open group attachment")
139141
val url = attachment.url.toHttpUrlOrNull()!!
140142
val fileID = url.pathSegments.last()
141-
OpenGroupApi.download(fileID, openGroup.room, openGroup.server).await().let {
142-
tempFile.writeBytes(it)
143+
OpenGroupApi.download(fileID, openGroup.room, openGroup.server).await().let { data ->
144+
FileOutputStream(tempFile).use { output -> output.write(data) }
143145
}
144146
}
145147
Log.d("AttachmentDownloadJob", "getting input stream")

libsession/src/main/java/org/session/libsession/messaging/jobs/GroupAvatarDownloadJob.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class GroupAvatarDownloadJob(val server: String, val room: String, val imageId:
4444
}
4545

4646
val groupId = GroupUtil.getEncodedOpenGroupID("$server.$room".toByteArray())
47-
storage.updateProfilePicture(groupId, bytes)
47+
storage.updateProfilePicture(groupId, bytes.copyToBytes())
4848
storage.updateTimestampUpdated(groupId, SnodeAPI.nowWithOffset)
4949
delegate?.handleJobSucceeded(this, dispatcherName)
5050
} catch (e: Exception) {

libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupApi.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import com.goterl.lazysodium.interfaces.GenericHash
1010
import com.goterl.lazysodium.interfaces.Sign
1111
import kotlinx.coroutines.GlobalScope
1212
import kotlinx.coroutines.flow.MutableSharedFlow
13-
import kotlinx.coroutines.launch
1413
import nl.komponents.kovenant.Promise
1514
import nl.komponents.kovenant.functional.map
1615
import okhttp3.Headers.Companion.toHeaders
@@ -39,6 +38,7 @@ import org.session.libsignal.utilities.Hex
3938
import org.session.libsignal.utilities.IdPrefix
4039
import org.session.libsignal.utilities.JsonUtil
4140
import org.session.libsignal.utilities.Log
41+
import org.session.libsignal.utilities.ByteArraySlice
4242
import org.session.libsignal.utilities.removingIdPrefixIfNeeded
4343
import org.whispersystems.curve25519.Curve25519
4444
import java.util.concurrent.TimeUnit
@@ -79,7 +79,7 @@ object OpenGroupApi {
7979
object NoEd25519KeyPair : Error("Couldn't find ed25519 key pair.")
8080
}
8181

82-
data class DefaultGroup(val id: String, val name: String, val image: ByteArray?) {
82+
data class DefaultGroup(val id: String, val name: String, val image: ByteArraySlice?) {
8383

8484
val joinURL: String get() = "$defaultServer/$id?public_key=$defaultServerPublicKey"
8585
}
@@ -290,7 +290,7 @@ object OpenGroupApi {
290290
return RequestBody.create("application/json".toMediaType(), parametersAsJSON)
291291
}
292292

293-
private fun getResponseBody(request: Request): Promise<ByteArray, Exception> {
293+
private fun getResponseBody(request: Request): Promise<ByteArraySlice, Exception> {
294294
return send(request).map { response ->
295295
response.body ?: throw Error.ParsingFailed
296296
}
@@ -417,7 +417,7 @@ object OpenGroupApi {
417417
server: String,
418418
roomID: String,
419419
imageId: String
420-
): Promise<ByteArray, Exception> {
420+
): Promise<ByteArraySlice, Exception> {
421421
val request = Request(
422422
verb = GET,
423423
room = roomID,
@@ -445,7 +445,7 @@ object OpenGroupApi {
445445
}
446446
}
447447

448-
fun download(fileId: String, room: String, server: String): Promise<ByteArray, Exception> {
448+
fun download(fileId: String, room: String, server: String): Promise<ByteArraySlice, Exception> {
449449
val request = Request(
450450
verb = GET,
451451
room = room,

libsession/src/main/java/org/session/libsession/snode/OnionRequestAPI.kt

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package org.session.libsession.snode
22

3-
import kotlinx.coroutines.Dispatchers
43
import kotlinx.coroutines.GlobalScope
54
import kotlinx.coroutines.flow.MutableStateFlow
65
import kotlinx.coroutines.flow.SharingStarted
@@ -27,15 +26,15 @@ import org.session.libsignal.crypto.secureRandom
2726
import org.session.libsignal.crypto.secureRandomOrNull
2827
import org.session.libsignal.database.LokiAPIDatabaseProtocol
2928
import org.session.libsignal.utilities.Base64
30-
import org.session.libsignal.utilities.Broadcaster
3129
import org.session.libsignal.utilities.ForkInfo
3230
import org.session.libsignal.utilities.HTTP
3331
import org.session.libsignal.utilities.JsonUtil
3432
import org.session.libsignal.utilities.Log
33+
import org.session.libsignal.utilities.ByteArraySlice
34+
import org.session.libsignal.utilities.ByteArraySlice.Companion.view
3535
import org.session.libsignal.utilities.Snode
3636
import org.session.libsignal.utilities.recover
3737
import org.session.libsignal.utilities.toHexString
38-
import java.util.concurrent.atomic.AtomicReference
3938
import kotlin.collections.set
4039

4140
private typealias Path = List<Snode>
@@ -629,7 +628,7 @@ object OnionRequestAPI {
629628
)
630629
return deferred.reject(exception)
631630
}
632-
deferred.resolve(OnionResponse(body, JsonUtil.toJson(body).toByteArray()))
631+
deferred.resolve(OnionResponse(body, JsonUtil.toJson(body).toByteArray().view()))
633632
}
634633
else -> {
635634
if (statusCode != 200) {
@@ -640,7 +639,7 @@ object OnionRequestAPI {
640639
)
641640
return deferred.reject(exception)
642641
}
643-
deferred.resolve(OnionResponse(json, JsonUtil.toJson(json).toByteArray()))
642+
deferred.resolve(OnionResponse(json, JsonUtil.toJson(json).toByteArray().view()))
644643
}
645644
}
646645
} catch (exception: Exception) {
@@ -652,17 +651,16 @@ object OnionRequestAPI {
652651
}
653652
}
654653

655-
private fun ByteArray.getBody(infoLength: Int, infoEndIndex: Int): ByteArray {
654+
private fun ByteArray.getBody(infoLength: Int, infoEndIndex: Int): ByteArraySlice {
656655
// If there is no data in the response, i.e. only `l123:jsone`, then just return the ResponseInfo
657656
val infoLengthStringLength = infoLength.toString().length
658657
if (size <= infoLength + infoLengthStringLength + 2/*l and e bytes*/) {
659-
return byteArrayOf()
658+
return ByteArraySlice.EMPTY
660659
}
661660
// Extract the response data as well
662-
val dataSlice = slice(infoEndIndex + 1 until size - 1)
663-
val dataSepIdx = dataSlice.indexOfFirst { byteArrayOf(it).contentEquals(":".toByteArray()) }
664-
val responseBody = dataSlice.slice(dataSepIdx + 1 until dataSlice.size)
665-
return responseBody.toByteArray()
661+
val dataSlice = view(infoEndIndex + 1 until size - 1)
662+
val dataSepIdx = dataSlice.asList().indexOfFirst { it.toInt() == ':'.code }
663+
return dataSlice.view(dataSepIdx + 1 until dataSlice.len)
666664
}
667665

668666
// endregion
@@ -676,7 +674,7 @@ enum class Version(val value: String) {
676674

677675
data class OnionResponse(
678676
val info: Map<*, *>,
679-
val body: ByteArray? = null
677+
val body: ByteArraySlice? = null
680678
) {
681679
val code: Int? get() = info["code"] as? Int
682680
val message: String? get() = info["message"] as? String

libsession/src/main/java/org/session/libsession/utilities/DownloadUtilities.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,14 @@ package org.session.libsession.utilities
22

33
import kotlinx.coroutines.Dispatchers
44
import kotlinx.coroutines.withContext
5-
import okhttp3.HttpUrl
65
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
76
import org.session.libsession.messaging.file_server.FileServerApi
87
import org.session.libsession.snode.utilities.await
98
import org.session.libsignal.exceptions.NonRetryableException
109
import org.session.libsignal.utilities.HTTP
1110
import org.session.libsignal.utilities.Log
11+
import org.session.libsignal.utilities.ByteArraySlice.Companion.write
1212
import java.io.File
13-
import java.io.FileOutputStream
1413
import java.io.OutputStream
1514

1615
object DownloadUtilities {

libsession/src/main/java/org/session/libsession/utilities/TextSecurePreferences.kt

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
1515
import kotlinx.coroutines.flow.StateFlow
1616
import kotlinx.coroutines.flow.asSharedFlow
1717
import org.session.libsession.R
18+
import org.session.libsession.messaging.MessagingModuleConfiguration
1819
import org.session.libsession.utilities.TextSecurePreferences.Companion.AUTOPLAY_AUDIO_MESSAGES
1920
import org.session.libsession.utilities.TextSecurePreferences.Companion.CALL_NOTIFICATIONS_ENABLED
2021
import org.session.libsession.utilities.TextSecurePreferences.Companion.CLASSIC_DARK
@@ -213,7 +214,8 @@ interface TextSecurePreferences {
213214

214215

215216
// This is a stop-gap solution for static access to shared preference.
216-
internal lateinit var preferenceInstance: TextSecurePreferences
217+
val preferenceInstance: TextSecurePreferences
218+
get() = MessagingModuleConfiguration.shared.preferences
217219

218220
const val DISABLE_PASSPHRASE_PREF = "pref_disable_passphrase"
219221
const val LANGUAGE_PREF = "pref_language"
@@ -987,11 +989,6 @@ interface TextSecurePreferences {
987989
class AppTextSecurePreferences @Inject constructor(
988990
@ApplicationContext private val context: Context
989991
): TextSecurePreferences {
990-
init {
991-
// Should remove once all static access to the companion objects is removed
992-
TextSecurePreferences.preferenceInstance = this
993-
}
994-
995992
private val localNumberState = MutableStateFlow(getStringPreference(TextSecurePreferences.LOCAL_NUMBER_PREF, null))
996993

997994
override var migratedToGroupV2Config: Boolean
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package org.session.libsignal.utilities
2+
3+
import java.io.ByteArrayInputStream
4+
import java.io.InputStream
5+
import java.io.OutputStream
6+
7+
/**
8+
* A view of a byte array with a range. This is useful for avoiding copying data when slicing a byte array.
9+
*/
10+
class ByteArraySlice private constructor(
11+
val data: ByteArray,
12+
val offset: Int,
13+
val len: Int,
14+
) {
15+
init {
16+
check(offset in 0..data.size) { "Offset $offset is not within [0..${data.size}]" }
17+
check(len in 0..data.size) { "Length $len is not within [0..${data.size}]" }
18+
}
19+
20+
fun view(range: IntRange): ByteArraySlice {
21+
val newOffset = offset + range.first
22+
val newLength = range.last + 1 - range.first
23+
return ByteArraySlice(
24+
data = data,
25+
offset = newOffset,
26+
len = newLength
27+
)
28+
}
29+
30+
fun copyToBytes(): ByteArray {
31+
return data.copyOfRange(offset, offset + len)
32+
}
33+
34+
operator fun get(index: Int): Byte {
35+
return data[offset + index]
36+
}
37+
38+
fun asList(): List<Byte> {
39+
return object : AbstractList<Byte>() {
40+
override val size: Int
41+
get() = this@ByteArraySlice.len
42+
43+
override fun get(index: Int) = this@ByteArraySlice[index]
44+
}
45+
}
46+
47+
fun decodeToString(): String {
48+
return data.decodeToString(offset, offset + len)
49+
}
50+
51+
fun inputStream(): InputStream {
52+
return ByteArrayInputStream(data, offset, len)
53+
}
54+
55+
fun isEmpty(): Boolean = len == 0
56+
fun isNotEmpty(): Boolean = len != 0
57+
58+
override fun equals(other: Any?): Boolean {
59+
if (this === other) return true
60+
if (other !is ByteArraySlice) return false
61+
62+
if (offset != other.offset) return false
63+
if (len != other.len) return false
64+
if (!data.contentEquals(other.data)) return false
65+
66+
return true
67+
}
68+
69+
override fun hashCode(): Int {
70+
var result = offset
71+
result = 31 * result + len
72+
result = 31 * result + data.contentHashCode()
73+
return result
74+
}
75+
76+
companion object {
77+
val EMPTY = ByteArraySlice(byteArrayOf(), 0, 0)
78+
79+
/**
80+
* Create a view of a byte array
81+
*/
82+
fun ByteArray.view(range: IntRange = indices): ByteArraySlice {
83+
return ByteArraySlice(
84+
data = this,
85+
offset = range.first,
86+
len = range.last + 1 - range.first
87+
)
88+
}
89+
90+
fun OutputStream.write(view: ByteArraySlice) {
91+
write(view.data, view.offset, view.len)
92+
}
93+
}
94+
}

0 commit comments

Comments
 (0)