feat: finish most wrappers and compile issues, start new closed group compose code

pull/1403/head
0x330a 2 years ago
parent 993a2c4a64
commit 76ae6997db
No known key found for this signature in database
GPG Key ID: 267811D6E6A2698C

@ -19,7 +19,7 @@ object MentionManagerUtilities {
}
recipient.address.isClosedGroup -> {
val members = storage.getMembers(recipient.address.serialize())
TODO("Fix when compile errors are dealt with for recipient closed groups")
result.addAll(members.map { it.sessionId })
}
recipient.address.isOpenGroup -> {
val messageDatabase = DatabaseComponent.get(context).mmsSmsDatabase()

@ -10,7 +10,7 @@ import network.loki.messenger.libsession_util.Contacts
import network.loki.messenger.libsession_util.ConversationVolatileConfig
import network.loki.messenger.libsession_util.GroupInfoConfig
import network.loki.messenger.libsession_util.GroupKeysConfig
import network.loki.messenger.libsession_util.GroupMemberConfig
import network.loki.messenger.libsession_util.GroupMembersConfig
import network.loki.messenger.libsession_util.UserGroupsConfig
import network.loki.messenger.libsession_util.UserProfile
import network.loki.messenger.libsession_util.util.BaseCommunityInfo
@ -94,6 +94,7 @@ import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
import org.thoughtcrime.securesms.util.SessionMetaProtocol
import java.security.MessageDigest
import network.loki.messenger.libsession_util.util.Contact as LibSessionContact
import network.loki.messenger.libsession_util.util.GroupMember as LibSessionGroupMember
open class Storage(context: Context, helper: SQLCipherOpenHelper, private val configFactory: ConfigFactory) : Database(context, helper), StorageProtocol,
ThreadDatabase.ConversationThreadUpdateListener {
@ -460,9 +461,9 @@ open class Storage(context: Context, helper: SQLCipherOpenHelper, private val co
is Contacts -> updateContacts(forConfigObject)
is ConversationVolatileConfig -> updateConvoVolatile(forConfigObject)
is UserGroupsConfig -> updateUserGroups(forConfigObject)
is GroupInfoConfig -> TODO()
is GroupKeysConfig -> TODO()
is GroupMemberConfig -> TODO()
is GroupInfoConfig -> updateGroupInfo(forConfigObject)
is GroupKeysConfig -> updateGroupKeys(forConfigObject)
is GroupMembersConfig -> updateGroupMembers(forConfigObject)
}
}
@ -499,6 +500,19 @@ open class Storage(context: Context, helper: SQLCipherOpenHelper, private val co
}
private fun updateGroupInfo(groupInfoConfig: GroupInfoConfig) {
val threadId = getOrCreateThreadIdFor(Address.fromSerialized(groupInfoConfig.id().hexString()))
// TODO: handle deleted group, handle delete attachment / message before a certain time
}
private fun updateGroupKeys(groupKeys: GroupKeysConfig) {
// TODO: update something here?
}
private fun updateGroupMembers(groupMembers: GroupMembersConfig) {
// TODO: maybe clear out some contacts or something?
}
private fun updateContacts(contacts: Contacts) {
val extracted = contacts.all().toList()
addLibSessionContacts(extracted)
@ -1030,10 +1044,8 @@ open class Storage(context: Context, helper: SQLCipherOpenHelper, private val co
}
}
override fun getMembers(groupPublicKey: String): List<GroupMember> {
val groups = configFactory.userGroups ?: return emptyList()
TODO("Add group specific configs")
}
override fun getMembers(groupPublicKey: String): List<LibSessionGroupMember> =
configFactory.groupMemberConfig(SessionId.from(groupPublicKey))?.all()?.toList() ?: emptyList()
override fun setServerCapabilities(server: String, capabilities: List<String>) {
return DatabaseComponent.get(context).lokiAPIDatabase().setServerCapabilities(server, capabilities)
@ -1603,8 +1615,8 @@ open class Storage(context: Context, helper: SQLCipherOpenHelper, private val co
}
getAllContacts().forEach { contact ->
val sessionId = SessionId(contact.sessionID)
if (sessionId.prefix == IdPrefix.STANDARD && SodiumUtilities.sessionId(sessionId.hexString, blindedId, serverPublicKey)) {
val contactMapping = mapping.copy(sessionId = sessionId.hexString)
if (sessionId.prefix == IdPrefix.STANDARD && SodiumUtilities.sessionId(sessionId.hexString(), blindedId, serverPublicKey)) {
val contactMapping = mapping.copy(sessionId = sessionId.hexString())
db.addBlindedIdMapping(contactMapping)
return contactMapping
}

@ -5,6 +5,9 @@ import android.os.Trace
import network.loki.messenger.libsession_util.ConfigBase
import network.loki.messenger.libsession_util.Contacts
import network.loki.messenger.libsession_util.ConversationVolatileConfig
import network.loki.messenger.libsession_util.GroupInfoConfig
import network.loki.messenger.libsession_util.GroupKeysConfig
import network.loki.messenger.libsession_util.GroupMembersConfig
import network.loki.messenger.libsession_util.UserGroupsConfig
import network.loki.messenger.libsession_util.UserProfile
import org.session.libsession.snode.SnodeAPI
@ -12,7 +15,9 @@ import org.session.libsession.utilities.ConfigFactoryProtocol
import org.session.libsession.utilities.ConfigFactoryUpdateListener
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage
import org.session.libsignal.utilities.Hex
import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.SessionId
import org.thoughtcrime.securesms.database.ConfigDatabase
import org.thoughtcrime.securesms.dependencies.DatabaseComponent.Companion.get
import org.thoughtcrime.securesms.groups.GroupManager
@ -61,7 +66,7 @@ class ConfigFactory(
listeners -= listener
}
private inline fun <T> synchronizedWithLog(lock: Any, body: ()->T): T {
private inline fun <T> synchronizedWithLog(lock: Any, body: () -> T): T {
Trace.beginSection("synchronizedWithLog")
val result = synchronized(lock) {
body()
@ -72,7 +77,11 @@ class ConfigFactory(
override val user: UserProfile?
get() = synchronizedWithLog(userLock) {
if (!ConfigBase.isNewConfigEnabled(isConfigForcedOn, SnodeAPI.nowWithOffset)) return null
if (!ConfigBase.isNewConfigEnabled(
isConfigForcedOn,
SnodeAPI.nowWithOffset
)
) return null
if (_userConfig == null) {
val (secretKey, publicKey) = maybeGetUserInfo() ?: return null
val userDump = configDatabase.retrieveConfigAndHashes(
@ -92,7 +101,11 @@ class ConfigFactory(
override val contacts: Contacts?
get() = synchronizedWithLog(contactsLock) {
if (!ConfigBase.isNewConfigEnabled(isConfigForcedOn, SnodeAPI.nowWithOffset)) return null
if (!ConfigBase.isNewConfigEnabled(
isConfigForcedOn,
SnodeAPI.nowWithOffset
)
) return null
if (_contacts == null) {
val (secretKey, publicKey) = maybeGetUserInfo() ?: return null
val contactsDump = configDatabase.retrieveConfigAndHashes(
@ -112,7 +125,11 @@ class ConfigFactory(
override val convoVolatile: ConversationVolatileConfig?
get() = synchronizedWithLog(convoVolatileLock) {
if (!ConfigBase.isNewConfigEnabled(isConfigForcedOn, SnodeAPI.nowWithOffset)) return null
if (!ConfigBase.isNewConfigEnabled(
isConfigForcedOn,
SnodeAPI.nowWithOffset
)
) return null
if (_convoVolatileConfig == null) {
val (secretKey, publicKey) = maybeGetUserInfo() ?: return null
val convoDump = configDatabase.retrieveConfigAndHashes(
@ -133,7 +150,11 @@ class ConfigFactory(
override val userGroups: UserGroupsConfig?
get() = synchronizedWithLog(userGroupsLock) {
if (!ConfigBase.isNewConfigEnabled(isConfigForcedOn, SnodeAPI.nowWithOffset)) return null
if (!ConfigBase.isNewConfigEnabled(
isConfigForcedOn,
SnodeAPI.nowWithOffset
)
) return null
if (_userGroups == null) {
val (secretKey, publicKey) = maybeGetUserInfo() ?: return null
val userGroupsDump = configDatabase.retrieveConfigAndHashes(
@ -151,6 +172,61 @@ class ConfigFactory(
_userGroups
}
private fun getGroupAuthInfo(groupSessionId: SessionId) = userGroups?.getClosedGroup(groupSessionId.hexString())?.let {
it.adminKey to it.authData
}
override fun groupInfoConfig(groupSessionId: SessionId): GroupInfoConfig? = getGroupAuthInfo(groupSessionId)?.let { (sk, _) ->
// get any potential initial dumps
val dump = configDatabase.retrieveConfigAndHashes(
SharedConfigMessage.Kind.CLOSED_GROUP_INFO.name,
groupSessionId.hexString()
) ?: byteArrayOf()
GroupInfoConfig.newInstance(Hex.fromStringCondensed(groupSessionId.publicKey), sk, dump)
}
override fun groupKeysConfig(groupSessionId: SessionId): GroupKeysConfig? = getGroupAuthInfo(groupSessionId)?.let { (sk, _) ->
// Get the user info or return early
val (userSk, _) = maybeGetUserInfo() ?: return@let null
// Get the group info or return early
val info = groupInfoConfig(groupSessionId) ?: return@let null
// Get the group members or return early
val members = groupMemberConfig(groupSessionId) ?: return@let null
// Get the dump or empty
val dump = configDatabase.retrieveConfigAndHashes(
SharedConfigMessage.Kind.ENCRYPTION_KEYS.name,
groupSessionId.hexString()
) ?: byteArrayOf()
// Put it all together
GroupKeysConfig.newInstance(
userSk,
Hex.fromStringCondensed(groupSessionId.publicKey),
sk,
dump,
info,
members
)
}
override fun groupMemberConfig(groupSessionId: SessionId): GroupMembersConfig? = getGroupAuthInfo(groupSessionId)?.let { (sk, auth) ->
// Get initial dump if we have one
val dump = configDatabase.retrieveConfigAndHashes(
SharedConfigMessage.Kind.CLOSED_GROUP_MEMBERS.name,
groupSessionId.hexString()
) ?: byteArrayOf()
GroupMembersConfig.newInstance(
Hex.fromStringCondensed(groupSessionId.publicKey),
sk,
dump
)
}
override fun getUserConfigs(): List<ConfigBase> =
listOfNotNull(user, contacts, convoVolatile, userGroups)
@ -158,13 +234,23 @@ class ConfigFactory(
private fun persistUserConfigDump(timestamp: Long) = synchronized(userLock) {
val dumped = user?.dump() ?: return
val (_, publicKey) = maybeGetUserInfo() ?: return
configDatabase.storeConfig(SharedConfigMessage.Kind.USER_PROFILE.name, publicKey, dumped, timestamp)
configDatabase.storeConfig(
SharedConfigMessage.Kind.USER_PROFILE.name,
publicKey,
dumped,
timestamp
)
}
private fun persistContactsConfigDump(timestamp: Long) = synchronized(contactsLock) {
val dumped = contacts?.dump() ?: return
val (_, publicKey) = maybeGetUserInfo() ?: return
configDatabase.storeConfig(SharedConfigMessage.Kind.CONTACTS.name, publicKey, dumped, timestamp)
configDatabase.storeConfig(
SharedConfigMessage.Kind.CONTACTS.name,
publicKey,
dumped,
timestamp
)
}
private fun persistConvoVolatileConfigDump(timestamp: Long) = synchronized(convoVolatileLock) {
@ -181,7 +267,12 @@ class ConfigFactory(
private fun persistUserGroupsConfigDump(timestamp: Long) = synchronized(userGroupsLock) {
val dumped = userGroups?.dump() ?: return
val (_, publicKey) = maybeGetUserInfo() ?: return
configDatabase.storeConfig(SharedConfigMessage.Kind.GROUPS.name, publicKey, dumped, timestamp)
configDatabase.storeConfig(
SharedConfigMessage.Kind.GROUPS.name,
publicKey,
dumped,
timestamp
)
}
override fun persist(forConfigObject: ConfigBase, timestamp: Long) {
@ -214,23 +305,21 @@ class ConfigFactory(
if (openGroupId != null) {
val userGroups = userGroups ?: return false
val threadId = GroupManager.getOpenGroupThreadID(openGroupId, context)
val openGroup = get(context).lokiThreadDatabase().getOpenGroupChat(threadId) ?: return false
val openGroup =
get(context).lokiThreadDatabase().getOpenGroupChat(threadId) ?: return false
// Not handling the `hidden` behaviour for communities so just indicate the existence
return (userGroups.getCommunityInfo(openGroup.server, openGroup.room) != null)
}
else if (groupPublicKey != null) {
} else if (groupPublicKey != null) {
val userGroups = userGroups ?: return false
// Not handling the `hidden` behaviour for legacy groups so just indicate the existence
return (userGroups.getLegacyGroupInfo(groupPublicKey) != null)
}
else if (publicKey == userPublicKey) {
} else if (publicKey == userPublicKey) {
val user = user ?: return false
return (!visibleOnly || user.getNtsPriority() != ConfigBase.PRIORITY_HIDDEN)
}
else if (publicKey != null) {
} else if (publicKey != null) {
val contacts = contacts ?: return false
val targetContact = contacts.get(publicKey) ?: return false
@ -240,12 +329,18 @@ class ConfigFactory(
return false
}
override fun canPerformChange(variant: String, publicKey: String, changeTimestampMs: Long): Boolean {
override fun canPerformChange(
variant: String,
publicKey: String,
changeTimestampMs: Long
): Boolean {
if (!ConfigBase.isNewConfigEnabled(isConfigForcedOn, SnodeAPI.nowWithOffset)) return true
val lastUpdateTimestampMs = configDatabase.retrieveConfigLastUpdateTimestamp(variant, publicKey)
val lastUpdateTimestampMs =
configDatabase.retrieveConfigLastUpdateTimestamp(variant, publicKey)
// Ensure the change occurred after the last config message was handled (minus the buffer period)
return (changeTimestampMs >= (lastUpdateTimestampMs - ConfigFactory.configChangeBufferPeriod))
}
}

@ -34,16 +34,9 @@ object ClosedGroupManager {
}
}
fun ConfigFactory.removeLegacyGroup(group: GroupRecord): Boolean {
val groups = userGroups ?: return false
if (!group.isClosedGroup) return false
val groupPublicKey = GroupUtil.doubleEncodeGroupID(group.getId())
return groups.eraseLegacyGroup(groupPublicKey)
}
fun ConfigFactory.updateLegacyGroup(groupRecipientSettings: Recipient.RecipientSettings, group: GroupRecord) {
val groups = userGroups ?: return
if (!group.isClosedGroup) return
if (!group.isLegacyClosedGroup) return
val storage = MessagingModuleConfiguration.shared.storage
val threadId = storage.getThreadId(group.encodedId) ?: return
val groupPublicKey = GroupUtil.doubleEncodeGroupID(group.getId())

@ -7,6 +7,13 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.tooling.preview.Preview
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
@ -21,6 +28,7 @@ import nl.komponents.kovenant.ui.successUi
import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.messaging.sending_receiving.groupSizeLimit
import org.session.libsession.utilities.Address
import org.session.libsession.utilities.Contact
import org.session.libsession.utilities.Device
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.recipients.Recipient
@ -49,8 +57,11 @@ class CreateGroupFragment : Fragment() {
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentCreateGroupBinding.inflate(inflater)
return binding.root
return ComposeView(requireContext()).apply {
setContent {
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -121,4 +132,15 @@ class CreateGroupFragment : Fragment() {
context.startActivity(intent)
}
}
@Composable
fun MemberList(contacts: List<Contact>, modifier: Modifier = Modifier) {
Avatar
}
@Preview
@Composable
fun ClosedGroupPreview() {
MemberList(contacts = emptyList(), modifier = Modifier.fillMaxWidth())
}

@ -144,7 +144,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
intent.putExtra(ConversationActivityV2.ADDRESS, Address.fromSerialized(address))
push(intent)
}
is GlobalSearchAdapter.Model.GroupConversation -> {
is GlobalSearchAdapter.Model.LegacyGroupConversation -> {
val groupAddress = Address.fromSerialized(model.groupRecord.encodedId)
val threadId = threadDb.getThreadIdIfExistsFor(Recipient.from(this, groupAddress, false))
if (threadId >= 0) {
@ -257,7 +257,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
globalSearchViewModel.result.collect { result ->
val currentUserPublicKey = publicKey
val contactAndGroupList = result.contacts.map { GlobalSearchAdapter.Model.Contact(it) } +
result.threads.map { GlobalSearchAdapter.Model.GroupConversation(it) }
result.threads.map { GlobalSearchAdapter.Model.LegacyGroupConversation(it) }
val contactResults = contactAndGroupList.toMutableList()
@ -642,14 +642,18 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
// Cancel any outstanding jobs
DatabaseComponent.get(context).sessionJobDatabase().cancelPendingMessageSendJobs(threadID)
// Send a leave group message if this is an active closed group
if (recipient.address.isClosedGroup && DatabaseComponent.get(context).groupDatabase().isActive(recipient.address.toGroupString())) {
if (recipient.address.isLegacyClosedGroup && DatabaseComponent.get(context).groupDatabase().isActive(recipient.address.toGroupString())) {
try {
GroupUtil.doubleDecodeGroupID(recipient.address.toString()).toHexString()
.takeIf(DatabaseComponent.get(context).lokiAPIDatabase()::isClosedGroup)
?.let { MessageSender.explicitLeave(it, false) }
} catch (_: IOException) {
} catch (e: IOException) {
Log.e("Loki", e)
}
}
if (recipient.address.isClosedGroup) {
TODO("Implement leaving / deleting a new closed group conversation")
}
// Delete the conversation
val v2OpenGroup = DatabaseComponent.get(context).lokiThreadDatabase().getOpenGroupChat(threadID)
if (v2OpenGroup != null) {

@ -9,9 +9,10 @@ import androidx.recyclerview.widget.RecyclerView
import network.loki.messenger.R
import network.loki.messenger.databinding.ViewGlobalSearchHeaderBinding
import network.loki.messenger.databinding.ViewGlobalSearchResultBinding
import network.loki.messenger.libsession_util.util.GroupInfo
import org.session.libsession.utilities.GroupRecord
import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.mms.GlideApp
import org.session.libsignal.utilities.SessionId
import org.thoughtcrime.securesms.search.model.MessageResult
import java.security.InvalidParameterException
import org.session.libsession.messaging.contacts.Contact as ContactModel
@ -98,7 +99,7 @@ class GlobalSearchAdapter (private val modelCallback: (Model)->Unit): RecyclerVi
fun bind(query: String, model: Model) {
binding.searchResultProfilePicture.recycle()
when (model) {
is Model.GroupConversation -> bindModel(query, model)
is Model.LegacyGroupConversation -> bindModel(query, model)
is Model.Contact -> bindModel(query, model)
is Model.Message -> bindModel(query, model)
is Model.SavedMessages -> bindModel(model)
@ -119,7 +120,8 @@ class GlobalSearchAdapter (private val modelCallback: (Model)->Unit): RecyclerVi
data class Header(@StringRes val title: Int) : Model()
data class SavedMessages(val currentUserPublicKey: String): Model()
data class Contact(val contact: ContactModel) : Model()
data class GroupConversation(val groupRecord: GroupRecord) : Model()
data class LegacyGroupConversation(val groupRecord: GroupRecord) : Model()
data class ClosedGroupConversation(val sessionId: SessionId)
data class Message(val messageResult: MessageResult, val unread: Int) : Model()
}

@ -11,7 +11,7 @@ import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.utilities.Address
import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.ContentView
import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.Model.GroupConversation
import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.Model.LegacyGroupConversation
import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.Model.Header
import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.Model.Message
import org.thoughtcrime.securesms.home.search.GlobalSearchAdapter.Model.SavedMessages
@ -65,7 +65,7 @@ fun ContentView.bindQuery(query: String, model: GlobalSearchAdapter.Model) {
binding.searchResultSubtitle.isVisible = true
binding.searchResultTitle.text = model.messageResult.conversationRecipient.toShortString()
}
is GroupConversation -> {
is LegacyGroupConversation -> {
binding.searchResultTitle.text = getHighlight(
query,
model.groupRecord.title
@ -86,10 +86,10 @@ private fun getHighlight(query: String?, toSearch: String): Spannable? {
return SearchUtil.getHighlightedSpan(Locale.getDefault(), BoldStyleFactory, toSearch, query)
}
fun ContentView.bindModel(query: String?, model: GroupConversation) {
fun ContentView.bindModel(query: String?, model: LegacyGroupConversation) {
binding.searchResultProfilePicture.isVisible = true
binding.searchResultSavedMessages.isVisible = false
binding.searchResultSubtitle.isVisible = model.groupRecord.isClosedGroup
binding.searchResultSubtitle.isVisible = model.groupRecord.isLegacyClosedGroup
binding.searchResultTimestamp.isVisible = false
val threadRecipient = Recipient.from(binding.root.context, Address.fromSerialized(model.groupRecord.encodedId), false)
binding.searchResultProfilePicture.update(threadRecipient)
@ -102,7 +102,7 @@ fun ContentView.bindModel(query: String?, model: GroupConversation) {
val address = it.address.serialize()
it.name ?: "${address.take(4)}...${address.takeLast(4)}"
}
if (model.groupRecord.isClosedGroup) {
if (model.groupRecord.isLegacyClosedGroup) {
binding.searchResultSubtitle.text = getHighlight(query, membersString)
}
}
@ -132,11 +132,6 @@ fun ContentView.bindModel(query: String?, model: Message) {
binding.searchResultProfilePicture.isVisible = true
binding.searchResultSavedMessages.isVisible = false
binding.searchResultTimestamp.isVisible = true
// val hasUnreads = model.unread > 0
// binding.unreadCountIndicator.isVisible = hasUnreads
// if (hasUnreads) {
// binding.unreadCountTextView.text = model.unread.toString()
// }
binding.searchResultTimestamp.text = DateUtils.getDisplayFormattedTimeSpanString(binding.root.context, Locale.getDefault(), model.messageResult.sentTimestampMs)
binding.searchResultProfilePicture.update(model.messageResult.conversationRecipient)
val textSpannable = SpannableStringBuilder()

@ -42,7 +42,6 @@ import com.goterl.lazysodium.utils.KeyPair;
import org.session.libsession.messaging.open_groups.OpenGroup;
import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier;
import org.session.libsession.messaging.utilities.SessionId;
import org.session.libsession.messaging.utilities.SodiumUtilities;
import org.session.libsession.snode.SnodeAPI;
import org.session.libsession.utilities.Address;
@ -52,6 +51,7 @@ import org.session.libsession.utilities.TextSecurePreferences;
import org.session.libsession.utilities.recipients.Recipient;
import org.session.libsignal.utilities.IdPrefix;
import org.session.libsignal.utilities.Log;
import org.session.libsignal.utilities.SessionId;
import org.session.libsignal.utilities.Util;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.contacts.ContactUtil;
@ -555,7 +555,7 @@ public class DefaultMessageNotifier implements MessageNotifier {
if (openGroup != null && edKeyPair != null) {
KeyPair blindedKeyPair = SodiumUtilities.blindedKeyPair(openGroup.getPublicKey(), edKeyPair);
if (blindedKeyPair != null) {
return new SessionId(IdPrefix.BLINDED, blindedKeyPair.getPublicKey().getAsBytes()).getHexString();
return new SessionId(IdPrefix.BLINDED, blindedKeyPair.getPublicKey().getAsBytes()).hexString();
}
}
return null;

@ -9,11 +9,10 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import org.session.libsession.messaging.utilities.SessionId;
import org.session.libsignal.utilities.SessionId;
import org.thoughtcrime.securesms.components.ProfilePictureView;
import org.thoughtcrime.securesms.components.emoji.EmojiImageView;
import org.thoughtcrime.securesms.database.model.MessageId;
import org.thoughtcrime.securesms.mms.GlideApp;
import java.util.Collections;
import java.util.List;

@ -5,11 +5,11 @@ import network.loki.messenger.libsession_util.util.UserPic
import org.session.libsession.messaging.contacts.Contact
import org.session.libsession.messaging.jobs.JobQueue
import org.session.libsession.messaging.jobs.RetrieveProfileAvatarJob
import org.session.libsession.messaging.utilities.SessionId
import org.session.libsession.utilities.SSKEnvironment
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.utilities.IdPrefix
import org.session.libsignal.utilities.SessionId
import org.thoughtcrime.securesms.dependencies.ConfigFactory
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities

@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.util
import android.content.Context
import android.provider.Telephony.Sms.Conversations
import network.loki.messenger.libsession_util.ConfigBase
import network.loki.messenger.libsession_util.Contacts
import network.loki.messenger.libsession_util.ConversationVolatileConfig
@ -26,6 +27,7 @@ import org.session.libsession.utilities.WindowDebouncer
import org.session.libsignal.crypto.ecc.DjbECPublicKey
import org.session.libsignal.utilities.Hex
import org.session.libsignal.utilities.IdPrefix
import org.session.libsignal.utilities.SessionId
import org.session.libsignal.utilities.toHexString
import org.thoughtcrime.securesms.database.GroupDatabase
import org.thoughtcrime.securesms.database.ThreadDatabase
@ -199,6 +201,11 @@ object ConfigurationMessageUtilities {
convoConfig.getOrConstructCommunity(base, room, pubKey)
}
recipient.isClosedGroupRecipient -> {
// It's probably safe to assume there will never be a case where new closed groups will ever be there before a dump is created...
// but just in case...
convoConfig.getOrConstructClosedGroup(recipient.address.serialize())
}
recipient.isLegacyClosedGroupRecipient -> {
val groupPublicKey = GroupUtil.doubleDecodeGroupId(recipient.address.serialize())
convoConfig.getOrConstructLegacyGroup(groupPublicKey)
}
@ -241,7 +248,7 @@ object ConfigurationMessageUtilities {
}
val allLgc = storage.getAllGroups(includeInactive = false).filter {
it.isClosedGroup && it.isActive && it.members.size > 1
it.isLegacyClosedGroup && it.isActive && it.members.size > 1
}.mapNotNull { group ->
val groupAddress = Address.fromSerialized(group.encodedId)
val groupPublicKey = GroupUtil.doubleDecodeGroupID(groupAddress.serialize()).toHexString()
@ -252,7 +259,7 @@ object ConfigurationMessageUtilities {
val admins = group.admins.map { it.serialize() to true }.toMap()
val members = group.members.filterNot { it.serialize() !in admins.keys }.map { it.serialize() to false }.toMap()
GroupInfo.LegacyGroupInfo(
sessionId = groupPublicKey,
sessionId = SessionId.from(groupPublicKey),
name = group.title,
members = admins + members,
priority = if (isPinned) ConfigBase.PRIORITY_PINNED else ConfigBase.PRIORITY_VISIBLE,

@ -13,6 +13,8 @@ fun ConversationVolatileConfig.getConversationUnread(thread: ThreadRecord): Bool
&& recipient.address.serialize().startsWith(IdPrefix.STANDARD.value)) {
return getOneToOne(recipient.address.serialize())?.unread == true
} else if (recipient.isClosedGroupRecipient) {
return getClosedGroup(recipient.address.serialize())?.unread == true
} else if (recipient.isLegacyClosedGroupRecipient) {
return getLegacyClosedGroup(GroupUtil.doubleDecodeGroupId(recipient.address.toGroupString()))?.unread == true
} else if (recipient.isOpenGroupRecipient) {
val openGroup = MessagingModuleConfiguration.shared.storage.getOpenGroup(thread.threadId) ?: return false

@ -601,7 +601,7 @@ class InstrumentedTests {
assertEquals("New Group", infoConf.getName())
infoConf.setCreated(System.currentTimeMillis())
assertThat(infoConf.getCreated(), notNullValue())
val memberConf = GroupMemberConfig.newInstance(groupPublic, groupSecret)
val memberConf = GroupMembersConfig.newInstance(groupPublic, groupSecret)
memberConf.set(
GroupMember(
sessionId = "05"+Hex.toStringCondensed(userCurve),
@ -637,7 +637,7 @@ class InstrumentedTests {
assertEquals("New Group", infoConf.getName())
infoConf.setCreated(System.currentTimeMillis())
assertThat(infoConf.getCreated(), notNullValue())
val memberConf = GroupMemberConfig.newInstance(groupPublic, groupSecret)
val memberConf = GroupMembersConfig.newInstance(groupPublic, groupSecret)
memberConf.set(
GroupMember(
sessionId = "05"+Hex.toStringCondensed(userCurve),

@ -177,3 +177,11 @@ Java_network_loki_messenger_libsession_1util_GroupInfoConfig_storageNamespace(JN
auto group_info = ptrToInfo(env, thiz);
return static_cast<jlong>(group_info->storage_namespace());
}
extern "C"
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_GroupInfoConfig_id(JNIEnv *env, jobject thiz) {
std::lock_guard guard{util::util_mutex_};
auto group_info = ptrToInfo(env, thiz);
return util::serialize_session_id(env, group_info->id);
}

@ -65,6 +65,7 @@ Java_network_loki_messenger_libsession_1util_GroupKeysConfig_groupKeys(JNIEnv *e
extern "C"
JNIEXPORT void JNICALL
Java_network_loki_messenger_libsession_1util_GroupKeysConfig_loadKey(JNIEnv *env, jobject thiz,
jstring hash,
jbyteArray data,
jbyteArray msg_id,
jlong timestamp_ms,
@ -72,11 +73,13 @@ Java_network_loki_messenger_libsession_1util_GroupKeysConfig_loadKey(JNIEnv *env
jobject members_jobject) {
std::lock_guard lock{util::util_mutex_};
auto keys = ptrToKeys(env, thiz);
auto hash_bytes = env->GetStringUTFChars(hash, nullptr);
auto data_bytes = util::ustring_from_bytes(env, data);
auto msg_bytes = util::ustring_from_bytes(env, msg_id);
auto info = ptrToInfo(env, info_jobject);
auto members = ptrToMembers(env, members_jobject);
keys->load_key_message(data_bytes, timestamp_ms, *info, *members);
keys->load_key_message(hash_bytes, data_bytes, timestamp_ms, *info, *members);
env->ReleaseStringUTFChars(hash, hash_bytes);
}
extern "C"

@ -2,7 +2,7 @@
extern "C"
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_GroupMemberConfig_00024Companion_newInstance(
Java_network_loki_messenger_libsession_1util_GroupMembersConfig_00024Companion_newInstance(
JNIEnv *env, jobject thiz, jbyteArray pub_key, jbyteArray secret_key,
jbyteArray initial_dump) {
std::lock_guard lock{util::util_mutex_};
@ -20,7 +20,7 @@ Java_network_loki_messenger_libsession_1util_GroupMemberConfig_00024Companion_ne
auto* group_members = new session::config::groups::Members(pub_key_bytes, secret_key_optional, initial_dump_optional);
jclass groupMemberClass = env->FindClass("network/loki/messenger/libsession_util/GroupMemberConfig");
jclass groupMemberClass = env->FindClass("network/loki/messenger/libsession_util/GroupMembersConfig");
jmethodID constructor = env->GetMethodID(groupMemberClass, "<init>", "(J)V");
jobject newConfig = env->NewObject(groupMemberClass, constructor, reinterpret_cast<jlong>(group_members));
@ -29,7 +29,7 @@ Java_network_loki_messenger_libsession_1util_GroupMemberConfig_00024Companion_ne
extern "C"
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_GroupMemberConfig_all(JNIEnv *env, jobject thiz) {
Java_network_loki_messenger_libsession_1util_GroupMembersConfig_all(JNIEnv *env, jobject thiz) {
std::lock_guard lock{util::util_mutex_};
auto config = ptrToMembers(env, thiz);
jclass stack = env->FindClass("java/util/Stack");
@ -45,8 +45,8 @@ Java_network_loki_messenger_libsession_1util_GroupMemberConfig_all(JNIEnv *env,
extern "C"
JNIEXPORT jboolean JNICALL
Java_network_loki_messenger_libsession_1util_GroupMemberConfig_erase(JNIEnv *env, jobject thiz,
jobject group_member) {
Java_network_loki_messenger_libsession_1util_GroupMembersConfig_erase(JNIEnv *env, jobject thiz,
jobject group_member) {
auto config = ptrToMembers(env, thiz);
auto member = util::deserialize_group_member(env, group_member);
return config->erase(member.session_id);
@ -54,8 +54,8 @@ Java_network_loki_messenger_libsession_1util_GroupMemberConfig_erase(JNIEnv *env
extern "C"
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_GroupMemberConfig_get(JNIEnv *env, jobject thiz,
jstring pub_key_hex) {
Java_network_loki_messenger_libsession_1util_GroupMembersConfig_get(JNIEnv *env, jobject thiz,
jstring pub_key_hex) {
std::lock_guard lock{util::util_mutex_};
auto config = ptrToMembers(env, thiz);
auto pub_key_bytes = env->GetStringUTFChars(pub_key_hex, nullptr);
@ -70,9 +70,9 @@ Java_network_loki_messenger_libsession_1util_GroupMemberConfig_get(JNIEnv *env,
extern "C"
JNIEXPORT jobject JNICALL
Java_network_loki_messenger_libsession_1util_GroupMemberConfig_getOrConstruct(JNIEnv *env,
jobject thiz,
jstring pub_key_hex) {
Java_network_loki_messenger_libsession_1util_GroupMembersConfig_getOrConstruct(JNIEnv *env,
jobject thiz,
jstring pub_key_hex) {
std::lock_guard lock{util::util_mutex_};
auto config = ptrToMembers(env, thiz);
auto pub_key_bytes = env->GetStringUTFChars(pub_key_hex, nullptr);
@ -84,8 +84,8 @@ Java_network_loki_messenger_libsession_1util_GroupMemberConfig_getOrConstruct(JN
extern "C"
JNIEXPORT void JNICALL
Java_network_loki_messenger_libsession_1util_GroupMemberConfig_set(JNIEnv *env, jobject thiz,
jobject group_member) {
Java_network_loki_messenger_libsession_1util_GroupMembersConfig_set(JNIEnv *env, jobject thiz,
jobject group_member) {
std::lock_guard lock{util::util_mutex_};
auto config = ptrToMembers(env, thiz);
auto deserialized = util::deserialize_group_member(env, group_member);

@ -10,6 +10,8 @@ import network.loki.messenger.libsession_util.util.UserPic
import org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Kind
import org.session.libsignal.utilities.IdPrefix
import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.SessionId
import java.io.Closeable
import java.util.Stack
@ -27,7 +29,7 @@ sealed class ConfigBase(protected val /* yucky */ pointer: Long) {
is UserGroupsConfig -> Kind.GROUPS
is GroupInfoConfig -> Kind.CLOSED_GROUP_INFO
is GroupKeysConfig -> Kind.ENCRYPTION_KEYS
is GroupMemberConfig -> Kind.CLOSED_GROUP_MEMBERS
is GroupMembersConfig -> Kind.CLOSED_GROUP_MEMBERS
}
// TODO: time in future to activate (hardcoded to 1st jan 2024 for testing, change before release)
@ -217,7 +219,7 @@ class UserGroupsConfig(pointer: Long): ConfigBase(pointer) {
external fun createGroup(): GroupInfo.ClosedGroupInfo
}
class GroupInfoConfig(pointer: Long): ConfigBase(pointer) {
class GroupInfoConfig(pointer: Long): ConfigBase(pointer), Closeable {
companion object {
init {
System.loadLibrary("session_util")
@ -229,6 +231,7 @@ class GroupInfoConfig(pointer: Long): ConfigBase(pointer) {
initialDump: ByteArray = byteArrayOf()
): GroupInfoConfig
}
external fun id(): SessionId
external fun destroyGroup()
external fun getCreated(): Long?
external fun getDeleteAttachmentsBefore(): Long?
@ -244,9 +247,12 @@ class GroupInfoConfig(pointer: Long): ConfigBase(pointer) {
external fun setName(newName: String)
external fun setProfilePic(newProfilePic: UserPic)
external fun storageNamespace(): Long
override fun close() {
free()
}
}
class GroupMemberConfig(pointer: Long): ConfigBase(pointer) {
class GroupMembersConfig(pointer: Long): ConfigBase(pointer), Closeable {
companion object {
init {
System.loadLibrary("session_util")
@ -255,16 +261,19 @@ class GroupMemberConfig(pointer: Long): ConfigBase(pointer) {
pubKey: ByteArray,
secretKey: ByteArray = byteArrayOf(),
initialDump: ByteArray = byteArrayOf()
): GroupMemberConfig
): GroupMembersConfig
}
external fun all(): Stack<GroupMember>
external fun erase(groupMember: GroupMember): Boolean
external fun get(pubKeyHex: String): GroupMember?
external fun getOrConstruct(pubKeyHex: String): GroupMember
external fun set(groupMember: GroupMember)
override fun close() {
free()
}
}
class GroupKeysConfig(pointer: Long): ConfigBase(pointer) {
class GroupKeysConfig(pointer: Long): ConfigBase(pointer), Closeable {
companion object {
init {
System.loadLibrary("session_util")
@ -275,17 +284,21 @@ class GroupKeysConfig(pointer: Long): ConfigBase(pointer) {
groupSecretKey: ByteArray = byteArrayOf(),
initialDump: ByteArray = byteArrayOf(),
info: GroupInfoConfig,
members: GroupMemberConfig
members: GroupMembersConfig
): GroupKeysConfig
}
external fun groupKeys(): Stack<ByteArray>
external fun loadKey(data: ByteArray,
external fun loadKey(hash: String,
data: ByteArray,
msgId: ByteArray,
timestampMs: Long,
info: GroupInfoConfig,
members: GroupMemberConfig)
members: GroupMembersConfig)
external fun needsRekey(): Boolean
external fun pendingKey(): ByteArray?
external fun pendingPush(): ByteArray?
external fun rekey(info: GroupInfoConfig, members: GroupMemberConfig): ByteArray
external fun rekey(info: GroupInfoConfig, members: GroupMembersConfig): ByteArray
override fun close() {
free()
}
}

@ -154,7 +154,7 @@ interface StorageProtocol {
fun setExpirationTimer(address: String, duration: Int)
// Closed Groups
fun getMembers(groupPublicKey: String): List<GroupMember>
fun getMembers(groupPublicKey: String): List<network.loki.messenger.libsession_util.util.GroupMember>
// Groups
fun getAllGroups(includeInactive: Boolean): List<GroupRecord>

@ -23,6 +23,8 @@ class Address private constructor(address: String) : Parcelable, Comparable<Addr
get() = GroupUtil.isEncodedGroup(address) || address.startsWith(IdPrefix.GROUP.value)
val isLegacyClosedGroup: Boolean
get() = GroupUtil.isLegacyClosedGroup(address)
val isClosedGroup: Boolean
get() = address.startsWith(IdPrefix.GROUP.value)
val isOpenGroup: Boolean
get() = GroupUtil.isOpenGroup(address)
val isOpenGroupInbox: Boolean

@ -3,14 +3,23 @@ package org.session.libsession.utilities
import network.loki.messenger.libsession_util.ConfigBase
import network.loki.messenger.libsession_util.Contacts
import network.loki.messenger.libsession_util.ConversationVolatileConfig
import network.loki.messenger.libsession_util.GroupInfoConfig
import network.loki.messenger.libsession_util.GroupKeysConfig
import network.loki.messenger.libsession_util.GroupMembersConfig
import network.loki.messenger.libsession_util.UserGroupsConfig
import network.loki.messenger.libsession_util.UserProfile
import org.session.libsignal.utilities.SessionId
interface ConfigFactoryProtocol {
val user: UserProfile?
val contacts: Contacts?
val convoVolatile: ConversationVolatileConfig?
val userGroups: UserGroupsConfig?
fun groupInfoConfig(groupSessionId: SessionId): GroupInfoConfig?
fun groupKeysConfig(groupSessionId: SessionId): GroupKeysConfig?
fun groupMemberConfig(groupSessionId: SessionId): GroupMembersConfig?
fun getUserConfigs(): List<ConfigBase>
fun persist(forConfigObject: ConfigBase, timestamp: Long)

@ -476,6 +476,10 @@ public class Recipient implements RecipientModifiedListener {
return address.isLegacyClosedGroup();
}
public boolean isClosedGroupRecipient() {
return address.isClosedGroup();
}
@Deprecated
public boolean isPushGroupRecipient() {

Loading…
Cancel
Save