mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-21 15:05:19 +00:00
Merge dev and cleanup
This commit is contained in:
commit
5cbe289a8d
@ -28,8 +28,8 @@ configurations.all {
|
|||||||
exclude module: "commons-logging"
|
exclude module: "commons-logging"
|
||||||
}
|
}
|
||||||
|
|
||||||
def canonicalVersionCode = 376
|
def canonicalVersionCode = 377
|
||||||
def canonicalVersionName = "1.18.6"
|
def canonicalVersionName = "1.19.0"
|
||||||
|
|
||||||
def postFixSize = 10
|
def postFixSize = 10
|
||||||
def abiPostFix = ['armeabi-v7a' : 1,
|
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.ReadReceiptManager;
|
||||||
import org.thoughtcrime.securesms.sskenvironment.TypingStatusRepository;
|
import org.thoughtcrime.securesms.sskenvironment.TypingStatusRepository;
|
||||||
import org.thoughtcrime.securesms.util.Broadcaster;
|
import org.thoughtcrime.securesms.util.Broadcaster;
|
||||||
|
import org.thoughtcrime.securesms.util.VersionDataFetcher;
|
||||||
import org.thoughtcrime.securesms.util.dynamiclanguage.LocaleParseHelper;
|
import org.thoughtcrime.securesms.util.dynamiclanguage.LocaleParseHelper;
|
||||||
import org.thoughtcrime.securesms.webrtc.CallMessageProcessor;
|
import org.thoughtcrime.securesms.webrtc.CallMessageProcessor;
|
||||||
import org.webrtc.PeerConnectionFactory;
|
import org.webrtc.PeerConnectionFactory;
|
||||||
@ -110,7 +111,6 @@ import javax.inject.Inject;
|
|||||||
import dagger.hilt.EntryPoints;
|
import dagger.hilt.EntryPoints;
|
||||||
import dagger.hilt.android.HiltAndroidApp;
|
import dagger.hilt.android.HiltAndroidApp;
|
||||||
import kotlin.Unit;
|
import kotlin.Unit;
|
||||||
import kotlinx.coroutines.Job;
|
|
||||||
import network.loki.messenger.BuildConfig;
|
import network.loki.messenger.BuildConfig;
|
||||||
import network.loki.messenger.libsession_util.ConfigBase;
|
import network.loki.messenger.libsession_util.ConfigBase;
|
||||||
import network.loki.messenger.libsession_util.UserProfile;
|
import network.loki.messenger.libsession_util.UserProfile;
|
||||||
@ -151,6 +151,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
|
|||||||
@Inject PushRegistry pushRegistry;
|
@Inject PushRegistry pushRegistry;
|
||||||
@Inject ConfigFactory configFactory;
|
@Inject ConfigFactory configFactory;
|
||||||
@Inject LastSentTimestampCache lastSentTimestampCache;
|
@Inject LastSentTimestampCache lastSentTimestampCache;
|
||||||
|
@Inject VersionDataFetcher versionDataFetcher;
|
||||||
CallMessageProcessor callMessageProcessor;
|
CallMessageProcessor callMessageProcessor;
|
||||||
MessagingModuleConfiguration messagingModuleConfiguration;
|
MessagingModuleConfiguration messagingModuleConfiguration;
|
||||||
|
|
||||||
@ -275,6 +276,9 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
|
|||||||
|
|
||||||
OpenGroupManager.INSTANCE.startPolling();
|
OpenGroupManager.INSTANCE.startPolling();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// fetch last version data
|
||||||
|
versionDataFetcher.startTimedVersionCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -287,12 +291,14 @@ public class ApplicationContext extends Application implements DefaultLifecycleO
|
|||||||
poller.stopIfNeeded();
|
poller.stopIfNeeded();
|
||||||
}
|
}
|
||||||
ClosedGroupPollerV2.getShared().stopAll();
|
ClosedGroupPollerV2.getShared().stopAll();
|
||||||
|
versionDataFetcher.stopTimedVersionCheck();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTerminate() {
|
public void onTerminate() {
|
||||||
stopKovenant(); // Loki
|
stopKovenant(); // Loki
|
||||||
OpenGroupManager.INSTANCE.stopPolling();
|
OpenGroupManager.INSTANCE.stopPolling();
|
||||||
|
versionDataFetcher.stopTimedVersionCheck();
|
||||||
super.onTerminate();
|
super.onTerminate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,8 +7,6 @@ import com.squareup.phrase.Phrase
|
|||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.session.libsession.LocalisedTimeUtil
|
import org.session.libsession.LocalisedTimeUtil
|
||||||
import org.session.libsession.utilities.StringSubstitutionConstants.TIME_LARGE_KEY
|
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 java.util.concurrent.TimeUnit
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
@ -27,7 +27,11 @@ import org.thoughtcrime.securesms.groups.JoinCommunityFragment
|
|||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class StartConversationFragment : BottomSheetDialogFragment(), StartConversationDelegate {
|
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(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
package org.thoughtcrime.securesms.conversation.start.newmessage
|
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.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
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.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
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.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
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.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
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.Flow
|
||||||
import kotlinx.coroutines.flow.emptyFlow
|
import kotlinx.coroutines.flow.emptyFlow
|
||||||
import network.loki.messenger.R
|
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.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.AppBar
|
||||||
import org.thoughtcrime.securesms.ui.components.BorderlessButtonWithIcon
|
import org.thoughtcrime.securesms.ui.components.BorderlessButtonWithIcon
|
||||||
import org.thoughtcrime.securesms.ui.components.MaybeScanQrCode
|
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.SessionOutlinedTextField
|
||||||
import org.thoughtcrime.securesms.ui.components.SessionTabRow
|
import org.thoughtcrime.securesms.ui.components.SessionTabRow
|
||||||
import org.thoughtcrime.securesms.ui.contentDescription
|
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.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)
|
private val TITLES = listOf(R.string.accountIdEnter, R.string.qrScan)
|
||||||
|
|
||||||
@ -76,63 +93,127 @@ private fun EnterAccountId(
|
|||||||
callbacks: Callbacks,
|
callbacks: Callbacks,
|
||||||
onHelp: () -> Unit = {}
|
onHelp: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
Column(
|
// the scaffold is required to provide the contentPadding. That contentPadding is needed
|
||||||
modifier = Modifier
|
// to properly handle the ime padding.
|
||||||
.fillMaxSize()
|
Scaffold() { contentPadding ->
|
||||||
.verticalScroll(rememberScrollState())
|
// we need this extra surface to handle nested scrolling properly,
|
||||||
.imePadding()
|
// because this scrollable component is inside a bottomSheet dialog which is itself scrollable
|
||||||
) {
|
Surface(
|
||||||
Column(
|
modifier = Modifier.nestedScroll(rememberNestedScrollInteropConnection()),
|
||||||
modifier = Modifier.padding(vertical = LocalDimensions.current.spacing),
|
color = LocalColors.current.backgroundSecondary
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(LocalDimensions.current.xxxsSpacing))
|
var accountModifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
|
||||||
BorderlessButtonWithIcon(
|
//<<<<<<< HEAD
|
||||||
text = stringResource(R.string.messageNewDescriptionMobile),
|
// BorderlessButtonWithIcon(
|
||||||
modifier = Modifier
|
// text = stringResource(R.string.messageNewDescriptionMobile),
|
||||||
.contentDescription(R.string.AccessibilityId_help_desk_link)
|
// modifier = Modifier
|
||||||
.padding(horizontal = LocalDimensions.current.mediumSpacing)
|
// .contentDescription(R.string.AccessibilityId_help_desk_link)
|
||||||
.fillMaxWidth(),
|
// .padding(horizontal = LocalDimensions.current.mediumSpacing)
|
||||||
style = LocalType.current.small,
|
// .fillMaxWidth(),
|
||||||
color = LocalColors.current.textSecondary,
|
// style = LocalType.current.small,
|
||||||
iconRes = R.drawable.ic_circle_question_mark,
|
// color = LocalColors.current.textSecondary,
|
||||||
onClick = onHelp
|
// 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))
|
Column(
|
||||||
Spacer(Modifier.weight(2f))
|
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(
|
Spacer(modifier = Modifier.height(LocalDimensions.current.xxxsSpacing))
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.CenterHorizontally)
|
BorderlessButtonWithIcon(
|
||||||
.padding(horizontal = LocalDimensions.current.xlargeSpacing)
|
text = stringResource(R.string.messageNewDescriptionMobile),
|
||||||
.padding(bottom = LocalDimensions.current.smallSpacing)
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.contentDescription(R.string.AccessibilityId_help_desk_link)
|
||||||
.contentDescription(R.string.next),
|
.padding(horizontal = LocalDimensions.current.mediumSpacing)
|
||||||
enabled = state.isNextButtonEnabled,
|
.fillMaxWidth(),
|
||||||
onClick = callbacks::onContinue
|
style = LocalType.current.small,
|
||||||
) {
|
color = LocalColors.current.textSecondary,
|
||||||
LoadingArcOr(state.loading) {
|
iconRes = R.drawable.ic_circle_question_mark,
|
||||||
Text(stringResource(R.string.next))
|
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
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
private fun PreviewNewMessage(
|
private fun PreviewNewMessage(
|
||||||
|
@ -46,7 +46,7 @@ internal class NewMessageViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onContinue() {
|
override fun onContinue() {
|
||||||
val idOrONS = state.value.newMessageIdOrOns
|
val idOrONS = state.value.newMessageIdOrOns.trim()
|
||||||
|
|
||||||
if (PublicKeyValidation.isValid(idOrONS, isPrefixRequired = false)) {
|
if (PublicKeyValidation.isValid(idOrONS, isPrefixRequired = false)) {
|
||||||
onUnvalidatedPublicKey(publicKey = idOrONS)
|
onUnvalidatedPublicKey(publicKey = idOrONS)
|
||||||
|
@ -178,7 +178,6 @@ import org.thoughtcrime.securesms.mms.VideoSlide
|
|||||||
import org.thoughtcrime.securesms.permissions.Permissions
|
import org.thoughtcrime.securesms.permissions.Permissions
|
||||||
import org.thoughtcrime.securesms.reactions.ReactionsDialogFragment
|
import org.thoughtcrime.securesms.reactions.ReactionsDialogFragment
|
||||||
import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiDialogFragment
|
import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiDialogFragment
|
||||||
import org.thoughtcrime.securesms.recoverypassword.RecoveryPasswordActivity
|
|
||||||
import org.thoughtcrime.securesms.showSessionDialog
|
import org.thoughtcrime.securesms.showSessionDialog
|
||||||
import org.thoughtcrime.securesms.util.ActivityDispatcher
|
import org.thoughtcrime.securesms.util.ActivityDispatcher
|
||||||
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
|
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.isScrolledToWithin30dpOfBottom
|
||||||
import org.thoughtcrime.securesms.util.push
|
import org.thoughtcrime.securesms.util.push
|
||||||
import org.thoughtcrime.securesms.util.show
|
import org.thoughtcrime.securesms.util.show
|
||||||
import org.thoughtcrime.securesms.util.start
|
|
||||||
import org.thoughtcrime.securesms.util.toPx
|
import org.thoughtcrime.securesms.util.toPx
|
||||||
|
|
||||||
private const val TAG = "ConversationActivityV2"
|
private const val TAG = "ConversationActivityV2"
|
||||||
@ -1651,8 +1649,15 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
|
|||||||
val text = getMessageBody()
|
val text = getMessageBody()
|
||||||
val userPublicKey = textSecurePreferences.getLocalNumber()
|
val userPublicKey = textSecurePreferences.getLocalNumber()
|
||||||
val isNoteToSelf = (recipient.isContactRecipient && recipient.address.toString() == userPublicKey)
|
val isNoteToSelf = (recipient.isContactRecipient && recipient.address.toString() == userPublicKey)
|
||||||
if (text.contains(seed) && !isNoteToSelf && !hasPermissionToSendSeed) {
|
if (seed in text && !isNoteToSelf && !hasPermissionToSendSeed) {
|
||||||
start<RecoveryPasswordActivity>()
|
showSessionDialog {
|
||||||
|
title(R.string.warning)
|
||||||
|
text(R.string.recoveryPasswordWarningSendDescription)
|
||||||
|
button(R.string.send) { sendTextOnlyMessage(true) }
|
||||||
|
cancelButton()
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
// Create the message
|
// Create the message
|
||||||
val message = VisibleMessage().applyExpiryMode(viewModel.threadId)
|
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.ByteArrayOutputStream
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.UnsupportedEncodingException
|
import java.io.UnsupportedEncodingException
|
||||||
import java.security.SecureRandom
|
|
||||||
import java.util.Collections
|
import java.util.Collections
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
@ -247,32 +246,6 @@ object Util {
|
|||||||
return result
|
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? {
|
fun uri(uri: String?): Uri? {
|
||||||
return if (uri == null) null
|
return if (uri == null) null
|
||||||
else Uri.parse(uri)
|
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;
|
package org.thoughtcrime.securesms.crypto;
|
||||||
|
|
||||||
|
|
||||||
|
import static org.session.libsignal.utilities.Util.SECURE_RANDOM;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import org.session.libsession.utilities.TextSecurePreferences;
|
import org.session.libsession.utilities.TextSecurePreferences;
|
||||||
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A provider that is responsible for creating or retrieving the AttachmentSecret model.
|
* 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) {
|
private AttachmentSecret createAndStoreAttachmentSecret(@NonNull Context context) {
|
||||||
SecureRandom random = new SecureRandom();
|
|
||||||
byte[] secret = new byte[32];
|
byte[] secret = new byte[32];
|
||||||
random.nextBytes(secret);
|
SECURE_RANDOM.nextBytes(secret);
|
||||||
|
|
||||||
AttachmentSecret attachmentSecret = new AttachmentSecret(null, null, secret);
|
AttachmentSecret attachmentSecret = new AttachmentSecret(null, null, secret);
|
||||||
storeAttachmentSecret(context, attachmentSecret);
|
storeAttachmentSecret(context, attachmentSecret);
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package org.thoughtcrime.securesms.crypto;
|
package org.thoughtcrime.securesms.crypto;
|
||||||
|
|
||||||
|
|
||||||
|
import static org.session.libsignal.utilities.Util.SECURE_RANDOM;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@ -8,7 +10,6 @@ import androidx.annotation.NonNull;
|
|||||||
import org.session.libsession.utilities.TextSecurePreferences;
|
import org.session.libsession.utilities.TextSecurePreferences;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.SecureRandom;
|
|
||||||
|
|
||||||
public class DatabaseSecretProvider {
|
public class DatabaseSecretProvider {
|
||||||
|
|
||||||
@ -60,9 +61,8 @@ public class DatabaseSecretProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private DatabaseSecret createAndStoreDatabaseSecret(@NonNull Context context) {
|
private DatabaseSecret createAndStoreDatabaseSecret(@NonNull Context context) {
|
||||||
SecureRandom random = new SecureRandom();
|
|
||||||
byte[] secret = new byte[32];
|
byte[] secret = new byte[32];
|
||||||
random.nextBytes(secret);
|
SECURE_RANDOM.nextBytes(secret);
|
||||||
|
|
||||||
DatabaseSecret databaseSecret = new DatabaseSecret(secret);
|
DatabaseSecret databaseSecret = new DatabaseSecret(secret);
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package org.thoughtcrime.securesms.crypto;
|
package org.thoughtcrime.securesms.crypto;
|
||||||
|
|
||||||
|
|
||||||
|
import static org.session.libsignal.utilities.Util.SECURE_RANDOM;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
|
||||||
@ -11,7 +13,6 @@ import java.io.OutputStream;
|
|||||||
import java.security.InvalidAlgorithmParameterException;
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.SecureRandom;
|
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.CipherOutputStream;
|
import javax.crypto.CipherOutputStream;
|
||||||
@ -31,7 +32,7 @@ public class ModernEncryptingPartOutputStream {
|
|||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
byte[] random = new byte[32];
|
byte[] random = new byte[32];
|
||||||
new SecureRandom().nextBytes(random);
|
SECURE_RANDOM.nextBytes(random);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Mac mac = Mac.getInstance("HmacSHA256");
|
Mac mac = Mac.getInstance("HmacSHA256");
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package org.thoughtcrime.securesms.database;
|
package org.thoughtcrime.securesms.database;
|
||||||
|
|
||||||
|
import static org.session.libsignal.utilities.Util.SECURE_RANDOM;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@ -26,7 +28,6 @@ import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
|||||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -303,7 +304,7 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
|
|||||||
public void updateProfilePicture(String groupID, byte[] newValue) {
|
public void updateProfilePicture(String groupID, byte[] newValue) {
|
||||||
long avatarId;
|
long avatarId;
|
||||||
|
|
||||||
if (newValue != null) avatarId = Math.abs(new SecureRandom().nextLong());
|
if (newValue != null) avatarId = Math.abs(SECURE_RANDOM.nextLong());
|
||||||
else avatarId = 0;
|
else avatarId = 0;
|
||||||
|
|
||||||
|
|
||||||
@ -458,12 +459,6 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt
|
|||||||
database.update(TABLE_NAME, values, GROUP_ID + " = ?", new String[] {groupId});
|
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) {
|
public boolean hasGroup(@NonNull String groupId) {
|
||||||
try (Cursor cursor = databaseHelper.getReadableDatabase().rawQuery(
|
try (Cursor cursor = databaseHelper.getReadableDatabase().rawQuery(
|
||||||
"SELECT 1 FROM " + TABLE_NAME + " WHERE " + GROUP_ID + " = ? LIMIT 1",
|
"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 RESET_SEQ_NO = "UPDATE $lastMessageServerIDTable SET $lastMessageServerID = 0;"
|
||||||
|
|
||||||
const val EMPTY_VERSION = "0.0.0"
|
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,15 +173,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
val database = databaseHelper.readableDatabase
|
val database = databaseHelper.readableDatabase
|
||||||
return database.get(snodePoolTable, "${Companion.dummyKey} = ?", wrap("dummy_key")) { cursor ->
|
return database.get(snodePoolTable, "${Companion.dummyKey} = ?", wrap("dummy_key")) { cursor ->
|
||||||
val snodePoolAsString = cursor.getString(cursor.getColumnIndexOrThrow(snodePool))
|
val snodePoolAsString = cursor.getString(cursor.getColumnIndexOrThrow(snodePool))
|
||||||
snodePoolAsString.split(", ").mapNotNull { snodeAsString ->
|
snodePoolAsString.split(", ").mapNotNull(::Snode)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}?.toSet() ?: setOf()
|
}?.toSet() ?: setOf()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,18 +221,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
val database = databaseHelper.readableDatabase
|
val database = databaseHelper.readableDatabase
|
||||||
fun get(indexPath: String): Snode? {
|
fun get(indexPath: String): Snode? {
|
||||||
return database.get(onionRequestPathTable, "${Companion.indexPath} = ?", wrap(indexPath)) { cursor ->
|
return database.get(onionRequestPathTable, "${Companion.indexPath} = ?", wrap(indexPath)) { cursor ->
|
||||||
val snodeAsString = cursor.getString(cursor.getColumnIndexOrThrow(snode))
|
Snode(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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val result = mutableListOf<List<Snode>>()
|
val result = mutableListOf<List<Snode>>()
|
||||||
@ -276,15 +255,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
val database = databaseHelper.readableDatabase
|
val database = databaseHelper.readableDatabase
|
||||||
return database.get(swarmTable, "${Companion.swarmPublicKey} = ?", wrap(publicKey)) { cursor ->
|
return database.get(swarmTable, "${Companion.swarmPublicKey} = ?", wrap(publicKey)) { cursor ->
|
||||||
val swarmAsString = cursor.getString(cursor.getColumnIndexOrThrow(swarm))
|
val swarmAsString = cursor.getString(cursor.getColumnIndexOrThrow(swarm))
|
||||||
swarmAsString.split(", ").mapNotNull { targetAsString ->
|
swarmAsString.split(", ").mapNotNull(::Snode)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}?.toSet()
|
}?.toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,11 +46,11 @@ import org.session.libsession.utilities.IdentityKeyMismatchList
|
|||||||
import org.session.libsession.utilities.NetworkFailure
|
import org.session.libsession.utilities.NetworkFailure
|
||||||
import org.session.libsession.utilities.NetworkFailureList
|
import org.session.libsession.utilities.NetworkFailureList
|
||||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.isReadReceiptsEnabled
|
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.libsession.utilities.recipients.Recipient
|
||||||
import org.session.libsignal.utilities.JsonUtil
|
import org.session.libsignal.utilities.JsonUtil
|
||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
import org.session.libsignal.utilities.ThreadUtils.queue
|
import org.session.libsignal.utilities.ThreadUtils.queue
|
||||||
|
import org.session.libsignal.utilities.Util.SECURE_RANDOM
|
||||||
import org.session.libsignal.utilities.guava.Optional
|
import org.session.libsignal.utilities.guava.Optional
|
||||||
import org.thoughtcrime.securesms.attachments.MmsNotificationAttachment
|
import org.thoughtcrime.securesms.attachments.MmsNotificationAttachment
|
||||||
import org.thoughtcrime.securesms.database.SmsDatabase.InsertListener
|
import org.thoughtcrime.securesms.database.SmsDatabase.InsertListener
|
||||||
@ -66,7 +66,6 @@ import org.thoughtcrime.securesms.mms.SlideDeck
|
|||||||
import org.thoughtcrime.securesms.util.asSequence
|
import org.thoughtcrime.securesms.util.asSequence
|
||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.security.SecureRandom
|
|
||||||
import java.util.LinkedList
|
import java.util.LinkedList
|
||||||
|
|
||||||
class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : MessagingDatabase(context, databaseHelper) {
|
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?,
|
inner class OutgoingMessageReader(private val message: OutgoingMediaMessage?,
|
||||||
private val threadId: Long) {
|
private val threadId: Long) {
|
||||||
private val id = SecureRandom().nextLong()
|
private val id = SECURE_RANDOM.nextLong()
|
||||||
val current: MessageRecord
|
val current: MessageRecord
|
||||||
get() {
|
get() {
|
||||||
val slideDeck = SlideDeck(context, message!!.attachments)
|
val slideDeck = SlideDeck(context, message!!.attachments)
|
||||||
|
@ -17,6 +17,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.thoughtcrime.securesms.database;
|
package org.thoughtcrime.securesms.database;
|
||||||
|
|
||||||
|
import static org.session.libsignal.utilities.Util.SECURE_RANDOM;
|
||||||
|
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
@ -49,7 +51,6 @@ import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
|||||||
import org.thoughtcrime.securesms.dependencies.DatabaseComponent;
|
import org.thoughtcrime.securesms.dependencies.DatabaseComponent;
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
@ -784,7 +785,7 @@ public class SmsDatabase extends MessagingDatabase {
|
|||||||
public OutgoingMessageReader(OutgoingTextMessage message, long threadId) {
|
public OutgoingMessageReader(OutgoingTextMessage message, long threadId) {
|
||||||
this.message = message;
|
this.message = message;
|
||||||
this.threadId = threadId;
|
this.threadId = threadId;
|
||||||
this.id = new SecureRandom().nextLong();
|
this.id = SECURE_RANDOM.nextLong();
|
||||||
}
|
}
|
||||||
|
|
||||||
public MessageRecord getCurrent() {
|
public MessageRecord getCurrent() {
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
package org.thoughtcrime.securesms.glide;
|
package org.thoughtcrime.securesms.glide;
|
||||||
|
|
||||||
|
import static org.session.libsignal.utilities.Util.SECURE_RANDOM;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.SecureRandom;
|
|
||||||
|
|
||||||
import okhttp3.Headers;
|
import okhttp3.Headers;
|
||||||
import okhttp3.Interceptor;
|
import okhttp3.Interceptor;
|
||||||
@ -30,15 +31,15 @@ public class PaddedHeadersInterceptor implements Interceptor {
|
|||||||
|
|
||||||
private @NonNull Headers getPaddedHeaders(@NonNull Headers headers) {
|
private @NonNull Headers getPaddedHeaders(@NonNull Headers headers) {
|
||||||
return headers.newBuilder()
|
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();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static @NonNull String getRandomString(@NonNull SecureRandom secureRandom, int minLength, int maxLength) {
|
private static @NonNull String getRandomString(int minLength, int maxLength) {
|
||||||
char[] buffer = new char[secureRandom.nextInt(maxLength - minLength) + minLength];
|
char[] buffer = new char[SECURE_RANDOM.nextInt(maxLength - minLength) + minLength];
|
||||||
|
|
||||||
for (int i = 0 ; i < buffer.length; i++) {
|
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);
|
return new String(buffer);
|
||||||
|
@ -89,6 +89,9 @@ import java.util.Calendar
|
|||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
private const val NEW_ACCOUNT = "HomeActivity_NEW_ACCOUNT"
|
||||||
|
private const val FROM_ONBOARDING = "HomeActivity_FROM_ONBOARDING"
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class HomeActivity : PassphraseRequiredActionBarActivity(),
|
class HomeActivity : PassphraseRequiredActionBarActivity(),
|
||||||
ConversationClickListener,
|
ConversationClickListener,
|
||||||
@ -96,10 +99,10 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
|||||||
|
|
||||||
private val TAG = "HomeActivity"
|
private val TAG = "HomeActivity"
|
||||||
|
|
||||||
companion object {
|
// companion object {
|
||||||
const val NEW_ACCOUNT = "HomeActivity_NEW_ACCOUNT"
|
// const val NEW_ACCOUNT = "HomeActivity_NEW_ACCOUNT"
|
||||||
const val FROM_ONBOARDING = "HomeActivity_FROM_ONBOARDING"
|
// const val FROM_ONBOARDING = "HomeActivity_FROM_ONBOARDING"
|
||||||
}
|
// }
|
||||||
|
|
||||||
private lateinit var binding: ActivityHomeBinding
|
private lateinit var binding: ActivityHomeBinding
|
||||||
private lateinit var glide: RequestManager
|
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
|
// region Lifecycle
|
||||||
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
|
override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) {
|
||||||
@ -262,7 +266,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
|||||||
}
|
}
|
||||||
else -> buildList {
|
else -> buildList {
|
||||||
result.contactAndGroupList.takeUnless { it.isEmpty() }?.let {
|
result.contactAndGroupList.takeUnless { it.isEmpty() }?.let {
|
||||||
add(GlobalSearchAdapter.Model.Header(R.string.contactContacts))
|
add(GlobalSearchAdapter.Model.Header(R.string.sessionConversations))
|
||||||
addAll(it)
|
addAll(it)
|
||||||
}
|
}
|
||||||
result.messageResults.takeUnless { it.isEmpty() }?.let {
|
result.messageResults.takeUnless { it.isEmpty() }?.let {
|
||||||
@ -275,8 +279,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
EventBus.getDefault().register(this@HomeActivity)
|
EventBus.getDefault().register(this@HomeActivity)
|
||||||
if (intent.hasExtra(FROM_ONBOARDING)
|
if (isFromOnboarding) {
|
||||||
&& intent.getBooleanExtra(FROM_ONBOARDING, false)) {
|
|
||||||
if (Build.VERSION.SDK_INT >= 33 &&
|
if (Build.VERSION.SDK_INT >= 33 &&
|
||||||
(getSystemService(NOTIFICATION_SERVICE) as NotificationManager).areNotificationsEnabled().not()) {
|
(getSystemService(NOTIFICATION_SERVICE) as NotificationManager).areNotificationsEnabled().not()) {
|
||||||
Permissions.with(this)
|
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 {
|
Intent(this, HomeActivity::class.java).apply {
|
||||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||||
putExtra(HomeActivity.NEW_ACCOUNT, true)
|
putExtra(NEW_ACCOUNT, isNewAccount)
|
||||||
putExtra(HomeActivity.FROM_ONBOARDING, true)
|
putExtra(FROM_ONBOARDING, isFromOnboarding)
|
||||||
}.also(::startActivity)
|
}.also(::startActivity)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package org.thoughtcrime.securesms.logging;
|
package org.thoughtcrime.securesms.logging;
|
||||||
|
|
||||||
import static org.session.libsignal.crypto.CipherUtil.CIPHER_LOCK;
|
import static org.session.libsignal.crypto.CipherUtil.CIPHER_LOCK;
|
||||||
|
import static org.session.libsignal.utilities.Util.SECURE_RANDOM;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
@ -17,7 +18,6 @@ import java.io.IOException;
|
|||||||
import java.security.InvalidAlgorithmParameterException;
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.SecureRandom;
|
|
||||||
|
|
||||||
import javax.crypto.BadPaddingException;
|
import javax.crypto.BadPaddingException;
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
@ -64,7 +64,7 @@ class LogFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void writeEntry(@NonNull String entry) throws IOException {
|
void writeEntry(@NonNull String entry) throws IOException {
|
||||||
new SecureRandom().nextBytes(ivBuffer);
|
SECURE_RANDOM.nextBytes(ivBuffer);
|
||||||
|
|
||||||
byte[] plaintext = entry.getBytes();
|
byte[] plaintext = entry.getBytes();
|
||||||
try {
|
try {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package org.thoughtcrime.securesms.logging;
|
package org.thoughtcrime.securesms.logging;
|
||||||
|
|
||||||
|
import static org.session.libsignal.utilities.Util.SECURE_RANDOM;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@ -9,7 +11,6 @@ import org.session.libsignal.utilities.Base64;
|
|||||||
import org.session.libsession.utilities.TextSecurePreferences;
|
import org.session.libsession.utilities.TextSecurePreferences;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.SecureRandom;
|
|
||||||
|
|
||||||
class LogSecretProvider {
|
class LogSecretProvider {
|
||||||
|
|
||||||
@ -40,9 +41,8 @@ class LogSecretProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] createAndStoreSecret(@NonNull Context context) {
|
private static byte[] createAndStoreSecret(@NonNull Context context) {
|
||||||
SecureRandom random = new SecureRandom();
|
|
||||||
byte[] secret = new byte[32];
|
byte[] secret = new byte[32];
|
||||||
random.nextBytes(secret);
|
SECURE_RANDOM.nextBytes(secret);
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.seal(secret);
|
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.seal(secret);
|
||||||
|
@ -21,7 +21,6 @@ import android.content.res.Resources
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import com.squareup.phrase.Phrase
|
import com.squareup.phrase.Phrase
|
||||||
import java.security.SecureRandom
|
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
import org.session.libsession.messaging.sending_receiving.attachments.Attachment
|
import org.session.libsession.messaging.sending_receiving.attachments.Attachment
|
||||||
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress
|
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.StringSubstitutionConstants.EMOJI_KEY
|
||||||
import org.session.libsession.utilities.Util.equals
|
import org.session.libsession.utilities.Util.equals
|
||||||
import org.session.libsession.utilities.Util.hashCode
|
import org.session.libsession.utilities.Util.hashCode
|
||||||
|
import org.session.libsignal.utilities.Util.SECURE_RANDOM
|
||||||
import org.session.libsignal.utilities.guava.Optional
|
import org.session.libsignal.utilities.guava.Optional
|
||||||
import org.thoughtcrime.securesms.conversation.v2.Util
|
import org.thoughtcrime.securesms.conversation.v2.Util
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil
|
import org.thoughtcrime.securesms.util.MediaUtil
|
||||||
@ -160,7 +160,7 @@ abstract class Slide(@JvmField protected val context: Context, protected val att
|
|||||||
): Attachment {
|
): Attachment {
|
||||||
val resolvedType =
|
val resolvedType =
|
||||||
Optional.fromNullable(MediaUtil.getMimeType(context, uri)).or(defaultMime)
|
Optional.fromNullable(MediaUtil.getMimeType(context, uri)).or(defaultMime)
|
||||||
val fastPreflightId = SecureRandom().nextLong().toString()
|
val fastPreflightId = SECURE_RANDOM.nextLong().toString()
|
||||||
return UriAttachment(
|
return UriAttachment(
|
||||||
uri,
|
uri,
|
||||||
if (hasThumbnail) uri else null,
|
if (hasThumbnail) uri else null,
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package org.thoughtcrime.securesms.net;
|
package org.thoughtcrime.securesms.net;
|
||||||
|
|
||||||
|
import static org.session.libsignal.utilities.Util.SECURE_RANDOM;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
@ -15,7 +17,6 @@ import org.session.libsignal.utilities.guava.Optional;
|
|||||||
import java.io.FilterInputStream;
|
import java.io.FilterInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -54,7 +55,7 @@ public class ChunkedDataFetcher {
|
|||||||
private RequestController fetchChunksWithUnknownTotalSize(@NonNull String url, @NonNull Callback callback) {
|
private RequestController fetchChunksWithUnknownTotalSize(@NonNull String url, @NonNull Callback callback) {
|
||||||
CompositeRequestController compositeController = new CompositeRequestController();
|
CompositeRequestController compositeController = new CompositeRequestController();
|
||||||
|
|
||||||
long chunkSize = new SecureRandom().nextInt(1024) + 1024;
|
long chunkSize = SECURE_RANDOM.nextInt(1024) + 1024;
|
||||||
Request request = new Request.Builder()
|
Request request = new Request.Builder()
|
||||||
.url(url)
|
.url(url)
|
||||||
.cacheControl(NO_CACHE)
|
.cacheControl(NO_CACHE)
|
||||||
|
@ -32,7 +32,7 @@ class LoadingActivity: BaseActionBarActivity() {
|
|||||||
|
|
||||||
when {
|
when {
|
||||||
loadFailed -> startPickDisplayNameActivity(loadFailed = true)
|
loadFailed -> startPickDisplayNameActivity(loadFailed = true)
|
||||||
else -> startHomeActivity(isNewAccount = false)
|
else -> startHomeActivity(isNewAccount = false, isFromOnboarding = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
finish()
|
finish()
|
||||||
|
@ -8,6 +8,7 @@ import org.session.libsignal.utilities.KeyHelper
|
|||||||
import org.session.libsignal.utilities.hexEncodedPublicKey
|
import org.session.libsignal.utilities.hexEncodedPublicKey
|
||||||
import org.thoughtcrime.securesms.crypto.KeyPairUtilities
|
import org.thoughtcrime.securesms.crypto.KeyPairUtilities
|
||||||
import org.thoughtcrime.securesms.dependencies.ConfigFactory
|
import org.thoughtcrime.securesms.dependencies.ConfigFactory
|
||||||
|
import org.thoughtcrime.securesms.util.VersionDataFetcher
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@ -16,6 +17,7 @@ class CreateAccountManager @Inject constructor(
|
|||||||
private val application: Application,
|
private val application: Application,
|
||||||
private val prefs: TextSecurePreferences,
|
private val prefs: TextSecurePreferences,
|
||||||
private val configFactory: ConfigFactory,
|
private val configFactory: ConfigFactory,
|
||||||
|
private val versionDataFetcher: VersionDataFetcher
|
||||||
) {
|
) {
|
||||||
private val database: LokiAPIDatabaseProtocol
|
private val database: LokiAPIDatabaseProtocol
|
||||||
get() = SnodeModule.shared.storage
|
get() = SnodeModule.shared.storage
|
||||||
@ -41,5 +43,7 @@ class CreateAccountManager @Inject constructor(
|
|||||||
prefs.setLocalRegistrationId(registrationID)
|
prefs.setLocalRegistrationId(registrationID)
|
||||||
prefs.setLocalNumber(userHexEncodedPublicKey)
|
prefs.setLocalNumber(userHexEncodedPublicKey)
|
||||||
prefs.setRestorationTime(0)
|
prefs.setRestorationTime(0)
|
||||||
|
|
||||||
|
versionDataFetcher.startTimedVersionCheck()
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -12,6 +12,7 @@ import org.session.libsignal.utilities.hexEncodedPublicKey
|
|||||||
import org.thoughtcrime.securesms.ApplicationContext
|
import org.thoughtcrime.securesms.ApplicationContext
|
||||||
import org.thoughtcrime.securesms.crypto.KeyPairUtilities
|
import org.thoughtcrime.securesms.crypto.KeyPairUtilities
|
||||||
import org.thoughtcrime.securesms.dependencies.ConfigFactory
|
import org.thoughtcrime.securesms.dependencies.ConfigFactory
|
||||||
|
import org.thoughtcrime.securesms.util.VersionDataFetcher
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@ -19,7 +20,8 @@ import javax.inject.Singleton
|
|||||||
class LoadAccountManager @Inject constructor(
|
class LoadAccountManager @Inject constructor(
|
||||||
@dagger.hilt.android.qualifiers.ApplicationContext private val context: Context,
|
@dagger.hilt.android.qualifiers.ApplicationContext private val context: Context,
|
||||||
private val configFactory: ConfigFactory,
|
private val configFactory: ConfigFactory,
|
||||||
private val prefs: TextSecurePreferences
|
private val prefs: TextSecurePreferences,
|
||||||
|
private val versionDataFetcher: VersionDataFetcher
|
||||||
) {
|
) {
|
||||||
private val database: LokiAPIDatabaseProtocol
|
private val database: LokiAPIDatabaseProtocol
|
||||||
get() = SnodeModule.shared.storage
|
get() = SnodeModule.shared.storage
|
||||||
@ -52,6 +54,8 @@ class LoadAccountManager @Inject constructor(
|
|||||||
setHasViewedSeed(true)
|
setHasViewedSeed(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
versionDataFetcher.startTimedVersionCheck()
|
||||||
|
|
||||||
ApplicationContext.getInstance(context).retrieveUserProfile()
|
ApplicationContext.getInstance(context).retrieveUserProfile()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ class MessageNotificationsActivity : BaseActionBarActivity() {
|
|||||||
viewModel.events.collect {
|
viewModel.events.collect {
|
||||||
when (it) {
|
when (it) {
|
||||||
Event.Loading -> start<LoadingActivity>()
|
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 {
|
viewModel.events.collect {
|
||||||
when (it) {
|
when (it) {
|
||||||
is Event.CreateAccount -> startMessageNotificationsActivity(it.profileName)
|
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;
|
package org.thoughtcrime.securesms.permissions;
|
||||||
|
|
||||||
|
import static org.session.libsignal.utilities.Util.SECURE_RANDOM;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
@ -11,9 +11,7 @@ import android.os.Build;
|
|||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.util.DisplayMetrics;
|
import android.util.DisplayMetrics;
|
||||||
import android.view.Display;
|
import android.view.Display;
|
||||||
import android.view.ViewGroup;
|
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.widget.Button;
|
|
||||||
|
|
||||||
import androidx.annotation.DrawableRes;
|
import androidx.annotation.DrawableRes;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@ -28,13 +26,10 @@ import org.session.libsession.utilities.ServiceUtil;
|
|||||||
import org.thoughtcrime.securesms.util.LRUCache;
|
import org.thoughtcrime.securesms.util.LRUCache;
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import network.loki.messenger.R;
|
|
||||||
|
|
||||||
public class Permissions {
|
public class Permissions {
|
||||||
|
|
||||||
private static final Map<Integer, PermissionsRequest> OUTSTANDING = new LRUCache<>(2);
|
private static final Map<Integer, PermissionsRequest> OUTSTANDING = new LRUCache<>(2);
|
||||||
@ -172,7 +167,7 @@ public class Permissions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void executePermissionsRequest(PermissionsRequest request) {
|
private void executePermissionsRequest(PermissionsRequest request) {
|
||||||
int requestCode = new SecureRandom().nextInt(65434) + 100;
|
int requestCode = SECURE_RANDOM.nextInt(65434) + 100;
|
||||||
|
|
||||||
synchronized (OUTSTANDING) {
|
synchronized (OUTSTANDING) {
|
||||||
OUTSTANDING.put(requestCode, request);
|
OUTSTANDING.put(requestCode, request);
|
||||||
|
@ -37,7 +37,6 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
|||||||
import com.squareup.phrase.Phrase
|
import com.squareup.phrase.Phrase
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.security.SecureRandom
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
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.recipients.Recipient
|
||||||
import org.session.libsession.utilities.truncateIdForDisplay
|
import org.session.libsession.utilities.truncateIdForDisplay
|
||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
|
import org.session.libsignal.utilities.Util.SECURE_RANDOM
|
||||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
||||||
import org.thoughtcrime.securesms.avatar.AvatarSelection
|
import org.thoughtcrime.securesms.avatar.AvatarSelection
|
||||||
import org.thoughtcrime.securesms.components.ProfilePictureView
|
import org.thoughtcrime.securesms.components.ProfilePictureView
|
||||||
@ -300,7 +300,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
|
|
||||||
val userConfig = configFactory.user
|
val userConfig = configFactory.user
|
||||||
AvatarHelper.setAvatar(this, Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)!!), profilePicture)
|
AvatarHelper.setAvatar(this, Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)!!), profilePicture)
|
||||||
prefs.setProfileAvatarId(SecureRandom().nextInt() )
|
prefs.setProfileAvatarId(SECURE_RANDOM.nextInt() )
|
||||||
ProfileKeyUtil.setEncodedProfileKey(this, encodedProfileKey)
|
ProfileKeyUtil.setEncodedProfileKey(this, encodedProfileKey)
|
||||||
|
|
||||||
// Attempt to grab the details we require to update the profile picture
|
// 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.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
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 org.thoughtcrime.securesms.util.themeState
|
import org.thoughtcrime.securesms.util.themeState
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -21,6 +21,8 @@ class AppearanceSettingsViewModel @Inject constructor(private val prefs: TextSec
|
|||||||
prefs.setAccentColorStyle(newAccentColorStyle)
|
prefs.setAccentColorStyle(newAccentColorStyle)
|
||||||
// update UI state
|
// update UI state
|
||||||
_uiState.value = prefs.themeState()
|
_uiState.value = prefs.themeState()
|
||||||
|
|
||||||
|
invalidateComposeThemeColors()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setNewStyle(newThemeStyle: String) {
|
fun setNewStyle(newThemeStyle: String) {
|
||||||
@ -28,16 +30,13 @@ class AppearanceSettingsViewModel @Inject constructor(private val prefs: TextSec
|
|||||||
// update UI state
|
// update UI state
|
||||||
_uiState.value = prefs.themeState()
|
_uiState.value = prefs.themeState()
|
||||||
|
|
||||||
// force compose to refresh its style reference
|
invalidateComposeThemeColors()
|
||||||
selectedTheme = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setNewFollowSystemSettings(followSystemSettings: Boolean) {
|
fun setNewFollowSystemSettings(followSystemSettings: Boolean) {
|
||||||
prefs.setFollowSystemSettings(followSystemSettings)
|
prefs.setFollowSystemSettings(followSystemSettings)
|
||||||
_uiState.value = prefs.themeState()
|
_uiState.value = prefs.themeState()
|
||||||
|
|
||||||
// force compose to refresh its style reference
|
invalidateComposeThemeColors()
|
||||||
selectedTheme = null
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
@ -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
|
package org.thoughtcrime.securesms.ui.theme
|
||||||
|
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.BLUE_ACCENT
|
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
|
* 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
|
* But future themes will be picked and set directly from the "Appearance" screen
|
||||||
*/
|
*/
|
||||||
@Composable
|
fun TextSecurePreferences.getColorsProvider(): ThemeColorsProvider {
|
||||||
fun TextSecurePreferences.getComposeTheme(): ThemeColors {
|
|
||||||
val selectedTheme = getThemeStyle()
|
val selectedTheme = getThemeStyle()
|
||||||
|
|
||||||
// get the chosen primary color from the preferences
|
// get the chosen primary color from the preferences
|
||||||
val selectedPrimary = primaryColor()
|
val selectedPrimary = primaryColor()
|
||||||
|
|
||||||
// create a theme set with the appropriate primary
|
val isOcean = "ocean" in selectedTheme
|
||||||
val colorSet = when(selectedTheme){
|
|
||||||
TextSecurePreferences.OCEAN_DARK,
|
val createLight = if (isOcean) ::OceanLight else ::ClassicLight
|
||||||
TextSecurePreferences.OCEAN_LIGHT -> ThemeColorSet(
|
val createDark = if (isOcean) ::OceanDark else ::ClassicDark
|
||||||
light = OceanLight(selectedPrimary),
|
|
||||||
dark = OceanDark(selectedPrimary)
|
return when {
|
||||||
)
|
getFollowSystemSettings() -> FollowSystemThemeColorsProvider(
|
||||||
|
light = createLight(selectedPrimary),
|
||||||
else -> ThemeColorSet(
|
dark = createDark(selectedPrimary)
|
||||||
light = ClassicLight(selectedPrimary),
|
|
||||||
dark = ClassicDark(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()) {
|
fun TextSecurePreferences.primaryColor(): Color = when(getSelectedAccentColor()) {
|
||||||
@ -60,6 +45,3 @@ fun TextSecurePreferences.primaryColor(): Color = when(getSelectedAccentColor())
|
|||||||
YELLOW_ACCENT -> primaryYellow
|
YELLOW_ACCENT -> primaryYellow
|
||||||
else -> primaryGreen
|
else -> primaryGreen
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -20,7 +20,12 @@ import org.session.libsession.utilities.AppTextSecurePreferences
|
|||||||
val LocalColors = compositionLocalOf <ThemeColors> { ClassicDark() }
|
val LocalColors = compositionLocalOf <ThemeColors> { ClassicDark() }
|
||||||
val LocalType = compositionLocalOf { sessionTypography }
|
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.
|
* Apply a Material2 compose theme based on user selections in SharedPreferences.
|
||||||
@ -29,15 +34,15 @@ var selectedTheme: ThemeColors? = null
|
|||||||
fun SessionMaterialTheme(
|
fun SessionMaterialTheme(
|
||||||
content: @Composable () -> Unit
|
content: @Composable () -> Unit
|
||||||
) {
|
) {
|
||||||
// set the theme data if it hasn't been done yet
|
val context = LocalContext.current
|
||||||
if(selectedTheme == null) {
|
val preferences = AppTextSecurePreferences(context)
|
||||||
// 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()
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
LocalType provides sessionTypography,
|
||||||
LocalContentColor provides colors.text,
|
LocalContentColor provides colors.text,
|
||||||
LocalTextSelectionColors provides colors.textSelectionColors,
|
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
|
package org.thoughtcrime.securesms.webrtc
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import org.session.libsignal.crypto.shuffledRandom
|
||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
import org.session.libsignal.utilities.SettableFuture
|
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.Camera
|
||||||
import org.thoughtcrime.securesms.webrtc.video.CameraEventListener
|
import org.thoughtcrime.securesms.webrtc.video.CameraEventListener
|
||||||
import org.thoughtcrime.securesms.webrtc.video.CameraState
|
import org.thoughtcrime.securesms.webrtc.video.CameraState
|
||||||
@ -22,9 +24,7 @@ import org.webrtc.SurfaceTextureHelper
|
|||||||
import org.webrtc.VideoSink
|
import org.webrtc.VideoSink
|
||||||
import org.webrtc.VideoSource
|
import org.webrtc.VideoSource
|
||||||
import org.webrtc.VideoTrack
|
import org.webrtc.VideoTrack
|
||||||
import java.security.SecureRandom
|
|
||||||
import java.util.concurrent.ExecutionException
|
import java.util.concurrent.ExecutionException
|
||||||
import kotlin.random.asKotlinRandom
|
|
||||||
|
|
||||||
class PeerConnectionWrapper(private val context: Context,
|
class PeerConnectionWrapper(private val context: Context,
|
||||||
private val factory: PeerConnectionFactory,
|
private val factory: PeerConnectionFactory,
|
||||||
@ -49,8 +49,7 @@ class PeerConnectionWrapper(private val context: Context,
|
|||||||
private var isInitiator = false
|
private var isInitiator = false
|
||||||
|
|
||||||
private fun initPeerConnection() {
|
private fun initPeerConnection() {
|
||||||
val random = SecureRandom().asKotlinRandom()
|
val iceServers = listOf("freyr","angus","hereford","holstein", "brahman").shuffledRandom().take(2).map { sub ->
|
||||||
val iceServers = listOf("freyr","angus","hereford","holstein", "brahman").shuffled(random).take(2).map { sub ->
|
|
||||||
PeerConnection.IceServer.builder("turn:$sub.getsession.org")
|
PeerConnection.IceServer.builder("turn:$sub.getsession.org")
|
||||||
.setUsername("session202111")
|
.setUsername("session202111")
|
||||||
.setPassword("053c268164bc7bd7")
|
.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
|
config_base.cpp
|
||||||
contacts.cpp
|
contacts.cpp
|
||||||
conversation.cpp
|
conversation.cpp
|
||||||
|
blinded_key.cpp
|
||||||
util.cpp)
|
util.cpp)
|
||||||
|
|
||||||
add_library( # Sets the name of the library.
|
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
|
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.Promise
|
||||||
import nl.komponents.kovenant.functional.map
|
import nl.komponents.kovenant.functional.map
|
||||||
import okhttp3.Headers
|
|
||||||
import okhttp3.Headers.Companion.toHeaders
|
import okhttp3.Headers.Companion.toHeaders
|
||||||
import okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
import okhttp3.MediaType
|
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.RequestBody
|
import okhttp3.RequestBody
|
||||||
|
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||||
import org.session.libsession.snode.OnionRequestAPI
|
import org.session.libsession.snode.OnionRequestAPI
|
||||||
|
import org.session.libsession.snode.utilities.await
|
||||||
import org.session.libsignal.utilities.HTTP
|
import org.session.libsignal.utilities.HTTP
|
||||||
import org.session.libsignal.utilities.JsonUtil
|
import org.session.libsignal.utilities.JsonUtil
|
||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
|
import org.session.libsignal.utilities.toHexString
|
||||||
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
object FileServerApi {
|
object FileServerApi {
|
||||||
|
|
||||||
@ -23,6 +27,7 @@ object FileServerApi {
|
|||||||
sealed class Error(message: String) : Exception(message) {
|
sealed class Error(message: String) : Exception(message) {
|
||||||
object ParsingFailed : Error("Invalid response.")
|
object ParsingFailed : Error("Invalid response.")
|
||||||
object InvalidURL : Error("Invalid URL.")
|
object InvalidURL : Error("Invalid URL.")
|
||||||
|
object NoEd25519KeyPair : Error("Couldn't find ed25519 key pair.")
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Request(
|
data class Request(
|
||||||
@ -105,4 +110,50 @@ object FileServerApi {
|
|||||||
val request = Request(verb = HTTP.Verb.GET, endpoint = "file/$file")
|
val request = Request(verb = HTTP.Verb.GET, endpoint = "file/$file")
|
||||||
return send(request)
|
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
|
// return a list of batch request objects
|
||||||
val snodeMessage = MessageSender.buildConfigMessageToSnode(destination.destinationPublicKey(), message)
|
val snodeMessage = MessageSender.buildConfigMessageToSnode(destination.destinationPublicKey(), message)
|
||||||
val authenticated = SnodeAPI.buildAuthenticatedStoreBatchInfo(
|
val authenticated = SnodeAPI.buildAuthenticatedStoreBatchInfo(
|
||||||
destination.destinationPublicKey(),
|
|
||||||
config.configNamespace(),
|
config.configNamespace(),
|
||||||
snodeMessage
|
snodeMessage
|
||||||
) ?: return@map null // this entry will be null otherwise
|
) ?: 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.libsession.utilities.recipients.Recipient
|
||||||
import org.session.libsignal.streams.ProfileCipherInputStream
|
import org.session.libsignal.streams.ProfileCipherInputStream
|
||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
|
import org.session.libsignal.utilities.Util.SECURE_RANDOM
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.security.SecureRandom
|
|
||||||
import java.util.concurrent.ConcurrentSkipListSet
|
import java.util.concurrent.ConcurrentSkipListSet
|
||||||
|
|
||||||
class RetrieveProfileAvatarJob(private val profileAvatar: String?, val recipientAddress: Address): Job {
|
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())
|
Log.w(TAG, "Removing profile avatar for: " + recipient.address.serialize())
|
||||||
|
|
||||||
if (recipient.isLocalNumber) {
|
if (recipient.isLocalNumber) {
|
||||||
setProfileAvatarId(context, SecureRandom().nextInt())
|
setProfileAvatarId(context, SECURE_RANDOM.nextInt())
|
||||||
setProfilePictureURL(context, null)
|
setProfilePictureURL(context, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,7 +83,7 @@ class RetrieveProfileAvatarJob(private val profileAvatar: String?, val recipient
|
|||||||
decryptDestination.renameTo(AvatarHelper.getAvatarFile(context, recipient.address))
|
decryptDestination.renameTo(AvatarHelper.getAvatarFile(context, recipient.address))
|
||||||
|
|
||||||
if (recipient.isLocalNumber) {
|
if (recipient.isLocalNumber) {
|
||||||
setProfileAvatarId(context, SecureRandom().nextInt())
|
setProfileAvatarId(context, SECURE_RANDOM.nextInt())
|
||||||
setProfilePictureURL(context, profileAvatar)
|
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.messaging.jobs.MessageReceiveParameters
|
||||||
import org.session.libsession.snode.SnodeAPI
|
import org.session.libsession.snode.SnodeAPI
|
||||||
import org.session.libsession.utilities.GroupUtil
|
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.Log
|
||||||
import org.session.libsignal.utilities.Namespace
|
import org.session.libsignal.utilities.Namespace
|
||||||
import org.session.libsignal.utilities.defaultRequiresAuth
|
import org.session.libsignal.utilities.defaultRequiresAuth
|
||||||
@ -104,7 +104,7 @@ class ClosedGroupPollerV2 {
|
|||||||
fun poll(groupPublicKey: String): Promise<Unit, Exception> {
|
fun poll(groupPublicKey: String): Promise<Unit, Exception> {
|
||||||
if (!isPolling(groupPublicKey)) { return Promise.of(Unit) }
|
if (!isPolling(groupPublicKey)) { return Promise.of(Unit) }
|
||||||
val promise = SnodeAPI.getSwarm(groupPublicKey).bind { swarm ->
|
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() }
|
if (!isPolling(groupPublicKey)) { throw PollingCanceledException() }
|
||||||
val currentForkInfo = SnodeAPI.forkInfo
|
val currentForkInfo = SnodeAPI.forkInfo
|
||||||
when {
|
when {
|
||||||
|
@ -19,8 +19,6 @@ import org.session.libsession.messaging.MessagingModuleConfiguration
|
|||||||
import org.session.libsession.messaging.jobs.BatchMessageReceiveJob
|
import org.session.libsession.messaging.jobs.BatchMessageReceiveJob
|
||||||
import org.session.libsession.messaging.jobs.JobQueue
|
import org.session.libsession.messaging.jobs.JobQueue
|
||||||
import org.session.libsession.messaging.jobs.MessageReceiveParameters
|
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.RawResponse
|
||||||
import org.session.libsession.snode.SnodeAPI
|
import org.session.libsession.snode.SnodeAPI
|
||||||
import org.session.libsession.snode.SnodeModule
|
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.Log
|
||||||
import org.session.libsignal.utilities.Namespace
|
import org.session.libsignal.utilities.Namespace
|
||||||
import org.session.libsignal.utilities.Snode
|
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.Timer
|
||||||
import java.util.TimerTask
|
import java.util.TimerTask
|
||||||
import kotlin.time.Duration.Companion.days
|
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 swarm = SnodeModule.shared.storage.getSwarm(userPublicKey) ?: setOf()
|
||||||
val unusedSnodes = swarm.subtract(usedSnodes)
|
val unusedSnodes = swarm.subtract(usedSnodes)
|
||||||
if (unusedSnodes.isNotEmpty()) {
|
if (unusedSnodes.isNotEmpty()) {
|
||||||
val index = SecureRandom().nextInt(unusedSnodes.size)
|
val index = SECURE_RANDOM.nextInt(unusedSnodes.size)
|
||||||
val nextSnode = unusedSnodes.elementAt(index)
|
val nextSnode = unusedSnodes.elementAt(index)
|
||||||
usedSnodes.add(nextSnode)
|
usedSnodes.add(nextSnode)
|
||||||
Log.d(TAG, "Polling $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 messages = rawMessages["messages"] as? List<*>
|
||||||
val processed = if (!messages.isNullOrEmpty()) {
|
val processed = if (!messages.isNullOrEmpty()) {
|
||||||
SnodeAPI.updateLastMessageHashValueIfPossible(snode, userPublicKey, messages, namespace)
|
SnodeAPI.updateLastMessageHashValueIfPossible(snode, userPublicKey, messages, namespace)
|
||||||
SnodeAPI.removeDuplicates(userPublicKey, messages, namespace, true).mapNotNull { messageBody ->
|
SnodeAPI.removeDuplicates(userPublicKey, messages, namespace, true).mapNotNull { rawMessageAsJSON ->
|
||||||
val rawMessageAsJSON = messageBody as? Map<*, *> ?: return@mapNotNull null
|
|
||||||
val hashValue = rawMessageAsJSON["hash"] as? String ?: return@mapNotNull null
|
val hashValue = rawMessageAsJSON["hash"] as? String ?: return@mapNotNull null
|
||||||
val b64EncodedBody = rawMessageAsJSON["data"] as? String ?: return@mapNotNull null
|
val b64EncodedBody = rawMessageAsJSON["data"] as? String ?: return@mapNotNull null
|
||||||
val timestamp = rawMessageAsJSON["t"] as? Long ?: SnodeAPI.nowWithOffset
|
val timestamp = rawMessageAsJSON["t"] as? Long ?: SnodeAPI.nowWithOffset
|
||||||
|
@ -28,7 +28,7 @@ object MessageWrapper {
|
|||||||
val webSocketMessage = createWebSocketMessage(envelope)
|
val webSocketMessage = createWebSocketMessage(envelope)
|
||||||
return webSocketMessage.toByteArray()
|
return webSocketMessage.toByteArray()
|
||||||
} catch (e: Exception) {
|
} 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 {
|
private fun createWebSocketMessage(envelope: Envelope): WebSocketMessage {
|
||||||
try {
|
try {
|
||||||
val requestBuilder = WebSocketRequestMessage.newBuilder()
|
return WebSocketMessage.newBuilder().apply {
|
||||||
requestBuilder.verb = "PUT"
|
request = WebSocketRequestMessage.newBuilder().apply {
|
||||||
requestBuilder.path = "/api/v1/message"
|
verb = "PUT"
|
||||||
requestBuilder.id = SecureRandom.getInstance("SHA1PRNG").nextLong()
|
path = "/api/v1/message"
|
||||||
requestBuilder.body = envelope.toByteString()
|
id = SecureRandom.getInstance("SHA1PRNG").nextLong()
|
||||||
val messageBuilder = WebSocketMessage.newBuilder()
|
body = envelope.toByteString()
|
||||||
messageBuilder.request = requestBuilder.build()
|
}.build()
|
||||||
messageBuilder.type = WebSocketMessage.Type.REQUEST
|
type = WebSocketMessage.Type.REQUEST
|
||||||
return messageBuilder.build()
|
}.build()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.d("Loki", "Failed to wrap envelope in web socket message: ${e.message}.")
|
Log.d("Loki", "Failed to wrap envelope in web socket message: ${e.message}.")
|
||||||
throw Error.FailedToWrapEnvelopeInWebSocketMessage
|
throw Error.FailedToWrapEnvelopeInWebSocketMessage
|
||||||
|
@ -10,11 +10,10 @@ import okhttp3.Request
|
|||||||
import org.session.libsession.messaging.file_server.FileServerApi
|
import org.session.libsession.messaging.file_server.FileServerApi
|
||||||
import org.session.libsession.utilities.AESGCM
|
import org.session.libsession.utilities.AESGCM
|
||||||
import org.session.libsession.utilities.AESGCM.EncryptionResult
|
import org.session.libsession.utilities.AESGCM.EncryptionResult
|
||||||
import org.session.libsession.utilities.Util
|
|
||||||
import org.session.libsession.utilities.getBodyForOnionRequest
|
import org.session.libsession.utilities.getBodyForOnionRequest
|
||||||
import org.session.libsession.utilities.getHeadersForOnionRequest
|
import org.session.libsession.utilities.getHeadersForOnionRequest
|
||||||
import org.session.libsignal.crypto.getRandomElement
|
import org.session.libsignal.crypto.secureRandom
|
||||||
import org.session.libsignal.crypto.getRandomElementOrNull
|
import org.session.libsignal.crypto.secureRandomOrNull
|
||||||
import org.session.libsignal.database.LokiAPIDatabaseProtocol
|
import org.session.libsignal.database.LokiAPIDatabaseProtocol
|
||||||
import org.session.libsignal.utilities.Base64
|
import org.session.libsignal.utilities.Base64
|
||||||
import org.session.libsignal.utilities.Broadcaster
|
import org.session.libsignal.utilities.Broadcaster
|
||||||
@ -149,7 +148,7 @@ object OnionRequestAPI {
|
|||||||
val reusableGuardSnodeCount = reusableGuardSnodes.count()
|
val reusableGuardSnodeCount = reusableGuardSnodes.count()
|
||||||
if (unusedSnodes.count() < (targetGuardSnodeCount - reusableGuardSnodeCount)) { throw InsufficientSnodesException() }
|
if (unusedSnodes.count() < (targetGuardSnodeCount - reusableGuardSnodeCount)) { throw InsufficientSnodesException() }
|
||||||
fun getGuardSnode(): Promise<Snode, Exception> {
|
fun getGuardSnode(): Promise<Snode, Exception> {
|
||||||
val candidate = unusedSnodes.getRandomElementOrNull()
|
val candidate = unusedSnodes.secureRandomOrNull()
|
||||||
?: return Promise.ofFail(InsufficientSnodesException())
|
?: return Promise.ofFail(InsufficientSnodesException())
|
||||||
unusedSnodes = unusedSnodes.minus(candidate)
|
unusedSnodes = unusedSnodes.minus(candidate)
|
||||||
Log.d("Loki", "Testing guard snode: $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
|
// Don't test path snodes as this would reveal the user's IP to them
|
||||||
guardSnodes.minus(reusableGuardSnodes).map { guardSnode ->
|
guardSnodes.minus(reusableGuardSnodes).map { guardSnode ->
|
||||||
val result = listOf( guardSnode ) + (0 until (pathSize - 1)).mapIndexed() { index, _ ->
|
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
|
// remove the snode from the unused list and return it
|
||||||
unusedSnodes = unusedSnodes.minus(pathSnode)
|
unusedSnodes = unusedSnodes.minus(pathSnode)
|
||||||
@ -228,9 +227,9 @@ object OnionRequestAPI {
|
|||||||
OnionRequestAPI.guardSnodes = guardSnodes
|
OnionRequestAPI.guardSnodes = guardSnodes
|
||||||
fun getPath(paths: List<Path>): Path {
|
fun getPath(paths: List<Path>): Path {
|
||||||
return if (snodeToExclude != null) {
|
return if (snodeToExclude != null) {
|
||||||
paths.filter { !it.contains(snodeToExclude) }.getRandomElement()
|
paths.filter { !it.contains(snodeToExclude) }.secureRandom()
|
||||||
} else {
|
} else {
|
||||||
paths.getRandomElement()
|
paths.secureRandom()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
when {
|
when {
|
||||||
@ -273,7 +272,7 @@ object OnionRequestAPI {
|
|||||||
path.removeAt(snodeIndex)
|
path.removeAt(snodeIndex)
|
||||||
val unusedSnodes = SnodeAPI.snodePool.minus(oldPaths.flatten())
|
val unusedSnodes = SnodeAPI.snodePool.minus(oldPaths.flatten())
|
||||||
if (unusedSnodes.isEmpty()) { throw InsufficientSnodesException() }
|
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
|
// Don't test the new snode as this would reveal the user's IP
|
||||||
oldPaths.removeAt(pathIndex)
|
oldPaths.removeAt(pathIndex)
|
||||||
val newPaths = oldPaths + listOf( path )
|
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.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
import org.session.libsession.R
|
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.AUTOPLAY_AUDIO_MESSAGES
|
||||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.CALL_NOTIFICATIONS_ENABLED
|
import org.session.libsession.utilities.TextSecurePreferences.Companion.CALL_NOTIFICATIONS_ENABLED
|
||||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.CLASSIC_DARK
|
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.FOLLOW_SYSTEM_SETTINGS
|
||||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.HIDE_PASSWORD
|
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_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.LEGACY_PREF_KEY_SELECTED_UI_MODE
|
||||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.OCEAN_DARK
|
import org.session.libsession.utilities.TextSecurePreferences.Companion.OCEAN_DARK
|
||||||
import org.session.libsession.utilities.TextSecurePreferences.Companion.OCEAN_LIGHT
|
import org.session.libsession.utilities.TextSecurePreferences.Companion.OCEAN_LIGHT
|
||||||
@ -186,6 +188,8 @@ interface TextSecurePreferences {
|
|||||||
fun clearAll()
|
fun clearAll()
|
||||||
fun getHidePassword(): Boolean
|
fun getHidePassword(): Boolean
|
||||||
fun setHidePassword(value: Boolean)
|
fun setHidePassword(value: Boolean)
|
||||||
|
fun getLastVersionCheck(): Long
|
||||||
|
fun setLastVersionCheck()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val TAG = TextSecurePreferences::class.simpleName
|
val TAG = TextSecurePreferences::class.simpleName
|
||||||
@ -272,6 +276,7 @@ interface TextSecurePreferences {
|
|||||||
const val AUTOPLAY_AUDIO_MESSAGES = "pref_autoplay_audio"
|
const val AUTOPLAY_AUDIO_MESSAGES = "pref_autoplay_audio"
|
||||||
const val FINGERPRINT_KEY_GENERATED = "fingerprint_key_generated"
|
const val FINGERPRINT_KEY_GENERATED = "fingerprint_key_generated"
|
||||||
const val SELECTED_ACCENT_COLOR = "selected_accent_color"
|
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_RECEIVED_LEGACY_CONFIG = "has_received_legacy_config"
|
||||||
const val HAS_FORCED_NEW_CONFIG = "has_forced_new_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())
|
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 {
|
override fun setShownCallNotification(): Boolean {
|
||||||
val previousValue = getBooleanPreference(SHOWN_CALL_NOTIFICATION, false)
|
val previousValue = getBooleanPreference(SHOWN_CALL_NOTIFICATION, false)
|
||||||
if (previousValue) return false
|
if (previousValue) return false
|
||||||
|
@ -13,6 +13,7 @@ import android.text.TextUtils
|
|||||||
import android.text.style.StyleSpan
|
import android.text.style.StyleSpan
|
||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
import org.session.libsignal.utilities.Base64
|
import org.session.libsignal.utilities.Base64
|
||||||
|
import org.session.libsignal.utilities.Util.SECURE_RANDOM
|
||||||
import java.io.*
|
import java.io.*
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
@ -292,15 +293,10 @@ object Util {
|
|||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getSecretBytes(size: Int): ByteArray {
|
fun getSecretBytes(size: Int): ByteArray {
|
||||||
val secret = ByteArray(size)
|
val secret = ByteArray(size)
|
||||||
getSecureRandom().nextBytes(secret)
|
SECURE_RANDOM.nextBytes(secret)
|
||||||
return secret
|
return secret
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun getSecureRandom(): SecureRandom {
|
|
||||||
return SecureRandom()
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getFirstNonEmpty(vararg values: String?): String? {
|
fun getFirstNonEmpty(vararg values: String?): String? {
|
||||||
for (value in values) {
|
for (value in values) {
|
||||||
@ -317,18 +313,14 @@ object Util {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun <T> getRandomElement(elements: Array<T>): T {
|
fun <T> getRandomElement(elements: Array<T>): T = elements[SECURE_RANDOM.nextInt(elements.size)]
|
||||||
return elements[SecureRandom().nextInt(elements.size)]
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getBoldedString(value: String?): CharSequence {
|
fun getBoldedString(value: String?): CharSequence {
|
||||||
if (value.isNullOrEmpty()) { return "" }
|
if (value.isNullOrEmpty()) { return "" }
|
||||||
val spanned = SpannableString(value)
|
return SpannableString(value).also {
|
||||||
spanned.setSpan(StyleSpan(Typeface.BOLD), 0,
|
it.setSpan(StyleSpan(Typeface.BOLD), 0, it.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
spanned.length,
|
}
|
||||||
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
||||||
return spanned
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@ -366,34 +358,6 @@ object Util {
|
|||||||
val digitGroups = (Math.log10(sizeBytes.toDouble()) / Math.log10(1024.0)).toInt()
|
val digitGroups = (Math.log10(sizeBytes.toDouble()) / Math.log10(1024.0)).toInt()
|
||||||
return DecimalFormat("#,##0.#").format(sizeBytes / Math.pow(1024.0, digitGroups.toDouble())) + " " + units[digitGroups]
|
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
|
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 }
|
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
|
* 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
|
* 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 {
|
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 } }
|
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
|
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.
|
* 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
|
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)
|
return elementAtOrNull(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uses `SecureRandom` to pick an element from this collection.
|
* Uses `SecureRandom` to pick an element from this collection.
|
||||||
|
*
|
||||||
|
* @throws [NullPointerException] if the [Collection] is empty
|
||||||
*/
|
*/
|
||||||
fun <T> Collection<T>.getRandomElement(): T {
|
fun <T> Collection<T>.secureRandom(): T {
|
||||||
return getRandomElementOrNull()!!
|
return secureRandomOrNull()!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <T> Collection<T>.shuffledRandom(): List<T> = shuffled(SECURE_RANDOM)
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
package org.session.libsignal.streams;
|
package org.session.libsignal.streams;
|
||||||
|
|
||||||
import static org.session.libsignal.crypto.CipherUtil.CIPHER_LOCK;
|
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.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.SecureRandom;
|
|
||||||
|
|
||||||
import javax.crypto.BadPaddingException;
|
import javax.crypto.BadPaddingException;
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
@ -80,7 +80,7 @@ public class ProfileCipherOutputStream extends DigestingOutputStream {
|
|||||||
|
|
||||||
private byte[] generateNonce() {
|
private byte[] generateNonce() {
|
||||||
byte[] nonce = new byte[12];
|
byte[] nonce = new byte[12];
|
||||||
new SecureRandom().nextBytes(nonce);
|
SECURE_RANDOM.nextBytes(nonce);
|
||||||
return nonce;
|
return nonce;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package org.session.libsignal.utilities;
|
package org.session.libsignal.utilities;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>Encodes and decodes to and from Base64 notation.</p>
|
* <p>Encodes and decodes to and from Base64 notation.</p>
|
||||||
* <p>Homepage: <a href="http://iharder.net/base64">http://iharder.net/base64</a>.</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
|
* @throws NullPointerException if source array is null
|
||||||
* @since 1.4
|
* @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,
|
// 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're not going to have an java.io.IOException thrown, so
|
||||||
// we should not force the user to have to catch it.
|
// 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.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.RequestBody
|
import okhttp3.RequestBody
|
||||||
import okhttp3.Response
|
import org.session.libsignal.utilities.Util.SECURE_RANDOM
|
||||||
import java.security.SecureRandom
|
|
||||||
import java.security.cert.X509Certificate
|
import java.security.cert.X509Certificate
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import javax.net.ssl.SSLContext
|
import javax.net.ssl.SSLContext
|
||||||
@ -35,7 +34,7 @@ object HTTP {
|
|||||||
override fun getAcceptedIssuers(): Array<X509Certificate> { return arrayOf() }
|
override fun getAcceptedIssuers(): Array<X509Certificate> { return arrayOf() }
|
||||||
}
|
}
|
||||||
val sslContext = SSLContext.getInstance("SSL")
|
val sslContext = SSLContext.getInstance("SSL")
|
||||||
sslContext.init(null, arrayOf( trustManager ), SecureRandom())
|
sslContext.init(null, arrayOf( trustManager ), SECURE_RANDOM)
|
||||||
OkHttpClient().newBuilder()
|
OkHttpClient().newBuilder()
|
||||||
.sslSocketFactory(sslContext.socketFactory, trustManager)
|
.sslSocketFactory(sslContext.socketFactory, trustManager)
|
||||||
.hostnameVerifier { _, _ -> true }
|
.hostnameVerifier { _, _ -> true }
|
||||||
@ -55,7 +54,7 @@ object HTTP {
|
|||||||
override fun getAcceptedIssuers(): Array<X509Certificate> { return arrayOf() }
|
override fun getAcceptedIssuers(): Array<X509Certificate> { return arrayOf() }
|
||||||
}
|
}
|
||||||
val sslContext = SSLContext.getInstance("SSL")
|
val sslContext = SSLContext.getInstance("SSL")
|
||||||
sslContext.init(null, arrayOf( trustManager ), SecureRandom())
|
sslContext.init(null, arrayOf( trustManager ), SECURE_RANDOM)
|
||||||
return OkHttpClient().newBuilder()
|
return OkHttpClient().newBuilder()
|
||||||
.sslSocketFactory(sslContext.socketFactory, trustManager)
|
.sslSocketFactory(sslContext.socketFactory, trustManager)
|
||||||
.hostnameVerifier { _, _ -> true }
|
.hostnameVerifier { _, _ -> true }
|
||||||
|
@ -1,9 +1,25 @@
|
|||||||
package org.session.libsignal.utilities
|
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://")
|
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"),
|
GetSwarm("get_snodes_for_pubkey"),
|
||||||
Retrieve("retrieve"),
|
Retrieve("retrieve"),
|
||||||
SendMessage("store"),
|
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)
|
data class KeySet(val ed25519Key: String, val x25519Key: String)
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?) = other is Snode && address == other.address && port == other.port
|
||||||
return if (other is Snode) {
|
override fun hashCode(): Int = address.hashCode() xor port.hashCode()
|
||||||
address == other.address && port == other.port
|
override fun toString(): String = "$address:$port"
|
||||||
} else {
|
|
||||||
false
|
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 {
|
internal constructor(value: String): this(
|
||||||
return address.hashCode() xor port.hashCode()
|
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.io.OutputStream;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class Util {
|
public class Util {
|
||||||
|
public static SecureRandom SECURE_RANDOM = new SecureRandom();
|
||||||
|
|
||||||
public static byte[] join(byte[]... input) {
|
public static byte[] join(byte[]... input) {
|
||||||
try {
|
try {
|
||||||
@ -67,7 +65,7 @@ public class Util {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isEmpty(String value) {
|
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) {
|
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 {
|
public static String readFully(InputStream in) throws IOException {
|
||||||
ByteArrayOutputStream bout = new ByteArrayOutputStream();
|
ByteArrayOutputStream bout = new ByteArrayOutputStream();
|
||||||
byte[] buffer = new byte[4096];
|
byte[] buffer = new byte[4096];
|
||||||
@ -98,7 +89,7 @@ public class Util {
|
|||||||
|
|
||||||
in.close();
|
in.close();
|
||||||
|
|
||||||
return new String(bout.toByteArray());
|
return bout.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void readFully(InputStream in, byte[] buffer) throws IOException {
|
public static void readFully(InputStream in, byte[] buffer) throws IOException {
|
||||||
@ -146,9 +137,4 @@ public class Util {
|
|||||||
}
|
}
|
||||||
return (int)value;
|
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