package network.loki.messenger.libsession_util import network.loki.messenger.libsession_util.util.BaseCommunityInfo import network.loki.messenger.libsession_util.util.ConfigPush import network.loki.messenger.libsession_util.util.Contact import network.loki.messenger.libsession_util.util.Conversation import network.loki.messenger.libsession_util.util.GroupInfo import network.loki.messenger.libsession_util.util.GroupMember 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.Namespace import org.session.libsignal.utilities.SessionId import java.io.Closeable import java.util.Stack sealed class Config(protected val pointer: Long): Closeable { abstract fun namespace(): Int external fun free() override fun close() { free() } } sealed class ConfigBase(pointer: Long): Config(pointer) { companion object { init { System.loadLibrary("session_util") } external fun kindFor(configNamespace: Int): Class fun ConfigBase.protoKindFor(): Kind = when (this) { is UserProfile -> Kind.USER_PROFILE is Contacts -> Kind.CONTACTS is ConversationVolatileConfig -> Kind.CONVO_INFO_VOLATILE is UserGroupsConfig -> Kind.GROUPS is GroupInfoConfig -> Kind.CLOSED_GROUP_INFO is GroupMembersConfig -> Kind.CLOSED_GROUP_MEMBERS } private const val ACTIVATE_TIME = 1690761600000 fun isNewConfigEnabled(forced: Boolean, currentTime: Long) = forced || currentTime >= ACTIVATE_TIME const val PRIORITY_HIDDEN = -1L const val PRIORITY_VISIBLE = 0L const val PRIORITY_PINNED = 1L } external fun dirty(): Boolean external fun needsPush(): Boolean external fun needsDump(): Boolean external fun push(): ConfigPush external fun dump(): ByteArray external fun encryptionDomain(): String external fun confirmPushed(seqNo: Long, newHash: String) external fun merge(toMerge: Array>): Stack external fun currentHashes(): List // Singular merge external fun merge(toMerge: Pair): Stack } class Contacts(pointer: Long) : ConfigBase(pointer) { companion object { init { System.loadLibrary("session_util") } external fun newInstance(ed25519SecretKey: ByteArray): Contacts external fun newInstance(ed25519SecretKey: ByteArray, initialDump: ByteArray): Contacts } override fun namespace() = Namespace.CONTACTS() external fun get(sessionId: String): Contact? external fun getOrConstruct(sessionId: String): Contact external fun all(): List external fun set(contact: Contact) external fun erase(sessionId: String): Boolean /** * Similar to [updateIfExists], but will create the underlying contact if it doesn't exist before passing to [updateFunction] */ fun upsertContact(sessionId: String, updateFunction: Contact.()->Unit = {}) { if (sessionId.startsWith(IdPrefix.BLINDED.value)) { Log.w("Loki", "Trying to create a contact with a blinded ID prefix") return } else if (sessionId.startsWith(IdPrefix.UN_BLINDED.value)) { Log.w("Loki", "Trying to create a contact with an un-blinded ID prefix") return } else if (sessionId.startsWith(IdPrefix.BLINDEDV2.value)) { Log.w("Loki", "Trying to create a contact with a blindedv2 ID prefix") return } val contact = getOrConstruct(sessionId) updateFunction(contact) set(contact) } /** * Updates the contact by sessionId with a given [updateFunction], and applies to the underlying config. * the [updateFunction] doesn't run if there is no contact */ fun updateIfExists(sessionId: String, updateFunction: Contact.()->Unit) { if (sessionId.startsWith(IdPrefix.BLINDED.value)) { Log.w("Loki", "Trying to create a contact with a blinded ID prefix") return } else if (sessionId.startsWith(IdPrefix.UN_BLINDED.value)) { Log.w("Loki", "Trying to create a contact with an un-blinded ID prefix") return } else if (sessionId.startsWith(IdPrefix.BLINDEDV2.value)) { Log.w("Loki", "Trying to create a contact with a blindedv2 ID prefix") return } val contact = get(sessionId) ?: return updateFunction(contact) set(contact) } } class UserProfile(pointer: Long) : ConfigBase(pointer) { companion object { init { System.loadLibrary("session_util") } external fun newInstance(ed25519SecretKey: ByteArray): UserProfile external fun newInstance(ed25519SecretKey: ByteArray, initialDump: ByteArray): UserProfile } override fun namespace() = Namespace.USER_PROFILE() external fun setName(newName: String) external fun getName(): String? external fun getPic(): UserPic external fun setPic(userPic: UserPic) external fun setNtsPriority(priority: Long) external fun getNtsPriority(): Long external fun getCommunityMessageRequests(): Boolean external fun setCommunityMessageRequests(blocks: Boolean) external fun isBlockCommunityMessageRequestsSet(): Boolean } class ConversationVolatileConfig(pointer: Long): ConfigBase(pointer) { companion object { init { System.loadLibrary("session_util") } external fun newInstance(ed25519SecretKey: ByteArray): ConversationVolatileConfig external fun newInstance(ed25519SecretKey: ByteArray, initialDump: ByteArray): ConversationVolatileConfig } override fun namespace() = Namespace.CONVO_INFO_VOLATILE() external fun getOneToOne(pubKeyHex: String): Conversation.OneToOne? external fun getOrConstructOneToOne(pubKeyHex: String): Conversation.OneToOne external fun eraseOneToOne(pubKeyHex: String): Boolean external fun getCommunity(baseUrl: String, room: String): Conversation.Community? external fun getOrConstructCommunity(baseUrl: String, room: String, pubKeyHex: String): Conversation.Community external fun getOrConstructCommunity(baseUrl: String, room: String, pubKey: ByteArray): Conversation.Community external fun eraseCommunity(community: Conversation.Community): Boolean external fun eraseCommunity(baseUrl: String, room: String): Boolean external fun getLegacyClosedGroup(groupId: String): Conversation.LegacyGroup? external fun getOrConstructLegacyGroup(groupId: String): Conversation.LegacyGroup external fun eraseLegacyClosedGroup(groupId: String): Boolean external fun getClosedGroup(sessionId: String): Conversation.ClosedGroup? external fun getOrConstructClosedGroup(sessionId: String): Conversation.ClosedGroup external fun eraseClosedGroup(sessionId: String): Boolean external fun erase(conversation: Conversation): Boolean external fun set(toStore: Conversation) /** * Erase all conversations that do not satisfy the `predicate`, similar to [MutableList.removeAll] */ external fun eraseAll(predicate: (Conversation) -> Boolean): Int external fun sizeOneToOnes(): Int external fun sizeCommunities(): Int external fun sizeLegacyClosedGroups(): Int external fun size(): Int external fun empty(): Boolean external fun allOneToOnes(): List external fun allCommunities(): List external fun allLegacyClosedGroups(): List external fun all(): List } class UserGroupsConfig(pointer: Long): ConfigBase(pointer) { companion object { init { System.loadLibrary("session_util") } external fun newInstance(ed25519SecretKey: ByteArray): UserGroupsConfig external fun newInstance(ed25519SecretKey: ByteArray, initialDump: ByteArray): UserGroupsConfig } override fun namespace() = Namespace.GROUPS() external fun getCommunityInfo(baseUrl: String, room: String): GroupInfo.CommunityGroupInfo? external fun getLegacyGroupInfo(sessionId: String): GroupInfo.LegacyGroupInfo? external fun getClosedGroup(sessionId: String): GroupInfo.ClosedGroupInfo? external fun getOrConstructCommunityInfo(baseUrl: String, room: String, pubKeyHex: String): GroupInfo.CommunityGroupInfo external fun getOrConstructLegacyGroupInfo(sessionId: String): GroupInfo.LegacyGroupInfo external fun getOrConstructClosedGroup(sessionId: String): GroupInfo.ClosedGroupInfo external fun set(groupInfo: GroupInfo) external fun erase(groupInfo: GroupInfo) external fun eraseCommunity(baseCommunityInfo: BaseCommunityInfo): Boolean external fun eraseCommunity(server: String, room: String): Boolean external fun eraseLegacyGroup(sessionId: String): Boolean external fun eraseClosedGroup(sessionId: String): Boolean external fun sizeCommunityInfo(): Long external fun sizeLegacyGroupInfo(): Long external fun sizeClosedGroup(): Long external fun size(): Long external fun all(): List external fun allCommunityInfo(): List external fun allLegacyGroupInfo(): List external fun allClosedGroupInfo(): List external fun createGroup(): GroupInfo.ClosedGroupInfo } class GroupInfoConfig(pointer: Long): ConfigBase(pointer), Closeable { companion object { init { System.loadLibrary("session_util") } external fun newInstance( pubKey: ByteArray, secretKey: ByteArray = byteArrayOf(), initialDump: ByteArray = byteArrayOf() ): GroupInfoConfig } override fun namespace() = Namespace.CLOSED_GROUP_INFO() external fun id(): SessionId external fun destroyGroup() external fun getCreated(): Long? external fun getDeleteAttachmentsBefore(): Long? external fun getDeleteBefore(): Long? external fun getExpiryTimer(): Long? // TODO: maybe refactor this to new type when disappearing messages merged external fun getName(): String external fun getProfilePic(): UserPic external fun isDestroyed(): Boolean external fun setCreated(createdAt: Long) external fun setDeleteAttachmentsBefore(deleteBefore: Long) external fun setDeleteBefore(deleteBefore: Long) external fun setExpiryTimer(expireSeconds: Long) external fun setName(newName: String) external fun getDescription(): String external fun setDescription(newDescription: String) external fun setProfilePic(newProfilePic: UserPic) external fun storageNamespace(): Long override fun close() { free() } } class GroupMembersConfig(pointer: Long): ConfigBase(pointer), Closeable { companion object { init { System.loadLibrary("session_util") } external fun newInstance( pubKey: ByteArray, secretKey: ByteArray = byteArrayOf(), initialDump: ByteArray = byteArrayOf() ): GroupMembersConfig } override fun namespace() = Namespace.CLOSED_GROUP_MEMBERS() external fun all(): Stack 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() } } sealed class ConfigSig(pointer: Long) : Config(pointer) class GroupKeysConfig(pointer: Long): ConfigSig(pointer) { companion object { init { System.loadLibrary("session_util") } external fun newInstance( userSecretKey: ByteArray, groupPublicKey: ByteArray, groupSecretKey: ByteArray = byteArrayOf(), initialDump: ByteArray = byteArrayOf(), info: GroupInfoConfig, members: GroupMembersConfig ): GroupKeysConfig } override fun namespace() = Namespace.ENCRYPTION_KEYS() external fun groupKeys(): Stack external fun needsDump(): Boolean external fun dump(): ByteArray external fun loadKey(message: ByteArray, hash: String, timestampMs: Long, info: GroupInfoConfig, members: GroupMembersConfig) external fun needsRekey(): Boolean external fun pendingKey(): ByteArray? external fun pendingConfig(): ByteArray? external fun currentHashes(): List external fun rekey(info: GroupInfoConfig, members: GroupMembersConfig): ByteArray override fun close() { free() } external fun encrypt(plaintext: ByteArray): ByteArray external fun decrypt(ciphertext: ByteArray): Pair? external fun keys(): Stack external fun makeSubAccount(sessionId: SessionId, canWrite: Boolean = true, canDelete: Boolean = false): ByteArray external fun subAccountSign(message: ByteArray, signingValue: ByteArray): SwarmAuth data class SwarmAuth( val subAccount: String, val subAccountSig: String, val signature: String ) }