mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-27 12:05:22 +00:00
Clean
This commit is contained in:
parent
69f05dabdf
commit
fdede1c656
@ -5,6 +5,7 @@ import android.text.TextUtils
|
|||||||
import com.google.protobuf.ByteString
|
import com.google.protobuf.ByteString
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
import org.session.libsession.database.MessageDataProvider
|
import org.session.libsession.database.MessageDataProvider
|
||||||
|
import org.session.libsession.messaging.open_groups.OpenGroup
|
||||||
import org.session.libsession.messaging.sending_receiving.attachments.*
|
import org.session.libsession.messaging.sending_receiving.attachments.*
|
||||||
import org.session.libsession.messaging.threads.Address
|
import org.session.libsession.messaging.threads.Address
|
||||||
import org.session.libsession.messaging.utilities.DotNetAPI
|
import org.session.libsession.messaging.utilities.DotNetAPI
|
||||||
@ -26,7 +27,6 @@ import org.thoughtcrime.securesms.util.MediaUtil
|
|||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
|
|
||||||
class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), MessageDataProvider {
|
class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), MessageDataProvider {
|
||||||
|
|
||||||
override fun getAttachmentStream(attachmentId: Long): SessionServiceAttachmentStream? {
|
override fun getAttachmentStream(attachmentId: Long): SessionServiceAttachmentStream? {
|
||||||
@ -104,6 +104,10 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper)
|
|||||||
return smsDatabase.isOutgoingMessage(timestamp) || mmsDatabase.isOutgoingMessage(timestamp)
|
return smsDatabase.isOutgoingMessage(timestamp) || mmsDatabase.isOutgoingMessage(timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getOpenGroup(threadID: Long): OpenGroup? {
|
||||||
|
return null // TODO: Implement
|
||||||
|
}
|
||||||
|
|
||||||
override fun updateAttachmentAfterUploadSucceeded(attachmentId: Long, attachmentStream: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: DotNetAPI.UploadResult) {
|
override fun updateAttachmentAfterUploadSucceeded(attachmentId: Long, attachmentStream: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: DotNetAPI.UploadResult) {
|
||||||
val database = DatabaseFactory.getAttachmentDatabase(context)
|
val database = DatabaseFactory.getAttachmentDatabase(context)
|
||||||
val databaseAttachment = getDatabaseAttachment(attachmentId) ?: return
|
val databaseAttachment = getDatabaseAttachment(attachmentId) ?: return
|
||||||
|
@ -30,10 +30,10 @@ import org.greenrobot.eventbus.EventBus
|
|||||||
import org.greenrobot.eventbus.Subscribe
|
import org.greenrobot.eventbus.Subscribe
|
||||||
import org.greenrobot.eventbus.ThreadMode
|
import org.greenrobot.eventbus.ThreadMode
|
||||||
import org.session.libsession.messaging.jobs.JobQueue
|
import org.session.libsession.messaging.jobs.JobQueue
|
||||||
|
import org.session.libsession.messaging.mentions.MentionsManager
|
||||||
import org.session.libsession.messaging.open_groups.OpenGroupAPI
|
import org.session.libsession.messaging.open_groups.OpenGroupAPI
|
||||||
import org.session.libsession.messaging.sending_receiving.MessageSender
|
import org.session.libsession.messaging.sending_receiving.MessageSender
|
||||||
import org.session.libsession.utilities.*
|
import org.session.libsession.utilities.*
|
||||||
import org.session.libsignal.service.loki.utilities.mentions.MentionsManager
|
|
||||||
import org.session.libsignal.service.loki.utilities.toHexString
|
import org.session.libsignal.service.loki.utilities.toHexString
|
||||||
import org.session.libsignal.utilities.ThreadUtils
|
import org.session.libsignal.utilities.ThreadUtils
|
||||||
import org.thoughtcrime.securesms.ApplicationContext
|
import org.thoughtcrime.securesms.ApplicationContext
|
||||||
@ -139,7 +139,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
|
|||||||
val userDB = DatabaseFactory.getLokiUserDatabase(this)
|
val userDB = DatabaseFactory.getLokiUserDatabase(this)
|
||||||
val userPublicKey = TextSecurePreferences.getLocalNumber(this)
|
val userPublicKey = TextSecurePreferences.getLocalNumber(this)
|
||||||
if (userPublicKey != null) {
|
if (userPublicKey != null) {
|
||||||
MentionsManager.configureIfNeeded(userPublicKey, threadDB, userDB)
|
MentionsManager.configureIfNeeded(userPublicKey, userDB)
|
||||||
application.publicChatManager.startPollersIfNeeded()
|
application.publicChatManager.startPollersIfNeeded()
|
||||||
JobQueue.shared.resumePendingJobs()
|
JobQueue.shared.resumePendingJobs()
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
|
|||||||
import org.thoughtcrime.securesms.loki.utilities.*
|
import org.thoughtcrime.securesms.loki.utilities.*
|
||||||
import org.thoughtcrime.securesms.loki.views.GlowViewUtilities
|
import org.thoughtcrime.securesms.loki.views.GlowViewUtilities
|
||||||
import org.thoughtcrime.securesms.loki.views.PathDotView
|
import org.thoughtcrime.securesms.loki.views.PathDotView
|
||||||
import org.session.libsignal.service.loki.api.Snode
|
import org.session.libsignal.service.loki.Snode
|
||||||
|
|
||||||
class PathActivity : PassphraseRequiredActionBarActivity() {
|
class PathActivity : PassphraseRequiredActionBarActivity() {
|
||||||
private val broadcastReceivers = mutableListOf<BroadcastReceiver>()
|
private val broadcastReceivers = mutableListOf<BroadcastReceiver>()
|
||||||
|
@ -7,7 +7,7 @@ import org.session.libsession.utilities.TextSecurePreferences
|
|||||||
import org.session.libsignal.libsignal.ecc.DjbECPrivateKey
|
import org.session.libsignal.libsignal.ecc.DjbECPrivateKey
|
||||||
import org.session.libsignal.libsignal.ecc.DjbECPublicKey
|
import org.session.libsignal.libsignal.ecc.DjbECPublicKey
|
||||||
import org.session.libsignal.libsignal.ecc.ECKeyPair
|
import org.session.libsignal.libsignal.ecc.ECKeyPair
|
||||||
import org.session.libsignal.service.loki.api.Snode
|
import org.session.libsignal.service.loki.Snode
|
||||||
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
|
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
|
||||||
import org.session.libsignal.service.loki.utilities.PublicKeyValidation
|
import org.session.libsignal.service.loki.utilities.PublicKeyValidation
|
||||||
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
|
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package org.session.libsession.database
|
package org.session.libsession.database
|
||||||
|
|
||||||
|
import org.session.libsession.messaging.open_groups.OpenGroup
|
||||||
import org.session.libsession.messaging.sending_receiving.attachments.*
|
import org.session.libsession.messaging.sending_receiving.attachments.*
|
||||||
import org.session.libsession.messaging.threads.Address
|
import org.session.libsession.messaging.threads.Address
|
||||||
import org.session.libsession.messaging.utilities.DotNetAPI
|
import org.session.libsession.messaging.utilities.DotNetAPI
|
||||||
@ -37,4 +38,5 @@ interface MessageDataProvider {
|
|||||||
fun getAttachmentIDsFor(messageID: Long): List<Long>
|
fun getAttachmentIDsFor(messageID: Long): List<Long>
|
||||||
fun getLinkPreviewAttachmentIDFor(messageID: Long): Long?
|
fun getLinkPreviewAttachmentIDFor(messageID: Long): Long?
|
||||||
|
|
||||||
|
fun getOpenGroup(threadID: Long): OpenGroup?
|
||||||
}
|
}
|
@ -1,19 +1,20 @@
|
|||||||
package org.session.libsignal.service.loki.utilities.mentions
|
package org.session.libsession.messaging.mentions
|
||||||
|
|
||||||
|
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||||
|
import org.session.libsignal.service.loki.utilities.mentions.Mention
|
||||||
|
|
||||||
import org.session.libsignal.service.loki.database.LokiThreadDatabaseProtocol
|
|
||||||
import org.session.libsignal.service.loki.database.LokiUserDatabaseProtocol
|
import org.session.libsignal.service.loki.database.LokiUserDatabaseProtocol
|
||||||
|
|
||||||
class MentionsManager(private val userPublicKey: String, private val threadDatabase: LokiThreadDatabaseProtocol,
|
class MentionsManager(private val userPublicKey: String, private val userDatabase: LokiUserDatabaseProtocol) {
|
||||||
private val userDatabase: LokiUserDatabaseProtocol) {
|
|
||||||
var userPublicKeyCache = mutableMapOf<Long, Set<String>>() // Thread ID to set of user hex encoded public keys
|
var userPublicKeyCache = mutableMapOf<Long, Set<String>>() // Thread ID to set of user hex encoded public keys
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
public lateinit var shared: MentionsManager
|
public lateinit var shared: MentionsManager
|
||||||
|
|
||||||
public fun configureIfNeeded(userPublicKey: String, threadDatabase: LokiThreadDatabaseProtocol, userDatabase: LokiUserDatabaseProtocol) {
|
public fun configureIfNeeded(userPublicKey: String, userDatabase: LokiUserDatabaseProtocol) {
|
||||||
if (::shared.isInitialized) { return; }
|
if (::shared.isInitialized) { return; }
|
||||||
shared = MentionsManager(userPublicKey, threadDatabase, userDatabase)
|
shared = MentionsManager(userPublicKey, userDatabase)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,7 +31,7 @@ class MentionsManager(private val userPublicKey: String, private val threadDatab
|
|||||||
// Prepare
|
// Prepare
|
||||||
val cache = userPublicKeyCache[threadID] ?: return listOf()
|
val cache = userPublicKeyCache[threadID] ?: return listOf()
|
||||||
// Gather candidates
|
// Gather candidates
|
||||||
val publicChat = threadDatabase.getPublicChat(threadID)
|
val publicChat = MessagingModuleConfiguration.shared.messageDataProvider.getOpenGroup(threadID)
|
||||||
var candidates: List<Mention> = cache.mapNotNull { publicKey ->
|
var candidates: List<Mention> = cache.mapNotNull { publicKey ->
|
||||||
val displayName: String?
|
val displayName: String?
|
||||||
if (publicChat != null) {
|
if (publicChat != null) {
|
@ -1,6 +1,5 @@
|
|||||||
package org.session.libsession.messaging.open_groups
|
package org.session.libsession.messaging.open_groups
|
||||||
|
|
||||||
import org.session.libsignal.service.loki.api.opengroups.PublicChat
|
|
||||||
import org.session.libsignal.utilities.JsonUtil
|
import org.session.libsignal.utilities.JsonUtil
|
||||||
|
|
||||||
data class OpenGroup(
|
data class OpenGroup(
|
||||||
@ -14,9 +13,6 @@ data class OpenGroup(
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
@JvmStatic fun from(publicChat: PublicChat): OpenGroup =
|
|
||||||
OpenGroup(publicChat.channel, publicChat.server, publicChat.displayName, publicChat.isDeletable)
|
|
||||||
|
|
||||||
@JvmStatic fun getId(channel: Long, server: String): String {
|
@JvmStatic fun getId(channel: Long, server: String): String {
|
||||||
return "$server.$channel"
|
return "$server.$channel"
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ import org.session.libsession.messaging.jobs.MessageReceiveJob
|
|||||||
import org.session.libsession.messaging.utilities.MessageWrapper
|
import org.session.libsession.messaging.utilities.MessageWrapper
|
||||||
import org.session.libsession.snode.SnodeAPI
|
import org.session.libsession.snode.SnodeAPI
|
||||||
import org.session.libsession.snode.SnodeModule
|
import org.session.libsession.snode.SnodeModule
|
||||||
import org.session.libsignal.service.loki.api.Snode
|
import org.session.libsignal.service.loki.Snode
|
||||||
import org.session.libsignal.utilities.Base64
|
import org.session.libsignal.utilities.Base64
|
||||||
import org.session.libsignal.utilities.logging.Log
|
import org.session.libsignal.utilities.logging.Log
|
||||||
import java.security.SecureRandom
|
import java.security.SecureRandom
|
||||||
|
@ -11,7 +11,7 @@ import org.session.libsession.utilities.AESGCM
|
|||||||
import org.session.libsignal.utilities.logging.Log
|
import org.session.libsignal.utilities.logging.Log
|
||||||
import org.session.libsignal.utilities.Base64
|
import org.session.libsignal.utilities.Base64
|
||||||
import org.session.libsignal.utilities.*
|
import org.session.libsignal.utilities.*
|
||||||
import org.session.libsignal.service.loki.api.Snode
|
import org.session.libsignal.service.loki.Snode
|
||||||
import org.session.libsignal.service.loki.api.utilities.*
|
import org.session.libsignal.service.loki.api.utilities.*
|
||||||
import org.session.libsession.utilities.AESGCM.EncryptionResult
|
import org.session.libsession.utilities.AESGCM.EncryptionResult
|
||||||
import org.session.libsignal.utilities.ThreadUtils
|
import org.session.libsignal.utilities.ThreadUtils
|
||||||
@ -79,7 +79,7 @@ object OnionRequestAPI {
|
|||||||
)
|
)
|
||||||
|
|
||||||
internal sealed class Destination {
|
internal sealed class Destination {
|
||||||
class Snode(val snode: org.session.libsignal.service.loki.api.Snode) : Destination()
|
class Snode(val snode: org.session.libsignal.service.loki.Snode) : Destination()
|
||||||
class Server(val host: String, val target: String, val x25519PublicKey: String) : Destination()
|
class Server(val host: String, val target: String, val x25519PublicKey: String) : Destination()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import nl.komponents.kovenant.*
|
|||||||
import nl.komponents.kovenant.functional.bind
|
import nl.komponents.kovenant.functional.bind
|
||||||
import nl.komponents.kovenant.functional.map
|
import nl.komponents.kovenant.functional.map
|
||||||
import org.session.libsession.snode.utilities.getRandomElement
|
import org.session.libsession.snode.utilities.getRandomElement
|
||||||
import org.session.libsignal.service.loki.api.Snode
|
import org.session.libsignal.service.loki.Snode
|
||||||
import org.session.libsignal.service.loki.api.utilities.HTTP
|
import org.session.libsignal.service.loki.api.utilities.HTTP
|
||||||
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
|
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
|
||||||
import org.session.libsignal.service.loki.utilities.Broadcaster
|
import org.session.libsignal.service.loki.utilities.Broadcaster
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package org.session.libsession.snode
|
package org.session.libsession.snode
|
||||||
|
|
||||||
|
import org.session.libsignal.service.loki.Snode
|
||||||
|
|
||||||
interface SnodeStorageProtocol {
|
interface SnodeStorageProtocol {
|
||||||
|
|
||||||
fun getSnodePool(): Set<Snode>
|
fun getSnodePool(): Set<Snode>
|
||||||
|
@ -10,7 +10,6 @@ import org.session.libsignal.libsignal.util.guava.Optional;
|
|||||||
import org.session.libsignal.service.api.messages.shared.SharedContact;
|
import org.session.libsignal.service.api.messages.shared.SharedContact;
|
||||||
import org.session.libsignal.service.api.push.SignalServiceAddress;
|
import org.session.libsignal.service.api.push.SignalServiceAddress;
|
||||||
import org.session.libsignal.service.internal.push.SignalServiceProtos.DataMessage.ClosedGroupControlMessage;
|
import org.session.libsignal.service.internal.push.SignalServiceProtos.DataMessage.ClosedGroupControlMessage;
|
||||||
import org.session.libsignal.service.loki.utilities.TTLUtilities;
|
|
||||||
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -247,7 +246,7 @@ public class SignalServiceDataMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int getTTL() {
|
public int getTTL() {
|
||||||
return TTLUtilities.getTTL(TTLUtilities.MessageType.Regular);
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
package org.session.libsignal.service.api.messages;
|
package org.session.libsignal.service.api.messages;
|
||||||
|
|
||||||
|
|
||||||
import org.session.libsignal.service.loki.utilities.TTLUtilities;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class SignalServiceReceiptMessage {
|
public class SignalServiceReceiptMessage {
|
||||||
@ -41,5 +39,5 @@ public class SignalServiceReceiptMessage {
|
|||||||
return type == Type.READ;
|
return type == Type.READ;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getTTL() { return TTLUtilities.getTTL(TTLUtilities.MessageType.Receipt); }
|
public int getTTL() { return 0; }
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
package org.session.libsignal.service.api.messages;
|
package org.session.libsignal.service.api.messages;
|
||||||
|
|
||||||
import org.session.libsignal.service.loki.utilities.TTLUtilities;
|
|
||||||
|
|
||||||
public class SignalServiceTypingMessage {
|
public class SignalServiceTypingMessage {
|
||||||
|
|
||||||
public enum Action {
|
public enum Action {
|
||||||
@ -32,5 +30,5 @@ public class SignalServiceTypingMessage {
|
|||||||
return action == Action.STOPPED;
|
return action == Action.STOPPED;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getTTL() { return TTLUtilities.getTTL(TTLUtilities.MessageType.TypingIndicator); }
|
public int getTTL() { return 0; }
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package org.session.libsession.snode
|
package org.session.libsignal.service.loki
|
||||||
|
|
||||||
class Snode(val address: String, val port: Int, val publicKeySet: KeySet?) {
|
class Snode(val address: String, val port: Int, val publicKeySet: KeySet?) {
|
||||||
val ip: String get() = address.removePrefix("https://")
|
val ip: String get() = address.removePrefix("https://")
|
||||||
|
|
||||||
internal enum class Method(val rawValue: String) {
|
public enum class Method(val rawValue: String) {
|
||||||
GetSwarm("get_snodes_for_pubkey"),
|
GetSwarm("get_snodes_for_pubkey"),
|
||||||
GetMessages("retrieve"),
|
GetMessages("retrieve"),
|
||||||
SendMessage("store")
|
SendMessage("store")
|
@ -1,84 +0,0 @@
|
|||||||
package org.session.libsignal.service.loki.api
|
|
||||||
|
|
||||||
import com.google.protobuf.ByteString
|
|
||||||
import org.session.libsignal.utilities.logging.Log
|
|
||||||
import org.session.libsignal.service.internal.push.SignalServiceProtos.Envelope
|
|
||||||
import org.session.libsignal.utilities.Base64
|
|
||||||
import org.session.libsignal.service.internal.websocket.WebSocketProtos.WebSocketMessage
|
|
||||||
import org.session.libsignal.service.internal.websocket.WebSocketProtos.WebSocketRequestMessage
|
|
||||||
import java.security.SecureRandom
|
|
||||||
|
|
||||||
object MessageWrapper {
|
|
||||||
|
|
||||||
// region Types
|
|
||||||
sealed class Error(val description: String) : Exception() {
|
|
||||||
object FailedToWrapData : Error("Failed to wrap data.")
|
|
||||||
object FailedToWrapMessageInEnvelope : Error("Failed to wrap message in envelope.")
|
|
||||||
object FailedToWrapEnvelopeInWebSocketMessage : Error("Failed to wrap envelope in web socket message.")
|
|
||||||
object FailedToUnwrapData : Error("Failed to unwrap data.")
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Wrapping
|
|
||||||
/**
|
|
||||||
* Wraps `message` in a `SignalServiceProtos.Envelope` and then a `WebSocketProtos.WebSocketMessage` to match the desktop application.
|
|
||||||
*/
|
|
||||||
fun wrap(message: SignalMessageInfo): ByteArray {
|
|
||||||
try {
|
|
||||||
val envelope = createEnvelope(message)
|
|
||||||
val webSocketMessage = createWebSocketMessage(envelope)
|
|
||||||
return webSocketMessage.toByteArray()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
throw if (e is Error) { e } else { Error.FailedToWrapData }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createEnvelope(message: SignalMessageInfo): Envelope {
|
|
||||||
try {
|
|
||||||
val builder = Envelope.newBuilder()
|
|
||||||
builder.type = message.type
|
|
||||||
builder.timestamp = message.timestamp
|
|
||||||
builder.source = message.senderPublicKey
|
|
||||||
builder.sourceDevice = message.senderDeviceID
|
|
||||||
builder.content = ByteString.copyFrom(Base64.decode(message.content))
|
|
||||||
return builder.build()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.d("Loki", "Failed to wrap message in envelope: ${e.message}.")
|
|
||||||
throw Error.FailedToWrapMessageInEnvelope
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createWebSocketMessage(envelope: Envelope): WebSocketMessage {
|
|
||||||
try {
|
|
||||||
val requestBuilder = WebSocketRequestMessage.newBuilder()
|
|
||||||
requestBuilder.verb = "PUT"
|
|
||||||
requestBuilder.path = "/api/v1/message"
|
|
||||||
requestBuilder.id = SecureRandom.getInstance("SHA1PRNG").nextLong()
|
|
||||||
requestBuilder.body = envelope.toByteString()
|
|
||||||
val messageBuilder = WebSocketMessage.newBuilder()
|
|
||||||
messageBuilder.request = requestBuilder.build()
|
|
||||||
messageBuilder.type = WebSocketMessage.Type.REQUEST
|
|
||||||
return messageBuilder.build()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.d("Loki", "Failed to wrap envelope in web socket message: ${e.message}.")
|
|
||||||
throw Error.FailedToWrapEnvelopeInWebSocketMessage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Unwrapping
|
|
||||||
/**
|
|
||||||
* `data` shouldn't be base 64 encoded.
|
|
||||||
*/
|
|
||||||
fun unwrap(data: ByteArray): Envelope {
|
|
||||||
try {
|
|
||||||
val webSocketMessage = WebSocketMessage.parseFrom(data)
|
|
||||||
val envelopeAsData = webSocketMessage.request.body
|
|
||||||
return Envelope.parseFrom(envelopeAsData)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.d("Loki", "Failed to unwrap data: ${e.message}.")
|
|
||||||
throw Error.FailedToUnwrapData
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
package org.session.libsignal.service.loki.api
|
|
||||||
|
|
||||||
public class Snode(val address: String, val port: Int, val publicKeySet: KeySet?) {
|
|
||||||
|
|
||||||
val ip: String get() = address.removePrefix("https://")
|
|
||||||
|
|
||||||
enum class Method(val rawValue: String) {
|
|
||||||
/**
|
|
||||||
* Only supported by snode targets.
|
|
||||||
*/
|
|
||||||
GetSwarm("get_snodes_for_pubkey"),
|
|
||||||
/**
|
|
||||||
* Only supported by snode targets.
|
|
||||||
*/
|
|
||||||
GetMessages("retrieve"),
|
|
||||||
SendMessage("store")
|
|
||||||
}
|
|
||||||
|
|
||||||
data class KeySet(val ed25519Key: String, val x25519Key: String)
|
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
|
||||||
return if (other is Snode) {
|
|
||||||
address == other.address && port == other.port
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
return address.hashCode() xor port.hashCode()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String { return "$address:$port" }
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
package org.session.libsignal.service.loki.api.opengroups
|
|
||||||
|
|
||||||
import org.session.libsignal.utilities.JsonUtil
|
|
||||||
|
|
||||||
public data class PublicChat(
|
|
||||||
public val channel: Long,
|
|
||||||
private val serverURL: String,
|
|
||||||
public val displayName: String,
|
|
||||||
public val isDeletable: Boolean
|
|
||||||
) {
|
|
||||||
public val server get() = serverURL.toLowerCase()
|
|
||||||
public val id get() = getId(channel, server)
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
@JvmStatic fun getId(channel: Long, server: String): String {
|
|
||||||
return "$server.$channel"
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic fun fromJSON(jsonAsString: String): PublicChat? {
|
|
||||||
try {
|
|
||||||
val json = JsonUtil.fromJson(jsonAsString)
|
|
||||||
val channel = json.get("channel").asLong()
|
|
||||||
val server = json.get("server").asText().toLowerCase()
|
|
||||||
val displayName = json.get("displayName").asText()
|
|
||||||
val isDeletable = json.get("isDeletable").asBoolean()
|
|
||||||
return PublicChat(channel, server, displayName, isDeletable)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun toJSON(): Map<String, Any> {
|
|
||||||
return mapOf( "channel" to channel, "server" to server, "displayName" to displayName, "isDeletable" to isDeletable )
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
package org.session.libsignal.service.loki.api.opengroups
|
|
||||||
|
|
||||||
public data class PublicChatInfo (
|
|
||||||
public val displayName: String,
|
|
||||||
public val profilePictureURL: String,
|
|
||||||
public val memberCount: Int
|
|
||||||
)
|
|
@ -1,178 +0,0 @@
|
|||||||
package org.session.libsignal.service.loki.api.opengroups
|
|
||||||
|
|
||||||
import org.whispersystems.curve25519.Curve25519
|
|
||||||
import org.session.libsignal.utilities.logging.Log
|
|
||||||
import org.session.libsignal.utilities.Hex
|
|
||||||
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
|
|
||||||
|
|
||||||
public data class PublicChatMessage(
|
|
||||||
public val serverID: Long?,
|
|
||||||
public val senderPublicKey: String,
|
|
||||||
public val displayName: String,
|
|
||||||
public val body: String,
|
|
||||||
public val timestamp: Long,
|
|
||||||
public val type: String,
|
|
||||||
public val quote: Quote?,
|
|
||||||
public val attachments: List<Attachment>,
|
|
||||||
public val profilePicture: ProfilePicture?,
|
|
||||||
public val signature: Signature?,
|
|
||||||
public val serverTimestamp: Long
|
|
||||||
) {
|
|
||||||
|
|
||||||
// region Settings
|
|
||||||
companion object {
|
|
||||||
private val curve = Curve25519.getInstance(Curve25519.BEST)
|
|
||||||
private val signatureVersion: Long = 1
|
|
||||||
private val attachmentType = "net.app.core.oembed"
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Types
|
|
||||||
public data class ProfilePicture(
|
|
||||||
public val profileKey: ByteArray,
|
|
||||||
public val url: String
|
|
||||||
)
|
|
||||||
|
|
||||||
public data class Quote(
|
|
||||||
public val quotedMessageTimestamp: Long,
|
|
||||||
public val quoteePublicKey: String,
|
|
||||||
public val quotedMessageBody: String,
|
|
||||||
public val quotedMessageServerID: Long? = null
|
|
||||||
)
|
|
||||||
|
|
||||||
public data class Signature(
|
|
||||||
public val data: ByteArray,
|
|
||||||
public val version: Long
|
|
||||||
)
|
|
||||||
|
|
||||||
public data class Attachment(
|
|
||||||
public val kind: Kind,
|
|
||||||
public val server: String,
|
|
||||||
public val serverID: Long,
|
|
||||||
public val contentType: String,
|
|
||||||
public val size: Int,
|
|
||||||
public val fileName: String,
|
|
||||||
public val flags: Int,
|
|
||||||
public val width: Int,
|
|
||||||
public val height: Int,
|
|
||||||
public val caption: String?,
|
|
||||||
public val url: String,
|
|
||||||
/**
|
|
||||||
Guaranteed to be non-`nil` if `kind` is `LinkPreview`.
|
|
||||||
*/
|
|
||||||
public val linkPreviewURL: String?,
|
|
||||||
/**
|
|
||||||
Guaranteed to be non-`nil` if `kind` is `LinkPreview`.
|
|
||||||
*/
|
|
||||||
public val linkPreviewTitle: String?
|
|
||||||
) {
|
|
||||||
public val dotNetAPIType = when {
|
|
||||||
contentType.startsWith("image") -> "photo"
|
|
||||||
contentType.startsWith("video") -> "video"
|
|
||||||
contentType.startsWith("audio") -> "audio"
|
|
||||||
else -> "other"
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum class Kind(val rawValue: String) {
|
|
||||||
Attachment("attachment"), LinkPreview("preview")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Initialization
|
|
||||||
constructor(hexEncodedPublicKey: String, displayName: String, body: String, timestamp: Long, type: String, quote: Quote?, attachments: List<Attachment>)
|
|
||||||
: this(null, hexEncodedPublicKey, displayName, body, timestamp, type, quote, attachments, null, null, 0)
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Crypto
|
|
||||||
internal fun sign(privateKey: ByteArray): PublicChatMessage? {
|
|
||||||
val data = getValidationData(signatureVersion)
|
|
||||||
if (data == null) {
|
|
||||||
Log.d("Loki", "Failed to sign public chat message.")
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
val signatureData = curve.calculateSignature(privateKey, data)
|
|
||||||
val signature = Signature(signatureData, signatureVersion)
|
|
||||||
return copy(signature = signature)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.d("Loki", "Failed to sign public chat message due to error: ${e.message}.")
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun hasValidSignature(): Boolean {
|
|
||||||
if (signature == null) { return false }
|
|
||||||
val data = getValidationData(signature.version) ?: return false
|
|
||||||
val publicKey = Hex.fromStringCondensed(senderPublicKey.removing05PrefixIfNeeded())
|
|
||||||
try {
|
|
||||||
return curve.verifySignature(publicKey, data, signature.data)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.d("Loki", "Failed to verify public chat message due to error: ${e.message}.")
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Parsing
|
|
||||||
internal fun toJSON(): Map<String, Any> {
|
|
||||||
val value = mutableMapOf<String, Any>( "timestamp" to timestamp )
|
|
||||||
if (quote != null) {
|
|
||||||
value["quote"] = mapOf( "id" to quote.quotedMessageTimestamp, "author" to quote.quoteePublicKey, "text" to quote.quotedMessageBody )
|
|
||||||
}
|
|
||||||
if (signature != null) {
|
|
||||||
value["sig"] = Hex.toStringCondensed(signature.data)
|
|
||||||
value["sigver"] = signature.version
|
|
||||||
}
|
|
||||||
val annotation = mapOf( "type" to type, "value" to value )
|
|
||||||
val annotations = mutableListOf( annotation )
|
|
||||||
attachments.forEach { attachment ->
|
|
||||||
val attachmentValue = mutableMapOf(
|
|
||||||
// Fields required by the .NET API
|
|
||||||
"version" to 1,
|
|
||||||
"type" to attachment.dotNetAPIType,
|
|
||||||
// Custom fields
|
|
||||||
"lokiType" to attachment.kind.rawValue,
|
|
||||||
"server" to attachment.server,
|
|
||||||
"id" to attachment.serverID,
|
|
||||||
"contentType" to attachment.contentType,
|
|
||||||
"size" to attachment.size,
|
|
||||||
"fileName" to attachment.fileName,
|
|
||||||
"flags" to attachment.flags,
|
|
||||||
"width" to attachment.width,
|
|
||||||
"height" to attachment.height,
|
|
||||||
"url" to attachment.url
|
|
||||||
)
|
|
||||||
if (attachment.caption != null) { attachmentValue["caption"] = attachment.caption }
|
|
||||||
if (attachment.linkPreviewURL != null) { attachmentValue["linkPreviewUrl"] = attachment.linkPreviewURL }
|
|
||||||
if (attachment.linkPreviewTitle != null) { attachmentValue["linkPreviewTitle"] = attachment.linkPreviewTitle }
|
|
||||||
val attachmentAnnotation = mapOf( "type" to attachmentType, "value" to attachmentValue )
|
|
||||||
annotations.add(attachmentAnnotation)
|
|
||||||
}
|
|
||||||
val result = mutableMapOf( "text" to body, "annotations" to annotations )
|
|
||||||
if (quote?.quotedMessageServerID != null) {
|
|
||||||
result["reply_to"] = quote.quotedMessageServerID
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Convenience
|
|
||||||
private fun getValidationData(signatureVersion: Long): ByteArray? {
|
|
||||||
var string = "${body.trim()}$timestamp"
|
|
||||||
if (quote != null) {
|
|
||||||
string += "${quote.quotedMessageTimestamp}${quote.quoteePublicKey}${quote.quotedMessageBody.trim()}"
|
|
||||||
if (quote.quotedMessageServerID != null) {
|
|
||||||
string += "${quote.quotedMessageServerID}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
string += attachments.sortedBy { it.serverID }.map { it.serverID }.joinToString("")
|
|
||||||
string += "$signatureVersion"
|
|
||||||
try {
|
|
||||||
return string.toByteArray(Charsets.UTF_8)
|
|
||||||
} catch (exception: Exception) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// endregion
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package org.session.libsignal.service.loki.api.utilities
|
|
||||||
|
|
||||||
import javax.crypto.Cipher
|
|
||||||
import javax.crypto.spec.GCMParameterSpec
|
|
||||||
import javax.crypto.spec.SecretKeySpec
|
|
||||||
|
|
||||||
internal object DecryptionUtilities {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sync. Don't call from the main thread.
|
|
||||||
*/
|
|
||||||
internal fun decryptUsingAESGCM(ivAndCiphertext: ByteArray, symmetricKey: ByteArray): ByteArray {
|
|
||||||
val iv = ivAndCiphertext.sliceArray(0 until EncryptionUtilities.ivSize)
|
|
||||||
val ciphertext = ivAndCiphertext.sliceArray(EncryptionUtilities.ivSize until ivAndCiphertext.count())
|
|
||||||
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
|
|
||||||
cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(symmetricKey, "AES"), GCMParameterSpec(EncryptionUtilities.gcmTagSize, iv))
|
|
||||||
return cipher.doFinal(ciphertext)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,45 +0,0 @@
|
|||||||
package org.session.libsignal.service.loki.api.utilities
|
|
||||||
|
|
||||||
import org.whispersystems.curve25519.Curve25519
|
|
||||||
import org.session.libsignal.libsignal.util.ByteUtil
|
|
||||||
import org.session.libsignal.utilities.Hex
|
|
||||||
import org.session.libsignal.service.internal.util.Util
|
|
||||||
import javax.crypto.Cipher
|
|
||||||
import javax.crypto.Mac
|
|
||||||
import javax.crypto.spec.GCMParameterSpec
|
|
||||||
import javax.crypto.spec.SecretKeySpec
|
|
||||||
|
|
||||||
internal data class EncryptionResult(
|
|
||||||
internal val ciphertext: ByteArray,
|
|
||||||
internal val symmetricKey: ByteArray,
|
|
||||||
internal val ephemeralPublicKey: ByteArray
|
|
||||||
)
|
|
||||||
|
|
||||||
internal object EncryptionUtilities {
|
|
||||||
internal val gcmTagSize = 128
|
|
||||||
internal val ivSize = 12
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sync. Don't call from the main thread.
|
|
||||||
*/
|
|
||||||
internal fun encryptUsingAESGCM(plaintext: ByteArray, symmetricKey: ByteArray): ByteArray {
|
|
||||||
val iv = Util.getSecretBytes(ivSize)
|
|
||||||
val cipher = Cipher.getInstance("AES/GCM/NoPadding")
|
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(symmetricKey, "AES"), GCMParameterSpec(gcmTagSize, iv))
|
|
||||||
return ByteUtil.combine(iv, cipher.doFinal(plaintext))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sync. Don't call from the main thread.
|
|
||||||
*/
|
|
||||||
internal fun encryptForX25519PublicKey(plaintext: ByteArray, hexEncodedX25519PublicKey: String): EncryptionResult {
|
|
||||||
val x25519PublicKey = Hex.fromStringCondensed(hexEncodedX25519PublicKey)
|
|
||||||
val ephemeralKeyPair = Curve25519.getInstance(Curve25519.BEST).generateKeyPair()
|
|
||||||
val ephemeralSharedSecret = Curve25519.getInstance(Curve25519.BEST).calculateAgreement(x25519PublicKey, ephemeralKeyPair.privateKey)
|
|
||||||
val mac = Mac.getInstance("HmacSHA256")
|
|
||||||
mac.init(SecretKeySpec("LOKI".toByteArray(), "HmacSHA256"))
|
|
||||||
val symmetricKey = mac.doFinal(ephemeralSharedSecret)
|
|
||||||
val ciphertext = encryptUsingAESGCM(plaintext, symmetricKey)
|
|
||||||
return EncryptionResult(ciphertext, symmetricKey, ephemeralKeyPair.publicKey)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +1,7 @@
|
|||||||
package org.session.libsignal.service.loki.database
|
package org.session.libsignal.service.loki.database
|
||||||
|
|
||||||
import org.session.libsignal.libsignal.ecc.ECKeyPair
|
import org.session.libsignal.libsignal.ecc.ECKeyPair
|
||||||
import org.session.libsignal.service.loki.api.Snode
|
import org.session.libsignal.service.loki.Snode
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
interface LokiAPIDatabaseProtocol {
|
interface LokiAPIDatabaseProtocol {
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
package org.session.libsignal.service.loki.database
|
|
||||||
|
|
||||||
import org.session.libsignal.service.loki.api.opengroups.PublicChat
|
|
||||||
|
|
||||||
interface LokiThreadDatabaseProtocol {
|
|
||||||
|
|
||||||
fun getThreadID(publicKey: String): Long
|
|
||||||
fun getPublicChat(threadID: Long): PublicChat?
|
|
||||||
fun setPublicChat(publicChat: PublicChat, threadID: Long)
|
|
||||||
fun removePublicChat(threadID: Long)
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
package org.session.libsignal.service.loki.utilities
|
|
||||||
|
|
||||||
public object TTLUtilities {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If a message type specifies an invalid TTL, this will be used.
|
|
||||||
*/
|
|
||||||
public val fallbackMessageTTL = 2 * 24 * 60 * 60 * 1000
|
|
||||||
|
|
||||||
public enum class MessageType {
|
|
||||||
// Unimportant control messages
|
|
||||||
Address, Call, TypingIndicator, Verified,
|
|
||||||
// Somewhat important control messages
|
|
||||||
DeviceLink,
|
|
||||||
// Important control messages
|
|
||||||
ClosedGroupUpdate, Ephemeral, SessionRequest, Receipt, Sync, DeviceUnlinkingRequest,
|
|
||||||
// Visible messages
|
|
||||||
Regular
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
public fun getTTL(messageType: MessageType): Int {
|
|
||||||
val minuteInMs = 60 * 1000
|
|
||||||
val hourInMs = 60 * minuteInMs
|
|
||||||
val dayInMs = 24 * hourInMs
|
|
||||||
return when (messageType) {
|
|
||||||
// Unimportant control messages
|
|
||||||
MessageType.Address, MessageType.Call, MessageType.TypingIndicator, MessageType.Verified -> 20 * 1000
|
|
||||||
// Somewhat important control messages
|
|
||||||
MessageType.DeviceLink -> 1 * hourInMs
|
|
||||||
// Important control messages
|
|
||||||
MessageType.ClosedGroupUpdate, MessageType.Ephemeral, MessageType.SessionRequest, MessageType.Receipt,
|
|
||||||
MessageType.Sync, MessageType.DeviceUnlinkingRequest -> 2 * dayInMs - 1 * hourInMs
|
|
||||||
// Visible messages
|
|
||||||
MessageType.Regular -> 2 * dayInMs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user