From 118447799a882bc71294e1fb4ee88659d659a21e Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Fri, 23 Jul 2021 14:09:27 +1000 Subject: [PATCH 1/2] Handle incorrect clock setting --- app/build.gradle | 4 ++-- .../messaging/open_groups/OpenGroupAPIV2.kt | 4 ++-- .../sending_receiving/MessageSender.kt | 3 ++- .../libsession/snode/OnionRequestAPI.kt | 19 +++++++++++-------- .../org/session/libsession/snode/SnodeAPI.kt | 6 +++++- 5 files changed, 22 insertions(+), 14 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 1349147184..1050057fb5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -143,8 +143,8 @@ dependencies { testImplementation 'org.robolectric:shadows-multidex:4.2' } -def canonicalVersionCode = 206 -def canonicalVersionName = "1.11.5" +def canonicalVersionCode = 207 +def canonicalVersionName = "1.11.6" def postFixSize = 10 def abiPostFix = ['armeabi-v7a' : 1, diff --git a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupAPIV2.kt b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupAPIV2.kt index c6f335472c..6fcc926975 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupAPIV2.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupAPIV2.kt @@ -95,7 +95,7 @@ object OpenGroupAPIV2 { return RequestBody.create(MediaType.get("application/json"), parametersAsJSON) } - private fun send(request: Request, isJsonRequired: Boolean = true): Promise, Exception> { + private fun send(request: Request): Promise, Exception> { val url = HttpUrl.parse(request.server) ?: return Promise.ofFail(Error.InvalidURL) val urlBuilder = HttpUrl.Builder() .scheme(url.scheme()) @@ -127,7 +127,7 @@ object OpenGroupAPIV2 { if (request.useOnionRouting) { val publicKey = MessagingModuleConfiguration.shared.storage.getOpenGroupPublicKey(request.server) ?: return Promise.ofFail(Error.NoPublicKey) - return OnionRequestAPI.sendOnionRequest(requestBuilder.build(), request.server, publicKey, isJSONRequired = isJsonRequired).fail { e -> + return OnionRequestAPI.sendOnionRequest(requestBuilder.build(), request.server, publicKey).fail { e -> // A 401 means that we didn't provide a (valid) auth token for a route that required one. We use this as an // indication that the token we're using has expired. Note that a 403 has a different meaning; it means that // we provided a valid token but it doesn't have a high enough permission level for the route in question. diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt index 1b1edf2473..437ef86a61 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt @@ -146,7 +146,8 @@ object MessageSender { } val base64EncodedData = Base64.encodeBytes(wrappedMessage) // Send the result - val snodeMessage = SnodeMessage(message.recipient!!, base64EncodedData, message.ttl, message.sentTimestamp!!) + val timestamp = message.sentTimestamp!! + SnodeAPI.clockOffset + val snodeMessage = SnodeMessage(message.recipient!!, base64EncodedData, message.ttl, timestamp) if (destination is Destination.Contact && message is VisibleMessage && !isSelfSend) { SnodeModule.shared.broadcaster.broadcast("sendingMessage", message.sentTimestamp!!) } diff --git a/libsession/src/main/java/org/session/libsession/snode/OnionRequestAPI.kt b/libsession/src/main/java/org/session/libsession/snode/OnionRequestAPI.kt index 2fd82336e1..5a5256c10f 100644 --- a/libsession/src/main/java/org/session/libsession/snode/OnionRequestAPI.kt +++ b/libsession/src/main/java/org/session/libsession/snode/OnionRequestAPI.kt @@ -20,6 +20,8 @@ import org.session.libsignal.crypto.getRandomElementOrNull import org.session.libsignal.utilities.Broadcaster import org.session.libsignal.utilities.HTTP import org.session.libsignal.database.LokiAPIDatabaseProtocol +import java.util.* +import kotlin.math.abs private typealias Path = List @@ -306,7 +308,7 @@ object OnionRequestAPI { /** * Sends an onion request to `destination`. Builds new paths as needed. */ - private fun sendOnionRequest(destination: Destination, payload: Map<*, *>, isJSONRequired: Boolean = true): Promise, Exception> { + private fun sendOnionRequest(destination: Destination, payload: Map<*, *>): Promise, Exception> { val deferred = deferred, Exception>() lateinit var guardSnode: Snode buildOnionForDestination(payload, destination).success { result -> @@ -347,11 +349,12 @@ object OnionRequestAPI { body = json["body"] as Map<*, *> } else { val bodyAsString = json["body"] as String - if (!isJSONRequired) { - body = mapOf( "result" to bodyAsString ) - } else { - body = JsonUtil.fromJson(bodyAsString, Map::class.java) - } + body = JsonUtil.fromJson(bodyAsString, Map::class.java) + } + if (body["t"] != null) { + val timestamp = body["t"] as Long + val offset = timestamp - Date().time + SnodeAPI.clockOffset = offset } if (statusCode != 200) { val exception = HTTPRequestFailedAtDestinationException(statusCode, body, destination.description) @@ -455,7 +458,7 @@ object OnionRequestAPI { * * `publicKey` is the hex encoded public key of the user the call is associated with. This is needed for swarm cache maintenance. */ - fun sendOnionRequest(request: Request, server: String, x25519PublicKey: String, target: String = "/loki/v3/lsrpc", isJSONRequired: Boolean = true): Promise, Exception> { + fun sendOnionRequest(request: Request, server: String, x25519PublicKey: String, target: String = "/loki/v3/lsrpc"): Promise, Exception> { val headers = request.getHeadersForOnionRequest() val url = request.url() val urlAsString = url.toString() @@ -472,7 +475,7 @@ object OnionRequestAPI { "headers" to headers ) val destination = Destination.Server(host, target, x25519PublicKey, url.scheme(), url.port()) - return sendOnionRequest(destination, payload, isJSONRequired).recover { exception -> + return sendOnionRequest(destination, payload).recover { exception -> Log.d("Loki", "Couldn't reach server: $urlAsString due to error: $exception.") throw exception } diff --git a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt index 379e2a1638..b373aa916e 100644 --- a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt +++ b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt @@ -28,7 +28,6 @@ import kotlin.Pair object SnodeAPI { private val sodium by lazy { LazySodiumAndroid(SodiumAndroid()) } - private val database: LokiAPIDatabaseProtocol get() = SnodeModule.shared.storage private val broadcaster: Broadcaster @@ -38,6 +37,11 @@ object SnodeAPI { internal var snodePool: Set get() = database.getSnodePool() set(newValue) { database.setSnodePool(newValue) } + /** + * The offset between the user's clock and the Service Node's clock. Used in cases where the + * user's clock is incorrect. + */ + internal var clockOffset = 0L // Settings private val maxRetryCount = 6 From 3c6b1eff4448b06a0b2bc001dbf912975641ddb6 Mon Sep 17 00:00:00 2001 From: Harris Date: Fri, 23 Jul 2021 16:04:18 +1000 Subject: [PATCH 2/2] fix: open groups update the sent timestamp locally to be the returned group --- .../securesms/database/MmsDatabase.java | 9 +++++++++ .../securesms/database/SmsDatabase.java | 11 ++++++++-- .../securesms/database/Storage.kt | 20 ++++++++++++++++--- .../libsession/database/StorageProtocol.kt | 1 + .../sending_receiving/MessageSender.kt | 16 +++++++++------ 5 files changed, 46 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java index ef68a39cea..38e9eeccce 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -281,6 +281,15 @@ public class MmsDatabase extends MessagingDatabase { } } + public void updateSentTimestamp(long messageId, long newTimestamp, long threadId) { + SQLiteDatabase db = databaseHelper.getWritableDatabase(); + db.execSQL("UPDATE " + TABLE_NAME + " SET " + DATE_SENT + " = ? " + + "WHERE " + ID + " = ?", + new String[] {newTimestamp + "", messageId + ""}); + notifyConversationListeners(threadId); + notifyConversationListListeners(); + } + public long getThreadIdForMessage(long id) { String sql = "SELECT " + THREAD_ID + " FROM " + TABLE_NAME + " WHERE " + ID + " = ?"; String[] sqlArgs = new String[] {id+""}; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java index f66479c41f..c148f8ebcc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -20,8 +20,6 @@ package org.thoughtcrime.securesms.database; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; -import android.os.Handler; -import android.os.Looper; import android.text.TextUtils; import android.util.Pair; @@ -320,6 +318,15 @@ public class SmsDatabase extends MessagingDatabase { return results; } + public void updateSentTimestamp(long messageId, long newTimestamp, long threadId) { + SQLiteDatabase db = databaseHelper.getWritableDatabase(); + db.execSQL("UPDATE " + TABLE_NAME + " SET " + DATE_SENT + " = ? " + + "WHERE " + ID + " = ?", + new String[] {newTimestamp + "", messageId + ""}); + notifyConversationListeners(threadId); + notifyConversationListListeners(); + } + public Pair updateBundleMessageBody(long messageId, String body) { long type = Types.BASE_INBOX_TYPE | Types.SECURE_MESSAGE_BIT | Types.PUSH_MESSAGE_BIT; return updateMessageBodyAndType(messageId, body, Types.TOTAL_MASK, type); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index b76610e815..fe9327a13e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -27,12 +27,11 @@ import org.session.libsignal.messages.SignalServiceGroup import org.session.libsignal.utilities.KeyHelper import org.session.libsignal.utilities.guava.Optional import org.thoughtcrime.securesms.ApplicationContext -import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper -import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob import org.thoughtcrime.securesms.groups.OpenGroupManager -import org.thoughtcrime.securesms.util.SessionMetaProtocol +import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob import org.thoughtcrime.securesms.mms.PartAuthority +import org.thoughtcrime.securesms.util.SessionMetaProtocol class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), StorageProtocol { @@ -284,6 +283,21 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, return database.getMessageFor(timestamp, address)?.getId() } + override fun updateSentTimestamp( + messageID: Long, + isMms: Boolean, + openGroupSentTimestamp: Long, + threadId: Long + ) { + if (isMms) { + val mmsDb = DatabaseFactory.getMmsDatabase(context) + mmsDb.updateSentTimestamp(messageID, openGroupSentTimestamp, threadId) + } else { + val smsDb = DatabaseFactory.getSmsDatabase(context) + smsDb.updateSentTimestamp(messageID, openGroupSentTimestamp, threadId) + } + } + override fun markAsSent(timestamp: Long, author: String) { val database = DatabaseFactory.getMmsSmsDatabase(context) val messageRecord = database.getMessageFor(timestamp, author) ?: return diff --git a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt index 137a49f73d..bc7324d21a 100644 --- a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -88,6 +88,7 @@ interface StorageProtocol { fun persistAttachments(messageID: Long, attachments: List): List fun getAttachmentsForMessage(messageID: Long): List fun getMessageIdInDatabase(timestamp: Long, author: String): Long? // TODO: This is a weird name + fun updateSentTimestamp(messageID: Long, isMms: Boolean, openGroupSentTimestamp: Long, threadId: Long) fun markAsSending(timestamp: Long, author: String) fun markAsSent(timestamp: Long, author: String) fun markUnidentified(timestamp: Long, author: String) diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt index 1b1edf2473..c7faefe333 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt @@ -13,20 +13,19 @@ import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate import org.session.libsession.messaging.messages.visible.* import org.session.libsession.messaging.open_groups.* -import org.session.libsession.utilities.Address import org.session.libsession.messaging.utilities.MessageWrapper import org.session.libsession.snode.RawResponsePromise import org.session.libsession.snode.SnodeAPI -import org.session.libsession.snode.SnodeModule import org.session.libsession.snode.SnodeMessage +import org.session.libsession.snode.SnodeModule +import org.session.libsession.utilities.Address import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.SSKEnvironment import org.session.libsignal.crypto.PushTransportDetails import org.session.libsignal.protos.SignalServiceProtos -import org.session.libsignal.utilities.hexEncodedPublicKey import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.Log -import java.lang.IllegalStateException +import org.session.libsignal.utilities.hexEncodedPublicKey import org.session.libsession.messaging.sending_receiving.attachments.Attachment as SignalAttachment import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview as SignalLinkPreview import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel as SignalQuote @@ -234,7 +233,7 @@ object MessageSender { ) OpenGroupAPIV2.send(openGroupMessage,room,server).success { message.openGroupServerMessageID = it.serverID - handleSuccessfulMessageSend(message, destination) + handleSuccessfulMessageSend(message, destination, openGroupSentTimestamp = it.sentTimestamp) deferred.resolve(Unit) }.fail { handleFailure(it) @@ -248,12 +247,17 @@ object MessageSender { } // Result Handling - fun handleSuccessfulMessageSend(message: Message, destination: Destination, isSyncMessage: Boolean = false) { + fun handleSuccessfulMessageSend(message: Message, destination: Destination, isSyncMessage: Boolean = false, openGroupSentTimestamp: Long = -1) { val storage = MessagingModuleConfiguration.shared.storage val userPublicKey = storage.getUserPublicKey()!! val messageID = storage.getMessageIdInDatabase(message.sentTimestamp!!, message.sender?:userPublicKey) ?: return // Ignore future self-sends storage.addReceivedMessageTimestamp(message.sentTimestamp!!) + if (openGroupSentTimestamp != -1L && message is VisibleMessage) { + storage.addReceivedMessageTimestamp(openGroupSentTimestamp) + storage.updateSentTimestamp(messageID, message.isMediaMessage(), openGroupSentTimestamp, message.threadID!!) + message.sentTimestamp = openGroupSentTimestamp + } // Track the open group server message ID if (message.openGroupServerMessageID != null && destination is Destination.OpenGroupV2) { val encoded = GroupUtil.getEncodedOpenGroupID("${destination.server}.${destination.room}".toByteArray())