mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-24 10:35:19 +00:00
Merge dev and cleanup
This commit is contained in:
commit
5cbe289a8d
@ -28,8 +28,8 @@ configurations.all {
|
||||
exclude module: "commons-logging"
|
||||
}
|
||||
|
||||
def canonicalVersionCode = 376
|
||||
def canonicalVersionName = "1.18.6"
|
||||
def canonicalVersionCode = 377
|
||||
def canonicalVersionName = "1.19.0"
|
||||
|
||||
def postFixSize = 10
|
||||
def abiPostFix = ['armeabi-v7a' : 1,
|
||||
|
@ -86,6 +86,7 @@ import org.thoughtcrime.securesms.sskenvironment.ProfileManager;
|
||||
import org.thoughtcrime.securesms.sskenvironment.ReadReceiptManager;
|
||||
import org.thoughtcrime.securesms.sskenvironment.TypingStatusRepository;
|
||||
import org.thoughtcrime.securesms.util.Broadcaster;
|
||||
import org.thoughtcrime.securesms.util.VersionDataFetcher;
|
||||
import org.thoughtcrime.securesms.util.dynamiclanguage.LocaleParseHelper;
|
||||
import org.thoughtcrime.securesms.webrtc.CallMessageProcessor;
|
||||
import org.webrtc.PeerConnectionFactory;
|
||||
@ -110,7 +111,6 @@ import javax.inject.Inject;
|
||||
import dagger.hilt.EntryPoints;
|
||||
import dagger.hilt.android.HiltAndroidApp;
|
||||
import kotlin.Unit;
|
||||
import kotlinx.coroutines.Job;
|
||||
import network.loki.messenger.BuildConfig;
|
||||
import network.loki.messenger.libsession_util.ConfigBase;
|
||||
import network.loki.messenger.libsession_util.UserProfile;
|
||||
@ -151,6 +151,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
|
||||
@Inject PushRegistry pushRegistry;
|
||||
@Inject ConfigFactory configFactory;
|
||||
@Inject LastSentTimestampCache lastSentTimestampCache;
|
||||
@Inject VersionDataFetcher versionDataFetcher;
|
||||
CallMessageProcessor callMessageProcessor;
|
||||
MessagingModuleConfiguration messagingModuleConfiguration;
|
||||
|
||||
@ -275,6 +276,9 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
|
||||
|
||||
OpenGroupManager.INSTANCE.startPolling();
|
||||
});
|
||||
|
||||
// fetch last version data
|
||||
versionDataFetcher.startTimedVersionCheck();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -287,12 +291,14 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
|
||||
poller.stopIfNeeded();
|
||||
}
|
||||
ClosedGroupPollerV2.getShared().stopAll();
|
||||
versionDataFetcher.stopTimedVersionCheck();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTerminate() {
|
||||
stopKovenant(); // Loki
|
||||
OpenGroupManager.INSTANCE.stopPolling();
|
||||
versionDataFetcher.stopTimedVersionCheck();
|
||||
super.onTerminate();
|
||||
}
|
||||
|
||||
|
@ -7,8 +7,6 @@ import com.squareup.phrase.Phrase
|
||||
import network.loki.messenger.R
|
||||
import org.session.libsession.LocalisedTimeUtil
|
||||
import org.session.libsession.utilities.StringSubstitutionConstants.TIME_LARGE_KEY
|
||||
import org.session.libsignal.utilities.Log
|
||||
import java.time.Duration
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
@ -27,7 +27,11 @@ import org.thoughtcrime.securesms.groups.JoinCommunityFragment
|
||||
@AndroidEntryPoint
|
||||
class StartConversationFragment : BottomSheetDialogFragment(), StartConversationDelegate {
|
||||
|
||||
private val defaultPeekHeight: Int by lazy { (Resources.getSystem().displayMetrics.heightPixels * 0.94).toInt() }
|
||||
companion object{
|
||||
const val PEEK_RATIO = 0.94f
|
||||
}
|
||||
|
||||
private val defaultPeekHeight: Int by lazy { (Resources.getSystem().displayMetrics.heightPixels * PEEK_RATIO).toInt() }
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
|
@ -1,10 +1,13 @@
|
||||
package org.thoughtcrime.securesms.conversation.start.newmessage
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import android.graphics.Rect
|
||||
import android.os.Build
|
||||
import android.view.ViewTreeObserver
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
@ -15,23 +18,31 @@ import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.onboarding.ui.ContinuePrimaryOutlineButton
|
||||
import org.thoughtcrime.securesms.conversation.start.StartConversationFragment.Companion.PEEK_RATIO
|
||||
import org.thoughtcrime.securesms.ui.LoadingArcOr
|
||||
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
|
||||
import org.thoughtcrime.securesms.ui.theme.PreviewTheme
|
||||
import org.thoughtcrime.securesms.ui.theme.SessionColorsParameterProvider
|
||||
import org.thoughtcrime.securesms.ui.theme.ThemeColors
|
||||
import org.thoughtcrime.securesms.ui.theme.LocalColors
|
||||
import org.thoughtcrime.securesms.ui.components.AppBar
|
||||
import org.thoughtcrime.securesms.ui.components.BorderlessButtonWithIcon
|
||||
import org.thoughtcrime.securesms.ui.components.MaybeScanQrCode
|
||||
@ -39,7 +50,13 @@ import org.thoughtcrime.securesms.ui.components.PrimaryOutlineButton
|
||||
import org.thoughtcrime.securesms.ui.components.SessionOutlinedTextField
|
||||
import org.thoughtcrime.securesms.ui.components.SessionTabRow
|
||||
import org.thoughtcrime.securesms.ui.contentDescription
|
||||
import org.thoughtcrime.securesms.ui.theme.LocalColors
|
||||
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
|
||||
import org.thoughtcrime.securesms.ui.theme.LocalType
|
||||
import org.thoughtcrime.securesms.ui.theme.PreviewTheme
|
||||
import org.thoughtcrime.securesms.ui.theme.SessionColorsParameterProvider
|
||||
import org.thoughtcrime.securesms.ui.theme.ThemeColors
|
||||
import kotlin.math.max
|
||||
|
||||
private val TITLES = listOf(R.string.accountIdEnter, R.string.qrScan)
|
||||
|
||||
@ -76,63 +93,127 @@ private fun EnterAccountId(
|
||||
callbacks: Callbacks,
|
||||
onHelp: () -> Unit = {}
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.imePadding()
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(vertical = LocalDimensions.current.spacing),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
// the scaffold is required to provide the contentPadding. That contentPadding is needed
|
||||
// to properly handle the ime padding.
|
||||
Scaffold() { contentPadding ->
|
||||
// we need this extra surface to handle nested scrolling properly,
|
||||
// because this scrollable component is inside a bottomSheet dialog which is itself scrollable
|
||||
Surface(
|
||||
modifier = Modifier.nestedScroll(rememberNestedScrollInteropConnection()),
|
||||
color = LocalColors.current.backgroundSecondary
|
||||
) {
|
||||
SessionOutlinedTextField(
|
||||
text = state.newMessageIdOrOns,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = LocalDimensions.current.spacing),
|
||||
contentDescription = "Session id input box",
|
||||
placeholder = stringResource(R.string.accountIdOrOnsEnter),
|
||||
onChange = callbacks::onChange,
|
||||
onContinue = callbacks::onContinue,
|
||||
error = state.error?.string(),
|
||||
isTextErrorColor = state.isTextErrorColor
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(LocalDimensions.current.xxxsSpacing))
|
||||
var accountModifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
|
||||
BorderlessButtonWithIcon(
|
||||
text = stringResource(R.string.messageNewDescriptionMobile),
|
||||
modifier = Modifier
|
||||
.contentDescription(R.string.AccessibilityId_help_desk_link)
|
||||
.padding(horizontal = LocalDimensions.current.mediumSpacing)
|
||||
.fillMaxWidth(),
|
||||
style = LocalType.current.small,
|
||||
color = LocalColors.current.textSecondary,
|
||||
iconRes = R.drawable.ic_circle_question_mark,
|
||||
onClick = onHelp
|
||||
)
|
||||
}
|
||||
//<<<<<<< HEAD
|
||||
// BorderlessButtonWithIcon(
|
||||
// text = stringResource(R.string.messageNewDescriptionMobile),
|
||||
// modifier = Modifier
|
||||
// .contentDescription(R.string.AccessibilityId_help_desk_link)
|
||||
// .padding(horizontal = LocalDimensions.current.mediumSpacing)
|
||||
// .fillMaxWidth(),
|
||||
// style = LocalType.current.small,
|
||||
// color = LocalColors.current.textSecondary,
|
||||
// iconRes = R.drawable.ic_circle_question_mark,
|
||||
// onClick = onHelp
|
||||
// )
|
||||
// }
|
||||
//=======
|
||||
// There is a known issue with the ime padding on android versions below 30
|
||||
/// So on these older versions we need to resort to some manual padding based on the visible height
|
||||
// when the keyboard is up
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||
val keyboardHeight by keyboardHeight()
|
||||
accountModifier = accountModifier.padding(bottom = keyboardHeight)
|
||||
} else {
|
||||
accountModifier = accountModifier
|
||||
.consumeWindowInsets(contentPadding)
|
||||
.imePadding()
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(LocalDimensions.current.smallSpacing))
|
||||
Spacer(Modifier.weight(2f))
|
||||
Column(
|
||||
modifier = accountModifier
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(vertical = LocalDimensions.current.spacing),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
SessionOutlinedTextField(
|
||||
text = state.newMessageIdOrOns,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = LocalDimensions.current.spacing),
|
||||
contentDescription = "Session id input box",
|
||||
placeholder = stringResource(R.string.accountIdOrOnsEnter),
|
||||
onChange = callbacks::onChange,
|
||||
onContinue = callbacks::onContinue,
|
||||
error = state.error?.string(),
|
||||
isTextErrorColor = state.isTextErrorColor
|
||||
)
|
||||
|
||||
PrimaryOutlineButton(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.padding(horizontal = LocalDimensions.current.xlargeSpacing)
|
||||
.padding(bottom = LocalDimensions.current.smallSpacing)
|
||||
.fillMaxWidth()
|
||||
.contentDescription(R.string.next),
|
||||
enabled = state.isNextButtonEnabled,
|
||||
onClick = callbacks::onContinue
|
||||
) {
|
||||
LoadingArcOr(state.loading) {
|
||||
Text(stringResource(R.string.next))
|
||||
Spacer(modifier = Modifier.height(LocalDimensions.current.xxxsSpacing))
|
||||
|
||||
BorderlessButtonWithIcon(
|
||||
text = stringResource(R.string.messageNewDescriptionMobile),
|
||||
modifier = Modifier
|
||||
.contentDescription(R.string.AccessibilityId_help_desk_link)
|
||||
.padding(horizontal = LocalDimensions.current.mediumSpacing)
|
||||
.fillMaxWidth(),
|
||||
style = LocalType.current.small,
|
||||
color = LocalColors.current.textSecondary,
|
||||
iconRes = R.drawable.ic_circle_question_mark,
|
||||
onClick = onHelp
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(LocalDimensions.current.smallSpacing))
|
||||
Spacer(Modifier.weight(2f))
|
||||
|
||||
PrimaryOutlineButton(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.padding(horizontal = LocalDimensions.current.xlargeSpacing)
|
||||
.padding(bottom = LocalDimensions.current.smallSpacing)
|
||||
.fillMaxWidth()
|
||||
.contentDescription(R.string.next),
|
||||
enabled = state.isNextButtonEnabled,
|
||||
onClick = callbacks::onContinue
|
||||
) {
|
||||
LoadingArcOr(state.loading) {
|
||||
Text(stringResource(R.string.next))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun keyboardHeight(): MutableState<Dp> {
|
||||
val view = LocalView.current
|
||||
var keyboardHeight = remember { mutableStateOf(0.dp) }
|
||||
val density = LocalDensity.current
|
||||
|
||||
DisposableEffect(view) {
|
||||
val listener = ViewTreeObserver.OnGlobalLayoutListener {
|
||||
val rect = Rect()
|
||||
view.getWindowVisibleDisplayFrame(rect)
|
||||
val screenHeight = view.rootView.height * PEEK_RATIO
|
||||
val keypadHeightPx = max( screenHeight - rect.bottom, 0f)
|
||||
|
||||
keyboardHeight.value = with(density) { keypadHeightPx.toDp() }
|
||||
}
|
||||
|
||||
view.viewTreeObserver.addOnGlobalLayoutListener(listener)
|
||||
onDispose {
|
||||
view.viewTreeObserver.removeOnGlobalLayoutListener(listener)
|
||||
}
|
||||
}
|
||||
|
||||
return keyboardHeight
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun PreviewNewMessage(
|
||||
|
@ -46,7 +46,7 @@ internal class NewMessageViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
override fun onContinue() {
|
||||
val idOrONS = state.value.newMessageIdOrOns
|
||||
val idOrONS = state.value.newMessageIdOrOns.trim()
|
||||
|
||||
if (PublicKeyValidation.isValid(idOrONS, isPrefixRequired = false)) {
|
||||
onUnvalidatedPublicKey(publicKey = idOrONS)
|
||||
|
@ -178,7 +178,6 @@ import org.thoughtcrime.securesms.mms.VideoSlide
|
||||
import org.thoughtcrime.securesms.permissions.Permissions
|
||||
import org.thoughtcrime.securesms.reactions.ReactionsDialogFragment
|
||||
import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiDialogFragment
|
||||
import org.thoughtcrime.securesms.recoverypassword.RecoveryPasswordActivity
|
||||
import org.thoughtcrime.securesms.showSessionDialog
|
||||
import org.thoughtcrime.securesms.util.ActivityDispatcher
|
||||
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
|
||||
@ -190,7 +189,6 @@ import org.thoughtcrime.securesms.util.isScrolledToBottom
|
||||
import org.thoughtcrime.securesms.util.isScrolledToWithin30dpOfBottom
|
||||
import org.thoughtcrime.securesms.util.push
|
||||
import org.thoughtcrime.securesms.util.show
|
||||
import org.thoughtcrime.securesms.util.start
|
||||
import org.thoughtcrime.securesms.util.toPx
|
||||
|
||||
private const val TAG = "ConversationActivityV2"
|
||||
@ -1651,8 +1649,15 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
||||
val text = getMessageBody()
|
||||
val userPublicKey = textSecurePreferences.getLocalNumber()
|
||||
val isNoteToSelf = (recipient.isContactRecipient && recipient.address.toString() == userPublicKey)
|
||||
if (text.contains(seed) && !isNoteToSelf && !hasPermissionToSendSeed) {
|
||||
start<RecoveryPasswordActivity>()
|
||||
if (seed in text && !isNoteToSelf && !hasPermissionToSendSeed) {
|
||||
showSessionDialog {
|
||||
title(R.string.warning)
|
||||
text(R.string.recoveryPasswordWarningSendDescription)
|
||||
button(R.string.send) { sendTextOnlyMessage(true) }
|
||||
cancelButton()
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
// Create the message
|
||||
val message = VisibleMessage().applyExpiryMode(viewModel.threadId)
|
||||
|
@ -36,7 +36,6 @@ import com.google.android.mms.pdu_alt.EncodedStringValue
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.UnsupportedEncodingException
|
||||
import java.security.SecureRandom
|
||||
import java.util.Collections
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.math.max
|
||||
@ -247,32 +246,6 @@ object Util {
|
||||
return result
|
||||
}
|
||||
|
||||
fun getSecretBytes(size: Int): ByteArray {
|
||||
return getSecretBytes(SecureRandom(), size)
|
||||
}
|
||||
|
||||
fun getSecretBytes(secureRandom: SecureRandom, size: Int): ByteArray {
|
||||
val secret = ByteArray(size)
|
||||
secureRandom.nextBytes(secret)
|
||||
return secret
|
||||
}
|
||||
|
||||
fun <T> getRandomElement(elements: Array<T>): T {
|
||||
return elements[SecureRandom().nextInt(elements.size)]
|
||||
}
|
||||
|
||||
fun <T> getRandomElement(elements: List<T>): T {
|
||||
return elements[SecureRandom().nextInt(elements.size)]
|
||||
}
|
||||
|
||||
fun equals(a: Any?, b: Any?): Boolean {
|
||||
return a === b || (a != null && a == b)
|
||||
}
|
||||
|
||||
fun hashCode(vararg objects: Any?): Int {
|
||||
return objects.contentHashCode()
|
||||
}
|
||||
|
||||
fun uri(uri: String?): Uri? {
|
||||
return if (uri == null) null
|
||||
else Uri.parse(uri)
|
||||
|
@ -1,23 +0,0 @@
|
||||
package org.thoughtcrime.securesms.conversation.v2.dialogs
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.createSessionDialog
|
||||
|
||||
/** Shown if the user is about to send their recovery phrase to someone. */
|
||||
class SendSeedDialog(private val proceed: (() -> Unit)? = null) : DialogFragment() {
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = createSessionDialog {
|
||||
title(R.string.warning)
|
||||
text(R.string.recoveryPasswordWarningSendDescription)
|
||||
button(R.string.send) { send() }
|
||||
cancelButton()
|
||||
}
|
||||
|
||||
private fun send() {
|
||||
proceed?.invoke()
|
||||
dismiss()
|
||||
}
|
||||
}
|
@ -1,14 +1,14 @@
|
||||
package org.thoughtcrime.securesms.crypto;
|
||||
|
||||
|
||||
import static org.session.libsignal.utilities.Util.SECURE_RANDOM;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.session.libsession.utilities.TextSecurePreferences;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
|
||||
/**
|
||||
* A provider that is responsible for creating or retrieving the AttachmentSecret model.
|
||||
*
|
||||
@ -81,9 +81,8 @@ public class AttachmentSecretProvider {
|
||||
}
|
||||
|
||||
private AttachmentSecret createAndStoreAttachmentSecret(@NonNull Context context) {
|
||||
SecureRandom random = new SecureRandom();
|
||||
byte[] secret = new byte[32];
|
||||
random.nextBytes(secret);
|
||||
SECURE_RANDOM.nextBytes(secret);
|
||||
|
||||
AttachmentSecret attachmentSecret = new AttachmentSecret(null, null, secret);
|
||||
storeAttachmentSecret(context, attachmentSecret);
|
||||
|
@ -1,6 +1,8 @@
|
||||
package org.thoughtcrime.securesms.crypto;
|
||||
|
||||
|
||||
import static org.session.libsignal.utilities.Util.SECURE_RANDOM;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import androidx.annotation.NonNull;
|
||||
@ -8,7 +10,6 @@ import androidx.annotation.NonNull;
|
||||
import org.session.libsession.utilities.TextSecurePreferences;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public class DatabaseSecretProvider {
|
||||
|
||||
@ -60,9 +61,8 @@ public class DatabaseSecretProvider {
|
||||
}
|
||||
|
||||
private DatabaseSecret createAndStoreDatabaseSecret(@NonNull Context context) {
|
||||
SecureRandom random = new SecureRandom();
|
||||
byte[] secret = new byte[32];
|
||||
random.nextBytes(secret);
|
||||
SECURE_RANDOM.nextBytes(secret);
|
||||
|
||||
DatabaseSecret databaseSecret = new DatabaseSecret(secret);
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
package org.thoughtcrime.securesms.crypto;
|
||||
|
||||
|
||||
import static org.session.libsignal.utilities.Util.SECURE_RANDOM;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import android.util.Pair;
|
||||
|
||||
@ -11,7 +13,6 @@ import java.io.OutputStream;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.CipherOutputStream;
|
||||
@ -31,7 +32,7 @@ public class ModernEncryptingPartOutputStream {
|
||||
throws IOException
|
||||
{
|
||||
byte[] random = new byte[32];
|
||||
new SecureRandom().nextBytes(random);
|
||||
SECURE_RANDOM.nextBytes(random);
|
||||
|
||||
try {
|
||||
Mac mac = Mac.getInstance("HmacSHA256");
|
||||
|
@ -1,5 +1,7 @@
|
||||
package org.thoughtcrime.securesms.database;
|
||||
|
||||
import static org.session.libsignal.utilities.Util.SECURE_RANDOM;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
@ -26,7 +28,6 @@ import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@ -303,7 +304,7 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
|
||||
public void updateProfilePicture(String groupID, byte[] newValue) {
|
||||
long avatarId;
|
||||
|
||||
if (newValue != null) avatarId = Math.abs(new SecureRandom().nextLong());
|
||||
if (newValue != null) avatarId = Math.abs(SECURE_RANDOM.nextLong());
|
||||
else avatarId = 0;
|
||||
|
||||
|
||||
@ -458,12 +459,6 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
|
||||
database.update(TABLE_NAME, values, GROUP_ID + " = ?", new String[] {groupId});
|
||||
}
|
||||
|
||||
public byte[] allocateGroupId() {
|
||||
byte[] groupId = new byte[16];
|
||||
new SecureRandom().nextBytes(groupId);
|
||||
return groupId;
|
||||
}
|
||||
|
||||
public boolean hasGroup(@NonNull String groupId) {
|
||||
try (Cursor cursor = databaseHelper.getReadableDatabase().rawQuery(
|
||||
"SELECT 1 FROM " + TABLE_NAME + " WHERE " + GROUP_ID + " = ? LIMIT 1",
|
||||
|
@ -166,8 +166,6 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
||||
|
||||
const val RESET_SEQ_NO = "UPDATE $lastMessageServerIDTable SET $lastMessageServerID = 0;"
|
||||
|
||||
const val EMPTY_VERSION = "0.0.0"
|
||||
|
||||
// endregion
|
||||
}
|
||||
|
||||
@ -175,15 +173,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
||||
val database = databaseHelper.readableDatabase
|
||||
return database.get(snodePoolTable, "${Companion.dummyKey} = ?", wrap("dummy_key")) { cursor ->
|
||||
val snodePoolAsString = cursor.getString(cursor.getColumnIndexOrThrow(snodePool))
|
||||
snodePoolAsString.split(", ").mapNotNull { snodeAsString ->
|
||||
val components = snodeAsString.split("-")
|
||||
val address = components[0]
|
||||
val port = components.getOrNull(1)?.toIntOrNull() ?: return@mapNotNull null
|
||||
val ed25519Key = components.getOrNull(2) ?: return@mapNotNull null
|
||||
val x25519Key = components.getOrNull(3) ?: return@mapNotNull null
|
||||
val version = components.getOrNull(4) ?: EMPTY_VERSION
|
||||
Snode(address, port, Snode.KeySet(ed25519Key, x25519Key), version)
|
||||
}
|
||||
snodePoolAsString.split(", ").mapNotNull(::Snode)
|
||||
}?.toSet() ?: setOf()
|
||||
}
|
||||
|
||||
@ -231,18 +221,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
||||
val database = databaseHelper.readableDatabase
|
||||
fun get(indexPath: String): Snode? {
|
||||
return database.get(onionRequestPathTable, "${Companion.indexPath} = ?", wrap(indexPath)) { cursor ->
|
||||
val snodeAsString = cursor.getString(cursor.getColumnIndexOrThrow(snode))
|
||||
val components = snodeAsString.split("-")
|
||||
val address = components[0]
|
||||
val port = components.getOrNull(1)?.toIntOrNull()
|
||||
val ed25519Key = components.getOrNull(2)
|
||||
val x25519Key = components.getOrNull(3)
|
||||
val version = components.getOrNull(4) ?: EMPTY_VERSION
|
||||
if (port != null && ed25519Key != null && x25519Key != null) {
|
||||
Snode(address, port, Snode.KeySet(ed25519Key, x25519Key), version)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
Snode(cursor.getString(cursor.getColumnIndexOrThrow(snode)))
|
||||
}
|
||||
}
|
||||
val result = mutableListOf<List<Snode>>()
|
||||
@ -276,15 +255,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
||||
val database = databaseHelper.readableDatabase
|
||||
return database.get(swarmTable, "${Companion.swarmPublicKey} = ?", wrap(publicKey)) { cursor ->
|
||||
val swarmAsString = cursor.getString(cursor.getColumnIndexOrThrow(swarm))
|
||||
swarmAsString.split(", ").mapNotNull { targetAsString ->
|
||||
val components = targetAsString.split("-")
|
||||
val address = components[0]
|
||||
val port = components.getOrNull(1)?.toIntOrNull() ?: return@mapNotNull null
|
||||
val ed25519Key = components.getOrNull(2) ?: return@mapNotNull null
|
||||
val x25519Key = components.getOrNull(3) ?: return@mapNotNull null
|
||||
val version = components.getOrNull(4) ?: EMPTY_VERSION
|
||||
Snode(address, port, Snode.KeySet(ed25519Key, x25519Key), version)
|
||||
}
|
||||
swarmAsString.split(", ").mapNotNull(::Snode)
|
||||
}?.toSet()
|
||||
}
|
||||
|
||||
|
@ -46,11 +46,11 @@ import org.session.libsession.utilities.IdentityKeyMismatchList
|
||||
import org.session.libsession.utilities.NetworkFailure
|
||||
import org.session.libsession.utilities.NetworkFailureList
|
||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.isReadReceiptsEnabled
|
||||
import org.session.libsession.utilities.Util.toIsoBytes
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.session.libsignal.utilities.JsonUtil
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.session.libsignal.utilities.ThreadUtils.queue
|
||||
import org.session.libsignal.utilities.Util.SECURE_RANDOM
|
||||
import org.session.libsignal.utilities.guava.Optional
|
||||
import org.thoughtcrime.securesms.attachments.MmsNotificationAttachment
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase.InsertListener
|
||||
@ -66,7 +66,6 @@ import org.thoughtcrime.securesms.mms.SlideDeck
|
||||
import org.thoughtcrime.securesms.util.asSequence
|
||||
import java.io.Closeable
|
||||
import java.io.IOException
|
||||
import java.security.SecureRandom
|
||||
import java.util.LinkedList
|
||||
|
||||
class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : MessagingDatabase(context, databaseHelper) {
|
||||
@ -1200,7 +1199,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
|
||||
|
||||
inner class OutgoingMessageReader(private val message: OutgoingMediaMessage?,
|
||||
private val threadId: Long) {
|
||||
private val id = SecureRandom().nextLong()
|
||||
private val id = SECURE_RANDOM.nextLong()
|
||||
val current: MessageRecord
|
||||
get() {
|
||||
val slideDeck = SlideDeck(context, message!!.attachments)
|
||||
|
@ -17,6 +17,8 @@
|
||||
*/
|
||||
package org.thoughtcrime.securesms.database;
|
||||
|
||||
import static org.session.libsignal.utilities.Util.SECURE_RANDOM;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
@ -49,7 +51,6 @@ import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
@ -784,7 +785,7 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
public OutgoingMessageReader(OutgoingTextMessage message, long threadId) {
|
||||
this.message = message;
|
||||
this.threadId = threadId;
|
||||
this.id = new SecureRandom().nextLong();
|
||||
this.id = SECURE_RANDOM.nextLong();
|
||||
}
|
||||
|
||||
public MessageRecord getCurrent() {
|
||||
|
@ -1,9 +1,10 @@
|
||||
package org.thoughtcrime.securesms.glide;
|
||||
|
||||
import static org.session.libsignal.utilities.Util.SECURE_RANDOM;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import okhttp3.Headers;
|
||||
import okhttp3.Interceptor;
|
||||
@ -30,15 +31,15 @@ public class PaddedHeadersInterceptor implements Interceptor {
|
||||
|
||||
private @NonNull Headers getPaddedHeaders(@NonNull Headers headers) {
|
||||
return headers.newBuilder()
|
||||
.add(PADDING_HEADER, getRandomString(new SecureRandom(), MIN_RANDOM_BYTES, MAX_RANDOM_BYTES))
|
||||
.add(PADDING_HEADER, getRandomString(MIN_RANDOM_BYTES, MAX_RANDOM_BYTES))
|
||||
.build();
|
||||
}
|
||||
|
||||
private static @NonNull String getRandomString(@NonNull SecureRandom secureRandom, int minLength, int maxLength) {
|
||||
char[] buffer = new char[secureRandom.nextInt(maxLength - minLength) + minLength];
|
||||
private static @NonNull String getRandomString(int minLength, int maxLength) {
|
||||
char[] buffer = new char[SECURE_RANDOM.nextInt(maxLength - minLength) + minLength];
|
||||
|
||||
for (int i = 0 ; i < buffer.length; i++) {
|
||||
buffer[i] = (char) (secureRandom.nextInt(74) + 48); // Random char from 0-Z
|
||||
buffer[i] = (char) (SECURE_RANDOM.nextInt(74) + 48); // Random char from 0-Z
|
||||
}
|
||||
|
||||
return new String(buffer);
|
||||
|
@ -89,6 +89,9 @@ import java.util.Calendar
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val NEW_ACCOUNT = "HomeActivity_NEW_ACCOUNT"
|
||||
private const val FROM_ONBOARDING = "HomeActivity_FROM_ONBOARDING"
|
||||
|
||||
@AndroidEntryPoint
|
||||
class HomeActivity : PassphraseRequiredActionBarActivity(),
|
||||
ConversationClickListener,
|
||||
@ -96,10 +99,10 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
||||
|
||||
private val TAG = "HomeActivity"
|
||||
|
||||
companion object {
|
||||
const val NEW_ACCOUNT = "HomeActivity_NEW_ACCOUNT"
|
||||
const val FROM_ONBOARDING = "HomeActivity_FROM_ONBOARDING"
|
||||
}
|
||||
// companion object {
|
||||
// const val NEW_ACCOUNT = "HomeActivity_NEW_ACCOUNT"
|
||||
// const val FROM_ONBOARDING = "HomeActivity_FROM_ONBOARDING"
|
||||
// }
|
||||
|
||||
private lateinit var binding: ActivityHomeBinding
|
||||
private lateinit var glide: RequestManager
|
||||
@ -148,7 +151,8 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
||||
}
|
||||
}
|
||||
|
||||
private val isNewAccount: Boolean get() = intent.getBooleanExtra(FROM_ONBOARDING, false)
|
||||
private val isFromOnboarding: Boolean get() = intent.getBooleanExtra(FROM_ONBOARDING, false)
|
||||
private val isNewAccount: Boolean get() = intent.getBooleanExtra(NEW_ACCOUNT, false)
|
||||
|
||||
// region Lifecycle
|
||||
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
|
||||
@ -262,7 +266,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
||||
}
|
||||
else -> buildList {
|
||||
result.contactAndGroupList.takeUnless { it.isEmpty() }?.let {
|
||||
add(GlobalSearchAdapter.Model.Header(R.string.contactContacts))
|
||||
add(GlobalSearchAdapter.Model.Header(R.string.sessionConversations))
|
||||
addAll(it)
|
||||
}
|
||||
result.messageResults.takeUnless { it.isEmpty() }?.let {
|
||||
@ -275,8 +279,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
||||
}
|
||||
}
|
||||
EventBus.getDefault().register(this@HomeActivity)
|
||||
if (intent.hasExtra(FROM_ONBOARDING)
|
||||
&& intent.getBooleanExtra(FROM_ONBOARDING, false)) {
|
||||
if (isFromOnboarding) {
|
||||
if (Build.VERSION.SDK_INT >= 33 &&
|
||||
(getSystemService(NOTIFICATION_SERVICE) as NotificationManager).areNotificationsEnabled().not()) {
|
||||
Permissions.with(this)
|
||||
@ -681,10 +684,10 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.startHomeActivity(isNewAccount: Boolean) {
|
||||
fun Context.startHomeActivity(isFromOnboarding: Boolean, isNewAccount: Boolean) {
|
||||
Intent(this, HomeActivity::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
putExtra(HomeActivity.NEW_ACCOUNT, true)
|
||||
putExtra(HomeActivity.FROM_ONBOARDING, true)
|
||||
putExtra(NEW_ACCOUNT, isNewAccount)
|
||||
putExtra(FROM_ONBOARDING, isFromOnboarding)
|
||||
}.also(::startActivity)
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package org.thoughtcrime.securesms.logging;
|
||||
|
||||
import static org.session.libsignal.crypto.CipherUtil.CIPHER_LOCK;
|
||||
import static org.session.libsignal.utilities.Util.SECURE_RANDOM;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
@ -17,7 +18,6 @@ import java.io.IOException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
@ -64,7 +64,7 @@ class LogFile {
|
||||
}
|
||||
|
||||
void writeEntry(@NonNull String entry) throws IOException {
|
||||
new SecureRandom().nextBytes(ivBuffer);
|
||||
SECURE_RANDOM.nextBytes(ivBuffer);
|
||||
|
||||
byte[] plaintext = entry.getBytes();
|
||||
try {
|
||||
|
@ -1,5 +1,7 @@
|
||||
package org.thoughtcrime.securesms.logging;
|
||||
|
||||
import static org.session.libsignal.utilities.Util.SECURE_RANDOM;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import androidx.annotation.NonNull;
|
||||
@ -9,7 +11,6 @@ import org.session.libsignal.utilities.Base64;
|
||||
import org.session.libsession.utilities.TextSecurePreferences;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
class LogSecretProvider {
|
||||
|
||||
@ -40,9 +41,8 @@ class LogSecretProvider {
|
||||
}
|
||||
|
||||
private static byte[] createAndStoreSecret(@NonNull Context context) {
|
||||
SecureRandom random = new SecureRandom();
|
||||
byte[] secret = new byte[32];
|
||||
random.nextBytes(secret);
|
||||
SECURE_RANDOM.nextBytes(secret);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.seal(secret);
|
||||
|
@ -21,7 +21,6 @@ import android.content.res.Resources
|
||||
import android.net.Uri
|
||||
import androidx.annotation.DrawableRes
|
||||
import com.squareup.phrase.Phrase
|
||||
import java.security.SecureRandom
|
||||
import network.loki.messenger.R
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.Attachment
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress
|
||||
@ -29,6 +28,7 @@ import org.session.libsession.messaging.sending_receiving.attachments.UriAttachm
|
||||
import org.session.libsession.utilities.StringSubstitutionConstants.EMOJI_KEY
|
||||
import org.session.libsession.utilities.Util.equals
|
||||
import org.session.libsession.utilities.Util.hashCode
|
||||
import org.session.libsignal.utilities.Util.SECURE_RANDOM
|
||||
import org.session.libsignal.utilities.guava.Optional
|
||||
import org.thoughtcrime.securesms.conversation.v2.Util
|
||||
import org.thoughtcrime.securesms.util.MediaUtil
|
||||
@ -160,7 +160,7 @@ abstract class Slide(@JvmField protected val context: Context, protected val att
|
||||
): Attachment {
|
||||
val resolvedType =
|
||||
Optional.fromNullable(MediaUtil.getMimeType(context, uri)).or(defaultMime)
|
||||
val fastPreflightId = SecureRandom().nextLong().toString()
|
||||
val fastPreflightId = SECURE_RANDOM.nextLong().toString()
|
||||
return UriAttachment(
|
||||
uri,
|
||||
if (hasThumbnail) uri else null,
|
||||
|
@ -1,5 +1,7 @@
|
||||
package org.thoughtcrime.securesms.net;
|
||||
|
||||
import static org.session.libsignal.utilities.Util.SECURE_RANDOM;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
|
||||
@ -15,7 +17,6 @@ import org.session.libsignal.utilities.guava.Optional;
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
@ -54,7 +55,7 @@ public class ChunkedDataFetcher {
|
||||
private RequestController fetchChunksWithUnknownTotalSize(@NonNull String url, @NonNull Callback callback) {
|
||||
CompositeRequestController compositeController = new CompositeRequestController();
|
||||
|
||||
long chunkSize = new SecureRandom().nextInt(1024) + 1024;
|
||||
long chunkSize = SECURE_RANDOM.nextInt(1024) + 1024;
|
||||
Request request = new Request.Builder()
|
||||
.url(url)
|
||||
.cacheControl(NO_CACHE)
|
||||
|
@ -32,7 +32,7 @@ class LoadingActivity: BaseActionBarActivity() {
|
||||
|
||||
when {
|
||||
loadFailed -> startPickDisplayNameActivity(loadFailed = true)
|
||||
else -> startHomeActivity(isNewAccount = false)
|
||||
else -> startHomeActivity(isNewAccount = false, isFromOnboarding = true)
|
||||
}
|
||||
|
||||
finish()
|
||||
|
@ -8,6 +8,7 @@ import org.session.libsignal.utilities.KeyHelper
|
||||
import org.session.libsignal.utilities.hexEncodedPublicKey
|
||||
import org.thoughtcrime.securesms.crypto.KeyPairUtilities
|
||||
import org.thoughtcrime.securesms.dependencies.ConfigFactory
|
||||
import org.thoughtcrime.securesms.util.VersionDataFetcher
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -16,6 +17,7 @@ class CreateAccountManager @Inject constructor(
|
||||
private val application: Application,
|
||||
private val prefs: TextSecurePreferences,
|
||||
private val configFactory: ConfigFactory,
|
||||
private val versionDataFetcher: VersionDataFetcher
|
||||
) {
|
||||
private val database: LokiAPIDatabaseProtocol
|
||||
get() = SnodeModule.shared.storage
|
||||
@ -41,5 +43,7 @@ class CreateAccountManager @Inject constructor(
|
||||
prefs.setLocalRegistrationId(registrationID)
|
||||
prefs.setLocalNumber(userHexEncodedPublicKey)
|
||||
prefs.setRestorationTime(0)
|
||||
|
||||
versionDataFetcher.startTimedVersionCheck()
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@ import org.session.libsignal.utilities.hexEncodedPublicKey
|
||||
import org.thoughtcrime.securesms.ApplicationContext
|
||||
import org.thoughtcrime.securesms.crypto.KeyPairUtilities
|
||||
import org.thoughtcrime.securesms.dependencies.ConfigFactory
|
||||
import org.thoughtcrime.securesms.util.VersionDataFetcher
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@ -19,7 +20,8 @@ import javax.inject.Singleton
|
||||
class LoadAccountManager @Inject constructor(
|
||||
@dagger.hilt.android.qualifiers.ApplicationContext private val context: Context,
|
||||
private val configFactory: ConfigFactory,
|
||||
private val prefs: TextSecurePreferences
|
||||
private val prefs: TextSecurePreferences,
|
||||
private val versionDataFetcher: VersionDataFetcher
|
||||
) {
|
||||
private val database: LokiAPIDatabaseProtocol
|
||||
get() = SnodeModule.shared.storage
|
||||
@ -52,6 +54,8 @@ class LoadAccountManager @Inject constructor(
|
||||
setHasViewedSeed(true)
|
||||
}
|
||||
|
||||
versionDataFetcher.startTimedVersionCheck()
|
||||
|
||||
ApplicationContext.getInstance(context).retrieveUserProfile()
|
||||
}
|
||||
}
|
||||
|
@ -49,7 +49,7 @@ class MessageNotificationsActivity : BaseActionBarActivity() {
|
||||
viewModel.events.collect {
|
||||
when (it) {
|
||||
Event.Loading -> start<LoadingActivity>()
|
||||
Event.OnboardingComplete -> startHomeActivity(isNewAccount = true)
|
||||
Event.OnboardingComplete -> startHomeActivity(isNewAccount = true, isFromOnboarding = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ class PickDisplayNameActivity : BaseActionBarActivity() {
|
||||
viewModel.events.collect {
|
||||
when (it) {
|
||||
is Event.CreateAccount -> startMessageNotificationsActivity(it.profileName)
|
||||
Event.LoadAccountComplete -> startHomeActivity(isNewAccount = false)
|
||||
Event.LoadAccountComplete -> startHomeActivity(isNewAccount = false, isFromOnboarding = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
package org.thoughtcrime.securesms.permissions;
|
||||
|
||||
import static org.session.libsignal.utilities.Util.SECURE_RANDOM;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
@ -11,9 +11,7 @@ import android.os.Build;
|
||||
import android.provider.Settings;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.Display;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Button;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
@ -28,13 +26,10 @@ import org.session.libsession.utilities.ServiceUtil;
|
||||
import org.thoughtcrime.securesms.util.LRUCache;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import network.loki.messenger.R;
|
||||
|
||||
public class Permissions {
|
||||
|
||||
private static final Map<Integer, PermissionsRequest> OUTSTANDING = new LRUCache<>(2);
|
||||
@ -172,7 +167,7 @@ public class Permissions {
|
||||
}
|
||||
|
||||
private void executePermissionsRequest(PermissionsRequest request) {
|
||||
int requestCode = new SecureRandom().nextInt(65434) + 100;
|
||||
int requestCode = SECURE_RANDOM.nextInt(65434) + 100;
|
||||
|
||||
synchronized (OUTSTANDING) {
|
||||
OUTSTANDING.put(requestCode, request);
|
||||
|
@ -37,7 +37,6 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||
import com.squareup.phrase.Phrase
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import java.io.File
|
||||
import java.security.SecureRandom
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
@ -67,6 +66,7 @@ import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.session.libsession.utilities.truncateIdForDisplay
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.session.libsignal.utilities.Util.SECURE_RANDOM
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||
import org.thoughtcrime.securesms.avatar.AvatarSelection
|
||||
import org.thoughtcrime.securesms.components.ProfilePictureView
|
||||
@ -300,7 +300,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
||||
|
||||
val userConfig = configFactory.user
|
||||
AvatarHelper.setAvatar(this, Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)!!), profilePicture)
|
||||
prefs.setProfileAvatarId(SecureRandom().nextInt() )
|
||||
prefs.setProfileAvatarId(SECURE_RANDOM.nextInt() )
|
||||
ProfileKeyUtil.setEncodedProfileKey(this, encodedProfileKey)
|
||||
|
||||
// Attempt to grab the details we require to update the profile picture
|
||||
|
@ -6,7 +6,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.thoughtcrime.securesms.ui.theme.selectedTheme
|
||||
import org.thoughtcrime.securesms.ui.theme.invalidateComposeThemeColors
|
||||
import org.thoughtcrime.securesms.util.ThemeState
|
||||
import org.thoughtcrime.securesms.util.themeState
|
||||
import javax.inject.Inject
|
||||
@ -21,6 +21,8 @@ class AppearanceSettingsViewModel @Inject constructor(private val prefs: TextSec
|
||||
prefs.setAccentColorStyle(newAccentColorStyle)
|
||||
// update UI state
|
||||
_uiState.value = prefs.themeState()
|
||||
|
||||
invalidateComposeThemeColors()
|
||||
}
|
||||
|
||||
fun setNewStyle(newThemeStyle: String) {
|
||||
@ -28,16 +30,13 @@ class AppearanceSettingsViewModel @Inject constructor(private val prefs: TextSec
|
||||
// update UI state
|
||||
_uiState.value = prefs.themeState()
|
||||
|
||||
// force compose to refresh its style reference
|
||||
selectedTheme = null
|
||||
invalidateComposeThemeColors()
|
||||
}
|
||||
|
||||
fun setNewFollowSystemSettings(followSystemSettings: Boolean) {
|
||||
prefs.setFollowSystemSettings(followSystemSettings)
|
||||
_uiState.value = prefs.themeState()
|
||||
|
||||
// force compose to refresh its style reference
|
||||
selectedTheme = null
|
||||
invalidateComposeThemeColors()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +0,0 @@
|
||||
package org.thoughtcrime.securesms.ui.theme
|
||||
|
||||
/**
|
||||
* This class holds two instances of [ThemeColors], [light] representing the [ThemeColors] to use when the system is in a
|
||||
* light theme, and [dark] representing the [ThemeColors] to use when the system is in a dark theme.
|
||||
*/
|
||||
data class ThemeColorSet(
|
||||
val light: ThemeColors,
|
||||
val dark: ThemeColors
|
||||
)
|
@ -0,0 +1,20 @@
|
||||
package org.thoughtcrime.securesms.ui.theme
|
||||
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
fun interface ThemeColorsProvider {
|
||||
@Composable
|
||||
fun get(): ThemeColors
|
||||
}
|
||||
|
||||
@Suppress("FunctionName")
|
||||
fun FollowSystemThemeColorsProvider(light: ThemeColors, dark: ThemeColors) = ThemeColorsProvider {
|
||||
when {
|
||||
isSystemInDarkTheme() -> dark
|
||||
else -> light
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("FunctionName")
|
||||
fun ThemeColorsProvider(colors: ThemeColors) = ThemeColorsProvider { colors }
|
@ -1,7 +1,5 @@
|
||||
package org.thoughtcrime.securesms.ui.theme
|
||||
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.BLUE_ACCENT
|
||||
@ -17,38 +15,25 @@ import org.session.libsession.utilities.TextSecurePreferences.Companion.YELLOW_A
|
||||
* Some behaviour is hardcoded to cater for legacy usage of people with themes already set
|
||||
* But future themes will be picked and set directly from the "Appearance" screen
|
||||
*/
|
||||
@Composable
|
||||
fun TextSecurePreferences.getComposeTheme(): ThemeColors {
|
||||
fun TextSecurePreferences.getColorsProvider(): ThemeColorsProvider {
|
||||
val selectedTheme = getThemeStyle()
|
||||
|
||||
// get the chosen primary color from the preferences
|
||||
val selectedPrimary = primaryColor()
|
||||
|
||||
// create a theme set with the appropriate primary
|
||||
val colorSet = when(selectedTheme){
|
||||
TextSecurePreferences.OCEAN_DARK,
|
||||
TextSecurePreferences.OCEAN_LIGHT -> ThemeColorSet(
|
||||
light = OceanLight(selectedPrimary),
|
||||
dark = OceanDark(selectedPrimary)
|
||||
)
|
||||
|
||||
else -> ThemeColorSet(
|
||||
light = ClassicLight(selectedPrimary),
|
||||
dark = ClassicDark(selectedPrimary)
|
||||
val isOcean = "ocean" in selectedTheme
|
||||
|
||||
val createLight = if (isOcean) ::OceanLight else ::ClassicLight
|
||||
val createDark = if (isOcean) ::OceanDark else ::ClassicDark
|
||||
|
||||
return when {
|
||||
getFollowSystemSettings() -> FollowSystemThemeColorsProvider(
|
||||
light = createLight(selectedPrimary),
|
||||
dark = createDark(selectedPrimary)
|
||||
)
|
||||
"light" in selectedTheme -> ThemeColorsProvider(createLight(selectedPrimary))
|
||||
else -> ThemeColorsProvider(createDark(selectedPrimary))
|
||||
}
|
||||
|
||||
// deliver the right set from the light/dark mode chosen
|
||||
val theme = when{
|
||||
getFollowSystemSettings() -> if(isSystemInDarkTheme()) colorSet.dark else colorSet.light
|
||||
|
||||
selectedTheme == TextSecurePreferences.CLASSIC_LIGHT ||
|
||||
selectedTheme == TextSecurePreferences.OCEAN_LIGHT -> colorSet.light
|
||||
|
||||
else -> colorSet.dark
|
||||
}
|
||||
|
||||
return theme
|
||||
}
|
||||
|
||||
fun TextSecurePreferences.primaryColor(): Color = when(getSelectedAccentColor()) {
|
||||
@ -60,6 +45,3 @@ fun TextSecurePreferences.primaryColor(): Color = when(getSelectedAccentColor())
|
||||
YELLOW_ACCENT -> primaryYellow
|
||||
else -> primaryGreen
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -20,7 +20,12 @@ import org.session.libsession.utilities.AppTextSecurePreferences
|
||||
val LocalColors = compositionLocalOf <ThemeColors> { ClassicDark() }
|
||||
val LocalType = compositionLocalOf { sessionTypography }
|
||||
|
||||
var selectedTheme: ThemeColors? = null
|
||||
var cachedColorsProvider: ThemeColorsProvider? = null
|
||||
|
||||
fun invalidateComposeThemeColors() {
|
||||
// invalidate compose theme colors
|
||||
cachedColorsProvider = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a Material2 compose theme based on user selections in SharedPreferences.
|
||||
@ -29,15 +34,15 @@ var selectedTheme: ThemeColors? = null
|
||||
fun SessionMaterialTheme(
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
// set the theme data if it hasn't been done yet
|
||||
if(selectedTheme == null) {
|
||||
// Some values can be set from the preferences, and if not should fallback to a default value
|
||||
val context = LocalContext.current
|
||||
val preferences = AppTextSecurePreferences(context)
|
||||
selectedTheme = preferences.getComposeTheme()
|
||||
}
|
||||
val context = LocalContext.current
|
||||
val preferences = AppTextSecurePreferences(context)
|
||||
|
||||
SessionMaterialTheme(colors = selectedTheme ?: ClassicDark()) { content() }
|
||||
val cachedColors = cachedColorsProvider ?: preferences.getColorsProvider().also { cachedColorsProvider = it }
|
||||
|
||||
SessionMaterialTheme(
|
||||
colors = cachedColors.get(),
|
||||
content = content
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -58,9 +63,8 @@ fun SessionMaterialTheme(
|
||||
LocalType provides sessionTypography,
|
||||
LocalContentColor provides colors.text,
|
||||
LocalTextSelectionColors provides colors.textSelectionColors,
|
||||
) {
|
||||
content()
|
||||
}
|
||||
content = content
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,60 @@
|
||||
package org.thoughtcrime.securesms.util
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import org.session.libsession.messaging.file_server.FileServerApi
|
||||
import org.session.libsession.utilities.TextSecurePreferences
|
||||
import org.session.libsignal.utilities.Log
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.time.Duration.Companion.hours
|
||||
|
||||
private val TAG: String = VersionDataFetcher::class.java.simpleName
|
||||
private val REFRESH_TIME_MS = 4.hours.inWholeMilliseconds
|
||||
|
||||
@Singleton
|
||||
class VersionDataFetcher @Inject constructor(
|
||||
private val prefs: TextSecurePreferences
|
||||
) {
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
private val fetchVersionData = Runnable {
|
||||
scope.launch {
|
||||
try {
|
||||
// Perform the version check
|
||||
val clientVersion = FileServerApi.getClientVersion()
|
||||
Log.i(TAG, "Fetched version data: $clientVersion")
|
||||
prefs.setLastVersionCheck()
|
||||
startTimedVersionCheck()
|
||||
} catch (e: Exception) {
|
||||
// We can silently ignore the error
|
||||
Log.e(TAG, "Error fetching version data", e)
|
||||
// Schedule the next check for 4 hours from now, but do not setLastVersionCheck
|
||||
// so the app will retry when the app is next foregrounded.
|
||||
startTimedVersionCheck(REFRESH_TIME_MS)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val scope = CoroutineScope(Dispatchers.Default)
|
||||
|
||||
/**
|
||||
* Schedules fetching version data.
|
||||
*
|
||||
* @param delayMillis The delay before fetching version data. Default value is 4 hours from the
|
||||
* last check or 0 if there was no previous check or if it was longer than 4 hours ago.
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun startTimedVersionCheck(
|
||||
delayMillis: Long = REFRESH_TIME_MS + prefs.getLastVersionCheck() - System.currentTimeMillis()
|
||||
) {
|
||||
stopTimedVersionCheck()
|
||||
handler.postDelayed(fetchVersionData, delayMillis)
|
||||
}
|
||||
|
||||
fun stopTimedVersionCheck() {
|
||||
handler.removeCallbacks(fetchVersionData)
|
||||
}
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
package org.thoughtcrime.securesms.webrtc
|
||||
|
||||
import android.content.Context
|
||||
import org.session.libsignal.crypto.shuffledRandom
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.session.libsignal.utilities.SettableFuture
|
||||
import org.session.libsignal.utilities.Util.SECURE_RANDOM
|
||||
import org.thoughtcrime.securesms.webrtc.video.Camera
|
||||
import org.thoughtcrime.securesms.webrtc.video.CameraEventListener
|
||||
import org.thoughtcrime.securesms.webrtc.video.CameraState
|
||||
@ -22,9 +24,7 @@ import org.webrtc.SurfaceTextureHelper
|
||||
import org.webrtc.VideoSink
|
||||
import org.webrtc.VideoSource
|
||||
import org.webrtc.VideoTrack
|
||||
import java.security.SecureRandom
|
||||
import java.util.concurrent.ExecutionException
|
||||
import kotlin.random.asKotlinRandom
|
||||
|
||||
class PeerConnectionWrapper(private val context: Context,
|
||||
private val factory: PeerConnectionFactory,
|
||||
@ -49,8 +49,7 @@ class PeerConnectionWrapper(private val context: Context,
|
||||
private var isInitiator = false
|
||||
|
||||
private fun initPeerConnection() {
|
||||
val random = SecureRandom().asKotlinRandom()
|
||||
val iceServers = listOf("freyr","angus","hereford","holstein", "brahman").shuffled(random).take(2).map { sub ->
|
||||
val iceServers = listOf("freyr","angus","hereford","holstein", "brahman").shuffledRandom().take(2).map { sub ->
|
||||
PeerConnection.IceServer.builder("turn:$sub.getsession.org")
|
||||
.setUsername("session202111")
|
||||
.setPassword("053c268164bc7bd7")
|
||||
|
@ -0,0 +1,52 @@
|
||||
package org.session.libsignal.utilities
|
||||
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.core.IsEqual.equalTo
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
|
||||
@RunWith(Parameterized::class)
|
||||
class SnodeVersionTest(
|
||||
private val v1: String,
|
||||
private val v2: String,
|
||||
private val expectedEqual: Boolean,
|
||||
private val expectedLessThan: Boolean
|
||||
) {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@Parameterized.Parameters(name = "{index}: testVersion({0},{1}) = (equalTo: {2}, lessThan: {3})")
|
||||
fun data(): Collection<Array<Any>> = listOf(
|
||||
arrayOf("1", "1", true, false),
|
||||
arrayOf("1", "2", false, true),
|
||||
arrayOf("2", "1", false, false),
|
||||
arrayOf("1.0", "1", true, false),
|
||||
arrayOf("1.0", "1.0.0", true, false),
|
||||
arrayOf("1.0", "1.0.0.0", true, false),
|
||||
arrayOf("1.0", "1.0.0.0.0.0", true, false),
|
||||
arrayOf("2.0", "1.2", false, false),
|
||||
arrayOf("1.0.0.0", "1.0.0.1", false, true),
|
||||
// Snode.Version only considers the first 4 integers, so these are equal
|
||||
arrayOf("1.0.0.0", "1.0.0.0.1", true, false),
|
||||
arrayOf("1.0.0.1", "1.0.0.1", true, false),
|
||||
// parts can be up to 16 bits, around 65,535
|
||||
arrayOf("65535.65535.65535.65535", "65535.65535.65535.65535", true, false),
|
||||
// values higher than this are coerced to 65535 (:
|
||||
arrayOf("65535.65535.65535.65535", "65535.65535.65535.99999", true, false),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testVersionEqual() {
|
||||
val version1 = Snode.Version(v1)
|
||||
val version2 = Snode.Version(v2)
|
||||
assertThat(version1 == version2, equalTo(expectedEqual))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testVersionOnePartLessThan() {
|
||||
val version1 = Snode.Version(v1)
|
||||
val version2 = Snode.Version(v2)
|
||||
assertThat(version1 < version2, equalTo(expectedLessThan))
|
||||
}
|
||||
}
|
@ -1 +1 @@
|
||||
Subproject commit 626b6628a2af8fff798042416b3b469b8bfc6ecf
|
||||
Subproject commit 0193c36e0dad461385d6407a00f33b7314e6d740
|
@ -30,6 +30,7 @@ set(SOURCES
|
||||
config_base.cpp
|
||||
contacts.cpp
|
||||
conversation.cpp
|
||||
blinded_key.cpp
|
||||
util.cpp)
|
||||
|
||||
add_library( # Sets the name of the library.
|
||||
|
34
libsession-util/src/main/cpp/blinded_key.cpp
Normal file
34
libsession-util/src/main/cpp/blinded_key.cpp
Normal file
@ -0,0 +1,34 @@
|
||||
#include <jni.h>
|
||||
#include <session/blinding.hpp>
|
||||
|
||||
#include "util.h"
|
||||
#include "jni_utils.h"
|
||||
|
||||
//
|
||||
// Created by Thomas Ruffie on 29/7/2024.
|
||||
//
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jobject JNICALL
|
||||
Java_network_loki_messenger_libsession_1util_util_BlindKeyAPI_blindVersionKeyPair(JNIEnv *env,
|
||||
jobject thiz,
|
||||
jbyteArray ed25519_secret_key) {
|
||||
return jni_utils::run_catching_cxx_exception_or_throws<jobject>(env, [=] {
|
||||
const auto [pk, sk] = session::blind_version_key_pair(util::ustring_from_bytes(env, ed25519_secret_key));
|
||||
|
||||
jclass kp_class = env->FindClass("network/loki/messenger/libsession_util/util/KeyPair");
|
||||
jmethodID kp_constructor = env->GetMethodID(kp_class, "<init>", "([B[B)V");
|
||||
return env->NewObject(kp_class, kp_constructor, util::bytes_from_ustring(env, {pk.data(), pk.size()}), util::bytes_from_ustring(env, {sk.data(), sk.size()}));
|
||||
});
|
||||
}
|
||||
extern "C"
|
||||
JNIEXPORT jbyteArray JNICALL
|
||||
Java_network_loki_messenger_libsession_1util_util_BlindKeyAPI_blindVersionSign(JNIEnv *env,
|
||||
jobject thiz,
|
||||
jbyteArray ed25519_secret_key,
|
||||
jlong timestamp) {
|
||||
return jni_utils::run_catching_cxx_exception_or_throws<jbyteArray>(env, [=] {
|
||||
auto bytes = session::blind_version_sign(util::ustring_from_bytes(env, ed25519_secret_key), session::Platform::android, timestamp);
|
||||
return util::bytes_from_ustring(env, bytes);
|
||||
});
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package network.loki.messenger.libsession_util.util
|
||||
|
||||
object BlindKeyAPI {
|
||||
private val loadLibrary by lazy {
|
||||
System.loadLibrary("session_util")
|
||||
}
|
||||
|
||||
init {
|
||||
// Ensure the library is loaded at initialization
|
||||
loadLibrary
|
||||
}
|
||||
|
||||
external fun blindVersionKeyPair(ed25519SecretKey: ByteArray): KeyPair
|
||||
external fun blindVersionSign(ed25519SecretKey: ByteArray, timestamp: Long): ByteArray
|
||||
}
|
@ -1,18 +1,22 @@
|
||||
package org.session.libsession.messaging.file_server
|
||||
|
||||
import android.util.Base64
|
||||
import network.loki.messenger.libsession_util.util.BlindKeyAPI
|
||||
import nl.komponents.kovenant.Promise
|
||||
import nl.komponents.kovenant.functional.map
|
||||
import okhttp3.Headers
|
||||
import okhttp3.Headers.Companion.toHeaders
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import okhttp3.MediaType
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.RequestBody
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.snode.OnionRequestAPI
|
||||
import org.session.libsession.snode.utilities.await
|
||||
import org.session.libsignal.utilities.HTTP
|
||||
import org.session.libsignal.utilities.JsonUtil
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.session.libsignal.utilities.toHexString
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
object FileServerApi {
|
||||
|
||||
@ -23,6 +27,7 @@ object FileServerApi {
|
||||
sealed class Error(message: String) : Exception(message) {
|
||||
object ParsingFailed : Error("Invalid response.")
|
||||
object InvalidURL : Error("Invalid URL.")
|
||||
object NoEd25519KeyPair : Error("Couldn't find ed25519 key pair.")
|
||||
}
|
||||
|
||||
data class Request(
|
||||
@ -105,4 +110,50 @@ object FileServerApi {
|
||||
val request = Request(verb = HTTP.Verb.GET, endpoint = "file/$file")
|
||||
return send(request)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current version of session
|
||||
* This is effectively proxying (and caching) the response from the github release
|
||||
* page.
|
||||
*
|
||||
* Note that the value is cached and can be up to 30 minutes out of date normally, and up to 24
|
||||
* hours out of date if we cannot reach the Github API for some reason.
|
||||
*
|
||||
* https://github.com/oxen-io/session-file-server/blob/dev/doc/api.yaml#L119
|
||||
*/
|
||||
suspend fun getClientVersion(): VersionData {
|
||||
// Generate the auth signature
|
||||
val secretKey = MessagingModuleConfiguration.shared.getUserED25519KeyPair()?.secretKey?.asBytes
|
||||
?: throw (Error.NoEd25519KeyPair)
|
||||
|
||||
val blindedKeys = BlindKeyAPI.blindVersionKeyPair(secretKey)
|
||||
val timestamp = System.currentTimeMillis().milliseconds.inWholeSeconds // The current timestamp in seconds
|
||||
val signature = BlindKeyAPI.blindVersionSign(secretKey, timestamp)
|
||||
|
||||
// The hex encoded version-blinded public key with a 07 prefix
|
||||
val blindedPkHex = "07" + blindedKeys.pubKey.toHexString()
|
||||
|
||||
val request = Request(
|
||||
verb = HTTP.Verb.GET,
|
||||
endpoint = "session_version",
|
||||
queryParameters = mapOf("platform" to "android"),
|
||||
headers = mapOf(
|
||||
"X-FS-Pubkey" to blindedPkHex,
|
||||
"X-FS-Timestamp" to timestamp.toString(),
|
||||
"X-FS-Signature" to Base64.encodeToString(signature, Base64.NO_WRAP)
|
||||
)
|
||||
)
|
||||
|
||||
// transform the promise into a coroutine
|
||||
val result = send(request).await()
|
||||
|
||||
// map out the result
|
||||
return JsonUtil.fromJson(result, Map::class.java).let {
|
||||
VersionData(
|
||||
statusCode = it["status_code"] as? Int ?: 0,
|
||||
version = it["result"] as? String ?: "",
|
||||
updated = it["updated"] as? Double ?: 0.0
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package org.session.libsession.messaging.file_server
|
||||
|
||||
data class VersionData(
|
||||
val statusCode: Int, // The value 200. Included for backwards compatibility, and may be removed someday.
|
||||
val version: String, // The Session version.
|
||||
val updated: Double // The unix timestamp when this version was retrieved from Github; this can be up to 24 hours ago in case of consistent fetch errors, though normally will be within the last 30 minutes.
|
||||
)
|
@ -63,7 +63,6 @@ data class ConfigurationSyncJob(val destination: Destination): Job {
|
||||
// return a list of batch request objects
|
||||
val snodeMessage = MessageSender.buildConfigMessageToSnode(destination.destinationPublicKey(), message)
|
||||
val authenticated = SnodeAPI.buildAuthenticatedStoreBatchInfo(
|
||||
destination.destinationPublicKey(),
|
||||
config.configNamespace(),
|
||||
snodeMessage
|
||||
) ?: return@map null // this entry will be null otherwise
|
||||
|
@ -12,11 +12,11 @@ import org.session.libsession.utilities.Util.equals
|
||||
import org.session.libsession.utilities.recipients.Recipient
|
||||
import org.session.libsignal.streams.ProfileCipherInputStream
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.session.libsignal.utilities.Util.SECURE_RANDOM
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.io.InputStream
|
||||
import java.security.SecureRandom
|
||||
import java.util.concurrent.ConcurrentSkipListSet
|
||||
|
||||
class RetrieveProfileAvatarJob(private val profileAvatar: String?, val recipientAddress: Address): Job {
|
||||
@ -64,7 +64,7 @@ class RetrieveProfileAvatarJob(private val profileAvatar: String?, val recipient
|
||||
Log.w(TAG, "Removing profile avatar for: " + recipient.address.serialize())
|
||||
|
||||
if (recipient.isLocalNumber) {
|
||||
setProfileAvatarId(context, SecureRandom().nextInt())
|
||||
setProfileAvatarId(context, SECURE_RANDOM.nextInt())
|
||||
setProfilePictureURL(context, null)
|
||||
}
|
||||
|
||||
@ -83,7 +83,7 @@ class RetrieveProfileAvatarJob(private val profileAvatar: String?, val recipient
|
||||
decryptDestination.renameTo(AvatarHelper.getAvatarFile(context, recipient.address))
|
||||
|
||||
if (recipient.isLocalNumber) {
|
||||
setProfileAvatarId(context, SecureRandom().nextInt())
|
||||
setProfileAvatarId(context, SECURE_RANDOM.nextInt())
|
||||
setProfilePictureURL(context, profileAvatar)
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ import org.session.libsession.messaging.jobs.JobQueue
|
||||
import org.session.libsession.messaging.jobs.MessageReceiveParameters
|
||||
import org.session.libsession.snode.SnodeAPI
|
||||
import org.session.libsession.utilities.GroupUtil
|
||||
import org.session.libsignal.crypto.getRandomElementOrNull
|
||||
import org.session.libsignal.crypto.secureRandomOrNull
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.session.libsignal.utilities.Namespace
|
||||
import org.session.libsignal.utilities.defaultRequiresAuth
|
||||
@ -104,7 +104,7 @@ class ClosedGroupPollerV2 {
|
||||
fun poll(groupPublicKey: String): Promise<Unit, Exception> {
|
||||
if (!isPolling(groupPublicKey)) { return Promise.of(Unit) }
|
||||
val promise = SnodeAPI.getSwarm(groupPublicKey).bind { swarm ->
|
||||
val snode = swarm.getRandomElementOrNull() ?: throw InsufficientSnodesException() // Should be cryptographically secure
|
||||
val snode = swarm.secureRandomOrNull() ?: throw InsufficientSnodesException() // Should be cryptographically secure
|
||||
if (!isPolling(groupPublicKey)) { throw PollingCanceledException() }
|
||||
val currentForkInfo = SnodeAPI.forkInfo
|
||||
when {
|
||||
|
@ -19,8 +19,6 @@ import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.jobs.BatchMessageReceiveJob
|
||||
import org.session.libsession.messaging.jobs.JobQueue
|
||||
import org.session.libsession.messaging.jobs.MessageReceiveParameters
|
||||
import org.session.libsession.messaging.messages.control.SharedConfigurationMessage
|
||||
import org.session.libsession.messaging.sending_receiving.MessageReceiver
|
||||
import org.session.libsession.snode.RawResponse
|
||||
import org.session.libsession.snode.SnodeAPI
|
||||
import org.session.libsession.snode.SnodeModule
|
||||
@ -29,7 +27,7 @@ import org.session.libsignal.utilities.Base64
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.session.libsignal.utilities.Namespace
|
||||
import org.session.libsignal.utilities.Snode
|
||||
import java.security.SecureRandom
|
||||
import org.session.libsignal.utilities.Util.SECURE_RANDOM
|
||||
import java.util.Timer
|
||||
import java.util.TimerTask
|
||||
import kotlin.time.Duration.Companion.days
|
||||
@ -106,7 +104,7 @@ class Poller(private val configFactory: ConfigFactoryProtocol, debounceTimer: Ti
|
||||
val swarm = SnodeModule.shared.storage.getSwarm(userPublicKey) ?: setOf()
|
||||
val unusedSnodes = swarm.subtract(usedSnodes)
|
||||
if (unusedSnodes.isNotEmpty()) {
|
||||
val index = SecureRandom().nextInt(unusedSnodes.size)
|
||||
val index = SECURE_RANDOM.nextInt(unusedSnodes.size)
|
||||
val nextSnode = unusedSnodes.elementAt(index)
|
||||
usedSnodes.add(nextSnode)
|
||||
Log.d(TAG, "Polling $nextSnode.")
|
||||
@ -142,8 +140,7 @@ class Poller(private val configFactory: ConfigFactoryProtocol, debounceTimer: Ti
|
||||
val messages = rawMessages["messages"] as? List<*>
|
||||
val processed = if (!messages.isNullOrEmpty()) {
|
||||
SnodeAPI.updateLastMessageHashValueIfPossible(snode, userPublicKey, messages, namespace)
|
||||
SnodeAPI.removeDuplicates(userPublicKey, messages, namespace, true).mapNotNull { messageBody ->
|
||||
val rawMessageAsJSON = messageBody as? Map<*, *> ?: return@mapNotNull null
|
||||
SnodeAPI.removeDuplicates(userPublicKey, messages, namespace, true).mapNotNull { rawMessageAsJSON ->
|
||||
val hashValue = rawMessageAsJSON["hash"] as? String ?: return@mapNotNull null
|
||||
val b64EncodedBody = rawMessageAsJSON["data"] as? String ?: return@mapNotNull null
|
||||
val timestamp = rawMessageAsJSON["t"] as? Long ?: SnodeAPI.nowWithOffset
|
||||
|
@ -28,7 +28,7 @@ object MessageWrapper {
|
||||
val webSocketMessage = createWebSocketMessage(envelope)
|
||||
return webSocketMessage.toByteArray()
|
||||
} catch (e: Exception) {
|
||||
throw if (e is Error) { e } else { Error.FailedToWrapData }
|
||||
throw if (e is Error) e else Error.FailedToWrapData
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,15 +49,15 @@ object MessageWrapper {
|
||||
|
||||
private fun createWebSocketMessage(envelope: Envelope): WebSocketMessage {
|
||||
try {
|
||||
val requestBuilder = WebSocketRequestMessage.newBuilder()
|
||||
requestBuilder.verb = "PUT"
|
||||
requestBuilder.path = "/api/v1/message"
|
||||
requestBuilder.id = SecureRandom.getInstance("SHA1PRNG").nextLong()
|
||||
requestBuilder.body = envelope.toByteString()
|
||||
val messageBuilder = WebSocketMessage.newBuilder()
|
||||
messageBuilder.request = requestBuilder.build()
|
||||
messageBuilder.type = WebSocketMessage.Type.REQUEST
|
||||
return messageBuilder.build()
|
||||
return WebSocketMessage.newBuilder().apply {
|
||||
request = WebSocketRequestMessage.newBuilder().apply {
|
||||
verb = "PUT"
|
||||
path = "/api/v1/message"
|
||||
id = SecureRandom.getInstance("SHA1PRNG").nextLong()
|
||||
body = envelope.toByteString()
|
||||
}.build()
|
||||
type = WebSocketMessage.Type.REQUEST
|
||||
}.build()
|
||||
} catch (e: Exception) {
|
||||
Log.d("Loki", "Failed to wrap envelope in web socket message: ${e.message}.")
|
||||
throw Error.FailedToWrapEnvelopeInWebSocketMessage
|
||||
|
@ -10,11 +10,10 @@ import okhttp3.Request
|
||||
import org.session.libsession.messaging.file_server.FileServerApi
|
||||
import org.session.libsession.utilities.AESGCM
|
||||
import org.session.libsession.utilities.AESGCM.EncryptionResult
|
||||
import org.session.libsession.utilities.Util
|
||||
import org.session.libsession.utilities.getBodyForOnionRequest
|
||||
import org.session.libsession.utilities.getHeadersForOnionRequest
|
||||
import org.session.libsignal.crypto.getRandomElement
|
||||
import org.session.libsignal.crypto.getRandomElementOrNull
|
||||
import org.session.libsignal.crypto.secureRandom
|
||||
import org.session.libsignal.crypto.secureRandomOrNull
|
||||
import org.session.libsignal.database.LokiAPIDatabaseProtocol
|
||||
import org.session.libsignal.utilities.Base64
|
||||
import org.session.libsignal.utilities.Broadcaster
|
||||
@ -149,7 +148,7 @@ object OnionRequestAPI {
|
||||
val reusableGuardSnodeCount = reusableGuardSnodes.count()
|
||||
if (unusedSnodes.count() < (targetGuardSnodeCount - reusableGuardSnodeCount)) { throw InsufficientSnodesException() }
|
||||
fun getGuardSnode(): Promise<Snode, Exception> {
|
||||
val candidate = unusedSnodes.getRandomElementOrNull()
|
||||
val candidate = unusedSnodes.secureRandomOrNull()
|
||||
?: return Promise.ofFail(InsufficientSnodesException())
|
||||
unusedSnodes = unusedSnodes.minus(candidate)
|
||||
Log.d("Loki", "Testing guard snode: $candidate.")
|
||||
@ -191,7 +190,7 @@ object OnionRequestAPI {
|
||||
// Don't test path snodes as this would reveal the user's IP to them
|
||||
guardSnodes.minus(reusableGuardSnodes).map { guardSnode ->
|
||||
val result = listOf( guardSnode ) + (0 until (pathSize - 1)).mapIndexed() { index, _ ->
|
||||
var pathSnode = unusedSnodes.getRandomElement()
|
||||
var pathSnode = unusedSnodes.secureRandom()
|
||||
|
||||
// remove the snode from the unused list and return it
|
||||
unusedSnodes = unusedSnodes.minus(pathSnode)
|
||||
@ -228,9 +227,9 @@ object OnionRequestAPI {
|
||||
OnionRequestAPI.guardSnodes = guardSnodes
|
||||
fun getPath(paths: List<Path>): Path {
|
||||
return if (snodeToExclude != null) {
|
||||
paths.filter { !it.contains(snodeToExclude) }.getRandomElement()
|
||||
paths.filter { !it.contains(snodeToExclude) }.secureRandom()
|
||||
} else {
|
||||
paths.getRandomElement()
|
||||
paths.secureRandom()
|
||||
}
|
||||
}
|
||||
when {
|
||||
@ -273,7 +272,7 @@ object OnionRequestAPI {
|
||||
path.removeAt(snodeIndex)
|
||||
val unusedSnodes = SnodeAPI.snodePool.minus(oldPaths.flatten())
|
||||
if (unusedSnodes.isEmpty()) { throw InsufficientSnodesException() }
|
||||
path.add(unusedSnodes.getRandomElement())
|
||||
path.add(unusedSnodes.secureRandom())
|
||||
// Don't test the new snode as this would reveal the user's IP
|
||||
oldPaths.removeAt(pathIndex)
|
||||
val newPaths = oldPaths + listOf( path )
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,13 @@
|
||||
package org.session.libsession.snode.utilities
|
||||
|
||||
import nl.komponents.kovenant.Promise
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
suspend fun <T, E: Throwable> Promise<T, E>.await(): T {
|
||||
return suspendCoroutine { cont ->
|
||||
success(cont::resume)
|
||||
fail(cont::resumeWithException)
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import org.session.libsession.R
|
||||
import org.session.libsession.utilities.TextSecurePreferences.Companion
|
||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.AUTOPLAY_AUDIO_MESSAGES
|
||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.CALL_NOTIFICATIONS_ENABLED
|
||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.CLASSIC_DARK
|
||||
@ -20,6 +21,7 @@ import org.session.libsession.utilities.TextSecurePreferences.Companion.CLASSIC_
|
||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.FOLLOW_SYSTEM_SETTINGS
|
||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.HIDE_PASSWORD
|
||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.LAST_VACUUM_TIME
|
||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.LAST_VERSION_CHECK
|
||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.LEGACY_PREF_KEY_SELECTED_UI_MODE
|
||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.OCEAN_DARK
|
||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.OCEAN_LIGHT
|
||||
@ -186,6 +188,8 @@ interface TextSecurePreferences {
|
||||
fun clearAll()
|
||||
fun getHidePassword(): Boolean
|
||||
fun setHidePassword(value: Boolean)
|
||||
fun getLastVersionCheck(): Long
|
||||
fun setLastVersionCheck()
|
||||
|
||||
companion object {
|
||||
val TAG = TextSecurePreferences::class.simpleName
|
||||
@ -272,6 +276,7 @@ interface TextSecurePreferences {
|
||||
const val AUTOPLAY_AUDIO_MESSAGES = "pref_autoplay_audio"
|
||||
const val FINGERPRINT_KEY_GENERATED = "fingerprint_key_generated"
|
||||
const val SELECTED_ACCENT_COLOR = "selected_accent_color"
|
||||
const val LAST_VERSION_CHECK = "pref_last_version_check"
|
||||
|
||||
const val HAS_RECEIVED_LEGACY_CONFIG = "has_received_legacy_config"
|
||||
const val HAS_FORCED_NEW_CONFIG = "has_forced_new_config"
|
||||
@ -1541,6 +1546,14 @@ class AppTextSecurePreferences @Inject constructor(
|
||||
setLongPreference(LAST_VACUUM_TIME, System.currentTimeMillis())
|
||||
}
|
||||
|
||||
override fun getLastVersionCheck(): Long {
|
||||
return getLongPreference(LAST_VERSION_CHECK, 0)
|
||||
}
|
||||
|
||||
override fun setLastVersionCheck() {
|
||||
setLongPreference(LAST_VERSION_CHECK, System.currentTimeMillis())
|
||||
}
|
||||
|
||||
override fun setShownCallNotification(): Boolean {
|
||||
val previousValue = getBooleanPreference(SHOWN_CALL_NOTIFICATION, false)
|
||||
if (previousValue) return false
|
||||
|
@ -13,6 +13,7 @@ import android.text.TextUtils
|
||||
import android.text.style.StyleSpan
|
||||
import org.session.libsignal.utilities.Log
|
||||
import org.session.libsignal.utilities.Base64
|
||||
import org.session.libsignal.utilities.Util.SECURE_RANDOM
|
||||
import java.io.*
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.security.SecureRandom
|
||||
@ -292,15 +293,10 @@ object Util {
|
||||
@JvmStatic
|
||||
fun getSecretBytes(size: Int): ByteArray {
|
||||
val secret = ByteArray(size)
|
||||
getSecureRandom().nextBytes(secret)
|
||||
SECURE_RANDOM.nextBytes(secret)
|
||||
return secret
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getSecureRandom(): SecureRandom {
|
||||
return SecureRandom()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getFirstNonEmpty(vararg values: String?): String? {
|
||||
for (value in values) {
|
||||
@ -317,18 +313,14 @@ object Util {
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun <T> getRandomElement(elements: Array<T>): T {
|
||||
return elements[SecureRandom().nextInt(elements.size)]
|
||||
}
|
||||
fun <T> getRandomElement(elements: Array<T>): T = elements[SECURE_RANDOM.nextInt(elements.size)]
|
||||
|
||||
@JvmStatic
|
||||
fun getBoldedString(value: String?): CharSequence {
|
||||
if (value.isNullOrEmpty()) { return "" }
|
||||
val spanned = SpannableString(value)
|
||||
spanned.setSpan(StyleSpan(Typeface.BOLD), 0,
|
||||
spanned.length,
|
||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
return spanned
|
||||
return SpannableString(value).also {
|
||||
it.setSpan(StyleSpan(Typeface.BOLD), 0, it.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@ -366,34 +358,6 @@ object Util {
|
||||
val digitGroups = (Math.log10(sizeBytes.toDouble()) / Math.log10(1024.0)).toInt()
|
||||
return DecimalFormat("#,##0.#").format(sizeBytes / Math.pow(1024.0, digitGroups.toDouble())) + " " + units[digitGroups]
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two version strings (for example "1.8.0")
|
||||
*
|
||||
* @param version1 the first version string to compare.
|
||||
* @param version2 the second version string to compare.
|
||||
* @return an integer indicating the result of the comparison:
|
||||
* - 0 if the versions are equal
|
||||
* - a positive number if version1 is greater than version2
|
||||
* - a negative number if version1 is less than version2
|
||||
*/
|
||||
@JvmStatic
|
||||
fun compareVersions(version1: String, version2: String): Int {
|
||||
val parts1 = version1.split(".").map { it.toIntOrNull() ?: 0 }
|
||||
val parts2 = version2.split(".").map { it.toIntOrNull() ?: 0 }
|
||||
|
||||
val maxLength = maxOf(parts1.size, parts2.size)
|
||||
val paddedParts1 = parts1 + List(maxLength - parts1.size) { 0 }
|
||||
val paddedParts2 = parts2 + List(maxLength - parts2.size) { 0 }
|
||||
|
||||
for (i in 0 until maxLength) {
|
||||
val compare = paddedParts1[i].compareTo(paddedParts2[i])
|
||||
if (compare != 0) {
|
||||
return compare
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
fun <T, R> T.runIf(condition: Boolean, block: T.() -> R): R where T: R = if (condition) block() else this
|
||||
@ -430,6 +394,12 @@ fun <E, K: Any, V: Any> Iterable<E>.associateByNotNull(
|
||||
for(e in this) { it[keySelector(e) ?: continue] = valueTransform(e) ?: continue }
|
||||
}
|
||||
|
||||
fun <K: Any, V: Any, W : Any> Map<K, V>.mapValuesNotNull(
|
||||
valueTransform: (Map.Entry<K, V>) -> W?
|
||||
): Map<K, W> = mutableMapOf<K, W>().also {
|
||||
for(e in this) { it[e.key] = valueTransform(e) ?: continue }
|
||||
}
|
||||
|
||||
/**
|
||||
* Groups elements of the original collection by the key returned by the given [keySelector] function
|
||||
* applied to each element and returns a map where each group key is associated with a list of
|
||||
@ -440,3 +410,23 @@ fun <E, K: Any, V: Any> Iterable<E>.associateByNotNull(
|
||||
inline fun <E, K> Iterable<E>.groupByNotNull(keySelector: (E) -> K?): Map<K, List<E>> = LinkedHashMap<K, MutableList<E>>().also {
|
||||
forEach { e -> keySelector(e)?.let { k -> it.getOrPut(k) { mutableListOf() } += e } }
|
||||
}
|
||||
|
||||
/**
|
||||
* Analogous to [buildMap], this function creates a [MutableMap] and populates it using the given [action].
|
||||
*/
|
||||
inline fun <K, V> buildMutableMap(action: MutableMap<K, V>.() -> Unit): MutableMap<K, V> =
|
||||
mutableMapOf<K, V>().apply(action)
|
||||
|
||||
/**
|
||||
* Converts a list of Pairs into a Map, filtering out any Pairs where the value is null.
|
||||
*
|
||||
* @param pairs The list of Pairs to convert.
|
||||
* @return A Map with non-null values.
|
||||
*/
|
||||
fun <K : Any, V : Any> Iterable<Pair<K, V?>>.toMapNotNull(): Map<K, V> =
|
||||
associateByNotNull(Pair<K, V?>::first, Pair<K, V?>::second)
|
||||
|
||||
fun Sequence<String>.toByteArray(): ByteArray = ByteArrayOutputStream().use { output ->
|
||||
forEach { it.byteInputStream().use { input -> input.copyTo(output) } }
|
||||
output.toByteArray()
|
||||
}
|
||||
|
@ -1,19 +1,23 @@
|
||||
package org.session.libsignal.crypto
|
||||
|
||||
import java.security.SecureRandom
|
||||
import org.session.libsignal.utilities.Util.SECURE_RANDOM
|
||||
|
||||
/**
|
||||
* Uses `SecureRandom` to pick an element from this collection.
|
||||
*/
|
||||
fun <T> Collection<T>.getRandomElementOrNull(): T? {
|
||||
fun <T> Collection<T>.secureRandomOrNull(): T? {
|
||||
if (isEmpty()) return null
|
||||
val index = SecureRandom().nextInt(size) // SecureRandom() should be cryptographically secure
|
||||
val index = SECURE_RANDOM.nextInt(size) // SecureRandom should be cryptographically secure
|
||||
return elementAtOrNull(index)
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses `SecureRandom` to pick an element from this collection.
|
||||
*
|
||||
* @throws [NullPointerException] if the [Collection] is empty
|
||||
*/
|
||||
fun <T> Collection<T>.getRandomElement(): T {
|
||||
return getRandomElementOrNull()!!
|
||||
fun <T> Collection<T>.secureRandom(): T {
|
||||
return secureRandomOrNull()!!
|
||||
}
|
||||
|
||||
fun <T> Collection<T>.shuffledRandom(): List<T> = shuffled(SECURE_RANDOM)
|
||||
|
@ -1,13 +1,13 @@
|
||||
package org.session.libsignal.streams;
|
||||
|
||||
import static org.session.libsignal.crypto.CipherUtil.CIPHER_LOCK;
|
||||
import static org.session.libsignal.utilities.Util.SECURE_RANDOM;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
@ -80,7 +80,7 @@ public class ProfileCipherOutputStream extends DigestingOutputStream {
|
||||
|
||||
private byte[] generateNonce() {
|
||||
byte[] nonce = new byte[12];
|
||||
new SecureRandom().nextBytes(nonce);
|
||||
SECURE_RANDOM.nextBytes(nonce);
|
||||
return nonce;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
package org.session.libsignal.utilities;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* <p>Encodes and decodes to and from Base64 notation.</p>
|
||||
* <p>Homepage: <a href="http://iharder.net/base64">http://iharder.net/base64</a>.</p>
|
||||
@ -714,7 +716,7 @@ public class Base64
|
||||
* @throws NullPointerException if source array is null
|
||||
* @since 1.4
|
||||
*/
|
||||
public static String encodeBytes( byte[] source ) {
|
||||
public static String encodeBytes(@NonNull byte[] source ) {
|
||||
// Since we're not going to have the GZIP encoding turned on,
|
||||
// we're not going to have an java.io.IOException thrown, so
|
||||
// we should not force the user to have to catch it.
|
||||
|
@ -5,8 +5,7 @@ import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.Response
|
||||
import java.security.SecureRandom
|
||||
import org.session.libsignal.utilities.Util.SECURE_RANDOM
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.net.ssl.SSLContext
|
||||
@ -35,7 +34,7 @@ object HTTP {
|
||||
override fun getAcceptedIssuers(): Array<X509Certificate> { return arrayOf() }
|
||||
}
|
||||
val sslContext = SSLContext.getInstance("SSL")
|
||||
sslContext.init(null, arrayOf( trustManager ), SecureRandom())
|
||||
sslContext.init(null, arrayOf( trustManager ), SECURE_RANDOM)
|
||||
OkHttpClient().newBuilder()
|
||||
.sslSocketFactory(sslContext.socketFactory, trustManager)
|
||||
.hostnameVerifier { _, _ -> true }
|
||||
@ -55,7 +54,7 @@ object HTTP {
|
||||
override fun getAcceptedIssuers(): Array<X509Certificate> { return arrayOf() }
|
||||
}
|
||||
val sslContext = SSLContext.getInstance("SSL")
|
||||
sslContext.init(null, arrayOf( trustManager ), SecureRandom())
|
||||
sslContext.init(null, arrayOf( trustManager ), SECURE_RANDOM)
|
||||
return OkHttpClient().newBuilder()
|
||||
.sslSocketFactory(sslContext.socketFactory, trustManager)
|
||||
.hostnameVerifier { _, _ -> true }
|
||||
|
@ -1,9 +1,25 @@
|
||||
package org.session.libsignal.utilities
|
||||
|
||||
class Snode(val address: String, val port: Int, val publicKeySet: KeySet?, val version: String) {
|
||||
import android.annotation.SuppressLint
|
||||
import android.util.LruCache
|
||||
|
||||
/**
|
||||
* Create a Snode from a "-" delimited String if valid, null otherwise.
|
||||
*/
|
||||
fun Snode(string: String): Snode? {
|
||||
val components = string.split("-")
|
||||
val address = components[0]
|
||||
val port = components.getOrNull(1)?.toIntOrNull() ?: return null
|
||||
val ed25519Key = components.getOrNull(2) ?: return null
|
||||
val x25519Key = components.getOrNull(3) ?: return null
|
||||
val version = components.getOrNull(4)?.let(Snode::Version) ?: Snode.Version.ZERO
|
||||
return Snode(address, port, Snode.KeySet(ed25519Key, x25519Key), version)
|
||||
}
|
||||
|
||||
class Snode(val address: String, val port: Int, val publicKeySet: KeySet?, val version: Version) {
|
||||
val ip: String get() = address.removePrefix("https://")
|
||||
|
||||
public enum class Method(val rawValue: String) {
|
||||
enum class Method(val rawValue: String) {
|
||||
GetSwarm("get_snodes_for_pubkey"),
|
||||
Retrieve("retrieve"),
|
||||
SendMessage("store"),
|
||||
@ -19,17 +35,37 @@ class Snode(val address: String, val port: Int, val publicKeySet: KeySet?, val v
|
||||
|
||||
data class KeySet(val ed25519Key: String, val x25519Key: String)
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return if (other is Snode) {
|
||||
address == other.address && port == other.port
|
||||
} else {
|
||||
false
|
||||
override fun equals(other: Any?) = other is Snode && address == other.address && port == other.port
|
||||
override fun hashCode(): Int = address.hashCode() xor port.hashCode()
|
||||
override fun toString(): String = "$address:$port"
|
||||
|
||||
companion object {
|
||||
private val CACHE = LruCache<String, Version>(100)
|
||||
|
||||
@SuppressLint("NotConstructor")
|
||||
@Synchronized
|
||||
fun Version(value: String) = CACHE[value] ?: Snode.Version(value).also { CACHE.put(value, it) }
|
||||
|
||||
fun Version(parts: List<Int>) = Version(parts.joinToString("."))
|
||||
}
|
||||
|
||||
@JvmInline
|
||||
value class Version(val value: ULong) {
|
||||
companion object {
|
||||
val ZERO = Version(0UL)
|
||||
private const val MASK_BITS = 16
|
||||
private const val MASK = 0xFFFFUL
|
||||
}
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return address.hashCode() xor port.hashCode()
|
||||
}
|
||||
internal constructor(value: String): this(
|
||||
value.splitToSequence(".")
|
||||
.take(4)
|
||||
.map { it.toULongOrNull() ?: 0UL }
|
||||
.foldIndexed(0UL) { i, acc, it ->
|
||||
it.coerceAtMost(MASK) shl (3 - i) * MASK_BITS or acc
|
||||
}
|
||||
)
|
||||
|
||||
override fun toString(): String { return "$address:$port" }
|
||||
operator fun compareTo(other: Version): Int = value.compareTo(other.value)
|
||||
}
|
||||
}
|
||||
|
@ -12,12 +12,10 @@ import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class Util {
|
||||
public static SecureRandom SECURE_RANDOM = new SecureRandom();
|
||||
|
||||
public static byte[] join(byte[]... input) {
|
||||
try {
|
||||
@ -67,7 +65,7 @@ public class Util {
|
||||
}
|
||||
|
||||
public static boolean isEmpty(String value) {
|
||||
return value == null || value.trim().length() == 0;
|
||||
return value == null || value.trim().isEmpty();
|
||||
}
|
||||
|
||||
public static byte[] getSecretBytes(int size) {
|
||||
@ -80,13 +78,6 @@ public class Util {
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] getRandomLengthBytes(int maxSize) {
|
||||
SecureRandom secureRandom = new SecureRandom();
|
||||
byte[] result = new byte[secureRandom.nextInt(maxSize) + 1];
|
||||
secureRandom.nextBytes(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static String readFully(InputStream in) throws IOException {
|
||||
ByteArrayOutputStream bout = new ByteArrayOutputStream();
|
||||
byte[] buffer = new byte[4096];
|
||||
@ -98,7 +89,7 @@ public class Util {
|
||||
|
||||
in.close();
|
||||
|
||||
return new String(bout.toByteArray());
|
||||
return bout.toString();
|
||||
}
|
||||
|
||||
public static void readFully(InputStream in, byte[] buffer) throws IOException {
|
||||
@ -146,9 +137,4 @@ public class Util {
|
||||
}
|
||||
return (int)value;
|
||||
}
|
||||
|
||||
public static <T> List<T> immutableList(T... elements) {
|
||||
return Collections.unmodifiableList(Arrays.asList(elements.clone()));
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user